tapimo 0.9.0 → 0.10.1
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 +4292 -3708
- 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 +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/App.d.ts +91 -84
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +26 -17
- 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/apps/verified-tokens.d.ts +168 -168
- 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 +26 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/apps/Zones.d.ts +4280 -84
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +30 -10
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +5799 -4929
- 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 +3391 -492
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +372 -29
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +853 -339
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +10 -10
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +145 -83
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +123 -123
- package/dist/apps/data/routes/coingecko.d.ts +169 -169
- package/dist/apps/data/routes/earn.d.ts +455 -1
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +369 -45
- package/dist/apps/data/routes/earn.js.map +1 -1
- 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/fee-amm.d.ts +24 -24
- package/dist/apps/data/routes/indexer.d.ts +35 -34
- 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.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/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 +461 -459
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +313 -305
- 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/webhooks.d.ts +519 -868
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +642 -353
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +31 -31
- package/dist/apps/data/routes/zones.d.ts.map +1 -1
- package/dist/apps/data/routes/zones.js +4 -2
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/providers.d.ts +13 -13
- package/dist/apps/funding/routes/quotes.d.ts +89 -89
- package/dist/apps/funding/routes/transfers.d.ts +36 -36
- 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 +11 -309
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +74 -74
- 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 +35 -35
- package/dist/apps/management/routes/faucet.js +2 -2
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +134 -134
- package/dist/apps/management/routes/invite-links.d.ts +80 -80
- package/dist/apps/management/routes/members.d.ts +69 -69
- package/dist/apps/management/routes/orgs.d.ts +98 -98
- package/dist/apps/management/routes/projects.d.ts +60 -60
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
- package/dist/apps/management/routes/webhooks.d.ts +203 -618
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- 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/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 +4 -2
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +75 -15
- 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/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/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 +58 -3
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +86 -3
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +3 -0
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +6 -4
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +139 -3
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +144 -4
- 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/Schema.js +1 -1
- package/dist/internal/Schema.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 +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/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.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 +75 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -0
- package/dist/internal/WebhookTransfer.js +299 -0
- package/dist/internal/WebhookTransfer.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +176 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +216 -20
- 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 +6 -4
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +71 -9
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +4 -4
- package/src/ApiKeys.test.ts +20 -0
- package/src/ApiKeys.ts +28 -1
- package/src/App.test.ts +10 -8
- package/src/App.ts +1 -0
- package/src/Client.test-d.ts +61 -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 +85 -29
- 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 +8 -0
- package/src/analytics/Analytics.ts +26 -0
- package/src/apps/Zones.test.ts +23 -5
- package/src/apps/Zones.ts +31 -10
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.test.ts +2 -2
- package/src/apps/data/FxOracle.test.ts +18 -0
- package/src/apps/data/FxOracle.ts +28 -5
- package/src/apps/data/routes/activities.bench.ts +41 -0
- package/src/apps/data/routes/activities.test.ts +122 -2
- package/src/apps/data/routes/activities.ts +435 -32
- package/src/apps/data/routes/balances.bench.ts +21 -0
- package/src/apps/data/routes/balances.test.ts +119 -10
- package/src/apps/data/routes/balances.ts +164 -97
- package/src/apps/data/routes/blocks.bench.ts +34 -0
- package/src/apps/data/routes/blocks.test.ts +12 -6
- 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.test.ts +197 -0
- package/src/apps/data/routes/earn.ts +487 -45
- package/src/apps/data/routes/exchanges.bench.ts +85 -0
- package/src/apps/data/routes/exchanges.test.ts +7 -6
- package/src/apps/data/routes/exchanges.ts +124 -117
- 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.test.ts +24 -7
- package/src/apps/data/routes/receipts.ts +67 -50
- 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.test.ts +51 -0
- package/src/apps/data/routes/transactions.bench.ts +40 -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.bench.ts +28 -0
- 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.bench.ts +14 -0
- package/src/apps/data/routes/valuation.test.ts +26 -0
- package/src/apps/data/routes/valuation.ts +6 -2
- 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 +466 -135
- package/src/apps/data/routes/webhooks.ts +820 -429
- 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 +7 -2
- package/src/apps/funding/routes/chains.test.ts +1 -1
- package/src/apps/funding/routes/deposit-addresses.test.ts +1 -1
- package/src/apps/funding/routes/deposit-addresses.ts +1 -1
- package/src/apps/funding/routes/providers.test.ts +1 -1
- package/src/apps/funding/routes/quotes.test.ts +2 -2
- package/src/apps/funding/routes/transfers.test.ts +3 -6
- 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/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 +114 -19
- package/src/db/Schema.ts +32 -0
- package/src/db/tables/sponsoredTransactions.ts +9 -2
- package/src/db/tables/webhookQueueEvents.ts +285 -0
- package/src/db/tables/webhookSubscriptions.ts +131 -4
- package/src/internal/Auth.test.ts +61 -50
- package/src/internal/Auth.ts +9 -4
- package/src/internal/EarnVaults.test.ts +2 -2
- package/src/internal/MetricSink.test.ts +575 -0
- package/src/internal/MetricSink.ts +325 -10
- package/src/internal/RateLimit.test.ts +23 -18
- package/src/internal/RateLimit.ts +23 -15
- package/src/internal/Schema.ts +1 -1
- 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 +1 -3
- package/src/internal/Value.test.ts +25 -0
- package/src/internal/Value.ts +29 -0
- 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.test.ts +89 -0
- package/src/internal/WebhookTransfer.ts +430 -0
- package/src/internal/Webhooks.test.ts +616 -0
- package/src/internal/Webhooks.ts +412 -24
- package/src/internal/funding/Provider.test.ts +48 -8
- 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/index.test.ts +73 -3
- package/src/internal/funding/providers/relay.test.ts +2 -9
- package/src/internal/funding/providers/relay.ts +72 -13
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { type Db, Webhooks as Core } from 'tapimo'
|
|
1
|
+
import { type Db, Store, Webhooks as Core } from 'tapimo'
|
|
2
2
|
import * as z from 'zod/mini'
|
|
3
3
|
|
|
4
4
|
import * as Cursor from '../../../internal/Cursor.js'
|
|
5
|
-
import * as Tidx from '../../../internal/Tidx.js'
|
|
6
5
|
import * as Viem from '../../../internal/Viem.js'
|
|
7
6
|
import * as TestApp from '../../../../test/App.js'
|
|
8
7
|
import * as Mppx from '../../../../test/Mppx.js'
|
|
@@ -1209,16 +1208,13 @@ describe('OpenAPI visibility', () => {
|
|
|
1209
1208
|
"from",
|
|
1210
1209
|
"to",
|
|
1211
1210
|
"includeCalls",
|
|
1212
|
-
"includeReceipt",
|
|
1213
1211
|
"value",
|
|
1214
1212
|
"input",
|
|
1215
1213
|
"calls",
|
|
1216
1214
|
"callCount",
|
|
1217
1215
|
"txType",
|
|
1218
1216
|
"feeToken",
|
|
1219
|
-
"feePayer",
|
|
1220
1217
|
"gasLimit",
|
|
1221
|
-
"gasUsed",
|
|
1222
1218
|
"maxFeePerGas",
|
|
1223
1219
|
"maxPriorityFeePerGas",
|
|
1224
1220
|
"nonce",
|
|
@@ -1227,7 +1223,6 @@ describe('OpenAPI visibility', () => {
|
|
|
1227
1223
|
"validAfter",
|
|
1228
1224
|
"blockNumber",
|
|
1229
1225
|
"timestamp",
|
|
1230
|
-
"status",
|
|
1231
1226
|
],
|
|
1232
1227
|
"title": "Included-transaction subscription",
|
|
1233
1228
|
},
|
|
@@ -1261,6 +1256,10 @@ describe('OpenAPI visibility', () => {
|
|
|
1261
1256
|
})
|
|
1262
1257
|
|
|
1263
1258
|
describe('envelope schema', () => {
|
|
1259
|
+
test('transaction events exclude receipt metadata', () => {
|
|
1260
|
+
expect(Object.keys(Webhooks.schema.TransactionEvent.shape.meta.shape)).toEqual(['rpc'])
|
|
1261
|
+
})
|
|
1262
|
+
|
|
1264
1263
|
/** A `transfer` subscription used to build representative envelopes. */
|
|
1265
1264
|
async function transferSubscription() {
|
|
1266
1265
|
const db = TestApp.database()
|
|
@@ -1352,10 +1351,9 @@ describe('MPP access (disabled for now — API-key-only)', () => {
|
|
|
1352
1351
|
})
|
|
1353
1352
|
})
|
|
1354
1353
|
|
|
1355
|
-
// The poll tests pin a
|
|
1356
|
-
//
|
|
1357
|
-
//
|
|
1358
|
-
// whole block is testnet-only.
|
|
1354
|
+
// The poll tests pin a fixed immutable historical window on testnet chain 42431.
|
|
1355
|
+
// Under `localnet`, the test RPC client is redirected to the disposable local
|
|
1356
|
+
// chain, which lacks that history, so the whole block is testnet-only.
|
|
1359
1357
|
describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
1360
1358
|
/** Decodes an opaque creation checkpoint or `(block, index)` poller cursor. */
|
|
1361
1359
|
function decodeCursor(token: string | null): number[] | null {
|
|
@@ -1372,17 +1370,8 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1372
1370
|
/** End-of-block index sentinel (matches the poller's own cursor sentinel). */
|
|
1373
1371
|
const endOfBlockIndex = 2_147_483_647
|
|
1374
1372
|
|
|
1375
|
-
//
|
|
1376
|
-
//
|
|
1377
|
-
// this window yields a deterministic, snapshot-stable row set while exercising
|
|
1378
|
-
// the real keyset/`toBlock` SQL and decode path end to end — no fabricated rows.
|
|
1379
|
-
const realTidx = Tidx.getClient({
|
|
1380
|
-
chainId: 42431,
|
|
1381
|
-
tidx: { auth: process.env.TIDX_AUTH, baseUrl: 'https://tidx.tempo.xyz' },
|
|
1382
|
-
})
|
|
1383
|
-
// The monitoring scanners (`log:emitted`/`block:created`/`transaction:included`)
|
|
1384
|
-
// read the same immutable historical window over real RPC (chain 42431 →
|
|
1385
|
-
// `rpc.testnet.tempo.xyz`), so their results are snapshot-stable too.
|
|
1373
|
+
// Every scanner reads this immutable window over real RPC (chain 42431 to
|
|
1374
|
+
// `rpc.testnet.tempo.xyz`), so the results remain snapshot-stable.
|
|
1386
1375
|
const client = Viem.getClient({ chainId: 42431, rpc: TestApp.rpc })
|
|
1387
1376
|
// Confirmed scan window `[windowFrom, windowTo]`: anchor the cursor just below
|
|
1388
1377
|
// `windowFrom`, and pass `headBlockNumber = windowTo + confirmations` so the
|
|
@@ -1390,10 +1379,11 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1390
1379
|
const windowFrom = 1_000_000
|
|
1391
1380
|
const windowTo = 1_000_002
|
|
1392
1381
|
const windowCursor = encodeCursor([windowFrom - 1, endOfBlockIndex])
|
|
1393
|
-
const windowHead = windowTo +
|
|
1382
|
+
const windowHead = windowTo + 1
|
|
1394
1383
|
// A sender whose only transfers in `[windowFrom, windowTo]` are exactly two
|
|
1395
1384
|
// rows, both in block 1,000,002 (`log_idx` 0 then 1): a TIP-20 transfer
|
|
1396
1385
|
// followed by its fee payment. Filtering on it makes the scan deterministic.
|
|
1386
|
+
const windowRecipient = '0x9e39034aae71fb89f66061a2602eb6efec271754'
|
|
1397
1387
|
const windowSender = '0xe7687128b0a808c2831ff94d4f7b2fb35c65af38'
|
|
1398
1388
|
// This token has 226 transfers across blocks 1,000,000 through 1,000,003.
|
|
1399
1389
|
const volumeHead = 1_000_005
|
|
@@ -1428,17 +1418,28 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1428
1418
|
await Core.setCursor(db, subscription.id, windowCursor)
|
|
1429
1419
|
|
|
1430
1420
|
const dispatched: Webhooks.poll.Dispatchable[] = []
|
|
1421
|
+
const scanCheckpoints: Webhooks.poll.ScanCheckpoint[] = []
|
|
1422
|
+
const stages: Webhooks.poll.Stage[] = []
|
|
1423
|
+
let cursorLagBlocks: number | undefined
|
|
1431
1424
|
const result = await Webhooks.poll({
|
|
1432
1425
|
chainId: 42431,
|
|
1433
1426
|
dispatch: async (input) => {
|
|
1434
1427
|
dispatched.push(input)
|
|
1435
1428
|
return { ok: true }
|
|
1436
1429
|
},
|
|
1437
|
-
eventTypes: ['token:transfer'],
|
|
1430
|
+
eventTypes: ['token:transfer', 'token:transfer'],
|
|
1438
1431
|
headBlockNumber: windowHead,
|
|
1439
1432
|
client,
|
|
1440
1433
|
db,
|
|
1441
|
-
|
|
1434
|
+
onCursorLag(blocks) {
|
|
1435
|
+
cursorLagBlocks = blocks
|
|
1436
|
+
},
|
|
1437
|
+
onScanCheckpoint(checkpoint) {
|
|
1438
|
+
scanCheckpoints.push(checkpoint)
|
|
1439
|
+
},
|
|
1440
|
+
onStage(stage) {
|
|
1441
|
+
stages.push(stage)
|
|
1442
|
+
},
|
|
1442
1443
|
})
|
|
1443
1444
|
|
|
1444
1445
|
expect(result).toMatchInlineSnapshot(`
|
|
@@ -1451,9 +1452,68 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1451
1452
|
"subscriptions": 1,
|
|
1452
1453
|
}
|
|
1453
1454
|
`)
|
|
1455
|
+
expect(cursorLagBlocks).toBe(3)
|
|
1456
|
+
expect(scanCheckpoints.map(({ durationMs: _, ...checkpoint }) => checkpoint))
|
|
1457
|
+
.toMatchInlineSnapshot(`
|
|
1458
|
+
[
|
|
1459
|
+
{
|
|
1460
|
+
"concurrency": 8,
|
|
1461
|
+
"eventType": "token:transfer",
|
|
1462
|
+
"maxWindowBlocks": 3,
|
|
1463
|
+
"phase": "start",
|
|
1464
|
+
"rpcMethod": "eth_getLogs",
|
|
1465
|
+
"subscriptionCount": 1,
|
|
1466
|
+
"subscriptionLimit": 256,
|
|
1467
|
+
"totalWindowBlocks": 3,
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
"concurrency": 8,
|
|
1471
|
+
"eventType": "token:transfer",
|
|
1472
|
+
"failedSubscriptions": 0,
|
|
1473
|
+
"matchedItems": 2,
|
|
1474
|
+
"maxWindowBlocks": 3,
|
|
1475
|
+
"outcome": "ok",
|
|
1476
|
+
"phase": "complete",
|
|
1477
|
+
"rpcMethod": "eth_getLogs",
|
|
1478
|
+
"subscriptionCount": 1,
|
|
1479
|
+
"subscriptionLimit": 256,
|
|
1480
|
+
"totalWindowBlocks": 3,
|
|
1481
|
+
},
|
|
1482
|
+
]
|
|
1483
|
+
`)
|
|
1484
|
+
expect(stages.map(({ eventType, outcome, stage }) => ({ eventType, outcome, stage })))
|
|
1485
|
+
.toMatchInlineSnapshot(`
|
|
1486
|
+
[
|
|
1487
|
+
{
|
|
1488
|
+
"eventType": undefined,
|
|
1489
|
+
"outcome": "ok",
|
|
1490
|
+
"stage": "rpc_head",
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
"eventType": undefined,
|
|
1494
|
+
"outcome": "ok",
|
|
1495
|
+
"stage": "subscription_read",
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
"eventType": "token:transfer",
|
|
1499
|
+
"outcome": "ok",
|
|
1500
|
+
"stage": "scan",
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
"eventType": "token:transfer",
|
|
1504
|
+
"outcome": "ok",
|
|
1505
|
+
"stage": "enrich",
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
"eventType": "token:transfer",
|
|
1509
|
+
"outcome": "ok",
|
|
1510
|
+
"stage": "cursor_write",
|
|
1511
|
+
},
|
|
1512
|
+
]
|
|
1513
|
+
`)
|
|
1454
1514
|
expect(dispatched).toHaveLength(2)
|
|
1455
|
-
// The envelope carries the
|
|
1456
|
-
//
|
|
1515
|
+
// The envelope carries the decoded RPC transfer row, here the first of the
|
|
1516
|
+
// two pinned-window transfers from `windowSender`.
|
|
1457
1517
|
expect(dispatched[0]?.envelope.data).toMatchInlineSnapshot(`
|
|
1458
1518
|
{
|
|
1459
1519
|
"address": "0x20c00000000000000000000071de0cd31ab0d105",
|
|
@@ -1462,7 +1522,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1462
1522
|
"logIndex": 0,
|
|
1463
1523
|
"recipient": "0x9e39034aae71fb89f66061a2602eb6efec271754",
|
|
1464
1524
|
"sender": "0xe7687128b0a808c2831ff94d4f7b2fb35c65af38",
|
|
1465
|
-
"timestamp": "2026-01-14T18:38:03.
|
|
1525
|
+
"timestamp": "2026-01-14T18:38:03.685Z",
|
|
1466
1526
|
"token": {
|
|
1467
1527
|
"decimals": 6,
|
|
1468
1528
|
"symbol": "QKSUSD",
|
|
@@ -1480,6 +1540,221 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1480
1540
|
`)
|
|
1481
1541
|
})
|
|
1482
1542
|
|
|
1543
|
+
test('delivers collision-shaped token metadata from a cache hit', async () => {
|
|
1544
|
+
const db = TestApp.database()
|
|
1545
|
+
const subscription = await createTransferSubscription(db)
|
|
1546
|
+
await Core.setCursor(db, subscription.id, windowCursor)
|
|
1547
|
+
const cache = Store.memory()
|
|
1548
|
+
const address = '0x20c00000000000000000000071de0cd31ab0d105'
|
|
1549
|
+
await Store.memoize(
|
|
1550
|
+
async () => ({
|
|
1551
|
+
currency: 'USD',
|
|
1552
|
+
decimals: 6,
|
|
1553
|
+
name: 'Collision token',
|
|
1554
|
+
symbol: '123#__bigint',
|
|
1555
|
+
}),
|
|
1556
|
+
{
|
|
1557
|
+
key: `webhook:token-meta:v2:42431:${address}`,
|
|
1558
|
+
store: cache,
|
|
1559
|
+
ttl: 60_000,
|
|
1560
|
+
},
|
|
1561
|
+
)
|
|
1562
|
+
const bodies: string[] = []
|
|
1563
|
+
const fetch = (async (_input, init) => {
|
|
1564
|
+
bodies.push(typeof init?.body === 'string' ? init.body : '')
|
|
1565
|
+
return new Response(null, { status: 200 })
|
|
1566
|
+
}) satisfies typeof globalThis.fetch
|
|
1567
|
+
|
|
1568
|
+
const result = await Webhooks.poll({
|
|
1569
|
+
cache,
|
|
1570
|
+
chainId: 42431,
|
|
1571
|
+
client,
|
|
1572
|
+
db,
|
|
1573
|
+
dispatch: ({ envelope, subscription }) =>
|
|
1574
|
+
Core.deliverAndRecord(db, subscription, envelope, { fetch }),
|
|
1575
|
+
eventTypes: ['token:transfer'],
|
|
1576
|
+
headBlockNumber: windowHead,
|
|
1577
|
+
})
|
|
1578
|
+
|
|
1579
|
+
const payloads = bodies.map((body) => Webhooks.schema.Envelope.parse(JSON.parse(body)))
|
|
1580
|
+
const delivered = payloads.find(
|
|
1581
|
+
(payload) => payload.type === 'token:transfer' && payload.data.address === address,
|
|
1582
|
+
)
|
|
1583
|
+
expect(result.dispatched).toBe(2)
|
|
1584
|
+
expect(delivered?.type === 'token:transfer' ? delivered.data.token?.symbol : undefined).toBe(
|
|
1585
|
+
'123#__bigint',
|
|
1586
|
+
)
|
|
1587
|
+
})
|
|
1588
|
+
|
|
1589
|
+
test('scans event types concurrently without increasing scan bounds', async () => {
|
|
1590
|
+
const db = TestApp.database()
|
|
1591
|
+
const transfer = await createTransferSubscription(db)
|
|
1592
|
+
const block = await Core.createSubscription(db, {
|
|
1593
|
+
chainId: 42431,
|
|
1594
|
+
destination: { type: 'url', url: 'https://hooks.example.com/block' },
|
|
1595
|
+
eventType: 'block:created',
|
|
1596
|
+
filters: {},
|
|
1597
|
+
owner: { orgId: 'org_block', type: 'api_key' },
|
|
1598
|
+
})
|
|
1599
|
+
await Promise.all([
|
|
1600
|
+
Core.setCursor(db, transfer.id, windowCursor),
|
|
1601
|
+
Core.setCursor(db, block.id, windowCursor),
|
|
1602
|
+
])
|
|
1603
|
+
|
|
1604
|
+
const active = new Map<
|
|
1605
|
+
Webhooks.EventType,
|
|
1606
|
+
Pick<Webhooks.poll.ScanCheckpoint, 'concurrency' | 'subscriptionLimit'>
|
|
1607
|
+
>()
|
|
1608
|
+
let maxConcurrency = 0
|
|
1609
|
+
let maxEventTypes = 0
|
|
1610
|
+
let maxSubscriptionLimit = 0
|
|
1611
|
+
await Webhooks.poll({
|
|
1612
|
+
chainId: 42431,
|
|
1613
|
+
client,
|
|
1614
|
+
db,
|
|
1615
|
+
dispatch: async () => ({ ok: true }),
|
|
1616
|
+
eventTypes: ['token:transfer', 'block:created'],
|
|
1617
|
+
headBlockNumber: windowHead,
|
|
1618
|
+
onScanCheckpoint(checkpoint) {
|
|
1619
|
+
expect(checkpoint.subscriptionCount).toBeLessThanOrEqual(checkpoint.subscriptionLimit)
|
|
1620
|
+
if (checkpoint.phase === 'start')
|
|
1621
|
+
active.set(checkpoint.eventType, {
|
|
1622
|
+
concurrency: checkpoint.concurrency,
|
|
1623
|
+
subscriptionLimit: checkpoint.subscriptionLimit,
|
|
1624
|
+
})
|
|
1625
|
+
else active.delete(checkpoint.eventType)
|
|
1626
|
+
maxConcurrency = Math.max(
|
|
1627
|
+
maxConcurrency,
|
|
1628
|
+
...[...active.values()].map((value) => value.concurrency),
|
|
1629
|
+
)
|
|
1630
|
+
maxEventTypes = Math.max(maxEventTypes, active.size)
|
|
1631
|
+
maxSubscriptionLimit = Math.max(
|
|
1632
|
+
maxSubscriptionLimit,
|
|
1633
|
+
[...active.values()].reduce((total, value) => total + value.subscriptionLimit, 0),
|
|
1634
|
+
)
|
|
1635
|
+
},
|
|
1636
|
+
})
|
|
1637
|
+
|
|
1638
|
+
expect(active.size).toBe(0)
|
|
1639
|
+
expect(maxConcurrency).toBe(8)
|
|
1640
|
+
expect(maxEventTypes).toBe(2)
|
|
1641
|
+
expect(maxSubscriptionLimit).toBe(256)
|
|
1642
|
+
})
|
|
1643
|
+
|
|
1644
|
+
test('dispatches a page as one batch and advances the cursor', async () => {
|
|
1645
|
+
const db = TestApp.database()
|
|
1646
|
+
const subscription = await createTransferSubscription(db)
|
|
1647
|
+
await Core.setCursor(db, subscription.id, windowCursor)
|
|
1648
|
+
|
|
1649
|
+
const batches: Webhooks.poll.Dispatchable[][] = []
|
|
1650
|
+
const result = await Webhooks.poll({
|
|
1651
|
+
chainId: 42431,
|
|
1652
|
+
client,
|
|
1653
|
+
db,
|
|
1654
|
+
dispatchBatch: async (dispatchables, context) => {
|
|
1655
|
+
expect(context.db).toBe(db)
|
|
1656
|
+
batches.push([...dispatchables])
|
|
1657
|
+
return { ok: true }
|
|
1658
|
+
},
|
|
1659
|
+
eventTypes: ['token:transfer'],
|
|
1660
|
+
headBlockNumber: windowHead,
|
|
1661
|
+
})
|
|
1662
|
+
|
|
1663
|
+
expect(result.dispatched).toBe(2)
|
|
1664
|
+
expect(batches.map((batch) => batch.length)).toEqual([2])
|
|
1665
|
+
expect(decodeCursor(await Core.getCursor(db, subscription.id))).toEqual([windowTo, 1])
|
|
1666
|
+
})
|
|
1667
|
+
|
|
1668
|
+
test('keeps the cursor unchanged when a batch dispatch fails', async () => {
|
|
1669
|
+
const db = TestApp.database()
|
|
1670
|
+
const subscription = await createTransferSubscription(db)
|
|
1671
|
+
await Core.setCursor(db, subscription.id, windowCursor)
|
|
1672
|
+
|
|
1673
|
+
const result = await Webhooks.poll({
|
|
1674
|
+
chainId: 42431,
|
|
1675
|
+
client,
|
|
1676
|
+
db,
|
|
1677
|
+
dispatchBatch: async () => ({ error: 'unreachable', ok: false }),
|
|
1678
|
+
eventTypes: ['token:transfer'],
|
|
1679
|
+
headBlockNumber: windowHead,
|
|
1680
|
+
})
|
|
1681
|
+
|
|
1682
|
+
expect(result.dispatched).toBe(0)
|
|
1683
|
+
expect(result.failed).toBe(2)
|
|
1684
|
+
expect(decodeCursor(await Core.getCursor(db, subscription.id))).toEqual([
|
|
1685
|
+
windowFrom - 1,
|
|
1686
|
+
endOfBlockIndex,
|
|
1687
|
+
])
|
|
1688
|
+
})
|
|
1689
|
+
|
|
1690
|
+
test('matches the either-side address filter through Viem log filters', async () => {
|
|
1691
|
+
const db = TestApp.database()
|
|
1692
|
+
const subscription = await Core.createSubscription(db, {
|
|
1693
|
+
chainId: 42431,
|
|
1694
|
+
destination: { type: 'url', url: 'https://hooks.example.com/address' },
|
|
1695
|
+
eventType: 'token:transfer',
|
|
1696
|
+
filters: { address: windowSender },
|
|
1697
|
+
owner: { orgId: 'org_address', type: 'api_key' },
|
|
1698
|
+
})
|
|
1699
|
+
await Core.setCursor(db, subscription.id, windowCursor)
|
|
1700
|
+
|
|
1701
|
+
const positions: string[] = []
|
|
1702
|
+
const result = await Webhooks.poll({
|
|
1703
|
+
chainId: 42431,
|
|
1704
|
+
client,
|
|
1705
|
+
db,
|
|
1706
|
+
dispatch: async ({ envelope }) => {
|
|
1707
|
+
const data = envelope.data as { blockNumber: number; logIndex: number }
|
|
1708
|
+
positions.push(`${data.blockNumber}:${data.logIndex}`)
|
|
1709
|
+
return { ok: true }
|
|
1710
|
+
},
|
|
1711
|
+
eventTypes: ['token:transfer'],
|
|
1712
|
+
headBlockNumber: windowHead,
|
|
1713
|
+
})
|
|
1714
|
+
|
|
1715
|
+
expect(result.dispatched).toBe(2)
|
|
1716
|
+
expect(positions).toEqual(['1000002:0', '1000002:1'])
|
|
1717
|
+
})
|
|
1718
|
+
|
|
1719
|
+
test('keeps distinct transfer pushdowns isolated', async () => {
|
|
1720
|
+
const db = TestApp.database()
|
|
1721
|
+
const recipient = await Core.createSubscription(db, {
|
|
1722
|
+
chainId: 42431,
|
|
1723
|
+
destination: { type: 'url', url: 'https://hooks.example.com/recipient' },
|
|
1724
|
+
eventType: 'token:transfer',
|
|
1725
|
+
filters: { recipient: windowRecipient },
|
|
1726
|
+
owner: { orgId: 'org_recipient', type: 'api_key' },
|
|
1727
|
+
})
|
|
1728
|
+
const sender = await createTransferSubscription(db)
|
|
1729
|
+
await Promise.all([
|
|
1730
|
+
Core.setCursor(db, recipient.id, windowCursor),
|
|
1731
|
+
Core.setCursor(db, sender.id, windowCursor),
|
|
1732
|
+
])
|
|
1733
|
+
|
|
1734
|
+
const dispatched: Webhooks.poll.Dispatchable[] = []
|
|
1735
|
+
const result = await Webhooks.poll({
|
|
1736
|
+
chainId: 42431,
|
|
1737
|
+
client,
|
|
1738
|
+
db,
|
|
1739
|
+
dispatch: async (input) => {
|
|
1740
|
+
dispatched.push(input)
|
|
1741
|
+
return { ok: true }
|
|
1742
|
+
},
|
|
1743
|
+
eventTypes: ['token:transfer'],
|
|
1744
|
+
headBlockNumber: windowHead,
|
|
1745
|
+
})
|
|
1746
|
+
|
|
1747
|
+
expect(result.dispatched).toBe(3)
|
|
1748
|
+
expect(
|
|
1749
|
+
Object.fromEntries(
|
|
1750
|
+
[...Map.groupBy(dispatched, ({ subscription }) => subscription.id)].map(([id, items]) => [
|
|
1751
|
+
id,
|
|
1752
|
+
items.length,
|
|
1753
|
+
]),
|
|
1754
|
+
),
|
|
1755
|
+
).toEqual({ [recipient.id]: 1, [sender.id]: 2 })
|
|
1756
|
+
})
|
|
1757
|
+
|
|
1483
1758
|
test('pauses the cursor at the last success when dispatch fails', async () => {
|
|
1484
1759
|
const db = TestApp.database()
|
|
1485
1760
|
const subscription = await createTransferSubscription(db)
|
|
@@ -1494,7 +1769,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1494
1769
|
headBlockNumber: windowHead,
|
|
1495
1770
|
client,
|
|
1496
1771
|
db,
|
|
1497
|
-
tidx: realTidx,
|
|
1498
1772
|
})
|
|
1499
1773
|
|
|
1500
1774
|
expect(result.dispatched).toMatchInlineSnapshot(`1`)
|
|
@@ -1522,7 +1796,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1522
1796
|
headBlockNumber: windowHead,
|
|
1523
1797
|
client,
|
|
1524
1798
|
db,
|
|
1525
|
-
tidx: realTidx,
|
|
1526
1799
|
})
|
|
1527
1800
|
|
|
1528
1801
|
expect(result.dispatched).toMatchInlineSnapshot(`0`)
|
|
@@ -1537,6 +1810,31 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1537
1810
|
`)
|
|
1538
1811
|
})
|
|
1539
1812
|
|
|
1813
|
+
test('stops dispatching when the poll signal is aborted', async () => {
|
|
1814
|
+
const db = TestApp.database()
|
|
1815
|
+
const subscription = await createTransferSubscription(db)
|
|
1816
|
+
await Core.setCursor(db, subscription.id, windowCursor)
|
|
1817
|
+
|
|
1818
|
+
const abort = new AbortController()
|
|
1819
|
+
let calls = 0
|
|
1820
|
+
await expect(
|
|
1821
|
+
Webhooks.poll({
|
|
1822
|
+
chainId: 42431,
|
|
1823
|
+
client,
|
|
1824
|
+
db,
|
|
1825
|
+
dispatch: async () => {
|
|
1826
|
+
calls++
|
|
1827
|
+
abort.abort(new Error('lease lost'))
|
|
1828
|
+
return { ok: true }
|
|
1829
|
+
},
|
|
1830
|
+
eventTypes: ['token:transfer'],
|
|
1831
|
+
headBlockNumber: windowHead,
|
|
1832
|
+
signal: abort.signal,
|
|
1833
|
+
}),
|
|
1834
|
+
).rejects.toThrow('lease lost')
|
|
1835
|
+
expect(calls).toBe(1)
|
|
1836
|
+
})
|
|
1837
|
+
|
|
1540
1838
|
test('drains two bounded transfer pages per tick without gaps', async () => {
|
|
1541
1839
|
const db = TestApp.database()
|
|
1542
1840
|
const subscription = await createVolumeSubscription(db)
|
|
@@ -1547,6 +1845,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1547
1845
|
Webhooks.poll({
|
|
1548
1846
|
chainId: 42431,
|
|
1549
1847
|
client,
|
|
1848
|
+
confirmations: 2,
|
|
1550
1849
|
db,
|
|
1551
1850
|
dispatch: async ({ envelope }) => {
|
|
1552
1851
|
const data = envelope.data as { blockNumber: number; logIndex: number }
|
|
@@ -1555,7 +1854,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1555
1854
|
},
|
|
1556
1855
|
eventTypes: ['token:transfer'],
|
|
1557
1856
|
headBlockNumber: volumeHead,
|
|
1558
|
-
tidx: realTidx,
|
|
1559
1857
|
})
|
|
1560
1858
|
|
|
1561
1859
|
expect((await tick()).dispatched).toBe(200)
|
|
@@ -1577,11 +1875,11 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1577
1875
|
const first = await Webhooks.poll({
|
|
1578
1876
|
chainId: 42431,
|
|
1579
1877
|
client,
|
|
1878
|
+
confirmations: 2,
|
|
1580
1879
|
db,
|
|
1581
1880
|
dispatch: async () => (++calls === 101 ? { error: 'unreachable', ok: false } : { ok: true }),
|
|
1582
1881
|
eventTypes: ['token:transfer'],
|
|
1583
1882
|
headBlockNumber: volumeHead,
|
|
1584
|
-
tidx: realTidx,
|
|
1585
1883
|
})
|
|
1586
1884
|
|
|
1587
1885
|
expect(first.dispatched).toBe(100)
|
|
@@ -1593,6 +1891,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1593
1891
|
batchSize: 1,
|
|
1594
1892
|
chainId: 42431,
|
|
1595
1893
|
client,
|
|
1894
|
+
confirmations: 2,
|
|
1596
1895
|
db,
|
|
1597
1896
|
dispatch: async ({ envelope }) => {
|
|
1598
1897
|
const data = envelope.data as { blockNumber: number; logIndex: number }
|
|
@@ -1601,7 +1900,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1601
1900
|
},
|
|
1602
1901
|
eventTypes: ['token:transfer'],
|
|
1603
1902
|
headBlockNumber: volumeHead,
|
|
1604
|
-
tidx: realTidx,
|
|
1605
1903
|
})
|
|
1606
1904
|
expect(resumed).toEqual([1_000_002, 10])
|
|
1607
1905
|
})
|
|
@@ -1618,6 +1916,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1618
1916
|
await Webhooks.poll({
|
|
1619
1917
|
chainId: 42431,
|
|
1620
1918
|
client,
|
|
1919
|
+
confirmations: 2,
|
|
1621
1920
|
db,
|
|
1622
1921
|
dispatch: async ({ subscription }) => {
|
|
1623
1922
|
order.push(subscription.id)
|
|
@@ -1625,7 +1924,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1625
1924
|
},
|
|
1626
1925
|
eventTypes: ['token:transfer'],
|
|
1627
1926
|
headBlockNumber: volumeHead,
|
|
1628
|
-
tidx: realTidx,
|
|
1629
1927
|
})
|
|
1630
1928
|
|
|
1631
1929
|
expect(order).toHaveLength(400)
|
|
@@ -1651,7 +1949,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1651
1949
|
client,
|
|
1652
1950
|
db,
|
|
1653
1951
|
// Rows exist in history, but a brand-new subscription must not replay them.
|
|
1654
|
-
tidx: realTidx,
|
|
1655
1952
|
})
|
|
1656
1953
|
|
|
1657
1954
|
// Nothing scanned or dispatched on the first tick; the cursor is anchored at
|
|
@@ -1689,7 +1986,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1689
1986
|
headBlockNumber: creationHead + 3,
|
|
1690
1987
|
client,
|
|
1691
1988
|
db,
|
|
1692
|
-
tidx: realTidx,
|
|
1693
1989
|
})
|
|
1694
1990
|
|
|
1695
1991
|
expect(result.dispatched).toBe(2)
|
|
@@ -1729,15 +2025,14 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1729
2025
|
headBlockNumber: windowHead,
|
|
1730
2026
|
client,
|
|
1731
2027
|
db,
|
|
1732
|
-
tidx: realTidx,
|
|
1733
2028
|
})
|
|
1734
2029
|
|
|
1735
2030
|
expect(decodeCursor(await Core.getCursor(db, transfer.id))).toEqual([
|
|
1736
|
-
startBlockNumber -
|
|
2031
|
+
startBlockNumber - 1,
|
|
1737
2032
|
endOfBlockIndex,
|
|
1738
2033
|
])
|
|
1739
2034
|
expect(decodeCursor(await Core.getCursor(db, block.id))).toEqual([
|
|
1740
|
-
startBlockNumber -
|
|
2035
|
+
startBlockNumber - 1,
|
|
1741
2036
|
endOfBlockIndex,
|
|
1742
2037
|
])
|
|
1743
2038
|
})
|
|
@@ -1754,10 +2049,9 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1754
2049
|
chainId: 42431,
|
|
1755
2050
|
dispatch: async () => ({ ok: true }),
|
|
1756
2051
|
eventTypes: ['token:transfer'],
|
|
1757
|
-
headBlockNumber: windowTo +
|
|
2052
|
+
headBlockNumber: windowTo + 3,
|
|
1758
2053
|
client,
|
|
1759
2054
|
db,
|
|
1760
|
-
tidx: realTidx,
|
|
1761
2055
|
})
|
|
1762
2056
|
|
|
1763
2057
|
expect(result.scanned).toMatchInlineSnapshot(`0`)
|
|
@@ -1779,6 +2073,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1779
2073
|
const transferSignature =
|
|
1780
2074
|
'event Transfer(address indexed from, address indexed to, uint256 value)'
|
|
1781
2075
|
const transferSelector = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
|
|
2076
|
+
const windowToken = '0x20c00000000000000000000071de0cd31ab0d105'
|
|
1782
2077
|
const windowSenderTopic = `0x${'0'.repeat(24)}${windowSender.slice(2)}`
|
|
1783
2078
|
|
|
1784
2079
|
test('log:emitted decodes args from a signature-matched event view', async () => {
|
|
@@ -1803,7 +2098,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1803
2098
|
headBlockNumber: windowHead,
|
|
1804
2099
|
client,
|
|
1805
2100
|
db,
|
|
1806
|
-
tidx: realTidx,
|
|
1807
2101
|
})
|
|
1808
2102
|
|
|
1809
2103
|
expect(result.scanned).toMatchInlineSnapshot(`2`)
|
|
@@ -1823,7 +2117,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1823
2117
|
"topic0": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
|
1824
2118
|
},
|
|
1825
2119
|
"logIndex": 0,
|
|
1826
|
-
"timestamp": "2026-01-14T18:38:03.
|
|
2120
|
+
"timestamp": "2026-01-14T18:38:03.685Z",
|
|
1827
2121
|
"topics": [
|
|
1828
2122
|
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
|
1829
2123
|
"0x000000000000000000000000e7687128b0a808c2831ff94d4f7b2fb35c65af38",
|
|
@@ -1857,7 +2151,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1857
2151
|
headBlockNumber: windowHead,
|
|
1858
2152
|
client,
|
|
1859
2153
|
db,
|
|
1860
|
-
tidx: realTidx,
|
|
1861
2154
|
})
|
|
1862
2155
|
|
|
1863
2156
|
expect(result.scanned).toMatchInlineSnapshot(`2`)
|
|
@@ -1867,7 +2160,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1867
2160
|
"blockNumber": 1000002,
|
|
1868
2161
|
"data": "0x0000000000000000000000000000000000000000000000000000000000002710",
|
|
1869
2162
|
"logIndex": 0,
|
|
1870
|
-
"timestamp": "2026-01-14T18:38:03.
|
|
2163
|
+
"timestamp": "2026-01-14T18:38:03.685Z",
|
|
1871
2164
|
"topics": [
|
|
1872
2165
|
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
|
1873
2166
|
"0x000000000000000000000000e7687128b0a808c2831ff94d4f7b2fb35c65af38",
|
|
@@ -1879,6 +2172,87 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1879
2172
|
`)
|
|
1880
2173
|
})
|
|
1881
2174
|
|
|
2175
|
+
test('log:emitted scans an address-only filter', async () => {
|
|
2176
|
+
const db = TestApp.database()
|
|
2177
|
+
const subscription = await Core.createSubscription(db, {
|
|
2178
|
+
chainId: 42431,
|
|
2179
|
+
destination: { type: 'url', url: 'https://hooks.example.com/address-only' },
|
|
2180
|
+
eventType: 'log:emitted',
|
|
2181
|
+
filters: { address: '0xffffffffffffffffffffffffffffffffffffffff' },
|
|
2182
|
+
owner: { orgId: 'org_address_only', type: 'api_key' },
|
|
2183
|
+
})
|
|
2184
|
+
await Core.setCursor(db, subscription.id, windowCursor)
|
|
2185
|
+
|
|
2186
|
+
const result = await Webhooks.poll({
|
|
2187
|
+
chainId: 42431,
|
|
2188
|
+
client,
|
|
2189
|
+
db,
|
|
2190
|
+
eventTypes: ['log:emitted'],
|
|
2191
|
+
headBlockNumber: windowHead,
|
|
2192
|
+
})
|
|
2193
|
+
|
|
2194
|
+
expect(result.scanned).toBe(0)
|
|
2195
|
+
expect(decodeCursor(await Core.getCursor(db, subscription.id))).toEqual([
|
|
2196
|
+
windowTo,
|
|
2197
|
+
endOfBlockIndex,
|
|
2198
|
+
])
|
|
2199
|
+
})
|
|
2200
|
+
|
|
2201
|
+
test('log:emitted keeps distinct address and topic pushdowns isolated', async () => {
|
|
2202
|
+
const db = TestApp.database()
|
|
2203
|
+
const address = await Core.createSubscription(db, {
|
|
2204
|
+
chainId: 42431,
|
|
2205
|
+
destination: { type: 'url', url: 'https://hooks.example.com/address' },
|
|
2206
|
+
eventType: 'log:emitted',
|
|
2207
|
+
filters: {
|
|
2208
|
+
address: windowToken,
|
|
2209
|
+
args: { from: windowSender },
|
|
2210
|
+
signature: transferSignature,
|
|
2211
|
+
},
|
|
2212
|
+
owner: { orgId: 'org_address', type: 'api_key' },
|
|
2213
|
+
})
|
|
2214
|
+
const topic = await Core.createSubscription(db, {
|
|
2215
|
+
chainId: 42431,
|
|
2216
|
+
destination: { type: 'url', url: 'https://hooks.example.com/topic' },
|
|
2217
|
+
eventType: 'log:emitted',
|
|
2218
|
+
filters: { topic0: transferSelector, topic1: windowSenderTopic },
|
|
2219
|
+
owner: { orgId: 'org_topic', type: 'api_key' },
|
|
2220
|
+
})
|
|
2221
|
+
await Promise.all([
|
|
2222
|
+
Core.setCursor(db, address.id, windowCursor),
|
|
2223
|
+
Core.setCursor(db, topic.id, windowCursor),
|
|
2224
|
+
])
|
|
2225
|
+
|
|
2226
|
+
const dispatched: Webhooks.poll.Dispatchable[] = []
|
|
2227
|
+
const result = await Webhooks.poll({
|
|
2228
|
+
chainId: 42431,
|
|
2229
|
+
client,
|
|
2230
|
+
db,
|
|
2231
|
+
dispatch: async (input) => {
|
|
2232
|
+
dispatched.push(input)
|
|
2233
|
+
return { ok: true }
|
|
2234
|
+
},
|
|
2235
|
+
eventTypes: ['log:emitted'],
|
|
2236
|
+
headBlockNumber: windowHead,
|
|
2237
|
+
})
|
|
2238
|
+
|
|
2239
|
+
expect(result.dispatched).toBe(3)
|
|
2240
|
+
expect(
|
|
2241
|
+
Object.fromEntries(
|
|
2242
|
+
[...Map.groupBy(dispatched, ({ subscription }) => subscription.id)].map(([id, items]) => [
|
|
2243
|
+
id,
|
|
2244
|
+
items.length,
|
|
2245
|
+
]),
|
|
2246
|
+
),
|
|
2247
|
+
).toEqual({ [address.id]: 1, [topic.id]: 2 })
|
|
2248
|
+
expect(
|
|
2249
|
+
await Promise.all([Core.getCursor(db, address.id), Core.getCursor(db, topic.id)]),
|
|
2250
|
+
).toEqual([
|
|
2251
|
+
encodeCursor([windowTo, endOfBlockIndex]),
|
|
2252
|
+
encodeCursor([windowTo, endOfBlockIndex]),
|
|
2253
|
+
])
|
|
2254
|
+
})
|
|
2255
|
+
|
|
1882
2256
|
test('log:emitted limits a tick and resumes within the block', async () => {
|
|
1883
2257
|
const db = TestApp.database()
|
|
1884
2258
|
const subscription = await Core.createSubscription(db, {
|
|
@@ -1903,13 +2277,23 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1903
2277
|
},
|
|
1904
2278
|
eventTypes: ['log:emitted'],
|
|
1905
2279
|
headBlockNumber: windowHead,
|
|
1906
|
-
tidx: realTidx,
|
|
1907
2280
|
})
|
|
1908
2281
|
|
|
1909
|
-
|
|
2282
|
+
const first = await tick()
|
|
2283
|
+
expect(first.dispatched).toBe(1)
|
|
2284
|
+
expect(first.hasMore).toBe(true)
|
|
1910
2285
|
expect(decodeCursor(await Core.getCursor(db, subscription.id))).toEqual([windowTo, 0])
|
|
1911
|
-
|
|
2286
|
+
const second = await tick()
|
|
2287
|
+
expect(second.dispatched).toBe(1)
|
|
2288
|
+
expect(second.hasMore).toBe(true)
|
|
1912
2289
|
expect(decodeCursor(await Core.getCursor(db, subscription.id))).toEqual([windowTo, 1])
|
|
2290
|
+
const third = await tick()
|
|
2291
|
+
expect(third.dispatched).toBe(0)
|
|
2292
|
+
expect(third.hasMore).toBeUndefined()
|
|
2293
|
+
expect(decodeCursor(await Core.getCursor(db, subscription.id))).toEqual([
|
|
2294
|
+
windowTo,
|
|
2295
|
+
endOfBlockIndex,
|
|
2296
|
+
])
|
|
1913
2297
|
expect(
|
|
1914
2298
|
dispatched.map(({ envelope }) => (envelope.data as { logIndex: number }).logIndex),
|
|
1915
2299
|
).toEqual([0, 1])
|
|
@@ -1942,7 +2326,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1942
2326
|
headBlockNumber: windowTo + 3,
|
|
1943
2327
|
client,
|
|
1944
2328
|
db,
|
|
1945
|
-
tidx: realTidx,
|
|
1946
2329
|
})
|
|
1947
2330
|
|
|
1948
2331
|
expect(result.scanned).toMatchInlineSnapshot(`1`)
|
|
@@ -1954,7 +2337,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1954
2337
|
"miner": "0x0000000000000000000000000000000000000000",
|
|
1955
2338
|
"number": 1000002,
|
|
1956
2339
|
"parentHash": "0xa406dff9c51cc2abbef3a811f0499e99a568bf781019f129ab45dca1df0e43e5",
|
|
1957
|
-
"timestamp": "2026-01-14T18:38:03.
|
|
2340
|
+
"timestamp": "2026-01-14T18:38:03.685Z",
|
|
1958
2341
|
"transactionCount": 52,
|
|
1959
2342
|
}
|
|
1960
2343
|
`)
|
|
@@ -1992,7 +2375,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1992
2375
|
},
|
|
1993
2376
|
eventTypes: ['block:created'],
|
|
1994
2377
|
headBlockNumber: windowHead,
|
|
1995
|
-
tidx: realTidx,
|
|
1996
2378
|
})
|
|
1997
2379
|
|
|
1998
2380
|
expect((await tick()).dispatched).toBe(1)
|
|
@@ -2035,6 +2417,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2035
2417
|
await Core.setCursor(db, healthy.id, windowCursor)
|
|
2036
2418
|
|
|
2037
2419
|
const dispatched: Webhooks.poll.Dispatchable[] = []
|
|
2420
|
+
const subscriptions: number[] = []
|
|
2038
2421
|
|
|
2039
2422
|
await expect(
|
|
2040
2423
|
Webhooks.poll({
|
|
@@ -2048,11 +2431,14 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2048
2431
|
client,
|
|
2049
2432
|
db,
|
|
2050
2433
|
maxBlocks: 3,
|
|
2051
|
-
|
|
2434
|
+
onSubscriptions(count) {
|
|
2435
|
+
subscriptions.push(count)
|
|
2436
|
+
},
|
|
2052
2437
|
}),
|
|
2053
2438
|
).rejects.toThrow()
|
|
2054
2439
|
|
|
2055
2440
|
expect(await Core.getCursor(db, unavailable.id)).toBe(cursor)
|
|
2441
|
+
expect(subscriptions).toEqual([2])
|
|
2056
2442
|
expect(decodeCursor(await Core.getCursor(db, healthy.id))).toMatchInlineSnapshot(`
|
|
2057
2443
|
[
|
|
2058
2444
|
1000002,
|
|
@@ -2090,7 +2476,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2090
2476
|
headBlockNumber: windowTo + 3,
|
|
2091
2477
|
client,
|
|
2092
2478
|
db,
|
|
2093
|
-
tidx: realTidx,
|
|
2094
2479
|
})
|
|
2095
2480
|
|
|
2096
2481
|
expect(result.scanned).toMatchInlineSnapshot(`1`)
|
|
@@ -2112,6 +2497,28 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2112
2497
|
])
|
|
2113
2498
|
})
|
|
2114
2499
|
|
|
2500
|
+
test('disables stored subscriptions with removed receipt filters', async () => {
|
|
2501
|
+
const db = TestApp.database()
|
|
2502
|
+
const subscription = await Core.createSubscription(db, {
|
|
2503
|
+
chainId: 42431,
|
|
2504
|
+
destination: { type: 'url', url: 'https://hooks.example.com/legacy' },
|
|
2505
|
+
eventType: 'transaction:included',
|
|
2506
|
+
filters: { includeReceipt: true },
|
|
2507
|
+
owner: { orgId: 'org_legacy', type: 'api_key' },
|
|
2508
|
+
})
|
|
2509
|
+
|
|
2510
|
+
await Webhooks.poll({
|
|
2511
|
+
chainId: 42431,
|
|
2512
|
+
client,
|
|
2513
|
+
db,
|
|
2514
|
+
eventTypes: ['transaction:included'],
|
|
2515
|
+
headBlockNumber: windowHead,
|
|
2516
|
+
})
|
|
2517
|
+
|
|
2518
|
+
const disabled = await Core.getSubscription(db, subscription.owner, subscription.id)
|
|
2519
|
+
expect(disabled?.status).toBe('disabled')
|
|
2520
|
+
})
|
|
2521
|
+
|
|
2115
2522
|
test('transaction:included limits a tick and resumes within the block', async () => {
|
|
2116
2523
|
const db = TestApp.database()
|
|
2117
2524
|
const subscription = await Core.createSubscription(db, {
|
|
@@ -2136,7 +2543,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2136
2543
|
},
|
|
2137
2544
|
eventTypes: ['transaction:included'],
|
|
2138
2545
|
headBlockNumber: windowHead,
|
|
2139
|
-
tidx: realTidx,
|
|
2140
2546
|
})
|
|
2141
2547
|
|
|
2142
2548
|
expect((await tick()).dispatched).toBe(1)
|
|
@@ -2168,7 +2574,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2168
2574
|
headBlockNumber: missingBlock + 2,
|
|
2169
2575
|
client,
|
|
2170
2576
|
db,
|
|
2171
|
-
tidx: realTidx,
|
|
2172
2577
|
}),
|
|
2173
2578
|
).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
2174
2579
|
[Webhooks.BlockUnavailableError: Block 4000000000 is unavailable.]
|
|
@@ -2199,7 +2604,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2199
2604
|
headBlockNumber: windowHead,
|
|
2200
2605
|
client,
|
|
2201
2606
|
db,
|
|
2202
|
-
tidx: realTidx,
|
|
2203
2607
|
})
|
|
2204
2608
|
|
|
2205
2609
|
expect(result.scanned).toMatchInlineSnapshot(`1`)
|
|
@@ -2209,78 +2613,10 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2209
2613
|
})
|
|
2210
2614
|
})
|
|
2211
2615
|
|
|
2212
|
-
test('transaction:included routes a status filter through receipts', async () => {
|
|
2213
|
-
const db = TestApp.database()
|
|
2214
|
-
const subscription = await Core.createSubscription(db, {
|
|
2215
|
-
chainId: 42431,
|
|
2216
|
-
eventType: 'transaction:included',
|
|
2217
|
-
filters: { from: windowSender, status: 'success' },
|
|
2218
|
-
owner: { orgId: 'org_test', type: 'api_key' },
|
|
2219
|
-
destination: { type: 'url', url: 'https://hooks.example.com/x' },
|
|
2220
|
-
})
|
|
2221
|
-
await Core.setCursor(db, subscription.id, windowCursor)
|
|
2222
|
-
|
|
2223
|
-
const result = await Webhooks.poll({
|
|
2224
|
-
chainId: 42431,
|
|
2225
|
-
dispatch: async () => ({ ok: true }),
|
|
2226
|
-
eventTypes: ['transaction:included'],
|
|
2227
|
-
headBlockNumber: windowHead,
|
|
2228
|
-
client,
|
|
2229
|
-
db,
|
|
2230
|
-
tidx: realTidx,
|
|
2231
|
-
})
|
|
2232
|
-
|
|
2233
|
-
// The window tx succeeded, so the status=success subquery keeps it.
|
|
2234
|
-
expect(result.scanned).toMatchInlineSnapshot(`1`)
|
|
2235
|
-
})
|
|
2236
|
-
|
|
2237
|
-
test('transaction:included embeds the receipt when includeReceipt is set', async () => {
|
|
2238
|
-
const db = TestApp.database()
|
|
2239
|
-
const subscription = await Core.createSubscription(db, {
|
|
2240
|
-
chainId: 42431,
|
|
2241
|
-
eventType: 'transaction:included',
|
|
2242
|
-
// No receipt-touching filter — `includeReceipt` alone must trigger the
|
|
2243
|
-
// fetch and embed it in the delivered payload.
|
|
2244
|
-
filters: { from: windowSender, input: { selector: '0xa9059cbb' }, includeReceipt: true },
|
|
2245
|
-
owner: { orgId: 'org_test', type: 'api_key' },
|
|
2246
|
-
destination: { type: 'url', url: 'https://hooks.example.com/x' },
|
|
2247
|
-
})
|
|
2248
|
-
await Core.setCursor(db, subscription.id, windowCursor)
|
|
2249
|
-
|
|
2250
|
-
const dispatched: Webhooks.poll.Dispatchable[] = []
|
|
2251
|
-
const result = await Webhooks.poll({
|
|
2252
|
-
chainId: 42431,
|
|
2253
|
-
dispatch: async (input) => {
|
|
2254
|
-
dispatched.push(input)
|
|
2255
|
-
return { ok: true }
|
|
2256
|
-
},
|
|
2257
|
-
eventTypes: ['transaction:included'],
|
|
2258
|
-
headBlockNumber: windowHead,
|
|
2259
|
-
client,
|
|
2260
|
-
db,
|
|
2261
|
-
tidx: realTidx,
|
|
2262
|
-
})
|
|
2263
|
-
|
|
2264
|
-
expect(result.scanned).toMatchInlineSnapshot(`1`)
|
|
2265
|
-
const data = dispatched[0]?.envelope.data as TransactionEvent | undefined
|
|
2266
|
-
// The receipt is embedded under `meta.receipt`, matching the read endpoint's
|
|
2267
|
-
// `include=receipt` shape, and refers to the same transaction.
|
|
2268
|
-
expect(data?.meta?.receipt?.status).toMatchInlineSnapshot(`"success"`)
|
|
2269
|
-
expect(data?.meta?.receipt?.transactionHash).toBe(data?.hash)
|
|
2270
|
-
const receipt = data?.meta.receipt
|
|
2271
|
-
if (receipt?.meta.rpc.feeToken) {
|
|
2272
|
-
expect(receipt.feeToken?.address).toBe(receipt.meta.rpc.feeToken)
|
|
2273
|
-
expect(typeof receipt.feeToken?.currency).toBe('string')
|
|
2274
|
-
expect(typeof receipt.feeToken?.decimals).toBe('number')
|
|
2275
|
-
expect(typeof receipt.feeToken?.name).toBe('string')
|
|
2276
|
-
expect(typeof receipt.feeToken?.symbol).toBe('string')
|
|
2277
|
-
} else expect(receipt?.feeToken).toBeUndefined()
|
|
2278
|
-
})
|
|
2279
|
-
|
|
2280
2616
|
// End-to-end through the *default* transport: `poll` with no injected
|
|
2281
2617
|
// `dispatch` falls back to `deliverAndRecord`, which signs and POSTs via
|
|
2282
|
-
// `globalThis.fetch` and records the outcome. These
|
|
2283
|
-
//
|
|
2618
|
+
// `globalThis.fetch` and records the outcome. These intercept the delivery
|
|
2619
|
+
// request while real RPC reads pass through.
|
|
2284
2620
|
describe('default transport (deliverAndRecord)', () => {
|
|
2285
2621
|
afterEach(() => {
|
|
2286
2622
|
vi.restoreAllMocks()
|
|
@@ -2291,9 +2627,8 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2291
2627
|
const subscription = await createTransferSubscription(db)
|
|
2292
2628
|
await Core.setCursor(db, subscription.id, windowCursor)
|
|
2293
2629
|
|
|
2294
|
-
//
|
|
2295
|
-
// non-webhook requests through
|
|
2296
|
-
// delivery POST to the subscription URL.
|
|
2630
|
+
// RPC queries also go through `globalThis.fetch`, so the interceptor passes
|
|
2631
|
+
// non-webhook requests through and captures only the delivery POST.
|
|
2297
2632
|
const realFetch = globalThis.fetch.bind(globalThis)
|
|
2298
2633
|
const calls: { body: string; headers: Headers; url: string }[] = []
|
|
2299
2634
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {
|
|
@@ -2317,7 +2652,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2317
2652
|
headBlockNumber: windowHead,
|
|
2318
2653
|
client,
|
|
2319
2654
|
db,
|
|
2320
|
-
tidx: realTidx,
|
|
2321
2655
|
})
|
|
2322
2656
|
|
|
2323
2657
|
expect(result.dispatched).toBe(1)
|
|
@@ -2369,7 +2703,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2369
2703
|
headBlockNumber: windowHead,
|
|
2370
2704
|
client,
|
|
2371
2705
|
db,
|
|
2372
|
-
tidx: realTidx,
|
|
2373
2706
|
})
|
|
2374
2707
|
|
|
2375
2708
|
const first = await tick()
|
|
@@ -2393,7 +2726,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2393
2726
|
const subscription = await createTransferSubscription(db)
|
|
2394
2727
|
await Core.setCursor(db, subscription.id, windowCursor)
|
|
2395
2728
|
|
|
2396
|
-
// Real
|
|
2729
|
+
// Real RPC queries pass through; only the webhook delivery fails (500).
|
|
2397
2730
|
const realFetch = globalThis.fetch.bind(globalThis)
|
|
2398
2731
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {
|
|
2399
2732
|
const url =
|
|
@@ -2410,7 +2743,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2410
2743
|
headBlockNumber: windowHead,
|
|
2411
2744
|
client,
|
|
2412
2745
|
db,
|
|
2413
|
-
tidx: realTidx,
|
|
2414
2746
|
})
|
|
2415
2747
|
|
|
2416
2748
|
// Each tick delivers once and fails; after `maxFailures` ticks the
|
|
@@ -2428,7 +2760,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2428
2760
|
})
|
|
2429
2761
|
|
|
2430
2762
|
// Full loop through the public HTTP surface: create over the route, let the
|
|
2431
|
-
// poller detect
|
|
2763
|
+
// poller detect an RPC transfer log and deliver via the default transport, then
|
|
2432
2764
|
// read the recorded outcome back through the deliveries route. Unlike the
|
|
2433
2765
|
// `poll` tests above (which seed subscriptions via the core helper), this
|
|
2434
2766
|
// proves the route-issued secret and id line up with what is delivered/logged.
|
|
@@ -2467,7 +2799,7 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2467
2799
|
return new Response(null, { status: 200 })
|
|
2468
2800
|
}) satisfies typeof globalThis.fetch
|
|
2469
2801
|
|
|
2470
|
-
// 3. Poll a real
|
|
2802
|
+
// 3. Poll a real RPC transfer log; dispatch through the real
|
|
2471
2803
|
// sign+deliver+record core with the injected transport. `batchSize: 1`
|
|
2472
2804
|
// delivers exactly one envelope from the window.
|
|
2473
2805
|
const result = await Webhooks.poll({
|
|
@@ -2479,7 +2811,6 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
2479
2811
|
headBlockNumber: windowHead,
|
|
2480
2812
|
client,
|
|
2481
2813
|
db,
|
|
2482
|
-
tidx: realTidx,
|
|
2483
2814
|
})
|
|
2484
2815
|
expect(result.dispatched).toBe(1)
|
|
2485
2816
|
|