tapimo 0.10.0 → 0.10.2
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/ApiKey.d.ts +2 -1
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKeys.d.ts +4 -2
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +26 -1
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +6007 -5003
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +1 -0
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.d.ts +1 -1
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +6 -6
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +91 -84
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +22 -13
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +6 -0
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +4 -2
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- 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/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +232 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +4 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +2 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/Zones.d.ts +4266 -82
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +27 -8
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +3467 -2696
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +2 -0
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +2999 -100
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +340 -2
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +514 -0
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +26 -26
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +7 -3
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +4 -4
- package/dist/apps/data/routes/earn.d.ts +75 -75
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +65 -36
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +124 -120
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +115 -77
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +24 -24
- package/dist/apps/data/routes/indexer.d.ts +2 -1
- package/dist/apps/data/routes/indexer.d.ts.map +1 -1
- package/dist/apps/data/routes/indexer.js +3 -2
- package/dist/apps/data/routes/indexer.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +4 -3
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +15 -14
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +5 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/tokens.d.ts +1 -1
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +4 -3
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +25 -7
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
- package/dist/apps/data/routes/webhooks.d.ts +71 -1
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +177 -23
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.js +1 -1
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/App.d.ts +322 -89
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +375 -45
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +208 -12
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +32 -32
- package/dist/apps/funding/routes/transfers.js +1 -1
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +418 -418
- package/dist/apps/management/routes/api-keys.d.ts +50 -50
- package/dist/apps/management/routes/billing.d.ts +72 -72
- package/dist/apps/management/routes/billing.js +1 -1
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +8 -8
- package/dist/apps/management/routes/faucet.js +2 -2
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +42 -42
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +14 -14
- package/dist/apps/management/routes/projects.d.ts +40 -40
- package/dist/apps/management/routes/usage.d.ts +8 -8
- package/dist/apps/management/routes/webhooks.d.ts +49 -49
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +4 -1
- package/dist/apps/mcp.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +90 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +19 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +24 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +20 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +17 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +41 -0
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +69 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/db/tables/webhookQueueEvents.d.ts +122 -0
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -0
- package/dist/db/tables/webhookQueueEvents.js +160 -0
- package/dist/db/tables/webhookQueueEvents.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +2 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +10 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +3 -26
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +6 -37
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Credential.d.ts +27 -0
- package/dist/internal/Credential.d.ts.map +1 -0
- package/dist/internal/Credential.js +34 -0
- package/dist/internal/Credential.js.map +1 -0
- package/dist/internal/EarnRates.js +11 -7
- package/dist/internal/EarnRates.js.map +1 -1
- package/dist/internal/Log.d.ts +8 -0
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +51 -0
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +54 -2
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +4 -6
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +15 -14
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Store.d.ts +2 -1
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +57 -18
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Tidx.d.ts +4 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +2 -2
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Value.d.ts +2 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +25 -0
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/Viem.d.ts +4 -3
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +3 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +20 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -1
- package/dist/internal/WebhookTransfer.js +7 -4
- package/dist/internal/WebhookTransfer.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +141 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +139 -10
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +3 -4
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +59 -8
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/index.js.map +1 -1
- package/package.json +3 -3
- package/src/ApiKeys.test.ts +20 -0
- package/src/ApiKeys.ts +28 -1
- package/src/App.test.ts +40 -8
- package/src/App.ts +1 -0
- package/src/Client.test-d.ts +34 -1
- package/src/Webhooks.ts +20 -18
- package/src/admin/apps/api-keys.test.ts +9 -0
- package/src/admin/apps/api-keys.ts +15 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -2
- package/src/admin/ui/src/routes/api-keys.tsx +31 -9
- package/src/admin/ui.generated.ts +69 -69
- package/src/analytics/Analytics.test.ts +42 -0
- package/src/analytics/Analytics.ts +232 -0
- package/src/analytics/tables/requestEvents.ts +6 -0
- package/src/apps/Zones.test.ts +20 -3
- package/src/apps/Zones.ts +28 -8
- package/src/apps/composition.test.ts +2 -0
- package/src/apps/data/App.test.ts +2 -2
- package/src/apps/data/App.ts +2 -0
- package/src/apps/data/routes/activities.bench.ts +41 -0
- package/src/apps/data/routes/activities.test.ts +103 -2
- package/src/apps/data/routes/activities.ts +401 -2
- package/src/apps/data/routes/balances.bench.ts +21 -0
- package/src/apps/data/routes/balances.ts +7 -3
- package/src/apps/data/routes/blocks.bench.ts +34 -0
- package/src/apps/data/routes/coingecko.bench.ts +53 -0
- package/src/apps/data/routes/earn.bench.ts +71 -0
- package/src/apps/data/routes/earn.ts +66 -36
- package/src/apps/data/routes/exchanges.bench.ts +85 -0
- package/src/apps/data/routes/exchanges.test.ts +8 -3
- package/src/apps/data/routes/exchanges.ts +134 -82
- package/src/apps/data/routes/fee-amm.bench.ts +13 -0
- package/src/apps/data/routes/indexer.bench.ts +8 -0
- package/src/apps/data/routes/indexer.test.ts +2 -1
- package/src/apps/data/routes/indexer.ts +3 -2
- package/src/apps/data/routes/receipts.bench.ts +39 -0
- package/src/apps/data/routes/receipts.ts +16 -14
- package/src/apps/data/routes/rpc.bench.ts +14 -0
- package/src/apps/data/routes/rpc.test.ts +27 -0
- package/src/apps/data/routes/rpc.ts +5 -1
- package/src/apps/data/routes/tokenlist.bench.ts +3 -0
- package/src/apps/data/routes/tokens.bench.ts +76 -0
- package/src/apps/data/routes/tokens.ts +3 -2
- package/src/apps/data/routes/transactions.bench.ts +48 -0
- package/src/apps/data/routes/transfers.bench.ts +28 -0
- package/src/apps/data/routes/transfers.ts +36 -10
- package/src/apps/data/routes/valuation.bench.ts +14 -0
- package/src/apps/data/routes/verified-tokens.bench.ts +8 -0
- package/src/apps/data/routes/webhooks.bench.ts +26 -0
- package/src/apps/data/routes/webhooks.scan.test.ts +326 -0
- package/src/apps/data/routes/webhooks.test.ts +53 -6
- package/src/apps/data/routes/webhooks.ts +252 -24
- package/src/apps/data/routes/zones.bench.ts +32 -0
- package/src/apps/data/routes/zones.test.ts +1 -1
- package/src/apps/data/routes/zones.ts +1 -1
- package/src/apps/funding/routes/chains.test.ts +1 -1
- package/src/apps/funding/routes/deposit-addresses.test.ts +217 -3
- package/src/apps/funding/routes/deposit-addresses.ts +255 -19
- package/src/apps/funding/routes/providers.test.ts +1 -1
- package/src/apps/funding/routes/quotes.test.ts +1 -1
- package/src/apps/funding/routes/transfers.test.ts +20 -8
- package/src/apps/funding/routes/transfers.ts +1 -1
- package/src/apps/management/routes/billing.ts +1 -1
- package/src/apps/management/routes/faucet.ts +2 -2
- package/src/apps/management/routes/me.test.ts +31 -0
- package/src/apps/mcp.ts +4 -1
- package/src/db/Db.ts +117 -0
- package/src/db/Schema.ts +32 -0
- package/src/db/tables/fundingDepositAddresses.test.ts +28 -0
- package/src/db/tables/fundingDepositAddresses.ts +39 -0
- package/src/db/tables/fundingDeposits.test.ts +82 -0
- package/src/db/tables/fundingDeposits.ts +130 -0
- package/src/db/tables/webhookQueueEvents.ts +285 -0
- package/src/db/tables/webhookSubscriptions.ts +15 -0
- package/src/internal/Auth.test.ts +61 -98
- package/src/internal/Auth.ts +9 -63
- package/src/internal/Credential.test.ts +51 -0
- package/src/internal/Credential.ts +58 -0
- package/src/internal/EarnRates.ts +13 -7
- package/src/internal/EarnVaults.test.ts +2 -2
- package/src/internal/Log.test.ts +3 -1
- package/src/internal/Log.ts +14 -0
- package/src/internal/MetricSink.test.ts +108 -2
- package/src/internal/MetricSink.ts +112 -2
- package/src/internal/RateLimit.test.ts +23 -18
- package/src/internal/RateLimit.ts +23 -15
- package/src/internal/Store.test.ts +99 -13
- package/src/internal/Store.ts +52 -17
- package/src/internal/Tidx.test.ts +4 -4
- package/src/internal/Tidx.ts +5 -4
- package/src/internal/Value.test.ts +25 -0
- package/src/internal/Value.ts +29 -0
- package/src/internal/Viem.test.ts +4 -0
- package/src/internal/Viem.ts +6 -4
- package/src/internal/WebhookTransfer.test.ts +89 -0
- package/src/internal/WebhookTransfer.ts +11 -7
- package/src/internal/Webhooks.test.ts +488 -0
- package/src/internal/Webhooks.ts +291 -10
- package/src/internal/funding/Provider.test.ts +1 -1
- package/src/internal/funding/providers/index.test.ts +73 -3
- package/src/internal/funding/providers/relay.test.ts +1 -5
- package/src/internal/funding/providers/relay.ts +56 -12
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
package/src/internal/Webhooks.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Hash, Hex } from 'ox'
|
|
|
3
3
|
import type * as app_Webhooks from '../apps/data/routes/webhooks.js'
|
|
4
4
|
import type * as Db from '../db/Db.js'
|
|
5
5
|
import * as WebhookDeliveries from '../db/tables/webhookDeliveries.js'
|
|
6
|
+
import * as WebhookQueueEvents from '../db/tables/webhookQueueEvents.js'
|
|
6
7
|
import * as WebhookSubscriptions from '../db/tables/webhookSubscriptions.js'
|
|
7
8
|
import type * as Metrics from '../Metrics.js'
|
|
8
9
|
import * as Cursor from './Cursor.js'
|
|
@@ -372,13 +373,20 @@ export async function maxCursorBlock(db: Db.Db, chainId: number): Promise<number
|
|
|
372
373
|
)
|
|
373
374
|
let max: number | null = null
|
|
374
375
|
for (const cursor of cursors) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
if (typeof block === 'number' && (max === null || block > max)) max = block
|
|
376
|
+
const block = cursorBlock(cursor)
|
|
377
|
+
if (block !== undefined && (max === null || block > max)) max = block
|
|
378
378
|
}
|
|
379
379
|
return max
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
/** Decodes the block component of a subscription cursor, if any. */
|
|
383
|
+
export function cursorBlock(cursor: string | null): number | undefined {
|
|
384
|
+
if (cursor === null) return undefined
|
|
385
|
+
const decoded = Cursor.decode(cursor, ['int', 'int']) ?? Cursor.decode(cursor, ['int'])
|
|
386
|
+
const block = decoded?.[0]
|
|
387
|
+
return typeof block === 'number' ? block : undefined
|
|
388
|
+
}
|
|
389
|
+
|
|
382
390
|
/** Reads a subscription's keyset cursor. Returns null when unset. */
|
|
383
391
|
export async function getCursor(db: Db.Db, subscriptionId: string): Promise<string | null> {
|
|
384
392
|
return WebhookSubscriptions.getCursor(db, subscriptionId)
|
|
@@ -423,6 +431,7 @@ export async function setCursor(db: Db.Db, subscriptionId: string, cursor: strin
|
|
|
423
431
|
export async function pruneExpired(db: Db.Db, options: pruneExpired.Options = {}): Promise<void> {
|
|
424
432
|
const iso = (options.now ?? (() => new Date()))().toISOString()
|
|
425
433
|
await WebhookDeliveries.pruneExpired(db, iso)
|
|
434
|
+
await WebhookQueueEvents.pruneExpired(db, iso)
|
|
426
435
|
await WebhookSubscriptions.pruneExpired(db, iso)
|
|
427
436
|
}
|
|
428
437
|
|
|
@@ -440,6 +449,263 @@ export declare namespace pruneExpired {
|
|
|
440
449
|
*/
|
|
441
450
|
export const deliveryRetentionMs = 7 * 24 * 60 * 60 * 1_000
|
|
442
451
|
|
|
452
|
+
/** A compact Cloudflare Queue message referencing a durable staged envelope. */
|
|
453
|
+
export type QueueReference = {
|
|
454
|
+
/** Idempotent event id of the staged envelope. */
|
|
455
|
+
eventId: string
|
|
456
|
+
/** Subscription that owns the staged envelope. */
|
|
457
|
+
subscriptionId: string
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** A full envelope staged outside Cloudflare Queues. */
|
|
461
|
+
export type QueueEvent = QueueReference & {
|
|
462
|
+
/** ISO timestamp when the envelope was staged. */
|
|
463
|
+
createdAt: string
|
|
464
|
+
/** Full envelope loaded by the Queue consumer. */
|
|
465
|
+
envelope: Envelope
|
|
466
|
+
/** ISO timestamp after which an unconsumed envelope may be pruned. */
|
|
467
|
+
expiresAt: string
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** A claim newer than this counts as an in-flight attempt; above the 10s destination timeout default. */
|
|
471
|
+
export const deliveryAttemptStaleMs = 60_000
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Retry backoff by attempt count: 5s, 25s, ~2m, ~10m, ~52m, then hourly.
|
|
475
|
+
* Endpoint outages retry on this schedule until delivery or auto-disable.
|
|
476
|
+
*/
|
|
477
|
+
export function retryDelayMs(attemptCount: number): number {
|
|
478
|
+
return Math.min(5_000 * 5 ** Math.max(0, attemptCount - 1), 3_600_000)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Stages full envelopes as delivery obligations and returns compact
|
|
483
|
+
* references safe for Queue admission. Existing rows are left untouched, so
|
|
484
|
+
* the first staged envelope wins and terminal outcomes never resurrect.
|
|
485
|
+
*/
|
|
486
|
+
export async function ensureQueueEvents(
|
|
487
|
+
db: Db.Db,
|
|
488
|
+
dispatchables: readonly ensureQueueEvents.Dispatchable[],
|
|
489
|
+
options: ensureQueueEvents.Options = {},
|
|
490
|
+
): Promise<ensureQueueEvents.Result> {
|
|
491
|
+
const now = (options.now ?? (() => new Date()))()
|
|
492
|
+
const expiresAt = new Date(now.getTime() + (options.ttl ?? deliveryRetentionMs)).toISOString()
|
|
493
|
+
const events = dispatchables.map(({ envelope, subscription }) => ({
|
|
494
|
+
createdAt: now.toISOString(),
|
|
495
|
+
envelope,
|
|
496
|
+
eventId: envelope.id,
|
|
497
|
+
// Inert while pending; the meaningful dedupe deadline is stamped at
|
|
498
|
+
// terminal transitions.
|
|
499
|
+
expiresAt,
|
|
500
|
+
// Due immediately; the value orders the sweep so it is never null here.
|
|
501
|
+
nextAttemptAt: now.toISOString(),
|
|
502
|
+
subscriptionId: subscription.id,
|
|
503
|
+
}))
|
|
504
|
+
return {
|
|
505
|
+
created: await WebhookQueueEvents.ensure(db, events),
|
|
506
|
+
references: events.map(({ eventId, subscriptionId }) => ({ eventId, subscriptionId })),
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export declare namespace ensureQueueEvents {
|
|
511
|
+
/** One full envelope and the subscription that produced it. */
|
|
512
|
+
type Dispatchable = {
|
|
513
|
+
/** Webhook envelope. */
|
|
514
|
+
envelope: Envelope
|
|
515
|
+
/** Subscription that owns the envelope. */
|
|
516
|
+
subscription: Subscription
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** Staged-envelope retention options. */
|
|
520
|
+
type Options = {
|
|
521
|
+
/** Clock used for staging timestamps. */
|
|
522
|
+
now?: (() => Date) | undefined
|
|
523
|
+
/** Staged-envelope TTL in ms; defaults to {@link deliveryRetentionMs}. */
|
|
524
|
+
ttl?: number | undefined
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Created-row count plus references for every requested obligation. */
|
|
528
|
+
type Result = {
|
|
529
|
+
/** Newly staged obligations. */
|
|
530
|
+
created: number
|
|
531
|
+
/** Compact references for all inputs, including pre-existing rows. */
|
|
532
|
+
references: QueueReference[]
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** Claims one due pending obligation; misses classify why the claim is unavailable. */
|
|
537
|
+
export function claimQueueEvent(
|
|
538
|
+
db: Db.Db,
|
|
539
|
+
reference: QueueReference,
|
|
540
|
+
): Promise<WebhookQueueEvents.claim.Result> {
|
|
541
|
+
const now = new Date()
|
|
542
|
+
return WebhookQueueEvents.claim(db, reference, {
|
|
543
|
+
now: now.toISOString(),
|
|
544
|
+
staleBefore: new Date(now.getTime() - deliveryAttemptStaleMs).toISOString(),
|
|
545
|
+
})
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Marks a claimed obligation terminal and restarts its retention window, so
|
|
550
|
+
* the dedupe row outlives replays. Fenced by the claim marker: a stale
|
|
551
|
+
* claimant's completion returns false instead of clobbering a newer claim.
|
|
552
|
+
*/
|
|
553
|
+
export function completeQueueEvent(
|
|
554
|
+
db: Db.Db,
|
|
555
|
+
reference: QueueReference,
|
|
556
|
+
options: completeQueueEvent.Options,
|
|
557
|
+
): Promise<boolean> {
|
|
558
|
+
return WebhookQueueEvents.complete(db, reference, {
|
|
559
|
+
...options,
|
|
560
|
+
expiresAt: new Date(Date.now() + deliveryRetentionMs).toISOString(),
|
|
561
|
+
})
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export declare namespace completeQueueEvent {
|
|
565
|
+
/** Fenced terminal transition. */
|
|
566
|
+
type Options = {
|
|
567
|
+
/** Claim marker from the claimed record's `attemptingAt`. */
|
|
568
|
+
claimedAt: string
|
|
569
|
+
/** Terminal obligation outcome. */
|
|
570
|
+
status: WebhookQueueEvents.TerminalStatus
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Releases a claim and schedules the next attempt on the retry backoff.
|
|
576
|
+
* Fenced by the claim marker like {@link completeQueueEvent}.
|
|
577
|
+
*/
|
|
578
|
+
export function scheduleQueueEventRetry(
|
|
579
|
+
db: Db.Db,
|
|
580
|
+
reference: QueueReference,
|
|
581
|
+
options: scheduleQueueEventRetry.Options,
|
|
582
|
+
): Promise<boolean> {
|
|
583
|
+
return WebhookQueueEvents.schedule(db, reference, {
|
|
584
|
+
claimedAt: options.claimedAt,
|
|
585
|
+
nextAttemptAt: new Date(Date.now() + retryDelayMs(options.attemptCount)).toISOString(),
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export declare namespace scheduleQueueEventRetry {
|
|
590
|
+
/** Fenced retry scheduling. */
|
|
591
|
+
type Options = {
|
|
592
|
+
/** Attempts claimed so far; drives the backoff position. */
|
|
593
|
+
attemptCount: number
|
|
594
|
+
/** Claim marker from the claimed record's `attemptingAt`. */
|
|
595
|
+
claimedAt: string
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Lists due pending references for the sweeper, oldest first. */
|
|
600
|
+
export function dueQueueEvents(db: Db.Db, limit: number): Promise<QueueReference[]> {
|
|
601
|
+
const now = new Date()
|
|
602
|
+
return WebhookQueueEvents.due(db, {
|
|
603
|
+
limit,
|
|
604
|
+
now: now.toISOString(),
|
|
605
|
+
staleBefore: new Date(now.getTime() - deliveryAttemptStaleMs).toISOString(),
|
|
606
|
+
})
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** Returns the authoritative pending-obligation snapshot. */
|
|
610
|
+
export function pendingQueueEvents(db: Db.Db): Promise<WebhookQueueEvents.pending.Result> {
|
|
611
|
+
const now = new Date()
|
|
612
|
+
return WebhookQueueEvents.pending(db, {
|
|
613
|
+
now: now.toISOString(),
|
|
614
|
+
staleBefore: new Date(now.getTime() - deliveryAttemptStaleMs).toISOString(),
|
|
615
|
+
})
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** Reads a full staged envelope for a compact Queue reference. */
|
|
619
|
+
export async function getQueueEvent(
|
|
620
|
+
db: Db.Db,
|
|
621
|
+
reference: QueueReference,
|
|
622
|
+
): Promise<QueueEvent | null> {
|
|
623
|
+
return WebhookQueueEvents.get(db, reference)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** Loads a staged envelope and delivers it with the subscription's current configuration. */
|
|
627
|
+
export async function deliverQueueEventAndRecord(
|
|
628
|
+
db: Db.Db,
|
|
629
|
+
reference: QueueReference,
|
|
630
|
+
options: deliverAndRecord.Options = {},
|
|
631
|
+
): Promise<deliverQueueEventAndRecord.Result> {
|
|
632
|
+
const dequeuedAt = Date.now()
|
|
633
|
+
const event = await getQueueEvent(db, reference)
|
|
634
|
+
if (!event) return { status: 'missing' }
|
|
635
|
+
return deliverClaimedAndRecord(db, event, { ...options, dequeuedAt })
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Delivers an already-loaded obligation envelope with the subscription's
|
|
640
|
+
* current configuration, skipping the staged-row read a claim already paid.
|
|
641
|
+
*/
|
|
642
|
+
export async function deliverClaimedAndRecord(
|
|
643
|
+
db: Db.Db,
|
|
644
|
+
claimed: deliverClaimedAndRecord.Claimed,
|
|
645
|
+
options: deliverClaimedAndRecord.Options = {},
|
|
646
|
+
): Promise<deliverClaimedAndRecord.Result> {
|
|
647
|
+
const dequeuedAt = options.dequeuedAt ?? Date.now()
|
|
648
|
+
const subscriptionReadStartedAt = Date.now()
|
|
649
|
+
const subscription = await WebhookSubscriptions.getById(
|
|
650
|
+
db,
|
|
651
|
+
claimed.subscriptionId,
|
|
652
|
+
new Date().toISOString(),
|
|
653
|
+
)
|
|
654
|
+
if (!subscription || subscription.status !== 'active') return { status: 'skipped' }
|
|
655
|
+
return {
|
|
656
|
+
result: await deliverAndRecord(db, subscription, claimed.envelope, {
|
|
657
|
+
...options,
|
|
658
|
+
dequeuedAt,
|
|
659
|
+
subscriptionReadMs: Date.now() - subscriptionReadStartedAt,
|
|
660
|
+
}),
|
|
661
|
+
status: 'delivered',
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export declare namespace deliverClaimedAndRecord {
|
|
666
|
+
/** Envelope and owning subscription id from a claimed obligation. */
|
|
667
|
+
type Claimed = {
|
|
668
|
+
/** Immutable event envelope to deliver. */
|
|
669
|
+
envelope: Envelope
|
|
670
|
+
/** Subscription re-resolved for current status and secret. */
|
|
671
|
+
subscriptionId: string
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** Delivery options plus the dequeue timestamp of the claiming path. */
|
|
675
|
+
type Options = deliverAndRecord.Options & {
|
|
676
|
+
/** Epoch ms the claiming message was dequeued; defaults to now. */
|
|
677
|
+
dequeuedAt?: number | undefined
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** A delivered event or an inactive subscription. */
|
|
681
|
+
type Result =
|
|
682
|
+
| {
|
|
683
|
+
/** Transport result from the current subscription state. */
|
|
684
|
+
result: WebhookDestination.Result
|
|
685
|
+
/** The envelope was delivered. */
|
|
686
|
+
status: 'delivered'
|
|
687
|
+
}
|
|
688
|
+
| {
|
|
689
|
+
/** The current subscription is inactive or deleted. */
|
|
690
|
+
status: 'skipped'
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export declare namespace deliverQueueEventAndRecord {
|
|
695
|
+
/** A delivered event, inactive subscription, or stale duplicate reference. */
|
|
696
|
+
type Result =
|
|
697
|
+
| {
|
|
698
|
+
/** Transport result from the current subscription state. */
|
|
699
|
+
result: WebhookDestination.Result
|
|
700
|
+
/** The staged event was delivered. */
|
|
701
|
+
status: 'delivered'
|
|
702
|
+
}
|
|
703
|
+
| {
|
|
704
|
+
/** The staged event or current subscription was not deliverable. */
|
|
705
|
+
status: 'missing' | 'skipped'
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
443
709
|
/** Records one delivery-log row (stateful, owner-agnostic; scoped per subscription). */
|
|
444
710
|
export async function recordDelivery(
|
|
445
711
|
db: Db.Db,
|
|
@@ -587,13 +853,21 @@ export async function deliverAndRecord(
|
|
|
587
853
|
): Promise<WebhookDestination.Result> {
|
|
588
854
|
const startedAt = Date.now()
|
|
589
855
|
// Snapshot the attempt before recordSuccess/recordFailure mutate the counter.
|
|
590
|
-
const attempt = subscription.failureCount + 1
|
|
591
|
-
const result = await
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
856
|
+
const attempt = options.attempt ?? subscription.failureCount + 1
|
|
857
|
+
const result = await (async () => {
|
|
858
|
+
try {
|
|
859
|
+
return await WebhookDestination.from(subscription.destination).deliver({
|
|
860
|
+
envelope,
|
|
861
|
+
secret: subscription.secret,
|
|
862
|
+
...(options.fetch === undefined ? {} : { fetch: options.fetch }),
|
|
863
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
864
|
+
})
|
|
865
|
+
} catch {
|
|
866
|
+
// A formatting failure is a failed attempt, not an exception that bypasses
|
|
867
|
+
// subscription failure state and delivery metrics on every queue retry.
|
|
868
|
+
return { error: 'delivery failed before request', ok: false }
|
|
869
|
+
}
|
|
870
|
+
})()
|
|
597
871
|
const stateWriteStartedAt = Date.now()
|
|
598
872
|
if (result.ok) await recordSuccess(db, subscription, options)
|
|
599
873
|
else await recordFailure(db, subscription, options)
|
|
@@ -625,6 +899,7 @@ export async function deliverAndRecord(
|
|
|
625
899
|
queueAttempt: options.queueAttempt,
|
|
626
900
|
result,
|
|
627
901
|
subscription,
|
|
902
|
+
attempt,
|
|
628
903
|
timings: {
|
|
629
904
|
deliveryLogMs: completedAt - deliveryLogStartedAt,
|
|
630
905
|
envelopeToQueueMs:
|
|
@@ -667,6 +942,12 @@ function eventTimestamp(envelope: Envelope): number | undefined {
|
|
|
667
942
|
export declare namespace deliverAndRecord {
|
|
668
943
|
/** Options for {@link deliverAndRecord}. */
|
|
669
944
|
type Options = {
|
|
945
|
+
/**
|
|
946
|
+
* Delivery-log attempt number; the ledger claim count on the queue path.
|
|
947
|
+
* Defaults to the subscription failure counter, which concurrent
|
|
948
|
+
* deliveries can read stale.
|
|
949
|
+
*/
|
|
950
|
+
attempt?: number | undefined
|
|
670
951
|
/** Delivery-log row TTL in ms; defaults to {@link deliveryRetentionMs}. */
|
|
671
952
|
deliveryRetentionMs?: number | undefined
|
|
672
953
|
/** Queue dequeue time as epoch milliseconds, captured before subscription lookup. */
|
|
@@ -13,7 +13,7 @@ const providers = [
|
|
|
13
13
|
integratorId: '0xdead',
|
|
14
14
|
userAddress: '0x1111111111111111111111111111111111111111',
|
|
15
15
|
}),
|
|
16
|
-
Relay.relay({
|
|
16
|
+
Relay.relay({ userAddresses: TestFunding.relayQuoteUsers }),
|
|
17
17
|
Rhino.rhino(),
|
|
18
18
|
Squid.squid({
|
|
19
19
|
integratorId: 'squid-integrator-id',
|
|
@@ -7,13 +7,15 @@ import * as Squid from './squid.js'
|
|
|
7
7
|
import * as Symbiosis from './symbiosis.js'
|
|
8
8
|
|
|
9
9
|
const now = () => new Date('2026-01-01T00:00:00.000Z')
|
|
10
|
+
const evmUserAddress = TestFunding.relayQuoteUsers['eip155:4217']
|
|
11
|
+
const solanaUserAddress = '7ZwiMrYFNCzZ4TcPkeMx7BTZSZ9jDgGR8H8Nkazhr6L1'
|
|
10
12
|
const providers = [
|
|
11
13
|
Across.across({
|
|
12
14
|
apiKey: 'across-api-key',
|
|
13
15
|
integratorId: '0xdead',
|
|
14
16
|
userAddress: '0x1111111111111111111111111111111111111111',
|
|
15
17
|
}),
|
|
16
|
-
Relay.relay({
|
|
18
|
+
Relay.relay({ userAddresses: TestFunding.relayQuoteUsers }),
|
|
17
19
|
Rhino.rhino(),
|
|
18
20
|
Squid.squid({
|
|
19
21
|
integratorId: 'squid-integrator-id',
|
|
@@ -141,7 +143,7 @@ describe('funding providers', () => {
|
|
|
141
143
|
apiKey: 'relay-api-key',
|
|
142
144
|
fetch,
|
|
143
145
|
now,
|
|
144
|
-
|
|
146
|
+
userAddresses: TestFunding.relayQuoteUsers,
|
|
145
147
|
})
|
|
146
148
|
|
|
147
149
|
const result = await provider.getQuote(getQuoteInput(candidate), new AbortController().signal)
|
|
@@ -192,6 +194,74 @@ describe('funding providers', () => {
|
|
|
192
194
|
`)
|
|
193
195
|
})
|
|
194
196
|
|
|
197
|
+
test('requires a Relay quote wallet for the source chain', async () => {
|
|
198
|
+
const requests: RecordedRequest[] = []
|
|
199
|
+
const candidate = quoteCandidate('relay', {
|
|
200
|
+
destinationToken: 'pathUSD',
|
|
201
|
+
sourceChain: 'solana',
|
|
202
|
+
})
|
|
203
|
+
const provider = Relay.relay({
|
|
204
|
+
fetch: recordingFetch(requests, async () => jsonResponse({})),
|
|
205
|
+
now,
|
|
206
|
+
userAddresses: { 'eip155:4217': evmUserAddress },
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const result = await provider.getQuote(getQuoteInput(candidate), new AbortController().signal)
|
|
210
|
+
|
|
211
|
+
expect(requests).toHaveLength(0)
|
|
212
|
+
expect(result).toMatchObject({
|
|
213
|
+
quality: {
|
|
214
|
+
sourceDetail: 'relay:quote-v2:QUOTE_USER_NOT_CONFIGURED',
|
|
215
|
+
tier: 'unavailable',
|
|
216
|
+
},
|
|
217
|
+
status: 'unavailable',
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('uses chain-specific Relay quote wallets', async () => {
|
|
222
|
+
const requests: RecordedRequest[] = []
|
|
223
|
+
const candidate = quoteCandidate('relay', {
|
|
224
|
+
destinationToken: 'pathUSD',
|
|
225
|
+
sourceChain: 'solana',
|
|
226
|
+
})
|
|
227
|
+
const fetch = recordingFetch(requests, async () =>
|
|
228
|
+
jsonResponse({
|
|
229
|
+
details: {
|
|
230
|
+
currencyOut: { amount: '990000', minimumAmount: '980000' },
|
|
231
|
+
timeEstimate: 15,
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
)
|
|
235
|
+
const provider = Relay.relay({
|
|
236
|
+
fetch,
|
|
237
|
+
now,
|
|
238
|
+
userAddresses: {
|
|
239
|
+
'eip155:4217': evmUserAddress,
|
|
240
|
+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': solanaUserAddress,
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
const result = await provider.getQuote(getQuoteInput(candidate), new AbortController().signal)
|
|
245
|
+
|
|
246
|
+
expect(jsonRequestBody(requests[0])).toMatchObject({
|
|
247
|
+
destinationChainId: 4217,
|
|
248
|
+
originChainId: 792703809,
|
|
249
|
+
recipient: evmUserAddress,
|
|
250
|
+
user: solanaUserAddress,
|
|
251
|
+
})
|
|
252
|
+
expect(result).toMatchObject({ status: 'available' })
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test('rejects invalid chain-specific Relay quote wallets', () => {
|
|
256
|
+
expect(() =>
|
|
257
|
+
Relay.relay({
|
|
258
|
+
userAddresses: {
|
|
259
|
+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': '0x1111111111111111111111111111111111111111',
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
).toThrowErrorMatchingInlineSnapshot(`[FundingProvider.ProviderConfigurationError]`)
|
|
263
|
+
})
|
|
264
|
+
|
|
195
265
|
test('maps successful Squid quotes to available quote results', async () => {
|
|
196
266
|
const requests: RecordedRequest[] = []
|
|
197
267
|
const candidate = quoteCandidate('squid')
|
|
@@ -646,7 +716,7 @@ describe('funding providers', () => {
|
|
|
646
716
|
try {
|
|
647
717
|
const provider = Relay.relay({
|
|
648
718
|
now,
|
|
649
|
-
|
|
719
|
+
userAddresses: TestFunding.relayQuoteUsers,
|
|
650
720
|
})
|
|
651
721
|
const result = await provider.getQuote(getQuoteInput(candidate), new AbortController().signal)
|
|
652
722
|
|
|
@@ -123,7 +123,6 @@ describe.skipIf(!apiKey)('relay.prepareTransfer (live)', () => {
|
|
|
123
123
|
requestBody = JSON.parse(init.body) as Record<string, unknown>
|
|
124
124
|
return globalThis.fetch(input, init)
|
|
125
125
|
},
|
|
126
|
-
userAddress: sender,
|
|
127
126
|
})
|
|
128
127
|
result = await provider.prepareTransfer!(prepareInput(provider), new AbortController().signal)
|
|
129
128
|
})
|
|
@@ -172,7 +171,6 @@ describe.skipIf(!apiKey)('relay.createDepositAddress (live)', () => {
|
|
|
172
171
|
return globalThis.fetch(input, init)
|
|
173
172
|
},
|
|
174
173
|
subsidies: { maxAmount: '100' },
|
|
175
|
-
userAddress: sender,
|
|
176
174
|
})
|
|
177
175
|
result = await provider.createDepositAddress!(
|
|
178
176
|
{ ...depositAddressInput(provider), subsidize: true },
|
|
@@ -410,9 +408,7 @@ describe('relay.createDepositAddress', () => {
|
|
|
410
408
|
|
|
411
409
|
describe('relay.prepareTransfer', () => {
|
|
412
410
|
test('refuses methods and modes it cannot prepare', async () => {
|
|
413
|
-
const provider = Relay.relay(
|
|
414
|
-
userAddress: sender,
|
|
415
|
-
})
|
|
411
|
+
const provider = Relay.relay()
|
|
416
412
|
await expect(
|
|
417
413
|
provider.prepareTransfer!(
|
|
418
414
|
{ ...prepareInput(provider), method: 'depositAddress' },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AbiEvent, AbiFunction, Address, Hash, Hex, RpcTransport } from 'ox'
|
|
1
|
+
import { AbiEvent, AbiFunction, Address, Base58, Bytes, Hash, Hex, RpcTransport } from 'ox'
|
|
2
2
|
import { createClient, custom, fallback } from 'viem'
|
|
3
3
|
import { getBlockNumber, getLogs, getTransaction, getTransactionReceipt } from 'viem/actions'
|
|
4
4
|
import * as z from 'zod/mini'
|
|
@@ -253,11 +253,9 @@ export function parseDepositAddressRequests(value: unknown) {
|
|
|
253
253
|
|
|
254
254
|
/** Creates a Relay funding provider backed by `/quote/v2`. */
|
|
255
255
|
export function relay(options: relay.Options = {}): FundingProvider.Provider<'relay'> {
|
|
256
|
+
const userAddresses = parseUserAddresses(options.userAddresses)
|
|
256
257
|
if (
|
|
257
|
-
(
|
|
258
|
-
!Address.validate(options.userAddress, { strict: false })) ||
|
|
259
|
-
(options.recipientAddress !== undefined &&
|
|
260
|
-
!Address.validate(options.recipientAddress, { strict: false }))
|
|
258
|
+
Object.entries(userAddresses).some(([chainId, address]) => invalidUserAddress(chainId, address))
|
|
261
259
|
)
|
|
262
260
|
throw new FundingProvider.ProviderConfigurationError()
|
|
263
261
|
|
|
@@ -411,13 +409,22 @@ export function relay(options: relay.Options = {}): FundingProvider.Provider<'re
|
|
|
411
409
|
}
|
|
412
410
|
},
|
|
413
411
|
async getQuote({ candidate, route }, signal) {
|
|
414
|
-
|
|
412
|
+
const userAddress = userAddresses[route.source.chain.id]
|
|
413
|
+
if (userAddress === undefined)
|
|
415
414
|
return FundingProvider.unavailableResult({
|
|
416
415
|
detail: 'relay:quote-v2',
|
|
417
416
|
message: 'QUOTE_USER_NOT_CONFIGURED',
|
|
418
417
|
now: now(),
|
|
419
418
|
source: 'providerQuote',
|
|
420
419
|
})
|
|
420
|
+
const recipientAddress = userAddresses[route.destination.chain.id]
|
|
421
|
+
if (recipientAddress === undefined)
|
|
422
|
+
return FundingProvider.unavailableResult({
|
|
423
|
+
detail: 'relay:quote-v2',
|
|
424
|
+
message: 'QUOTE_RECIPIENT_NOT_CONFIGURED',
|
|
425
|
+
now: now(),
|
|
426
|
+
source: 'providerQuote',
|
|
427
|
+
})
|
|
421
428
|
const destinationChainId = relayChainId(route.destination.chain)
|
|
422
429
|
const sourceChainId = relayChainId(route.source.chain)
|
|
423
430
|
try {
|
|
@@ -428,9 +435,9 @@ export function relay(options: relay.Options = {}): FundingProvider.Provider<'re
|
|
|
428
435
|
destinationCurrency: candidate.destinationToken.address,
|
|
429
436
|
originChainId: sourceChainId,
|
|
430
437
|
originCurrency: candidate.sourceToken.address,
|
|
431
|
-
recipient:
|
|
438
|
+
recipient: recipientAddress,
|
|
432
439
|
tradeType: 'EXACT_INPUT',
|
|
433
|
-
user:
|
|
440
|
+
user: userAddress,
|
|
434
441
|
},
|
|
435
442
|
fetch,
|
|
436
443
|
headers: options.apiKey ? { 'x-api-key': options.apiKey } : undefined,
|
|
@@ -621,12 +628,10 @@ export declare namespace relay {
|
|
|
621
628
|
now?: (() => Date) | undefined
|
|
622
629
|
/** EVM chain evidence used to accelerate authenticated provider delivery. */
|
|
623
630
|
observation?: Observation | undefined
|
|
624
|
-
/** Optional recipient override; defaults to `userAddress`. */
|
|
625
|
-
recipientAddress?: string | undefined
|
|
626
631
|
/** Deposit subsidy policy enabled for this integration. */
|
|
627
632
|
subsidies?: Subsidy.Policy | undefined
|
|
628
|
-
/** Optional
|
|
629
|
-
|
|
633
|
+
/** Optional source and destination quote wallets keyed by CAIP-2 chain id. */
|
|
634
|
+
userAddresses?: Readonly<Partial<Record<FundingChain.Id, string>>> | string | undefined
|
|
630
635
|
/** Tempo infrastructure used to receive authenticated Relay webhooks. */
|
|
631
636
|
webhook?: Webhook | undefined
|
|
632
637
|
}
|
|
@@ -662,6 +667,45 @@ export declare namespace relay {
|
|
|
662
667
|
| 'unknown_address'
|
|
663
668
|
}
|
|
664
669
|
|
|
670
|
+
function parseUserAddresses(value: relay.Options['userAddresses']) {
|
|
671
|
+
if (!value) return {}
|
|
672
|
+
const addresses =
|
|
673
|
+
typeof value === 'string'
|
|
674
|
+
? (() => {
|
|
675
|
+
try {
|
|
676
|
+
return JSON.parse(value) as unknown
|
|
677
|
+
} catch {
|
|
678
|
+
throw new FundingProvider.ProviderConfigurationError()
|
|
679
|
+
}
|
|
680
|
+
})()
|
|
681
|
+
: value
|
|
682
|
+
if (!addresses || typeof addresses !== 'object' || Array.isArray(addresses))
|
|
683
|
+
throw new FundingProvider.ProviderConfigurationError()
|
|
684
|
+
return addresses as Readonly<Partial<Record<FundingChain.Id, string>>>
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function invalidUserAddress(chainId: string, address: unknown) {
|
|
688
|
+
if (typeof address !== 'string') return true
|
|
689
|
+
if (/^eip155:\d+$/.test(chainId)) return !Address.validate(address, { strict: false })
|
|
690
|
+
if (/^solana:[1-9A-HJ-NP-Za-km-z]+$/.test(chainId)) {
|
|
691
|
+
try {
|
|
692
|
+
return Base58.toBytes(address).length !== 32
|
|
693
|
+
} catch {
|
|
694
|
+
return true
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (!/^tron:0x[0-9a-fA-F]+$/.test(chainId)) return true
|
|
698
|
+
try {
|
|
699
|
+
const bytes = Base58.toBytes(address)
|
|
700
|
+
if (bytes.length !== 25 || bytes[0] !== 0x41) return true
|
|
701
|
+
const payload = bytes.slice(0, 21)
|
|
702
|
+
const checksum = Hash.sha256(Hash.sha256(payload), { as: 'Bytes' }).slice(0, 4)
|
|
703
|
+
return !Bytes.isEqual(bytes.slice(21), checksum)
|
|
704
|
+
} catch {
|
|
705
|
+
return true
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
665
709
|
type DepositAddressObserverOptions = relay.Observation & {
|
|
666
710
|
apiKey?: string | undefined
|
|
667
711
|
baseUrl?: string | undefined
|
package/src/internal/index.ts
CHANGED