import { base } from 'viem/chains' import { tempoMainnet } from 'viem/tempo/chains' import * as Anvil from '../../../test/Anvil.js' import * as TestApp from '../../../test/App.js' import * as TestRoutes from '../../../test/Routes.js' import * as Metrics from '../../Metrics.js' import * as Organizations from '../../db/tables/organizations.js' import * as RoutesSubsidies from '../../db/tables/routesSubsidies.js' import * as RoutesTransfers from '../../db/tables/routesTransfers.js' import * as StripeCustomers from '../../db/tables/stripeCustomers.js' import * as Catalog from './Catalog.js' import * as Stargate from './providers/stargate.js' import * as Transfer from './Transfer.js' import * as TransferReconciliation from './TransferReconciliation.js' import * as TransferSubsidy from './TransferSubsidy.js' const db = TestApp.database() const amount = { baseUnits: '200000', currency: 'USD', decimals: 6, formatted: '0.2', } as const const destinationTransactionHash = '0x3dfaba0a78b8f4bc09e55e05211c8120729e7b6879efe32613883befbca666da' const guid = '0x5549d117c8b23f0ecdf05c2604ed989cf0034f378707bd81319e93834f2f85f2' const recipient = '0x7212623e4cff9ad010d28aa065fad2bf58578783' const sender = '0x52b650a62e384f68880d0a1dc2b84dfed47d5660' const sourceTransactionHash = '0xfd71359adf5095f2ab512a19e0a2df6c8cc0db14ac8a52286d61287daabdccd4' const routeConfiguration = TestRoutes.snapshot.routesByProvider .get('stargate')! .find((candidate) => candidate.route.source.chain.id === 'eip155:8453')!.configuration! const providers = [Stargate.stargate()] const outboundRouteConfiguration = TestRoutes.snapshot.routesByProvider .get('stargate')! .find((candidate) => candidate.route.source.chain.id === 'eip155:4217')!.configuration! describe('createTracker', () => { test('preserves case-sensitive source references during lookup', async () => { const database = TestApp.database() await TestRoutes.publish(database) const { sourceChain, sourceToken } = TestRoutes.depositAddressSnapshot() const transactionRef = 'A'.repeat(87) const transfer = await Transfer.create(database, { apiKeyId: 'key_test', environment: 'production', orgId: 'org_test', providerState: {}, snapshot: TestRoutes.transferSnapshot({ provider: { id: 'stargate', name: 'Stargate' }, sourceChain, sourceToken, }), }) await Transfer.registerSourceTransactions(database, { chainId: sourceChain.id, id: transfer.id, providerState: {}, transactionHashes: [transactionRef], }) const tracker = TransferReconciliation.createTracker({ db: database, providers }) await expect( tracker.reconcile({ transferId: transfer.id, type: 'routes:transfer:reconcile' }), ).rejects.toThrow('Tempo delivery conflicts with registered transfer terms.') }) }) describe.skipIf(!Anvil.available)('createTracker', () => { let destination: Anvil.start.ReturnType let source: Anvil.start.ReturnType beforeAll(async () => { ;[destination, source] = await Promise.all([ Anvil.start({ chain: tempoMainnet, forkUrl: tempoMainnet.rpcUrls.default.http[0]! }), Anvil.start({ chain: base, forkUrl: base.rpcUrls.default.http[0]! }), ]) await Catalog.publish(db, { ...TestRoutes.data, chains: TestRoutes.data.chains.map((chain) => chain.id === 'eip155:4217' ? { ...chain, rpcUrls: [destination.rpcUrl] } : chain, ), }) }) afterAll(async () => { await Promise.all([destination?.stop(), source?.stop()]) }) test('rechecks pending destinations after two seconds', async () => { const database = TestApp.database() await Catalog.publish(database, { ...TestRoutes.data, chains: TestRoutes.data.chains.map((chain) => { if (chain.id === 'eip155:4217') return { ...chain, rpcUrls: [destination.rpcUrl] } if (chain.id === 'eip155:8453') return { ...chain, rpcUrls: [source.rpcUrl] } return chain }), }) const destinationBlockNumber = await destination.client.getBlockNumber() const transfer = await Transfer.create(database, { apiKeyId: 'key_test', environment: 'production', orgId: 'org_test', providerState: { destinationBlockNumber: (destinationBlockNumber + 1n).toString(), routeConfiguration, }, snapshot: TestRoutes.transferSnapshot({ destinationAmount: amount, destinationAmountMin: amount, provider: { id: 'stargate', name: 'Stargate' }, recipient, sender, sourceAmount: amount, }), }) await Transfer.registerSourceTransactions(database, { chainId: 'eip155:8453', id: transfer.id, providerState: { stargate: { guid, sourceEid: 30_184 } }, transactionHashes: [sourceTransactionHash], }) type Dispatch = { delaySeconds: number; message: TransferReconciliation.Message } const counts: Parameters[] = [] const dispatches: Dispatch[] = [] const histograms: Parameters[] = [] const tracker = TransferReconciliation.createTracker({ db: database, dispatch: async (message, options) => { dispatches.push({ delaySeconds: options.delaySeconds, message }) }, metrics: Metrics.from({ count(...parameters) { counts.push(parameters) }, flush() {}, gauge() {}, histogram(...parameters) { histograms.push(parameters) }, }), providers, }) await expect( tracker.reconcile({ transferId: transfer.id, type: 'routes:transfer:reconcile' }), ).resolves.toEqual({ type: 'pending' }) expect(dispatches).toEqual([ { delaySeconds: 2, message: { attempt: 1, transferId: transfer.id, type: 'routes:transfer:reconcile' }, }, ]) expect(counts.filter(([name]) => name === 'routes_provider_operation_count')) .toMatchInlineSnapshot(` [ [ "routes_provider_operation_count", 1, { "operation": "verifyDestinationDelivery", "outcome": "success", "provider": "stargate", }, ], [ "routes_provider_operation_count", 1, { "operation": "verifySourceTransaction", "outcome": "success", "provider": "stargate", }, ], ] `) const reconciliation = histograms.find( ([name]) => name === 'routes_transfer_reconciliation_duration_ms', ) expect(reconciliation?.[1]).toBeGreaterThanOrEqual(0) expect(reconciliation?.[2]).toEqual({ outcome: 'pending' }) const foreign = await Transfer.create(database, { apiKeyId: 'key_foreign', environment: transfer.environment, orgId: 'org_foreign', providerState: transfer.providerState ?? {}, snapshot: transfer.snapshot, }) expect( ( await Transfer.registerSourceTransactions(database, { chainId: 'eip155:8453', id: foreign.id, providerState: { stargate: { guid, sourceEid: 30_184 } }, transactionHashes: [sourceTransactionHash], }) ).type, ).toBe('registered') await expect(tracker.reconcile(dispatches[0]!.message)).resolves.toEqual({ type: 'pending' }) expect(dispatches[1]).toEqual({ delaySeconds: 2, message: { attempt: 2, transferId: transfer.id, type: 'routes:transfer:reconcile' }, }) await expect( tracker.reconcile({ transferId: foreign.id, type: 'routes:transfer:reconcile' }), ).resolves.toEqual({ type: 'pending' }) expect(dispatches[2]).toEqual({ delaySeconds: 2, message: { attempt: 1, transferId: foreign.id, type: 'routes:transfer:reconcile' }, }) }) test.each([ { expected: { result: 'completed', status: 'completed', statusReason: null }, name: 'exact delivery', required: { baseUnits: '5000000', formatted: '5' }, }, { expected: { result: 'completed', status: 'completed', statusReason: null }, name: 'excess delivery', required: { baseUnits: '4999999', formatted: '4.999999' }, }, { expected: { result: 'ignored', status: 'action-required', statusReason: { code: 'subsidy_failed', message: 'Destination subsidy settlement is unavailable.', }, }, name: 'delivery shortfall', required: { baseUnits: '5000001', formatted: '5.000001' }, }, ])('reconciles $name without a subsidy signer', async ({ expected, required }) => { const database = TestApp.database() await Catalog.publish(database, { ...TestRoutes.data, chains: TestRoutes.data.chains.map((chain) => chain.id === 'eip155:8453' ? { ...chain, rpcUrls: [source.rpcUrl] } : chain, ), }) const snapshot = TestRoutes.transferSnapshot() const providerTransactionHash = '0xdf95be7fd90f6530144053d3e8ee2c73537c826eea1bf8ef2dbbc3d6c8f30712' const receipt = await source.client.getTransactionReceipt({ hash: providerTransactionHash }) const transfer = await Transfer.create(database, { apiKeyId: 'key_test', environment: 'production', orgId: 'org_test', providerState: { destinationBlockNumber: receipt.blockNumber.toString(), routeConfiguration: outboundRouteConfiguration, }, snapshot: TestRoutes.transferSnapshot({ destinationAmount: { ...amount, baseUnits: '5000000', formatted: '5' }, destinationAmountMin: { ...amount, baseUnits: '5000000', formatted: '5' }, destinationAmountRequired: { ...amount, ...required }, destinationChain: snapshot.sourceChain, destinationToken: snapshot.sourceToken, provider: { id: 'stargate', name: 'Stargate' }, recipient: '0x10a3097afe2c7ebc8f1c621bc9bb371c4dff76a8', sender: '0x85b0b03ed6fc421ca55aaa3a55723a70701908b8', sourceAmount: { ...amount, ...required }, sourceChain: snapshot.destinationChain, sourceToken: snapshot.destinationToken, subsidize: true, }), }) await Transfer.registerSourceTransactions(database, { chainId: 'eip155:4217', id: transfer.id, providerState: { stargate: { guid: '0xc456bbb98940eee646c7f9e12d6cd211fe28c510e335f10c2381532358abdd0c', sourceEid: 30_410, }, }, transactionHashes: ['0x9664a5b8dff87c44181abb174b295fd91b2eedae5cff6dacfbd83ab3d8350e88'], }) const tracker = TransferReconciliation.createTracker({ db: database, providers }) const result = await tracker.reconcile({ transferId: transfer.id, type: 'routes:transfer:reconcile', }) const record = await RoutesTransfers.get(database, transfer.id) expect({ result: result.type, status: record?.status, statusReason: record?.statusReason, }).toEqual(expected) expect({ destinationAmount: record?.snapshot.destinationAmount?.baseUnits, providerDelivered: Boolean(record?.providerDeliveredAt), subsidyAccount: record?.subsidyAccount, subsidyAmount: record?.subsidyAmount, subsidyTransactionHash: record?.subsidyTransactionHash, }).toMatchInlineSnapshot(` { "destinationAmount": "5000000", "providerDelivered": true, "subsidyAccount": null, "subsidyAmount": null, "subsidyTransactionHash": null, } `) if (expected.status === 'completed') expect(record?.snapshot.destinationTransactionHashes).toEqual([providerTransactionHash]) }) test('tops up a verified Tempo to Base delivery before completion', async () => { const database = TestApp.database() const catalog = await Catalog.publish(database, { ...TestRoutes.data, chains: TestRoutes.data.chains.map((chain) => { if (chain.id === 'eip155:4217') return { ...chain, rpcUrls: [destination.rpcUrl] } if (chain.id === 'eip155:8453') return { ...chain, rpcUrls: [source.rpcUrl] } return chain }), }) const snapshot = TestRoutes.transferSnapshot() const account = Anvil.getAccount(5) const recipient = '0x10a3097afe2c7ebc8f1c621bc9bb371c4dff76a8' const sourceTransactionHash = '0x9664a5b8dff87c44181abb174b295fd91b2eedae5cff6dacfbd83ab3d8350e88' const providerTransactionHash = '0xdf95be7fd90f6530144053d3e8ee2c73537c826eea1bf8ef2dbbc3d6c8f30712' const receipt = await source.client.getTransactionReceipt({ hash: providerTransactionHash }) await Anvil.setErc20Balance(source.client, { address: account.address, amount: 1_000_000n, slot: 9n, token: snapshot.sourceToken.address as `0x${string}`, }) await Organizations.create(database, { id: 'org_test', name: 'Test' }) await RoutesSubsidies.upsertOrganization(database, { frequency: 'tx', maxAmount: '5', orgId: 'org_test', }) await StripeCustomers.create(database, { orgId: 'org_test', stripeCustomerId: 'cus_transfer_subsidy', }) await StripeCustomers.setStatus(database, 'org_test', 'active') const inventory = TransferSubsidy.createInventory({ account: account.address, fetch: globalThis.fetch, policy: { maxAmount: '5' }, }) const subsidies = TransferSubsidy.create({ account, fetch: globalThis.fetch, policy: { maxAmount: '5' }, }) const rotatedSubsidies = TransferSubsidy.create({ account: Anvil.getAccount(6), fetch: globalThis.fetch, policy: { maxAmount: '5' }, }) const transfer = await database.transaction(async (tx) => { const { record } = await Transfer.reserveSubsidyCommitmentIn( tx, { apiKeyId: 'key_test', environment: 'production', orgId: 'org_test', providerState: { destinationBlockNumber: receipt.blockNumber.toString(), routeConfiguration: outboundRouteConfiguration, }, snapshot: TestRoutes.transferSnapshot({ destinationAmount: { ...amount, baseUnits: '5000000', formatted: '5' }, destinationAmountMin: { ...amount, baseUnits: '4999999', formatted: '4.999999' }, destinationAmountRequired: { ...amount, baseUnits: '5000501', formatted: '5.000501' }, destinationChain: snapshot.sourceChain, destinationToken: snapshot.sourceToken, provider: { id: 'stargate', name: 'Stargate' }, quote: { expiresAt: '2099-09-01T04:00:00.000Z', sampledAt: '2026-09-01T03:00:00.000Z', }, recipient, sender: '0x85b0b03ed6fc421ca55aaa3a55723a70701908b8', sourceAmount: { ...amount, baseUnits: '5000501', formatted: '5.000501' }, sourceChain: snapshot.destinationChain, sourceToken: snapshot.destinationToken, subsidize: true, }), }, { account: inventory.account, chainId: snapshot.sourceChain.id, check: ({ reservations }) => inventory.check({ amount: 502n, chain: catalog.chainsByKey.get(snapshot.sourceChain.id)!, recipient, reservations, token: snapshot.sourceToken.address as `0x${string}`, }), subsidyCommitmentAmount: { ...amount, baseUnits: '502', formatted: '0.000502' }, tokenAddress: snapshot.sourceToken.address, }, ) return record }) await RoutesSubsidies.removeOrganization(database, 'org_test') await StripeCustomers.setStatus(database, 'org_test', 'past_due') await Transfer.registerSourceTransactions(database, { chainId: 'eip155:4217', id: transfer.id, providerState: { stargate: { guid: '0xc456bbb98940eee646c7f9e12d6cd211fe28c510e335f10c2381532358abdd0c', sourceEid: 30_410, }, }, transactionHashes: [sourceTransactionHash], }) const counts: Parameters[] = [] const dispatches: TransferReconciliation.Message[] = [] const tracker = TransferReconciliation.createTracker({ db: database, async dispatch(message) { dispatches.push(message) }, metrics: Metrics.from({ count(...parameters) { counts.push(parameters) }, flush() {}, gauge() {}, histogram() {}, }), providers, subsidies: [rotatedSubsidies, subsidies], }) await expect( tracker.reconcile({ transferId: transfer.id, type: 'routes:transfer:reconcile' }), ).resolves.toEqual({ type: 'pending' }) // Bound each mining RPC while preserving all 64 confirmations. for (let i = 0; i < 8; i++) await source.client.mine({ blocks: 8 }) await expect(tracker.reconcile(dispatches[0]!)).resolves.toEqual({ type: 'completed' }) const completed = await RoutesTransfers.get(database, transfer.id) expect(completed).toMatchObject({ snapshot: { destinationAmount: { baseUnits: '5000501' }, destinationTransactionHashes: [ providerTransactionHash, expect.stringMatching(/^0x[0-9a-f]{64}$/), ], }, status: 'completed', subsidyAmount: { baseUnits: '501' }, subsidyCommitmentAmount: { baseUnits: '502' }, subsidyMeterReportedAt: null, subsidyTransactionHash: expect.stringMatching(/^0x[0-9a-f]{64}$/), }) expect(counts.filter(([name]) => name === 'routes_transfer_subsidy_count')) .toMatchInlineSnapshot(` [ [ "routes_transfer_subsidy_count", 1, { "outcome": "prepared", "provider": "stargate", }, ], [ "routes_transfer_subsidy_count", 1, { "outcome": "broadcast", "provider": "stargate", }, ], [ "routes_transfer_subsidy_count", 1, { "outcome": "completed", "provider": "stargate", }, ], ] `) const shared = await Transfer.create(database, { apiKeyId: 'key_shared', environment: 'production', orgId: 'org_shared', providerState: transfer.providerState!, snapshot: transfer.snapshot, subsidyCommitment: { account: rotatedSubsidies.account, amount: transfer.subsidyCommitmentAmount!, chainId: transfer.subsidyChainId!, nativeAmount: '100', tokenAddress: transfer.subsidyTokenAddress!, }, }) expect( ( await Transfer.registerSourceTransactions(database, { chainId: 'eip155:4217', id: shared.id, providerState: { stargate: { guid: '0xc456bbb98940eee646c7f9e12d6cd211fe28c510e335f10c2381532358abdd0c', sourceEid: 30_410, }, }, transactionHashes: [sourceTransactionHash], }) ).type, ).toBe('registered') const nonce = await source.client.getTransactionCount({ address: account.address }) expect( await tracker.reconcile({ transferId: shared.id, type: 'routes:transfer:reconcile' }), ).toEqual({ type: 'completed' }) const sharedCompleted = await RoutesTransfers.get(database, shared.id) expect(sharedCompleted).toMatchObject({ snapshot: { destinationAmount: { baseUnits: '5000501' }, destinationTransactionHashes: completed!.snapshot.destinationTransactionHashes, }, status: 'completed', subsidyAmount: null, subsidyCommitmentAmount: null, subsidyTransactionHash: completed!.subsidyTransactionHash, }) expect(await source.client.getTransactionCount({ address: account.address })).toBe(nonce) expect((await RoutesTransfers.listUnreportedSubsidies(database)).map(({ id }) => id)).toEqual([ transfer.id, ]) expect(counts.filter(([, , tags]) => tags?.['outcome'] === 'shared')).toHaveLength(1) const { transferSubsidy: _transaction, transferSubsidyOwnerId: _owner, ...providerState } = sharedCompleted!.providerState! const conflicting = await Transfer.create(database, { apiKeyId: 'key_conflicting', environment: 'production', orgId: 'org_conflicting', providerState, snapshot: { ...transfer.snapshot, destinationAmountRequired: { ...amount, baseUnits: '5000502', formatted: '5.000502' }, }, subsidyCommitment: { account: rotatedSubsidies.account, amount: transfer.subsidyCommitmentAmount!, chainId: transfer.subsidyChainId!, nativeAmount: '100', tokenAddress: transfer.subsidyTokenAddress!, }, }) await Transfer.registerSourceTransactions(database, { chainId: 'eip155:4217', id: conflicting.id, providerState: {}, transactionHashes: [sourceTransactionHash], }) await expect( tracker.reconcile({ transferId: conflicting.id, type: 'routes:transfer:reconcile' }), ).rejects.toBeInstanceOf(Transfer.SubsidyEvidenceError) expect(counts.filter(([, , tags]) => tags?.['outcome'] === 'evidence_conflict')).toHaveLength(1) }) test('completes a registered transfer from Tempo evidence', async () => { const transfer = await Transfer.create(db, { apiKeyId: 'key_test', environment: 'production', orgId: 'org_test', providerState: { destinationBlockNumber: ( await destination.client.getTransactionReceipt({ hash: destinationTransactionHash }) ).blockNumber.toString(), routeConfiguration, }, snapshot: TestRoutes.transferSnapshot({ destinationAmount: amount, destinationAmountMin: amount, provider: { id: 'stargate', name: 'Stargate' }, recipient, sender, sourceAmount: amount, }), }) await Transfer.registerSourceTransactions(db, { chainId: 'eip155:8453', id: transfer.id, providerState: { stargate: { guid, sourceEid: 30_184 } }, transactionHashes: [sourceTransactionHash], }) await Catalog.publish(db, { ...TestRoutes.data, chains: TestRoutes.data.chains.map((chain) => chain.id === 'eip155:4217' ? { ...chain, rpcUrls: [destination.rpcUrl] } : chain, ), routes: TestRoutes.data.routes.filter((route) => route.providerId !== 'stargate'), }) const tracker = TransferReconciliation.createTracker({ db, providers }) await expect( tracker.reconcile({ transferId: transfer.id, type: 'routes:transfer:reconcile' }), ).resolves.toEqual({ type: 'completed' }) const completed = await Transfer.transition(db, { expectedVersion: 2, id: transfer.id, status: 'completed', }) expect(completed.type).toBe('stale') if (completed.type !== 'stale') return expect({ destinationTransactionHashes: completed.current.snapshot.destinationTransactionHashes, status: completed.current.status, version: completed.current.version, }).toEqual({ destinationTransactionHashes: [destinationTransactionHash], status: 'completed', version: 3, }) }) }) describe('tick', () => { test('expires abandoned subsidy commitments after the recovery window', async () => { const database = TestApp.database() const transfer = await Transfer.create(database, { apiKeyId: 'key_test', environment: 'production', orgId: 'org_expired_commitment', snapshot: TestRoutes.transferSnapshot({ quote: { expiresAt: '2026-01-01T00:01:00.000Z', sampledAt: '2026-01-01T00:00:00.000Z' }, subsidize: true, }), subsidyCommitment: { account: `0x${'55'.repeat(20)}`, amount: { baseUnits: '3001', currency: 'USD', decimals: 6, formatted: '0.003001', }, chainId: 'eip155:8453', nativeAmount: '100', tokenAddress: `0x${'66'.repeat(20)}`, }, }) const counts: Parameters[] = [] const tracker = TransferReconciliation.createTracker({ db: database, async dispatch() {}, metrics: Metrics.from({ count(...parameters) { counts.push(parameters) }, flush() {}, gauge() {}, histogram() {}, }), providers: [], }) await expect(tracker.tick()).resolves.toEqual({ checked: 0 }) expect(await RoutesTransfers.get(database, transfer.id)).toMatchObject({ status: 'expired', version: 2, }) expect(counts).toContainEqual([ 'routes_transfer_reconciliation_count', 1, { outcome: 'expired', provider: 'relay' }, ]) }) test('advances through bounded recovery pages', async () => { const database = TestApp.database() await TestRoutes.publish(database) for (let index = 0; index < 26; index++) { const transfer = await Transfer.create(database, { apiKeyId: 'key_test', environment: 'production', id: Transfer.generateId(new Date(Date.UTC(2026, 0, 1, 0, 0, index))), orgId: 'org_test', providerState: {}, snapshot: TestRoutes.transferSnapshot({ provider: { id: 'stargate', name: 'Stargate' }, }), }) await Transfer.transition(database, { expectedVersion: 1, id: transfer.id, status: 'processing', }) } type Dispatch = { delaySeconds: number; message: TransferReconciliation.Message } const dispatches: Dispatch[] = [] let available = false const tracker = TransferReconciliation.createTracker({ db: database, dispatch: async (message, options) => { dispatches.push({ delaySeconds: options.delaySeconds, message }) if (!available) throw new Error('queue unavailable') }, providers, }) await expect(tracker.tick()).rejects.toThrow('queue unavailable') available = true await expect(tracker.tick()).resolves.toEqual({ checked: 25 }) await expect(tracker.tick()).resolves.toEqual({ checked: 1 }) expect(dispatches).toHaveLength(27) expect(dispatches[0]).toEqual(dispatches[1]) expect(new Set(dispatches.map(({ delaySeconds }) => delaySeconds))).toEqual(new Set([0])) expect(new Set(dispatches.map(({ message }) => message.transferId)).size).toBe(26) }) })