tapimo 0.10.1 → 0.11.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 +5249 -3572
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.d.ts +1 -76
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +2 -144
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +74 -74
- package/dist/admin/apps/earn-vaults.d.ts +1 -1
- package/dist/admin/apps/organizations.d.ts +3 -3
- package/dist/admin/apps/verified-tokens.d.ts +74 -74
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +206 -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/data/App.d.ts +3618 -2954
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +7 -2
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +1 -1
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +55 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +11 -11
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +2 -6
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +5 -5
- package/dist/apps/data/routes/earn.d.ts +4 -4
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +63 -34
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +8 -4
- 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 +26 -26
- package/dist/apps/data/routes/receipts.d.ts +240 -239
- 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/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/tokens.d.ts +4 -4
- 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/transactions.d.ts +282 -282
- package/dist/apps/data/routes/transfers.d.ts +13 -13
- 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/valuation.d.ts +21 -21
- package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +1519 -643
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +134 -740
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +3 -3
- package/dist/apps/funding/App.d.ts +233 -0
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +331 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +207 -11
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +33 -33
- package/dist/apps/management/App.d.ts +793 -16
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +50 -50
- package/dist/apps/management/routes/faucet.d.ts +2 -2
- package/dist/apps/management/routes/invite-links.d.ts +42 -42
- package/dist/apps/management/routes/members.d.ts +2 -2
- package/dist/apps/management/routes/projects.d.ts +40 -40
- package/dist/apps/management/routes/webhooks.d.ts +1031 -59
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +27 -4
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +53 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +7 -1
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +9 -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 +3 -1
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
- package/dist/db/tables/webhookQueueEvents.js +4 -0
- package/dist/db/tables/webhookQueueEvents.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +33 -69
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +63 -100
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -26
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +0 -33
- 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 +50 -116
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +60 -106
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.d.ts +3 -1
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.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/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 +1 -55
- package/dist/internal/WebhookTransfer.d.ts.map +1 -1
- package/dist/internal/WebhookTransfer.js +1 -239
- package/dist/internal/WebhookTransfer.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +85 -44
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +94 -73
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/DepositWebhook.d.ts +25 -0
- package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
- package/dist/internal/funding/DepositWebhook.js +74 -0
- package/dist/internal/funding/DepositWebhook.js.map +1 -0
- package/dist/internal/funding/Reconciliation.d.ts +6 -0
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +42 -10
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- 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/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/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.test.ts +30 -0
- package/src/App.ts +3 -0
- package/src/Client.test-d.ts +55 -2
- package/src/Webhooks.ts +2 -221
- package/src/analytics/Analytics.test.ts +34 -0
- package/src/analytics/Analytics.ts +206 -0
- package/src/analytics/tables/requestEvents.ts +6 -0
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.ts +10 -2
- package/src/apps/data/routes/activities.test.ts +107 -11
- package/src/apps/data/routes/activities.ts +64 -32
- package/src/apps/data/routes/balances.ts +2 -6
- package/src/apps/data/routes/earn.ts +64 -34
- package/src/apps/data/routes/exchanges.test.ts +8 -3
- package/src/apps/data/routes/exchanges.ts +134 -82
- package/src/apps/data/routes/receipts.ts +16 -14
- package/src/apps/data/routes/tokens.ts +3 -2
- package/src/apps/data/routes/transactions.bench.ts +8 -0
- package/src/apps/data/routes/transfers.ts +36 -10
- package/src/apps/data/routes/webhooks.test.ts +179 -1497
- package/src/apps/data/routes/webhooks.ts +172 -975
- package/src/apps/funding/routes/deposit-addresses.test.ts +216 -2
- package/src/apps/funding/routes/deposit-addresses.ts +254 -18
- package/src/apps/funding/routes/transfers.test.ts +17 -2
- package/src/apps/management/routes/webhooks.test.ts +79 -1
- package/src/apps/management/routes/webhooks.ts +40 -4
- package/src/db/Db.test.ts +1 -5
- package/src/db/Db.ts +59 -0
- package/src/db/Schema.ts +13 -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/organizations.test.ts +5 -2
- package/src/db/tables/webhookQueueEvents.ts +7 -1
- package/src/db/tables/webhookSubscriptions.ts +89 -138
- package/src/internal/Auth.test.ts +0 -48
- package/src/internal/Auth.ts +0 -59
- 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/Log.test.ts +3 -1
- package/src/internal/Log.ts +14 -0
- package/src/internal/MetricSink.test.ts +75 -358
- package/src/internal/MetricSink.ts +117 -246
- package/src/internal/Schema.test.ts +18 -0
- package/src/internal/Schema.ts +13 -2
- package/src/internal/Tidx.ts +5 -4
- package/src/internal/Viem.test.ts +4 -0
- package/src/internal/Viem.ts +6 -4
- package/src/internal/WebhookTransfer.ts +1 -351
- package/src/internal/Webhooks.test.ts +111 -139
- package/src/internal/Webhooks.ts +181 -105
- package/src/internal/funding/DepositWebhook.test.ts +131 -0
- package/src/internal/funding/DepositWebhook.ts +101 -0
- package/src/internal/funding/Reconciliation.test.ts +14 -0
- package/src/internal/funding/Reconciliation.ts +161 -86
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
- package/src/internal/webhookDestinations/slack.ts +15 -0
- package/src/Webhooks.bench.ts +0 -714
- package/src/Webhooks.memory.bench.ts +0 -250
- package/src/Webhooks.test.ts +0 -146
package/src/internal/Webhooks.ts
CHANGED
|
@@ -49,6 +49,16 @@ export type Owner =
|
|
|
49
49
|
type: 'mpp'
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** API-key scope applied when managing private-resource subscriptions. */
|
|
53
|
+
export type Access = {
|
|
54
|
+
/** API-key environment. */
|
|
55
|
+
environment: 'production' | 'sandbox'
|
|
56
|
+
/** Project attribution, when the key is project-scoped. */
|
|
57
|
+
projectId?: string | undefined
|
|
58
|
+
/** API-key scopes used to authorize private resource reads. */
|
|
59
|
+
scopes: readonly string[]
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
/**
|
|
53
63
|
* Optional human context describing what a subscription is for. Carried onto
|
|
54
64
|
* every delivered {@link Envelope} and surfaced by destinations (Slack header +
|
|
@@ -73,6 +83,8 @@ export type Subscription = {
|
|
|
73
83
|
createdAt: string
|
|
74
84
|
/** Where matched events are delivered (URL or Slack channel). */
|
|
75
85
|
destination: WebhookDestination.Destination
|
|
86
|
+
/** API-key environment captured when the subscription was created. */
|
|
87
|
+
environment?: 'production' | 'sandbox' | undefined
|
|
76
88
|
/** Event type the subscription listens to. */
|
|
77
89
|
eventType: EventType
|
|
78
90
|
/** ISO expiry timestamp; set for MPP-owned subscriptions (TTL-bounded). */
|
|
@@ -87,6 +99,8 @@ export type Subscription = {
|
|
|
87
99
|
lastDeliveryAt?: string | undefined
|
|
88
100
|
/** Authenticated owner. */
|
|
89
101
|
owner: Owner
|
|
102
|
+
/** Project attribution captured from the creating API key. */
|
|
103
|
+
projectId?: string | undefined
|
|
90
104
|
/** HMAC signing secret. Returned once at creation; never exposed by reads. */
|
|
91
105
|
secret: string
|
|
92
106
|
/** Lifecycle status. */
|
|
@@ -103,12 +117,16 @@ export type CreateInput = {
|
|
|
103
117
|
context?: Context | undefined
|
|
104
118
|
/** Where matched events are delivered. `url` destinations are SSRF-validated. */
|
|
105
119
|
destination: WebhookDestination.Destination
|
|
120
|
+
/** API-key environment used to isolate private resource events. */
|
|
121
|
+
environment?: 'production' | 'sandbox' | undefined
|
|
106
122
|
/** Event type to listen to. */
|
|
107
123
|
eventType: EventType
|
|
108
124
|
/** Event-type-specific filter predicates. */
|
|
109
125
|
filters?: Record<string, unknown> | undefined
|
|
110
126
|
/** Owner derived from the request principal. */
|
|
111
127
|
owner: Owner
|
|
128
|
+
/** Project attribution used to isolate private resource events. */
|
|
129
|
+
projectId?: string | undefined
|
|
112
130
|
/** Time-to-live in milliseconds; set the subscription to expire (MPP TTL). */
|
|
113
131
|
ttl?: number | undefined
|
|
114
132
|
}
|
|
@@ -181,7 +199,7 @@ export async function createSubscription(
|
|
|
181
199
|
const now = (options.now ?? (() => new Date()))()
|
|
182
200
|
const iso = now.toISOString()
|
|
183
201
|
// A one-field checkpoint records the live creation head without claiming it
|
|
184
|
-
// was scanned
|
|
202
|
+
// was scanned; it anchors the fanout boundary for the new subscription.
|
|
185
203
|
const pollerCursor =
|
|
186
204
|
options.startBlockNumber === undefined ? undefined : Cursor.encode([options.startBlockNumber])
|
|
187
205
|
|
|
@@ -190,11 +208,13 @@ export async function createSubscription(
|
|
|
190
208
|
...(input.context === undefined ? {} : { context: input.context }),
|
|
191
209
|
createdAt: iso,
|
|
192
210
|
destination: input.destination,
|
|
211
|
+
...(input.environment === undefined ? {} : { environment: input.environment }),
|
|
193
212
|
eventType: input.eventType,
|
|
194
213
|
failureCount: 0,
|
|
195
214
|
filters: input.filters ?? {},
|
|
196
215
|
id: generateId(now),
|
|
197
216
|
owner: input.owner,
|
|
217
|
+
...(input.projectId === undefined ? {} : { projectId: input.projectId }),
|
|
198
218
|
secret: generateSecret(),
|
|
199
219
|
status: 'active',
|
|
200
220
|
updatedAt: iso,
|
|
@@ -236,8 +256,14 @@ export async function getSubscription(
|
|
|
236
256
|
db: Db.Db,
|
|
237
257
|
owner: Owner,
|
|
238
258
|
id: string,
|
|
259
|
+
options: getSubscription.Options = {},
|
|
239
260
|
): Promise<Subscription | null> {
|
|
240
|
-
return WebhookSubscriptions.get(db, owner, id, new Date().toISOString())
|
|
261
|
+
return WebhookSubscriptions.get(db, owner, id, new Date().toISOString(), options.access)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export declare namespace getSubscription {
|
|
265
|
+
/** Private-resource visibility options. */
|
|
266
|
+
type Options = { access?: Access | undefined }
|
|
241
267
|
}
|
|
242
268
|
|
|
243
269
|
/**
|
|
@@ -255,6 +281,8 @@ export async function listSubscriptions(
|
|
|
255
281
|
export declare namespace listSubscriptions {
|
|
256
282
|
/** Options for {@link listSubscriptions}. */
|
|
257
283
|
type Options = {
|
|
284
|
+
/** Private-resource visibility scope. */
|
|
285
|
+
access?: Access | undefined
|
|
258
286
|
/** Return subscriptions older than this id (keyset paging, newest first). */
|
|
259
287
|
cursor?: string | undefined
|
|
260
288
|
/** Maximum subscriptions to return. */
|
|
@@ -269,13 +297,22 @@ export declare namespace listSubscriptions {
|
|
|
269
297
|
* since the per-owner set is bounded by `maxPerOwner`. Feeds the opt-in
|
|
270
298
|
* `meta.totalCount` on `GET /webhooks`.
|
|
271
299
|
*/
|
|
272
|
-
export async function countSubscriptions(
|
|
273
|
-
|
|
300
|
+
export async function countSubscriptions(
|
|
301
|
+
db: Db.Db,
|
|
302
|
+
owner: Owner,
|
|
303
|
+
options: countSubscriptions.Options = {},
|
|
304
|
+
): Promise<number> {
|
|
305
|
+
return WebhookSubscriptions.count(db, owner, new Date().toISOString(), options.access)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export declare namespace countSubscriptions {
|
|
309
|
+
/** Private-resource visibility options. */
|
|
310
|
+
type Options = { access?: Access | undefined }
|
|
274
311
|
}
|
|
275
312
|
|
|
276
313
|
/**
|
|
277
314
|
* Patches an owner's subscription, re-validating the URL when changed.
|
|
278
|
-
* Returns null when absent. The status column drives the
|
|
315
|
+
* Returns null when absent. The status column drives the partial active
|
|
279
316
|
* index, so no secondary-index maintenance is needed.
|
|
280
317
|
*/
|
|
281
318
|
export async function updateSubscription(
|
|
@@ -285,7 +322,12 @@ export async function updateSubscription(
|
|
|
285
322
|
patch: PatchInput,
|
|
286
323
|
options: updateSubscription.Options = {},
|
|
287
324
|
): Promise<Subscription | null> {
|
|
288
|
-
const current = await getSubscription(
|
|
325
|
+
const current = await getSubscription(
|
|
326
|
+
db,
|
|
327
|
+
owner,
|
|
328
|
+
id,
|
|
329
|
+
options.access === undefined ? {} : { access: options.access },
|
|
330
|
+
)
|
|
289
331
|
if (!current) return null
|
|
290
332
|
|
|
291
333
|
if (current.destination.type !== 'url' && patch.destination?.type === 'url')
|
|
@@ -307,43 +349,29 @@ export async function updateSubscription(
|
|
|
307
349
|
export declare namespace updateSubscription {
|
|
308
350
|
/** Options for {@link updateSubscription}. */
|
|
309
351
|
type Options = {
|
|
352
|
+
/** Private-resource visibility scope. */
|
|
353
|
+
access?: Access | undefined
|
|
310
354
|
/** Clock used for timestamps (injectable for tests). */
|
|
311
355
|
now?: (() => Date) | undefined
|
|
312
356
|
}
|
|
313
357
|
}
|
|
314
358
|
|
|
315
359
|
/** Deletes an owner's subscription; its delivery rows cascade via the FK. */
|
|
316
|
-
export async function deleteSubscription(
|
|
317
|
-
return WebhookSubscriptions.remove(db, owner, id, new Date().toISOString())
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Lists active subscriptions for a `(chainId, eventType)` stream. Used by the
|
|
322
|
-
* poller; one indexed query (the partial status index replaces the old
|
|
323
|
-
* secondary key space).
|
|
324
|
-
*/
|
|
325
|
-
export async function listActive(
|
|
360
|
+
export async function deleteSubscription(
|
|
326
361
|
db: Db.Db,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
return
|
|
362
|
+
owner: Owner,
|
|
363
|
+
id: string,
|
|
364
|
+
options: deleteSubscription.Options = {},
|
|
365
|
+
): Promise<boolean> {
|
|
366
|
+
return WebhookSubscriptions.remove(db, owner, id, new Date().toISOString(), options.access)
|
|
332
367
|
}
|
|
333
368
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
*/
|
|
338
|
-
export async function listActiveWithCursors(
|
|
339
|
-
db: Db.Db,
|
|
340
|
-
chainId: number,
|
|
341
|
-
eventType: EventType,
|
|
342
|
-
): Promise<{ cursor: string | null; subscription: Subscription }[]> {
|
|
343
|
-
return WebhookSubscriptions.listActive(db, chainId, eventType, new Date().toISOString())
|
|
369
|
+
export declare namespace deleteSubscription {
|
|
370
|
+
/** Private-resource visibility options. */
|
|
371
|
+
type Options = { access?: Access | undefined }
|
|
344
372
|
}
|
|
345
373
|
|
|
346
|
-
/** Reads every live active subscription on a chain in one
|
|
374
|
+
/** Reads every live active subscription on a chain in one query. */
|
|
347
375
|
export async function listActiveForChain(
|
|
348
376
|
db: Db.Db,
|
|
349
377
|
options: listActiveForChain.Options,
|
|
@@ -356,28 +384,16 @@ export async function listActiveForChain(
|
|
|
356
384
|
}
|
|
357
385
|
|
|
358
386
|
export declare namespace listActiveForChain {
|
|
359
|
-
/** Filters for the
|
|
387
|
+
/** Filters for the chain-level subscription read. */
|
|
360
388
|
type Options = Pick<WebhookSubscriptions.listActiveForChain.Options, 'chainId' | 'eventTypes'>
|
|
361
389
|
}
|
|
362
390
|
|
|
363
|
-
/**
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const cursors = await WebhookSubscriptions.listActiveCursors(
|
|
370
|
-
db,
|
|
371
|
-
chainId,
|
|
372
|
-
new Date().toISOString(),
|
|
373
|
-
)
|
|
374
|
-
let max: number | null = null
|
|
375
|
-
for (const cursor of cursors) {
|
|
376
|
-
const decoded = Cursor.decode(cursor, ['int', 'int']) ?? Cursor.decode(cursor, ['int'])
|
|
377
|
-
const block = decoded?.[0]
|
|
378
|
-
if (typeof block === 'number' && (max === null || block > max)) max = block
|
|
379
|
-
}
|
|
380
|
-
return max
|
|
391
|
+
/** Decodes the block component of a subscription cursor, if any. */
|
|
392
|
+
export function cursorBlock(cursor: string | null): number | undefined {
|
|
393
|
+
if (cursor === null) return undefined
|
|
394
|
+
const decoded = Cursor.decode(cursor, ['int', 'int']) ?? Cursor.decode(cursor, ['int'])
|
|
395
|
+
const block = decoded?.[0]
|
|
396
|
+
return typeof block === 'number' ? block : undefined
|
|
381
397
|
}
|
|
382
398
|
|
|
383
399
|
/** Reads a subscription's keyset cursor. Returns null when unset. */
|
|
@@ -385,41 +401,10 @@ export async function getCursor(db: Db.Db, subscriptionId: string): Promise<stri
|
|
|
385
401
|
return WebhookSubscriptions.getCursor(db, subscriptionId)
|
|
386
402
|
}
|
|
387
403
|
|
|
388
|
-
/** Writes a cursor only when its stored value matches the expected cursor. */
|
|
389
|
-
export async function compareAndSetCursor(
|
|
390
|
-
db: Db.Db,
|
|
391
|
-
options: compareAndSetCursor.Options,
|
|
392
|
-
): Promise<boolean> {
|
|
393
|
-
return WebhookSubscriptions.compareAndSetCursor(db, options)
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export declare namespace compareAndSetCursor {
|
|
397
|
-
/** Options for {@link compareAndSetCursor}. */
|
|
398
|
-
type Options = WebhookSubscriptions.compareAndSetCursor.Options
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/** Writes cursors whose stored values match their expected cursors. */
|
|
402
|
-
export async function compareAndSetCursors(
|
|
403
|
-
db: Db.Db,
|
|
404
|
-
options: compareAndSetCursors.Options,
|
|
405
|
-
): Promise<Set<string>> {
|
|
406
|
-
return WebhookSubscriptions.compareAndSetCursors(db, options)
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
export declare namespace compareAndSetCursors {
|
|
410
|
-
/** Options for {@link compareAndSetCursors}. */
|
|
411
|
-
type Options = WebhookSubscriptions.compareAndSetCursors.Options
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/** Writes a subscription's keyset cursor. */
|
|
415
|
-
export async function setCursor(db: Db.Db, subscriptionId: string, cursor: string): Promise<void> {
|
|
416
|
-
await WebhookSubscriptions.setCursor(db, subscriptionId, cursor)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
404
|
/**
|
|
420
405
|
* Deletes expired MPP-owned subscriptions (delivery rows cascade) and
|
|
421
|
-
* deliveries past retention. Reads already filter expiry at query time
|
|
422
|
-
* is the janitor reclaiming space,
|
|
406
|
+
* deliveries past retention. Reads already filter expiry at query time; this
|
|
407
|
+
* is the janitor reclaiming space, run best-effort on a schedule.
|
|
423
408
|
*/
|
|
424
409
|
export async function pruneExpired(db: Db.Db, options: pruneExpired.Options = {}): Promise<void> {
|
|
425
410
|
const iso = (options.now ?? (() => new Date()))().toISOString()
|
|
@@ -483,6 +468,10 @@ export async function ensureQueueEvents(
|
|
|
483
468
|
): Promise<ensureQueueEvents.Result> {
|
|
484
469
|
const now = (options.now ?? (() => new Date()))()
|
|
485
470
|
const expiresAt = new Date(now.getTime() + (options.ttl ?? deliveryRetentionMs)).toISOString()
|
|
471
|
+
// Carried so the delivery worker can measure observation-to-attempt without
|
|
472
|
+
// re-reading the block; replays have no observation and stay null.
|
|
473
|
+
const observedAt =
|
|
474
|
+
options.observedAt === undefined ? null : new Date(options.observedAt).toISOString()
|
|
486
475
|
const events = dispatchables.map(({ envelope, subscription }) => ({
|
|
487
476
|
createdAt: now.toISOString(),
|
|
488
477
|
envelope,
|
|
@@ -492,6 +481,7 @@ export async function ensureQueueEvents(
|
|
|
492
481
|
expiresAt,
|
|
493
482
|
// Due immediately; the value orders the sweep so it is never null here.
|
|
494
483
|
nextAttemptAt: now.toISOString(),
|
|
484
|
+
observedAt,
|
|
495
485
|
subscriptionId: subscription.id,
|
|
496
486
|
}))
|
|
497
487
|
return {
|
|
@@ -513,6 +503,8 @@ export declare namespace ensureQueueEvents {
|
|
|
513
503
|
type Options = {
|
|
514
504
|
/** Clock used for staging timestamps. */
|
|
515
505
|
now?: (() => Date) | undefined
|
|
506
|
+
/** Epoch ms the head owing these events was observed; absent on replays. */
|
|
507
|
+
observedAt?: number | undefined
|
|
516
508
|
/** Staged-envelope TTL in ms; defaults to {@link deliveryRetentionMs}. */
|
|
517
509
|
ttl?: number | undefined
|
|
518
510
|
}
|
|
@@ -645,10 +637,13 @@ export async function deliverClaimedAndRecord(
|
|
|
645
637
|
new Date().toISOString(),
|
|
646
638
|
)
|
|
647
639
|
if (!subscription || subscription.status !== 'active') return { status: 'skipped' }
|
|
640
|
+
// A staged row predating the observation column, or a replay, reads null.
|
|
641
|
+
const observedAt = claimed.observedAt ? Date.parse(claimed.observedAt) : Number.NaN
|
|
648
642
|
return {
|
|
649
643
|
result: await deliverAndRecord(db, subscription, claimed.envelope, {
|
|
650
644
|
...options,
|
|
651
645
|
dequeuedAt,
|
|
646
|
+
...(Number.isNaN(observedAt) ? {} : { observedAt }),
|
|
652
647
|
subscriptionReadMs: Date.now() - subscriptionReadStartedAt,
|
|
653
648
|
}),
|
|
654
649
|
status: 'delivered',
|
|
@@ -660,6 +655,8 @@ export declare namespace deliverClaimedAndRecord {
|
|
|
660
655
|
type Claimed = {
|
|
661
656
|
/** Immutable event envelope to deliver. */
|
|
662
657
|
envelope: Envelope
|
|
658
|
+
/** ISO head-observation stamp carried by the staged row; null on replays. */
|
|
659
|
+
observedAt?: string | null | undefined
|
|
663
660
|
/** Subscription re-resolved for current status and secret. */
|
|
664
661
|
subscriptionId: string
|
|
665
662
|
}
|
|
@@ -707,7 +704,7 @@ export async function recordDelivery(
|
|
|
707
704
|
): Promise<void> {
|
|
708
705
|
const ttl = options.ttl ?? deliveryRetentionMs
|
|
709
706
|
// The retention deadline replaces the old store TTL: reads filter on it at
|
|
710
|
-
// query time and
|
|
707
|
+
// query time and pruning reclaims past it. Anchored to write time (not the
|
|
711
708
|
// row's `createdAt`, which tests pin) — exactly like the old `put` TTL.
|
|
712
709
|
const expiresAt = new Date(Date.now() + ttl).toISOString()
|
|
713
710
|
await WebhookDeliveries.insert(db, delivery, expiresAt)
|
|
@@ -765,7 +762,7 @@ export async function getDelivery(
|
|
|
765
762
|
|
|
766
763
|
/**
|
|
767
764
|
* Default number of consecutive delivery failures after which a subscription
|
|
768
|
-
* auto-disables (and is dropped from the
|
|
765
|
+
* auto-disables (and is dropped from the partial active index).
|
|
769
766
|
*/
|
|
770
767
|
export const maxFailures = 10
|
|
771
768
|
|
|
@@ -798,7 +795,7 @@ export declare namespace recordSuccess {
|
|
|
798
795
|
/**
|
|
799
796
|
* Records a failed delivery: atomically increments the consecutive-failure
|
|
800
797
|
* counter and, once it reaches `maxFailures`, sets `status: 'disabled'` so the
|
|
801
|
-
* subscription drops out of the
|
|
798
|
+
* subscription drops out of the partial active index (surfaced via the API).
|
|
802
799
|
* Returns the updated subscription.
|
|
803
800
|
*/
|
|
804
801
|
export async function recordFailure(
|
|
@@ -861,23 +858,32 @@ export async function deliverAndRecord(
|
|
|
861
858
|
return { error: 'delivery failed before request', ok: false }
|
|
862
859
|
}
|
|
863
860
|
})()
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
const
|
|
868
|
-
|
|
861
|
+
// Overlapped: the two writes touch different rows and the log carries the
|
|
862
|
+
// immutable pre-attempt subscription, so it never reads the state write's
|
|
863
|
+
// result. Sequentially they cost two Postgres round trips per delivery.
|
|
864
|
+
const writeStartedAt = Date.now()
|
|
869
865
|
// Append a delivery-log row. Best-effort: a logging failure must never change
|
|
870
866
|
// the delivery outcome the caller (Queue consumer / inline path) acts on.
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
867
|
+
const deliveryLog = (async () => {
|
|
868
|
+
try {
|
|
869
|
+
await recordDelivery(
|
|
870
|
+
db,
|
|
871
|
+
buildDelivery({ attempt, envelope, result, subscription }, options),
|
|
872
|
+
options.deliveryRetentionMs === undefined ? {} : { ttl: options.deliveryRetentionMs },
|
|
873
|
+
)
|
|
874
|
+
} catch {
|
|
875
|
+
// delivery logging is best-effort
|
|
876
|
+
}
|
|
877
|
+
return Date.now() - writeStartedAt
|
|
878
|
+
})()
|
|
879
|
+
const stateWrite = (async () => {
|
|
880
|
+
if (result.ok) await recordSuccess(db, subscription, options)
|
|
881
|
+
else await recordFailure(db, subscription, options)
|
|
882
|
+
return Date.now() - writeStartedAt
|
|
883
|
+
})()
|
|
884
|
+
// A state-write rejection still propagates to the caller's retry path; the
|
|
885
|
+
// log promise never rejects, so it cannot mask it or go unhandled.
|
|
886
|
+
const [stateWriteMs, deliveryLogMs] = await Promise.all([stateWrite, deliveryLog])
|
|
881
887
|
const completedAt = Date.now()
|
|
882
888
|
|
|
883
889
|
// Emit after all delivery work so the timings cover every stage. The
|
|
@@ -894,19 +900,34 @@ export async function deliverAndRecord(
|
|
|
894
900
|
subscription,
|
|
895
901
|
attempt,
|
|
896
902
|
timings: {
|
|
897
|
-
deliveryLogMs
|
|
903
|
+
deliveryLogMs,
|
|
898
904
|
envelopeToQueueMs:
|
|
899
905
|
options.queuedAt === undefined || Number.isNaN(envelopeAt)
|
|
900
906
|
? undefined
|
|
901
907
|
: Math.max(0, options.queuedAt - envelopeAt),
|
|
902
908
|
endToEndMs: eventAt === undefined ? undefined : Math.max(0, completedAt - eventAt),
|
|
909
|
+
// The attempt leaving, not bookkeeping finishing: everything after
|
|
910
|
+
// `startedAt` is either the subscriber's time or our own recording,
|
|
911
|
+
// neither of which delays the event reaching them. Gated on a request
|
|
912
|
+
// actually being made, so a failure before send (formatting, invalid
|
|
913
|
+
// destination) does not report an attempt that never left.
|
|
914
|
+
eventToAttemptMs:
|
|
915
|
+
eventAt === undefined || result.durationMs === undefined
|
|
916
|
+
? undefined
|
|
917
|
+
: Math.max(0, startedAt - eventAt),
|
|
903
918
|
eventToEnvelopeMs:
|
|
904
919
|
eventAt === undefined || Number.isNaN(envelopeAt)
|
|
905
920
|
? undefined
|
|
906
921
|
: Math.max(0, envelopeAt - eventAt),
|
|
922
|
+
observedToAttemptMs:
|
|
923
|
+
options.observedAt === undefined || result.durationMs === undefined
|
|
924
|
+
? undefined
|
|
925
|
+
: Math.max(0, startedAt - options.observedAt),
|
|
926
|
+
preflightMs: Math.max(0, startedAt - dequeuedAt),
|
|
907
927
|
processingMs: Math.max(0, completedAt - dequeuedAt),
|
|
908
928
|
queueWaitMs:
|
|
909
929
|
options.queuedAt === undefined ? undefined : Math.max(0, dequeuedAt - options.queuedAt),
|
|
930
|
+
settleMs: Math.max(0, completedAt - writeStartedAt),
|
|
910
931
|
stateWriteMs,
|
|
911
932
|
subscriptionReadMs: options.subscriptionReadMs,
|
|
912
933
|
},
|
|
@@ -953,6 +974,8 @@ export declare namespace deliverAndRecord {
|
|
|
953
974
|
metrics?: Metrics.Metrics | undefined
|
|
954
975
|
/** Clock used for timestamps; injectable for tests. */
|
|
955
976
|
now?: (() => Date) | undefined
|
|
977
|
+
/** Epoch ms the head owing this event was observed; absent on replays. */
|
|
978
|
+
observedAt?: number | undefined
|
|
956
979
|
/** Cloudflare Queue delivery attempt, starting at one; absent for inline delivery paths. */
|
|
957
980
|
queueAttempt?: number | undefined
|
|
958
981
|
/** Queue enqueue time as epoch milliseconds, used to isolate queue wait. */
|
|
@@ -1037,8 +1060,11 @@ function buildDelivery(
|
|
|
1037
1060
|
* re-deliveries are dedupable by the receiver.
|
|
1038
1061
|
*/
|
|
1039
1062
|
export function eventId(options: eventId.Options): string {
|
|
1040
|
-
|
|
1041
|
-
|
|
1063
|
+
return eventIdFromKey({
|
|
1064
|
+
chainId: options.chainId,
|
|
1065
|
+
eventType: options.eventType,
|
|
1066
|
+
key: `${options.blockNumber}:${options.logIndex}`,
|
|
1067
|
+
})
|
|
1042
1068
|
}
|
|
1043
1069
|
|
|
1044
1070
|
export declare namespace eventId {
|
|
@@ -1055,6 +1081,24 @@ export declare namespace eventId {
|
|
|
1055
1081
|
}
|
|
1056
1082
|
}
|
|
1057
1083
|
|
|
1084
|
+
/** Computes a stable event id from an event-type-specific identity key. */
|
|
1085
|
+
export function eventIdFromKey(options: eventIdFromKey.Options): string {
|
|
1086
|
+
const input = `${options.chainId}:${options.eventType}:${options.key}`
|
|
1087
|
+
return `evt_${Hash.sha256(Hex.fromString(input)).slice(2)}`
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
export declare namespace eventIdFromKey {
|
|
1091
|
+
/** Stable identity inputs for a webhook event. */
|
|
1092
|
+
type Options = {
|
|
1093
|
+
/** Chain the event belongs to. */
|
|
1094
|
+
chainId: number
|
|
1095
|
+
/** Event type. */
|
|
1096
|
+
eventType: EventType
|
|
1097
|
+
/** Stable event-type-specific identity. */
|
|
1098
|
+
key: string
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1058
1102
|
/** The JSON envelope delivered to subscribers. */
|
|
1059
1103
|
export type Envelope = {
|
|
1060
1104
|
/** Chain the event occurred on. */
|
|
@@ -1108,6 +1152,38 @@ export declare namespace buildEnvelope {
|
|
|
1108
1152
|
}
|
|
1109
1153
|
}
|
|
1110
1154
|
|
|
1155
|
+
/** Builds a delivery envelope from a stable application-event identity. */
|
|
1156
|
+
export function buildKeyedEnvelope(options: buildKeyedEnvelope.Options): Envelope {
|
|
1157
|
+
const { subscription } = options
|
|
1158
|
+
return {
|
|
1159
|
+
chainId: subscription.chainId,
|
|
1160
|
+
...(subscription.context === undefined ? {} : { context: subscription.context }),
|
|
1161
|
+
createdAt: (options.createdAt ?? new Date()).toISOString(),
|
|
1162
|
+
data: options.data,
|
|
1163
|
+
id: eventIdFromKey({
|
|
1164
|
+
chainId: subscription.chainId,
|
|
1165
|
+
eventType: subscription.eventType,
|
|
1166
|
+
key: options.key,
|
|
1167
|
+
}),
|
|
1168
|
+
subscriptionId: subscription.id,
|
|
1169
|
+
type: subscription.eventType,
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
export declare namespace buildKeyedEnvelope {
|
|
1174
|
+
/** Application-event envelope fields. */
|
|
1175
|
+
type Options = {
|
|
1176
|
+
/** Timestamp to stamp on the envelope; defaults to now. */
|
|
1177
|
+
createdAt?: Date | undefined
|
|
1178
|
+
/** Public event payload. */
|
|
1179
|
+
data: unknown
|
|
1180
|
+
/** Stable event-type-specific identity. */
|
|
1181
|
+
key: string
|
|
1182
|
+
/** Subscription the event matched. */
|
|
1183
|
+
subscription: Subscription
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1111
1187
|
/**
|
|
1112
1188
|
* Builds a synthetic `ping` envelope so an owner can test a subscription's
|
|
1113
1189
|
* endpoint (connectivity, TLS, signature verification) without waiting for a real
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as TestApp from '../../../test/App.js'
|
|
2
|
+
import * as TestFunding from '../../../test/Funding.js'
|
|
3
|
+
import * as WebhookQueueEvents from '../../db/tables/webhookQueueEvents.js'
|
|
4
|
+
import * as Webhooks from '../Webhooks.js'
|
|
5
|
+
import * as Deposit from './Deposit.js'
|
|
6
|
+
import * as FundingDepositAddress from './DepositAddress.js'
|
|
7
|
+
import * as DepositWebhook from './DepositWebhook.js'
|
|
8
|
+
|
|
9
|
+
const db = TestApp.database()
|
|
10
|
+
|
|
11
|
+
describe('stage', () => {
|
|
12
|
+
test('matches owner, environment, project, and filters with stable event ids', async () => {
|
|
13
|
+
const orgId = 'org_funding_deposit_webhook'
|
|
14
|
+
const projectId = 'prj_funding_deposit_webhook'
|
|
15
|
+
const address = await FundingDepositAddress.create(db, {
|
|
16
|
+
apiKeyId: 'key_funding_deposit_webhook',
|
|
17
|
+
deliveryStrategy: 'provider',
|
|
18
|
+
environment: 'production',
|
|
19
|
+
now: new Date('2026-08-12T00:00:00.000Z'),
|
|
20
|
+
orgId,
|
|
21
|
+
projectId,
|
|
22
|
+
providerOutputToken: TestFunding.providerOutputToken(),
|
|
23
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
24
|
+
address: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
25
|
+
recipient: `0x${'ab'.repeat(20)}`,
|
|
26
|
+
}),
|
|
27
|
+
})
|
|
28
|
+
const deposit = await Deposit.create(db, {
|
|
29
|
+
now: new Date('2026-08-12T00:01:00.000Z'),
|
|
30
|
+
providerRequestId: 'relay_deposit_webhook',
|
|
31
|
+
snapshot: TestFunding.depositSnapshot(address.id),
|
|
32
|
+
sourceTransactionHash: `0x${'aa'.repeat(32)}`,
|
|
33
|
+
sourceTransferIndex: 0,
|
|
34
|
+
})
|
|
35
|
+
const completed = await Deposit.transition(db, {
|
|
36
|
+
expectedVersion: deposit.version,
|
|
37
|
+
id: deposit.id,
|
|
38
|
+
status: 'completed',
|
|
39
|
+
})
|
|
40
|
+
if (completed.type !== 'applied') throw new Error('Expected deposit transition to apply.')
|
|
41
|
+
|
|
42
|
+
const filters = {
|
|
43
|
+
depositAddressId: address.id,
|
|
44
|
+
recipient: address.recipient.toLowerCase(),
|
|
45
|
+
status: 'completed',
|
|
46
|
+
}
|
|
47
|
+
const org = await subscription({ filters, orgId })
|
|
48
|
+
const project = await subscription({ filters, orgId, projectId })
|
|
49
|
+
await subscription({ orgId, projectId: 'prj_funding_deposit_sibling' })
|
|
50
|
+
await subscription({ environment: 'sandbox', orgId })
|
|
51
|
+
await subscription({ filters: { status: 'refunded' }, orgId })
|
|
52
|
+
|
|
53
|
+
const first = await DepositWebhook.stage(db, { records: [completed.record] })
|
|
54
|
+
const second = await DepositWebhook.stage(db, { records: [completed.record] })
|
|
55
|
+
expect({ created: first.created, repeated: second.created }).toEqual({
|
|
56
|
+
created: 2,
|
|
57
|
+
repeated: 0,
|
|
58
|
+
})
|
|
59
|
+
expect(first.references.map((reference) => reference.subscriptionId)).toEqual([
|
|
60
|
+
org.id,
|
|
61
|
+
project.id,
|
|
62
|
+
])
|
|
63
|
+
const event = await WebhookQueueEvents.get(db, first.references[0]!)
|
|
64
|
+
expect(event?.envelope).toMatchObject({
|
|
65
|
+
chainId: 4217,
|
|
66
|
+
data: { id: completed.record.id, status: 'completed' },
|
|
67
|
+
id: first.references[0]!.eventId,
|
|
68
|
+
subscriptionId: org.id,
|
|
69
|
+
type: 'funding:deposit.updated',
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('transition', () => {
|
|
75
|
+
test('commits the deposit version with its webhook obligation', async () => {
|
|
76
|
+
const orgId = 'org_funding_deposit_transition'
|
|
77
|
+
const address = await FundingDepositAddress.create(db, {
|
|
78
|
+
apiKeyId: 'key_funding_deposit_transition',
|
|
79
|
+
deliveryStrategy: 'provider',
|
|
80
|
+
environment: 'production',
|
|
81
|
+
now: new Date('2026-08-12T01:00:00.000Z'),
|
|
82
|
+
orgId,
|
|
83
|
+
providerOutputToken: TestFunding.providerOutputToken(),
|
|
84
|
+
snapshot: TestFunding.depositAddressSnapshot(),
|
|
85
|
+
})
|
|
86
|
+
const deposit = await Deposit.create(db, {
|
|
87
|
+
now: new Date('2026-08-12T01:01:00.000Z'),
|
|
88
|
+
providerRequestId: 'relay_deposit_transition',
|
|
89
|
+
snapshot: TestFunding.depositSnapshot(address.id),
|
|
90
|
+
sourceTransactionHash: `0x${'bb'.repeat(32)}`,
|
|
91
|
+
sourceTransferIndex: 0,
|
|
92
|
+
})
|
|
93
|
+
await subscription({ filters: {}, orgId })
|
|
94
|
+
|
|
95
|
+
const transitioned = await DepositWebhook.transition(db, {
|
|
96
|
+
expectedVersion: deposit.version,
|
|
97
|
+
id: deposit.id,
|
|
98
|
+
status: 'bridging',
|
|
99
|
+
})
|
|
100
|
+
if (transitioned.result.type !== 'applied') throw new Error('Expected applied transition.')
|
|
101
|
+
|
|
102
|
+
expect(transitioned.references).toHaveLength(1)
|
|
103
|
+
expect(await WebhookQueueEvents.get(db, transitioned.references[0]!)).toMatchObject({
|
|
104
|
+
envelope: {
|
|
105
|
+
data: { id: deposit.id, status: 'bridging' },
|
|
106
|
+
id: transitioned.references[0]!.eventId,
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
function subscription(options: subscription.Options) {
|
|
113
|
+
return Webhooks.createSubscription(db, {
|
|
114
|
+
chainId: 4217,
|
|
115
|
+
destination: { type: 'url', url: 'https://example.com/webhooks/tempo' },
|
|
116
|
+
environment: options.environment ?? 'production',
|
|
117
|
+
eventType: 'funding:deposit.updated',
|
|
118
|
+
filters: options.filters ?? { recipient: `0x${'ab'.repeat(20)}`, status: 'completed' },
|
|
119
|
+
owner: { orgId: options.orgId, type: 'api_key' },
|
|
120
|
+
...(options.projectId === undefined ? {} : { projectId: options.projectId }),
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
declare namespace subscription {
|
|
125
|
+
type Options = {
|
|
126
|
+
environment?: 'production' | 'sandbox' | undefined
|
|
127
|
+
filters?: Record<string, unknown> | undefined
|
|
128
|
+
orgId: string
|
|
129
|
+
projectId?: string | undefined
|
|
130
|
+
}
|
|
131
|
+
}
|