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
|
@@ -310,11 +310,11 @@ function privateResourceAccess(access: Webhooks.Access) {
|
|
|
310
310
|
)
|
|
311
311
|
return (eb: ExpressionBuilder<Db.Database, 'webhook_subscriptions'>) =>
|
|
312
312
|
eb.or([
|
|
313
|
-
eb('eventType'
|
|
313
|
+
sql<boolean>`${eb.ref('eventType')} not like ${'funding:%'}`,
|
|
314
314
|
...(fundingRead
|
|
315
315
|
? [
|
|
316
316
|
eb.and([
|
|
317
|
-
eb('eventType'
|
|
317
|
+
sql<boolean>`${eb.ref('eventType')} like ${'funding:%'}`,
|
|
318
318
|
eb('environment', '=', access.environment),
|
|
319
319
|
...(access.projectId === undefined ? [] : [eb('projectId', '=', access.projectId)]),
|
|
320
320
|
]),
|
|
@@ -390,41 +390,48 @@ export declare namespace listActiveForChain {
|
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
/** Lists live funding
|
|
394
|
-
export async function
|
|
393
|
+
/** Lists live funding subscriptions visible to one resource owner. */
|
|
394
|
+
export async function listActiveForFundingEvent(
|
|
395
395
|
db: Db.Db,
|
|
396
|
-
options:
|
|
396
|
+
options: listActiveForFundingEvent.Options,
|
|
397
397
|
): Promise<Webhooks.Subscription[]> {
|
|
398
398
|
let query = db.kysely
|
|
399
399
|
.selectFrom('webhook_subscriptions')
|
|
400
400
|
.selectAll()
|
|
401
401
|
.where('chainId', '=', options.chainId)
|
|
402
402
|
.where('environment', '=', options.environment)
|
|
403
|
-
.where('eventType', '=',
|
|
403
|
+
.where('eventType', '=', options.eventType)
|
|
404
404
|
.where('ownerId', '=', options.orgId)
|
|
405
405
|
.where('ownerType', '=', 'api_key')
|
|
406
406
|
.where('status', '=', 'active')
|
|
407
407
|
.where(live(options.now))
|
|
408
|
+
if (options.eventCreatedAt !== undefined)
|
|
409
|
+
query = query.where('createdAt', '<=', options.eventCreatedAt)
|
|
408
410
|
const projectId = options.projectId
|
|
409
411
|
query =
|
|
410
412
|
projectId === undefined
|
|
411
413
|
? query.where('projectId', 'is', null)
|
|
412
414
|
: query.where((eb) => eb.or([eb('projectId', 'is', null), eb('projectId', '=', projectId)]))
|
|
413
|
-
|
|
415
|
+
// Keep subscriptions present until their staged queue events commit.
|
|
416
|
+
return (await query.orderBy('id').forKeyShare().execute()).map(toSubscription)
|
|
414
417
|
}
|
|
415
418
|
|
|
416
|
-
export declare namespace
|
|
417
|
-
/** Ownership scope and chain for a funding
|
|
419
|
+
export declare namespace listActiveForFundingEvent {
|
|
420
|
+
/** Ownership scope and chain for a funding resource event. */
|
|
418
421
|
type Options = {
|
|
419
422
|
/** Destination Tempo chain id. */
|
|
420
423
|
chainId: number
|
|
421
|
-
/**
|
|
424
|
+
/** Resource API-key environment. */
|
|
422
425
|
environment: 'production' | 'sandbox'
|
|
426
|
+
/** Event timestamp used to keep subscriptions future-only, when supplied. */
|
|
427
|
+
eventCreatedAt?: string | undefined
|
|
428
|
+
/** Funding event type to match. */
|
|
429
|
+
eventType: Extract<Webhooks.EventType, `funding:${string}`>
|
|
423
430
|
/** ISO timestamp used to exclude expired subscriptions. */
|
|
424
431
|
now: string
|
|
425
432
|
/** Owning organization id. */
|
|
426
433
|
orgId: string
|
|
427
|
-
/** Attributed
|
|
434
|
+
/** Attributed funding resource project, when present. */
|
|
428
435
|
projectId?: string | undefined
|
|
429
436
|
}
|
|
430
437
|
}
|
|
@@ -157,7 +157,7 @@ export namespace schema {
|
|
|
157
157
|
),
|
|
158
158
|
vaultAddress: Schema.Address.check(
|
|
159
159
|
z.describe('Earn vault contract address.'),
|
|
160
|
-
z.meta({ examples: ['
|
|
160
|
+
z.meta({ examples: ['0x4f94590b636f5878bce585e82379de81e1ec174f'] }),
|
|
161
161
|
),
|
|
162
162
|
})
|
|
163
163
|
.check(z.describe('Onchain identity of an Earn vault.'))
|
|
@@ -167,7 +167,7 @@ export namespace schema {
|
|
|
167
167
|
.strictObject({
|
|
168
168
|
chainId: Schema.ChainId.check(
|
|
169
169
|
z.describe('Private Zone chain id.'),
|
|
170
|
-
z.meta({ examples: [
|
|
170
|
+
z.meta({ examples: [421700001] }),
|
|
171
171
|
),
|
|
172
172
|
deploymentBlock: z
|
|
173
173
|
.number()
|
|
@@ -175,11 +175,11 @@ export namespace schema {
|
|
|
175
175
|
z.int(),
|
|
176
176
|
z.nonnegative(),
|
|
177
177
|
z.describe('Parent-chain block that deployed the Earn router.'),
|
|
178
|
-
z.meta({ examples: [
|
|
178
|
+
z.meta({ examples: [35816964] }),
|
|
179
179
|
),
|
|
180
180
|
earnRouter: Schema.Address.check(
|
|
181
181
|
z.describe('Parent-chain router for this Zone and Earn vault.'),
|
|
182
|
-
z.meta({ examples: ['
|
|
182
|
+
z.meta({ examples: ['0x8117e0ba6239b9695f780deb010f72a2fa4bdfb6'] }),
|
|
183
183
|
),
|
|
184
184
|
})
|
|
185
185
|
.check(z.describe('Curated Earn route through one private Zone.'))
|
|
@@ -239,7 +239,7 @@ export namespace schema {
|
|
|
239
239
|
state,
|
|
240
240
|
vaultAddress: Schema.Address.check(
|
|
241
241
|
z.describe('Earn vault contract address.'),
|
|
242
|
-
z.meta({ examples: ['
|
|
242
|
+
z.meta({ examples: ['0x4f94590b636f5878bce585e82379de81e1ec174f'] }),
|
|
243
243
|
),
|
|
244
244
|
})
|
|
245
245
|
.check(z.describe('Live state resolved from an Earn vault.'))
|
|
@@ -29,6 +29,8 @@ test('bounds sponsorship diagnostics', () => {
|
|
|
29
29
|
'fee_token_unsupported',
|
|
30
30
|
'internal_error',
|
|
31
31
|
'production_api_key_required',
|
|
32
|
+
'project_id_invalid',
|
|
33
|
+
'project_id_required',
|
|
32
34
|
'spend_limit_exceeded',
|
|
33
35
|
'tx_fee_limit_exceeded',
|
|
34
36
|
] as const satisfies readonly Log.SponsorshipReason[]
|
package/src/internal/Log.ts
CHANGED
package/src/internal/Viem.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { ZoneRpcAuthentication } from 'ox/tempo'
|
|
2
|
-
import { createClient, http, publicActions } from 'viem'
|
|
3
|
-
import type {
|
|
2
|
+
import { createClient as core_createClient, fallback, http, publicActions } from 'viem'
|
|
3
|
+
import type {
|
|
4
|
+
Chain as core_Chain,
|
|
5
|
+
Client as core_Client,
|
|
6
|
+
FallbackTransport,
|
|
7
|
+
HttpTransport,
|
|
8
|
+
PublicActions,
|
|
9
|
+
} from 'viem'
|
|
4
10
|
import { tempoActions } from 'viem/tempo'
|
|
5
11
|
import type { TempoActions } from 'viem/tempo'
|
|
6
12
|
import { tempoMainnet, tempoTestnet } from 'viem/tempo/chains'
|
|
@@ -51,6 +57,36 @@ export type UrlResolver =
|
|
|
51
57
|
/** Headers required when connecting to a Zone upstream. */
|
|
52
58
|
export type ZoneHeaders = Record<string, string>
|
|
53
59
|
|
|
60
|
+
/** Creates a generic EVM client with ordered RPC endpoint fallback. */
|
|
61
|
+
export function createEvmClient(options: createEvmClient.Options): createEvmClient.ReturnType {
|
|
62
|
+
return core_createClient({
|
|
63
|
+
transport: fallback(
|
|
64
|
+
options.urls.map((url) =>
|
|
65
|
+
http(url, {
|
|
66
|
+
fetchFn: options.fetch,
|
|
67
|
+
...(options.signal ? { fetchOptions: { signal: options.signal } } : {}),
|
|
68
|
+
retryCount: 0,
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export declare namespace createEvmClient {
|
|
76
|
+
/** Generic EVM RPC client configuration. */
|
|
77
|
+
type Options = {
|
|
78
|
+
/** Fetch implementation used for RPC requests. */
|
|
79
|
+
fetch: typeof globalThis.fetch
|
|
80
|
+
/** Optional request cancellation signal. */
|
|
81
|
+
signal?: AbortSignal | undefined
|
|
82
|
+
/** Ordered RPC endpoint URLs. */
|
|
83
|
+
urls: readonly string[]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Generic EVM client with fallback transport. */
|
|
87
|
+
type ReturnType = core_Client<FallbackTransport, undefined, undefined>
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
/** Returns chain ids declared by a static chain-id-to-URL map. */
|
|
55
91
|
export function configuredChainIds(value: UrlResolver | undefined): number[] {
|
|
56
92
|
const urls = parseUrls(value)
|
|
@@ -158,7 +194,7 @@ export function getClient(options: getClient.Options): getClient.ReturnType {
|
|
|
158
194
|
}
|
|
159
195
|
: undefined
|
|
160
196
|
|
|
161
|
-
return
|
|
197
|
+
return core_createClient({
|
|
162
198
|
// Auto-batch `eth_call`s within the same macrotask into a single
|
|
163
199
|
// deployless multicall. Lets call sites issue `Promise.all` of N reads
|
|
164
200
|
// (e.g. `balanceOf` across many tokens) and pay one RPC round-trip.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Webhooks } from 'tapimo'
|
|
2
|
+
import { sql } from 'kysely'
|
|
2
3
|
import * as TestApp from '../../test/App.js'
|
|
4
|
+
import * as WebhookSubscriptions from '../db/tables/webhookSubscriptions.js'
|
|
3
5
|
import * as Metrics from '../Metrics.js'
|
|
4
6
|
import * as Cursor from './Cursor.js'
|
|
5
7
|
|
|
@@ -170,6 +172,49 @@ describe('createSubscription', () => {
|
|
|
170
172
|
})
|
|
171
173
|
})
|
|
172
174
|
|
|
175
|
+
describe('listActiveForFundingEvent', () => {
|
|
176
|
+
test('keeps selected subscriptions until the caller transaction commits', async () => {
|
|
177
|
+
const getDb = TestApp.databaseFactory()
|
|
178
|
+
const owner = { orgId: 'org_funding_subscription_lock', type: 'api_key' } as const
|
|
179
|
+
const subscription = await Webhooks.createSubscription(
|
|
180
|
+
getDb(),
|
|
181
|
+
createInput({
|
|
182
|
+
environment: 'production',
|
|
183
|
+
eventType: 'funding:transfer.updated',
|
|
184
|
+
owner,
|
|
185
|
+
}),
|
|
186
|
+
)
|
|
187
|
+
const locked = Promise.withResolvers<void>()
|
|
188
|
+
const release = Promise.withResolvers<void>()
|
|
189
|
+
const read = getDb().transaction(async (tx) => {
|
|
190
|
+
const subscriptions = await WebhookSubscriptions.listActiveForFundingEvent(tx, {
|
|
191
|
+
chainId: 4217,
|
|
192
|
+
environment: 'production',
|
|
193
|
+
eventType: 'funding:transfer.updated',
|
|
194
|
+
now: new Date().toISOString(),
|
|
195
|
+
orgId: owner.orgId,
|
|
196
|
+
})
|
|
197
|
+
locked.resolve()
|
|
198
|
+
await release.promise
|
|
199
|
+
return subscriptions
|
|
200
|
+
})
|
|
201
|
+
await locked.promise
|
|
202
|
+
|
|
203
|
+
const deletion = await getDb()
|
|
204
|
+
.transaction(async (tx) => {
|
|
205
|
+
await sql`select set_config('lock_timeout', '100ms', true)`.execute(tx.kysely)
|
|
206
|
+
return Webhooks.deleteSubscription(tx, owner, subscription.id)
|
|
207
|
+
})
|
|
208
|
+
.catch((cause: unknown) => cause)
|
|
209
|
+
release.resolve()
|
|
210
|
+
|
|
211
|
+
await expect(read).resolves.toEqual([subscription])
|
|
212
|
+
expect(deletion).toBeInstanceOf(Error)
|
|
213
|
+
expect(String(deletion)).toContain('lock timeout')
|
|
214
|
+
await expect(Webhooks.deleteSubscription(getDb(), owner, subscription.id)).resolves.toBe(true)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
173
218
|
describe('owner isolation', () => {
|
|
174
219
|
test('an owner cannot read or list another owner subscriptions', async () => {
|
|
175
220
|
const db = TestApp.database()
|
|
@@ -37,9 +37,10 @@ export async function stage(
|
|
|
37
37
|
for (const record of unique(options.records)) {
|
|
38
38
|
const chainId = destinationChainId(record)
|
|
39
39
|
if (chainId === undefined) continue
|
|
40
|
-
const subscriptions = await WebhookSubscriptions.
|
|
40
|
+
const subscriptions = await WebhookSubscriptions.listActiveForFundingEvent(db, {
|
|
41
41
|
chainId,
|
|
42
42
|
environment: record.environment,
|
|
43
|
+
eventType: 'funding:deposit.updated',
|
|
43
44
|
now,
|
|
44
45
|
orgId: record.orgId,
|
|
45
46
|
...(record.projectId === null ? {} : { projectId: record.projectId }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbiEvent, Address, Base58, Bytes, Hash, Hex, RpcSchema, RpcTransport } from 'ox'
|
|
2
|
-
import {
|
|
2
|
+
import { TransactionReceiptNotFoundError } from 'viem'
|
|
3
3
|
import { getBlockNumber, getTransactionReceipt } from 'viem/actions'
|
|
4
4
|
import * as z from 'zod/mini'
|
|
5
5
|
|
|
@@ -195,7 +195,7 @@ async function verifyEvm(
|
|
|
195
195
|
parameters: Reconciliation.VerifyTransfers.Parameters,
|
|
196
196
|
options: FetchOptions & { confirmations: number },
|
|
197
197
|
): Promise<readonly Reconciliation.TransferEvidence[]> {
|
|
198
|
-
const client =
|
|
198
|
+
const client = Viem.createEvmClient(options)
|
|
199
199
|
const [receipt, head] = await Promise.all([
|
|
200
200
|
getTransactionReceipt(client, { hash: parameters.transactionHash as Hex.Hex }).catch(
|
|
201
201
|
(cause) => {
|
|
@@ -334,17 +334,6 @@ async function verifyTron(
|
|
|
334
334
|
})
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
function jsonRpcClient(options: FetchOptions) {
|
|
338
|
-
return createClient({
|
|
339
|
-
transport: fallback(
|
|
340
|
-
options.urls.map((url) => {
|
|
341
|
-
const transport = RpcTransport.fromHttp(url, { fetchFn: options.fetch })
|
|
342
|
-
return custom(transport, { retryCount: 0 })
|
|
343
|
-
}),
|
|
344
|
-
),
|
|
345
|
-
})
|
|
346
|
-
}
|
|
347
|
-
|
|
348
337
|
async function requestSolana(
|
|
349
338
|
options: FetchOptions,
|
|
350
339
|
request: SolanaRpcSchema['Request'],
|
|
@@ -232,6 +232,7 @@ export namespace schema {
|
|
|
232
232
|
token: z.optional(TokenRef),
|
|
233
233
|
}),
|
|
234
234
|
),
|
|
235
|
+
providerState: z.optional(z.record(z.string(), z.unknown())),
|
|
235
236
|
quality: GetQuoteResult.shape.quality,
|
|
236
237
|
sampledAt: z.iso.datetime(),
|
|
237
238
|
})
|
|
@@ -485,6 +486,36 @@ export declare namespace validateTransferAction {
|
|
|
485
486
|
}
|
|
486
487
|
}
|
|
487
488
|
|
|
489
|
+
/** Contract for provider-specific source transaction verification. */
|
|
490
|
+
export declare namespace verifySourceTransaction {
|
|
491
|
+
/** Stored transfer terms and submitted source transaction. */
|
|
492
|
+
type Parameters = {
|
|
493
|
+
/** Configured chain that receives the provider delivery. */
|
|
494
|
+
destinationChain: Chain
|
|
495
|
+
/** Private provider state captured while preparing the transfer. */
|
|
496
|
+
providerState?: Transfer.ProviderState | undefined
|
|
497
|
+
/** Current configuration for the stored provider route. */
|
|
498
|
+
routeConfiguration?: core_Catalog.Configuration | undefined
|
|
499
|
+
/** Configured chain containing the submitted transaction. */
|
|
500
|
+
sourceChain: Chain
|
|
501
|
+
/** Submitted transaction reference. */
|
|
502
|
+
transactionHash: string
|
|
503
|
+
/** Stored public transfer terms. */
|
|
504
|
+
transfer: Transfer.Snapshot
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Source transaction verification outcome. */
|
|
508
|
+
type ReturnType =
|
|
509
|
+
| { type: 'invalid' }
|
|
510
|
+
| { type: 'pending' }
|
|
511
|
+
| {
|
|
512
|
+
/** Private provider evidence retained for reconciliation. */
|
|
513
|
+
providerState?: Transfer.ProviderState | undefined
|
|
514
|
+
/** The transaction matches the stored transfer terms. */
|
|
515
|
+
type: 'verified'
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
488
519
|
/** Contract of the provider `createDepositAddress` method. */
|
|
489
520
|
export declare namespace createDepositAddress {
|
|
490
521
|
/** Input passed to a provider provisioning one reusable deposit address. */
|
|
@@ -705,6 +736,11 @@ export type Provider<id extends string = string> = core_Provider.Provider<id, 'f
|
|
|
705
736
|
validateTransferAction?(
|
|
706
737
|
input: validateTransferAction.Parameters,
|
|
707
738
|
): validateTransferAction.ReturnType
|
|
739
|
+
/** Verifies a submitted source transaction, when supported. */
|
|
740
|
+
verifySourceTransaction?(
|
|
741
|
+
input: verifySourceTransaction.Parameters,
|
|
742
|
+
signal: AbortSignal,
|
|
743
|
+
): Promise<verifySourceTransaction.ReturnType>
|
|
708
744
|
/** Receives authenticated provider status events, when configured. */
|
|
709
745
|
webhook?: Webhook | undefined
|
|
710
746
|
}
|
|
@@ -756,6 +792,13 @@ export function canObserveDepositAddressRequest(provider: Provider): provider is
|
|
|
756
792
|
return typeof provider.observeDepositAddressRequest === 'function'
|
|
757
793
|
}
|
|
758
794
|
|
|
795
|
+
/** Narrows a funding provider to one that verifies submitted source transactions. */
|
|
796
|
+
export function canVerifySourceTransaction(provider: Provider): provider is Provider & {
|
|
797
|
+
verifySourceTransaction: NonNullable<Provider['verifySourceTransaction']>
|
|
798
|
+
} {
|
|
799
|
+
return typeof provider.verifySourceTransaction === 'function'
|
|
800
|
+
}
|
|
801
|
+
|
|
759
802
|
/** Narrows a funding provider to one with authenticated webhook receipt. */
|
|
760
803
|
export function canReceiveWebhook(provider: Provider): provider is Provider & {
|
|
761
804
|
webhook: Webhook
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { AbiEvent, Address
|
|
2
|
-
import { createClient, custom, fallback } from 'viem'
|
|
1
|
+
import { AbiEvent, Address } from 'ox'
|
|
3
2
|
import { getBlockNumber, getLogs } from 'viem/actions'
|
|
4
3
|
|
|
5
4
|
import type * as Db from '../../db/Db.js'
|
|
6
5
|
import * as FundingDepositAddresses from '../../db/tables/fundingDepositAddresses.js'
|
|
7
6
|
import * as FundingDeposits from '../../db/tables/fundingDeposits.js'
|
|
7
|
+
import * as Viem from '../Viem.js'
|
|
8
8
|
import * as Value from '../Value.js'
|
|
9
9
|
import * as Deposit from './Deposit.js'
|
|
10
10
|
|
|
@@ -52,13 +52,9 @@ export async function scan(options: scan.Options): Promise<scan.Result> {
|
|
|
52
52
|
throw new ConfigurationError('source token address')
|
|
53
53
|
if (options.rpcUrls.length === 0) throw new ConfigurationError('source RPC URL')
|
|
54
54
|
|
|
55
|
-
const client =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const transport = RpcTransport.fromHttp(url, { fetchFn: options.fetch ?? globalThis.fetch })
|
|
59
|
-
return custom(transport, { retryCount: 0 })
|
|
60
|
-
}),
|
|
61
|
-
),
|
|
55
|
+
const client = Viem.createEvmClient({
|
|
56
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
57
|
+
urls: options.rpcUrls,
|
|
62
58
|
})
|
|
63
59
|
const head = await getBlockNumber(client, { cacheTime: 0 })
|
|
64
60
|
const confirmations = BigInt(options.confirmations ?? defaultConfirmations)
|
|
@@ -42,6 +42,7 @@ describe('canTransition', () => {
|
|
|
42
42
|
"completed": [],
|
|
43
43
|
"expired": [],
|
|
44
44
|
"processing": [
|
|
45
|
+
"awaiting-source",
|
|
45
46
|
"refunding",
|
|
46
47
|
"completed",
|
|
47
48
|
"action-required",
|
|
@@ -129,6 +130,160 @@ describe('create', () => {
|
|
|
129
130
|
})
|
|
130
131
|
})
|
|
131
132
|
|
|
133
|
+
describe('registerSourceTransaction', () => {
|
|
134
|
+
const transactionHash = `0x${'a2'.repeat(32)}`
|
|
135
|
+
|
|
136
|
+
test('atomically assigns source evidence and starts reconciliation', async () => {
|
|
137
|
+
const record = await Transfer.create(
|
|
138
|
+
db,
|
|
139
|
+
createInput({
|
|
140
|
+
providerState: {
|
|
141
|
+
routeConfiguration: {
|
|
142
|
+
destinationPoolAddress: `0x${'cc'.repeat(20)}`,
|
|
143
|
+
poolAddress: `0x${'dd'.repeat(20)}`,
|
|
144
|
+
sourceEid: 30_184,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
snapshot: TestFunding.transferSnapshot({ provider: { id: 'stargate', name: 'Stargate' } }),
|
|
148
|
+
}),
|
|
149
|
+
)
|
|
150
|
+
vi.setSystemTime(new Date('2026-01-01T00:00:10.000Z'))
|
|
151
|
+
const result = await Transfer.registerSourceTransaction(db, {
|
|
152
|
+
chainId: 'eip155:8453',
|
|
153
|
+
id: record.id,
|
|
154
|
+
providerState: { stargate: { guid: `0x${'bb'.repeat(32)}`, sourceEid: 30_184 } },
|
|
155
|
+
transactionHash: `0x${'a1'.repeat(32)}`,
|
|
156
|
+
})
|
|
157
|
+
expect(result.type).toBe('registered')
|
|
158
|
+
if (result.type !== 'registered') return
|
|
159
|
+
expect({
|
|
160
|
+
providerState: result.record.providerState,
|
|
161
|
+
sourceTransactionHashes: result.record.snapshot.sourceTransactionHashes,
|
|
162
|
+
status: result.record.status,
|
|
163
|
+
version: result.record.version,
|
|
164
|
+
}).toMatchInlineSnapshot(`
|
|
165
|
+
{
|
|
166
|
+
"providerState": {
|
|
167
|
+
"routeConfiguration": {
|
|
168
|
+
"destinationPoolAddress": "0xcccccccccccccccccccccccccccccccccccccccc",
|
|
169
|
+
"poolAddress": "0xdddddddddddddddddddddddddddddddddddddddd",
|
|
170
|
+
"sourceEid": 30184,
|
|
171
|
+
},
|
|
172
|
+
"stargate": {
|
|
173
|
+
"guid": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
174
|
+
"sourceEid": 30184,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
"sourceTransactionHashes": [
|
|
178
|
+
"0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
|
|
179
|
+
],
|
|
180
|
+
"status": "processing",
|
|
181
|
+
"version": 2,
|
|
182
|
+
}
|
|
183
|
+
`)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('replays the same assignment and rejects reuse by another transfer', async () => {
|
|
187
|
+
const first = await Transfer.create(db, createInput())
|
|
188
|
+
const second = await Transfer.create(db, createInput())
|
|
189
|
+
const options = {
|
|
190
|
+
chainId: 'eip155:8453',
|
|
191
|
+
id: first.id,
|
|
192
|
+
providerState: { stargate: { guid: `0x${'bb'.repeat(32)}`, sourceEid: 30_184 } },
|
|
193
|
+
transactionHash,
|
|
194
|
+
}
|
|
195
|
+
await Transfer.registerSourceTransaction(db, options)
|
|
196
|
+
const replayed = await Transfer.registerSourceTransaction(db, options)
|
|
197
|
+
const conflict = await Transfer.registerSourceTransaction(db, { ...options, id: second.id })
|
|
198
|
+
expect({
|
|
199
|
+
conflict: conflict.type,
|
|
200
|
+
replayed: replayed.type,
|
|
201
|
+
replayedVersion: replayed.type === 'replayed' ? replayed.record.version : undefined,
|
|
202
|
+
}).toEqual({ conflict: 'conflict', replayed: 'replayed', replayedVersion: 2 })
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
describe('reopenSourceTransaction', () => {
|
|
207
|
+
test('removes invalidated source evidence and accepts a replacement', async () => {
|
|
208
|
+
const record = await Transfer.create(db, createInput())
|
|
209
|
+
const transactionHash = `0x${'a4'.repeat(32)}`
|
|
210
|
+
await Transfer.registerSourceTransaction(db, {
|
|
211
|
+
chainId: 'eip155:8453',
|
|
212
|
+
id: record.id,
|
|
213
|
+
providerState: { stargate: { guid: `0x${'bb'.repeat(32)}`, sourceEid: 30_184 } },
|
|
214
|
+
transactionHash,
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const reopened = await Transfer.reopenSourceTransaction(db, {
|
|
218
|
+
chainId: 'eip155:8453',
|
|
219
|
+
id: record.id,
|
|
220
|
+
providerState: { routeConfiguration: { poolAddress: `0x${'cc'.repeat(20)}` } },
|
|
221
|
+
transactionHash: transactionHash.toUpperCase(),
|
|
222
|
+
})
|
|
223
|
+
expect(reopened.type).toBe('reopened')
|
|
224
|
+
if (reopened.type !== 'reopened') return
|
|
225
|
+
expect({
|
|
226
|
+
providerState: reopened.record.providerState,
|
|
227
|
+
sourceTransactionHashes: reopened.record.snapshot.sourceTransactionHashes,
|
|
228
|
+
status: reopened.record.status,
|
|
229
|
+
version: reopened.record.version,
|
|
230
|
+
}).toEqual({
|
|
231
|
+
providerState: { routeConfiguration: { poolAddress: `0x${'cc'.repeat(20)}` } },
|
|
232
|
+
sourceTransactionHashes: undefined,
|
|
233
|
+
status: 'awaiting-source',
|
|
234
|
+
version: 3,
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const replacement = await Transfer.registerSourceTransaction(db, {
|
|
238
|
+
chainId: 'eip155:8453',
|
|
239
|
+
id: record.id,
|
|
240
|
+
providerState: { stargate: { guid: `0x${'dd'.repeat(32)}`, sourceEid: 30_184 } },
|
|
241
|
+
transactionHash: `0x${'a5'.repeat(32)}`,
|
|
242
|
+
})
|
|
243
|
+
expect(replacement.type).toBe('registered')
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
describe('complete', () => {
|
|
248
|
+
test('records verified destination evidence and completes once', async () => {
|
|
249
|
+
const record = await Transfer.create(db, createInput())
|
|
250
|
+
await Transfer.registerSourceTransaction(db, {
|
|
251
|
+
chainId: 'eip155:8453',
|
|
252
|
+
id: record.id,
|
|
253
|
+
providerState: { stargate: { guid: `0x${'bb'.repeat(32)}`, sourceEid: 30_184 } },
|
|
254
|
+
transactionHash: `0x${'a3'.repeat(32)}`,
|
|
255
|
+
})
|
|
256
|
+
const options = {
|
|
257
|
+
chainId: 'eip155:4217',
|
|
258
|
+
destinationAmount: TestFunding.transferSnapshot().destinationAmount!,
|
|
259
|
+
id: record.id,
|
|
260
|
+
transactionHash: `0x${'cc'.repeat(32)}`,
|
|
261
|
+
}
|
|
262
|
+
const completed = await Transfer.complete(db, options)
|
|
263
|
+
const replayed = await Transfer.complete(db, options)
|
|
264
|
+
expect({
|
|
265
|
+
completed: completed.type,
|
|
266
|
+
destinationTransactionHashes:
|
|
267
|
+
completed.type === 'completed'
|
|
268
|
+
? completed.record.snapshot.destinationTransactionHashes
|
|
269
|
+
: undefined,
|
|
270
|
+
replayed: replayed.type,
|
|
271
|
+
status: completed.type === 'completed' ? completed.record.status : undefined,
|
|
272
|
+
version: completed.type === 'completed' ? completed.record.version : undefined,
|
|
273
|
+
}).toMatchInlineSnapshot(`
|
|
274
|
+
{
|
|
275
|
+
"completed": "completed",
|
|
276
|
+
"destinationTransactionHashes": [
|
|
277
|
+
"0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
|
278
|
+
],
|
|
279
|
+
"replayed": "replayed",
|
|
280
|
+
"status": "completed",
|
|
281
|
+
"version": 3,
|
|
282
|
+
}
|
|
283
|
+
`)
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
132
287
|
describe('transition', () => {
|
|
133
288
|
test('applies a guarded status change and appends an event', async () => {
|
|
134
289
|
const record = await Transfer.create(db, createInput())
|