tapimo 0.9.0 → 0.10.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 +4887 -5074
- package/dist/App.d.ts.map +1 -1
- package/dist/Webhooks.d.ts +21 -20
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +66 -23
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +168 -168
- package/dist/apps/Zones.d.ts +34 -22
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +9 -8
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +5198 -5099
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +4 -5
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +19 -4
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +270 -270
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +32 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +52 -52
- package/dist/apps/data/routes/balances.d.ts +36 -36
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +135 -81
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +125 -125
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/earn.d.ts +512 -58
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +368 -44
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +120 -120
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +114 -118
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +68 -47
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokens.d.ts +461 -459
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +59 -51
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +25 -23
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +108 -86
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +618 -105
- 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/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +2 -2
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
- package/dist/apps/data/routes/webhooks.d.ts +102 -505
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +484 -342
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +12 -12
- package/dist/apps/data/routes/zones.d.ts.map +1 -1
- package/dist/apps/data/routes/zones.js +3 -1
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/App.d.ts +96 -96
- package/dist/apps/funding/routes/deposit-addresses.d.ts +52 -52
- package/dist/apps/funding/routes/quotes.d.ts +89 -89
- package/dist/apps/funding/routes/transfers.d.ts +4 -4
- package/dist/apps/management/App.d.ts +429 -727
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +24 -24
- package/dist/apps/management/routes/billing.d.ts +72 -72
- package/dist/apps/management/routes/faucet.d.ts +6 -6
- package/dist/apps/management/routes/invitations.d.ts +134 -134
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +30 -30
- package/dist/apps/management/routes/orgs.d.ts +98 -98
- package/dist/apps/management/routes/projects.d.ts +20 -20
- package/dist/apps/management/routes/usage.d.ts +24 -24
- package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
- package/dist/apps/management/routes/webhooks.d.ts +158 -573
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/mpp/App.d.ts +56 -44
- package/dist/apps/mpp/App.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.d.ts +3 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.js +3 -0
- package/dist/apps/mpp/Mpp.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +9 -2
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/db/Db.d.ts +2 -2
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +17 -15
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +4 -1
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +56 -3
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +76 -3
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +112 -3
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +116 -3
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.js +1 -1
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Tidx.d.ts +1 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +1 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +2 -1
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts +2 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +11 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +55 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -0
- package/dist/internal/WebhookTransfer.js +296 -0
- package/dist/internal/WebhookTransfer.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +37 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +87 -13
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +4 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +11 -7
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +128 -0
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +3 -0
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +12 -1
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/Client.test-d.ts +60 -5
- package/src/Webhooks.bench.ts +714 -0
- package/src/Webhooks.memory.bench.ts +250 -0
- package/src/Webhooks.test.ts +77 -6
- package/src/Webhooks.ts +83 -29
- package/src/apps/Zones.test.ts +5 -4
- package/src/apps/Zones.ts +9 -8
- package/src/apps/data/FxOracle.test.ts +18 -0
- package/src/apps/data/FxOracle.ts +28 -5
- package/src/apps/data/routes/activities.test.ts +19 -0
- package/src/apps/data/routes/activities.ts +34 -30
- package/src/apps/data/routes/balances.test.ts +119 -10
- package/src/apps/data/routes/balances.ts +154 -95
- package/src/apps/data/routes/blocks.test.ts +12 -6
- package/src/apps/data/routes/earn.test.ts +197 -0
- package/src/apps/data/routes/earn.ts +486 -44
- package/src/apps/data/routes/exchanges.test.ts +7 -6
- package/src/apps/data/routes/exchanges.ts +124 -117
- package/src/apps/data/routes/receipts.test.ts +24 -7
- package/src/apps/data/routes/receipts.ts +67 -50
- package/src/apps/data/routes/tokens.test.ts +51 -0
- package/src/apps/data/routes/transactions.test.ts +42 -0
- package/src/apps/data/routes/transactions.ts +44 -39
- package/src/apps/data/routes/transfers.test.ts +402 -24
- package/src/apps/data/routes/transfers.ts +708 -101
- package/src/apps/data/routes/valuation.test.ts +26 -0
- package/src/apps/data/routes/valuation.ts +6 -2
- package/src/apps/data/routes/webhooks.test.ts +414 -130
- package/src/apps/data/routes/webhooks.ts +603 -419
- package/src/apps/data/routes/zones.ts +6 -1
- package/src/apps/funding/routes/quotes.test.ts +1 -1
- package/src/apps/mpp/Mpp.test.ts +13 -1
- package/src/apps/mpp/Mpp.ts +8 -0
- package/src/apps/mpp/internal/Adapter.ts +10 -2
- package/src/db/Db.test.ts +40 -6
- package/src/db/Db.ts +33 -19
- package/src/db/tables/sponsoredTransactions.ts +9 -2
- package/src/db/tables/webhookSubscriptions.ts +116 -4
- package/src/internal/EarnVaults.test.ts +2 -2
- package/src/internal/MetricSink.test.ts +518 -0
- package/src/internal/MetricSink.ts +266 -9
- package/src/internal/Schema.ts +1 -1
- package/src/internal/Tidx.test.ts +4 -4
- package/src/internal/Tidx.ts +1 -3
- package/src/internal/Viem.test.ts +8 -0
- package/src/internal/Viem.ts +2 -1
- package/src/internal/WebhookDestination.test.ts +20 -0
- package/src/internal/WebhookDestination.ts +10 -0
- package/src/internal/WebhookTransfer.ts +426 -0
- package/src/internal/Webhooks.test.ts +137 -0
- package/src/internal/Webhooks.ts +131 -17
- package/src/internal/funding/Provider.test.ts +47 -7
- package/src/internal/funding/Provider.ts +12 -7
- package/src/internal/funding/Reconciliation.local.test.ts +3 -1
- package/src/internal/funding/Reconciliation.test.ts +1240 -0
- package/src/internal/funding/Reconciliation.ts +136 -0
- package/src/internal/funding/SourceObservation.test.ts +1 -0
- package/src/internal/funding/providers/relay.test.ts +1 -4
- package/src/internal/funding/providers/relay.ts +16 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Metrics } from '../Metrics.js'
|
|
2
2
|
import type * as Webhooks from '../Webhooks.js'
|
|
3
3
|
import type * as Log from './Log.js'
|
|
4
|
+
import * as WebhookDestination from './WebhookDestination.js'
|
|
4
5
|
|
|
5
6
|
/** Webhook event type, including the synthetic `ping` carried on test envelopes. */
|
|
6
7
|
export type EventType = Webhooks.Envelope['type']
|
|
@@ -8,6 +9,12 @@ export type EventType = Webhooks.Envelope['type']
|
|
|
8
9
|
/** Where a delivery attempt originated. */
|
|
9
10
|
export type Trigger = 'manual_retry' | 'ping' | 'queue'
|
|
10
11
|
|
|
12
|
+
/** Bounded poller stage names used by `webhook:poll`. */
|
|
13
|
+
export type PollStage = 'cursor_write' | 'enrich' | 'rpc_head' | 'scan' | 'subscription_read'
|
|
14
|
+
|
|
15
|
+
/** Bounded Durable Object or transport failure classes used by `webhook:poll-failure`. */
|
|
16
|
+
export type PollFailure = 'other' | 'overloaded' | 'remote' | 'retryable'
|
|
17
|
+
|
|
11
18
|
/** Context for one poller tick that the {@link Webhooks.createPoller.Result} omits. */
|
|
12
19
|
export type TickMeta = {
|
|
13
20
|
/** Chain polled this tick; used when the tick errored before a result existed. */
|
|
@@ -20,11 +27,112 @@ export type TickMeta = {
|
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
29
|
* A webhook metric event, recorded by the {@link webhooks} sink. The poller
|
|
23
|
-
* emits
|
|
24
|
-
* `webhook:delivery`.
|
|
30
|
+
* emits head recovery, poll, tick, and enqueue measurements; the delivery path
|
|
31
|
+
* emits `webhook:delivery`.
|
|
25
32
|
*/
|
|
26
33
|
export type WebhookEvent =
|
|
27
34
|
| {
|
|
35
|
+
/** Tempo chain whose poll cycle completed. */
|
|
36
|
+
chainId: number
|
|
37
|
+
/** Whether the poll cycle completed without scan or dispatch failures. */
|
|
38
|
+
outcome: 'error' | 'ok'
|
|
39
|
+
/** Requested boundary movement since the preceding successful poll, in blocks. */
|
|
40
|
+
requestedBoundaryAdvanceBlocks?: number | undefined
|
|
41
|
+
/** Execution path that acquired this poll claim. */
|
|
42
|
+
trigger: 'alarm' | 'cron' | 'direct' | 'queue'
|
|
43
|
+
/** Identifies a transport-neutral poll-cycle measurement. */
|
|
44
|
+
type: 'webhook:poll-cycle'
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
/** Tempo chain whose durable progress was read. */
|
|
48
|
+
chainId: number
|
|
49
|
+
/** Blocks between the desired and fully drained boundaries. */
|
|
50
|
+
lagBlocks: number
|
|
51
|
+
/** Identifies durable coordinator progress. */
|
|
52
|
+
type: 'webhook:head-progress'
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
/** Tempo chain whose recovery poke was dequeued. */
|
|
56
|
+
chainId: number
|
|
57
|
+
/** When the recovery poke was dequeued, as epoch milliseconds. */
|
|
58
|
+
dequeuedAt: number
|
|
59
|
+
/** When the recovery poke was enqueued, as epoch milliseconds. */
|
|
60
|
+
enqueuedAt: number
|
|
61
|
+
/** Recovery source carried through the Queue handoff. */
|
|
62
|
+
trigger: 'alarm' | 'queue'
|
|
63
|
+
/** Identifies recovery Queue latency, not observed head volume. */
|
|
64
|
+
type: 'webhook:head-recovery'
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
/** Tempo chain whose poll claim was acquired. */
|
|
68
|
+
chainId: number
|
|
69
|
+
/** Delay from observing the desired boundary to acquiring the claim. */
|
|
70
|
+
delayMs?: number | undefined
|
|
71
|
+
/** Execution path that acquired the claim. */
|
|
72
|
+
trigger: 'alarm' | 'cron' | 'direct' | 'queue'
|
|
73
|
+
/** Identifies a poll beginning, before scan work runs. */
|
|
74
|
+
type: 'webhook:poll-start'
|
|
75
|
+
}
|
|
76
|
+
| {
|
|
77
|
+
/** Tempo chain whose poll failed. */
|
|
78
|
+
chainId: number
|
|
79
|
+
/** Bounded class of the thrown failure. */
|
|
80
|
+
failure: PollFailure
|
|
81
|
+
/** Operation boundary that rejected the poll. */
|
|
82
|
+
stage: 'claim' | 'continuation_enqueue' | 'finish' | 'scan'
|
|
83
|
+
/** Execution path that failed before recovery. */
|
|
84
|
+
trigger: 'alarm' | 'cron' | 'direct' | 'queue'
|
|
85
|
+
/** Identifies a transport-neutral poll failure. */
|
|
86
|
+
type: 'webhook:poll-failure'
|
|
87
|
+
}
|
|
88
|
+
| {
|
|
89
|
+
chainId: number
|
|
90
|
+
durationMs: number
|
|
91
|
+
eventType?: EventType | undefined
|
|
92
|
+
outcome: 'error' | 'ok'
|
|
93
|
+
stage: PollStage
|
|
94
|
+
type: 'webhook:poll'
|
|
95
|
+
}
|
|
96
|
+
| {
|
|
97
|
+
/** Tempo chain whose eligible subscription count was read. */
|
|
98
|
+
chainId: number
|
|
99
|
+
/** Active, unexpired subscriptions selected for the poll. */
|
|
100
|
+
count: number
|
|
101
|
+
/** Identifies a pre-scan subscription eligibility measurement. */
|
|
102
|
+
type: 'webhook:poll-subscriptions'
|
|
103
|
+
}
|
|
104
|
+
| {
|
|
105
|
+
/** Chain scanned by this poll batch. */
|
|
106
|
+
chainId: number
|
|
107
|
+
/** Shared upper bound for concurrent upstream scans. */
|
|
108
|
+
concurrency: number
|
|
109
|
+
/** Scan duration, present only after completion. */
|
|
110
|
+
durationMs?: number | undefined
|
|
111
|
+
/** Subscription event type scanned by this batch. */
|
|
112
|
+
eventType: EventType
|
|
113
|
+
/** Subscriptions whose scan rejected, present only after completion. */
|
|
114
|
+
failedSubscriptions?: number | undefined
|
|
115
|
+
/** Matching rows retained by completed pages, present only after completion. */
|
|
116
|
+
matchedItems?: number | undefined
|
|
117
|
+
/** Largest block window requested by one subscription. */
|
|
118
|
+
maxWindowBlocks: number
|
|
119
|
+
/** Whether the checkpoint precedes allocation or follows scan completion. */
|
|
120
|
+
phase: 'complete' | 'start'
|
|
121
|
+
/** Outcome of the completed RPC-backed scan. */
|
|
122
|
+
outcome?: 'empty' | 'error' | 'missing' | 'ok' | undefined
|
|
123
|
+
/** JSON-RPC method used by this event type's scanner. */
|
|
124
|
+
rpcMethod: 'eth_getBlockByNumber' | 'eth_getLogs'
|
|
125
|
+
/** Subscriptions retained in this scan batch. */
|
|
126
|
+
subscriptionCount: number
|
|
127
|
+
/** Maximum subscriptions retained by this event-type batch. */
|
|
128
|
+
subscriptionLimit: number
|
|
129
|
+
/** Sum of subscription block windows in this scan batch. */
|
|
130
|
+
totalWindowBlocks: number
|
|
131
|
+
/** Identifies a memory-attribution scan boundary. */
|
|
132
|
+
type: 'webhook:poll-scan'
|
|
133
|
+
}
|
|
134
|
+
| {
|
|
135
|
+
cursorLagBlocks?: number | undefined
|
|
28
136
|
meta: TickMeta
|
|
29
137
|
result: Webhooks.createPoller.Result | undefined
|
|
30
138
|
type: 'webhook:tick'
|
|
@@ -38,8 +146,20 @@ export type WebhookEvent =
|
|
|
38
146
|
}
|
|
39
147
|
| {
|
|
40
148
|
envelope: Webhooks.Envelope
|
|
149
|
+
/** Cloudflare Queue delivery attempt, absent outside the Queue path. */
|
|
150
|
+
queueAttempt?: number | undefined
|
|
41
151
|
result: Webhooks.Result
|
|
42
152
|
subscription: Webhooks.Subscription
|
|
153
|
+
timings?: {
|
|
154
|
+
deliveryLogMs?: number | undefined
|
|
155
|
+
envelopeToQueueMs?: number | undefined
|
|
156
|
+
endToEndMs?: number | undefined
|
|
157
|
+
eventToEnvelopeMs?: number | undefined
|
|
158
|
+
processingMs?: number | undefined
|
|
159
|
+
queueWaitMs?: number | undefined
|
|
160
|
+
stateWriteMs?: number | undefined
|
|
161
|
+
subscriptionReadMs?: number | undefined
|
|
162
|
+
}
|
|
43
163
|
trigger: Trigger
|
|
44
164
|
type: 'webhook:delivery'
|
|
45
165
|
}
|
|
@@ -195,7 +315,7 @@ type StatusClass = '2xx' | '4xx' | '5xx' | 'network' | 'timeout' | 'unknown' | '
|
|
|
195
315
|
/**
|
|
196
316
|
* Builds a webhook delivery-reliability sink over `backend`. Tags stay
|
|
197
317
|
* low-cardinality (`chain_id`, `event_type`, `destination_type`, `status_class`,
|
|
198
|
-
* `attempt_bucket`, `outcome`) — never subscription/org/event ids, which belong
|
|
318
|
+
* `attempt_bucket`, `failure`, `queue_attempt_bucket`, `outcome`, `stage`) — never subscription/org/event ids, which belong
|
|
199
319
|
* in logs or analytics, not StatsD-style series.
|
|
200
320
|
*/
|
|
201
321
|
export function webhooks(backend: Metrics): WebhookSink {
|
|
@@ -205,6 +325,101 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
205
325
|
},
|
|
206
326
|
record(event) {
|
|
207
327
|
switch (event.type) {
|
|
328
|
+
case 'webhook:head-recovery': {
|
|
329
|
+
const tags = { chain_id: String(event.chainId), trigger: event.trigger }
|
|
330
|
+
backend.count('webhook_head_recovery_queue_count', 1, tags)
|
|
331
|
+
backend.histogram(
|
|
332
|
+
'webhook_head_recovery_queue_wait_ms',
|
|
333
|
+
Math.max(0, event.dequeuedAt - event.enqueuedAt),
|
|
334
|
+
tags,
|
|
335
|
+
)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
case 'webhook:head-progress':
|
|
339
|
+
backend.gauge('webhook_head_progress_lag_blocks', event.lagBlocks, {
|
|
340
|
+
chain_id: String(event.chainId),
|
|
341
|
+
})
|
|
342
|
+
return
|
|
343
|
+
case 'webhook:poll-failure':
|
|
344
|
+
backend.count('webhook_poll_failure_count', 1, {
|
|
345
|
+
chain_id: String(event.chainId),
|
|
346
|
+
failure: event.failure,
|
|
347
|
+
stage: event.stage,
|
|
348
|
+
trigger: event.trigger,
|
|
349
|
+
})
|
|
350
|
+
return
|
|
351
|
+
case 'webhook:poll': {
|
|
352
|
+
const tags = {
|
|
353
|
+
chain_id: String(event.chainId),
|
|
354
|
+
...(event.eventType === undefined ? {} : { event_type: event.eventType }),
|
|
355
|
+
outcome: event.outcome,
|
|
356
|
+
stage: event.stage,
|
|
357
|
+
}
|
|
358
|
+
backend.histogram('webhook_poller_stage_duration_ms', event.durationMs, tags)
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
case 'webhook:poll-scan': {
|
|
362
|
+
const tags = {
|
|
363
|
+
chain_id: String(event.chainId),
|
|
364
|
+
event_type: event.eventType,
|
|
365
|
+
phase: event.phase,
|
|
366
|
+
}
|
|
367
|
+
backend.count('webhook_poller_scan_checkpoint_count', 1, tags)
|
|
368
|
+
backend.histogram('webhook_poller_scan_concurrency', event.concurrency, tags)
|
|
369
|
+
backend.histogram('webhook_poller_scan_subscription_count', event.subscriptionCount, tags)
|
|
370
|
+
backend.histogram('webhook_poller_scan_subscription_limit', event.subscriptionLimit, tags)
|
|
371
|
+
backend.histogram('webhook_poller_scan_max_window_blocks', event.maxWindowBlocks, tags)
|
|
372
|
+
backend.histogram(
|
|
373
|
+
'webhook_poller_scan_total_window_blocks',
|
|
374
|
+
event.totalWindowBlocks,
|
|
375
|
+
tags,
|
|
376
|
+
)
|
|
377
|
+
if (event.durationMs !== undefined)
|
|
378
|
+
backend.histogram('webhook_poller_scan_duration_ms', event.durationMs, tags)
|
|
379
|
+
if (event.failedSubscriptions !== undefined)
|
|
380
|
+
backend.count(
|
|
381
|
+
'webhook_poller_scan_failed_subscription_count',
|
|
382
|
+
event.failedSubscriptions,
|
|
383
|
+
tags,
|
|
384
|
+
)
|
|
385
|
+
if (event.matchedItems !== undefined)
|
|
386
|
+
backend.count('webhook_poller_scan_matched_item_count', event.matchedItems, tags)
|
|
387
|
+
if (event.outcome !== undefined)
|
|
388
|
+
backend.count('webhook_poller_rpc_scan_count', 1, {
|
|
389
|
+
chain_id: String(event.chainId),
|
|
390
|
+
event_type: event.eventType,
|
|
391
|
+
outcome: event.outcome,
|
|
392
|
+
rpc_method: event.rpcMethod,
|
|
393
|
+
})
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
case 'webhook:poll-subscriptions':
|
|
397
|
+
backend.gauge('webhook_poller_active_subscription_count', event.count, {
|
|
398
|
+
chain_id: String(event.chainId),
|
|
399
|
+
})
|
|
400
|
+
return
|
|
401
|
+
case 'webhook:poll-cycle': {
|
|
402
|
+
const tags = {
|
|
403
|
+
chain_id: String(event.chainId),
|
|
404
|
+
outcome: event.outcome,
|
|
405
|
+
trigger: event.trigger,
|
|
406
|
+
}
|
|
407
|
+
backend.count('webhook_poll_cycle_count', 1, tags)
|
|
408
|
+
if (event.requestedBoundaryAdvanceBlocks !== undefined)
|
|
409
|
+
backend.histogram(
|
|
410
|
+
'webhook_poll_requested_boundary_advance_blocks',
|
|
411
|
+
event.requestedBoundaryAdvanceBlocks,
|
|
412
|
+
tags,
|
|
413
|
+
)
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
case 'webhook:poll-start': {
|
|
417
|
+
const tags = { chain_id: String(event.chainId), trigger: event.trigger }
|
|
418
|
+
backend.count('webhook_poll_start_count', 1, tags)
|
|
419
|
+
if (event.delayMs !== undefined)
|
|
420
|
+
backend.histogram('webhook_poll_start_delay_ms', event.delayMs, tags)
|
|
421
|
+
return
|
|
422
|
+
}
|
|
208
423
|
case 'webhook:tick': {
|
|
209
424
|
const { meta, result } = event
|
|
210
425
|
const chain_id = String(result?.chainId ?? meta.chainId ?? 'unknown')
|
|
@@ -213,6 +428,10 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
213
428
|
chain_id,
|
|
214
429
|
outcome: meta.outcome,
|
|
215
430
|
})
|
|
431
|
+
if (event.cursorLagBlocks !== undefined)
|
|
432
|
+
backend.gauge('webhook_subscription_cursor_lag_blocks', event.cursorLagBlocks, {
|
|
433
|
+
chain_id,
|
|
434
|
+
})
|
|
216
435
|
if (!result) return
|
|
217
436
|
backend.histogram('webhook_poller_subscription_count', result.subscriptions, { chain_id })
|
|
218
437
|
backend.count('webhook_poller_scanned_count', result.scanned, { chain_id })
|
|
@@ -238,7 +457,14 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
238
457
|
return
|
|
239
458
|
}
|
|
240
459
|
case 'webhook:delivery': {
|
|
241
|
-
const { envelope, result, subscription } = event
|
|
460
|
+
const { envelope, result, subscription, timings } = event
|
|
461
|
+
const queueTags =
|
|
462
|
+
event.queueAttempt === undefined
|
|
463
|
+
? {}
|
|
464
|
+
: {
|
|
465
|
+
queue_attempt_bucket: attemptBucket(event.queueAttempt),
|
|
466
|
+
queue_disposition: WebhookDestination.isRetryable(result) ? 'retried' : 'acked',
|
|
467
|
+
}
|
|
242
468
|
const tags = {
|
|
243
469
|
chain_id: String(envelope.chainId),
|
|
244
470
|
destination_type: subscription.destination.type satisfies DestinationType,
|
|
@@ -249,6 +475,7 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
249
475
|
...tags,
|
|
250
476
|
attempt_bucket: attemptBucket(subscription.failureCount + 1),
|
|
251
477
|
outcome,
|
|
478
|
+
...queueTags,
|
|
252
479
|
status_class: statusClass(result),
|
|
253
480
|
trigger: event.trigger,
|
|
254
481
|
})
|
|
@@ -257,11 +484,41 @@ export function webhooks(backend: Metrics): WebhookSink {
|
|
|
257
484
|
...tags,
|
|
258
485
|
outcome,
|
|
259
486
|
})
|
|
260
|
-
|
|
261
|
-
'
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
487
|
+
if (timings?.deliveryLogMs !== undefined)
|
|
488
|
+
backend.histogram('webhook_delivery_log_write_ms', timings.deliveryLogMs, tags)
|
|
489
|
+
if (timings?.envelopeToQueueMs !== undefined)
|
|
490
|
+
backend.histogram(
|
|
491
|
+
'webhook_delivery_envelope_to_queue_ms',
|
|
492
|
+
timings.envelopeToQueueMs,
|
|
493
|
+
tags,
|
|
494
|
+
)
|
|
495
|
+
if (timings?.endToEndMs !== undefined)
|
|
496
|
+
backend.histogram('webhook_delivery_end_to_end_ms', timings.endToEndMs, {
|
|
497
|
+
...tags,
|
|
498
|
+
outcome,
|
|
499
|
+
})
|
|
500
|
+
if (timings?.eventToEnvelopeMs !== undefined)
|
|
501
|
+
backend.histogram(
|
|
502
|
+
'webhook_delivery_event_to_envelope_ms',
|
|
503
|
+
timings.eventToEnvelopeMs,
|
|
504
|
+
tags,
|
|
505
|
+
)
|
|
506
|
+
if (timings?.processingMs !== undefined)
|
|
507
|
+
backend.histogram('webhook_delivery_processing_ms', timings.processingMs, tags)
|
|
508
|
+
if (timings?.queueWaitMs !== undefined)
|
|
509
|
+
backend.histogram('webhook_delivery_queue_wait_ms', timings.queueWaitMs, {
|
|
510
|
+
...tags,
|
|
511
|
+
outcome,
|
|
512
|
+
...queueTags,
|
|
513
|
+
})
|
|
514
|
+
if (timings?.stateWriteMs !== undefined)
|
|
515
|
+
backend.histogram('webhook_delivery_state_write_ms', timings.stateWriteMs, tags)
|
|
516
|
+
if (timings?.subscriptionReadMs !== undefined)
|
|
517
|
+
backend.histogram(
|
|
518
|
+
'webhook_delivery_subscription_read_ms',
|
|
519
|
+
timings.subscriptionReadMs,
|
|
520
|
+
tags,
|
|
521
|
+
)
|
|
265
522
|
return
|
|
266
523
|
}
|
|
267
524
|
}
|
package/src/internal/Schema.ts
CHANGED
|
@@ -356,7 +356,7 @@ export const Denomination = z
|
|
|
356
356
|
.check(
|
|
357
357
|
z.describe(
|
|
358
358
|
'Currency to denominate values in (case-insensitive, e.g. `AUD`). ' +
|
|
359
|
-
'Must be priced by the configured FX oracle.
|
|
359
|
+
'Must be priced by the configured FX oracle.',
|
|
360
360
|
),
|
|
361
361
|
z.meta({ examples: ['AUD'] }),
|
|
362
362
|
)
|
|
@@ -330,9 +330,9 @@ describe('resolve', () => {
|
|
|
330
330
|
test('passes Zone metadata to the resolver', () => {
|
|
331
331
|
expect(
|
|
332
332
|
Tidx.resolve(({ zone }) => ({ baseUrl: zone ? 'http://tidx:8080' : undefined }), {
|
|
333
|
-
chainId: Zones.
|
|
333
|
+
chainId: Zones.zoneModerato.id,
|
|
334
334
|
principal: null,
|
|
335
|
-
zone: Zones.
|
|
335
|
+
zone: Zones.zoneModerato,
|
|
336
336
|
}),
|
|
337
337
|
).toMatchInlineSnapshot(`
|
|
338
338
|
{
|
|
@@ -469,7 +469,7 @@ describe('getClient', () => {
|
|
|
469
469
|
)
|
|
470
470
|
try {
|
|
471
471
|
const client = Tidx.getClient({
|
|
472
|
-
chainId: Zones.
|
|
472
|
+
chainId: Zones.zoneModerato.id,
|
|
473
473
|
tidx: {
|
|
474
474
|
baseUrl: 'https://internal.indexer',
|
|
475
475
|
zoneHeaders: {
|
|
@@ -477,7 +477,7 @@ describe('getClient', () => {
|
|
|
477
477
|
'CF-Access-Client-Secret': 'client-secret',
|
|
478
478
|
},
|
|
479
479
|
},
|
|
480
|
-
zone: Zones.
|
|
480
|
+
zone: Zones.zoneModerato,
|
|
481
481
|
})
|
|
482
482
|
await client.fetch({ query: 'select 1' })
|
|
483
483
|
|
package/src/internal/Tidx.ts
CHANGED
|
@@ -92,9 +92,7 @@ export function getClient(options: getClient.Options): Client {
|
|
|
92
92
|
* output (`baseUrl` + auth + `chainId`), not the principal, so a resolver
|
|
93
93
|
* `tidx` may branch on any principal field while client cardinality stays
|
|
94
94
|
* bounded by the number of distinct upstreams — callers that resolve to the same
|
|
95
|
-
* upstream share a client.
|
|
96
|
-
* `Webhooks.createPoller` (the scheduled poller) so the resolver logic lives in
|
|
97
|
-
* one place.
|
|
95
|
+
* upstream share a client.
|
|
98
96
|
*/
|
|
99
97
|
export function createGetClient(options: createGetClient.Options = {}): GetClient {
|
|
100
98
|
const { defaultChainId = Viem.defaultChainId, tidx } = options
|
|
@@ -220,6 +220,14 @@ describe('resolveRpc', () => {
|
|
|
220
220
|
})
|
|
221
221
|
|
|
222
222
|
describe('getClient', () => {
|
|
223
|
+
test('disables multicall for Zones', () => {
|
|
224
|
+
const l1Client = Viem.getClient({ chainId: 4217 })
|
|
225
|
+
const zoneClient = Viem.getClient({ chainId: zone.id, zone })
|
|
226
|
+
|
|
227
|
+
expect(l1Client.batch?.multicall).toEqual({ deployless: true })
|
|
228
|
+
expect(zoneClient.batch?.multicall).toBe(false)
|
|
229
|
+
})
|
|
230
|
+
|
|
223
231
|
test('sends inferred Bearer auth', async () => {
|
|
224
232
|
const fetch = vi
|
|
225
233
|
.spyOn(globalThis, 'fetch')
|
package/src/internal/Viem.ts
CHANGED
|
@@ -163,7 +163,8 @@ export function getClient(options: getClient.Options): getClient.ReturnType {
|
|
|
163
163
|
// (e.g. `balanceOf` across many tokens) and pay one RPC round-trip.
|
|
164
164
|
// Deployless mode bytecode-injects multicall3 so Tempo, which doesn't
|
|
165
165
|
// configure a deployed multicall3, can still serve the aggregated call.
|
|
166
|
-
|
|
166
|
+
// Zones disable multicall entirely and issue concurrent direct `eth_call`s instead.
|
|
167
|
+
batch: { multicall: zone ? false : { deployless: true } },
|
|
167
168
|
chain: { ...tempoMainnet, id: chainId },
|
|
168
169
|
transport: http(rpcUrl, fetchOptions ? { fetchOptions } : {}),
|
|
169
170
|
})
|
|
@@ -93,6 +93,26 @@ describe('from', () => {
|
|
|
93
93
|
})
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
+
describe('isRetryable', () => {
|
|
97
|
+
test.each([
|
|
98
|
+
['success', { ok: true, status: 204 }, false],
|
|
99
|
+
['redirect', { error: 'redirect rejected (302)', ok: false, status: 302 }, false],
|
|
100
|
+
['bad request', { error: 'non-2xx response (400)', ok: false, status: 400 }, false],
|
|
101
|
+
['request timeout', { error: 'non-2xx response (408)', ok: false, status: 408 }, true],
|
|
102
|
+
['rate limited', { error: 'non-2xx response (429)', ok: false, status: 429 }, true],
|
|
103
|
+
['server error', { error: 'non-2xx response (503)', ok: false, status: 503 }, true],
|
|
104
|
+
['opaque redirect', { error: 'redirect rejected (0)', ok: false }, false],
|
|
105
|
+
['timeout', { error: 'timed out after 10000ms', ok: false }, true],
|
|
106
|
+
['network error', { error: 'TypeError: fetch failed', ok: false }, true],
|
|
107
|
+
['unknown error', { ok: false }, true],
|
|
108
|
+
] satisfies readonly [string, WebhookDestination.Result, boolean][])(
|
|
109
|
+
'%s',
|
|
110
|
+
(_label, result, retryable) => {
|
|
111
|
+
expect(WebhookDestination.isRetryable(result)).toBe(retryable)
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
})
|
|
115
|
+
|
|
96
116
|
describe('url().deliver', () => {
|
|
97
117
|
test('signs the body and POSTs a 2xx as success', async () => {
|
|
98
118
|
let captured:
|
|
@@ -100,6 +100,16 @@ export type Result = {
|
|
|
100
100
|
status?: number | undefined
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/** Returns whether another delivery attempt could plausibly succeed. */
|
|
104
|
+
export function isRetryable(result: Result): boolean {
|
|
105
|
+
if (result.ok) return false
|
|
106
|
+
if (result.status !== undefined)
|
|
107
|
+
return result.status === 408 || result.status === 429 || result.status >= 500
|
|
108
|
+
// Workers exposes rejected redirects as an opaque response without a status.
|
|
109
|
+
if (result.error && /redirect/i.test(result.error)) return false
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
/**
|
|
104
114
|
* Canonical constructor: normalizes a value into a validated {@link Instance} by
|
|
105
115
|
* delegating to the matching builder (`url`, `slack`, `betterStack`), each of
|