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
|
@@ -60,6 +60,8 @@ describe('GET /transactions/:transactionHash/receipt', () => {
|
|
|
60
60
|
expect(body.feeAmount.currency).toBe('USD')
|
|
61
61
|
expect(body.feeAmount.decimals).toBe(6)
|
|
62
62
|
expect(body.feeAmount.formatted).toMatch(/^\d+(\.\d+)?$/)
|
|
63
|
+
expect('valuation' in body.feeAmount).toBe(false)
|
|
64
|
+
expect(response.headers.get('server-timing')).not.toContain('valuation_rates;dur=')
|
|
63
65
|
// Core fee-token metadata is present by default while the raw address stays
|
|
64
66
|
// under `meta.rpc`. Curated fields remain opt-in.
|
|
65
67
|
if (body.meta.rpc.feeToken) {
|
|
@@ -272,6 +274,27 @@ describe('GET /transactions/receipts', () => {
|
|
|
272
274
|
for (const receipt of body.data) expect(receipt.status).toBe('success')
|
|
273
275
|
})
|
|
274
276
|
|
|
277
|
+
test('narrows receipts by fee token', async () => {
|
|
278
|
+
const client = TestApp.client()
|
|
279
|
+
const response = await client.v1.transactions.receipts.$get(
|
|
280
|
+
{
|
|
281
|
+
query: {
|
|
282
|
+
feeToken: TestApp.tokenWithHolders,
|
|
283
|
+
include: 'totalCount',
|
|
284
|
+
limit: '5',
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
TestApp.auth,
|
|
288
|
+
)
|
|
289
|
+
const body = await TestApp.json(response, Receipts.schema.getReceipts.Response)
|
|
290
|
+
expect(response.status).toBe(200)
|
|
291
|
+
expect(body.data.length).toBeGreaterThan(0)
|
|
292
|
+
expect(body.meta).toBeDefined()
|
|
293
|
+
expect(body.meta!.totalCount).toBeGreaterThanOrEqual(body.data.length)
|
|
294
|
+
for (const receipt of body.data)
|
|
295
|
+
expect(receipt.meta.rpc.feeToken).toBe(TestApp.tokenWithHolders)
|
|
296
|
+
})
|
|
297
|
+
|
|
275
298
|
test('embeds a capped total count on demand via include=totalCount', async () => {
|
|
276
299
|
const client = TestApp.client()
|
|
277
300
|
|
|
@@ -294,13 +317,7 @@ describe('GET /transactions/receipts', () => {
|
|
|
294
317
|
expect(body.meta!.totalCount).toBeGreaterThanOrEqual(body.data.length)
|
|
295
318
|
expect(body.meta!.totalCount).toBeLessThanOrEqual(Schema.countCap)
|
|
296
319
|
expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount! >= Schema.countCap)
|
|
297
|
-
|
|
298
|
-
// no rate provenance.
|
|
299
|
-
for (const receipt of body.data)
|
|
300
|
-
expect(receipt.feeAmount.valuation).toEqual({
|
|
301
|
-
amount: receipt.feeAmount.formatted,
|
|
302
|
-
currency: 'USD',
|
|
303
|
-
})
|
|
320
|
+
for (const receipt of body.data) expect('valuation' in receipt.feeAmount).toBe(false)
|
|
304
321
|
expect(body.meta!.valuation).toBeUndefined()
|
|
305
322
|
|
|
306
323
|
// Without `include=totalCount`, the same page omits `meta` and the count.
|
|
@@ -553,22 +553,28 @@ export function receipts(options: receipts.Options = {}) {
|
|
|
553
553
|
|
|
554
554
|
// Value each fee via its own currency tag, in the requested
|
|
555
555
|
// denomination.
|
|
556
|
-
const denomination = query['valuation.currency']
|
|
557
|
-
const rates =
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
556
|
+
const denomination = query['valuation.currency']
|
|
557
|
+
const rates = denomination
|
|
558
|
+
? await Valuation.ratesFor(c, {
|
|
559
|
+
currencies: receipts.data.map((receipt) => receipt.feeAmount.currency),
|
|
560
|
+
denomination,
|
|
561
|
+
oracle,
|
|
562
|
+
})
|
|
563
|
+
: undefined
|
|
562
564
|
const data = receipts.data.map((receipt) => ({
|
|
563
565
|
...receipt,
|
|
564
566
|
feeAmount: {
|
|
565
567
|
...receipt.feeAmount,
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
568
|
+
...(denomination
|
|
569
|
+
? {
|
|
570
|
+
valuation: Valuation.valuationFor({
|
|
571
|
+
amount: BigInt(receipt.feeAmount.baseUnits),
|
|
572
|
+
denomination,
|
|
573
|
+
rates,
|
|
574
|
+
token: receipt.feeAmount,
|
|
575
|
+
}),
|
|
576
|
+
}
|
|
577
|
+
: {}),
|
|
572
578
|
},
|
|
573
579
|
}))
|
|
574
580
|
|
|
@@ -680,22 +686,28 @@ export function receipts(options: receipts.Options = {}) {
|
|
|
680
686
|
|
|
681
687
|
// Value the fee via its own currency tag, in the requested
|
|
682
688
|
// denomination.
|
|
683
|
-
const denomination = query['valuation.currency']
|
|
684
|
-
const rates =
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
+
const denomination = query['valuation.currency']
|
|
690
|
+
const rates = denomination
|
|
691
|
+
? await Valuation.ratesFor(c, {
|
|
692
|
+
currencies: [enriched.feeAmount.currency],
|
|
693
|
+
denomination,
|
|
694
|
+
oracle,
|
|
695
|
+
})
|
|
696
|
+
: undefined
|
|
689
697
|
const valued = {
|
|
690
698
|
...enriched,
|
|
691
699
|
feeAmount: {
|
|
692
700
|
...enriched.feeAmount,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
701
|
+
...(denomination
|
|
702
|
+
? {
|
|
703
|
+
valuation: Valuation.valuationFor({
|
|
704
|
+
amount: BigInt(enriched.feeAmount.baseUnits),
|
|
705
|
+
denomination,
|
|
706
|
+
rates,
|
|
707
|
+
token: enriched.feeAmount,
|
|
708
|
+
}),
|
|
709
|
+
}
|
|
710
|
+
: {}),
|
|
699
711
|
},
|
|
700
712
|
meta: {
|
|
701
713
|
...enriched.meta,
|
|
@@ -771,9 +783,9 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
771
783
|
return Timing.time(c, 'receipts', () =>
|
|
772
784
|
Store.memoize(
|
|
773
785
|
async () => {
|
|
774
|
-
// Page the `receipts` table alone
|
|
775
|
-
//
|
|
776
|
-
//
|
|
786
|
+
// Page the `receipts` table alone. Fee-token queries read metadata
|
|
787
|
+
// denormalized on ClickHouse; other queries hydrate it from `txs`.
|
|
788
|
+
// Paging straight off `receipts` avoids the
|
|
777
789
|
// `receipts JOIN txs` shape, which TIDX's planner cannot execute under
|
|
778
790
|
// address scope: a `UNION` of two JOINs (or the `(from = X OR to = X)`
|
|
779
791
|
// form) reliably returns `db error` (HTTP 422) for high-traffic
|
|
@@ -785,10 +797,9 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
785
797
|
if (sender !== undefined) filters.push(`"from" = '${sender}'`)
|
|
786
798
|
if (recipient !== undefined) filters.push(`"to" = '${recipient}'`)
|
|
787
799
|
if (feePayer !== undefined) filters.push(`fee_payer = '${feePayer}'`)
|
|
788
|
-
//
|
|
789
|
-
//
|
|
790
|
-
if (feeToken !== undefined)
|
|
791
|
-
filters.push(`tx_hash IN (SELECT hash FROM txs WHERE fee_token = '${feeToken}')`)
|
|
800
|
+
// ClickHouse receipts denormalize fee metadata specifically to avoid
|
|
801
|
+
// an unbounded transaction-hash semi-join for this filter.
|
|
802
|
+
if (feeToken !== undefined) filters.push(`fee_token = '${feeToken}'`)
|
|
792
803
|
if (fromBlock !== undefined) filters.push(`block_num >= ${fromBlock}`)
|
|
793
804
|
if (toBlock !== undefined) filters.push(`block_num <= ${toBlock}`)
|
|
794
805
|
if (fromTimestamp !== undefined) filters.push(`block_timestamp >= '${fromTimestamp}'`)
|
|
@@ -804,7 +815,7 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
804
815
|
|
|
805
816
|
const columns = `block_num, block_timestamp, tx_idx, tx_hash, "from", "to",
|
|
806
817
|
contract_address, gas_used, cumulative_gas_used, effective_gas_price,
|
|
807
|
-
status, fee_payer`
|
|
818
|
+
status, fee_payer${feeToken === undefined ? '' : ', type, fee_token'}`
|
|
808
819
|
|
|
809
820
|
// For address-scoped queries, run one branch per side and `UNION` so
|
|
810
821
|
// each branch returns at most `limit + 1` index-ordered rows, the outer
|
|
@@ -814,14 +825,15 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
814
825
|
//
|
|
815
826
|
// The `receipts` table only indexes `from`, not `to` (a `to` scan over
|
|
816
827
|
// ~25M rows hits the indexer's statement timeout and returns `db error`,
|
|
817
|
-
// HTTP 422).
|
|
818
|
-
//
|
|
819
|
-
// `to`
|
|
828
|
+
// HTTP 422). A fee-payer filter bounds the direct `to` scan through the
|
|
829
|
+
// receipt fee-payer index. Other `to` branches resolve matching hashes
|
|
830
|
+
// through the indexed `txs.to` and `receipts.tx_hash` columns.
|
|
820
831
|
const sideQuery = (side: 'from' | 'to', addr: string) => {
|
|
821
|
-
const predicate =
|
|
822
|
-
side === 'from'
|
|
823
|
-
|
|
824
|
-
|
|
832
|
+
const predicate = (() => {
|
|
833
|
+
if (side === 'from') return `"from" = '${addr}'`
|
|
834
|
+
if (feePayer !== undefined) return `"to" = '${addr}'`
|
|
835
|
+
return `tx_hash IN (SELECT hash FROM txs WHERE "to" = '${addr}')`
|
|
836
|
+
})()
|
|
825
837
|
const where = [predicate, ...filters].join(' AND ')
|
|
826
838
|
// In the offset lane each branch over-fetches the full window
|
|
827
839
|
// (`offset + limit + 1` rows) because the skip applies to the merged
|
|
@@ -847,7 +859,11 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
847
859
|
LIMIT ${limit + 1}${skip}
|
|
848
860
|
`
|
|
849
861
|
|
|
850
|
-
const result = await tidx.fetch({
|
|
862
|
+
const result = await tidx.fetch({
|
|
863
|
+
chainId,
|
|
864
|
+
...(feeToken !== undefined ? { engine: 'clickhouse' } : {}),
|
|
865
|
+
query: query as string,
|
|
866
|
+
})
|
|
851
867
|
|
|
852
868
|
// The next page anchors below the last fetched row's `(block, tx_idx)`.
|
|
853
869
|
const page = Cursor.paginate({
|
|
@@ -860,11 +876,8 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
860
876
|
},
|
|
861
877
|
})
|
|
862
878
|
|
|
863
|
-
// Enrich
|
|
864
|
-
//
|
|
865
|
-
// sorted by `(block_num, idx)` with no hash index, so the hash list
|
|
866
|
-
// must be paired with the page's block numbers — a bare `hash IN`
|
|
867
|
-
// full-scans the table.
|
|
879
|
+
// Enrich non-fee-token pages with metadata from `txs`. Pairing hashes
|
|
880
|
+
// with their blocks avoids a full-table hash scan.
|
|
868
881
|
const hashes = [
|
|
869
882
|
...new Set(
|
|
870
883
|
page.rows
|
|
@@ -880,7 +893,7 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
880
893
|
),
|
|
881
894
|
]
|
|
882
895
|
const txByHash = new Map<string, { feeToken: unknown; type: unknown }>()
|
|
883
|
-
if (hashes.length > 0 && blocks.length > 0) {
|
|
896
|
+
if (feeToken === undefined && hashes.length > 0 && blocks.length > 0) {
|
|
884
897
|
const txs = await tidx.fetch({
|
|
885
898
|
chainId,
|
|
886
899
|
query: `SELECT hash, type, fee_token FROM txs WHERE block_num IN (${blocks.join(
|
|
@@ -900,7 +913,8 @@ export function getReceipts(c: Context<App.Environment>, options: getReceipts.Op
|
|
|
900
913
|
const data: humanizeReceipt.Output[] = []
|
|
901
914
|
for (const row of page.rows) {
|
|
902
915
|
const tx = typeof row['tx_hash'] === 'string' ? txByHash.get(row['tx_hash']) : undefined
|
|
903
|
-
const merged =
|
|
916
|
+
const merged =
|
|
917
|
+
feeToken === undefined ? { ...row, fee_token: tx?.feeToken, type: tx?.type } : row
|
|
904
918
|
const rpc = schema.Rpc.Receipt.safeParse(toRpcReceipt(merged))
|
|
905
919
|
if (rpc.success) data.push(humanizeReceipt(rpc.data))
|
|
906
920
|
}
|
|
@@ -950,8 +964,7 @@ function countFilters(options: countReceipts.Options): string[] {
|
|
|
950
964
|
if (options.sender !== undefined) filters.push(`"from" = '${options.sender}'`)
|
|
951
965
|
if (options.recipient !== undefined) filters.push(`"to" = '${options.recipient}'`)
|
|
952
966
|
if (options.feePayer !== undefined) filters.push(`fee_payer = '${options.feePayer}'`)
|
|
953
|
-
if (options.feeToken !== undefined)
|
|
954
|
-
filters.push(`tx_hash IN (SELECT hash FROM txs WHERE fee_token = '${options.feeToken}')`)
|
|
967
|
+
if (options.feeToken !== undefined) filters.push(`fee_token = '${options.feeToken}'`)
|
|
955
968
|
if (options.fromBlock !== undefined) filters.push(`block_num >= ${options.fromBlock}`)
|
|
956
969
|
if (options.toBlock !== undefined) filters.push(`block_num <= ${options.toBlock}`)
|
|
957
970
|
if (options.fromTimestamp !== undefined)
|
|
@@ -1003,7 +1016,11 @@ export function countReceipts(
|
|
|
1003
1016
|
) AS capped
|
|
1004
1017
|
`
|
|
1005
1018
|
}
|
|
1006
|
-
const result = await tidx.fetch({
|
|
1019
|
+
const result = await tidx.fetch({
|
|
1020
|
+
chainId,
|
|
1021
|
+
...(options.feeToken !== undefined ? { engine: 'clickhouse' } : {}),
|
|
1022
|
+
query: query as string,
|
|
1023
|
+
})
|
|
1007
1024
|
const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0
|
|
1008
1025
|
return { totalCountCapped: totalCount >= Schema.countCap, totalCount }
|
|
1009
1026
|
},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as QueryBench from '../../../../test/QueryBench.js'
|
|
2
|
+
|
|
3
|
+
QueryBench.bench({
|
|
4
|
+
body: {
|
|
5
|
+
id: 1,
|
|
6
|
+
jsonrpc: '2.0',
|
|
7
|
+
method: 'eth_blockNumber',
|
|
8
|
+
params: [],
|
|
9
|
+
},
|
|
10
|
+
chainId: false,
|
|
11
|
+
method: 'POST',
|
|
12
|
+
name: 'rpcRequest',
|
|
13
|
+
path: { 4217: '/rpc/mainnet', 42431: '/rpc/testnet' },
|
|
14
|
+
})
|
|
@@ -53,6 +53,30 @@ function zoneToken() {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
describe('POST /rpc', () => {
|
|
56
|
+
test('allows 20 public requests per second', async () => {
|
|
57
|
+
vi.useFakeTimers({ now: new Date('2026-08-12T00:00:00.000Z') })
|
|
58
|
+
try {
|
|
59
|
+
const app = TestApp.create()
|
|
60
|
+
const request = () =>
|
|
61
|
+
app.request('/rpc', {
|
|
62
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'tempo_blockNumber' }),
|
|
63
|
+
method: 'POST',
|
|
64
|
+
})
|
|
65
|
+
const responses = await Promise.all(
|
|
66
|
+
Array.from({ length: 21 }, () => Promise.resolve(request())),
|
|
67
|
+
)
|
|
68
|
+
const limited = responses.find((response) => response.status === 429)!
|
|
69
|
+
|
|
70
|
+
expect(responses.filter((response) => response.status === 401)).toHaveLength(20)
|
|
71
|
+
expect(limited.headers.get('RateLimit-Limit')).toBe('20')
|
|
72
|
+
|
|
73
|
+
vi.advanceTimersByTime(1_000)
|
|
74
|
+
expect((await Promise.resolve(request())).status).toBe(401)
|
|
75
|
+
} finally {
|
|
76
|
+
vi.useRealTimers()
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
56
80
|
test('routes by principal: a resolver sees the request principal and routes accordingly', async () => {
|
|
57
81
|
const fetch = mockRpc()
|
|
58
82
|
// A resolver that routes API-key principals to the private upstream and
|
|
@@ -113,6 +137,7 @@ describe('POST /rpc', () => {
|
|
|
113
137
|
'content-type': 'application/json',
|
|
114
138
|
cookie: 'session=secret',
|
|
115
139
|
'tempo-api-key': 'caller-key',
|
|
140
|
+
'x-api-key': 'legacy-caller-key',
|
|
116
141
|
'x-authorization-token': 'zone-secret',
|
|
117
142
|
'x-client-trace': 'trace_test',
|
|
118
143
|
},
|
|
@@ -139,6 +164,7 @@ describe('POST /rpc', () => {
|
|
|
139
164
|
expect(call.headers['authorization']).toMatchInlineSnapshot(`"Basic cnBjOnNlY3JldA=="`)
|
|
140
165
|
expect(call.headers['cookie']).toMatchInlineSnapshot(`null`)
|
|
141
166
|
expect(call.headers['tempo-api-key']).toMatchInlineSnapshot(`null`)
|
|
167
|
+
expect(call.headers['x-api-key']).toBeNull()
|
|
142
168
|
expect(call.headers['x-authorization-token']).toBeNull()
|
|
143
169
|
expect(call.headers['x-client-trace']).toMatchInlineSnapshot(`"trace_test"`)
|
|
144
170
|
} finally {
|
|
@@ -1156,6 +1182,7 @@ function mockRpc() {
|
|
|
1156
1182
|
authorization: request.headers.get('authorization'),
|
|
1157
1183
|
cookie: request.headers.get('cookie'),
|
|
1158
1184
|
'tempo-api-key': request.headers.get('tempo-api-key'),
|
|
1185
|
+
'x-api-key': request.headers.get('x-api-key'),
|
|
1159
1186
|
'x-authorization-token': request.headers.get('x-authorization-token'),
|
|
1160
1187
|
'x-client-trace': request.headers.get('x-client-trace'),
|
|
1161
1188
|
},
|
|
@@ -26,6 +26,7 @@ const strippedHeaders = new Set([
|
|
|
26
26
|
'trailer',
|
|
27
27
|
'transfer-encoding',
|
|
28
28
|
'upgrade',
|
|
29
|
+
'x-api-key',
|
|
29
30
|
ZoneRpcAuthentication.headerName.toLowerCase(),
|
|
30
31
|
])
|
|
31
32
|
|
|
@@ -163,7 +164,10 @@ export namespace schema {
|
|
|
163
164
|
export function rpc() {
|
|
164
165
|
return new Hono<App.Environment>().post(
|
|
165
166
|
'/rpc/:chain{(mainnet|testnet|[0-9]+)}?',
|
|
166
|
-
Auth.policy({
|
|
167
|
+
Auth.policy({
|
|
168
|
+
apiKey: { scopes: ['data:read'] },
|
|
169
|
+
public: { rateLimit: { limit: 20, period: 'second' } },
|
|
170
|
+
}),
|
|
167
171
|
OpenApi.describeRoute({
|
|
168
172
|
description:
|
|
169
173
|
'Sends a single or batch Ethereum JSON-RPC request directly to Tempo. Use `/rpc/testnet`, `/rpc/mainnet`, or `/rpc/:chain` to choose a chain. Zone read scopes permit read-only methods; Zone write scopes also permit signed transaction broadcasts. A Zone token in `X-Authorization-Token` is forwarded to the authenticated Zone RPC. This passthrough is not part of the stable API contract and is supported on a best-effort basis only. It has no compatibility, latency, availability, or data-freshness guarantees, and breaking changes may happen with limited notice.',
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as QueryBench from '../../../../test/QueryBench.js'
|
|
2
|
+
|
|
3
|
+
const address = {
|
|
4
|
+
4217: '0xc24357119432595a547cb2c1e41c51d753645519',
|
|
5
|
+
42431: '0x15496d3123afb2cfadc0ac068eadd11d0f8ae799',
|
|
6
|
+
} as const
|
|
7
|
+
|
|
8
|
+
QueryBench.bench({
|
|
9
|
+
name: 'getTokens',
|
|
10
|
+
path: '/v1/tokens',
|
|
11
|
+
queries: [
|
|
12
|
+
`addresses=${QueryBench.pathUsd}`,
|
|
13
|
+
`addresses=${QueryBench.pathUsd}&include=admin,createdAt,holderCount,quoteToken,transferStats`,
|
|
14
|
+
'include=admin',
|
|
15
|
+
'include=createdAt',
|
|
16
|
+
'include=holderCount',
|
|
17
|
+
'include=quoteToken',
|
|
18
|
+
'include=transferStats',
|
|
19
|
+
'verified=true',
|
|
20
|
+
'verified=true&include=admin,createdAt,holderCount,quoteToken,transferStats',
|
|
21
|
+
],
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
QueryBench.bench({
|
|
25
|
+
name: 'getTokenBySymbol',
|
|
26
|
+
path: '/v1/tokens/pathUSD',
|
|
27
|
+
queries: [
|
|
28
|
+
'include=admin',
|
|
29
|
+
'include=createdAt',
|
|
30
|
+
'include=holderCount',
|
|
31
|
+
'include=quoteToken',
|
|
32
|
+
'include=transferStats',
|
|
33
|
+
],
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
QueryBench.bench({
|
|
37
|
+
name: 'getToken',
|
|
38
|
+
path: `/v1/tokens/${QueryBench.pathUsd}`,
|
|
39
|
+
queries: [
|
|
40
|
+
'include=admin',
|
|
41
|
+
'include=createdAt',
|
|
42
|
+
'include=holderCount',
|
|
43
|
+
'include=quoteToken',
|
|
44
|
+
'include=transferStats',
|
|
45
|
+
],
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
QueryBench.bench({
|
|
49
|
+
name: 'getTokenLogo',
|
|
50
|
+
path: `/v1/tokens/${QueryBench.pathUsd}/logo`,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
QueryBench.bench({
|
|
54
|
+
name: 'getTokenHolders',
|
|
55
|
+
path: `/v1/tokens/${QueryBench.pathUsd}/holders`,
|
|
56
|
+
queries: ['include=token', 'include=totalCount'],
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
QueryBench.bench({
|
|
60
|
+
name: 'getTokenTransactions',
|
|
61
|
+
path: `/v1/tokens/${QueryBench.pathUsd}/transactions`,
|
|
62
|
+
queries: [
|
|
63
|
+
'include=feeToken.logoUri',
|
|
64
|
+
'include=feeToken.verified',
|
|
65
|
+
'include=token',
|
|
66
|
+
'include=totalCount',
|
|
67
|
+
],
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
QueryBench.bench({
|
|
71
|
+
name: 'getTokenTransactions?feePayer&include=totalCount',
|
|
72
|
+
path: {
|
|
73
|
+
4217: `/v1/tokens/${QueryBench.pathUsd}/transactions?feePayer=${address[4217]}&include=totalCount`,
|
|
74
|
+
42431: `/v1/tokens/${QueryBench.pathUsd}/transactions?feePayer=${address[42431]}&include=totalCount`,
|
|
75
|
+
},
|
|
76
|
+
})
|
|
@@ -1121,6 +1121,57 @@ describe('GET /tokens/:token/transactions', () => {
|
|
|
1121
1121
|
expect(secondBody.data.every((entry) => !firstHashes.has(entry.hash))).toBe(true)
|
|
1122
1122
|
})
|
|
1123
1123
|
|
|
1124
|
+
test('filters and counts by fee payer through receipts with pagination', async () => {
|
|
1125
|
+
const client = app()
|
|
1126
|
+
const seedResponse = await client.v1.tokens[':token'].transactions.$get(
|
|
1127
|
+
{ param: { token: TestApp.token }, query: { limit: '50' } },
|
|
1128
|
+
TestApp.auth,
|
|
1129
|
+
)
|
|
1130
|
+
const seed = await TestApp.json(seedResponse, Tokens.schema.getTokenTransactions.Response)
|
|
1131
|
+
const feePayers = seed.data.flatMap((transaction) => {
|
|
1132
|
+
const feePayer = transaction.meta.rpc.feePayer
|
|
1133
|
+
return typeof feePayer === 'string' ? [feePayer] : []
|
|
1134
|
+
})
|
|
1135
|
+
const feePayer = feePayers.find(
|
|
1136
|
+
(candidate) => feePayers.filter((value) => value === candidate).length > 5,
|
|
1137
|
+
)
|
|
1138
|
+
if (!feePayer) return
|
|
1139
|
+
|
|
1140
|
+
const first = await client.v1.tokens[':token'].transactions.$get(
|
|
1141
|
+
{
|
|
1142
|
+
param: { token: TestApp.token },
|
|
1143
|
+
query: { feePayer, include: 'totalCount', limit: '5' },
|
|
1144
|
+
},
|
|
1145
|
+
TestApp.auth,
|
|
1146
|
+
)
|
|
1147
|
+
const firstBody = await TestApp.json(first, Tokens.schema.getTokenTransactions.Response)
|
|
1148
|
+
expect(first.status).toBe(200)
|
|
1149
|
+
expect(first.headers.get('server-timing')).toContain('receipts;dur=')
|
|
1150
|
+
expect(first.headers.get('server-timing')).toContain('receipts_count;dur=')
|
|
1151
|
+
expect(firstBody.data).toHaveLength(5)
|
|
1152
|
+
expect(firstBody.meta?.totalCount).toBeGreaterThanOrEqual(firstBody.data.length)
|
|
1153
|
+
expect(firstBody.data.every((transaction) => transaction.meta.rpc.feePayer === feePayer)).toBe(
|
|
1154
|
+
true,
|
|
1155
|
+
)
|
|
1156
|
+
if (!firstBody.nextCursor) return
|
|
1157
|
+
|
|
1158
|
+
const second = await client.v1.tokens[':token'].transactions.$get(
|
|
1159
|
+
{
|
|
1160
|
+
param: { token: TestApp.token },
|
|
1161
|
+
query: { cursor: firstBody.nextCursor, feePayer, limit: '5' },
|
|
1162
|
+
},
|
|
1163
|
+
TestApp.auth,
|
|
1164
|
+
)
|
|
1165
|
+
const secondBody = await TestApp.json(second, Tokens.schema.getTokenTransactions.Response)
|
|
1166
|
+
expect(second.status).toBe(200)
|
|
1167
|
+
expect(secondBody.data.length).toBeGreaterThan(0)
|
|
1168
|
+
expect(secondBody.data.every((transaction) => transaction.meta.rpc.feePayer === feePayer)).toBe(
|
|
1169
|
+
true,
|
|
1170
|
+
)
|
|
1171
|
+
const firstHashes = new Set(firstBody.data.map((transaction) => transaction.hash))
|
|
1172
|
+
expect(secondBody.data.every((transaction) => !firstHashes.has(transaction.hash))).toBe(true)
|
|
1173
|
+
})
|
|
1174
|
+
|
|
1124
1175
|
test('returns 400 for an invalid token address', async () => {
|
|
1125
1176
|
// The typed client requires `token: `Hex.Hex`` so an invalid literal
|
|
1126
1177
|
// cannot pass through it; exercise the validator via `app.request`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as QueryBench from '../../../../test/QueryBench.js'
|
|
2
|
+
|
|
3
|
+
const hash = {
|
|
4
|
+
4217: '0x7f98e4b2bf4d1444b257526f7dd294ac3480d73cd91f723bf0df6062d1ba1c82',
|
|
5
|
+
42431: '0x1c8ccebb169f863b72388cc057fcecee240032eb7d2c64aa6f6fb8e5a60d3f9a',
|
|
6
|
+
} as const
|
|
7
|
+
|
|
8
|
+
const address = {
|
|
9
|
+
4217: '0xc24357119432595a547cb2c1e41c51d753645519',
|
|
10
|
+
42431: '0x15496d3123afb2cfadc0ac068eadd11d0f8ae799',
|
|
11
|
+
} as const
|
|
12
|
+
|
|
13
|
+
QueryBench.bench({
|
|
14
|
+
name: 'getTransactions',
|
|
15
|
+
path: '/v1/transactions',
|
|
16
|
+
queries: [
|
|
17
|
+
'include=feeToken.logoUri',
|
|
18
|
+
'include=feeToken.verified',
|
|
19
|
+
'include=receipt',
|
|
20
|
+
'include=totalCount',
|
|
21
|
+
'status=success&include=totalCount',
|
|
22
|
+
],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
QueryBench.bench({
|
|
26
|
+
name: 'getTransactions?address&include=totalCount',
|
|
27
|
+
path: {
|
|
28
|
+
4217: `/v1/transactions?address=${address[4217]}&include=totalCount`,
|
|
29
|
+
42431: `/v1/transactions?address=${address[42431]}&include=totalCount`,
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
QueryBench.bench({
|
|
34
|
+
name: 'getTransaction',
|
|
35
|
+
path: {
|
|
36
|
+
4217: `/v1/transactions/${hash[4217]}`,
|
|
37
|
+
42431: `/v1/transactions/${hash[42431]}`,
|
|
38
|
+
},
|
|
39
|
+
queries: ['include=feeToken.logoUri', 'include=feeToken.verified', 'include=receipt'],
|
|
40
|
+
})
|
|
@@ -311,6 +311,48 @@ describe('GET /transactions', () => {
|
|
|
311
311
|
`)
|
|
312
312
|
})
|
|
313
313
|
|
|
314
|
+
test('filters and counts by fee payer through receipts with pagination', async () => {
|
|
315
|
+
const client = TestApp.client()
|
|
316
|
+
const seedResponse = await client.v1.transactions.$get({ query: { limit: '50' } }, TestApp.auth)
|
|
317
|
+
const seed = await TestApp.json(seedResponse, Transactions.schema.getTransactions.Response)
|
|
318
|
+
const feePayers = seed.data.flatMap((transaction) => {
|
|
319
|
+
const feePayer = transaction.meta.rpc.feePayer
|
|
320
|
+
return typeof feePayer === 'string' ? [feePayer] : []
|
|
321
|
+
})
|
|
322
|
+
const feePayer = feePayers.find(
|
|
323
|
+
(candidate) => feePayers.filter((value) => value === candidate).length > 5,
|
|
324
|
+
)
|
|
325
|
+
if (!feePayer) return
|
|
326
|
+
|
|
327
|
+
const first = await client.v1.transactions.$get(
|
|
328
|
+
{ query: { feePayer, include: 'totalCount', limit: '5' } },
|
|
329
|
+
TestApp.auth,
|
|
330
|
+
)
|
|
331
|
+
const firstBody = await TestApp.json(first, Transactions.schema.getTransactions.Response)
|
|
332
|
+
expect(first.status).toBe(200)
|
|
333
|
+
expect(first.headers.get('server-timing')).toContain('receipts;dur=')
|
|
334
|
+
expect(first.headers.get('server-timing')).toContain('receipts_count;dur=')
|
|
335
|
+
expect(firstBody.data).toHaveLength(5)
|
|
336
|
+
expect(firstBody.meta?.totalCount).toBeGreaterThanOrEqual(firstBody.data.length)
|
|
337
|
+
expect(firstBody.data.every((transaction) => transaction.meta.rpc.feePayer === feePayer)).toBe(
|
|
338
|
+
true,
|
|
339
|
+
)
|
|
340
|
+
if (!firstBody.nextCursor) return
|
|
341
|
+
|
|
342
|
+
const second = await client.v1.transactions.$get(
|
|
343
|
+
{ query: { cursor: firstBody.nextCursor, feePayer, limit: '5' } },
|
|
344
|
+
TestApp.auth,
|
|
345
|
+
)
|
|
346
|
+
const secondBody = await TestApp.json(second, Transactions.schema.getTransactions.Response)
|
|
347
|
+
expect(second.status).toBe(200)
|
|
348
|
+
expect(secondBody.data.length).toBeGreaterThan(0)
|
|
349
|
+
expect(secondBody.data.every((transaction) => transaction.meta.rpc.feePayer === feePayer)).toBe(
|
|
350
|
+
true,
|
|
351
|
+
)
|
|
352
|
+
const firstHashes = new Set(firstBody.data.map((transaction) => transaction.hash))
|
|
353
|
+
expect(secondBody.data.every((transaction) => !firstHashes.has(transaction.hash))).toBe(true)
|
|
354
|
+
})
|
|
355
|
+
|
|
314
356
|
test('filters reverted transactions by execution status', async () => {
|
|
315
357
|
const client = TestApp.client()
|
|
316
358
|
const response = await client.v1.transactions.$get(
|