tapimo 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +6064 -4829
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +1 -0
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.js +2 -2
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +1008 -917
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.d.ts +48 -0
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +2 -1
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +92 -3
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +69 -7
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +74 -74
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +69 -69
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/Zones.d.ts +12 -12
- package/dist/apps/Zones.js +3 -3
- package/dist/apps/data/App.d.ts +2909 -2523
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +22 -22
- package/dist/apps/data/routes/earn.d.ts +183 -7
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +434 -75
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +15 -0
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +180 -114
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +18 -19
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +93 -93
- package/dist/apps/data/routes/transfers.d.ts +12 -12
- package/dist/apps/data/routes/valuation.d.ts +30 -30
- package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +805 -102
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +47 -5
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/funding/App.d.ts +456 -45
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/App.js +1 -1
- package/dist/apps/funding/App.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +654 -73
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
- package/dist/apps/funding/routes/transfers.js +211 -5
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +831 -387
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/Billing.d.ts +1 -1
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +17 -1
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +76 -76
- package/dist/apps/management/routes/billing.d.ts +76 -76
- package/dist/apps/management/routes/faucet.d.ts +12 -12
- package/dist/apps/management/routes/invitations.d.ts +24 -24
- package/dist/apps/management/routes/invite-links.d.ts +46 -46
- package/dist/apps/management/routes/members.d.ts +46 -46
- package/dist/apps/management/routes/orgs.d.ts +20 -20
- package/dist/apps/management/routes/projects.d.ts +44 -44
- package/dist/apps/management/routes/usage.d.ts +32 -32
- package/dist/apps/management/routes/verified-token-requests.d.ts +16 -16
- package/dist/apps/management/routes/webhooks.d.ts +707 -57
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +21 -21
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +115 -23
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +27 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +22 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +8 -3
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
- package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
- package/dist/db/tables/fundingTransferTransactions.js +44 -3
- package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
- package/dist/db/tables/fundingTransfers.d.ts +17 -0
- package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
- package/dist/db/tables/fundingTransfers.js +21 -0
- package/dist/db/tables/fundingTransfers.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +24 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +27 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -1
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +26 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +146 -13
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +9 -6
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/EarnVaults.js +5 -5
- package/dist/internal/EarnVaults.js.map +1 -1
- package/dist/internal/Log.d.ts +1 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/Viem.d.ts +16 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +12 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
- package/dist/internal/funding/DepositWebhook.js +2 -1
- package/dist/internal/funding/DepositWebhook.js.map +1 -1
- package/dist/internal/funding/Evidence.d.ts.map +1 -1
- package/dist/internal/funding/Evidence.js +2 -10
- package/dist/internal/funding/Evidence.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +40 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +5 -0
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
- package/dist/internal/funding/SourceObservation.js +5 -7
- package/dist/internal/funding/SourceObservation.js.map +1 -1
- package/dist/internal/funding/Transfer.d.ts +101 -6
- package/dist/internal/funding/Transfer.d.ts.map +1 -1
- package/dist/internal/funding/Transfer.js +162 -34
- package/dist/internal/funding/Transfer.js.map +1 -1
- package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
- package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
- package/dist/internal/funding/TransferReconciliation.js +192 -0
- package/dist/internal/funding/TransferReconciliation.js.map +1 -0
- package/dist/internal/funding/TransferWebhook.d.ts +21 -0
- package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
- package/dist/internal/funding/TransferWebhook.js +43 -0
- package/dist/internal/funding/TransferWebhook.js.map +1 -0
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +3 -12
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/dist/internal/funding/providers/stargate.d.ts +99 -0
- package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
- package/dist/internal/funding/providers/stargate.js +337 -35
- package/dist/internal/funding/providers/stargate.js.map +1 -1
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
- package/dist/internal/webhookDestinations/slack.js +16 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -1
- package/package.json +1 -1
- package/src/App.ts +1 -0
- package/src/Client.test-d.ts +83 -2
- package/src/Webhooks.ts +2 -2
- package/src/admin/apps/earn-vaults.ts +2 -1
- package/src/admin/apps/organizations.test.ts +53 -1
- package/src/admin/apps/organizations.ts +90 -5
- package/src/admin/ui/src/lib/organizations.test.ts +112 -1
- package/src/admin/ui/src/lib/organizations.ts +55 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
- package/src/admin/ui.generated.ts +69 -69
- package/src/apps/Zones.test.ts +3 -3
- package/src/apps/Zones.ts +3 -3
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.ts +15 -6
- package/src/apps/data/routes/earn.test.ts +369 -14
- package/src/apps/data/routes/earn.ts +548 -79
- package/src/apps/data/routes/exchanges.test.ts +740 -15
- package/src/apps/data/routes/exchanges.ts +954 -753
- package/src/apps/data/routes/fee-amm.ts +17 -18
- package/src/apps/data/routes/webhooks.test.ts +21 -5
- package/src/apps/data/routes/webhooks.ts +63 -8
- package/src/apps/funding/App.ts +12 -2
- package/src/apps/funding/routes/deposit-addresses.test.ts +4 -4
- package/src/apps/funding/routes/deposit-addresses.ts +2 -2
- package/src/apps/funding/routes/transfers.test.ts +510 -19
- package/src/apps/funding/routes/transfers.ts +271 -5
- package/src/apps/management/Billing.ts +18 -5
- package/src/apps/management/routes/webhooks.test.ts +3 -3
- package/src/apps/management/routes/webhooks.ts +24 -25
- package/src/apps/relay/App.test.ts +212 -1
- package/src/apps/relay/App.ts +158 -29
- package/src/apps/relay/Sponsorships.ts +24 -1
- package/src/db/Db.ts +35 -0
- package/src/db/Schema.ts +17 -2
- package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
- package/src/db/tables/fundingTransferTransactions.ts +79 -3
- package/src/db/tables/fundingTransfers.test.ts +26 -0
- package/src/db/tables/fundingTransfers.ts +36 -0
- package/src/db/tables/organizations.test.ts +41 -0
- package/src/db/tables/organizations.ts +49 -0
- package/src/db/tables/projects.test.ts +55 -0
- package/src/db/tables/projects.ts +46 -1
- package/src/db/tables/sponsoredTransactions.test.ts +338 -0
- package/src/db/tables/sponsoredTransactions.ts +197 -13
- package/src/db/tables/webhookSubscriptions.ts +18 -11
- package/src/internal/EarnVaults.ts +5 -5
- package/src/internal/Log.test-d.ts +2 -0
- package/src/internal/Log.ts +2 -0
- package/src/internal/Viem.ts +39 -3
- package/src/internal/Webhooks.test.ts +45 -0
- package/src/internal/funding/DepositWebhook.ts +2 -1
- package/src/internal/funding/Evidence.ts +2 -13
- package/src/internal/funding/Provider.ts +43 -0
- package/src/internal/funding/SourceObservation.ts +5 -9
- package/src/internal/funding/Transfer.test.ts +155 -0
- package/src/internal/funding/Transfer.ts +254 -37
- package/src/internal/funding/TransferReconciliation.test.ts +147 -0
- package/src/internal/funding/TransferReconciliation.ts +264 -0
- package/src/internal/funding/TransferWebhook.test.ts +128 -0
- package/src/internal/funding/TransferWebhook.ts +67 -0
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/funding/providers/relay.ts +3 -15
- package/src/internal/funding/providers/stargate.test.ts +169 -4
- package/src/internal/funding/providers/stargate.ts +498 -51
- package/src/internal/webhookDestinations/slack.ts +15 -0
|
@@ -4,27 +4,24 @@ import { base } from 'viem/chains'
|
|
|
4
4
|
import { tempoMainnet } from 'viem/tempo/chains'
|
|
5
5
|
|
|
6
6
|
import * as Anvil from '../../../../test/Anvil.js'
|
|
7
|
+
import * as TestApp from '../../../../test/App.js'
|
|
8
|
+
import * as TestFunding from '../../../../test/Funding.js'
|
|
7
9
|
import * as FundingIdempotencyTable from '../../../db/tables/fundingIdempotency.js'
|
|
8
|
-
|
|
9
10
|
import * as Cache from '../../../internal/Cache.js'
|
|
10
|
-
|
|
11
|
+
import * as FundingCatalog from '../../../internal/funding/Catalog.js'
|
|
12
|
+
import * as FundingChain from '../../../internal/funding/Chain.js'
|
|
11
13
|
import * as FundingProvider from '../../../internal/funding/Provider.js'
|
|
12
|
-
|
|
13
14
|
import * as FundingTransfer from '../../../internal/funding/Transfer.js'
|
|
14
|
-
|
|
15
|
+
import * as FundingTransferReconciliation from '../../../internal/funding/TransferReconciliation.js'
|
|
15
16
|
import * as Idempotency from '../../../internal/Idempotency.js'
|
|
16
|
-
|
|
17
17
|
import * as Relay from '../../../internal/funding/providers/relay.js'
|
|
18
|
-
|
|
19
18
|
import * as Stargate from '../../../internal/funding/providers/stargate.js'
|
|
20
|
-
|
|
21
|
-
import * as TestApp from '../../../../test/App.js'
|
|
22
|
-
|
|
23
|
-
import * as TestFunding from '../../../../test/Funding.js'
|
|
24
|
-
|
|
19
|
+
import * as Webhooks from '../../../internal/Webhooks.js'
|
|
25
20
|
import * as Funding from './transfers.js'
|
|
26
21
|
|
|
27
22
|
const db = TestApp.database()
|
|
23
|
+
const destinationTransactionHash =
|
|
24
|
+
'0x3dfaba0a78b8f4bc09e55e05211c8120729e7b6879efe32613883befbca666da'
|
|
28
25
|
|
|
29
26
|
/** Providers exercised against executable calls on a forked source chain. */
|
|
30
27
|
const executableProviders = [
|
|
@@ -40,6 +37,9 @@ const executableProviders = [
|
|
|
40
37
|
id: 'stargate',
|
|
41
38
|
},
|
|
42
39
|
] as const
|
|
40
|
+
const stargateConfiguration = TestFunding.snapshot.routesByProvider
|
|
41
|
+
.get('stargate')!
|
|
42
|
+
.find((candidate) => candidate.route.source.chain.id === 'eip155:8453')!.configuration!
|
|
43
43
|
|
|
44
44
|
beforeAll(() => TestFunding.publish(db))
|
|
45
45
|
|
|
@@ -224,6 +224,54 @@ describe('GET /funding/transfers/:id', () => {
|
|
|
224
224
|
})
|
|
225
225
|
})
|
|
226
226
|
|
|
227
|
+
describe('POST /funding/transfers/:id/source-transactions', () => {
|
|
228
|
+
test('requires funding write access and validates the transaction hash', async () => {
|
|
229
|
+
const app = transfersApp()
|
|
230
|
+
const transfer = await seedTransfer()
|
|
231
|
+
const path = `/v1/funding/transfers/${transfer.id}/source-transactions`
|
|
232
|
+
expect((await app.request(path, { method: 'POST' })).status).toBe(401)
|
|
233
|
+
expect((await app.request(path, { ...as(fundingReader), method: 'POST' })).status).toBe(403)
|
|
234
|
+
expect(
|
|
235
|
+
(
|
|
236
|
+
await app.request(path, {
|
|
237
|
+
body: JSON.stringify({ transactionHash: '0xinvalid' }),
|
|
238
|
+
headers: { ...as(fundingWriter).headers, 'content-type': 'application/json' },
|
|
239
|
+
method: 'POST',
|
|
240
|
+
})
|
|
241
|
+
).status,
|
|
242
|
+
).toBe(400)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('reports when transfer reconciliation is not configured', async () => {
|
|
246
|
+
const app = transfersApp()
|
|
247
|
+
const transfer = await seedTransfer()
|
|
248
|
+
const response = await app.request(`/v1/funding/transfers/${transfer.id}/source-transactions`, {
|
|
249
|
+
body: JSON.stringify({ transactionHash: `0x${'aa'.repeat(32)}` }),
|
|
250
|
+
headers: { ...as(fundingWriter).headers, 'content-type': 'application/json' },
|
|
251
|
+
method: 'POST',
|
|
252
|
+
})
|
|
253
|
+
expect(response.status).toBe(501)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('does not expose a transfer owned by another organization', async () => {
|
|
257
|
+
const tracker = FundingTransferReconciliation.createTracker({ db })
|
|
258
|
+
const app = transfersApp({
|
|
259
|
+
funding: {
|
|
260
|
+
async dispatchTransferReconciliation(message) {
|
|
261
|
+
await tracker.reconcile(message)
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
const transfer = await seedTransfer()
|
|
266
|
+
const response = await app.request(`/v1/funding/transfers/${transfer.id}/source-transactions`, {
|
|
267
|
+
body: JSON.stringify({ transactionHash: `0x${'aa'.repeat(32)}` }),
|
|
268
|
+
headers: { ...as(fundingForeign).headers, 'content-type': 'application/json' },
|
|
269
|
+
method: 'POST',
|
|
270
|
+
})
|
|
271
|
+
expect(response.status).toBe(404)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
227
275
|
const createBody = {
|
|
228
276
|
amount: '1000000',
|
|
229
277
|
destinationToken: '0x20c0000000000000000000000000000000000000',
|
|
@@ -390,6 +438,40 @@ describe('POST /funding/transfers', () => {
|
|
|
390
438
|
expect(row.providerState).toEqual({ requestId: `0x${'ab'.repeat(32)}` })
|
|
391
439
|
})
|
|
392
440
|
|
|
441
|
+
test('dispatches staged webhook updates after creation commits', async () => {
|
|
442
|
+
const subscription = await Webhooks.createSubscription(db, {
|
|
443
|
+
chainId: tempoMainnet.id,
|
|
444
|
+
destination: { type: 'url', url: 'https://example.com/webhooks/funding-transfers' },
|
|
445
|
+
environment: 'production',
|
|
446
|
+
eventType: 'funding:transfer.updated',
|
|
447
|
+
filters: { status: 'awaiting-source' },
|
|
448
|
+
owner: { orgId: fundingWriter.orgId, type: 'api_key' },
|
|
449
|
+
})
|
|
450
|
+
const references: Webhooks.QueueReference[] = []
|
|
451
|
+
const app = transfersApp({
|
|
452
|
+
funding: {
|
|
453
|
+
async dispatchTransferUpdates(staged) {
|
|
454
|
+
const reference = staged[0]!
|
|
455
|
+
const queued = await db.kysely
|
|
456
|
+
.selectFrom('webhook_queue_events')
|
|
457
|
+
.select('eventId')
|
|
458
|
+
.where('eventId', '=', reference.eventId)
|
|
459
|
+
.where('subscriptionId', '=', reference.subscriptionId)
|
|
460
|
+
.executeTakeFirst()
|
|
461
|
+
expect(queued).toBeDefined()
|
|
462
|
+
references.push(...staged)
|
|
463
|
+
return staged.length
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
providers: [provider()],
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
const response = await postTransfer(app, { idempotencyKey: nextIdempotencyKey() })
|
|
470
|
+
|
|
471
|
+
expect(response.status).toBe(200)
|
|
472
|
+
expect(references).toEqual([{ eventId: expect.any(String), subscriptionId: subscription.id }])
|
|
473
|
+
})
|
|
474
|
+
|
|
393
475
|
test('creates a transfer with the requested provider', async () => {
|
|
394
476
|
const app = transfersApp({ providers: [provider()] })
|
|
395
477
|
const response = await postTransfer(app, {
|
|
@@ -728,7 +810,7 @@ describe.skipIf(!Anvil.available)('POST /funding/transfers (forked localnet)', (
|
|
|
728
810
|
expect(transfer.action.type).toBe('evm:calls')
|
|
729
811
|
if (transfer.action.type !== 'evm:calls') return
|
|
730
812
|
expect(transfer.action.calls.length).toBeGreaterThan(0)
|
|
731
|
-
if (executable.id === 'stargate')
|
|
813
|
+
if (executable.id === 'stargate') {
|
|
732
814
|
expect(transfer.fees).toMatchObject([
|
|
733
815
|
{
|
|
734
816
|
amount: { baseUnits: BigInt(transfer.action.calls.at(-1)!.value).toString() },
|
|
@@ -736,6 +818,16 @@ describe.skipIf(!Anvil.available)('POST /funding/transfers (forked localnet)', (
|
|
|
736
818
|
token: { standard: 'native', symbol: 'ETH' },
|
|
737
819
|
},
|
|
738
820
|
])
|
|
821
|
+
const row = await db.kysely
|
|
822
|
+
.selectFrom('funding_transfers')
|
|
823
|
+
.select('providerState')
|
|
824
|
+
.where('id', '=', transfer.id)
|
|
825
|
+
.executeTakeFirstOrThrow()
|
|
826
|
+
expect(row.providerState).toEqual({
|
|
827
|
+
destinationBlockNumber: expect.stringMatching(/^\d+$/),
|
|
828
|
+
routeConfiguration: stargateConfiguration,
|
|
829
|
+
})
|
|
830
|
+
}
|
|
739
831
|
|
|
740
832
|
const receipts = []
|
|
741
833
|
for (const call of transfer.action.calls) {
|
|
@@ -754,8 +846,158 @@ describe.skipIf(!Anvil.available)('POST /funding/transfers (forked localnet)', (
|
|
|
754
846
|
)
|
|
755
847
|
// The deposit is the last call and must emit; an approval alone proves nothing.
|
|
756
848
|
expect(receipts.at(-1)!.logs.length).toBeGreaterThan(0)
|
|
849
|
+
if (executable.id === 'stargate') {
|
|
850
|
+
const receipt = receipts.at(-1)!
|
|
851
|
+
const finalized = await client.getBlock({ blockTag: 'finalized' })
|
|
852
|
+
expect(receipt.blockNumber).toBeGreaterThan(finalized.number)
|
|
853
|
+
await expect(
|
|
854
|
+
Stargate.verifySourceTransaction({
|
|
855
|
+
configuration: stargateConfiguration,
|
|
856
|
+
destinationTokenAddress: transfer.destinationToken.address,
|
|
857
|
+
recipient: transfer.recipient,
|
|
858
|
+
sender: sender.address,
|
|
859
|
+
sourceAmount: transfer.sourceAmount.baseUnits,
|
|
860
|
+
sourceChain: FundingChain.from({
|
|
861
|
+
id: 'eip155:8453',
|
|
862
|
+
name: 'Base',
|
|
863
|
+
rpcUrls: [anvil.rpcUrl],
|
|
864
|
+
slug: 'base',
|
|
865
|
+
}),
|
|
866
|
+
sourceTokenAddress: transfer.sourceToken.address,
|
|
867
|
+
transactionHash: receipt.transactionHash,
|
|
868
|
+
validAfter: transfer.quote.sampledAt,
|
|
869
|
+
}),
|
|
870
|
+
).resolves.toMatchObject({ type: 'verified' })
|
|
871
|
+
}
|
|
757
872
|
})
|
|
758
873
|
|
|
874
|
+
if (executable.id === 'stargate')
|
|
875
|
+
test('reopens registration when the source transaction is reorged out', async () => {
|
|
876
|
+
await Anvil.setErc20Balance(anvil.client, {
|
|
877
|
+
address: sender.address,
|
|
878
|
+
amount,
|
|
879
|
+
slot: fiatTokenBalanceSlot,
|
|
880
|
+
token: usdc.address as `0x${string}`,
|
|
881
|
+
})
|
|
882
|
+
const response = await createTransfer(`bridge_reorg_${sender.address}`)
|
|
883
|
+
expect(response.status).toBe(200)
|
|
884
|
+
const transfer = await TestApp.json(
|
|
885
|
+
response,
|
|
886
|
+
Funding.schema.createFundingTransfer.Response,
|
|
887
|
+
)
|
|
888
|
+
expect(transfer.action.type).toBe('evm:calls')
|
|
889
|
+
if (transfer.action.type !== 'evm:calls') return
|
|
890
|
+
|
|
891
|
+
const checkpoint = await anvil.client.snapshot()
|
|
892
|
+
// Anvil increments fork timestamps instead of following wall time between tests.
|
|
893
|
+
const block = await anvil.client.getBlock()
|
|
894
|
+
const sampledAt = BigInt(Math.floor(Date.parse(transfer.quote.sampledAt) / 1_000))
|
|
895
|
+
if (block.timestamp < sampledAt)
|
|
896
|
+
await anvil.client.setNextBlockTimestamp({ timestamp: sampledAt })
|
|
897
|
+
let reverted = false
|
|
898
|
+
try {
|
|
899
|
+
const receipts = []
|
|
900
|
+
for (const call of transfer.action.calls)
|
|
901
|
+
receipts.push(
|
|
902
|
+
await anvil.client.sendTransactionSync({
|
|
903
|
+
account: sender,
|
|
904
|
+
data: call.data as `0x${string}`,
|
|
905
|
+
to: call.to as `0x${string}`,
|
|
906
|
+
value: BigInt(call.value),
|
|
907
|
+
}),
|
|
908
|
+
)
|
|
909
|
+
const sourceReceipt = receipts.at(-1)!
|
|
910
|
+
const transactionHash = sourceReceipt.transactionHash
|
|
911
|
+
await FundingCatalog.publish(db, {
|
|
912
|
+
...TestFunding.data,
|
|
913
|
+
chains: TestFunding.data.chains.map((chain) =>
|
|
914
|
+
chain.id === 'eip155:8453' ? { ...chain, rpcUrls: [anvil.rpcUrl] } : chain,
|
|
915
|
+
),
|
|
916
|
+
})
|
|
917
|
+
const messages: FundingTransferReconciliation.Message[] = []
|
|
918
|
+
const app = transfersApp({
|
|
919
|
+
funding: {
|
|
920
|
+
async dispatchTransferReconciliation(message) {
|
|
921
|
+
messages.push(message)
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
providers: [Stargate.stargate()],
|
|
925
|
+
})
|
|
926
|
+
const registered = await app.request(
|
|
927
|
+
`/v1/funding/transfers/${transfer.id}/source-transactions`,
|
|
928
|
+
{
|
|
929
|
+
body: JSON.stringify({ transactionHash }),
|
|
930
|
+
headers: { ...as(fundingWriter).headers, 'content-type': 'application/json' },
|
|
931
|
+
method: 'POST',
|
|
932
|
+
},
|
|
933
|
+
)
|
|
934
|
+
expect(registered.status).toBe(200)
|
|
935
|
+
expect(messages).toEqual([
|
|
936
|
+
{ transferId: transfer.id, type: 'funding:transfer:reconcile' },
|
|
937
|
+
])
|
|
938
|
+
|
|
939
|
+
await anvil.client.revert({ id: checkpoint })
|
|
940
|
+
reverted = true
|
|
941
|
+
const tracker = FundingTransferReconciliation.createTracker({
|
|
942
|
+
db,
|
|
943
|
+
async dispatch(message) {
|
|
944
|
+
messages.push(message)
|
|
945
|
+
},
|
|
946
|
+
})
|
|
947
|
+
await expect(tracker.reconcile(messages[0]!)).resolves.toEqual({ type: 'pending' })
|
|
948
|
+
expect(messages[1]).toEqual({
|
|
949
|
+
attempt: 1,
|
|
950
|
+
transferId: transfer.id,
|
|
951
|
+
type: 'funding:transfer:reconcile',
|
|
952
|
+
})
|
|
953
|
+
|
|
954
|
+
const pending = await db.kysely
|
|
955
|
+
.selectFrom('funding_transfers')
|
|
956
|
+
.select(['snapshot', 'status', 'version'])
|
|
957
|
+
.where('id', '=', transfer.id)
|
|
958
|
+
.executeTakeFirstOrThrow()
|
|
959
|
+
expect({
|
|
960
|
+
sourceTransactionHashes: pending.snapshot.sourceTransactionHashes,
|
|
961
|
+
status: pending.status,
|
|
962
|
+
version: pending.version,
|
|
963
|
+
}).toEqual({
|
|
964
|
+
sourceTransactionHashes: [transactionHash],
|
|
965
|
+
status: 'processing',
|
|
966
|
+
version: 2,
|
|
967
|
+
})
|
|
968
|
+
|
|
969
|
+
await anvil.client.mine({ blocks: 128 })
|
|
970
|
+
const finalized = await anvil.client.getBlock({ blockTag: 'finalized' })
|
|
971
|
+
expect(finalized.number).toBeGreaterThanOrEqual(sourceReceipt.blockNumber)
|
|
972
|
+
await expect(tracker.reconcile(messages[1]!)).resolves.toEqual({ type: 'reopened' })
|
|
973
|
+
|
|
974
|
+
const record = await db.kysely
|
|
975
|
+
.selectFrom('funding_transfers')
|
|
976
|
+
.select(['snapshot', 'status', 'version'])
|
|
977
|
+
.where('id', '=', transfer.id)
|
|
978
|
+
.executeTakeFirstOrThrow()
|
|
979
|
+
expect({
|
|
980
|
+
sourceTransactionHashes: record.snapshot.sourceTransactionHashes,
|
|
981
|
+
status: record.status,
|
|
982
|
+
version: record.version,
|
|
983
|
+
}).toEqual({
|
|
984
|
+
sourceTransactionHashes: undefined,
|
|
985
|
+
status: 'awaiting-source',
|
|
986
|
+
version: 3,
|
|
987
|
+
})
|
|
988
|
+
expect(
|
|
989
|
+
await db.kysely
|
|
990
|
+
.selectFrom('funding_transfer_transactions')
|
|
991
|
+
.selectAll()
|
|
992
|
+
.where('transferId', '=', transfer.id)
|
|
993
|
+
.execute(),
|
|
994
|
+
).toEqual([])
|
|
995
|
+
} finally {
|
|
996
|
+
if (!reverted) await anvil.client.revert({ id: checkpoint })
|
|
997
|
+
await TestFunding.publish(db)
|
|
998
|
+
}
|
|
999
|
+
})
|
|
1000
|
+
|
|
759
1001
|
test('bounds the approval to the quoted source spend', async () => {
|
|
760
1002
|
const response = await createTransfer(`bridge_bounds_${executable.id}_${sender.address}`)
|
|
761
1003
|
expect(response.status).toBe(200)
|
|
@@ -781,6 +1023,195 @@ describe.skipIf(!Anvil.available)('POST /funding/transfers (forked localnet)', (
|
|
|
781
1023
|
}
|
|
782
1024
|
})
|
|
783
1025
|
|
|
1026
|
+
describe.skipIf(!Anvil.available)(
|
|
1027
|
+
'POST /funding/transfers/:id/source-transactions (forked localnet)',
|
|
1028
|
+
() => {
|
|
1029
|
+
const trackingDb = TestApp.database()
|
|
1030
|
+
let destination: Anvil.start.ReturnType
|
|
1031
|
+
let source: Anvil.start.ReturnType
|
|
1032
|
+
|
|
1033
|
+
beforeAll(async () => {
|
|
1034
|
+
;[destination, source] = await Promise.all([
|
|
1035
|
+
Anvil.start({ chain: tempoMainnet, forkUrl: tempoMainnet.rpcUrls.default.http[0]! }),
|
|
1036
|
+
Anvil.start({ chain: base, forkUrl: base.rpcUrls.default.http[0]! }),
|
|
1037
|
+
])
|
|
1038
|
+
await FundingCatalog.publish(trackingDb, {
|
|
1039
|
+
...TestFunding.data,
|
|
1040
|
+
chains: TestFunding.data.chains.map((chain) => {
|
|
1041
|
+
if (chain.id === 'eip155:4217') return { ...chain, rpcUrls: [destination.rpcUrl] }
|
|
1042
|
+
if (chain.id === 'eip155:8453') return { ...chain, rpcUrls: [source.rpcUrl] }
|
|
1043
|
+
return chain
|
|
1044
|
+
}),
|
|
1045
|
+
})
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
afterAll(async () => {
|
|
1049
|
+
await Promise.all([destination?.stop(), source?.stop()])
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
test('backfills legacy state and completes from destination evidence', async () => {
|
|
1053
|
+
const destinationReceipt = await destination.client.getTransactionReceipt({
|
|
1054
|
+
hash: destinationTransactionHash,
|
|
1055
|
+
})
|
|
1056
|
+
const sourceTransactionHash =
|
|
1057
|
+
'0xfd71359adf5095f2ab512a19e0a2df6c8cc0db14ac8a52286d61287daabdccd4'
|
|
1058
|
+
const sourceReceipt = await source.client.getTransactionReceipt({
|
|
1059
|
+
hash: sourceTransactionHash,
|
|
1060
|
+
})
|
|
1061
|
+
const sourceBlock = await source.client.getBlock({ blockNumber: sourceReceipt.blockNumber })
|
|
1062
|
+
const sampledAt = new Date(Number(sourceBlock.timestamp - 1n) * 1_000).toISOString()
|
|
1063
|
+
const amount = {
|
|
1064
|
+
baseUnits: '200000',
|
|
1065
|
+
currency: 'USD',
|
|
1066
|
+
decimals: 6,
|
|
1067
|
+
formatted: '0.2',
|
|
1068
|
+
} as const
|
|
1069
|
+
const snapshot = {
|
|
1070
|
+
...TestFunding.transferSnapshot({
|
|
1071
|
+
destinationAmount: amount,
|
|
1072
|
+
destinationAmountMin: amount,
|
|
1073
|
+
provider: { id: 'stargate', name: 'Stargate' },
|
|
1074
|
+
recipient: '0x7212623e4cff9ad010d28aa065fad2bf58578783',
|
|
1075
|
+
sender: '0x52b650a62e384f68880d0a1dc2b84dfed47d5660',
|
|
1076
|
+
sourceAmount: amount,
|
|
1077
|
+
}),
|
|
1078
|
+
quote: {
|
|
1079
|
+
expiresAt: new Date(Date.parse(sampledAt) + 60_000).toISOString(),
|
|
1080
|
+
sampledAt,
|
|
1081
|
+
},
|
|
1082
|
+
}
|
|
1083
|
+
const transfer = await FundingTransfer.create(trackingDb, {
|
|
1084
|
+
apiKeyId: fundingWriter.id,
|
|
1085
|
+
environment: 'production',
|
|
1086
|
+
orgId: fundingWriter.orgId,
|
|
1087
|
+
snapshot,
|
|
1088
|
+
})
|
|
1089
|
+
const tracker = FundingTransferReconciliation.createTracker({ db: trackingDb })
|
|
1090
|
+
const messages: FundingTransferReconciliation.Message[] = []
|
|
1091
|
+
let dispatchEnabled = true
|
|
1092
|
+
const app = TestApp.create({
|
|
1093
|
+
auth: { keys: [fundingWriter] },
|
|
1094
|
+
db: trackingDb,
|
|
1095
|
+
defaultChainId: tempoMainnet.id,
|
|
1096
|
+
funding: {
|
|
1097
|
+
async dispatchTransferReconciliation(message) {
|
|
1098
|
+
if (!dispatchEnabled) throw new Error('reconciliation unavailable')
|
|
1099
|
+
messages.push(message)
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
providers: [Stargate.stargate()],
|
|
1103
|
+
})
|
|
1104
|
+
const path = `/v1/funding/transfers/${transfer.id}/source-transactions`
|
|
1105
|
+
const registration = {
|
|
1106
|
+
body: JSON.stringify({
|
|
1107
|
+
transactionHash: sourceTransactionHash,
|
|
1108
|
+
}),
|
|
1109
|
+
headers: { ...as(fundingWriter).headers, 'content-type': 'application/json' },
|
|
1110
|
+
method: 'POST',
|
|
1111
|
+
}
|
|
1112
|
+
const response = await app.request(path, registration)
|
|
1113
|
+
expect(response.status).toBe(200)
|
|
1114
|
+
const registered = await TestApp.json(
|
|
1115
|
+
response,
|
|
1116
|
+
Funding.schema.registerFundingTransferSourceTransaction.Response,
|
|
1117
|
+
)
|
|
1118
|
+
expect({
|
|
1119
|
+
sourceTransactionHashes: registered.sourceTransactionHashes,
|
|
1120
|
+
status: registered.status,
|
|
1121
|
+
version: registered.version,
|
|
1122
|
+
}).toEqual({
|
|
1123
|
+
sourceTransactionHashes: [
|
|
1124
|
+
'0xfd71359adf5095f2ab512a19e0a2df6c8cc0db14ac8a52286d61287daabdccd4',
|
|
1125
|
+
],
|
|
1126
|
+
status: 'processing',
|
|
1127
|
+
version: 2,
|
|
1128
|
+
})
|
|
1129
|
+
const stored = await trackingDb.kysely
|
|
1130
|
+
.selectFrom('funding_transfers')
|
|
1131
|
+
.select('providerState')
|
|
1132
|
+
.where('id', '=', transfer.id)
|
|
1133
|
+
.executeTakeFirstOrThrow()
|
|
1134
|
+
expect(stored).toEqual({
|
|
1135
|
+
providerState: {
|
|
1136
|
+
destinationBlockNumber: expect.stringMatching(/^\d+$/),
|
|
1137
|
+
routeConfiguration: stargateConfiguration,
|
|
1138
|
+
stargate: {
|
|
1139
|
+
guid: '0x5549d117c8b23f0ecdf05c2604ed989cf0034f378707bd81319e93834f2f85f2',
|
|
1140
|
+
sourceBlock: {
|
|
1141
|
+
hash: sourceReceipt.blockHash,
|
|
1142
|
+
number: sourceReceipt.blockNumber.toString(),
|
|
1143
|
+
},
|
|
1144
|
+
sourceEid: 30_184,
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
1147
|
+
})
|
|
1148
|
+
const destinationBlockNumber = stored.providerState?.['destinationBlockNumber']
|
|
1149
|
+
if (typeof destinationBlockNumber !== 'string')
|
|
1150
|
+
throw new Error('Expected a backfilled destination block number.')
|
|
1151
|
+
expect(BigInt(destinationBlockNumber)).toBeLessThanOrEqual(destinationReceipt.blockNumber)
|
|
1152
|
+
expect(messages).toEqual([{ transferId: transfer.id, type: 'funding:transfer:reconcile' }])
|
|
1153
|
+
await tracker.reconcile(messages[0]!)
|
|
1154
|
+
|
|
1155
|
+
const completed = await TestApp.json(
|
|
1156
|
+
await app.request(`/v1/funding/transfers/${transfer.id}`, as(fundingWriter)),
|
|
1157
|
+
Funding.schema.getFundingTransfer.Response,
|
|
1158
|
+
)
|
|
1159
|
+
expect({
|
|
1160
|
+
destinationTransactionHashes: completed.destinationTransactionHashes,
|
|
1161
|
+
status: completed.status,
|
|
1162
|
+
version: completed.version,
|
|
1163
|
+
}).toEqual({
|
|
1164
|
+
destinationTransactionHashes: [destinationTransactionHash],
|
|
1165
|
+
status: 'completed',
|
|
1166
|
+
version: 3,
|
|
1167
|
+
})
|
|
1168
|
+
|
|
1169
|
+
dispatchEnabled = false
|
|
1170
|
+
const replayed = await TestApp.json(
|
|
1171
|
+
await app.request(path, registration),
|
|
1172
|
+
Funding.schema.registerFundingTransferSourceTransaction.Response,
|
|
1173
|
+
)
|
|
1174
|
+
expect({ status: replayed.status, version: replayed.version }).toEqual({
|
|
1175
|
+
status: 'completed',
|
|
1176
|
+
version: 3,
|
|
1177
|
+
})
|
|
1178
|
+
|
|
1179
|
+
const other = await FundingTransfer.create(trackingDb, {
|
|
1180
|
+
apiKeyId: fundingWriter.id,
|
|
1181
|
+
environment: 'production',
|
|
1182
|
+
orgId: fundingWriter.orgId,
|
|
1183
|
+
snapshot,
|
|
1184
|
+
})
|
|
1185
|
+
const conflict = await app.request(
|
|
1186
|
+
`/v1/funding/transfers/${other.id}/source-transactions`,
|
|
1187
|
+
registration,
|
|
1188
|
+
)
|
|
1189
|
+
expect(conflict.status).toBe(409)
|
|
1190
|
+
expect(((await conflict.json()) as { error: { code: string } }).error.code).toBe(
|
|
1191
|
+
'source_transaction_conflict',
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
const pending = await FundingTransfer.create(trackingDb, {
|
|
1195
|
+
apiKeyId: fundingWriter.id,
|
|
1196
|
+
environment: 'production',
|
|
1197
|
+
orgId: fundingWriter.orgId,
|
|
1198
|
+
snapshot,
|
|
1199
|
+
})
|
|
1200
|
+
const pendingResponse = await app.request(
|
|
1201
|
+
`/v1/funding/transfers/${pending.id}/source-transactions`,
|
|
1202
|
+
{
|
|
1203
|
+
...registration,
|
|
1204
|
+
body: JSON.stringify({ transactionHash: `0x${'99'.repeat(32)}` }),
|
|
1205
|
+
},
|
|
1206
|
+
)
|
|
1207
|
+
expect(pendingResponse.status).toBe(409)
|
|
1208
|
+
expect(((await pendingResponse.json()) as { error: { code: string } }).error.code).toBe(
|
|
1209
|
+
'source_transaction_pending',
|
|
1210
|
+
)
|
|
1211
|
+
})
|
|
1212
|
+
},
|
|
1213
|
+
)
|
|
1214
|
+
|
|
784
1215
|
describe('POST /funding/transfers/{destination}', () => {
|
|
785
1216
|
test.each([
|
|
786
1217
|
{ body: vaultTransferBody, destination: 'vault' },
|
|
@@ -847,8 +1278,11 @@ describe('POST /funding/transfers/{destination}', () => {
|
|
|
847
1278
|
describe('funding transfers OpenAPI', () => {
|
|
848
1279
|
test('publishes generator-ready transfer contracts', async () => {
|
|
849
1280
|
const spec = await (await create({ auth: false }).request('/openapi.json')).json()
|
|
850
|
-
const detailPath = Object.keys(spec.paths).find(
|
|
851
|
-
path.includes('/v1/funding/transfers/{'),
|
|
1281
|
+
const detailPath = Object.keys(spec.paths).find(
|
|
1282
|
+
(path) => path.includes('/v1/funding/transfers/{') && !path.endsWith('/source-transactions'),
|
|
1283
|
+
)!
|
|
1284
|
+
const sourceTransactionPath = Object.keys(spec.paths).find((path) =>
|
|
1285
|
+
path.endsWith('/source-transactions'),
|
|
852
1286
|
)!
|
|
853
1287
|
const operations = {
|
|
854
1288
|
create: spec.paths['/v1/funding/transfers'].post,
|
|
@@ -856,6 +1290,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
856
1290
|
createZone: spec.paths['/v1/funding/transfers/zone'].post,
|
|
857
1291
|
get: spec.paths[detailPath].get,
|
|
858
1292
|
list: spec.paths['/v1/funding/transfers'].get,
|
|
1293
|
+
registerSourceTransaction: spec.paths[sourceTransactionPath].post,
|
|
859
1294
|
}
|
|
860
1295
|
const summarize = (operation: (typeof operations)[keyof typeof operations]) => ({
|
|
861
1296
|
errors: Object.fromEntries(
|
|
@@ -885,6 +1320,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
885
1320
|
'FundingTransferList',
|
|
886
1321
|
'FundingTransferQuote',
|
|
887
1322
|
'FundingTransferStatusReason',
|
|
1323
|
+
'RegisterFundingTransferSourceTransactionRequest',
|
|
888
1324
|
].filter((name) => spec.components.schemas[name]),
|
|
889
1325
|
formats: {
|
|
890
1326
|
createdAt: spec.components.schemas.FundingTransfer.properties.createdAt.format,
|
|
@@ -907,6 +1343,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
907
1343
|
createZone: summarize(operations.createZone),
|
|
908
1344
|
get: summarize(operations.get),
|
|
909
1345
|
list: summarize(operations.list),
|
|
1346
|
+
registerSourceTransaction: summarize(operations.registerSourceTransaction),
|
|
910
1347
|
},
|
|
911
1348
|
operationErrorCodes: {
|
|
912
1349
|
createInvalid:
|
|
@@ -944,6 +1381,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
944
1381
|
"FundingTransferList",
|
|
945
1382
|
"FundingTransferQuote",
|
|
946
1383
|
"FundingTransferStatusReason",
|
|
1384
|
+
"RegisterFundingTransferSourceTransactionRequest",
|
|
947
1385
|
],
|
|
948
1386
|
"formats": {
|
|
949
1387
|
"createdAt": "date-time",
|
|
@@ -1136,6 +1574,41 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1136
1574
|
"$ref": "#/components/schemas/FundingTransferList",
|
|
1137
1575
|
},
|
|
1138
1576
|
},
|
|
1577
|
+
"registerSourceTransaction": {
|
|
1578
|
+
"errors": {
|
|
1579
|
+
"400": {
|
|
1580
|
+
"$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidOrParamInvalidOrSourceTransactionInvalidError",
|
|
1581
|
+
},
|
|
1582
|
+
"401": {
|
|
1583
|
+
"$ref": "#/components/schemas/AuthenticationError",
|
|
1584
|
+
},
|
|
1585
|
+
"403": {
|
|
1586
|
+
"$ref": "#/components/schemas/ForbiddenError",
|
|
1587
|
+
},
|
|
1588
|
+
"404": {
|
|
1589
|
+
"$ref": "#/components/schemas/FundingTransferNotFoundError",
|
|
1590
|
+
},
|
|
1591
|
+
"409": {
|
|
1592
|
+
"$ref": "#/components/schemas/FundingTransferNotAwaitingSourceOrSourceTransactionConflictOrSourceTransactionPendingError",
|
|
1593
|
+
},
|
|
1594
|
+
"429": "#/components/responses/RateLimited",
|
|
1595
|
+
"500": "#/components/responses/InternalError",
|
|
1596
|
+
"501": {
|
|
1597
|
+
"$ref": "#/components/schemas/NotImplemented501Error",
|
|
1598
|
+
},
|
|
1599
|
+
"502": {
|
|
1600
|
+
"$ref": "#/components/schemas/UpstreamError",
|
|
1601
|
+
},
|
|
1602
|
+
"504": "#/components/responses/RequestTimeout",
|
|
1603
|
+
},
|
|
1604
|
+
"operationId": "registerFundingTransferSourceTransaction",
|
|
1605
|
+
"request": {
|
|
1606
|
+
"$ref": "#/components/schemas/RegisterFundingTransferSourceTransactionRequest",
|
|
1607
|
+
},
|
|
1608
|
+
"response": {
|
|
1609
|
+
"$ref": "#/components/schemas/FundingTransfer",
|
|
1610
|
+
},
|
|
1611
|
+
},
|
|
1139
1612
|
},
|
|
1140
1613
|
}
|
|
1141
1614
|
`)
|
|
@@ -1209,8 +1682,11 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1209
1682
|
'x-tagGroups': { name: string; tags: string[] }[]
|
|
1210
1683
|
}
|
|
1211
1684
|
const spec = (await (await app.request('/openapi.json')).json()) as Spec
|
|
1212
|
-
const detailPath = Object.keys(spec.paths).find(
|
|
1213
|
-
path.includes('/v1/funding/transfers/{'),
|
|
1685
|
+
const detailPath = Object.keys(spec.paths).find(
|
|
1686
|
+
(path) => path.includes('/v1/funding/transfers/{') && !path.endsWith('/source-transactions'),
|
|
1687
|
+
)!
|
|
1688
|
+
const sourceTransactionPath = Object.keys(spec.paths).find((path) =>
|
|
1689
|
+
path.endsWith('/source-transactions'),
|
|
1214
1690
|
)!
|
|
1215
1691
|
const depositAddressPath = Object.keys(spec.paths).find(
|
|
1216
1692
|
(path) =>
|
|
@@ -1239,6 +1715,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1239
1715
|
list: summarize(spec.paths['/v1/funding/transfers']!.get!),
|
|
1240
1716
|
providers: summarize(spec.paths['/v1/funding/providers']!.get!),
|
|
1241
1717
|
quotes: summarize(spec.paths['/v1/funding/quotes']!.get!),
|
|
1718
|
+
sourceTransaction: summarize(spec.paths[sourceTransactionPath]!.post!),
|
|
1242
1719
|
vault: summarize(spec.paths['/v1/funding/transfers/vault']!.post!),
|
|
1243
1720
|
zone: summarize(spec.paths['/v1/funding/transfers/zone']!.post!),
|
|
1244
1721
|
}
|
|
@@ -1309,6 +1786,10 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1309
1786
|
"description": "Returns live quotes for transferring stablecoins to Tempo.",
|
|
1310
1787
|
"summary": "Get quotes",
|
|
1311
1788
|
},
|
|
1789
|
+
"sourceTransaction": {
|
|
1790
|
+
"description": "Registers and verifies a funding source transaction.",
|
|
1791
|
+
"summary": "Register source transaction",
|
|
1792
|
+
},
|
|
1312
1793
|
"vault": {
|
|
1313
1794
|
"description": "Creates a transfer into a Tempo Earn vault.",
|
|
1314
1795
|
"summary": "Create transfer into vault",
|
|
@@ -1324,6 +1805,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1324
1805
|
"Get providers",
|
|
1325
1806
|
"List deposit addresses",
|
|
1326
1807
|
"Create deposit address",
|
|
1808
|
+
"Register source transaction",
|
|
1327
1809
|
"List transfers",
|
|
1328
1810
|
"Create transfer",
|
|
1329
1811
|
"Create transfer into vault",
|
|
@@ -1507,8 +1989,11 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1507
1989
|
}
|
|
1508
1990
|
>
|
|
1509
1991
|
}
|
|
1510
|
-
const detailPath = Object.keys(spec.paths).find(
|
|
1511
|
-
path.includes('/v1/funding/transfers/{'),
|
|
1992
|
+
const detailPath = Object.keys(spec.paths).find(
|
|
1993
|
+
(path) => path.includes('/v1/funding/transfers/{') && !path.endsWith('/source-transactions'),
|
|
1994
|
+
)
|
|
1995
|
+
const sourceTransactionPath = Object.keys(spec.paths).find((path) =>
|
|
1996
|
+
path.endsWith('/source-transactions'),
|
|
1512
1997
|
)
|
|
1513
1998
|
const depositAddressPath = Object.keys(spec.paths).find(
|
|
1514
1999
|
(path) =>
|
|
@@ -1544,6 +2029,10 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1544
2029
|
requiredCreateHeader: spec.paths['/v1/funding/transfers']?.post?.parameters.find(
|
|
1545
2030
|
(parameter) => parameter.name === 'idempotency-key',
|
|
1546
2031
|
),
|
|
2032
|
+
sourceTransaction:
|
|
2033
|
+
sourceTransactionPath && spec.paths[sourceTransactionPath]?.post?.operationId,
|
|
2034
|
+
sourceTransactionScopes:
|
|
2035
|
+
sourceTransactionPath && spec.paths[sourceTransactionPath]?.post?.['x-required-scopes'],
|
|
1547
2036
|
vault: spec.paths['/v1/funding/transfers/vault']?.post?.operationId,
|
|
1548
2037
|
vaultScopes: spec.paths['/v1/funding/transfers/vault']?.post?.['x-required-scopes'],
|
|
1549
2038
|
zone: spec.paths['/v1/funding/transfers/zone']?.post?.operationId,
|
|
@@ -1572,6 +2061,8 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1572
2061
|
required: true,
|
|
1573
2062
|
schema: expect.any(Object),
|
|
1574
2063
|
}),
|
|
2064
|
+
sourceTransaction: 'registerFundingTransferSourceTransaction',
|
|
2065
|
+
sourceTransactionScopes: ['funding:write'],
|
|
1575
2066
|
vault: 'createFundingTransferVault',
|
|
1576
2067
|
vaultScopes: ['funding:write'],
|
|
1577
2068
|
zone: 'createFundingTransferZone',
|
|
@@ -1598,7 +2089,7 @@ const fundingProjectReader = {
|
|
|
1598
2089
|
const fundingForeign = {
|
|
1599
2090
|
id: 'key_funding_other',
|
|
1600
2091
|
orgId: 'org_funding_other',
|
|
1601
|
-
scopes: ['funding:read'],
|
|
2092
|
+
scopes: ['funding:read', 'funding:write'],
|
|
1602
2093
|
token: 'secret_funding_other',
|
|
1603
2094
|
} satisfies TestApp.kvStore.Key
|
|
1604
2095
|
|