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
|
@@ -297,8 +297,8 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
297
297
|
currency: 'USD',
|
|
298
298
|
decimals: 6,
|
|
299
299
|
logoUri: 'https://example.com/pathusd.svg',
|
|
300
|
-
name: 'PathUSD',
|
|
301
|
-
symbol: '
|
|
300
|
+
name: 'Curated PathUSD',
|
|
301
|
+
symbol: 'curatedUSD',
|
|
302
302
|
},
|
|
303
303
|
],
|
|
304
304
|
})
|
|
@@ -321,9 +321,49 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
321
321
|
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
322
322
|
expect(response.status).toBe(200)
|
|
323
323
|
expect(body.data.length).toBeGreaterThan(0)
|
|
324
|
+
expect(response.headers.get('server-timing')).not.toContain('token_logo;dur=')
|
|
325
|
+
expect(response.headers.get('server-timing')).not.toContain('token_metadata;dur=')
|
|
324
326
|
for (const balance of body.data) expect(balance.token.verified).toBe(true)
|
|
325
327
|
const pathUsd = body.data.find((balance) => balance.token.address === TestApp.tokenWithHolders)
|
|
326
|
-
expect(pathUsd?.token
|
|
328
|
+
expect(pathUsd?.token).toMatchObject({
|
|
329
|
+
logoUri: 'https://example.com/pathusd.svg',
|
|
330
|
+
name: 'Curated PathUSD',
|
|
331
|
+
symbol: 'curatedUSD',
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
test('includes uploaded logos for verified tokens without a logo URI', async () => {
|
|
336
|
+
const logoDb = TestApp.database()
|
|
337
|
+
await TestApp.verifiedSeed(logoDb, runtime.chainId, {
|
|
338
|
+
tokens: [
|
|
339
|
+
{
|
|
340
|
+
address: TestApp.tokenWithHolders,
|
|
341
|
+
currency: 'USD',
|
|
342
|
+
decimals: 6,
|
|
343
|
+
name: 'Curated PathUSD',
|
|
344
|
+
symbol: 'curatedUSD',
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
})
|
|
348
|
+
const client = app({ db: logoDb })
|
|
349
|
+
const holder = await holderWithBalance
|
|
350
|
+
|
|
351
|
+
const response = await client.v1.addresses[':address'].balances.$get(
|
|
352
|
+
{
|
|
353
|
+
param: { address: holder },
|
|
354
|
+
query: { limit: '20', verified: 'true' },
|
|
355
|
+
},
|
|
356
|
+
TestApp.auth,
|
|
357
|
+
)
|
|
358
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
359
|
+
const pathUsd = body.data.find((balance) => balance.token.address === TestApp.tokenWithHolders)
|
|
360
|
+
|
|
361
|
+
expect(response.status).toBe(200)
|
|
362
|
+
expect(response.headers.get('server-timing')).toContain('token_logo;dur=')
|
|
363
|
+
expect(response.headers.get('server-timing')).not.toContain('token_metadata;dur=')
|
|
364
|
+
expect(pathUsd?.token.logoUri).toBe(
|
|
365
|
+
`http://localhost/assets/${runtime.chainId}/icons/${TestApp.tokenWithHolders}`,
|
|
366
|
+
)
|
|
327
367
|
})
|
|
328
368
|
|
|
329
369
|
test('narrows balances by currency and retains verification', async () => {
|
|
@@ -360,7 +400,23 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
360
400
|
for (const balance of body.data) expect(typeof balance.feeEligible).toBe('boolean')
|
|
361
401
|
})
|
|
362
402
|
|
|
363
|
-
test('
|
|
403
|
+
test('skips the fee-token lookup when an address has no holdings', async () => {
|
|
404
|
+
const client = app()
|
|
405
|
+
const response = await client.v1.addresses[':address'].balances.$get(
|
|
406
|
+
{
|
|
407
|
+
param: { address: privateKeyToAccount(generatePrivateKey()).address },
|
|
408
|
+
query: {},
|
|
409
|
+
},
|
|
410
|
+
TestApp.auth,
|
|
411
|
+
)
|
|
412
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
413
|
+
|
|
414
|
+
expect(response.status).toBe(200)
|
|
415
|
+
expect(body.data).toEqual([])
|
|
416
|
+
expect(response.headers.get('server-timing')).not.toContain('fee_token_set;dur=')
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
test('omits valuations unless a denomination is requested', async () => {
|
|
364
420
|
await TestApp.verifiedSeed(db, runtime.chainId)
|
|
365
421
|
const client = app()
|
|
366
422
|
const holder = await holderWithBalance
|
|
@@ -371,12 +427,8 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
371
427
|
)
|
|
372
428
|
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
373
429
|
expect(response.status).toBe(200)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// holding itself; unverified holdings are never valued.
|
|
377
|
-
const pathUsd = body.data.find((entry) => entry.token.address === TestApp.tokenWithHolders)
|
|
378
|
-
expect(pathUsd?.valuation).toEqual({ amount: pathUsd!.formatted, currency: 'USD' })
|
|
379
|
-
for (const entry of body.data) if (!entry.token.verified) expect(entry.valuation).toBeNull()
|
|
430
|
+
expect(response.headers.get('server-timing')).not.toContain('valuation_rates;dur=')
|
|
431
|
+
for (const entry of body.data) expect('valuation' in entry).toBe(false)
|
|
380
432
|
})
|
|
381
433
|
|
|
382
434
|
test('values holdings in a requested denomination', async () => {
|
|
@@ -444,6 +496,26 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
444
496
|
expect(stable.error.code).toMatchInlineSnapshot(`"query_invalid"`)
|
|
445
497
|
})
|
|
446
498
|
|
|
499
|
+
test('rejects unsupported denominations before balance RPC failures', async () => {
|
|
500
|
+
const client = app({
|
|
501
|
+
fx: { oracle: fixed },
|
|
502
|
+
rpc: { url: () => 'http://127.0.0.1:1' },
|
|
503
|
+
})
|
|
504
|
+
const holder = await holderWithBalance
|
|
505
|
+
|
|
506
|
+
const response = await client.v1.addresses[':address'].balances.$get(
|
|
507
|
+
{
|
|
508
|
+
param: { address: holder },
|
|
509
|
+
query: { 'valuation.currency': 'JPY' },
|
|
510
|
+
},
|
|
511
|
+
TestApp.auth,
|
|
512
|
+
)
|
|
513
|
+
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
514
|
+
|
|
515
|
+
expect(response.status).toMatchInlineSnapshot(`400`)
|
|
516
|
+
expect(body.error.code).toMatchInlineSnapshot(`"query_invalid"`)
|
|
517
|
+
})
|
|
518
|
+
|
|
447
519
|
test('restricts the page to fee tokens when feeEligible=true', async () => {
|
|
448
520
|
const client = app()
|
|
449
521
|
const holder = await holderWithBalance
|
|
@@ -489,7 +561,44 @@ describe('GET /v1/addresses/:address/balances/:token', () => {
|
|
|
489
561
|
expect(body.token.address).toBe(TestApp.tokenWithHolders)
|
|
490
562
|
expect(body.amount).toMatch(/^\d+$/)
|
|
491
563
|
expect(core_Value.from(body.formatted, body.decimals)).toBe(BigInt(body.amount))
|
|
564
|
+
expect('valuation' in body).toBe(false)
|
|
565
|
+
expect(response.headers.get('server-timing')).not.toContain('valuation_rates;dur=')
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
test('values one balance when a denomination is requested', async () => {
|
|
569
|
+
const client = app()
|
|
570
|
+
const holder = await holderWithBalance
|
|
571
|
+
const response = await client.v1.addresses[':address'].balances[':token'].$get(
|
|
572
|
+
{
|
|
573
|
+
param: { address: holder, token: TestApp.tokenWithHolders },
|
|
574
|
+
query: { 'valuation.currency': 'USD' },
|
|
575
|
+
},
|
|
576
|
+
TestApp.auth,
|
|
577
|
+
)
|
|
578
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalance.Response)
|
|
579
|
+
|
|
580
|
+
expect(response.status).toBe(200)
|
|
492
581
|
expect(body.valuation).toEqual({ amount: body.formatted, currency: 'USD' })
|
|
582
|
+
expect(response.headers.get('server-timing')).toContain('valuation_rates;dur=')
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
test('rejects an unsupported denomination before a balance RPC failure', async () => {
|
|
586
|
+
const client = app({
|
|
587
|
+
fx: { oracle: fixed },
|
|
588
|
+
rpc: { url: () => 'http://127.0.0.1:1' },
|
|
589
|
+
})
|
|
590
|
+
const holder = await holderWithBalance
|
|
591
|
+
const response = await client.v1.addresses[':address'].balances[':token'].$get(
|
|
592
|
+
{
|
|
593
|
+
param: { address: holder, token: TestApp.tokenWithHolders },
|
|
594
|
+
query: { 'valuation.currency': 'JPY' },
|
|
595
|
+
},
|
|
596
|
+
TestApp.auth,
|
|
597
|
+
)
|
|
598
|
+
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
599
|
+
|
|
600
|
+
expect(response.status).toMatchInlineSnapshot(`400`)
|
|
601
|
+
expect(body.error.code).toMatchInlineSnapshot(`"query_invalid"`)
|
|
493
602
|
})
|
|
494
603
|
|
|
495
604
|
test('returns zero for a token the account does not hold', async () => {
|
|
@@ -66,7 +66,12 @@ export namespace schema {
|
|
|
66
66
|
include: Schema.totalCountInclude,
|
|
67
67
|
limit: Schema.Limit,
|
|
68
68
|
page: Schema.Page,
|
|
69
|
-
'valuation.currency': Schema.Denomination
|
|
69
|
+
'valuation.currency': Schema.Denomination.check(
|
|
70
|
+
z.describe(
|
|
71
|
+
'When present, include each holding’s nominal value in this denomination. ' +
|
|
72
|
+
'Case-insensitive and must be priced by the configured FX oracle.',
|
|
73
|
+
),
|
|
74
|
+
),
|
|
70
75
|
verified: z
|
|
71
76
|
.optional(Schema.booleanQuery())
|
|
72
77
|
.check(
|
|
@@ -141,11 +146,11 @@ export namespace schema {
|
|
|
141
146
|
),
|
|
142
147
|
token: Tokens.schema.Token,
|
|
143
148
|
valuation: z
|
|
144
|
-
.nullable(Valuation.schema.Value)
|
|
149
|
+
.optional(z.nullable(Valuation.schema.Value))
|
|
145
150
|
.check(
|
|
146
151
|
z.describe(
|
|
147
|
-
'The holding’s nominal value
|
|
148
|
-
'token is unverified, its display currency has no rate, or rates were unavailable.',
|
|
152
|
+
'The holding’s nominal value when `valuation.currency` is requested, or `null` when ' +
|
|
153
|
+
'the token is unverified, its display currency has no rate, or rates were unavailable.',
|
|
149
154
|
),
|
|
150
155
|
),
|
|
151
156
|
}),
|
|
@@ -198,7 +203,12 @@ export namespace schema {
|
|
|
198
203
|
export const Query = z
|
|
199
204
|
.strictObject({
|
|
200
205
|
chainId: Schema.ChainIdQuery,
|
|
201
|
-
'valuation.currency': Schema.Denomination
|
|
206
|
+
'valuation.currency': Schema.Denomination.check(
|
|
207
|
+
z.describe(
|
|
208
|
+
'When present, include the holding’s nominal value in this denomination. ' +
|
|
209
|
+
'Case-insensitive and must be priced by the configured FX oracle.',
|
|
210
|
+
),
|
|
211
|
+
),
|
|
202
212
|
})
|
|
203
213
|
.check(z.describe('Query parameters for reading one account token balance.'))
|
|
204
214
|
|
|
@@ -229,7 +239,7 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
229
239
|
}),
|
|
230
240
|
OpenApi.describeRoute({
|
|
231
241
|
description:
|
|
232
|
-
'Lists how much of each TIP-20 token an account holds, ordered from largest to smallest balance. Amounts are returned in raw base units and human-readable form
|
|
242
|
+
'Lists how much of each TIP-20 token an account holds, ordered from largest to smallest balance. Amounts are returned in raw base units and human-readable form. Pass `valuation.currency` to include nominal values.',
|
|
233
243
|
operationId: 'getAddressBalances',
|
|
234
244
|
responses: OpenApi.responses({
|
|
235
245
|
errors: { 502: 'Could not read balance or token data from an upstream service.' },
|
|
@@ -269,26 +279,13 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
269
279
|
const verified = query.verified || query.currency !== undefined
|
|
270
280
|
const feeEligible = query.feeEligible ?? false
|
|
271
281
|
|
|
272
|
-
// The fee-token set powers both the per-row `feeEligible` flag (always)
|
|
273
|
-
// and the optional `feeEligible=true` page restriction. It is memoized
|
|
274
|
-
// per chain (a near-constant), so one read feeds the page, the count,
|
|
275
|
-
// and the row enrichment. It is a best-effort enrichment: a lookup
|
|
276
|
-
// failure omits `feeEligible` rather than failing the page — unless the
|
|
277
|
-
// caller explicitly asked to filter by `feeEligible`, where the filter
|
|
278
|
-
// can't be honored, so the failure surfaces as the declared `502`.
|
|
279
|
-
const feeTokens = await FeeAmm.feeTokenSet(c, chainId).catch((cause) => {
|
|
280
|
-
if (feeEligible) throw cause
|
|
281
|
-
return undefined
|
|
282
|
-
})
|
|
283
|
-
|
|
284
282
|
const balances = await getAddressBalances(c, {
|
|
285
283
|
address,
|
|
286
284
|
chainId,
|
|
287
285
|
currency: query.currency,
|
|
288
286
|
cursor: query.cursor,
|
|
289
|
-
denomination: query['valuation.currency']
|
|
287
|
+
denomination: query['valuation.currency'],
|
|
290
288
|
feeEligible,
|
|
291
|
-
feeTokens,
|
|
292
289
|
includeTotalCount: query.include.includes('totalCount'),
|
|
293
290
|
limit: query.limit,
|
|
294
291
|
oracle,
|
|
@@ -356,29 +353,43 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
356
353
|
const { address, token } = c.req.valid('param')
|
|
357
354
|
const query = c.req.valid('query')
|
|
358
355
|
const chainId = query.chainId ?? c.get('chainId')
|
|
356
|
+
const denomination = query['valuation.currency']
|
|
359
357
|
|
|
360
358
|
try {
|
|
361
|
-
const
|
|
362
|
-
|
|
359
|
+
const rates = denomination
|
|
360
|
+
? await Valuation.rateSet(c, oracle)
|
|
361
|
+
.then((rates) => {
|
|
362
|
+
if (denomination !== rates.base && rates.rates[denomination] === undefined)
|
|
363
|
+
throw new Valuation.UnsupportedDenominationError(denomination, oracle.name)
|
|
364
|
+
return rates
|
|
365
|
+
})
|
|
366
|
+
.catch((cause) => {
|
|
367
|
+
if (cause instanceof Valuation.UnsupportedDenominationError) throw cause
|
|
368
|
+
return undefined
|
|
369
|
+
})
|
|
370
|
+
: undefined
|
|
371
|
+
const snapshotPromise = VerifiedTokens.snapshot(c, chainId)
|
|
372
|
+
const resolvedTokenPromise = snapshotPromise.then((snapshot) =>
|
|
373
|
+
Timing.time(c, 'token', () =>
|
|
374
|
+
Tokens.resolveToken(c, { address: token, chainId, include: [], snapshot }),
|
|
375
|
+
),
|
|
376
|
+
)
|
|
377
|
+
const client = c.get('getClient')(chainId)
|
|
378
|
+
const caller = c.get('zones').has(chainId)
|
|
379
|
+
? (await client.zone.getZoneInfo()).sequencers[0]
|
|
380
|
+
: undefined
|
|
381
|
+
const [balance, feeTokens, snapshot, resolvedToken] = await Promise.all([
|
|
363
382
|
Timing.time(c, 'balance_rpc', async () => {
|
|
364
|
-
const
|
|
383
|
+
const call = Actions.token.getBalance.call(client, {
|
|
365
384
|
account: address,
|
|
366
385
|
token,
|
|
367
386
|
})
|
|
368
|
-
return
|
|
387
|
+
return client.readContract({ ...call, account: caller })
|
|
369
388
|
}),
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
389
|
+
FeeAmm.feeTokenSet(c, chainId).catch(() => undefined),
|
|
390
|
+
snapshotPromise,
|
|
391
|
+
resolvedTokenPromise,
|
|
373
392
|
])
|
|
374
|
-
const denomination = query['valuation.currency'] ?? 'USD'
|
|
375
|
-
const rates = await Valuation.ratesFor(c, {
|
|
376
|
-
currencies: snapshot.byAddress.has(token) ? [resolvedToken.currency] : [],
|
|
377
|
-
denomination,
|
|
378
|
-
oracle,
|
|
379
|
-
})
|
|
380
|
-
const feeTokens = await FeeAmm.feeTokenSet(c, chainId).catch(() => undefined)
|
|
381
|
-
|
|
382
393
|
return c.json(
|
|
383
394
|
Response.validated(schema.getAddressBalance.Response, {
|
|
384
395
|
amount: balance.toString(),
|
|
@@ -388,12 +399,16 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
388
399
|
formatted: core_Value.format(balance, resolvedToken.decimals),
|
|
389
400
|
id: token,
|
|
390
401
|
token: resolvedToken,
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
402
|
+
...(denomination
|
|
403
|
+
? {
|
|
404
|
+
valuation: Valuation.valuationFor({
|
|
405
|
+
amount: balance,
|
|
406
|
+
denomination,
|
|
407
|
+
rates,
|
|
408
|
+
token: snapshot.byAddress.get(token),
|
|
409
|
+
}),
|
|
410
|
+
}
|
|
411
|
+
: {}),
|
|
397
412
|
}),
|
|
398
413
|
200,
|
|
399
414
|
)
|
|
@@ -430,7 +445,6 @@ async function getAddressBalances(
|
|
|
430
445
|
currency,
|
|
431
446
|
denomination,
|
|
432
447
|
feeEligible,
|
|
433
|
-
feeTokens,
|
|
434
448
|
includeTotalCount,
|
|
435
449
|
limit,
|
|
436
450
|
oracle,
|
|
@@ -440,8 +454,9 @@ async function getAddressBalances(
|
|
|
440
454
|
const cursor = options.cursor ? Cursor.decode(options.cursor, ['uint', 'address']) : undefined
|
|
441
455
|
const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : 0
|
|
442
456
|
|
|
443
|
-
// Validate
|
|
444
|
-
|
|
457
|
+
// Validate an explicit denomination before balance discovery and correction
|
|
458
|
+
// so dependency failures cannot mask a caller error.
|
|
459
|
+
if (denomination) await Valuation.ratesFor(c, { currencies: [], denomination, oracle })
|
|
445
460
|
|
|
446
461
|
// Build the optional `token IN (…)` restriction before hitting TIDX. The
|
|
447
462
|
// verified/`currency` filter and the `feeEligible` filter each contribute a set
|
|
@@ -450,17 +465,32 @@ async function getAddressBalances(
|
|
|
450
465
|
// intersection. If any active filter rules every token out, return an empty
|
|
451
466
|
// page without a round trip. The snapshot is reused for the page enrichment
|
|
452
467
|
// below.
|
|
453
|
-
const
|
|
454
|
-
|
|
468
|
+
const snapshotPromise = VerifiedTokens.snapshot(c, chainId)
|
|
469
|
+
// Fee filtering can fail before the concurrently started snapshot is awaited.
|
|
470
|
+
// Observing this branch prevents an unhandled rejection; the later await still propagates it.
|
|
471
|
+
void snapshotPromise.catch(() => undefined)
|
|
472
|
+
const [snapshot_filter, feeTokens_filter] = await Promise.all([
|
|
473
|
+
verified ? snapshotPromise : Promise.resolve(undefined),
|
|
474
|
+
feeEligible ? FeeAmm.feeTokenSet(c, chainId) : Promise.resolve(undefined),
|
|
475
|
+
])
|
|
476
|
+
const allowed = allowedTokens({
|
|
477
|
+
currency,
|
|
478
|
+
feeEligible,
|
|
479
|
+
feeTokens: feeTokens_filter,
|
|
480
|
+
snapshot: snapshot_filter,
|
|
481
|
+
verified,
|
|
482
|
+
})
|
|
455
483
|
const tokenFilter = allowed?.map((address) => `'${address}'`)
|
|
456
|
-
if (tokenFilter?.length === 0)
|
|
484
|
+
if (tokenFilter?.length === 0) {
|
|
485
|
+
await snapshotPromise
|
|
457
486
|
return {
|
|
458
487
|
data: [],
|
|
459
488
|
...(includeTotalCount ? { meta: { totalCount: 0, totalCountCapped: false } } : {}),
|
|
460
489
|
nextCursor: null,
|
|
461
490
|
}
|
|
491
|
+
}
|
|
462
492
|
|
|
463
|
-
const
|
|
493
|
+
const holdingsPromise = Timing.time(c, 'balances_discovery', async () => {
|
|
464
494
|
const tokenIn = tokenFilter ? ` AND token IN (${tokenFilter.join(', ')})` : ''
|
|
465
495
|
|
|
466
496
|
const result = await tidx.fetch({
|
|
@@ -481,36 +511,58 @@ async function getAddressBalances(
|
|
|
481
511
|
})
|
|
482
512
|
})
|
|
483
513
|
|
|
484
|
-
|
|
514
|
+
// Correction starts as soon as discovery completes. Snapshot and fee-token
|
|
515
|
+
// cache misses continue concurrently until enrichment needs their results.
|
|
516
|
+
const correctedPromise = holdingsPromise.then(async (holdings) => {
|
|
517
|
+
if (holdings.length === 0) return []
|
|
518
|
+
const client = c.get('getClient')(chainId)
|
|
519
|
+
const caller = c.get('zones').has(chainId)
|
|
520
|
+
? (await client.zone.getZoneInfo()).sequencers[0]
|
|
521
|
+
: undefined
|
|
522
|
+
return Timing.time(c, 'balances_rpc', async () => {
|
|
523
|
+
// Concurrent reads enter viem's shared batch window and become one
|
|
524
|
+
// deployless multicall. A failed read fails the request rather than serving
|
|
525
|
+
// a stale or partial balance page.
|
|
526
|
+
const results = await Promise.all(
|
|
527
|
+
holdings.map(async (holding) => {
|
|
528
|
+
const call = Actions.token.getBalance.call(client, {
|
|
529
|
+
account: address,
|
|
530
|
+
token: holding.address,
|
|
531
|
+
})
|
|
532
|
+
const amount = await client.readContract({ ...call, account: caller })
|
|
533
|
+
return amount === 0n ? undefined : { address: holding.address, amount: amount.toString() }
|
|
534
|
+
}),
|
|
535
|
+
)
|
|
536
|
+
const rows = results.filter((holding) => holding !== undefined)
|
|
537
|
+
|
|
538
|
+
rows.sort((a, b) => {
|
|
539
|
+
const delta = BigInt(b.amount) - BigInt(a.amount)
|
|
540
|
+
if (delta !== 0n) return delta > 0n ? 1 : -1
|
|
541
|
+
return a.address < b.address ? -1 : a.address > b.address ? 1 : 0
|
|
542
|
+
})
|
|
543
|
+
return rows
|
|
544
|
+
})
|
|
545
|
+
})
|
|
546
|
+
const feeTokensPromise = feeEligible
|
|
547
|
+
? Promise.resolve(feeTokens_filter)
|
|
548
|
+
: holdingsPromise.then((holdings) =>
|
|
549
|
+
holdings.length === 0 ? undefined : FeeAmm.feeTokenSet(c, chainId).catch(() => undefined),
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
const [holdings, snapshot, feeTokens, corrected] = await Promise.all([
|
|
553
|
+
holdingsPromise,
|
|
554
|
+
snapshotPromise,
|
|
555
|
+
feeTokensPromise,
|
|
556
|
+
correctedPromise,
|
|
557
|
+
])
|
|
558
|
+
|
|
559
|
+
if (holdings.length === 0) {
|
|
485
560
|
return {
|
|
486
561
|
data: [],
|
|
487
562
|
...(includeTotalCount ? { meta: { totalCount: 0, totalCountCapped: false } } : {}),
|
|
488
563
|
nextCursor: null,
|
|
489
564
|
}
|
|
490
|
-
|
|
491
|
-
const client = c.get('getClient')(chainId)
|
|
492
|
-
const corrected = await Timing.time(c, 'balances_rpc', async () => {
|
|
493
|
-
// Concurrent reads enter viem's shared batch window and become one
|
|
494
|
-
// deployless multicall. A failed read fails the request rather than serving
|
|
495
|
-
// a stale or partial balance page.
|
|
496
|
-
const results = await Promise.all(
|
|
497
|
-
holdings.map(async (holding) => {
|
|
498
|
-
const { amount } = await Actions.token.getBalance(client, {
|
|
499
|
-
account: address,
|
|
500
|
-
token: holding.address,
|
|
501
|
-
})
|
|
502
|
-
return amount === 0n ? undefined : { address: holding.address, amount: amount.toString() }
|
|
503
|
-
}),
|
|
504
|
-
)
|
|
505
|
-
const rows = results.filter((holding) => holding !== undefined)
|
|
506
|
-
|
|
507
|
-
rows.sort((a, b) => {
|
|
508
|
-
const delta = BigInt(b.amount) - BigInt(a.amount)
|
|
509
|
-
if (delta !== 0n) return delta > 0n ? 1 : -1
|
|
510
|
-
return a.address < b.address ? -1 : a.address > b.address ? 1 : 0
|
|
511
|
-
})
|
|
512
|
-
return rows
|
|
513
|
-
})
|
|
565
|
+
}
|
|
514
566
|
|
|
515
567
|
const positioned = (() => {
|
|
516
568
|
if (!cursor) return corrected.slice(offset)
|
|
@@ -528,7 +580,7 @@ async function getAddressBalances(
|
|
|
528
580
|
key: (holding) => [holding.amount, holding.address],
|
|
529
581
|
})
|
|
530
582
|
|
|
531
|
-
if (page.rows.length === 0)
|
|
583
|
+
if (page.rows.length === 0) {
|
|
532
584
|
return {
|
|
533
585
|
data: [],
|
|
534
586
|
...(includeTotalCount
|
|
@@ -536,22 +588,38 @@ async function getAddressBalances(
|
|
|
536
588
|
: {}),
|
|
537
589
|
nextCursor: page.nextCursor,
|
|
538
590
|
}
|
|
591
|
+
}
|
|
539
592
|
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
593
|
+
const ratesPromise = denomination
|
|
594
|
+
? Valuation.ratesFor(c, {
|
|
595
|
+
currencies: page.rows.flatMap((row) => {
|
|
596
|
+
const held = snapshot.byAddress.get(row.address.toLowerCase())?.currency
|
|
597
|
+
return held === undefined ? [] : [held]
|
|
598
|
+
}),
|
|
599
|
+
denomination,
|
|
600
|
+
oracle,
|
|
601
|
+
})
|
|
602
|
+
: Promise.resolve(undefined)
|
|
548
603
|
|
|
549
604
|
// Balance tokens retain their logo and verification metadata by default.
|
|
550
|
-
const
|
|
605
|
+
const resolvedPromise = Timing.time(c, 'tokens', () =>
|
|
551
606
|
Promise.all(
|
|
552
607
|
page.rows.map(async (holding) => {
|
|
553
608
|
try {
|
|
554
609
|
const token = Schema.Address.parse(holding.address.toLowerCase())
|
|
610
|
+
const verifiedToken = snapshot.byAddress.get(token)
|
|
611
|
+
// The curated list owns verified-token metadata, but uploaded logos live only in the asset store.
|
|
612
|
+
if (verifiedToken)
|
|
613
|
+
return {
|
|
614
|
+
amount: holding.amount,
|
|
615
|
+
logoUri:
|
|
616
|
+
verifiedToken.logoUri ??
|
|
617
|
+
(await Timing.time(c, 'token_logo', () =>
|
|
618
|
+
Tokens.getTokenLogo(c, { address: token, chainId }),
|
|
619
|
+
).catch(() => undefined)),
|
|
620
|
+
metadata: verifiedToken,
|
|
621
|
+
token,
|
|
622
|
+
}
|
|
555
623
|
const [logoUri, metadata] = await Promise.all([
|
|
556
624
|
Timing.time(c, 'token_logo', () =>
|
|
557
625
|
Tokens.getTokenLogo(c, { address: token, chainId }),
|
|
@@ -567,6 +635,7 @@ async function getAddressBalances(
|
|
|
567
635
|
}),
|
|
568
636
|
),
|
|
569
637
|
)
|
|
638
|
+
const [rates, resolved] = await Promise.all([ratesPromise, resolvedPromise])
|
|
570
639
|
|
|
571
640
|
const data: z.output<typeof schema.getAddressBalances.Balance>[] = []
|
|
572
641
|
for (const entry of resolved) {
|
|
@@ -591,12 +660,16 @@ async function getAddressBalances(
|
|
|
591
660
|
symbol: entry.metadata.symbol,
|
|
592
661
|
verified: snapshot.byAddress.has(entry.token),
|
|
593
662
|
},
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
663
|
+
...(denomination
|
|
664
|
+
? {
|
|
665
|
+
valuation: Valuation.valuationFor({
|
|
666
|
+
amount: BigInt(entry.amount),
|
|
667
|
+
denomination,
|
|
668
|
+
rates,
|
|
669
|
+
token: snapshot.byAddress.get(entry.token),
|
|
670
|
+
}),
|
|
671
|
+
}
|
|
672
|
+
: {}),
|
|
600
673
|
})
|
|
601
674
|
}
|
|
602
675
|
|
|
@@ -620,15 +693,9 @@ declare namespace getAddressBalances {
|
|
|
620
693
|
/** Opaque keyset cursor anchoring the page; omit for the head page. */
|
|
621
694
|
cursor?: string | undefined
|
|
622
695
|
/** Currency the per-row valuations are denominated in (uppercase). */
|
|
623
|
-
denomination
|
|
624
|
-
/** When true, restrict the page to fee-usable tokens
|
|
696
|
+
denomination?: string | undefined
|
|
697
|
+
/** When true, restrict the page to fee-usable tokens. */
|
|
625
698
|
feeEligible?: boolean | undefined
|
|
626
|
-
/**
|
|
627
|
-
* The chain's fee-token set, if resolved (best-effort): flags each row's
|
|
628
|
-
* `feeEligible` and backs the `feeEligible` filter. Undefined when the lookup
|
|
629
|
-
* failed, in which case `feeEligible` is omitted from rows.
|
|
630
|
-
*/
|
|
631
|
-
feeTokens?: ReadonlySet<string> | undefined
|
|
632
699
|
/** When true, include the corrected candidate count in response metadata. */
|
|
633
700
|
includeTotalCount: boolean
|
|
634
701
|
limit: number
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as QueryBench from '../../../../test/QueryBench.js'
|
|
2
|
+
|
|
3
|
+
const fixtures = {
|
|
4
|
+
4217: { number: 34_335_551, timestamp: 1_786_485_830 },
|
|
5
|
+
42431: { number: 30_469_074, timestamp: 1_786_485_831 },
|
|
6
|
+
} as const
|
|
7
|
+
|
|
8
|
+
QueryBench.bench({ name: 'getBlocks', path: '/v1/blocks' })
|
|
9
|
+
|
|
10
|
+
QueryBench.bench({
|
|
11
|
+
name: 'getBlock',
|
|
12
|
+
path: {
|
|
13
|
+
4217: `/v1/blocks/${fixtures[4217].number}`,
|
|
14
|
+
42431: `/v1/blocks/${fixtures[42431].number}`,
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
QueryBench.bench({
|
|
19
|
+
chainId: false,
|
|
20
|
+
name: 'getBlockByTimestamp',
|
|
21
|
+
path: {
|
|
22
|
+
4217: `/chains/4217/blocks/by-timestamp?closest=before×tamp=${fixtures[4217].timestamp}`,
|
|
23
|
+
42431: `/chains/42431/blocks/by-timestamp?closest=before×tamp=${fixtures[42431].timestamp}`,
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
QueryBench.bench({
|
|
28
|
+
chainId: false,
|
|
29
|
+
name: 'getBlockByTimestamp?closest=after',
|
|
30
|
+
path: {
|
|
31
|
+
4217: `/chains/4217/blocks/by-timestamp?closest=after×tamp=${fixtures[4217].timestamp}`,
|
|
32
|
+
42431: `/chains/42431/blocks/by-timestamp?closest=after×tamp=${fixtures[42431].timestamp}`,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
@@ -232,20 +232,26 @@ describe('GET /blocks', () => {
|
|
|
232
232
|
|
|
233
233
|
describe('GET /blocks/:block (ISO 8601 timestamp)', () => {
|
|
234
234
|
test('resolves an ISO 8601 timestamp to the first block at or after it', async () => {
|
|
235
|
-
expect(recent).toBeDefined()
|
|
236
|
-
const recentBlock = await Viem.getClient().getBlock({ blockNumber: recent!.number })
|
|
237
|
-
const seconds = Number(recentBlock.timestamp)
|
|
238
|
-
const iso = new Date(seconds * 1000).toISOString()
|
|
239
235
|
const client = TestApp.client()
|
|
236
|
+
// Seed from TIDX rather than the RPC head, which can advance beyond the
|
|
237
|
+
// indexer's timestamp lookup watermark.
|
|
238
|
+
const blocksResponse = await client.v1.blocks.$get({ query: { limit: '5' } }, TestApp.auth)
|
|
239
|
+
const blocks = await TestApp.json(blocksResponse, Blocks.schema.getBlocks.Response)
|
|
240
|
+
const indexed = blocks.data[0]
|
|
241
|
+
expect(blocksResponse.status).toBe(200)
|
|
242
|
+
expect(indexed).toBeDefined()
|
|
243
|
+
|
|
240
244
|
const response = await client.v1.blocks[':block'].$get(
|
|
241
|
-
{ param: { block:
|
|
245
|
+
{ param: { block: indexed!.timestamp }, query: {} },
|
|
242
246
|
TestApp.auth,
|
|
243
247
|
)
|
|
244
248
|
const body = await TestApp.json(response, Blocks.schema.getBlock.Response)
|
|
245
249
|
expect(response.status).toBe(200)
|
|
246
250
|
expect(typeof body.number).toBe('number')
|
|
247
251
|
// The returned block's timestamp must be >= the requested timestamp.
|
|
248
|
-
expect(new Date(body.timestamp).getTime()
|
|
252
|
+
expect(new Date(body.timestamp).getTime()).toBeGreaterThanOrEqual(
|
|
253
|
+
new Date(indexed!.timestamp).getTime(),
|
|
254
|
+
)
|
|
249
255
|
})
|
|
250
256
|
|
|
251
257
|
test('returns 404 for a far-future timestamp with no indexed block', async () => {
|