tapimo 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +13987 -5755
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +910 -412
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +4 -0
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/early-access.d.ts +148 -0
- package/dist/admin/apps/early-access.d.ts.map +1 -0
- package/dist/admin/apps/early-access.js +133 -0
- package/dist/admin/apps/early-access.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +643 -118
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +214 -1
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +45 -33
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +3733 -2428
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +193 -2
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +71 -0
- package/dist/apps/data/FxOracle.d.ts.map +1 -0
- package/dist/apps/data/FxOracle.js +97 -0
- package/dist/apps/data/FxOracle.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3575 -1358
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +230 -88
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +963 -342
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/addresses.js +11 -9
- package/dist/apps/data/routes/addresses.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +108 -57
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +88 -45
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +169 -169
- package/dist/apps/data/routes/exchanges.d.ts +952 -201
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +518 -76
- 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/funding.d.ts +8 -2
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +32 -6
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/receipts.d.ts +392 -315
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +152 -77
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +166 -173
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +14 -12
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +277 -536
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +96 -77
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +82 -17
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +70 -13
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +307 -0
- package/dist/apps/data/routes/valuation.d.ts.map +1 -0
- package/dist/apps/data/routes/valuation.js +292 -0
- package/dist/apps/data/routes/valuation.js.map +1 -0
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +7 -5
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +459 -479
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -25
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +8606 -1681
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +12 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +23 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +5 -3
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +88 -48
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +10 -2
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +218 -0
- package/dist/apps/management/routes/faucet.d.ts.map +1 -0
- package/dist/apps/management/routes/faucet.js +139 -0
- package/dist/apps/management/routes/faucet.js.map +1 -0
- package/dist/apps/management/routes/invitations.d.ts +121 -121
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +3 -0
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +197 -168
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +26 -1
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +2 -0
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +4 -0
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +49 -49
- package/dist/apps/management/routes/orgs.d.ts +91 -91
- package/dist/apps/management/routes/projects.d.ts +115 -115
- package/dist/apps/management/routes/usage.d.ts +35 -32
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +4 -0
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
- package/dist/apps/management/routes/verified-token-requests.js +271 -0
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
- package/dist/apps/management/routes/webhooks.d.ts +6977 -0
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
- package/dist/apps/management/routes/webhooks.js +573 -0
- package/dist/apps/management/routes/webhooks.js.map +1 -0
- package/dist/apps/mcp.d.ts +9 -2
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +9 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +7 -2
- package/dist/cli/Cli.js.map +1 -1
- package/dist/db/Db.d.ts +4 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +83 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +41 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +45 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earlyAccess.d.ts +50 -0
- package/dist/db/tables/earlyAccess.d.ts.map +1 -0
- package/dist/db/tables/earlyAccess.js +63 -0
- package/dist/db/tables/earlyAccess.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +12 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +23 -0
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +10 -2
- package/dist/db/tables/inviteLinks.d.ts.map +1 -1
- package/dist/db/tables/inviteLinks.js +10 -1
- package/dist/db/tables/inviteLinks.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +2 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokenRequests.js +120 -0
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +16 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +43 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +60 -45
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +69 -15
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +74 -25
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +19 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +2 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +9 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +2 -2
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +3 -0
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Schema.d.ts +14 -5
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +23 -5
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Value.d.ts +18 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +11 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +10 -1
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/Webhooks.d.ts +24 -3
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +34 -8
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +2 -2
- package/src/App.ts +9 -1
- package/src/Client.test-d.ts +638 -6
- package/src/admin/App.ts +4 -0
- package/src/admin/apps/early-access.test.ts +125 -0
- package/src/admin/apps/early-access.ts +175 -0
- package/src/admin/apps/verified-tokens.test.ts +201 -0
- package/src/admin/apps/verified-tokens.ts +532 -286
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/lib/early-access.ts +63 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
- package/src/admin/ui/src/routeTree.gen.ts +33 -2
- package/src/admin/ui/src/routes/__root.tsx +2 -1
- package/src/admin/ui/src/routes/early-access.tsx +157 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
- package/src/admin/ui.generated.ts +48 -33
- package/src/apps/data/App.ts +22 -14
- package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
- package/src/apps/data/FundingQuoteProviders.ts +257 -4
- package/src/apps/data/FxOracle.test.ts +118 -0
- package/src/apps/data/FxOracle.ts +153 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/activities.test.ts +173 -3
- package/src/apps/data/routes/activities.ts +403 -132
- package/src/apps/data/routes/addresses.ts +19 -9
- package/src/apps/data/routes/balances.test.ts +144 -11
- package/src/apps/data/routes/balances.ts +140 -51
- package/src/apps/data/routes/exchanges.test.ts +540 -9
- package/src/apps/data/routes/exchanges.ts +690 -103
- package/src/apps/data/routes/funding.test.ts +84 -0
- package/src/apps/data/routes/funding.ts +38 -6
- package/src/apps/data/routes/receipts.test.ts +125 -23
- package/src/apps/data/routes/receipts.ts +205 -100
- package/src/apps/data/routes/tokens.test.ts +43 -0
- package/src/apps/data/routes/tokens.ts +27 -12
- package/src/apps/data/routes/transactions.test.ts +72 -19
- package/src/apps/data/routes/transactions.ts +162 -92
- package/src/apps/data/routes/transfers.test.ts +146 -4
- package/src/apps/data/routes/transfers.ts +91 -13
- package/src/apps/data/routes/valuation.test.ts +422 -0
- package/src/apps/data/routes/valuation.ts +423 -0
- package/src/apps/data/routes/verified-tokens.ts +14 -2
- package/src/apps/data/routes/webhooks.test.ts +56 -8
- package/src/apps/data/routes/webhooks.ts +144 -27
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +13 -0
- package/src/apps/management/routes/api-keys.test.ts +87 -0
- package/src/apps/management/routes/api-keys.ts +4 -3
- package/src/apps/management/routes/billing.test.ts +112 -1
- package/src/apps/management/routes/billing.ts +7 -2
- package/src/apps/management/routes/faucet.test.ts +79 -0
- package/src/apps/management/routes/faucet.ts +185 -0
- package/src/apps/management/routes/invitations.test.ts +172 -0
- package/src/apps/management/routes/invitations.ts +3 -0
- package/src/apps/management/routes/invite-links.test.ts +117 -0
- package/src/apps/management/routes/invite-links.ts +43 -1
- package/src/apps/management/routes/me.test.ts +140 -88
- package/src/apps/management/routes/me.ts +6 -0
- package/src/apps/management/routes/usage.test.ts +6 -0
- package/src/apps/management/routes/usage.ts +7 -0
- package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
- package/src/apps/management/routes/verified-token-requests.ts +397 -0
- package/src/apps/management/routes/webhooks.test.ts +426 -0
- package/src/apps/management/routes/webhooks.ts +672 -0
- package/src/apps/mcp.test.ts +13 -0
- package/src/apps/mcp.ts +18 -5
- package/src/cli/Cli.test.ts +36 -0
- package/src/cli/Cli.ts +7 -2
- package/src/db/Db.test.ts +19 -0
- package/src/db/Db.ts +95 -0
- package/src/db/Schema.ts +53 -0
- package/src/db/tables/earlyAccess.ts +86 -0
- package/src/db/tables/invitations.ts +26 -1
- package/src/db/tables/inviteLinks.ts +22 -3
- package/src/db/tables/sponsoredTransactions.ts +4 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
- package/src/db/tables/verifiedTokenRequests.ts +182 -0
- package/src/db/tables/webhookSubscriptions.ts +31 -0
- package/src/internal/Auth.ts +73 -45
- package/src/internal/FundingQuotes.test.ts +143 -1
- package/src/internal/FundingQuotes.ts +95 -28
- package/src/internal/Log.test-d.ts +21 -0
- package/src/internal/Log.test.ts +50 -0
- package/src/internal/Log.ts +24 -1
- package/src/internal/MetricSink.test.ts +160 -0
- package/src/internal/MetricSink.ts +10 -0
- package/src/internal/OpenApi.ts +3 -0
- package/src/internal/Schema.ts +50 -5
- package/src/internal/Value.test.ts +20 -0
- package/src/internal/Value.ts +24 -1
- package/src/internal/VerifiedTokens.ts +9 -1
- package/src/internal/Webhooks.test.ts +140 -0
- package/src/internal/Webhooks.ts +53 -9
|
@@ -1,11 +1,62 @@
|
|
|
1
1
|
import type * as z from 'zod/mini'
|
|
2
|
-
import {
|
|
2
|
+
import { Value as core_Value } from 'ox'
|
|
3
|
+
import { Db, Schema } from 'tapimo'
|
|
4
|
+
import { FxOracle } from 'tapimo/apps'
|
|
3
5
|
|
|
4
6
|
import * as TestApp from '../../../../test/App.js'
|
|
5
7
|
import * as Runtime from '../../../../test/runtime.js'
|
|
6
8
|
import * as Transfers from './transfers.js'
|
|
7
9
|
|
|
10
|
+
// Deterministic EUR-based rate set so conversion math is exact in assertions.
|
|
11
|
+
const fixed = FxOracle.from({
|
|
12
|
+
name: 'fixed',
|
|
13
|
+
rates: async () => ({ asOf: '2026-01-01', base: 'EUR', rates: { AUD: '1.6', USD: '1.0' } }),
|
|
14
|
+
})
|
|
15
|
+
|
|
8
16
|
describe('GET /transfers', () => {
|
|
17
|
+
test('degrades unavailable verification data to null valuations', async () => {
|
|
18
|
+
const db = () =>
|
|
19
|
+
Db.postgres({
|
|
20
|
+
connectionString: 'postgresql://postgres:postgres@127.0.0.1:1/none',
|
|
21
|
+
})
|
|
22
|
+
const client = TestApp.client({ db, fx: { oracle: fixed } })
|
|
23
|
+
|
|
24
|
+
const response = await client.v1.transfers.$get(
|
|
25
|
+
{ query: { limit: '5', token: TestApp.tokenWithHolders } },
|
|
26
|
+
TestApp.auth,
|
|
27
|
+
)
|
|
28
|
+
const body = await TestApp.json(response, Transfers.schema.getTransfers.Response)
|
|
29
|
+
|
|
30
|
+
expect(response.status).toBe(200)
|
|
31
|
+
expect(body.data.length).toBeGreaterThan(0)
|
|
32
|
+
for (const entry of body.data) expect(entry.sourceToken.valuation).toBeNull()
|
|
33
|
+
|
|
34
|
+
const emptyResponse = await client.v1.transfers.$get(
|
|
35
|
+
{ query: { limit: '5', token: `0x${'11'.repeat(20)}` } },
|
|
36
|
+
TestApp.auth,
|
|
37
|
+
)
|
|
38
|
+
const empty = await TestApp.json(emptyResponse, Transfers.schema.getTransfers.Response)
|
|
39
|
+
expect(emptyResponse.status).toBe(200)
|
|
40
|
+
expect(empty.data).toMatchInlineSnapshot(`[]`)
|
|
41
|
+
expect(emptyResponse.headers.get('server-timing')?.includes('verified_tokens;dur=')).not.toBe(
|
|
42
|
+
true,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const unsupportedResponse = await client.v1.transfers.$get(
|
|
46
|
+
{
|
|
47
|
+
query: {
|
|
48
|
+
denomination: 'JPY',
|
|
49
|
+
limit: '5',
|
|
50
|
+
token: TestApp.tokenWithHolders,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
TestApp.auth,
|
|
54
|
+
)
|
|
55
|
+
const unsupported = await TestApp.json(unsupportedResponse, Schema.ErrorResponse)
|
|
56
|
+
expect(unsupportedResponse.status).toBe(400)
|
|
57
|
+
expect(unsupported.error.code).toBe('query_invalid')
|
|
58
|
+
})
|
|
59
|
+
|
|
9
60
|
test('returns an unscoped transfer page, embedding tokens on demand', async () => {
|
|
10
61
|
const client = TestApp.client()
|
|
11
62
|
const baseResponse = await client.v1.transfers.$get({ query: { limit: '5' } }, TestApp.auth)
|
|
@@ -147,6 +198,9 @@ describe('GET /transfers', () => {
|
|
|
147
198
|
const second = await TestApp.json(secondResponse, Transfers.schema.getTransfers.Response)
|
|
148
199
|
|
|
149
200
|
expect(secondResponse.status).toBe(200)
|
|
201
|
+
expect(secondResponse.headers.get('cache-control')).toMatchInlineSnapshot(
|
|
202
|
+
`"private, max-age=60, stale-while-revalidate=300"`,
|
|
203
|
+
)
|
|
150
204
|
expect(second.data.length).toBeGreaterThan(0)
|
|
151
205
|
// Each row's identity on the unscoped feed is `(blockNumber, txHash,
|
|
152
206
|
// sourceToken, sender, recipient, amount)` — a single transaction can
|
|
@@ -285,7 +339,7 @@ describe('GET /transfers', () => {
|
|
|
285
339
|
expect(Number.isInteger(body.meta?.totalCount)).toBe(true)
|
|
286
340
|
expect(body.meta!.totalCount).toBeGreaterThanOrEqual(body.data.length)
|
|
287
341
|
expect(body.meta!.totalCount).toBeLessThanOrEqual(Schema.countCap)
|
|
288
|
-
expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount >= Schema.countCap)
|
|
342
|
+
expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount! >= Schema.countCap)
|
|
289
343
|
})
|
|
290
344
|
|
|
291
345
|
test('embeds a capped token-scoped total count on demand', async () => {
|
|
@@ -309,7 +363,7 @@ describe('GET /transfers', () => {
|
|
|
309
363
|
expect(typeof body.meta?.totalCountCapped).toBe('boolean')
|
|
310
364
|
// The count never exceeds the cap; `totalCountCapped` is set iff the cap was hit.
|
|
311
365
|
expect(body.meta!.totalCount).toBeLessThanOrEqual(Schema.countCap)
|
|
312
|
-
expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount >= Schema.countCap)
|
|
366
|
+
expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount! >= Schema.countCap)
|
|
313
367
|
|
|
314
368
|
// Without `include=totalCount`, the same page omits `meta` entirely and
|
|
315
369
|
// does not run the count query.
|
|
@@ -319,7 +373,10 @@ describe('GET /transfers', () => {
|
|
|
319
373
|
)
|
|
320
374
|
const bare = await TestApp.json(bareResponse, Transfers.schema.getTransfers.Response)
|
|
321
375
|
expect(bareResponse.status).toBe(200)
|
|
322
|
-
|
|
376
|
+
// Without the include, counts are omitted (`meta` carries at most rate
|
|
377
|
+
// provenance).
|
|
378
|
+
expect(bare.meta?.totalCount).toBeUndefined()
|
|
379
|
+
expect(bare.meta?.totalCountCapped).toBeUndefined()
|
|
323
380
|
expect(bareResponse.headers.get('server-timing')?.includes('transfers_count;dur=')).toBe(false)
|
|
324
381
|
})
|
|
325
382
|
|
|
@@ -422,6 +479,91 @@ describe('GET /transfers', () => {
|
|
|
422
479
|
},
|
|
423
480
|
)
|
|
424
481
|
|
|
482
|
+
test('values verified transfer legs in the default denomination', async () => {
|
|
483
|
+
const db = TestApp.database()
|
|
484
|
+
await TestApp.verifiedSeed(db, Runtime.get().chainId)
|
|
485
|
+
const client = TestApp.client({ db })
|
|
486
|
+
|
|
487
|
+
// pathUSD is curated USD, so its legs value at identity with no rates.
|
|
488
|
+
const response = await client.v1.transfers.$get(
|
|
489
|
+
{ query: { limit: '5', token: TestApp.tokenWithHolders } },
|
|
490
|
+
TestApp.auth,
|
|
491
|
+
)
|
|
492
|
+
const body = await TestApp.json(response, Transfers.schema.getTransfers.Response)
|
|
493
|
+
|
|
494
|
+
expect(response.status).toBe(200)
|
|
495
|
+
expect(body.data.length).toBeGreaterThan(0)
|
|
496
|
+
for (const entry of body.data)
|
|
497
|
+
expect(entry.sourceToken.valuation).toEqual({
|
|
498
|
+
amount: core_Value.format(BigInt(entry.sourceToken.amount), 6),
|
|
499
|
+
currency: 'USD',
|
|
500
|
+
})
|
|
501
|
+
expect(body.meta?.pricing).toBeUndefined()
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
test('values transfer legs in a requested denomination', async () => {
|
|
505
|
+
const db = TestApp.database()
|
|
506
|
+
await TestApp.verifiedSeed(db, Runtime.get().chainId)
|
|
507
|
+
const client = TestApp.client({ db, fx: { oracle: fixed } })
|
|
508
|
+
|
|
509
|
+
// Denominations are case-insensitive.
|
|
510
|
+
const response = await client.v1.transfers.$get(
|
|
511
|
+
{ query: { denomination: 'aud', limit: '5', token: TestApp.tokenWithHolders } },
|
|
512
|
+
TestApp.auth,
|
|
513
|
+
)
|
|
514
|
+
const body = await TestApp.json(response, Transfers.schema.getTransfers.Response)
|
|
515
|
+
|
|
516
|
+
expect(response.status).toBe(200)
|
|
517
|
+
expect(body.data.length).toBeGreaterThan(0)
|
|
518
|
+
// USD -> AUD at the fixed 1.6 cross rate, floored to 6 dp.
|
|
519
|
+
for (const entry of body.data)
|
|
520
|
+
expect(entry.sourceToken.valuation).toEqual({
|
|
521
|
+
amount: core_Value.format((BigInt(entry.sourceToken.amount) * 16n) / 10n, 6),
|
|
522
|
+
currency: 'AUD',
|
|
523
|
+
})
|
|
524
|
+
expect(body.meta?.pricing).toMatchInlineSnapshot(`
|
|
525
|
+
{
|
|
526
|
+
"asOf": "2026-01-01",
|
|
527
|
+
"basis": "nominal",
|
|
528
|
+
"source": "fixed",
|
|
529
|
+
}
|
|
530
|
+
`)
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
test.skipIf(Runtime.get().mode !== 'localnet')(
|
|
534
|
+
'nulls leg values for unverified tokens',
|
|
535
|
+
async () => {
|
|
536
|
+
// Curate only pathUSD so the faucet token queried below is unverified
|
|
537
|
+
// for this app, and its legs never value.
|
|
538
|
+
const db = TestApp.database()
|
|
539
|
+
await TestApp.verifiedSeed(db, Runtime.get().chainId, {
|
|
540
|
+
tokens: [
|
|
541
|
+
{
|
|
542
|
+
address: TestApp.tokenWithHolders,
|
|
543
|
+
currency: 'USD',
|
|
544
|
+
decimals: 6,
|
|
545
|
+
name: 'PathUSD',
|
|
546
|
+
symbol: 'pathUSD',
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
})
|
|
550
|
+
const client = TestApp.client({ db })
|
|
551
|
+
|
|
552
|
+
const token = Runtime.get().fixtures!.transferTokenAddresses.find(
|
|
553
|
+
(address) => address.toLowerCase() !== TestApp.tokenWithHolders,
|
|
554
|
+
)!
|
|
555
|
+
const response = await client.v1.transfers.$get(
|
|
556
|
+
{ query: { limit: '5', token } },
|
|
557
|
+
TestApp.auth,
|
|
558
|
+
)
|
|
559
|
+
const body = await TestApp.json(response, Transfers.schema.getTransfers.Response)
|
|
560
|
+
|
|
561
|
+
expect(response.status).toBe(200)
|
|
562
|
+
expect(body.data.length).toBeGreaterThan(0)
|
|
563
|
+
for (const entry of body.data) expect(entry.sourceToken.valuation).toBeNull()
|
|
564
|
+
},
|
|
565
|
+
)
|
|
566
|
+
|
|
425
567
|
test('surfaces a 502 when transfer data is unavailable', async () => {
|
|
426
568
|
// Point at an unreachable indexer so the upstream query fails deterministically.
|
|
427
569
|
const client = TestApp.client({ tidx: { baseUrl: 'http://127.0.0.1:1' } })
|
|
@@ -17,7 +17,9 @@ import * as Timing from '../../../internal/Timing.js'
|
|
|
17
17
|
import * as Ttl from '../../../internal/Ttl.js'
|
|
18
18
|
import * as Value from '../../../internal/Value.js'
|
|
19
19
|
import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
|
|
20
|
+
import * as FxOracle from '../FxOracle.js'
|
|
20
21
|
import * as Tokens from './tokens.js'
|
|
22
|
+
import * as Valuation from './valuation.js'
|
|
21
23
|
|
|
22
24
|
// TIP-20 tokens emit the standard ERC-20 Transfer event. Supplying the
|
|
23
25
|
// signature exposes a decoded `Transfer` CTE (with `from`/`to`/`value`) to
|
|
@@ -93,6 +95,14 @@ export namespace schema {
|
|
|
93
95
|
z.describe('The short ticker symbol wallets and apps show for this token.'),
|
|
94
96
|
z.meta({ examples: ['USDC'] }),
|
|
95
97
|
),
|
|
98
|
+
valuation: z
|
|
99
|
+
.optional(z.nullable(Valuation.schema.Value))
|
|
100
|
+
.check(
|
|
101
|
+
z.describe(
|
|
102
|
+
'The leg’s nominal value in the requested `denomination`. `null` when the token is ' +
|
|
103
|
+
'unverified, its display currency has no rate, or rates were unavailable.',
|
|
104
|
+
),
|
|
105
|
+
),
|
|
96
106
|
verified: z
|
|
97
107
|
.optional(z.boolean())
|
|
98
108
|
.check(
|
|
@@ -174,6 +184,7 @@ export namespace schema {
|
|
|
174
184
|
'blockNumber.to': Schema.blockNumberBound('transfers', 'to'),
|
|
175
185
|
chainId: Schema.ChainIdQuery,
|
|
176
186
|
cursor: Schema.Cursor,
|
|
187
|
+
denomination: Schema.Denomination,
|
|
177
188
|
include: includeQuery,
|
|
178
189
|
limit: Schema.Limit,
|
|
179
190
|
order: Schema.Order,
|
|
@@ -266,16 +277,32 @@ export namespace schema {
|
|
|
266
277
|
),
|
|
267
278
|
)
|
|
268
279
|
|
|
280
|
+
/** Page-level resources: opt-in counts plus valuation rate provenance. */
|
|
281
|
+
export const Meta = z
|
|
282
|
+
.object({
|
|
283
|
+
pricing: z
|
|
284
|
+
.optional(Valuation.schema.Pricing)
|
|
285
|
+
.check(
|
|
286
|
+
z.describe(
|
|
287
|
+
'Rate provenance for leg valuations. Present when conversion rates were ' +
|
|
288
|
+
'consulted; absent when every value was identity-valued or rates were unavailable.',
|
|
289
|
+
),
|
|
290
|
+
),
|
|
291
|
+
totalCount: z.optional(Schema.TotalCount),
|
|
292
|
+
totalCountCapped: z.optional(Schema.TotalCountCapped),
|
|
293
|
+
})
|
|
294
|
+
.check(z.describe('Page-level resources attached to this response.'))
|
|
295
|
+
|
|
269
296
|
/** Page of TIP-20 token transfers across the chain, ordered by block then log index. */
|
|
270
297
|
export const Response = z
|
|
271
298
|
.object({
|
|
272
299
|
data: z.array(Transfer).check(z.describe('The transfers in this page.')),
|
|
273
300
|
// Response-wide `meta` carries the opt-in capped total count
|
|
274
|
-
// (`include=totalCount`); per-row embeds
|
|
275
|
-
// each `data` item, not here.
|
|
301
|
+
// (`include=totalCount`) and valuation rate provenance; per-row embeds
|
|
302
|
+
// (`memo`, `token`) attach to each `data` item, not here.
|
|
276
303
|
meta: z
|
|
277
|
-
.optional(
|
|
278
|
-
.check(z.describe('Page-level resources
|
|
304
|
+
.optional(Meta)
|
|
305
|
+
.check(z.describe('Page-level resources: opt-in counts and valuation rate provenance.')),
|
|
279
306
|
nextCursor: Schema.NextCursor,
|
|
280
307
|
})
|
|
281
308
|
.check(z.describe('A page of TIP-20 token transfer events across the chain.'))
|
|
@@ -283,7 +310,8 @@ export namespace schema {
|
|
|
283
310
|
}
|
|
284
311
|
|
|
285
312
|
/** Creates transfer handlers. */
|
|
286
|
-
export function transfers() {
|
|
313
|
+
export function transfers(options: transfers.Options = {}) {
|
|
314
|
+
const oracle = options.fx?.oracle ?? FxOracle.ecb()
|
|
287
315
|
return new Hono<App.Environment>().get(
|
|
288
316
|
'/v1/transfers',
|
|
289
317
|
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
@@ -319,11 +347,10 @@ export function transfers() {
|
|
|
319
347
|
|
|
320
348
|
const options = c.req.valid('query')
|
|
321
349
|
const chainId = options.chainId ?? c.get('chainId')
|
|
350
|
+
const denomination = options.denomination ?? 'USD'
|
|
322
351
|
|
|
323
|
-
// Cursor pages
|
|
324
|
-
|
|
325
|
-
// `feed` default.
|
|
326
|
-
if (options.cursor !== undefined) Cache.setPolicy(c, Cache.policies.immutable)
|
|
352
|
+
// Cursor pages carry current valuations and curated verification data.
|
|
353
|
+
if (options.cursor !== undefined) Cache.setPolicy(c, Cache.policies.metadata)
|
|
327
354
|
|
|
328
355
|
// Filters shared by the page query and the opt-in total count, so the
|
|
329
356
|
// count always matches the page it annotates.
|
|
@@ -427,7 +454,13 @@ export function transfers() {
|
|
|
427
454
|
return suppress.has(key) ? [] : [transfer]
|
|
428
455
|
})
|
|
429
456
|
}
|
|
430
|
-
|
|
457
|
+
// Verification is ancillary to the feed. Skip empty pages and degrade
|
|
458
|
+
// cold snapshot failures to null valuations.
|
|
459
|
+
const snapshot =
|
|
460
|
+
data.length > 0
|
|
461
|
+
? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
462
|
+
: undefined
|
|
463
|
+
if (options.include.includes('token') && data.length > 0 && snapshot) {
|
|
431
464
|
const uniqueTokens = Array.from(
|
|
432
465
|
new Set(
|
|
433
466
|
data.flatMap((transfer) => [
|
|
@@ -436,7 +469,6 @@ export function transfers() {
|
|
|
436
469
|
]),
|
|
437
470
|
),
|
|
438
471
|
)
|
|
439
|
-
const snapshot = await VerifiedTokens.snapshot(c, chainId)
|
|
440
472
|
const resolved = await Timing.time(c, 'tokens', () =>
|
|
441
473
|
Promise.all(
|
|
442
474
|
uniqueTokens.map((token) =>
|
|
@@ -466,23 +498,69 @@ export function transfers() {
|
|
|
466
498
|
}))
|
|
467
499
|
}
|
|
468
500
|
|
|
469
|
-
|
|
501
|
+
// Value each leg via its curated display currency, in the requested
|
|
502
|
+
// denomination.
|
|
503
|
+
const rates = await Valuation.ratesFor(c, {
|
|
504
|
+
currencies: data.flatMap((transfer) =>
|
|
505
|
+
[
|
|
506
|
+
transfer.sourceToken.address,
|
|
507
|
+
...(transfer.destinationToken ? [transfer.destinationToken.address] : []),
|
|
508
|
+
].flatMap((address) => {
|
|
509
|
+
const held = snapshot?.byAddress.get(address.toLowerCase())?.currency
|
|
510
|
+
return held === undefined ? [] : [held]
|
|
511
|
+
}),
|
|
512
|
+
),
|
|
513
|
+
denomination,
|
|
514
|
+
oracle,
|
|
515
|
+
})
|
|
516
|
+
const valued = <leg extends { address: string; amount: string }>(leg: leg) => ({
|
|
517
|
+
...leg,
|
|
518
|
+
valuation: Valuation.valuationFor({
|
|
519
|
+
amount: BigInt(leg.amount),
|
|
520
|
+
denomination,
|
|
521
|
+
rates,
|
|
522
|
+
token: snapshot?.byAddress.get(leg.address.toLowerCase()),
|
|
523
|
+
}),
|
|
524
|
+
})
|
|
525
|
+
data = data.map((transfer) => ({
|
|
526
|
+
...transfer,
|
|
527
|
+
sourceToken: valued(transfer.sourceToken),
|
|
528
|
+
...(transfer.destinationToken
|
|
529
|
+
? { destinationToken: valued(transfer.destinationToken) }
|
|
530
|
+
: {}),
|
|
531
|
+
}))
|
|
532
|
+
|
|
533
|
+
const counts = countPromise ? await countPromise : undefined
|
|
534
|
+
const meta = {
|
|
535
|
+
...(rates ? { pricing: Valuation.pricing(rates, oracle) } : {}),
|
|
536
|
+
...counts,
|
|
537
|
+
}
|
|
470
538
|
|
|
471
539
|
return c.json(
|
|
472
540
|
Response.validated(schema.getTransfers.Response, {
|
|
473
541
|
data,
|
|
474
|
-
...(meta ? { meta } : {}),
|
|
542
|
+
...(Object.keys(meta).length > 0 ? { meta } : {}),
|
|
475
543
|
nextCursor: page.nextCursor,
|
|
476
544
|
}),
|
|
477
545
|
200,
|
|
478
546
|
)
|
|
479
547
|
} catch (cause) {
|
|
548
|
+
if (cause instanceof Valuation.UnsupportedDenominationError)
|
|
549
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
480
550
|
return Response.upstream(c, cause)
|
|
481
551
|
}
|
|
482
552
|
},
|
|
483
553
|
)
|
|
484
554
|
}
|
|
485
555
|
|
|
556
|
+
export declare namespace transfers {
|
|
557
|
+
/** Options for the transfer handlers. */
|
|
558
|
+
type Options = {
|
|
559
|
+
/** FX configuration backing leg valuation. */
|
|
560
|
+
fx?: Valuation.addresses.Fx | undefined
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
486
564
|
/**
|
|
487
565
|
* Shared TIP-20 `Transfer` page query. Used by `GET /transfers` and the
|
|
488
566
|
* webhook poller. Builds the signature-decoded `Transfer` CTE query, applies
|