import { Address, Hash, Hex } from 'ox' import * as TestApp from '../../../../test/App.js' import * as TestRelay from '../../../../test/Relay.js' import * as TestRoutes from '../../../../test/Routes.js' import * as RoutesDepositRequestObservations from '../../../db/tables/routesDepositRequestObservations.js' import * as DepositAddress from '../DepositAddress.js' import * as RoutesProvider from '../Provider.js' import * as Relay from './relay.js' const apiKey = process.env.RELAY_API_KEY const db = TestApp.database() const sender = '0x1111111111111111111111111111111111111111' const recipient = '0x2222222222222222222222222222222222222222' const webhookNow = new Date('2027-01-01T00:00:00.000Z') const webhookSecret = 'relay-secret' function webhookPayload(address: string | null = 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8') { return JSON.stringify({ data: { depositAddress: address ? { address, depositAddressType: 'open', depositor: null } : null, requestId: `0x${'aa'.repeat(32)}`, status: 'pending', updatedAt: webhookNow.getTime() - 1_000, }, event: 'request.status.updated', timestamp: webhookNow.getTime(), }) } function signedWebhook(body: string) { const timestamp = String(webhookNow.getTime()) const signature = Hash.hmac256( Hex.fromString(webhookSecret), Hex.fromString(`${timestamp}.${body}`), ).slice(2) return { body, headers: new Headers({ 'x-signature-sha256': signature, 'x-signature-timestamp': timestamp, }), } } function webhookProvider( dispatch: (message: RoutesProvider.Webhook.receive.Dispatchable) => Promise, options: webhookProvider.Options = {}, ) { return Relay.relay({ apiKey: webhookSecret, webhook: { db, dispatch, ...(options.onResult ? { onResult: options.onResult } : {}) }, }) } declare namespace webhookProvider { /** Optional webhook observation hooks used by tests. */ type Options = { /** Receives bounded webhook dispositions. */ onResult?: Relay.relay.Webhook['onResult'] | undefined } } function candidateFor(provider: RoutesProvider.Provider) { const candidate = RoutesProvider.getQuoteCandidates({ catalog: TestRoutes.snapshot, destinationToken: 'USDC.e', providers: [provider], sourceAmount: '5000000', sourceAmountUnits: 'baseUnits', sourceChain: 'base', sourceToken: 'USDC', }).find((entry) => entry.id === 'base-usdc-tempo-usdce-relay') if (!candidate) throw new Error('Expected a relay candidate.') return candidate } function prepareInput( provider: RoutesProvider.Provider, ): RoutesProvider.prepareTransfer.Parameters { return { candidate: candidateFor(provider), method: 'transaction', mode: 'exactSource', recipient, sender, slippageBps: 300, } } function depositAddressInput( provider: RoutesProvider.Provider, ): RoutesProvider.createDepositAddress.Parameters { const candidate = RoutesProvider.getDepositAddressCandidates({ catalog: TestRoutes.snapshot, destinationChainId: 4217, destinationToken: '0x20c00000000000000000000014f22ca97301eb73', providers: [provider], sourceChain: 'tron', sourceToken: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', })[0]?.candidate if (!candidate) throw new Error('Expected a Relay deposit-address candidate.') return { amount: '100000000', candidate, recipient, refundAddress: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8', subsidize: false, } } // Requests against the live Relay API (quote-only; nothing signs, submits, or // funds). Runs whenever RELAY_API_KEY is in the repo-root .env; CI without the // key skips. Live amounts change per quote, so assertions stay shape-level. describe.skipIf(!apiKey)('relay.prepareTransfer (live)', () => { let requestBody: Record let result: RoutesProvider.prepareTransfer.ReturnType beforeAll(async () => { const provider = Relay.relay({ apiKey, fetch: async (input, init) => { if (typeof init?.body !== 'string') throw new Error('Expected a JSON request body.') requestBody = JSON.parse(init.body) as Record return globalThis.fetch(input, init) }, }) result = await provider.prepareTransfer!(prepareInput(provider), new AbortController().signal) }) test('forwards the caller terms', () => { expect(requestBody).toMatchObject({ amount: '5000000', destinationChainId: 4217, originChainId: 8453, recipient, slippageTolerance: '300', tradeType: 'EXACT_INPUT', user: sender, }) }) test('prepares bounded executable calls with private correlation', () => { expect(result.action.type).toBe('evm:calls') expect(result.action.calls.length).toBeGreaterThan(0) for (const call of result.action.calls) { expect(call.to).toMatch(/^0x[0-9a-fA-F]{40}$/) expect(call.data).toMatch(/^0x[0-9a-fA-F]*$/) expect(call.value).toBe('0x0') } expect(result.correlation?.requestId).toMatch(/^0x[0-9a-f]{64}$/) expect(result.correlation?.checkEndpoint).toContain('/intents/status/v3') expect(BigInt(result.destinationAmount) > 0n).toBe(true) expect(BigInt(result.destinationAmountMin) > 0n).toBe(true) expect(BigInt(result.destinationAmountMin) <= BigInt(result.destinationAmount)).toBe(true) // Relay returns no quote validity window; the fan-out applies the default. expect(result.expiresAt).toBeUndefined() expect(Number.isNaN(Date.parse(result.sampledAt))).toBe(false) }) }) describe.skipIf(!apiKey)('relay.createDepositAddress (live)', () => { let requestBody: Record let result: RoutesProvider.createDepositAddress.ReturnType beforeAll(async () => { const provider = Relay.relay({ apiKey, fetch: async (input, init) => { if (typeof init?.body !== 'string') throw new Error('Expected a JSON request body.') requestBody = JSON.parse(init.body) as Record return globalThis.fetch(input, init) }, subsidies: { maxAmount: '100' }, }) result = await provider.createDepositAddress!( { ...depositAddressInput(provider), subsidize: true }, new AbortController().signal, ) }) test('forwards the reusable exact-input route', () => { expect(requestBody).toMatchInlineSnapshot(` { "amount": "100000000", "destinationChainId": 4217, "destinationCurrency": "0x20c00000000000000000000014f22ca97301eb73", "originChainId": 728126428, "originCurrency": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "recipient": "0x2222222222222222222222222222222222222222", "refundTo": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8", "subsidizeFees": true, "tradeType": "EXACT_INPUT", "useDepositAddress": true, "user": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8", } `) }) test('provisions the curated Tron USDT to Tempo USDT0 route', () => { expect(result).toMatchInlineSnapshot( { address: expect.any(String), correlation: { checkEndpoint: expect.any(String), requestId: expect.any(String), }, destinationAmount: expect.any(String), destinationAmountMin: expect.any(String), fees: expect.any(Array), sampledAt: expect.any(String), }, ` { "address": Any, "correlation": { "checkEndpoint": Any, "requestId": Any, }, "destinationAmount": Any, "destinationAmountMin": Any, "fees": Any, "sampledAt": Any, } `, ) }) test('provisions the curated Solana USDT to Tempo USDT0 route', async () => { let requestBody: Record const provider = Relay.relay({ apiKey, fetch: async (input, init) => { if (typeof init?.body !== 'string') throw new Error('Expected a JSON request body.') requestBody = JSON.parse(init.body) as Record return globalThis.fetch(input, init) }, }) const candidate = RoutesProvider.getDepositAddressCandidates({ catalog: TestRoutes.snapshot, destinationChainId: 4217, destinationToken: '0x20c00000000000000000000014f22ca97301eb73', providers: [provider], sourceChain: 'solana', sourceToken: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', })[0]?.candidate if (!candidate) throw new Error('Expected a Relay Solana deposit-address candidate.') const result = await provider.createDepositAddress!( { amount: '100000000', candidate, recipient, refundAddress: '11111111111111111111111111111111', subsidize: false, }, new AbortController().signal, ) expect({ requestBody: requestBody!, result }).toMatchInlineSnapshot( { result: { address: expect.any(String), correlation: { checkEndpoint: expect.any(String), requestId: expect.any(String), }, destinationAmount: expect.any(String), destinationAmountMin: expect.any(String), fees: expect.any(Array), sampledAt: expect.any(String), }, }, ` { "requestBody": { "amount": "100000000", "destinationChainId": 4217, "destinationCurrency": "0x20c00000000000000000000014f22ca97301eb73", "originChainId": 792703809, "originCurrency": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", "recipient": "0x2222222222222222222222222222222222222222", "refundTo": "11111111111111111111111111111111", "subsidizeFees": false, "tradeType": "EXACT_INPUT", "useDepositAddress": true, "user": "11111111111111111111111111111111", }, "result": { "address": Any, "correlation": { "checkEndpoint": Any, "requestId": Any, }, "destinationAmount": Any, "destinationAmountMin": Any, "fees": Any, "sampledAt": Any, }, } `, ) }) test('lists child requests for the reusable address', async () => { let requestUrl: URL | undefined const provider = Relay.relay({ apiKey, fetch: (input, init) => { requestUrl = new URL(input instanceof Request ? input.url : input) return globalThis.fetch(input, init) }, }) const requests = await provider.listDepositAddressRequests!( { address: result.address }, new AbortController().signal, ) requestUrl?.searchParams.set('depositAddress', '
') expect({ requestUrl: requestUrl?.toString(), requests }).toMatchInlineSnapshot(` { "requestUrl": "https://api.relay.link/requests/v3?depositAddress=%3Caddress%3E&includeChildRequests=true&limit=50&sortBy=updatedAt&sortDirection=desc", "requests": { "requests": [], }, } `) }) }) describe('parseDepositAddressRequests', () => { test('accepts nullable fields from in-flight requests', () => { expect( Relay.parseDepositAddressRequests({ requests: [ { createdAt: '2026-08-05T06:13:34.181Z', data: { outTxs: [] }, depositAddress: { depositTxHash: null }, id: 'request-waiting', protocol: null, status: 'waiting', updatedAt: '2026-08-05T06:13:37.650Z', }, { createdAt: '2026-08-05T06:13:34.181Z', data: { outTxs: [] }, depositAddress: null, id: 'request-depositing', protocol: null, status: 'depositing', updatedAt: '2026-08-05T06:13:37.650Z', }, ], }), ).toMatchInlineSnapshot(` { "requests": [ { "createdAt": "2026-08-05T06:13:34.181Z", "data": { "outTxs": [], }, "depositAddress": { "depositTxHash": null, }, "id": "request-waiting", "protocol": null, "status": "waiting", "updatedAt": "2026-08-05T06:13:37.650Z", }, { "createdAt": "2026-08-05T06:13:34.181Z", "data": { "outTxs": [], }, "depositAddress": null, "id": "request-depositing", "protocol": null, "status": "depositing", "updatedAt": "2026-08-05T06:13:37.650Z", }, ], } `) }) test('reports the invalid provider field', () => { expect(() => Relay.parseDepositAddressRequests({ requests: null })) .toThrowErrorMatchingInlineSnapshot(` [RoutesProvider.ProviderPayloadError: ✖ Invalid input: expected array, received null → at requests] `) }) }) describe('relay.listDepositAddressRequests', () => { test('preserves completed deposit evidence when fee sponsorship is null', async () => { const server = await TestRelay.createServer((_request, response) => { response.writeHead(200, { 'content-type': 'application/json' }) response.end( JSON.stringify({ requests: [ { createdAt: '2026-09-14T19:20:46.206Z', data: { feeSponsorship: null, outTxs: [{ txHash: `0x${'bb'.repeat(32)}` }], }, depositAddress: { depositTxHash: `0x${'aa'.repeat(32)}` }, id: 'relay-unsponsored-request', status: 'success', updatedAt: '2026-09-14T19:21:18.075Z', }, ], }), ) }) try { const provider = Relay.relay({ apiKey: 'relay-api-key', baseUrl: server.url }) const candidate = candidateFor(provider) await expect( provider.listDepositAddressRequests!( { address: `0x${'11'.repeat(20)}`, destinationToken: candidate.destinationToken, sourceToken: candidate.sourceToken, }, new AbortController().signal, ), ).resolves.toMatchInlineSnapshot(` { "requests": [ { "createdAt": "2026-09-14T19:20:46.206Z", "destinationTransactionHashes": [ "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", ], "id": "relay-unsponsored-request", "refundTransactionHashes": [], "sourceTransactionHashes": [ "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ], "status": "success", "updatedAt": "2026-09-14T19:21:18.075Z", }, ], } `) } finally { await server.closeAsync() } }) test('returns the actual provider subsidy amount and token', async () => { const server = await TestRelay.createServer((_request, response) => { response.writeHead(200, { 'content-type': 'application/json' }) response.end( JSON.stringify({ requests: [ { createdAt: '2026-09-02T00:00:00.000Z', data: { feeSponsorship: { actual: { sponsorPayment: { address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', amount: '23838', chainId: 8453, }, }, }, outTxs: [], }, id: 'relay-sponsored-request', status: 'success', updatedAt: '2026-09-02T00:00:10.000Z', }, ], }), ) }) try { const provider = Relay.relay({ apiKey: 'relay-api-key', baseUrl: server.url }) const candidate = candidateFor(provider) await expect( provider.listDepositAddressRequests!( { address: `0x${'11'.repeat(20)}`, destinationToken: candidate.destinationToken, sourceToken: candidate.sourceToken, }, new AbortController().signal, ), ).resolves.toMatchInlineSnapshot(` { "requests": [ { "createdAt": "2026-09-02T00:00:00.000Z", "destinationTransactionHashes": [], "id": "relay-sponsored-request", "refundTransactionHashes": [], "sourceTransactionHashes": [], "status": "success", "subsidy": { "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, }, }, "updatedAt": "2026-09-02T00:00:10.000Z", }, ], } `) } finally { await server.closeAsync() } }) test('omits unfinished output transaction hashes', async () => { const server = await TestRelay.createServer((_request, response) => { response.writeHead(200, { 'content-type': 'application/json' }) response.end( JSON.stringify({ requests: [ { createdAt: '2026-09-01T22:31:00.000Z', data: { outTxs: [{}, { txHash: null }, { txHash: '' }, { txHash: `0x${'aa'.repeat(32)}` }], }, id: 'relay-in-flight-request', status: 'submitted', updatedAt: '2026-09-01T22:31:30.000Z', }, ], }), ) }) try { const provider = Relay.relay({ apiKey: 'relay-api-key', baseUrl: server.url }) await expect( provider.listDepositAddressRequests!( { address: `0x${'11'.repeat(20)}` }, new AbortController().signal, ), ).resolves.toMatchInlineSnapshot(` { "requests": [ { "createdAt": "2026-09-01T22:31:00.000Z", "destinationTransactionHashes": [ "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ], "id": "relay-in-flight-request", "refundTransactionHashes": [], "sourceTransactionHashes": [], "status": "submitted", "updatedAt": "2026-09-01T22:31:30.000Z", }, ], } `) } finally { await server.closeAsync() } }) }) describe('relay.createDepositAddress', () => { test('rejects subsidized provisioning outside the configured limit', async () => { const provider = Relay.relay({ subsidies: { maxAmount: '1' } }) await expect( provider.createDepositAddress!( { ...depositAddressInput(provider), subsidize: true }, new AbortController().signal, ), ).rejects.toThrowErrorMatchingInlineSnapshot(`[RoutesProvider.ProviderUnavailableError]`) }) }) describe('relay.prepareTransfer', () => { test('refuses methods and modes it cannot prepare', async () => { const provider = Relay.relay() await expect( provider.prepareTransfer!( { ...prepareInput(provider), method: 'depositAddress' }, new AbortController().signal, ), ).rejects.toThrow(RoutesProvider.ProviderConfigurationError) }) }) describe('verifyWebhook', () => { beforeEach(() => vi.useFakeTimers({ now: webhookNow })) afterEach(() => vi.useRealTimers()) test('authenticates the raw body and returns only the reconciliation hint', () => { const { webhook } = webhookProvider(async () => {}) if (!webhook) throw new Error('Expected Relay webhook support.') expect(webhook.verify(signedWebhook(webhookPayload()))).toMatchInlineSnapshot(` { "address": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8", "providerStatus": "pending", "providerUpdatedAt": "2026-12-31T23:59:59.000Z", "requestId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "sentAt": "2027-01-01T00:00:00.000Z", } `) }) test('rejects changed bodies and stale timestamps', () => { const { webhook } = webhookProvider(async () => {}) if (!webhook) throw new Error('Expected Relay webhook support.') const valid = signedWebhook(webhookPayload()) expect(() => webhook.verify({ ...valid, body: `${valid.body} ` }), ).toThrowErrorMatchingInlineSnapshot(`[Routes.Provider.Relay.WebhookSignatureError]`) valid.headers.set( 'x-signature-timestamp', String(Number(valid.headers.get('x-signature-timestamp')) - 300_001), ) expect(() => webhook.verify(valid)).toThrowErrorMatchingInlineSnapshot( `[Routes.Provider.Relay.WebhookSignatureError]`, ) }) }) describe('receiveWebhook', () => { beforeEach(() => vi.useFakeTimers({ now: webhookNow })) afterEach(() => vi.useRealTimers()) test('resolves the stored address and queues a bounded hint', async () => { const route = TestRoutes.transferSnapshot() const providerAddress = Address.checksum(`0x${'ab'.repeat(20)}`) const record = await DepositAddress.create(db, { deliveryStrategy: 'provider', environment: 'production', now: webhookNow, orgId: 'org_relay_webhook', providerOutputToken: TestRoutes.providerOutputToken(), snapshot: TestRoutes.depositAddressSnapshot({ address: providerAddress, provider: { id: 'relay', name: 'Relay' }, sourceChain: route.sourceChain, sourceToken: route.sourceToken, }), }) const dispatched: RoutesProvider.Webhook.receive.Dispatchable[] = [] const { webhook } = webhookProvider(async (message) => { dispatched.push(message) }) if (!webhook) throw new Error('Expected Relay webhook support.') const result = await webhook.receive( signedWebhook(webhookPayload(Address.checksum(record.address))), ) const observation = await RoutesDepositRequestObservations.get(db, { depositAddressId: record.id, providerRequestId: `0x${'aa'.repeat(32)}`, }) expect({ dispatched, observation, result }).toMatchInlineSnapshot( { dispatched: [{ addressId: expect.any(String) }], observation: { depositAddressId: expect.any(String) }, result: { addressId: expect.any(String) }, }, ` { "dispatched": [ { "addressId": Any, "requestId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "trigger": "webhook", "type": "routes:deposit-address:reconcile", "webhookTiming": { "enqueuedAt": "2027-01-01T00:00:00.000Z", "providerStatus": "pending", "providerUpdatedAt": "2026-12-31T23:59:59.000Z", "receivedAt": "2027-01-01T00:00:00.000Z", "sentAt": "2027-01-01T00:00:00.000Z", }, }, ], "observation": { "depositAddressId": Any, "pollObservedAt": null, "providerRequestId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "webhookReceivedAt": "2027-01-01T00:00:00.000Z", }, "result": { "addressId": Any, "type": "queued", }, } `, ) }) test('acknowledges unattributed and unknown addresses without dispatching', async () => { const dispatched: RoutesProvider.Webhook.receive.Dispatchable[] = [] const outcomes: Relay.relay.WebhookResult[] = [] const { webhook } = webhookProvider( async (message) => { dispatched.push(message) }, { onResult: (outcome) => outcomes.push(outcome) }, ) if (!webhook) throw new Error('Expected Relay webhook support.') const ignored = await webhook.receive(signedWebhook(webhookPayload(null))) const unknown = await webhook.receive(signedWebhook(webhookPayload('TUnknownRelayAddress'))) expect({ dispatched, ignored, outcomes, unknown }).toMatchInlineSnapshot(` { "dispatched": [], "ignored": { "type": "ignored", }, "outcomes": [ "ignored", "unknown_address", ], "unknown": { "type": "unknown_address", }, } `) }) })