import * as z from 'zod/mini' import type * as Db from '../../db/Db.js' import * as Organizations from '../../db/tables/organizations.js' import * as RoutesTransferEvents from '../../db/tables/routesTransferEvents.js' import * as RoutesSubsidies from '../../db/tables/routesSubsidies.js' import * as RoutesTransferSubsidies from '../../db/tables/routesTransferSubsidies.js' import * as RoutesTransferSubsidyNonces from '../../db/tables/routesTransferSubsidyNonces.js' import * as RoutesTransferTransactions from '../../db/tables/routesTransferTransactions.js' import * as RoutesTransfers from '../../db/tables/routesTransfers.js' import * as Id from '../Id.js' import * as OpenApi from '../OpenApi.js' import * as Schema from '../Schema.js' import type * as Webhooks from '../Webhooks.js' import * as Action from './Action.js' import * as Provider from './Provider.js' import * as TransferSubsidy from './TransferSubsidy.js' import * as TransferWebhook from './TransferWebhook.js' /** Public route transfer statuses, in the order documented by the API. */ export const statuses = [ 'awaiting-source', 'processing', 'refunding', 'completed', 'refunded', 'action-required', 'expired', ] as const /** Lifecycle status of a route transfer. */ export type Status = (typeof statuses)[number] /** Statuses that never change again after verified chain evidence. */ export const terminalStatuses = ['completed', 'expired', 'refunded'] as const satisfies readonly Status[] // prettier-ignore /** Route methods a caller can select at creation. */ export const methods = ['depositAddress', 'transaction'] as const /** Route method selected at creation. */ export type Method = (typeof methods)[number] /** Amount modes a caller can select at creation. */ export const modes = ['exactDestination', 'exactSource'] as const /** Amount mode selected at creation. */ export type Mode = (typeof modes)[number] /** Customer-safe codes explaining an `action-required` or refund outcome. */ export const statusReasonCodes = [ 'billing_past_due', 'billing_required', 'destination_amount_below_minimum', 'destination_recovered', 'manual_recovery_required', 'refund_failed', 'routes_subsidy_limit_exceeded', 'routes_subsidy_not_enabled', 'source_amount_above_maximum', 'source_amount_below_minimum', 'source_chain_mismatch', 'source_token_mismatch', 'subsidy_balance_unavailable', 'subsidy_failed', ] as const /** Stable customer-safe status reason code. */ export type StatusReasonCode = (typeof statusReasonCodes)[number] // The public state chart has no exits from `action-required`, but late chain // evidence or operator-triggered reconciliation may still resolve one, so // evidence-driven exits stay allowed. const transitions: Record = { 'action-required': ['completed', 'processing', 'refunded', 'refunding'], 'awaiting-source': ['action-required', 'expired', 'processing'], completed: [], expired: [], processing: ['action-required', 'awaiting-source', 'completed', 'refunding'], refunded: [], refunding: ['action-required', 'refunded'], } /** Returns whether a status is terminal. */ export function isTerminal(status: Status): boolean { return (terminalStatuses as readonly Status[]).includes(status) } /** Returns whether the lifecycle allows moving from one status to another. */ export function canTransition(from: Status, to: Status): boolean { return transitions[from].includes(to) } /** Generates a route transfer id (`rtr_…`, lexically time-ordered). */ export function generateId(now: Date = new Date()): string { return Id.generateSortable('rtr', now) } /** Zod schemas owned by the route transfer resource. */ export namespace schema { /** Route transfer lifecycle status. */ export const Status = z .enum(statuses) .check( z.describe('Lifecycle status of the route transfer.'), z.meta({ examples: ['awaiting-source'] }), ) /** Customer-safe explanation of a status that needs context. */ export const StatusReason = OpenApi.component( z .object({ code: z .enum(statusReasonCodes) .check( z.describe('Stable machine-readable reason code.'), z.meta({ examples: ['destination_amount_below_minimum'] }), ), message: z.string().check( z.describe('Human-readable explanation of the reason.'), z.meta({ examples: ['The verified destination amount did not satisfy the quoted minimum.'], }), ), }) .check(z.describe('Why the transfer is in its current status, when context is needed.')), 'RoutesTransferStatusReason', ) /** Route method selected at creation. */ export const Method = z .enum(methods) .check(z.describe('How the caller funds the transfer.'), z.meta({ examples: ['transaction'] })) /** Amount mode selected at creation. */ export const Mode = z .enum(modes) .check( z.describe('Which side of the transfer the quoted amount fixes.'), z.meta({ examples: ['exactSource'] }), ) /** One fee applied to the transfer. */ export const Fee = OpenApi.component( z .object({ amount: Schema.TokenAmount, side: z .enum(['destination', 'source']) .check( z.describe('Which side of the transfer the fee is taken from.'), z.meta({ examples: ['destination'] }), ), token: Provider.schema.TokenRef, type: z .enum(['provider']) .check(z.describe('What the fee pays for.'), z.meta({ examples: ['provider'] })), }) .check(z.describe('One fee applied to the transfer.')), 'RoutesTransferFee', ) /** Freshness and validity window of the selected quote. */ export const Quote = OpenApi.component( z .object({ expiresAt: z.iso .datetime() .check( z.describe('When the quoted terms stop being executable (ISO 8601).'), z.meta({ examples: ['2026-07-30T04:01:00.000Z'] }), ), sampledAt: z.iso .datetime() .check( z.describe('When the provider produced the quote (ISO 8601).'), z.meta({ examples: ['2026-07-30T04:00:00.000Z'] }), ), }) .check(z.describe('Freshness and validity window of the selected quote.')), 'RoutesTransferQuote', ) const hashesCheck = (description: string) => [ z.describe(description), z.meta({ examples: [[`0x${'aa'.repeat(32)}`]] }), ] const snapshotFields = { destinationAmount: z .optional(Schema.TokenAmount) .check( z.describe('Expected amount received after provider deductions, including pool fees.'), ), destinationAmountMin: z .optional(Schema.TokenAmount) .check(z.describe('Minimum destination amount encoded in the transfer action.')), destinationAmountRequired: z .optional(Schema.TokenAmount) .check(z.describe('Destination amount required for normalized 1:1 delivery, when enabled.')), destinationChain: Provider.schema.ChainRef, destinationToken: Provider.schema.TokenRef, destinationTransactionHashes: z.optional( z.array(z.string()).check(...hashesCheck('Verified destination transaction references.')), ), fees: z .array(Fee) .check( z.describe( 'Explicit fees charged separately from the routed amount; deductions reflected in `destinationAmount` are omitted.', ), ), method: Method, mode: Mode, provider: Provider.schema.ProviderRef, quote: Quote, recipient: z .string() .check( z.describe('Final beneficiary of the transfer.'), z.meta({ examples: [`0x${'11'.repeat(20)}`] }), ), refundAddress: z.optional( z .string() .check( z.describe('Source-chain refund recipient (deposit-address method).'), z.meta({ examples: [`0x${'22'.repeat(20)}`] }), ), ), refundAmount: z.optional(Schema.TokenAmount), refundTransactionHashes: z.optional( z.array(z.string()).check(...hashesCheck('Verified refund transaction references.')), ), sender: z.optional( z .string() .check( z.describe('Source-chain account that signs the route action (transaction method).'), z.meta({ examples: [`0x${'22'.repeat(20)}`] }), ), ), sourceAmount: Schema.TokenAmount, sourceAmountMax: z.optional(Schema.TokenAmount), sourceChain: Provider.schema.ChainRef, sourceToken: Provider.schema.TokenRef, sourceTransactionHashes: z.optional( z.array(z.string()).check(...hashesCheck('Verified source transaction references.')), ), subsidize: z ._default(z.boolean(), false) .check( z.describe('Whether Tempo guarantees normalized 1:1 destination delivery.'), z.meta({ examples: [false] }), ), } /** * The stored public snapshot: every transfer field except lifecycle columns * (`id`, `status`, `statusReason`, `version`, `createdAt`, `updatedAt`), * which live in typed columns and merge in at serialization. */ export const Snapshot = z .object(snapshotFields) .check(z.describe('Quote terms and reconciled public fields of a route transfer.')) const routesTransferFields = { createdAt: z.iso .datetime() .check( z.describe('When the transfer was created (ISO 8601).'), z.meta({ examples: ['2026-07-30T04:00:00.000Z'] }), ), destinationAmount: snapshotFields.destinationAmount, destinationAmountMin: snapshotFields.destinationAmountMin, destinationAmountRequired: snapshotFields.destinationAmountRequired, destinationChain: snapshotFields.destinationChain, destinationToken: snapshotFields.destinationToken, destinationTransactionHashes: snapshotFields.destinationTransactionHashes, fees: snapshotFields.fees, id: z .string() .check( z.describe('Route transfer id (`rtr_…`, lexically time-ordered).'), z.meta({ examples: ['rtr_001785729600000_2ZPE2gvateYEQ0dQslgvkhjx'] }), ), method: snapshotFields.method, mode: snapshotFields.mode, provider: snapshotFields.provider, quote: snapshotFields.quote, recipient: snapshotFields.recipient, refundAddress: snapshotFields.refundAddress, refundAmount: snapshotFields.refundAmount, refundTransactionHashes: snapshotFields.refundTransactionHashes, sender: snapshotFields.sender, sourceAmount: snapshotFields.sourceAmount, sourceAmountMax: snapshotFields.sourceAmountMax, sourceChain: snapshotFields.sourceChain, sourceToken: snapshotFields.sourceToken, sourceTransactionHashes: snapshotFields.sourceTransactionHashes, subsidize: snapshotFields.subsidize, status: Status, statusReason: z.optional(StatusReason), updatedAt: z.iso .datetime() .check( z.describe('When the transfer last materially changed (ISO 8601).'), z.meta({ examples: ['2026-07-30T04:00:20.000Z'] }), ), version: z .number() .check( z.int(), z.positive(), z.describe('Monotonic revision that increments whenever the transfer materially changes.'), z.meta({ examples: [1] }), ), } /** A route transfer as returned by reads. Reads never include `action`. */ export const RoutesTransfer = OpenApi.component( z.object(routesTransferFields).check(z.describe('One durable route transfer.')), 'RoutesTransfer', ) /** A created route transfer: the read shape plus the executable action. */ export const CreatedRoutesTransfer = OpenApi.component( z .object({ action: Action.schema.Action, ...routesTransferFields }) .check(z.describe('The created route transfer, including its one-time executable action.')), 'CreatedRoutesTransfer', ) /** Request body accepted by route transfer creation. */ export const CreateRequest = OpenApi.component( z .strictObject({ amount: z .string() .check( z.regex(/^[1-9]\d*$/), z.describe('Amount in base units of the token the mode fixes.'), z.meta({ examples: ['1000000'] }), ), destinationChain: z .optional(z.string().check(z.minLength(1))) .check( z.describe('Destination chain CAIP-2 id, slug, or alias. Defaults to Tempo.'), z.meta({ examples: ['base', 'ethereum', 'tempo', 'eip155:8453'] }), ), destinationToken: z .string() .check( z.minLength(1), z.describe('Destination token symbol, contract address, or token key.'), z.meta({ examples: ['usdc', 'pathusd'] }), ), mode: Mode, provider: z .optional(z.string().check(z.minLength(1))) .check( z.describe('Route provider ID. Omit to select the best available provider.'), z.meta({ examples: ['stargate', 'relay'] }), ), recipient: z .string() .check( z.minLength(1), z.describe('Destination-chain account that receives the transfer.'), z.meta({ examples: [`0x${'11'.repeat(20)}`] }), ), sender: z .string() .check( z.minLength(1), z.describe('Source-chain account that signs the route action.'), z.meta({ examples: [`0x${'22'.repeat(20)}`] }), ), slippageBps: z.optional( z .number() .check( z.int(), z.gte(0), z.lte(10_000), z.describe('Maximum acceptable slippage in basis points.'), z.meta({ examples: [50] }), ), ), sourceChain: z .string() .check( z.describe('Source chain CAIP-2 id, slug, or alias.'), z.meta({ examples: ['tempo', 'base', 'ethereum', 'eip155:4217'] }), ), sourceToken: z .string() .check( z.minLength(1), z.describe('Source token symbol, contract address, or token key.'), z.meta({ examples: ['usdce', 'usdc'] }), ), subsidize: z .optional(z.boolean()) .check( z.describe( 'Guarantees normalized 1:1 delivery for exact-source Tempo USD transfers to Base or Ethereum. A Tempo Admin must enable subsidies for the organization.', ), z.meta({ examples: [true] }), ), }) .check(z.describe('Route transfer creation request.')), 'CreateRoutesTransferRequest', ) } /** Stored public snapshot of a route transfer (see {@link schema.Snapshot}). */ export type Snapshot = z.output /** Customer-safe status reason (see {@link schema.StatusReason}). */ export type StatusReason = z.output /** A route transfer as returned by reads (see {@link schema.RoutesTransfer}). */ export type Public = z.output /** A created route transfer (see {@link schema.CreatedRoutesTransfer}). */ export type Created = z.output /** A validated creation request (see {@link schema.CreateRequest}). */ export type CreateRequest = z.output /** What an associated transaction reference evidences. */ export const transactionRoles = ['destination', 'refund', 'source'] as const /** Role of a verified transaction reference on a transfer. */ export type TransactionRole = (typeof transactionRoles)[number] /** Bounded private provider state retained for transfer reconciliation. */ export type ProviderState = Record type Staged = { /** Route transfer record committed with the webhook obligations. */ record: RoutesTransfers.Record /** Webhook queue references committed with the transfer. */ references: Webhooks.QueueReference[] } /** * Creates a durable route transfer: the row plus its version-1 event commit * in one transaction, so every stored version has an immutable public record. */ export async function create(db: Db.Db, input: create.Input): Promise { return db.transaction(async (tx) => (await createIn(tx, input)).record) } /** * Creates the transfer row and its version-1 event inside the caller's * transaction, for writes that must commit atomically with sibling state * (idempotency completion). */ export async function createIn(tx: Db.Db, input: create.Input): Promise { const now = new Date() const snapshot = parseSnapshot(input.snapshot) const record: RoutesTransfers.Record = { apiKeyId: input.apiKeyId, createdAt: now.toISOString(), environment: input.environment, id: input.id ?? generateId(now), method: snapshot.method, mode: snapshot.mode, orgId: input.orgId, projectId: input.projectId ?? null, providerDeliveredAt: null, providerId: snapshot.provider.id, providerState: input.providerState ?? null, quoteExpiresAt: snapshot.quote.expiresAt, snapshot, status: 'awaiting-source', statusReason: null, statusUpdatedAt: now.toISOString(), subsidyAccount: input.subsidyCommitment?.account.toLowerCase() ?? null, subsidyAmount: null, subsidyChainId: input.subsidyCommitment?.chainId ?? null, subsidyCommitmentAmount: input.subsidyCommitment?.amount ?? null, subsidyMeterReportedAt: null, subsidyNativeAmount: input.subsidyCommitment?.nativeAmount ?? null, subsidyTokenAddress: input.subsidyCommitment?.tokenAddress.toLowerCase() ?? null, subsidyTransactionHash: null, updatedAt: now.toISOString(), version: 1, } const created = await RoutesTransfers.insert(tx, record) const event = await RoutesTransferEvents.insert(tx, { createdAt: record.createdAt, snapshot: toPublic(created), status: created.status, transferId: created.id, version: created.version, }) const { references } = await TransferWebhook.stage(tx, { changes: [{ event, record: created }], }) return { record: created, references } } export declare namespace createIn { /** Created transfer and webhook queue references committed with it. */ type Result = Staged } export declare namespace create { /** Fields required to create a route transfer. */ type Input = { /** API key that created the transfer. */ apiKeyId: string /** Key environment the transfer belongs to. */ environment: 'production' | 'sandbox' /** Transfer id override for deterministic tests. */ id?: string | undefined /** Owning organization id (`org_…`). */ orgId: string /** Attributed project id, when the creating key is project-attributed. */ projectId?: string | undefined /** Private provider correlation retained for reconciliation. */ providerState?: ProviderState | undefined /** The public snapshot captured from the selected quote. */ snapshot: Snapshot /** Inventory commitment approved atomically with the executable action. */ subsidyCommitment?: create.SubsidyCommitment | undefined } /** Private inventory promised before returning an executable action. */ type SubsidyCommitment = { /** Destination subsidy signer address. */ account: string /** Maximum destination token amount promised by Tempo. */ amount: NonNullable /** Destination EVM chain CAIP-2 id. */ chainId: string /** Worst-case native gas amount reserved for settlement. */ nativeAmount: string /** Destination ERC-20 token address. */ tokenAddress: string } } /** Checks and persists a creation-time subsidy commitment under the signer lock. */ export async function reserveSubsidyCommitmentIn( tx: Db.Db, input: create.Input, options: reserveSubsidyCommitmentIn.Options, ): Promise { await RoutesSubsidies.lockOrganization(tx, input.orgId) if (!(await Organizations.get(tx, input.orgId))) throw new OrganizationNotFoundError() await RoutesTransferSubsidyNonces.lockAvailableIn(tx, options).catch((cause) => { if (cause instanceof RoutesTransferSubsidyNonces.BlockedError) throw new SubsidySignerBlockedError() throw cause }) const reservations = await RoutesTransfers.subsidyReservations(tx, { account: options.account, chainId: options.chainId, tokenAddress: options.tokenAddress, }) const checked = await options.check({ reservations }) if (Date.parse(input.snapshot.quote.expiresAt) <= Date.now()) throw new QuoteExpiredError() return createIn(tx, { ...input, subsidyCommitment: { account: options.account, amount: options.subsidyCommitmentAmount, chainId: options.chainId, nativeAmount: checked.nativeAmount, tokenAddress: options.tokenAddress, }, }) } export declare namespace reserveSubsidyCommitmentIn { /** Creation-time subsidy commitment inputs. */ type Options = { /** Destination subsidy signer address. */ account: string /** Verifies aggregate inventory while the signer lane is locked. */ check: (options: CheckOptions) => Promise<{ nativeAmount: string }> /** Destination EVM chain CAIP-2 id. */ chainId: string /** Maximum destination token amount promised by Tempo. */ subsidyCommitmentAmount: NonNullable /** Destination ERC-20 token address. */ tokenAddress: string } /** Active inventory already promised by other transfers. */ type CheckOptions = { /** Existing token and native reservations. */ reservations: RoutesTransfers.subsidyReservations.Result } } /** Atomically assigns verified source transactions and starts reconciliation. */ export async function registerSourceTransactions( db: Db.Db, options: registerSourceTransactions.Options, ): Promise { try { return await db.transaction(async (tx) => { const current = await RoutesTransfers.getForUpdate(tx, options.id) if (!current) return { type: 'not_found' } if (options.transactionHashes.length === 0) return { current, type: 'invalid' } const existing = await Promise.all( options.transactionHashes.map((transactionRef) => RoutesTransferTransactions.getSource(tx, { chainId: options.chainId, transactionRef, transferId: current.id, }), ), ) if ( existing.some( (transaction) => transaction !== undefined && transaction.transferId !== current.id, ) ) return { type: 'conflict' } if (current.status !== 'awaiting-source') { if ( existing.every((transaction) => transaction?.transferId === current.id) && (current.status === 'processing' || current.status === 'completed') ) return { record: current, type: 'replayed' } return { current, type: 'invalid' } } if (RoutesTransfers.isSubsidyCommitmentExpired(current)) return { current, type: 'invalid' } const ordered = options.transactionHashes.toSorted((left, right) => RoutesTransferTransactions.normalize(options.chainId, left).localeCompare( RoutesTransferTransactions.normalize(options.chainId, right), ), ) for (const transactionRef of ordered) { const result = await RoutesTransferTransactions.claimSource(tx, { chainId: options.chainId, createdAt: new Date().toISOString(), role: 'source', transactionRef, transferId: options.id, }) if (result.type === 'conflict') throw new SourceTransactionConflictError() } const result = await transitionIn(tx, { current, expectedVersion: current.version, id: current.id, providerState: { ...current.providerState, ...options.providerState }, snapshot: { ...current.snapshot, ...(options.destinationAmount ? { destinationAmount: options.destinationAmount } : {}), sourceTransactionHashes: options.transactionHashes.map((transactionRef) => RoutesTransferTransactions.normalize(options.chainId, transactionRef), ), }, status: 'processing', }) if (result.type !== 'applied') throw new AtomicTransitionError() return { record: result.record, references: result.references, type: 'registered' } }) } catch (cause) { if (cause instanceof SourceTransactionConflictError) return { type: 'conflict' } throw cause } } export declare namespace registerSourceTransactions { /** Verified source evidence and private provider correlation. */ type Options = { /** Source chain CAIP-2 id. */ chainId: string /** Exact destination amount observed while verifying the source transaction. */ destinationAmount?: NonNullable | undefined /** Transfer id (`rtr_…`). */ id: string /** Private state added for destination reconciliation. */ providerState: ProviderState /** Verified source transaction hashes, with the execution hash first. */ transactionHashes: readonly [string, ...string[]] } /** Source transaction registration outcome. */ type Result = | { type: 'conflict' } | { current: RoutesTransfers.Record; type: 'invalid' } | (Staged & { type: 'registered' }) | { record: RoutesTransfers.Record; type: 'replayed' } | { type: 'not_found' } } /** Atomically removes invalidated source evidence and accepts a replacement. */ export async function reopenSourceTransaction( db: Db.Db, options: reopenSourceTransaction.Options, ): Promise { return db.transaction(async (tx) => { const current = await RoutesTransfers.getForUpdate(tx, options.id) if (!current) return { type: 'not_found' } if (current.status !== 'processing') return { current, type: 'invalid' } const removed = await RoutesTransferTransactions.removeSource(tx, { chainId: options.chainId, transactionHash: options.transactionHash, transferId: current.id, }) if (!removed) return { current, type: 'invalid' } await RoutesTransferTransactions.removeSources(tx, { transferId: current.id }) const { sourceTransactionHashes: _sourceTransactionHashes, ...snapshot } = current.snapshot const result = await transitionIn(tx, { current, expectedVersion: current.version, id: current.id, providerState: options.providerState, snapshot, status: 'awaiting-source', }) if (result.type !== 'applied') throw new AtomicTransitionError() return { record: result.record, references: result.references, type: 'reopened' } }) } export declare namespace reopenSourceTransaction { /** Source evidence to remove after it disappears from the canonical chain. */ type Options = { /** Source chain CAIP-2 id. */ chainId: string /** Transfer id (`rtr_…`). */ id: string /** Private provider state retained while awaiting replacement evidence. */ providerState: ProviderState /** Invalidated source transaction hash. */ transactionHash: string } /** Source transaction reopening outcome. */ type Result = | { current: RoutesTransfers.Record; type: 'invalid' } | (Staged & { type: 'reopened' }) | { type: 'not_found' } } /** Atomically reserves a signer nonce and persists the signed subsidy before broadcast. */ export async function reserveSubsidy( db: Db.Db, options: reserveSubsidy.Options, ): Promise { return db.transaction(async (tx) => { await RoutesSubsidies.lockOrganization(tx, options.orgId) const current = await RoutesTransfers.getForUpdate(tx, options.id) if (!current) return { type: 'not_found' } if (current.orgId !== options.orgId || current.status !== 'processing') return { current, type: 'invalid' } const existing = TransferSubsidy.read(current.providerState?.['transferSubsidy']) if (existing) return { record: current, transaction: existing, type: 'replayed' } if ( !current.subsidyCommitmentAmount || BigInt(options.subsidyAmount.baseUnits) > BigInt(current.subsidyCommitmentAmount.baseUnits) ) throw new SubsidyCommitmentExceededError() const transactionRef = current.snapshot.sourceTransactionHashes?.[0] const source = transactionRef ? await RoutesTransferTransactions.getSource(tx, { chainId: current.snapshot.sourceChain.id, transactionRef, transferId: current.id, }) : undefined if (source?.transferId !== current.id) throw new SubsidyEvidenceError() const settlement = await RoutesTransferSubsidies.getForUpdateIn(tx, { sourceChainId: source.chainId, transactionRef: source.transactionRef, }) if (settlement) { const transaction = TransferSubsidy.read(settlement.transaction) if ( !transaction || settlement.destinationChainId !== options.chainId || settlement.recipient !== current.snapshot.recipient.toLowerCase() || settlement.tokenAddress !== options.tokenAddress.toLowerCase() || BigInt(transaction.amount) !== BigInt(options.subsidyAmount.baseUnits) ) throw new SubsidyEvidenceError() const result = await transitionIn(tx, { current, expectedVersion: current.version, id: current.id, providerState: { ...current.providerState, transferSubsidy: transaction, transferSubsidyOwnerId: settlement.transferId, }, subsidyAccount: transaction.account, subsidyAmount: null, subsidyChainId: transaction.chainId, subsidyCommitmentAmount: null, subsidyNativeAmount: null, subsidyTokenAddress: settlement.tokenAddress, subsidyTransactionHash: transaction.hash, }) if (result.type !== 'applied') throw new AtomicTransitionError() return { record: result.record, references: result.references, transaction, type: 'shared' } } const nonce = await RoutesTransferSubsidyNonces.reserve(tx, { account: options.account, chainId: options.chainId, pendingNonce: options.pendingNonce, }).catch((cause) => { if (cause instanceof RoutesTransferSubsidyNonces.BlockedError) throw new SubsidySignerBlockedError() throw cause }) const reservations = await RoutesTransfers.subsidyReservations(tx, { account: options.account, chainId: options.chainId, excludeId: current.id, tokenAddress: options.tokenAddress, }) const transaction = await options.prepare({ nonce, reservations }) const result = await transitionIn(tx, { current, expectedVersion: current.version, id: current.id, providerState: { ...current.providerState, transferSubsidy: transaction }, subsidyAccount: options.account.toLowerCase(), subsidyAmount: options.subsidyAmount, subsidyChainId: options.chainId, subsidyNativeAmount: transaction.nativeAmount ?? null, subsidyTokenAddress: options.tokenAddress.toLowerCase(), subsidyTransactionHash: transaction.hash, }) if (result.type !== 'applied') throw new AtomicTransitionError() return { record: result.record, references: result.references, transaction, type: 'reserved', } }) } /** Persists the first verified provider-delivery boundary exactly once. */ export async function recordProviderDelivery( db: Db.Db, options: recordProviderDelivery.Options, ): Promise { return db.transaction(async (tx) => { const current = await RoutesTransfers.getForUpdate(tx, options.id) if (!current) return { type: 'not_found' } if (current.providerDeliveredAt) return { record: current, type: 'replayed' } if (current.status !== 'processing') return { current, type: 'invalid' } const record = await RoutesTransfers.recordProviderDelivery(tx, { deliveredAt: new Date().toISOString(), id: current.id, }) if (!record) throw new AtomicTransitionError() return { record, type: 'recorded' } }) } export declare namespace recordProviderDelivery { /** Provider delivery boundary input. */ type Options = { id: string } /** Provider delivery boundary outcome. */ type Result = | { current: RoutesTransfers.Record; type: 'invalid' } | { record: RoutesTransfers.Record; type: 'recorded' } | { record: RoutesTransfers.Record; type: 'replayed' } | { type: 'not_found' } } /** Blocks a rejected signer lane and marks the owning transfer for recovery. */ export async function blockSubsidy( db: Db.Db, options: blockSubsidy.Options, ): Promise { return db.transaction(async (tx) => { const current = await RoutesTransfers.getForUpdate(tx, options.id) if (!current) return { type: 'not_found' } if (current.status !== 'processing' || !current.subsidyAccount || !current.subsidyChainId) return { current, type: 'invalid' } await RoutesTransferSubsidyNonces.blockIn(tx, { account: current.subsidyAccount, chainId: current.subsidyChainId, reason: 'broadcast_rejected', transferId: typeof current.providerState?.['transferSubsidyOwnerId'] === 'string' ? current.providerState['transferSubsidyOwnerId'] : current.id, }) return transitionIn(tx, { current, expectedVersion: current.version, id: current.id, status: 'action-required', statusReason: options.statusReason, }) }) } export declare namespace blockSubsidy { /** Rejected subsidy transfer and customer-safe reason. */ type Options = { /** Route transfer id (`rtr_…`). */ id: string /** Customer-safe recovery reason. */ statusReason: StatusReason } } export declare namespace reserveSubsidy { /** Destination signer and transaction preparation inputs. */ type Options = { /** Destination subsidy signer address. */ account: string /** Destination EVM chain CAIP-2 id. */ chainId: string /** Route transfer id (`rtr_…`). */ id: string /** Owning organization serialized with subsidy liability persistence. */ orgId: string /** Pending signer nonce observed before entering the database lock. */ pendingNonce: number /** Prepares the signed transaction after inventory and nonce serialization. */ prepare: (options: reserveSubsidy.PrepareOptions) => Promise /** Destination token amount reserved for subsidy settlement. */ subsidyAmount: NonNullable /** Destination ERC-20 token address. */ tokenAddress: string } /** Values resolved while holding the destination signer lock. */ type PrepareOptions = { /** EOA nonce reserved for this transaction. */ nonce: number /** Inventory already reserved by unsettled transfers. */ reservations: RoutesTransfers.subsidyReservations.Result } /** Durable transaction reservation outcome. */ type Result = | { current: RoutesTransfers.Record; type: 'invalid' } | (Staged & { transaction: TransferSubsidy.Transaction; type: 'reserved' }) | (Staged & { transaction: TransferSubsidy.Transaction; type: 'shared' }) | { record: RoutesTransfers.Record; transaction: TransferSubsidy.Transaction; type: 'replayed' } | { type: 'not_found' } } /** Atomically records a verified destination transaction and completes a transfer. */ export async function complete(db: Db.Db, options: complete.Options): Promise { return db.transaction(async (tx) => { const current = await RoutesTransfers.getForUpdate(tx, options.id) if (!current) return { type: 'not_found' } if (current.status === 'completed') { const transactions = await RoutesTransferTransactions.listByTransfer(tx, current.id) const existing = options.transactionHashes.every((transactionHash) => transactions.some( (transaction) => transaction.chainId === options.chainId && transaction.role === 'destination' && transaction.transactionRef === transactionHash.toLowerCase(), ), ) return existing ? { record: current, type: 'replayed' } : { current, type: 'invalid' } } if (current.status !== 'processing') return { current, type: 'invalid' } const transactions = await Promise.all( options.transactionHashes.map((transactionRef) => RoutesTransferTransactions.insert(tx, { chainId: options.chainId, createdAt: new Date().toISOString(), role: 'destination', transactionRef, transferId: options.id, }), ), ) if (transactions.some((transaction) => !transaction)) throw new AtomicTransitionError() const result = await transitionIn(tx, { current, expectedVersion: current.version, id: current.id, snapshot: { ...current.snapshot, destinationAmount: options.destinationAmount, destinationTransactionHashes: transactions.map( (transaction) => transaction!.transactionRef, ), }, status: 'completed', }) if (result.type !== 'applied') throw new AtomicTransitionError() return { record: result.record, references: result.references, type: 'completed' } }) } export declare namespace complete { /** Verified destination evidence for one processing transfer. */ type Options = { /** Destination chain CAIP-2 id. */ chainId: string /** Exact verified destination amount. */ destinationAmount: NonNullable /** Transfer id (`rtr_…`). */ id: string /** Verified destination transaction hashes, provider delivery first. */ transactionHashes: readonly [string, ...string[]] } /** Transfer completion outcome. */ type Result = | { current: RoutesTransfers.Record; type: 'invalid' } | (Staged & { type: 'completed' }) | { record: RoutesTransfers.Record; type: 'replayed' } | { type: 'not_found' } } /** * Applies a version-guarded material change: a status transition, a snapshot * update, or both. The row update and its immutable event commit in one * transaction; a stale `expectedVersion` or disallowed transition applies * nothing, so concurrent observations cannot overwrite newer evidence. */ export async function transition( db: Db.Db, options: transition.Options, ): Promise { return db.transaction((tx) => transitionIn(tx, options)) } async function transitionIn( tx: Db.Db, options: transition.Options & { current?: RoutesTransfers.Record | undefined }, ): Promise { const now = new Date() const current = options.current ?? (await RoutesTransfers.get(tx, options.id)) if (!current) return { type: 'not_found' } if (current.version !== options.expectedVersion) return { current, type: 'stale' } const status = options.status ?? current.status if (status !== current.status && !canTransition(current.status, status)) return { current, type: 'invalid' } const updated = await RoutesTransfers.update(tx, { expectedVersion: options.expectedVersion, id: options.id, providerDeliveredAt: current.providerDeliveredAt, providerState: options.providerState === undefined ? current.providerState : options.providerState, snapshot: options.snapshot === undefined ? current.snapshot : parseSnapshot(options.snapshot), status, statusReason: options.statusReason ?? (status === current.status ? current.statusReason : null), statusUpdatedAt: status === current.status ? (current.statusUpdatedAt ?? current.updatedAt) : now.toISOString(), subsidyAccount: options.subsidyAccount === undefined ? current.subsidyAccount : options.subsidyAccount, subsidyAmount: options.subsidyAmount === undefined ? current.subsidyAmount : options.subsidyAmount, subsidyChainId: options.subsidyChainId === undefined ? current.subsidyChainId : options.subsidyChainId, subsidyCommitmentAmount: options.subsidyCommitmentAmount === undefined ? current.subsidyCommitmentAmount : options.subsidyCommitmentAmount, subsidyNativeAmount: options.subsidyNativeAmount === undefined ? current.subsidyNativeAmount : options.subsidyNativeAmount, subsidyTokenAddress: options.subsidyTokenAddress === undefined ? current.subsidyTokenAddress : options.subsidyTokenAddress, subsidyTransactionHash: options.subsidyTransactionHash === undefined ? current.subsidyTransactionHash : options.subsidyTransactionHash, updatedAt: now.toISOString(), version: current.version + 1, }) if (!updated) { const latest = await RoutesTransfers.get(tx, options.id) return latest ? { current: latest, type: 'stale' } : { type: 'not_found' } } const event = await RoutesTransferEvents.insert(tx, { createdAt: updated.updatedAt, snapshot: toPublic(updated), status: updated.status, transferId: updated.id, version: updated.version, }) const { references } = await TransferWebhook.stage(tx, { changes: [{ event, record: updated }], }) return { record: updated, references, type: 'applied' } } export declare namespace transition { /** Fields describing a material change to apply. */ type Options = { /** The version the change was computed against. */ expectedVersion: number /** Transfer id (`rtr_…`). */ id: string /** Replacement private provider state; omitted keeps the stored value. */ providerState?: ProviderState | null | undefined /** Replacement public snapshot; omitted keeps the stored snapshot. */ snapshot?: Snapshot | undefined /** New status; omitted keeps the current status (snapshot-only change). */ status?: Status | undefined /** Customer-safe reason attached to the new status. */ statusReason?: StatusReason | undefined /** Destination subsidy signer; omitted keeps the stored value. */ subsidyAccount?: RoutesTransfers.Record['subsidyAccount'] | undefined /** Tempo-funded destination amount; omitted keeps the stored value. */ subsidyAmount?: RoutesTransfers.Record['subsidyAmount'] | undefined /** Destination subsidy chain; omitted keeps the stored value. */ subsidyChainId?: RoutesTransfers.Record['subsidyChainId'] | undefined /** Creation-time subsidy commitment; omitted keeps the stored value. */ subsidyCommitmentAmount?: RoutesTransfers.Record['subsidyCommitmentAmount'] | undefined /** Native gas reservation; omitted keeps the stored value. */ subsidyNativeAmount?: RoutesTransfers.Record['subsidyNativeAmount'] | undefined /** Destination subsidy token; omitted keeps the stored value. */ subsidyTokenAddress?: RoutesTransfers.Record['subsidyTokenAddress'] | undefined /** Destination subsidy transaction hash; omitted keeps the stored value. */ subsidyTransactionHash?: RoutesTransfers.Record['subsidyTransactionHash'] | undefined } /** Outcome of a guarded transition. */ type Result = | (Staged & { type: 'applied' }) | { current: RoutesTransfers.Record; type: 'invalid' } | { current: RoutesTransfers.Record; type: 'stale' } | { type: 'not_found' } } /** Serializes a created row with its one-time executable action. */ export function toCreated(record: RoutesTransfers.Record, action: Action.Action): Created { return schema.CreatedRoutesTransfer.parse({ action, ...toPublic(record) }) } /** Serializes a stored row to the public read shape. Never includes actions. */ export function toPublic(record: RoutesTransfers.Record): Public { return schema.RoutesTransfer.parse({ ...record.snapshot, createdAt: record.createdAt, id: record.id, status: record.status, ...(record.statusReason === null ? {} : { statusReason: record.statusReason }), updatedAt: record.updatedAt, version: record.version, }) } function parseSnapshot(snapshot: Snapshot): Snapshot { const parsed = schema.Snapshot.parse(snapshot) return { ...parsed, quote: { ...parsed.quote, expiresAt: new Date(parsed.quote.expiresAt).toISOString(), }, } } /** Actual settlement exceeded the creation-time subsidy commitment. */ export class SubsidyCommitmentExceededError extends Error { override name = 'Routes.Transfer.SubsidyCommitmentExceededError' } /** Source evidence is absent or conflicts with an existing subsidy settlement. */ export class SubsidyEvidenceError extends Error { override name = 'Routes.Transfer.SubsidyEvidenceError' } /** The configured subsidy signer cannot accept another commitment. */ export class SubsidySignerBlockedError extends Error { override name = 'Routes.Transfer.SubsidySignerBlockedError' } /** The commitment owner disappeared before its locked persistence boundary. */ export class OrganizationNotFoundError extends Error { override name = 'Routes.Transfer.OrganizationNotFoundError' } /** The provider quote expired while waiting to reserve subsidy inventory. */ export class QuoteExpiredError extends Error { override name = 'Routes.Transfer.QuoteExpiredError' } class AtomicTransitionError extends Error { override name = 'Routes.Transfer.AtomicTransitionError' } class SourceTransactionConflictError extends Error { override name = 'Routes.Transfer.SourceTransactionConflictError' }