import { Hono } from 'hono' import * as z from 'zod/mini' import * as Db from '../../db/Db.js' import * as RoutesDepositAddresses from '../../db/tables/routesDepositAddresses.js' import * as RoutesDeposits from '../../db/tables/routesDeposits.js' import * as RoutesTransferEvents from '../../db/tables/routesTransferEvents.js' import * as RoutesTransfers from '../../db/tables/routesTransfers.js' import * as RoutesTransferTransactions from '../../db/tables/routesTransferTransactions.js' import * as Organizations from '../../db/tables/organizations.js' import * as Projects from '../../db/tables/projects.js' import * as Cursor from '../../internal/Cursor.js' import * as Deposit from '../../internal/routes/Deposit.js' import * as DepositAddress from '../../internal/routes/DepositAddress.js' import * as Provider from '../../internal/routes/Provider.js' import * as Transfer from '../../internal/routes/Transfer.js' import * as OpenApi from '../../internal/OpenApi.js' import * as Response from '../../internal/Response.js' import * as Schema from '../../internal/Schema.js' import type * as App from '../App.js' const maximumCorrelationIds = 20 const depositInProgressStatuses = [ 'bridging', 'detected', 'refunding', 'settling', ] as const satisfies readonly Deposit.Status[] const transferInProgressStatuses = [ 'processing', 'refunding', ] as const satisfies readonly Transfer.Status[] function queryValues>(item: item) { return z.pipe( z.pipe( z.string(), z.transform((value) => [...new Set(value.split(',').map((entry) => entry.trim()))]), ), z.array(item).check(z.minLength(1), z.maxLength(20)), ) } /** Zod schemas owned by the admin routes handler. */ export namespace schema { const environments = z .optional(queryValues(z.enum(['production', 'sandbox']))) .check(z.describe('Route environments.'), z.meta({ examples: ['production,sandbox'] })) const identifier = z .optional(z.string().check(z.minLength(1))) .check(z.describe('Exact resource identifier.'), z.meta({ examples: ['org_acme'] })) const methods = z .optional(queryValues(z.enum(Transfer.methods))) .check(z.describe('Route methods.'), z.meta({ examples: ['transaction,depositAddress'] })) const providers = z .optional(queryValues(z.string().check(z.minLength(1)))) .check(z.describe('Route provider ids.'), z.meta({ examples: ['relay,stargate'] })) const sourceChains = z .optional(queryValues(z.string().check(z.minLength(1)))) .check(z.describe('Source chain CAIP-2 ids.'), z.meta({ examples: ['eip155:1,eip155:8453'] })) const timestamp = z .string() .check(z.describe('An ISO 8601 timestamp.'), z.meta({ examples: ['2026-08-26T00:00:00.000Z'] })) const timestampOrNull = z .nullable(timestamp) .check(z.describe('An ISO 8601 timestamp, or null when unavailable.')) const attribution = { environment: z .enum(['production', 'sandbox']) .check(z.describe('Route environment.'), z.meta({ examples: ['production'] })), 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 the record is unavailable.'), z.meta({ examples: ['Acme'] })), // prettier-ignore }) .check(z.describe('Organization that owns the route resource.')), 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 the record is unavailable.'), z.meta({ examples: ['Checkout'] })), // prettier-ignore }), ) .check(z.describe('Attributed project, or null for organization-scoped routes.')), statusUpdatedAt: timestampOrNull, } const filters = { environment: environments, orgId: identifier, projectId: identifier, provider: providers, sourceChain: sourceChains, } const query = z .optional( z .pipe( z.string(), z.transform((value) => value.trim()), ) .check(z.minLength(1), z.maxLength(256)), ) .check( z.describe('Exact resource id, deposit address, or transaction reference.'), z.meta({ examples: [`0x${'aa'.repeat(32)}`] }), ) const needsAttention = z .optional(Schema.booleanQuery()) .check( z.describe('Restricts results to resources that require operator action.'), z.meta({ examples: [true] }), ) const inProgress = z .optional(Schema.booleanQuery()) .check( z.describe('Restricts results to resources still being processed.'), z.meta({ examples: [true] }), ) const correlationIds = z .readonly(z.array(z.string())) .check( z.describe('Recent provider correlation identifiers.'), z.meta({ examples: [['provider_request_123']] }), ) const subsidiesExample = [ { amount: { baseUnits: '23838', currency: 'USD', decimals: 6, formatted: '0.023838', }, provider: 'relay', token: { address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', currency: 'USD', decimals: 6, name: 'USD Coin', standard: 'ERC-20', symbol: 'USDC', tokenKey: 'eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', verified: true, }, }, ] satisfies readonly Provider.Subsidy[] /** Admin route transfer summary. */ export const RoutesTransfer = Schema.describe( z.object({ ...Transfer.schema.RoutesTransfer.shape, ...attribution, subsidies: z .readonly(z.array(Provider.schema.Subsidy)) .check( z.describe('Token amounts paid by subsidy providers.'), z.meta({ examples: [subsidiesExample] }), ), }), 'One route transfer with administrative attribution.', ) /** Admin route deposit-address summary. */ export const RoutesDepositAddress = Schema.describe( z.object({ ...DepositAddress.schema.RoutesDepositAddress.shape, ...attribution, consecutivePollFailures: z .number() .check(z.int(), z.nonnegative(), z.describe('Consecutive failed provider checks.'), z.meta({ examples: [0] })), // prettier-ignore deliveryStrategy: z .enum(DepositAddress.deliveryStrategies) .check(z.describe('Whether the provider or Tempo delivers the destination token.'), z.meta({ examples: ['provider'] })), // prettier-ignore lastPolledAt: timestampOrNull, nextPollAt: timestamp, pollLeaseUntil: timestampOrNull, providerOutputToken: Provider.schema.TokenRef, providerRequestIds: correlationIds, status: DepositAddress.schema.Status, version: z .number() .check(z.int(), z.positive(), z.describe('Current material revision.'), z.meta({ examples: [1] })), // prettier-ignore }), 'One deposit address with reconciliation state and administrative attribution.', ) /** Admin route deposit summary. */ export const RoutesDeposit = Schema.describe( z.object({ ...Deposit.schema.RoutesDeposit.shape, ...attribution, pollObservedAt: timestampOrNull, providerDeliveredAt: timestampOrNull, providerOutputAmount: z .nullable(Schema.TokenAmount) .check(z.describe('Provider output verified on Tempo, or null when pending.')), providerOutputToken: Provider.schema.TokenRef, providerRequestIds: correlationIds, providerTransactionHashes: z .readonly(z.array(z.string())) .check(z.describe('Recent provider-leg transaction references.'), z.meta({ examples: [[`0x${'bb'.repeat(32)}`]] })), // prettier-ignore retryAttempts: z .nullable(z.number().check(z.int(), z.nonnegative())) .check(z.describe('Recorded retry count, or null when unavailable.'), z.meta({ examples: [1] })), // prettier-ignore settlementTransactionHash: z .nullable(z.string()) .check(z.describe('Tempo settlement transaction hash, or null when unavailable.'), z.meta({ examples: [`0x${'cc'.repeat(32)}`] })), // prettier-ignore subsidies: z .readonly(z.array(Provider.schema.Subsidy)) .check( z.describe('Token amounts paid by subsidy providers.'), z.meta({ examples: [subsidiesExample] }), ), tempoGasPaid: z .nullable(z.string()) .check(z.describe('Tempo gas paid for settlement, or null.'), z.meta({ examples: ['1000000000000000'] })), // prettier-ignore version: z .number() .check(z.int(), z.positive(), z.describe('Current material revision.'), z.meta({ examples: [1] })), // prettier-ignore webhookReceivedAt: timestampOrNull, }), 'One detected deposit with safe operational state and administrative attribution.', ) const summary = z .object({ actionRequired: z .number() .check(z.int(), z.nonnegative(), z.describe('Resources that require operator action.'), z.meta({ examples: [1] })), // prettier-ignore inProgress: z .number() .check(z.int(), z.nonnegative(), z.describe('Resources currently being processed.'), z.meta({ examples: [2] })), // prettier-ignore oldestActiveAt: timestampOrNull, }) .check(z.describe('Current mutually exclusive lifecycle totals for one resource type.')) /** Schemas for the routes summary operation. */ export namespace getRoutesSummary { /** Summary filters. */ export const Query = z .strictObject({ ...filters, method: methods, }) .check(z.describe('Filters for current routes work.')) /** Current routes work by resource type. */ export const Response = Schema.describe( z.object({ actionRequired: summary.shape.actionRequired, inProgress: summary.shape.inProgress, oldestActiveAt: timestampOrNull, resources: z .object({ depositAddresses: summary, deposits: summary, transfers: summary, }) .check(z.describe('Current work totals by route resource type.')), }), 'Current mutually exclusive route lifecycle totals.', ) } /** Schemas for listing route transfers. */ export namespace listRoutesTransfers { /** Transfer filters and pagination. */ export const Query = z .strictObject({ ...filters, cursor: Schema.Cursor, inProgress, limit: Schema.Limit, method: methods, needsAttention, query, status: z .optional(queryValues(Transfer.schema.Status)) .check(z.describe('Route transfer lifecycle statuses.'), z.meta({ examples: ['processing,refunding'] })), // prettier-ignore }) .check(z.describe('Filters for listing route transfers.')) /** Paginated route transfers. */ export const Response = Schema.describe( z.object({ data: z.array(RoutesTransfer).check(z.describe('Route transfers.')), nextCursor: Schema.NextCursor, }), 'A page of route transfers.', ) } /** Schemas for reading one route transfer. */ export namespace getRoutesTransfer { /** Transfer path parameters. */ export const Params = z .strictObject({ id: z .string() .check(z.regex(/^rtr_[A-Za-z0-9_-]+$/), z.describe('Route transfer id.'), z.meta({ examples: ['rtr_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx'] })), // prettier-ignore }) .check(z.describe('Route transfer path parameters.')) /** Route transfer with lifecycle and transaction evidence. */ export const Response = Schema.describe( z.object({ ...RoutesTransfer.shape, events: z .array( z.object({ createdAt: timestamp, status: Transfer.schema.Status, statusReason: z.optional(Transfer.schema.StatusReason), version: Transfer.schema.RoutesTransfer.shape.version, }), ) .check(z.describe('Material lifecycle events, oldest first.')), transactions: z .array( z.object({ chainId: z .string() .check(z.describe('Chain containing the transaction.'), z.meta({ examples: ['eip155:8453'] })), // prettier-ignore createdAt: timestamp, role: z .enum(['destination', 'refund', 'source']) .check(z.describe('What the transaction evidences.'), z.meta({ examples: ['source'] })), // prettier-ignore transactionRef: z .string() .check(z.describe('Transaction reference.'), z.meta({ examples: [`0x${'aa'.repeat(32)}`] })), // prettier-ignore }), ) .check(z.describe('Verified transaction references, oldest first.')), }), 'One route transfer with lifecycle and transaction evidence.', ) } /** Schemas for listing deposit addresses. */ export namespace listRoutesDepositAddresses { /** Deposit-address filters and pagination. */ export const Query = z .strictObject({ ...filters, cursor: Schema.Cursor, inProgress, limit: Schema.Limit, needsAttention, query, status: z .optional(queryValues(DepositAddress.schema.Status)) .check(z.describe('Deposit-address lifecycle statuses.'), z.meta({ examples: ['active,action-required,deactivated'] })), // prettier-ignore }) .check(z.describe('Filters for listing route deposit addresses.')) /** Paginated deposit addresses. */ export const Response = Schema.describe( z.object({ data: z.array(RoutesDepositAddress).check(z.describe('Route deposit addresses.')), nextCursor: Schema.NextCursor, }), 'A page of route deposit addresses.', ) } /** Schemas for reading one deposit address. */ export namespace getRoutesDepositAddress { /** Deposit-address path parameters. */ export const Params = z .strictObject({ id: z .string() .check(z.regex(/^(?:fda|rda)_[A-Za-z0-9_-]+$/), z.describe('Route deposit address id, including legacy funding IDs.'), z.meta({ examples: ['rda_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx'] })), // prettier-ignore }) .check(z.describe('Route deposit-address path parameters.')) /** One deposit address. */ export const Response = RoutesDepositAddress } /** Schemas for listing deposits. */ export namespace listRoutesDeposits { /** Deposit filters and pagination. */ export const Query = z .strictObject({ ...filters, cursor: Schema.Cursor, depositAddressId: z .optional(z.string().check(z.regex(/^(?:fda|rda)_[A-Za-z0-9_-]+$/))) .check(z.describe('Route deposit address id, including legacy funding IDs.'), z.meta({ examples: ['rda_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx'] })), // prettier-ignore inProgress, limit: Schema.Limit, needsAttention, query, status: z .optional(queryValues(Deposit.schema.Status)) .check(z.describe('Route deposit lifecycle statuses.'), z.meta({ examples: ['bridging,settling'] })), // prettier-ignore }) .check(z.describe('Filters for listing route deposits.')) /** Paginated deposits. */ export const Response = Schema.describe( z.object({ data: z.array(RoutesDeposit).check(z.describe('Route deposits.')), nextCursor: Schema.NextCursor, }), 'A page of route deposits.', ) } /** Schemas for reading one route deposit. */ export namespace getRoutesDeposit { /** Route deposit path parameters. */ export const Params = z .strictObject({ id: z .string() .check(z.regex(/^(?:fdp|rdp)_[A-Za-z0-9_-]+$/), z.describe('Route deposit id, including legacy funding IDs.'), z.meta({ examples: ['rdp_001785792060000_2ZPE2gvateYEQ0dQslgvkhjx'] })), // prettier-ignore }) .check(z.describe('Route deposit path parameters.')) /** Route deposit with its reusable address. */ export const Response = Schema.describe( z.object({ ...RoutesDeposit.shape, depositAddress: RoutesDepositAddress, }), 'One route deposit with its reusable address.', ) } } /** Read-only routes operations for authenticated administrators. */ export function routes() { return new Hono() .get( '/summary', OpenApi.validate('query', schema.getRoutesSummary.Query, { code: 'query_invalid', message: 'Invalid query parameters', }), OpenApi.describeRoute({ operationId: 'getAdminRoutesSummary', responses: OpenApi.responses({ success: { description: 'Current routes work by resource type.', schema: schema.getRoutesSummary.Response, }, }), summary: 'Get routes summary', tags: ['Routes'], }), async (c) => { const db = Db.get(c.get('db')) const { environment: environments, method: methods, orgId, projectId, provider: providers, sourceChain: sourceChains, } = c.req.valid('query') const filters = { ...(environments ? { environments } : {}), ...(orgId ? { orgId } : {}), ...(projectId ? { projectId } : {}), ...(providers ? { providerIds: providers } : {}), ...(sourceChains ? { sourceChainIds: sourceChains } : {}), } const empty = { actionRequired: 0, inProgress: 0, oldestActiveAt: null } const includesDepositAddresses = !methods || methods.includes('depositAddress') const [depositAddresses, deposits, transfers] = await Promise.all([ includesDepositAddresses ? RoutesDepositAddresses.summarize(db, filters) : empty, includesDepositAddresses ? RoutesDeposits.summarize(db, filters) : empty, RoutesTransfers.summarize(db, { ...filters, ...(methods ? { methods } : {}), }), ]) const resources = { depositAddresses, deposits, transfers } const active = Object.values(resources).flatMap((resource) => resource.oldestActiveAt ? [resource.oldestActiveAt] : [], ) return c.json( Response.validated(schema.getRoutesSummary.Response, { actionRequired: Object.values(resources).reduce( (sum, resource) => sum + resource.actionRequired, 0, ), inProgress: Object.values(resources).reduce( (sum, resource) => sum + resource.inProgress, 0, ), oldestActiveAt: active.sort().at(0) ?? null, resources, }), 200, ) }, ) .get( '/transfers', OpenApi.validate('query', schema.listRoutesTransfers.Query, { code: 'query_invalid', message: 'Invalid query parameters', }), OpenApi.describeRoute({ operationId: 'listAdminRoutesTransfers', responses: OpenApi.responses({ success: { description: 'Route transfers matching the filters.', schema: schema.listRoutesTransfers.Response, }, }), summary: 'List route transfers', tags: ['Routes'], }), async (c) => { const db = Db.get(c.get('db')) const { cursor, environment: environments, inProgress, limit, method: methods, needsAttention, orgId, projectId, provider: providers, query, sourceChain: sourceChains, status: statuses } = c.req.valid('query') // prettier-ignore const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined const records = await RoutesTransfers.list(db, { ...(decoded ? { cursor: { id: String(decoded[1]), updatedAt: String(decoded[0]) } } : {}), ...(environments ? { environments } : {}), limit: limit + 1, ...(methods ? { methods } : {}), ...(orgId ? { orgId } : {}), ...(projectId ? { projectId } : {}), ...(providers ? { providerIds: providers } : {}), ...(query ? { query } : {}), ...(sourceChains ? { sourceChainIds: sourceChains } : {}), ...(inProgress ? { statuses: transferInProgressStatuses } : needsAttention ? { statuses: ['action-required'] as const } : statuses ? { statuses } : {}), }) const page = Cursor.paginate({ key: (record) => [record.updatedAt, record.id], limit, rows: records, }) const attribution = await loadAttribution(db, page.rows) return c.json( Response.validated(schema.listRoutesTransfers.Response, { data: page.rows.map((record) => serializeTransfer(record, attribution)), nextCursor: page.nextCursor, }), 200, ) }, ) .get( '/transfers/:id', OpenApi.validate('param', schema.getRoutesTransfer.Params, { code: 'param_invalid', message: 'Invalid path parameters', }), OpenApi.describeRoute({ operationId: 'getAdminRoutesTransfer', responses: OpenApi.responses({ errors: { 404: 'Route transfer not found.' }, success: { description: 'One route transfer with lifecycle and transaction evidence.', schema: schema.getRoutesTransfer.Response, }, }), summary: 'Get route transfer', tags: ['Routes'], }), async (c) => { const db = Db.get(c.get('db')) const record = await RoutesTransfers.get(db, c.req.valid('param').id) if (!record) return Response.error(c, { code: 'routes_transfer_not_found', message: 'Route transfer not found.', status: 404, }) const [attribution, events, transactions] = await Promise.all([ loadAttribution(db, [record]), RoutesTransferEvents.listByTransfer(db, record.id), RoutesTransferTransactions.listByTransfer(db, record.id), ]) return c.json( Response.validated(schema.getRoutesTransfer.Response, { ...serializeTransfer(record, attribution), events: events.map((event) => ({ createdAt: event.createdAt, status: event.status, ...(event.snapshot.statusReason ? { statusReason: event.snapshot.statusReason } : {}), version: event.version, })), transactions: transactions.map(({ chainId, createdAt, role, transactionRef }) => ({ chainId, createdAt, role, transactionRef, })), }), 200, ) }, ) .get( '/deposit-addresses', OpenApi.validate('query', schema.listRoutesDepositAddresses.Query, { code: 'query_invalid', message: 'Invalid query parameters', }), OpenApi.describeRoute({ operationId: 'listAdminRoutesDepositAddresses', responses: OpenApi.responses({ success: { description: 'Route deposit addresses matching the filters.', schema: schema.listRoutesDepositAddresses.Response, }, }), summary: 'List deposit addresses', tags: ['Routes'], }), async (c) => { const db = Db.get(c.get('db')) const { cursor, environment: environments, inProgress, limit, needsAttention, orgId, projectId, provider: providers, query, sourceChain: sourceChains, status: statuses } = c.req.valid('query') // prettier-ignore const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined const records = inProgress ? [] : await RoutesDepositAddresses.list(db, { ...(decoded ? { cursor: { id: String(decoded[1]), updatedAt: String(decoded[0]) } } : {}), ...(environments ? { environments } : {}), limit: limit + 1, ...(orgId ? { orgId } : {}), ...(projectId ? { projectId } : {}), ...(providers ? { providerIds: providers } : {}), ...(query ? { query } : {}), ...(sourceChains ? { sourceChainIds: sourceChains } : {}), ...(needsAttention ? { statuses: ['action-required'] as const } : statuses ? { statuses } : {}), }) const page = Cursor.paginate({ key: (record) => [record.updatedAt, record.id], limit, rows: records, }) const attribution = await loadAttribution(db, page.rows) return c.json( Response.validated(schema.listRoutesDepositAddresses.Response, { data: page.rows.map((record) => serializeDepositAddress(record, attribution)), nextCursor: page.nextCursor, }), 200, ) }, ) .get( '/deposit-addresses/:id', OpenApi.validate('param', schema.getRoutesDepositAddress.Params, { code: 'param_invalid', message: 'Invalid path parameters', }), OpenApi.describeRoute({ operationId: 'getAdminRoutesDepositAddress', responses: OpenApi.responses({ errors: { 404: 'Route deposit address not found.' }, success: { description: 'One route deposit address with reconciliation state.', schema: schema.getRoutesDepositAddress.Response, }, }), summary: 'Get deposit address', tags: ['Routes'], }), async (c) => { const db = Db.get(c.get('db')) const record = await RoutesDepositAddresses.get(db, c.req.valid('param').id) if (!record) return Response.error(c, { code: 'routes_deposit_address_not_found', message: 'Route deposit address not found.', status: 404, }) const attribution = await loadAttribution(db, [record]) return c.json( Response.validated( schema.getRoutesDepositAddress.Response, serializeDepositAddress(record, attribution), ), 200, ) }, ) .get( '/deposits', OpenApi.validate('query', schema.listRoutesDeposits.Query, { code: 'query_invalid', message: 'Invalid query parameters', }), OpenApi.describeRoute({ operationId: 'listAdminRoutesDeposits', responses: OpenApi.responses({ success: { description: 'Route deposits matching the filters.', schema: schema.listRoutesDeposits.Response, }, }), summary: 'List route deposits', tags: ['Routes'], }), async (c) => { const db = Db.get(c.get('db')) const { cursor, depositAddressId, environment: environments, inProgress, limit, needsAttention, orgId, projectId, provider: providers, query, sourceChain: sourceChains, status: statuses } = c.req.valid('query') // prettier-ignore const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined const records = await RoutesDeposits.list(db, { ...(decoded ? { cursor: { id: String(decoded[1]), updatedAt: String(decoded[0]) } } : {}), ...(depositAddressId ? { depositAddressId } : {}), ...(environments ? { environments } : {}), limit: limit + 1, ...(orgId ? { orgId } : {}), ...(projectId ? { projectId } : {}), ...(providers ? { providerIds: providers } : {}), ...(query ? { query } : {}), ...(sourceChains ? { sourceChainIds: sourceChains } : {}), ...(inProgress ? { statuses: depositInProgressStatuses } : needsAttention ? { statuses: ['action-required'] as const } : statuses ? { statuses } : {}), }) const page = Cursor.paginate({ key: (record) => [record.updatedAt, record.id], limit, rows: records, }) const attribution = await loadAttribution(db, page.rows) return c.json( Response.validated(schema.listRoutesDeposits.Response, { data: page.rows.map((record) => serializeDeposit(record, attribution)), nextCursor: page.nextCursor, }), 200, ) }, ) .get( '/deposits/:id', OpenApi.validate('param', schema.getRoutesDeposit.Params, { code: 'param_invalid', message: 'Invalid path parameters', }), OpenApi.describeRoute({ operationId: 'getAdminRoutesDeposit', responses: OpenApi.responses({ errors: { 404: 'Route deposit not found.' }, success: { description: 'One route deposit with its reusable address.', schema: schema.getRoutesDeposit.Response, }, }), summary: 'Get route deposit', tags: ['Routes'], }), async (c) => { const db = Db.get(c.get('db')) const record = await RoutesDeposits.get(db, c.req.valid('param').id) if (!record) return Response.error(c, { code: 'routes_deposit_not_found', message: 'Route deposit not found.', status: 404, }) const address = await RoutesDepositAddresses.get(db, record.depositAddressId) if (!address) return Response.error(c, { code: 'routes_deposit_address_not_found', message: 'Route deposit address not found.', status: 404, }) const attribution = await loadAttribution(db, [record, address]) return c.json( Response.validated(schema.getRoutesDeposit.Response, { ...serializeDeposit(record, attribution), depositAddress: serializeDepositAddress(address, attribution), }), 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 serializeTransfer(record: RoutesTransfers.Record, attribution: Attribution) { return { ...Transfer.toPublic(record), ...serializeAttribution(record, attribution), environment: record.environment, statusUpdatedAt: record.statusUpdatedAt, subsidies: RoutesTransfers.subsidies(record), } } function serializeDepositAddress(record: RoutesDepositAddresses.Record, attribution: Attribution) { return { ...record.snapshot, ...serializeAttribution(record, attribution), consecutivePollFailures: record.pollFailureCount, createdAt: record.createdAt, deliveryStrategy: record.deliveryStrategy, environment: record.environment, id: record.id, lastPolledAt: record.lastPolledAt, nextPollAt: record.nextPollAt, pollLeaseUntil: record.pollLeaseUntil, providerOutputToken: record.providerOutputToken, providerRequestIds: record.providerRequestIds.slice(-maximumCorrelationIds), status: record.status, statusUpdatedAt: record.statusUpdatedAt, subsidize: record.subsidize, updatedAt: record.updatedAt, version: record.version, } } function serializeDeposit(record: RoutesDeposits.Record, attribution: Attribution) { const attempts = record.retryState?.['attempts'] return { ...Deposit.toPublic(record), ...serializeAttribution(record, attribution), environment: record.environment, pollObservedAt: record.pollObservedAt, providerDeliveredAt: record.providerDeliveredAt, providerOutputAmount: record.providerOutputAmount, providerOutputToken: record.providerOutputToken, providerRequestIds: record.providerRequestIds.slice(-maximumCorrelationIds), providerTransactionHashes: record.providerTransactionHashes.slice(-maximumCorrelationIds), retryAttempts: typeof attempts === 'number' && Number.isInteger(attempts) && attempts >= 0 ? attempts : null, settlementTransactionHash: record.settlementTransactionHash, statusUpdatedAt: record.statusUpdatedAt, subsidies: RoutesDeposits.subsidies(record), tempoGasPaid: record.tempoGasPaid, version: record.version, webhookReceivedAt: record.webhookReceivedAt, } }