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
|
@@ -11,11 +11,13 @@ import * as Cache from '../../../internal/Cache.js'
|
|
|
11
11
|
import * as FundingCatalog from '../../../internal/funding/Catalog.js'
|
|
12
12
|
import * as FundingProvider from '../../../internal/funding/Provider.js'
|
|
13
13
|
import * as FundingTransfer from '../../../internal/funding/Transfer.js'
|
|
14
|
+
import * as FundingTransferReconciliation from '../../../internal/funding/TransferReconciliation.js'
|
|
14
15
|
import * as Idempotency from '../../../internal/Idempotency.js'
|
|
15
16
|
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
16
17
|
import * as Response from '../../../internal/Response.js'
|
|
17
18
|
import * as Schema from '../../../internal/Schema.js'
|
|
18
19
|
import * as Value from '../../../internal/Value.js'
|
|
20
|
+
import type * as Webhooks from '../../../internal/Webhooks.js'
|
|
19
21
|
import * as Access from '../Access.js'
|
|
20
22
|
import * as ProviderAttempts from '../ProviderAttempts.js'
|
|
21
23
|
|
|
@@ -161,6 +163,25 @@ export namespace schema {
|
|
|
161
163
|
/** The funding transfer resource, returned directly. */
|
|
162
164
|
export const Response = Transfer
|
|
163
165
|
}
|
|
166
|
+
|
|
167
|
+
/** Schemas for the registerFundingTransferSourceTransaction operation. */
|
|
168
|
+
export namespace registerFundingTransferSourceTransaction {
|
|
169
|
+
/** Source transaction registration request body. */
|
|
170
|
+
export const Body = OpenApi.component(
|
|
171
|
+
z
|
|
172
|
+
.strictObject({
|
|
173
|
+
transactionHash: Schema.hash(`0x${'51'.repeat(32)}`),
|
|
174
|
+
})
|
|
175
|
+
.check(z.describe('Verified source transaction to associate with the transfer.')),
|
|
176
|
+
'RegisterFundingTransferSourceTransactionRequest',
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
/** Path parameters for source transaction registration. */
|
|
180
|
+
export const Params = getFundingTransfer.Params
|
|
181
|
+
|
|
182
|
+
/** The transfer after source transaction registration. */
|
|
183
|
+
export const Response = Transfer
|
|
184
|
+
}
|
|
164
185
|
}
|
|
165
186
|
|
|
166
187
|
const baseChainExample = {
|
|
@@ -227,6 +248,14 @@ const fundingTransferExample = {
|
|
|
227
248
|
version: 1,
|
|
228
249
|
} satisfies z.output<typeof schema.Transfer>
|
|
229
250
|
|
|
251
|
+
const registeredFundingTransferExample = {
|
|
252
|
+
...fundingTransferExample,
|
|
253
|
+
sourceTransactionHashes: [`0x${'51'.repeat(32)}`],
|
|
254
|
+
status: 'processing',
|
|
255
|
+
updatedAt: '2026-01-01T00:00:20.000Z',
|
|
256
|
+
version: 2,
|
|
257
|
+
} satisfies z.output<typeof schema.registerFundingTransferSourceTransaction.Response>
|
|
258
|
+
|
|
230
259
|
const listFundingTransfersResponseExample = {
|
|
231
260
|
data: [fundingTransferExample],
|
|
232
261
|
nextCursor: null,
|
|
@@ -276,9 +305,17 @@ const idempotencyReplayTtlMs = 86_400_000
|
|
|
276
305
|
// Pending claims only cover the bounded provider preparation window.
|
|
277
306
|
const idempotencyPendingTtlMs = 60_000
|
|
278
307
|
const fundingEtag = etag()
|
|
308
|
+
const bodyValidation = {
|
|
309
|
+
code: 'body_invalid',
|
|
310
|
+
message: 'Check the request body and try again.',
|
|
311
|
+
} as const
|
|
312
|
+
const paramValidation = {
|
|
313
|
+
code: 'param_invalid',
|
|
314
|
+
message: 'Check the path parameters and try again.',
|
|
315
|
+
} as const
|
|
279
316
|
|
|
280
317
|
/** Mounts durable funding transfer routes. */
|
|
281
|
-
export function transfers() {
|
|
318
|
+
export function transfers(options: transfers.Options = {}) {
|
|
282
319
|
return new Hono<App.Environment>()
|
|
283
320
|
.get(
|
|
284
321
|
'/v1/funding/transfers',
|
|
@@ -422,6 +459,209 @@ export function transfers() {
|
|
|
422
459
|
}
|
|
423
460
|
},
|
|
424
461
|
)
|
|
462
|
+
.post(
|
|
463
|
+
'/v1/funding/transfers/:id{ftr_[A-Za-z0-9_-]+}/source-transactions',
|
|
464
|
+
Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
|
|
465
|
+
OpenApi.validate(
|
|
466
|
+
'param',
|
|
467
|
+
schema.registerFundingTransferSourceTransaction.Params,
|
|
468
|
+
paramValidation,
|
|
469
|
+
),
|
|
470
|
+
OpenApi.validate(
|
|
471
|
+
'json',
|
|
472
|
+
schema.registerFundingTransferSourceTransaction.Body,
|
|
473
|
+
bodyValidation,
|
|
474
|
+
),
|
|
475
|
+
OpenApi.describeRoute({
|
|
476
|
+
description: 'Registers and verifies a funding source transaction.',
|
|
477
|
+
operationId: 'registerFundingTransferSourceTransaction',
|
|
478
|
+
responses: OpenApi.responses({
|
|
479
|
+
errors: {
|
|
480
|
+
400: {
|
|
481
|
+
codes: ['body_invalid', 'param_invalid', 'source_transaction_invalid'],
|
|
482
|
+
description: 'The path, body, or submitted source transaction is invalid.',
|
|
483
|
+
},
|
|
484
|
+
404: {
|
|
485
|
+
codes: ['funding_transfer_not_found'],
|
|
486
|
+
description: 'No visible funding transfer exists for the identifier.',
|
|
487
|
+
},
|
|
488
|
+
409: {
|
|
489
|
+
codes: [
|
|
490
|
+
'funding_transfer_not_awaiting_source',
|
|
491
|
+
'source_transaction_conflict',
|
|
492
|
+
'source_transaction_pending',
|
|
493
|
+
],
|
|
494
|
+
description: 'The transfer or source transaction cannot be registered yet.',
|
|
495
|
+
},
|
|
496
|
+
501: {
|
|
497
|
+
codes: ['not_implemented'],
|
|
498
|
+
description: 'Source transaction reconciliation is not configured.',
|
|
499
|
+
},
|
|
500
|
+
502: {
|
|
501
|
+
codes: ['upstream_error'],
|
|
502
|
+
description: 'Source verification or reconciliation dispatch failed.',
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
success: {
|
|
506
|
+
description: 'The transfer with its verified source transaction.',
|
|
507
|
+
example: registeredFundingTransferExample,
|
|
508
|
+
schema: schema.registerFundingTransferSourceTransaction.Response,
|
|
509
|
+
},
|
|
510
|
+
}),
|
|
511
|
+
summary: 'Register source transaction',
|
|
512
|
+
tags: ['Funding Transfers'],
|
|
513
|
+
}),
|
|
514
|
+
async (c) => {
|
|
515
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
516
|
+
if (OpenApi.narrowValidation) return OpenApi.validationError(c, paramValidation)
|
|
517
|
+
if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
|
|
518
|
+
|
|
519
|
+
const owner = Access.owner(c)
|
|
520
|
+
if (!owner) return Access.unauthorized(c)
|
|
521
|
+
c.header('Cache-Control', Cache.policies.noStore)
|
|
522
|
+
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key')
|
|
523
|
+
|
|
524
|
+
const dispatch = options.dispatchTransferReconciliation
|
|
525
|
+
if (!dispatch)
|
|
526
|
+
return Response.error(c, {
|
|
527
|
+
code: 'not_implemented',
|
|
528
|
+
message: 'Source transaction reconciliation is not configured',
|
|
529
|
+
status: 501,
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
const db = Db.get(c.get('db'))
|
|
533
|
+
const { id } = c.req.valid('param')
|
|
534
|
+
const { transactionHash } = c.req.valid('json')
|
|
535
|
+
try {
|
|
536
|
+
const record = await FundingTransfers.getOwned(db, owner, id)
|
|
537
|
+
if (!record)
|
|
538
|
+
return Response.error(c, {
|
|
539
|
+
code: 'funding_transfer_not_found',
|
|
540
|
+
message: 'Funding transfer not found',
|
|
541
|
+
status: 404,
|
|
542
|
+
})
|
|
543
|
+
if (
|
|
544
|
+
record.snapshot.sourceTransactionHashes?.some(
|
|
545
|
+
(candidate) => candidate.toLowerCase() === transactionHash,
|
|
546
|
+
)
|
|
547
|
+
) {
|
|
548
|
+
if (record.status === 'processing')
|
|
549
|
+
await dispatch({ transferId: record.id, type: 'funding:transfer:reconcile' })
|
|
550
|
+
return c.json(
|
|
551
|
+
Response.validated(
|
|
552
|
+
schema.registerFundingTransferSourceTransaction.Response,
|
|
553
|
+
FundingTransfer.toPublic(record),
|
|
554
|
+
),
|
|
555
|
+
200,
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
const provider = c
|
|
559
|
+
.get('providers')
|
|
560
|
+
.filter(FundingProvider.is)
|
|
561
|
+
.find((candidate) => candidate.id === record.providerId)
|
|
562
|
+
if (!provider || !FundingProvider.canVerifySourceTransaction(provider))
|
|
563
|
+
return Response.error(c, {
|
|
564
|
+
code: 'funding_transfer_not_awaiting_source',
|
|
565
|
+
message: 'Funding transfer does not accept source transactions',
|
|
566
|
+
status: 409,
|
|
567
|
+
})
|
|
568
|
+
if (record.status !== 'awaiting-source')
|
|
569
|
+
return Response.error(c, {
|
|
570
|
+
code: 'funding_transfer_not_awaiting_source',
|
|
571
|
+
message: 'Funding transfer is not awaiting a source transaction',
|
|
572
|
+
status: 409,
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
const catalog = await FundingCatalog.snapshot(c)
|
|
576
|
+
const destinationChain = catalog.chainsByKey.get(record.snapshot.destinationChain.id)
|
|
577
|
+
const sourceChain = catalog.chainsByKey.get(record.snapshot.sourceChain.id)
|
|
578
|
+
const routeConfiguration = catalog.routesByProvider
|
|
579
|
+
.get(record.providerId)
|
|
580
|
+
?.find(
|
|
581
|
+
(candidate) =>
|
|
582
|
+
candidate.route.source.chain.id === record.snapshot.sourceChain.id &&
|
|
583
|
+
candidate.route.source.address.toLowerCase() ===
|
|
584
|
+
record.snapshot.sourceToken.address.toLowerCase() &&
|
|
585
|
+
candidate.route.destination.chain.id === record.snapshot.destinationChain.id &&
|
|
586
|
+
candidate.route.destination.address.toLowerCase() ===
|
|
587
|
+
record.snapshot.destinationToken.address.toLowerCase(),
|
|
588
|
+
)?.configuration
|
|
589
|
+
if (!destinationChain || !sourceChain)
|
|
590
|
+
return Response.error(c, {
|
|
591
|
+
code: 'funding_transfer_not_awaiting_source',
|
|
592
|
+
message: 'Funding transfer route chains are no longer configured',
|
|
593
|
+
status: 409,
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
const evidence = await provider.verifySourceTransaction(
|
|
597
|
+
{
|
|
598
|
+
destinationChain,
|
|
599
|
+
providerState: record.providerState ?? undefined,
|
|
600
|
+
routeConfiguration,
|
|
601
|
+
sourceChain,
|
|
602
|
+
transactionHash,
|
|
603
|
+
transfer: record.snapshot,
|
|
604
|
+
},
|
|
605
|
+
c.req.raw.signal,
|
|
606
|
+
)
|
|
607
|
+
if (evidence.type === 'pending')
|
|
608
|
+
return Response.error(c, {
|
|
609
|
+
code: 'source_transaction_pending',
|
|
610
|
+
message: 'Source transaction is not confirmed yet',
|
|
611
|
+
status: 409,
|
|
612
|
+
})
|
|
613
|
+
if (evidence.type === 'invalid')
|
|
614
|
+
return Response.error(c, {
|
|
615
|
+
code: 'source_transaction_invalid',
|
|
616
|
+
message: 'Source transaction does not match the funding transfer',
|
|
617
|
+
status: 400,
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
const registered = await FundingTransfer.registerSourceTransaction(db, {
|
|
621
|
+
chainId: record.snapshot.sourceChain.id,
|
|
622
|
+
id: record.id,
|
|
623
|
+
providerState: evidence.providerState ?? {},
|
|
624
|
+
transactionHash,
|
|
625
|
+
})
|
|
626
|
+
if (registered.type === 'not_found')
|
|
627
|
+
return Response.error(c, {
|
|
628
|
+
code: 'funding_transfer_not_found',
|
|
629
|
+
message: 'Funding transfer not found',
|
|
630
|
+
status: 404,
|
|
631
|
+
})
|
|
632
|
+
if (registered.type === 'conflict')
|
|
633
|
+
return Response.error(c, {
|
|
634
|
+
code: 'source_transaction_conflict',
|
|
635
|
+
message: 'Source transaction is already assigned to another funding transfer',
|
|
636
|
+
status: 409,
|
|
637
|
+
})
|
|
638
|
+
if (registered.type === 'invalid')
|
|
639
|
+
return Response.error(c, {
|
|
640
|
+
code: 'funding_transfer_not_awaiting_source',
|
|
641
|
+
message: 'Funding transfer is not awaiting a source transaction',
|
|
642
|
+
status: 409,
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
await Promise.all([
|
|
646
|
+
dispatch({ transferId: record.id, type: 'funding:transfer:reconcile' }),
|
|
647
|
+
registered.type === 'registered' &&
|
|
648
|
+
options.dispatchTransferUpdates &&
|
|
649
|
+
registered.references.length > 0
|
|
650
|
+
? options.dispatchTransferUpdates(registered.references)
|
|
651
|
+
: undefined,
|
|
652
|
+
])
|
|
653
|
+
return c.json(
|
|
654
|
+
Response.validated(
|
|
655
|
+
schema.registerFundingTransferSourceTransaction.Response,
|
|
656
|
+
FundingTransfer.toPublic(registered.record),
|
|
657
|
+
),
|
|
658
|
+
200,
|
|
659
|
+
)
|
|
660
|
+
} catch (cause) {
|
|
661
|
+
return Response.upstream(c, cause)
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
)
|
|
425
665
|
.post(
|
|
426
666
|
'/v1/funding/transfers',
|
|
427
667
|
Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
|
|
@@ -590,12 +830,18 @@ export function transfers() {
|
|
|
590
830
|
// The transfer row, its version-1 event, and the replay body commit
|
|
591
831
|
// atomically so a crash cannot leave a claimed key without a replay.
|
|
592
832
|
const created = await db.transaction(async (tx) => {
|
|
593
|
-
const record = await FundingTransfer.createIn(tx, {
|
|
833
|
+
const { record, references } = await FundingTransfer.createIn(tx, {
|
|
594
834
|
apiKeyId: principal.id,
|
|
595
835
|
environment: owner.environment,
|
|
596
836
|
orgId: owner.orgId,
|
|
597
837
|
projectId: owner.projectId,
|
|
598
|
-
providerState:
|
|
838
|
+
providerState:
|
|
839
|
+
result.correlation || result.providerState
|
|
840
|
+
? {
|
|
841
|
+
...result.correlation,
|
|
842
|
+
...result.providerState,
|
|
843
|
+
}
|
|
844
|
+
: undefined,
|
|
599
845
|
snapshot,
|
|
600
846
|
})
|
|
601
847
|
const validated = Response.validated(
|
|
@@ -612,9 +858,15 @@ export function transfers() {
|
|
|
612
858
|
})
|
|
613
859
|
if (!completion)
|
|
614
860
|
throw new IdempotencyLeaseLostError('Funding transfer creation lease was lost.')
|
|
615
|
-
return validated
|
|
861
|
+
return { references, response: validated }
|
|
616
862
|
})
|
|
617
|
-
|
|
863
|
+
if (options.dispatchTransferUpdates && created.references.length > 0)
|
|
864
|
+
try {
|
|
865
|
+
await options.dispatchTransferUpdates(created.references)
|
|
866
|
+
} catch (cause) {
|
|
867
|
+
return Response.upstream(c, cause)
|
|
868
|
+
}
|
|
869
|
+
return c.json(created.response, 200)
|
|
618
870
|
} catch (cause) {
|
|
619
871
|
await release()
|
|
620
872
|
if (cause instanceof FundingProvider.InvalidAmountError)
|
|
@@ -708,6 +960,20 @@ export function transfers() {
|
|
|
708
960
|
)
|
|
709
961
|
}
|
|
710
962
|
|
|
963
|
+
export declare namespace transfers {
|
|
964
|
+
/** Funding transfer route dependencies. */
|
|
965
|
+
type Options = {
|
|
966
|
+
/** Enqueues one registered transfer for destination reconciliation. */
|
|
967
|
+
dispatchTransferReconciliation?:
|
|
968
|
+
| ((message: FundingTransferReconciliation.Message) => Promise<void>)
|
|
969
|
+
| undefined
|
|
970
|
+
/** Enqueues webhook updates staged with transfer mutations. */
|
|
971
|
+
dispatchTransferUpdates?:
|
|
972
|
+
| ((references: readonly Webhooks.QueueReference[]) => Promise<number>)
|
|
973
|
+
| undefined
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
711
977
|
function quoteNotAvailable(c: Context<App.Environment>) {
|
|
712
978
|
return Response.error(c, {
|
|
713
979
|
code: 'quote_not_available',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Hash, Hex, Value } from 'ox'
|
|
2
2
|
import type Stripe from 'stripe'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import * as ApiKey from '../../ApiKey.js'
|
|
5
5
|
import * as ApiKeys from '../../ApiKeys.js'
|
|
6
6
|
import type * as BillingSettings from '../../db/tables/billingSettings.js'
|
|
7
7
|
import * as Db from '../../db/Db.js'
|
|
@@ -9,6 +9,7 @@ import * as Fees from '../../internal/Fees.js'
|
|
|
9
9
|
import * as RequestUsage from '../../db/tables/requestUsage.js'
|
|
10
10
|
import type * as Scope from '../../Scope.js'
|
|
11
11
|
import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
|
|
12
|
+
import type * as Store from '../../internal/Store.js'
|
|
12
13
|
import * as StripeCustomers from '../../db/tables/stripeCustomers.js'
|
|
13
14
|
import * as Viem from '../../internal/Viem.js'
|
|
14
15
|
|
|
@@ -567,10 +568,22 @@ export function createRequestUsageReporter(
|
|
|
567
568
|
reported++
|
|
568
569
|
} catch (error) {
|
|
569
570
|
failed++
|
|
570
|
-
console.
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
571
|
+
// Cloudflare flattens multiple console arguments, so keep the driver details on one structured entry.
|
|
572
|
+
const cause = error instanceof Error ? error : undefined
|
|
573
|
+
console.error({
|
|
574
|
+
bucketStart,
|
|
575
|
+
component: 'request_usage_meter',
|
|
576
|
+
environment,
|
|
577
|
+
error: ApiKey.redact(cause?.message ?? String(error)).slice(0, 500),
|
|
578
|
+
...(cause &&
|
|
579
|
+
'code' in cause &&
|
|
580
|
+
(typeof cause.code === 'string' || typeof cause.code === 'number')
|
|
581
|
+
? { errorCode: ApiKey.redact(String(cause.code)).slice(0, 100) }
|
|
582
|
+
: {}),
|
|
583
|
+
errorType: cause?.name ?? typeof error,
|
|
584
|
+
orgId,
|
|
585
|
+
...(cause?.stack ? { stack: ApiKey.redact(cause.stack).slice(0, 4_000) } : {}),
|
|
586
|
+
})
|
|
574
587
|
}
|
|
575
588
|
}
|
|
576
589
|
}
|
|
@@ -11,7 +11,7 @@ import * as Webhooks from './webhooks.js'
|
|
|
11
11
|
|
|
12
12
|
const origin = 'http://localhost'
|
|
13
13
|
const webhook = {
|
|
14
|
-
applicationEventTypes: ['funding:deposit.updated'],
|
|
14
|
+
applicationEventTypes: ['funding:deposit.updated', 'funding:transfer.updated'],
|
|
15
15
|
supportedChainIds: [4217, 42431],
|
|
16
16
|
} satisfies App.Webhook
|
|
17
17
|
|
|
@@ -284,7 +284,7 @@ describe('webhooks', () => {
|
|
|
284
284
|
`)
|
|
285
285
|
})
|
|
286
286
|
|
|
287
|
-
test('creates funding
|
|
287
|
+
test('creates funding transfer subscriptions without reading the RPC head', async () => {
|
|
288
288
|
const db = TestApp.database()
|
|
289
289
|
const app = createApp({ db })
|
|
290
290
|
const cookie = await session(app)
|
|
@@ -295,7 +295,7 @@ describe('webhooks', () => {
|
|
|
295
295
|
chainId: 4217,
|
|
296
296
|
destination: { type: 'url', url: 'https://example.com/funding' },
|
|
297
297
|
environment: 'production',
|
|
298
|
-
eventType: 'funding:
|
|
298
|
+
eventType: 'funding:transfer.updated',
|
|
299
299
|
filters: { status: 'completed' },
|
|
300
300
|
}),
|
|
301
301
|
)
|
|
@@ -8,7 +8,7 @@ import * as OpenApi from '../../../internal/OpenApi.js'
|
|
|
8
8
|
import * as Response from '../../../internal/Response.js'
|
|
9
9
|
import * as WebhookDestination from '../../../internal/WebhookDestination.js'
|
|
10
10
|
import * as Webhooks from '../../../internal/Webhooks.js'
|
|
11
|
-
import
|
|
11
|
+
import * as DataWebhooks from '../../data/routes/webhooks.js'
|
|
12
12
|
import type { Environment } from '../App.js'
|
|
13
13
|
|
|
14
14
|
const defaultMaxPerOwner = 100
|
|
@@ -21,11 +21,11 @@ export namespace schema {
|
|
|
21
21
|
export const Subscription = OpenApi.component(
|
|
22
22
|
z
|
|
23
23
|
.object({
|
|
24
|
-
...
|
|
25
|
-
context:
|
|
24
|
+
...DataWebhooks.schema.Subscription.shape,
|
|
25
|
+
context: DataWebhooks.schema.Subscription.shape.context.check(
|
|
26
26
|
z.meta({ examples: [{ title: 'Production transfers' }] }),
|
|
27
27
|
),
|
|
28
|
-
destination:
|
|
28
|
+
destination: DataWebhooks.schema.Subscription.shape.destination.check(
|
|
29
29
|
z.meta({ examples: [{ type: 'url', url: 'https://example.com/webhooks' }] }),
|
|
30
30
|
),
|
|
31
31
|
})
|
|
@@ -35,7 +35,7 @@ export namespace schema {
|
|
|
35
35
|
|
|
36
36
|
/** Webhook delivery data returned in collection rows. */
|
|
37
37
|
export const Delivery = OpenApi.component(
|
|
38
|
-
|
|
38
|
+
DataWebhooks.schema.Delivery,
|
|
39
39
|
'OrganizationWebhookDeliverySummary',
|
|
40
40
|
)
|
|
41
41
|
|
|
@@ -44,7 +44,7 @@ export namespace schema {
|
|
|
44
44
|
const Environment = z
|
|
45
45
|
.enum(['production', 'sandbox'])
|
|
46
46
|
.check(
|
|
47
|
-
z.describe('API-key environment whose private funding
|
|
47
|
+
z.describe('API-key environment whose private funding resources are delivered.'),
|
|
48
48
|
z.meta({ examples: ['production'] }),
|
|
49
49
|
)
|
|
50
50
|
|
|
@@ -52,24 +52,24 @@ export namespace schema {
|
|
|
52
52
|
export const Body = OpenApi.component(
|
|
53
53
|
z
|
|
54
54
|
.intersection(
|
|
55
|
-
|
|
55
|
+
DataWebhooks.schema.createWebhook.Body,
|
|
56
56
|
z.object({
|
|
57
|
-
chainId:
|
|
57
|
+
chainId: DataWebhooks.schema.Subscription.shape.chainId,
|
|
58
58
|
environment: z.optional(Environment),
|
|
59
59
|
}),
|
|
60
60
|
)
|
|
61
61
|
.check(
|
|
62
62
|
z.refine(
|
|
63
63
|
(body) =>
|
|
64
|
-
body.eventType
|
|
65
|
-
{ error: '`environment` is required for funding
|
|
64
|
+
!DataWebhooks.isFundingEventType(body.eventType) || body.environment !== undefined,
|
|
65
|
+
{ error: '`environment` is required for funding subscriptions.' },
|
|
66
66
|
),
|
|
67
67
|
z.describe('Details for creating an organization webhook subscription.'),
|
|
68
68
|
),
|
|
69
69
|
'CreateOrganizationWebhookRequest',
|
|
70
70
|
)
|
|
71
71
|
|
|
72
|
-
const secret =
|
|
72
|
+
const secret = DataWebhooks.schema.createWebhook.Response.shape.secret
|
|
73
73
|
const urlDestination = z
|
|
74
74
|
.object({
|
|
75
75
|
type: z
|
|
@@ -192,7 +192,7 @@ export namespace schema {
|
|
|
192
192
|
/** Schemas for listing an organization webhook's delivery attempts. */
|
|
193
193
|
export namespace listWebhookDeliveries {
|
|
194
194
|
export const Params = WebhookParams
|
|
195
|
-
export const Query =
|
|
195
|
+
export const Query = DataWebhooks.schema.listWebhookDeliveries.Query
|
|
196
196
|
export const Response = OpenApi.component(
|
|
197
197
|
z
|
|
198
198
|
.object({
|
|
@@ -202,10 +202,10 @@ export namespace schema {
|
|
|
202
202
|
z.describe('Webhook delivery attempts on this page.'),
|
|
203
203
|
z.meta({ examples: [[]] }),
|
|
204
204
|
),
|
|
205
|
-
meta:
|
|
205
|
+
meta: DataWebhooks.schema.listWebhookDeliveries.Response.shape.meta.check(
|
|
206
206
|
z.meta({ examples: [{ totalCount: 1, totalCountCapped: false }] }),
|
|
207
207
|
),
|
|
208
|
-
nextCursor:
|
|
208
|
+
nextCursor: DataWebhooks.schema.listWebhookDeliveries.Response.shape.nextCursor,
|
|
209
209
|
})
|
|
210
210
|
.check(z.describe("A page of an organization's webhook delivery attempts.")),
|
|
211
211
|
'OrganizationWebhookDeliveryList',
|
|
@@ -214,7 +214,7 @@ export namespace schema {
|
|
|
214
214
|
|
|
215
215
|
const DeliveryParams = z
|
|
216
216
|
.object({
|
|
217
|
-
deliveryId:
|
|
217
|
+
deliveryId: DataWebhooks.schema.retryWebhookDelivery.Params.shape.deliveryId,
|
|
218
218
|
id: WebhookParams.shape.id,
|
|
219
219
|
orgId: WebhookParams.shape.orgId,
|
|
220
220
|
})
|
|
@@ -227,7 +227,7 @@ export namespace schema {
|
|
|
227
227
|
z
|
|
228
228
|
.object({
|
|
229
229
|
...Delivery.shape,
|
|
230
|
-
envelope:
|
|
230
|
+
envelope: DataWebhooks.schema.Envelope,
|
|
231
231
|
})
|
|
232
232
|
.check(z.describe('A webhook delivery attempt with the exact JSON payload Tempo sent.')),
|
|
233
233
|
'OrganizationWebhookDelivery',
|
|
@@ -238,7 +238,7 @@ export namespace schema {
|
|
|
238
238
|
export namespace retryWebhookDelivery {
|
|
239
239
|
export const Params = DeliveryParams
|
|
240
240
|
export const Response = OpenApi.component(
|
|
241
|
-
|
|
241
|
+
DataWebhooks.schema.retryWebhookDelivery.Response,
|
|
242
242
|
'RetryOrganizationWebhookDeliveryResponse',
|
|
243
243
|
)
|
|
244
244
|
}
|
|
@@ -261,7 +261,7 @@ export namespace schema {
|
|
|
261
261
|
/** Lifecycle state editable from the console. */
|
|
262
262
|
export const UpdateBody = OpenApi.component(
|
|
263
263
|
z
|
|
264
|
-
.object({ status:
|
|
264
|
+
.object({ status: DataWebhooks.schema.Status })
|
|
265
265
|
.check(z.describe('New delivery state for a webhook subscription.')),
|
|
266
266
|
'UpdateOrganizationWebhookRequest',
|
|
267
267
|
)
|
|
@@ -344,7 +344,7 @@ export function webhooks(options: webhooks.Options = {}) {
|
|
|
344
344
|
|
|
345
345
|
const body = c.req.valid('json')
|
|
346
346
|
if (
|
|
347
|
-
body.eventType
|
|
347
|
+
DataWebhooks.isFundingEventType(body.eventType) &&
|
|
348
348
|
!options.webhook.applicationEventTypes?.includes(body.eventType)
|
|
349
349
|
)
|
|
350
350
|
return Response.error(c, {
|
|
@@ -364,17 +364,16 @@ export function webhooks(options: webhooks.Options = {}) {
|
|
|
364
364
|
const owner_ = owner(Auth.org(c).id)
|
|
365
365
|
if ((await Webhooks.countSubscriptions(db, owner_)) >= maxPerOwner)
|
|
366
366
|
throw new Webhooks.LimitExceededError(maxPerOwner)
|
|
367
|
-
const startBlockNumber =
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
: Number(await c.get('getClient')(chainId).getBlockNumber())
|
|
367
|
+
const startBlockNumber = DataWebhooks.isFundingEventType(body.eventType)
|
|
368
|
+
? undefined
|
|
369
|
+
: Number(await c.get('getClient')(chainId).getBlockNumber())
|
|
371
370
|
const subscription = await Webhooks.createSubscription(
|
|
372
371
|
db,
|
|
373
372
|
{
|
|
374
373
|
chainId,
|
|
375
374
|
...(body.context === undefined ? {} : { context: body.context }),
|
|
376
375
|
destination: body.destination,
|
|
377
|
-
...(body.eventType
|
|
376
|
+
...(DataWebhooks.isFundingEventType(body.eventType) && body.environment !== undefined
|
|
378
377
|
? { environment: body.environment }
|
|
379
378
|
: {}),
|
|
380
379
|
eventType: body.eventType,
|
|
@@ -754,7 +753,7 @@ function publicSubscription(subscription: Webhooks.Subscription) {
|
|
|
754
753
|
...(subscription.context === undefined ? {} : { context: subscription.context }),
|
|
755
754
|
createdAt: subscription.createdAt,
|
|
756
755
|
destination,
|
|
757
|
-
...(subscription.eventType
|
|
756
|
+
...(DataWebhooks.isFundingEventType(subscription.eventType) &&
|
|
758
757
|
subscription.environment !== undefined
|
|
759
758
|
? { environment: subscription.environment }
|
|
760
759
|
: {}),
|