import { tempoMainnet } from 'viem/tempo/chains' import * as Anvil from '../../../test/Anvil.js' import * as TestApp from '../../../test/App.js' import * as TestFunding from '../../../test/Funding.js' import * as Catalog from './Catalog.js' import * as Transfer from './Transfer.js' import * as TransferReconciliation from './TransferReconciliation.js' const db = TestApp.database() const destinationTransactionHash = '0x3dfaba0a78b8f4bc09e55e05211c8120729e7b6879efe32613883befbca666da' const guid = '0x5549d117c8b23f0ecdf05c2604ed989cf0034f378707bd81319e93834f2f85f2' const recipient = '0x7212623e4cff9ad010d28aa065fad2bf58578783' const sender = '0x52b650a62e384f68880d0a1dc2b84dfed47d5660' const sourceTransactionHash = '0xfd71359adf5095f2ab512a19e0a2df6c8cc0db14ac8a52286d61287daabdccd4' const routeConfiguration = TestFunding.snapshot.routesByProvider .get('stargate')! .find((candidate) => candidate.route.source.chain.id === 'eip155:8453')!.configuration! describe.skipIf(!Anvil.available)('createTracker', () => { let destination: Anvil.start.ReturnType beforeAll(async () => { destination = await Anvil.start({ chain: tempoMainnet, forkUrl: tempoMainnet.rpcUrls.default.http[0]!, }) await Catalog.publish(db, { ...TestFunding.data, chains: TestFunding.data.chains.map((chain) => chain.id === 'eip155:4217' ? { ...chain, rpcUrls: [destination.rpcUrl] } : chain, ), }) }) afterAll(async () => { await destination?.stop() }) test('completes a registered transfer from Tempo evidence', async () => { const amount = { baseUnits: '200000', currency: 'USD', decimals: 6, formatted: '0.2', } as const 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: TestFunding.transferSnapshot({ destinationAmount: amount, destinationAmountMin: amount, provider: { id: 'stargate', name: 'Stargate' }, recipient, sender, sourceAmount: amount, }), }) await Transfer.registerSourceTransaction(db, { chainId: 'eip155:8453', id: transfer.id, providerState: { stargate: { guid, sourceEid: 30_184 } }, transactionHash: sourceTransactionHash, }) await Catalog.publish(db, { ...TestFunding.data, chains: TestFunding.data.chains.map((chain) => chain.id === 'eip155:4217' ? { ...chain, rpcUrls: [destination.rpcUrl] } : chain, ), routes: TestFunding.data.routes.filter((route) => route.providerId !== 'stargate'), }) const tracker = TransferReconciliation.createTracker({ db }) await expect( tracker.reconcile({ transferId: transfer.id, type: 'funding: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('advances through bounded recovery pages', async () => { const database = TestApp.database() await TestFunding.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: TestFunding.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') }, }) 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) }) })