import { Hono } from 'hono' import { type Hex, Secp256k1 } from 'ox' import { TxEnvelopeTempo } from 'ox/tempo' import * as z from 'zod/mini' import * as Db from '../../db/Db.js' import * as Organizations from '../../db/tables/organizations.js' import * as Projects from '../../db/tables/projects.js' import * as RoutesDeposits from '../../db/tables/routesDeposits.js' import * as RoutesTransfers from '../../db/tables/routesTransfers.js' import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js' import * as Cursor from '../../internal/Cursor.js' import * as Fees from '../../internal/Fees.js' import * as OpenApi from '../../internal/OpenApi.js' import * as Deposit from '../../internal/routes/Deposit.js' import * as Provider from '../../internal/routes/Provider.js' import * as Transfer from '../../internal/routes/Transfer.js' import * as Response from '../../internal/Response.js' import * as Schema from '../../internal/Schema.js' import * as Value from '../../internal/Value.js' import type * as App from '../App.js' const timestamp = z .string() .check(z.describe('An ISO 8601 timestamp.'), z.meta({ examples: ['2026-09-03T00:00:00.000Z'] })) const timestampOrNull = z .nullable(timestamp) .check(z.describe('An ISO 8601 timestamp, or null when unavailable.')) const attribution = { organization: z .object({ id: z .string() .check(z.describe('Owning organization id.'), z.meta({ examples: ['org_acme'] })), name: z .nullable(z.string()) .check(z.describe('Organization name, or null when unavailable.'), z.meta({ examples: ['Acme'] })), // prettier-ignore }) .check(z.describe('Organization that owns the subsidy.')), project: z .nullable( z.object({ id: z .string() .check(z.describe('Attributed project id.'), z.meta({ examples: ['prj_checkout'] })), name: z .nullable(z.string()) .check(z.describe('Project name, or null when unavailable.'), z.meta({ examples: ['Checkout'] })), // prettier-ignore }), ) .check(z.describe('Attributed project, or null for organization-scoped subsidies.')), } const listQuery = z .strictObject({ cursor: Schema.Cursor, limit: Schema.Limit }) .check(z.describe('Pagination for subsidy records.')) const transactionReference = z .object({ chainId: z .string() .check(z.describe('Chain containing the transaction.'), z.meta({ examples: ['eip155:8453'] })), // prettier-ignore transactionRef: z .string() .check(z.describe('Subsidy transaction hash.'), z.meta({ examples: [`0x${'aa'.repeat(32)}`] })), // prettier-ignore }) .check(z.describe('Transaction that supplied the route subsidy.')) /** Zod schemas owned by the admin subsidies handler. */ export namespace schema { /** One managed fee-payer transaction. */ export const Transaction = Schema.describe( z.object({ apiKeyId: z .string() .check( z.describe('API key that requested sponsorship.'), z.meta({ examples: ['key_abc'] }), ), billable: z .boolean() .check(z.describe('Whether the sponsorship accrues billable spend.'), z.meta({ examples: [true] })), // prettier-ignore chainId: Schema.ChainId, createdAt: timestamp, environment: z .enum(['production', 'sandbox']) .check(z.describe('API key environment.'), z.meta({ examples: ['production'] })), feeAmount: z .nullable(Schema.TokenAmount) .check(z.describe('Actual fee paid, or null until finalized.')), feeMaximum: z .nullable(Schema.TokenAmount) .check(z.describe('Maximum committed fee, or null when unavailable.')), feeToken: z .nullable(z.string()) .check(z.describe('Fee token address, or null when the chain default applied.'), z.meta({ examples: [`0x${'20'.repeat(20)}`] })), // prettier-ignore finalizedAt: timestampOrNull, id: z .string() .check(z.describe('Sponsored transaction id.'), z.meta({ examples: ['stx_abc'] })), meterReportedAt: timestampOrNull, ...attribution, sponsorshipAttributionId: z .nullable(z.string()) .check(z.describe('Promotional sponsorship attribution id, or null.'), z.meta({ examples: ['sat_abc'] })), // prettier-ignore status: z .enum(['failed', 'finalized', 'pending']) .check(z.describe('Sponsorship lifecycle status.'), z.meta({ examples: ['finalized'] })), subsidizerAccount: z .nullable(Schema.Address) .check(z.describe('Account that paid the transaction fee, or null when unavailable.')), transactionHash: z .nullable(z.string()) .check(z.describe('Sponsored transaction hash, or null while unresolved.'), z.meta({ examples: [`0x${'aa'.repeat(32)}`] })), // prettier-ignore }), 'One transaction handled by the managed fee payer.', ) /** One inbound deposit or outbound transfer with recorded subsidies. */ export const Route = Schema.describe( z.object({ createdAt: timestamp, destinationChain: Provider.schema.ChainRef, destinationToken: Provider.schema.TokenRef, direction: z .enum(['inbound', 'outbound']) .check(z.describe('Direction of the subsidized route.'), z.meta({ examples: ['outbound'] })), // prettier-ignore environment: z .enum(['production', 'sandbox']) .check(z.describe('Route environment.'), z.meta({ examples: ['production'] })), id: z.string().check(z.describe('Route resource id.'), z.meta({ examples: ['rtr_abc'] })), meterReportedAt: timestampOrNull, ...attribution, provider: Provider.schema.ProviderRef, recipient: Schema.Address, sourceChain: Provider.schema.ChainRef, sourceToken: Provider.schema.TokenRef, status: z .union([Deposit.schema.Status, Transfer.schema.Status]) .check( z.describe('Lifecycle status of the route resource.'), z.meta({ examples: ['completed'] }), ), subsidies: z .readonly(z.array(Provider.schema.Subsidy)) .check(z.minLength(1), z.describe('Positive subsidies recorded for this route.')), subsidizerAccount: z .nullable(Schema.Address) .check(z.describe('Account that paid the route subsidy, or null when unavailable.')), subsidyTransaction: z .nullable(transactionReference) .check(z.describe('Route subsidy transaction, or null when unavailable.')), updatedAt: timestamp, }), 'One inbound deposit or outbound transfer with provider or Tempo subsidy spend.', ) /** Schemas for listing managed fee-payer transactions. */ export namespace listTransactions { /** Transaction pagination. */ export const Query = listQuery /** Paginated managed fee-payer transactions. */ export const Response = Schema.describe( z.object({ data: z.array(Transaction).check(z.describe('Managed fee-payer transactions.')), nextCursor: Schema.NextCursor, }), 'A page of managed fee-payer transactions.', ) } /** Schemas for listing route subsidies. */ export namespace listRoutes { /** Route subsidy pagination. */ export const Query = listQuery /** Paginated route subsidies. */ export const Response = Schema.describe( z.object({ data: z.array(Route).check(z.describe('Route resources with recorded subsidies.')), nextCursor: Schema.NextCursor, }), 'A page of route resources with recorded subsidies.', ) } } /** Read-only subsidy operations for authenticated administrators. */ export function subsidies() { return new Hono() .get( '/transactions', OpenApi.validate('query', schema.listTransactions.Query, { code: 'query_invalid', message: 'Invalid query parameters', }), OpenApi.describeRoute({ operationId: 'listAdminSubsidyTransactions', responses: OpenApi.responses({ success: { description: 'Managed fee-payer transactions.', schema: schema.listTransactions.Response, }, }), summary: 'List subsidy transactions', tags: ['Subsidies'], }), async (c) => { const db = Db.get(c.get('db')) const { cursor, limit } = c.req.valid('query') const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined const records = await SponsoredTransactions.list(db, { ...(decoded ? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } } : {}), // prettier-ignore limit: limit + 1, }) const page = Cursor.paginate({ key: (record) => [record.createdAt, record.id], limit, rows: records, }) const attribution = await loadAttribution(db, page.rows) return c.json( Response.validated(schema.listTransactions.Response, { data: page.rows.map((record) => serializeTransaction(record, attribution)), nextCursor: page.nextCursor, }), 200, ) }, ) .get( '/routes', OpenApi.validate('query', schema.listRoutes.Query, { code: 'query_invalid', message: 'Invalid query parameters', }), OpenApi.describeRoute({ operationId: 'listAdminRouteSubsidies', responses: OpenApi.responses({ success: { description: 'Route resources with recorded subsidies.', schema: schema.listRoutes.Response, }, }), summary: 'List route subsidies', tags: ['Subsidies'], }), async (c) => { const db = Db.get(c.get('db')) const { cursor, limit } = c.req.valid('query') const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined const recordCursor = decoded ? { id: String(decoded[1]), updatedAt: String(decoded[0]) } : undefined const [deposits, transfers] = await Promise.all([ RoutesDeposits.list(db, { ...(recordCursor ? { cursor: recordCursor } : {}), hasSubsidy: true, limit: limit + 1, }), RoutesTransfers.list(db, { ...(recordCursor ? { cursor: recordCursor } : {}), hasSubsidy: true, limit: limit + 1, }), ]) const records = [ ...deposits.map((record) => ({ direction: 'inbound' as const, record })), ...transfers.map((record) => ({ direction: 'outbound' as const, record })), ].sort(compareRoutes) const page = Cursor.paginate({ key: ({ record }) => [record.updatedAt, record.id], limit, rows: records, }) const attribution = await loadAttribution( db, page.rows.map(({ record }) => record), ) return c.json( Response.validated(schema.listRoutes.Response, { data: page.rows.map(({ direction, record }) => direction === 'inbound' ? serializeDeposit(record, attribution) : serializeTransfer(record, attribution), ), nextCursor: page.nextCursor, }), 200, ) }, ) } type OwnedRecord = { orgId: string projectId: string | null } type Attribution = { organizations: Map projects: Map } async function loadAttribution(db: Db.Db, records: readonly OwnedRecord[]): Promise { const orgIds = [...new Set(records.map((record) => record.orgId))] const projectIds = [ ...new Set(records.flatMap((record) => (record.projectId ? [record.projectId] : []))), ] const [organizations, projects] = await Promise.all([ Organizations.listByIds(db, orgIds), Projects.listByIds(db, projectIds), ]) return { organizations: new Map(organizations.map((record) => [record.id, record])), projects: new Map(projects.map((record) => [record.id, record])), } } function serializeAttribution(record: OwnedRecord, attribution: Attribution) { const project = record.projectId ? attribution.projects.get(record.projectId) : undefined return { organization: { id: record.orgId, name: attribution.organizations.get(record.orgId)?.name ?? null, }, project: record.projectId ? { id: record.projectId, name: project?.name ?? null } : null, } } function amount(baseUnits: string | null, currency: string | null) { if (baseUnits === null) return null return Value.tokenAmount({ baseUnits, currency: currency?.toUpperCase() ?? 'UNKNOWN', decimals: Fees.tokenDecimals, }) } function serializeTransaction(record: SponsoredTransactions.Record, attribution: Attribution) { return { apiKeyId: record.apiKeyId, billable: record.billable, chainId: record.chainId, createdAt: record.createdAt, environment: record.environment, feeAmount: amount(record.feeAmount, record.currency), feeMaximum: amount(record.feeMax, record.currency), feeToken: record.feeToken, finalizedAt: record.finalizedAt, id: record.id, meterReportedAt: record.meterReportedAt, ...serializeAttribution(record, attribution), sponsorshipAttributionId: record.sponsorshipAttributionId, status: record.status, subsidizerAccount: transactionSubsidizer(record), transactionHash: record.transactionHash, } } function serializeDeposit(record: RoutesDeposits.Record, attribution: Attribution) { const { destinationChain, destinationToken, provider, sourceChain, sourceToken } = record.snapshot return { createdAt: record.createdAt, destinationChain, destinationToken, direction: 'inbound' as const, environment: record.environment, id: record.id, meterReportedAt: record.subsidyMeterReportedAt, ...serializeAttribution(record, attribution), provider, recipient: record.snapshot.recipient, sourceChain, sourceToken, status: record.status, subsidies: RoutesDeposits.subsidies(record).filter( (subsidy) => BigInt(subsidy.amount.baseUnits) > 0n, ), subsidizerAccount: depositSubsidizer(record), subsidyTransaction: record.settlementTransactionHash ? { chainId: record.snapshot.destinationChain.id, transactionRef: record.settlementTransactionHash, } : null, updatedAt: record.updatedAt, } } function serializeTransfer(record: RoutesTransfers.Record, attribution: Attribution) { const { destinationChain, destinationToken, provider, sourceChain, sourceToken } = record.snapshot return { createdAt: record.createdAt, destinationChain, destinationToken, direction: 'outbound' as const, environment: record.environment, id: record.id, meterReportedAt: record.subsidyMeterReportedAt, ...serializeAttribution(record, attribution), provider, recipient: record.snapshot.recipient, sourceChain, sourceToken, status: record.status, subsidies: RoutesTransfers.subsidies(record).filter( (subsidy) => BigInt(subsidy.amount.baseUnits) > 0n, ), subsidizerAccount: record.subsidyAccount, subsidyTransaction: record.subsidyChainId && record.subsidyTransactionHash ? { chainId: record.subsidyChainId, transactionRef: record.subsidyTransactionHash, } : null, updatedAt: record.updatedAt, } } type RouteRecord = | { direction: 'inbound'; record: RoutesDeposits.Record } | { direction: 'outbound'; record: RoutesTransfers.Record } function compareRoutes(left: RouteRecord, right: RouteRecord) { if (left.record.updatedAt !== right.record.updatedAt) return left.record.updatedAt > right.record.updatedAt ? -1 : 1 if (left.record.id === right.record.id) return 0 return left.record.id > right.record.id ? -1 : 1 } function transactionSubsidizer(record: SponsoredTransactions.Record) { try { const transaction = TxEnvelopeTempo.deserialize( record.transaction as TxEnvelopeTempo.Serialized, ) if (!transaction.feePayerSignature) return null return Secp256k1.recoverAddress({ payload: record.signPayload as Hex.Hex, signature: transaction.feePayerSignature, }) } catch { return null } } function depositSubsidizer(record: RoutesDeposits.Record) { if (!record.settlementTransaction) return null try { return ( TxEnvelopeTempo.deserialize(record.settlementTransaction as TxEnvelopeTempo.Serialized) .from ?? null ) } catch { return null } }