tapimo 0.6.0 → 0.7.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 +8641 -8263
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +9 -1
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +4 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +112 -11
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +2 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/early-access.d.ts +2 -2
- package/dist/admin/apps/early-access.d.ts.map +1 -1
- package/dist/admin/apps/early-access.js +5 -5
- package/dist/admin/apps/early-access.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +124 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +97 -6
- package/dist/admin/apps/organizations.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 +95 -87
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +20 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +18 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +36 -7
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +5016 -4985
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +2 -15
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +1 -1
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +1 -1
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +27 -27
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +21 -1
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +58 -58
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +113 -113
- package/dist/apps/data/routes/fee-amm.d.ts +105 -91
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +76 -91
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +72 -34
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +34 -26
- 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 +6 -6
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokens.d.ts +2 -2
- package/dist/apps/data/routes/tokens.js +1 -1
- package/dist/apps/data/routes/transactions.d.ts +4 -4
- package/dist/apps/data/routes/transfers.d.ts +99 -88
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +226 -141
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +34 -34
- package/dist/apps/data/routes/valuation.js +3 -3
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +366 -366
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +460 -523
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -23
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +20 -8
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +106 -151
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +28 -28
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +40 -26
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +33 -18
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +131 -131
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +0 -3
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -8
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +0 -4
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +45 -45
- package/dist/apps/management/routes/orgs.d.ts +94 -94
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +5 -0
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +32 -32
- package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
- package/dist/apps/management/routes/webhooks.d.ts +1 -1
- package/dist/apps/mpp/App.d.ts +363 -0
- package/dist/apps/mpp/App.d.ts.map +1 -0
- package/dist/apps/mpp/App.js +72 -0
- package/dist/apps/mpp/App.js.map +1 -0
- package/dist/apps/mpp/Mpp.d.ts +178 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -0
- package/dist/apps/mpp/Mpp.js +183 -0
- package/dist/apps/mpp/Mpp.js.map +1 -0
- package/dist/apps/mpp/index.d.ts +3 -0
- package/dist/apps/mpp/index.d.ts.map +1 -0
- package/dist/apps/mpp/index.js +3 -0
- package/dist/apps/mpp/index.js.map +1 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +56 -9
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +27 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +11 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/enabledBillingSources.d.ts +73 -0
- package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
- package/dist/db/tables/enabledBillingSources.js +69 -0
- package/dist/db/tables/enabledBillingSources.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -12
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +3 -23
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +5 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +13 -2
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +1 -16
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +17 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +37 -5
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -32
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +3 -51
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +78 -32
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +45 -8
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +18 -4
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +50 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +15 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Response.d.ts +2 -2
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +12 -4
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +14 -2
- package/src/Client.test-d.ts +109 -28
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/apps/early-access.ts +6 -5
- package/src/admin/apps/organizations.test.ts +86 -2
- package/src/admin/apps/organizations.ts +155 -31
- package/src/admin/apps/scopes.test.ts +5 -0
- package/src/admin/ui/src/lib/organizations.test.ts +81 -1
- package/src/admin/ui/src/lib/organizations.ts +92 -0
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +8 -3
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui.generated.ts +95 -109
- package/src/analytics/Analytics.test.ts +83 -0
- package/src/analytics/Analytics.ts +20 -0
- package/src/analytics/tables/requestEvents.ts +60 -7
- package/src/apps/composition.test.ts +11 -2
- package/src/apps/data/App.test.ts +1 -1
- package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
- package/src/apps/data/FundingQuoteProviders.ts +2 -29
- package/src/apps/data/FxOracle.test.ts +2 -2
- package/src/apps/data/FxOracle.ts +2 -2
- package/src/apps/data/routes/activities.test.ts +6 -2
- package/src/apps/data/routes/activities.ts +39 -1
- package/src/apps/data/routes/balances.test.ts +6 -2
- package/src/apps/data/routes/exchanges.test.ts +7 -3
- package/src/apps/data/routes/fee-amm.test.ts +47 -23
- package/src/apps/data/routes/fee-amm.ts +98 -119
- package/src/apps/data/routes/funding.test.ts +29 -8
- package/src/apps/data/routes/funding.ts +42 -48
- package/src/apps/data/routes/receipts.test.ts +6 -2
- package/src/apps/data/routes/tokens.ts +1 -1
- package/src/apps/data/routes/transfers.test.ts +203 -35
- package/src/apps/data/routes/transfers.ts +294 -172
- package/src/apps/data/routes/valuation.test.ts +18 -3
- package/src/apps/data/routes/valuation.ts +4 -4
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +2 -75
- package/src/apps/management/routes/api-keys.ts +30 -7
- package/src/apps/management/routes/billing.test.ts +106 -85
- package/src/apps/management/routes/billing.ts +32 -21
- package/src/apps/management/routes/faucet.test.ts +11 -3
- package/src/apps/management/routes/faucet.ts +46 -30
- package/src/apps/management/routes/invitations.test.ts +0 -172
- package/src/apps/management/routes/invitations.ts +0 -3
- package/src/apps/management/routes/me.test.ts +3 -145
- package/src/apps/management/routes/me.ts +0 -6
- package/src/apps/management/routes/orgs.test.ts +45 -2
- package/src/apps/management/routes/orgs.ts +6 -0
- package/src/apps/management/routes/scopes.test.ts +1 -0
- package/src/apps/mpp/App.ts +86 -0
- package/src/apps/mpp/Mpp.test.ts +193 -0
- package/src/apps/mpp/Mpp.ts +353 -0
- package/src/apps/mpp/index.ts +2 -0
- package/src/apps/mpp/internal/Adapter.ts +11 -0
- package/src/apps/mpp/internal/Error.ts +9 -0
- package/src/apps/mpp/internal/Idempotency.ts +7 -0
- package/src/apps/mpp/internal/Screening.ts +10 -0
- package/src/apps/relay/App.test.ts +88 -6
- package/src/apps/relay/App.ts +61 -9
- package/src/db/Db.test.ts +2 -2
- package/src/db/Db.ts +30 -0
- package/src/db/Schema.ts +15 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +4 -26
- package/src/db/tables/organizations.test.ts +103 -0
- package/src/db/tables/organizations.ts +18 -2
- package/src/handlers/internal/utils.ts +22 -6
- package/src/handlers/relay.errors.test.ts +153 -0
- package/src/handlers/relay.ts +42 -7
- package/src/internal/Auth.test.ts +3 -3
- package/src/internal/Auth.ts +3 -63
- package/src/internal/FundingQuotes.test.ts +18 -5
- package/src/internal/FundingQuotes.ts +46 -11
- package/src/internal/Log.test.ts +91 -0
- package/src/internal/Log.ts +69 -5
- package/src/internal/MetricSink.test.ts +55 -0
- package/src/internal/MetricSink.ts +14 -0
- package/src/internal/Response.test.ts +28 -0
- package/src/internal/Response.ts +12 -4
|
@@ -9,6 +9,7 @@ import type * as Log from '../../../internal/Log.js'
|
|
|
9
9
|
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
10
10
|
import * as Response from '../../../internal/Response.js'
|
|
11
11
|
import * as Schema from '../../../internal/Schema.js'
|
|
12
|
+
import * as Value from '../../../internal/Value.js'
|
|
12
13
|
import * as FundingQuoteProviders from '../FundingQuoteProviders.js'
|
|
13
14
|
|
|
14
15
|
/** Zod schemas owned by funding quote resources. */
|
|
@@ -58,6 +59,12 @@ export namespace schema {
|
|
|
58
59
|
z.describe('Contract address, mint, or issuer address on the token chain.'),
|
|
59
60
|
z.meta({ examples: ['0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'] }),
|
|
60
61
|
),
|
|
62
|
+
currency: z
|
|
63
|
+
.string()
|
|
64
|
+
.check(
|
|
65
|
+
z.describe('Monetary denomination represented by this token.'),
|
|
66
|
+
z.meta({ examples: ['USD'] }),
|
|
67
|
+
),
|
|
61
68
|
decimals: z
|
|
62
69
|
.number()
|
|
63
70
|
.check(
|
|
@@ -97,22 +104,10 @@ export namespace schema {
|
|
|
97
104
|
'A normalized token reference for funding quotes.',
|
|
98
105
|
)
|
|
99
106
|
|
|
100
|
-
/**
|
|
107
|
+
/** Self-contained source or destination amount in a funding quote. */
|
|
101
108
|
export const TokenAmount = Schema.describe(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
z.describe("The requested source amount in the token's smallest unit."),
|
|
105
|
-
),
|
|
106
|
-
formatted: z
|
|
107
|
-
.string()
|
|
108
|
-
.check(
|
|
109
|
-
z.describe(
|
|
110
|
-
'The same amount in human-readable decimal form, using the source token’s `decimals`.',
|
|
111
|
-
),
|
|
112
|
-
z.meta({ examples: ['1'] }),
|
|
113
|
-
),
|
|
114
|
-
}),
|
|
115
|
-
'The requested source amount in provider-safe base units and display form.',
|
|
109
|
+
Schema.TokenAmount,
|
|
110
|
+
'A funding amount in base units and human-readable form.',
|
|
116
111
|
)
|
|
117
112
|
|
|
118
113
|
/** Provider summary embedded in a funding quote. */
|
|
@@ -171,7 +166,7 @@ export namespace schema {
|
|
|
171
166
|
'Machine-readable liquidity, fee, and source signals for a successful quote.',
|
|
172
167
|
)
|
|
173
168
|
|
|
174
|
-
/** Provider
|
|
169
|
+
/** Provider quote freshness data. */
|
|
175
170
|
export const QuoteDetails = Schema.describe(
|
|
176
171
|
z.object({
|
|
177
172
|
expiresAt: z
|
|
@@ -180,31 +175,6 @@ export namespace schema {
|
|
|
180
175
|
z.describe('Timestamp after which the provider quote is no longer valid.'),
|
|
181
176
|
z.meta({ examples: ['2026-01-01T00:01:00Z'] }),
|
|
182
177
|
),
|
|
183
|
-
inputAmount: Schema.DecimalString.check(
|
|
184
|
-
z.describe('Source amount sent to the provider in base units.'),
|
|
185
|
-
z.meta({ examples: ['1000000'] }),
|
|
186
|
-
),
|
|
187
|
-
minimumOutputAmount: z
|
|
188
|
-
.optional(Schema.DecimalString)
|
|
189
|
-
.check(
|
|
190
|
-
z.describe(
|
|
191
|
-
'Provider-derived minimum destination amount in base units. Symbiosis and Squid quotes use a fixed 1% slippage tolerance.',
|
|
192
|
-
),
|
|
193
|
-
z.meta({ examples: ['988000'] }),
|
|
194
|
-
),
|
|
195
|
-
outputAmount: z
|
|
196
|
-
.string()
|
|
197
|
-
.check(z.regex(/^\d+$/))
|
|
198
|
-
.check(
|
|
199
|
-
z.describe('Expected destination amount in base units.'),
|
|
200
|
-
z.meta({ examples: ['989102'] }),
|
|
201
|
-
),
|
|
202
|
-
outputAmountFormatted: z
|
|
203
|
-
.string()
|
|
204
|
-
.check(
|
|
205
|
-
z.describe('Expected destination amount rendered with destination token decimals.'),
|
|
206
|
-
z.meta({ examples: ['0.989102'] }),
|
|
207
|
-
),
|
|
208
178
|
sampledAt: z.iso
|
|
209
179
|
.datetime()
|
|
210
180
|
.check(
|
|
@@ -212,12 +182,20 @@ export namespace schema {
|
|
|
212
182
|
z.meta({ examples: ['2026-01-01T00:00:00Z'] }),
|
|
213
183
|
),
|
|
214
184
|
}),
|
|
215
|
-
'
|
|
185
|
+
'Freshness data for a provider funding quote.',
|
|
216
186
|
)
|
|
217
187
|
|
|
218
188
|
/** Successful live funding quote. */
|
|
219
189
|
export const FundingQuote = Schema.describe(
|
|
220
190
|
z.object({
|
|
191
|
+
destinationAmount: TokenAmount.check(z.describe('Expected destination amount.')),
|
|
192
|
+
destinationAmountMin: z
|
|
193
|
+
.optional(TokenAmount)
|
|
194
|
+
.check(
|
|
195
|
+
z.describe(
|
|
196
|
+
'Provider-derived minimum destination amount. Symbiosis and Squid quotes use a fixed 1% slippage tolerance.',
|
|
197
|
+
),
|
|
198
|
+
),
|
|
221
199
|
destinationChain: Chain.check(z.describe('Destination chain, always Tempo mainnet in V1.')),
|
|
222
200
|
destinationToken: Token.check(z.describe('Tempo token expected from this quote.')),
|
|
223
201
|
id: z
|
|
@@ -377,7 +355,20 @@ const fundingQuoteResponseExample = (() => {
|
|
|
377
355
|
return {
|
|
378
356
|
data: [
|
|
379
357
|
{
|
|
380
|
-
|
|
358
|
+
destinationAmount: Value.tokenAmount({
|
|
359
|
+
baseUnits: '979295',
|
|
360
|
+
currency: candidate.destinationToken.currency,
|
|
361
|
+
decimals: candidate.destinationToken.decimals,
|
|
362
|
+
}),
|
|
363
|
+
destinationAmountMin: Value.tokenAmount({
|
|
364
|
+
baseUnits: '939242',
|
|
365
|
+
currency: candidate.destinationToken.currency,
|
|
366
|
+
decimals: candidate.destinationToken.decimals,
|
|
367
|
+
}),
|
|
368
|
+
destinationChain: candidate.destinationChain,
|
|
369
|
+
destinationToken: candidate.destinationToken,
|
|
370
|
+
id: candidate.id,
|
|
371
|
+
provider: candidate.provider,
|
|
381
372
|
quality: {
|
|
382
373
|
estimatedSeconds: 1,
|
|
383
374
|
liquiditySource: 'providerQuote',
|
|
@@ -385,12 +376,15 @@ const fundingQuoteResponseExample = (() => {
|
|
|
385
376
|
tier: 'liquid',
|
|
386
377
|
},
|
|
387
378
|
quote: {
|
|
388
|
-
inputAmount: '1000000',
|
|
389
|
-
minimumOutputAmount: '939242',
|
|
390
|
-
outputAmount: '979295',
|
|
391
|
-
outputAmountFormatted: '0.979295',
|
|
392
379
|
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
393
380
|
},
|
|
381
|
+
sourceAmount: Value.tokenAmount({
|
|
382
|
+
baseUnits: candidate.sourceAmount.amount,
|
|
383
|
+
currency: candidate.sourceToken.currency,
|
|
384
|
+
decimals: candidate.sourceToken.decimals,
|
|
385
|
+
}),
|
|
386
|
+
sourceChain: candidate.sourceChain,
|
|
387
|
+
sourceToken: candidate.sourceToken,
|
|
394
388
|
},
|
|
395
389
|
],
|
|
396
390
|
}
|
|
@@ -508,7 +502,7 @@ export function funding(options: funding.Options = {}) {
|
|
|
508
502
|
}),
|
|
509
503
|
Cache.response<App.Environment>({
|
|
510
504
|
cacheControl: Cache.policies.stable,
|
|
511
|
-
name: 'tempo-api:funding-chains:
|
|
505
|
+
name: 'tempo-api:funding-chains:v2',
|
|
512
506
|
key: (c) => c.req.url,
|
|
513
507
|
}),
|
|
514
508
|
(c) => {
|
|
@@ -11,7 +11,11 @@ import * as Receipts from './receipts.js'
|
|
|
11
11
|
// Deterministic EUR-based rate set so conversion math is exact in assertions.
|
|
12
12
|
const fixed = FxOracle.from({
|
|
13
13
|
name: 'fixed',
|
|
14
|
-
rates: async () => ({
|
|
14
|
+
rates: async () => ({
|
|
15
|
+
asOf: '2026-01-01T00:00:00.000Z',
|
|
16
|
+
base: 'EUR',
|
|
17
|
+
rates: { AUD: '1.6', USD: '1.0' },
|
|
18
|
+
}),
|
|
15
19
|
})
|
|
16
20
|
|
|
17
21
|
/** The decoded `type` names a humanized receipt may report. */
|
|
@@ -217,7 +221,7 @@ describe('GET /transactions/receipts', () => {
|
|
|
217
221
|
})
|
|
218
222
|
expect(body.meta.pricing).toMatchInlineSnapshot(`
|
|
219
223
|
{
|
|
220
|
-
"asOf": "2026-01-
|
|
224
|
+
"asOf": "2026-01-01T00:00:00.000Z",
|
|
221
225
|
"basis": "nominal",
|
|
222
226
|
"source": "fixed",
|
|
223
227
|
}
|
|
@@ -1214,7 +1214,7 @@ declare namespace getTokenMetadata {
|
|
|
1214
1214
|
|
|
1215
1215
|
/**
|
|
1216
1216
|
* Resolves a token's curated R2 icon URI, memoized per `(chainId, address)`
|
|
1217
|
-
* so page enrichers (balances, holders, transfers `include=token`) don't
|
|
1217
|
+
* so page enrichers (balances, holders, transfers `include=token.logoUri`) don't
|
|
1218
1218
|
* re-fetch the asset per row on every request. Misses are memoized as `null`
|
|
1219
1219
|
* too — most tokens have no curated icon, and the negative lookup is exactly
|
|
1220
1220
|
* what page enrichment would otherwise repeat N times per page.
|
|
@@ -10,10 +10,28 @@ import * as Transfers from './transfers.js'
|
|
|
10
10
|
// Deterministic EUR-based rate set so conversion math is exact in assertions.
|
|
11
11
|
const fixed = FxOracle.from({
|
|
12
12
|
name: 'fixed',
|
|
13
|
-
rates: async () => ({
|
|
13
|
+
rates: async () => ({
|
|
14
|
+
asOf: '2026-01-01T00:00:00.000Z',
|
|
15
|
+
base: 'EUR',
|
|
16
|
+
rates: { AUD: '1.6', USD: '1.0' },
|
|
17
|
+
}),
|
|
14
18
|
})
|
|
15
19
|
|
|
16
20
|
describe('GET /transfers', () => {
|
|
21
|
+
test('parses granular token includes', () => {
|
|
22
|
+
expect(
|
|
23
|
+
Transfers.schema.getTransfers.Query.parse({
|
|
24
|
+
include: 'token.logoUri,token.verified',
|
|
25
|
+
}).include,
|
|
26
|
+
).toMatchInlineSnapshot(`
|
|
27
|
+
[
|
|
28
|
+
"token.logoUri",
|
|
29
|
+
"token.verified",
|
|
30
|
+
]
|
|
31
|
+
`)
|
|
32
|
+
expect(Transfers.schema.getTransfers.Query.safeParse({ include: 'token' }).success).toBe(false)
|
|
33
|
+
})
|
|
34
|
+
|
|
17
35
|
test('degrades unavailable verification data to null valuations', async () => {
|
|
18
36
|
const db = () =>
|
|
19
37
|
Db.postgres({
|
|
@@ -29,7 +47,7 @@ describe('GET /transfers', () => {
|
|
|
29
47
|
|
|
30
48
|
expect(response.status).toBe(200)
|
|
31
49
|
expect(body.data.length).toBeGreaterThan(0)
|
|
32
|
-
for (const entry of body.data) expect(entry.
|
|
50
|
+
for (const entry of body.data) expect(entry.sourceAmount.valuation).toBeNull()
|
|
33
51
|
|
|
34
52
|
const emptyResponse = await client.v1.transfers.$get(
|
|
35
53
|
{ query: { limit: '5', token: `0x${'11'.repeat(20)}` } },
|
|
@@ -57,7 +75,7 @@ describe('GET /transfers', () => {
|
|
|
57
75
|
expect(unsupported.error.code).toBe('query_invalid')
|
|
58
76
|
})
|
|
59
77
|
|
|
60
|
-
test('returns an unscoped transfer page
|
|
78
|
+
test('returns an unscoped transfer page with token metadata', async () => {
|
|
61
79
|
const client = TestApp.client()
|
|
62
80
|
const baseResponse = await client.v1.transfers.$get({ query: { limit: '5' } }, TestApp.auth)
|
|
63
81
|
const base = await TestApp.json(baseResponse, Transfers.schema.getTransfers.Response)
|
|
@@ -74,7 +92,14 @@ describe('GET /transfers', () => {
|
|
|
74
92
|
base.data.every(
|
|
75
93
|
(entry) =>
|
|
76
94
|
/^0x[0-9a-f]{40}$/.test(entry.sourceToken.address) &&
|
|
77
|
-
/^\d+$/.test(entry.
|
|
95
|
+
/^\d+$/.test(entry.sourceAmount.baseUnits) &&
|
|
96
|
+
typeof entry.sourceAmount.currency === 'string' &&
|
|
97
|
+
Number.isInteger(entry.sourceAmount.decimals) &&
|
|
98
|
+
typeof entry.sourceAmount.formatted === 'string' &&
|
|
99
|
+
typeof entry.sourceToken.currency === 'string' &&
|
|
100
|
+
Number.isInteger(entry.sourceToken.decimals) &&
|
|
101
|
+
typeof entry.sourceToken.name === 'string' &&
|
|
102
|
+
typeof entry.sourceToken.symbol === 'string' &&
|
|
78
103
|
Number.isInteger(entry.blockNumber) &&
|
|
79
104
|
/^0x[0-9a-f]{40}$/.test(entry.sender) &&
|
|
80
105
|
/^0x[0-9a-f]{40}$/.test(entry.recipient) &&
|
|
@@ -85,28 +110,35 @@ describe('GET /transfers', () => {
|
|
|
85
110
|
// Transfers default to newest-first (descending block number).
|
|
86
111
|
const blocks = base.data.map((entry) => entry.blockNumber)
|
|
87
112
|
expect(blocks.every((block, index) => index === 0 || blocks[index - 1]! >= block)).toBe(true)
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
// Quantities live beside the token reference; curated fields remain opt-in.
|
|
114
|
+
expect(
|
|
115
|
+
base.data.every(
|
|
116
|
+
(entry) =>
|
|
117
|
+
!('amount' in entry.sourceToken) &&
|
|
118
|
+
!('valuation' in entry.sourceToken) &&
|
|
119
|
+
entry.sourceToken.logoUri === undefined &&
|
|
120
|
+
entry.sourceToken.verified === undefined,
|
|
121
|
+
),
|
|
122
|
+
).toBe(true)
|
|
91
123
|
|
|
92
124
|
const includedResponse = await client.v1.transfers.$get(
|
|
93
|
-
{ query: { limit: '5', include: 'token' } },
|
|
125
|
+
{ query: { limit: '5', include: 'token.logoUri,token.verified' } },
|
|
94
126
|
TestApp.auth,
|
|
95
127
|
)
|
|
96
128
|
const included = await TestApp.json(includedResponse, Transfers.schema.getTransfers.Response)
|
|
97
129
|
|
|
98
130
|
expect(includedResponse.status).toBe(200)
|
|
99
|
-
//
|
|
100
|
-
// it is spread alongside. Resolution is best-effort, so a row whose
|
|
101
|
-
// metadata is unavailable simply omits the metadata fields.
|
|
131
|
+
// Curated fields remain optional without moving quantities back into the token.
|
|
102
132
|
expect(
|
|
103
133
|
included.data.every(
|
|
104
134
|
(entry) =>
|
|
105
135
|
/^0x[0-9a-f]{40}$/.test(entry.sourceToken.address) &&
|
|
106
|
-
/^\d+$/.test(entry.
|
|
107
|
-
(entry.sourceToken
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
/^\d+$/.test(entry.sourceAmount.baseUnits) &&
|
|
137
|
+
!('amount' in entry.sourceToken) &&
|
|
138
|
+
!('valuation' in entry.sourceToken) &&
|
|
139
|
+
typeof entry.sourceToken.symbol === 'string' &&
|
|
140
|
+
Number.isInteger(entry.sourceToken.decimals) &&
|
|
141
|
+
typeof entry.sourceToken.verified === 'boolean',
|
|
110
142
|
),
|
|
111
143
|
).toBe(true)
|
|
112
144
|
})
|
|
@@ -125,24 +157,156 @@ describe('GET /transfers', () => {
|
|
|
125
157
|
|
|
126
158
|
test('narrows transfers by address (either side)', async () => {
|
|
127
159
|
const client = TestApp.client()
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{ query: { limit: '5', address: account } },
|
|
160
|
+
const runtime = Runtime.get()
|
|
161
|
+
const address = Schema.Address.parse(
|
|
162
|
+
runtime.mode === 'testnet'
|
|
163
|
+
? '0xb1f382bd6deed37895528a6387bae4cd9569ac3c'
|
|
164
|
+
: runtime.fixtures!.fundedAccounts[0]!,
|
|
165
|
+
)
|
|
166
|
+
const response = await client.v1.transfers.$get(
|
|
167
|
+
{ query: { address, include: 'totalCount', limit: '10' } },
|
|
137
168
|
TestApp.auth,
|
|
138
169
|
)
|
|
139
|
-
const
|
|
140
|
-
|
|
170
|
+
const body = await TestApp.json(response, Transfers.schema.getTransfers.Response)
|
|
171
|
+
|
|
172
|
+
expect(response.status).toBe(200)
|
|
141
173
|
expect(
|
|
142
|
-
|
|
174
|
+
body.data.every((entry) => entry.sender === address || entry.recipient === address),
|
|
143
175
|
).toBe(true)
|
|
176
|
+
expect(body.meta!.totalCount).toBeGreaterThanOrEqual(body.data.length)
|
|
177
|
+
expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount! >= Schema.countCap)
|
|
144
178
|
})
|
|
145
179
|
|
|
180
|
+
test.runIf(Runtime.get().mode === 'testnet')(
|
|
181
|
+
'merges both address sides before paginating',
|
|
182
|
+
async () => {
|
|
183
|
+
const client = TestApp.client()
|
|
184
|
+
const address = '0xabb0023644c54a20d0fe92becfa764dacaf59ba9'
|
|
185
|
+
const toBlock = '27608730'
|
|
186
|
+
const [senderResponse, recipientResponse, addressResponse] = await Promise.all([
|
|
187
|
+
client.v1.transfers.$get(
|
|
188
|
+
{
|
|
189
|
+
query: { 'blockNumber.to': toBlock, limit: '10', order: 'desc', sender: address },
|
|
190
|
+
},
|
|
191
|
+
TestApp.auth,
|
|
192
|
+
),
|
|
193
|
+
client.v1.transfers.$get(
|
|
194
|
+
{
|
|
195
|
+
query: { 'blockNumber.to': toBlock, limit: '10', order: 'desc', recipient: address },
|
|
196
|
+
},
|
|
197
|
+
TestApp.auth,
|
|
198
|
+
),
|
|
199
|
+
client.v1.transfers.$get(
|
|
200
|
+
{
|
|
201
|
+
query: { address, 'blockNumber.to': toBlock, limit: '10', order: 'desc' },
|
|
202
|
+
},
|
|
203
|
+
TestApp.auth,
|
|
204
|
+
),
|
|
205
|
+
])
|
|
206
|
+
const sender = await TestApp.json(senderResponse, Transfers.schema.getTransfers.Response)
|
|
207
|
+
const recipient = await TestApp.json(
|
|
208
|
+
recipientResponse,
|
|
209
|
+
Transfers.schema.getTransfers.Response,
|
|
210
|
+
)
|
|
211
|
+
const combined = await TestApp.json(addressResponse, Transfers.schema.getTransfers.Response)
|
|
212
|
+
|
|
213
|
+
expect([senderResponse.status, recipientResponse.status, addressResponse.status]).toEqual([
|
|
214
|
+
200, 200, 200,
|
|
215
|
+
])
|
|
216
|
+
const seen = new Set<string>()
|
|
217
|
+
const expected = [...sender.data, ...recipient.data]
|
|
218
|
+
.sort((a, b) => b.blockNumber - a.blockNumber || b.logIndex - a.logIndex)
|
|
219
|
+
.filter((entry) => {
|
|
220
|
+
if (seen.has(entry.id)) return false
|
|
221
|
+
seen.add(entry.id)
|
|
222
|
+
return true
|
|
223
|
+
})
|
|
224
|
+
.slice(0, 10)
|
|
225
|
+
expect(expected).toHaveLength(10)
|
|
226
|
+
expect(combined.data.map(({ id }) => id)).toEqual(expected.map(({ id }) => id))
|
|
227
|
+
|
|
228
|
+
const firstResponse = await client.v1.transfers.$get(
|
|
229
|
+
{
|
|
230
|
+
query: { address, 'blockNumber.to': toBlock, limit: '5', order: 'desc' },
|
|
231
|
+
},
|
|
232
|
+
TestApp.auth,
|
|
233
|
+
)
|
|
234
|
+
const first = await TestApp.json(firstResponse, Transfers.schema.getTransfers.Response)
|
|
235
|
+
expect(firstResponse.status).toBe(200)
|
|
236
|
+
expect(first.nextCursor).not.toBeNull()
|
|
237
|
+
if (!first.nextCursor) return
|
|
238
|
+
|
|
239
|
+
const [cursorResponse, positionalResponse] = await Promise.all([
|
|
240
|
+
client.v1.transfers.$get(
|
|
241
|
+
{
|
|
242
|
+
query: {
|
|
243
|
+
address,
|
|
244
|
+
'blockNumber.to': toBlock,
|
|
245
|
+
cursor: first.nextCursor,
|
|
246
|
+
limit: '5',
|
|
247
|
+
order: 'desc',
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
TestApp.auth,
|
|
251
|
+
),
|
|
252
|
+
client.v1.transfers.$get(
|
|
253
|
+
{
|
|
254
|
+
query: {
|
|
255
|
+
address,
|
|
256
|
+
'blockNumber.to': toBlock,
|
|
257
|
+
limit: '5',
|
|
258
|
+
order: 'desc',
|
|
259
|
+
page: '2',
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
TestApp.auth,
|
|
263
|
+
),
|
|
264
|
+
])
|
|
265
|
+
const cursor = await TestApp.json(cursorResponse, Transfers.schema.getTransfers.Response)
|
|
266
|
+
const positional = await TestApp.json(
|
|
267
|
+
positionalResponse,
|
|
268
|
+
Transfers.schema.getTransfers.Response,
|
|
269
|
+
)
|
|
270
|
+
const expectedSecondPage = expected.slice(5, 10).map(({ id }) => id)
|
|
271
|
+
|
|
272
|
+
expect([cursorResponse.status, positionalResponse.status]).toEqual([200, 200])
|
|
273
|
+
expect([...first.data, ...cursor.data].map(({ id }) => id)).toEqual(
|
|
274
|
+
expected.map(({ id }) => id),
|
|
275
|
+
)
|
|
276
|
+
expect(positional.data.map(({ id }) => id)).toEqual(expectedSecondPage)
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
test.runIf(Runtime.get().mode === 'testnet')(
|
|
281
|
+
'deduplicates self-transfers in address pages and counts',
|
|
282
|
+
async () => {
|
|
283
|
+
const client = TestApp.client()
|
|
284
|
+
const address = '0xabb0023644c54a20d0fe92becfa764dacaf59ba9'
|
|
285
|
+
const transactionHash = '0x915a11bcf4b5e667c631f9fb09ac8729f3b436f24eb0b552e54ec13a490ef57f'
|
|
286
|
+
const response = await client.v1.transfers.$get(
|
|
287
|
+
{
|
|
288
|
+
query: {
|
|
289
|
+
address,
|
|
290
|
+
'blockNumber.from': '27608730',
|
|
291
|
+
'blockNumber.to': '27608730',
|
|
292
|
+
include: 'totalCount',
|
|
293
|
+
limit: '50',
|
|
294
|
+
token: '0x20c0000000000000000000000000000000000003',
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
TestApp.auth,
|
|
298
|
+
)
|
|
299
|
+
const body = await TestApp.json(response, Transfers.schema.getTransfers.Response)
|
|
300
|
+
|
|
301
|
+
expect(response.status).toBe(200)
|
|
302
|
+
expect(body.data.filter(({ id }) => id === `${transactionHash}-233`)).toHaveLength(1)
|
|
303
|
+
expect(body.meta).toMatchObject({
|
|
304
|
+
totalCount: body.data.length,
|
|
305
|
+
totalCountCapped: false,
|
|
306
|
+
})
|
|
307
|
+
},
|
|
308
|
+
)
|
|
309
|
+
|
|
146
310
|
test('narrows transfers by sender and recipient independently', async () => {
|
|
147
311
|
const client = TestApp.client()
|
|
148
312
|
const seedResponse = await client.v1.transfers.$get({ query: { limit: '5' } }, TestApp.auth)
|
|
@@ -208,7 +372,7 @@ describe('GET /transfers', () => {
|
|
|
208
372
|
// `(txHash, blockNumber)` shortcut is not unique here.
|
|
209
373
|
type Entry = z.output<typeof Transfers.schema.getTransfers.Transfer>
|
|
210
374
|
const keyOf = (entry: Entry) =>
|
|
211
|
-
`${entry.blockNumber}:${entry.transactionHash}:${entry.sourceToken.address}:${entry.sender}:${entry.recipient}:${entry.
|
|
375
|
+
`${entry.blockNumber}:${entry.transactionHash}:${entry.sourceToken.address}:${entry.sender}:${entry.recipient}:${entry.sourceAmount.baseUnits}`
|
|
212
376
|
const firstKeys = new Set(first.data.map(keyOf))
|
|
213
377
|
expect(second.data.every((entry) => !firstKeys.has(keyOf(entry)))).toBe(true)
|
|
214
378
|
})
|
|
@@ -256,7 +420,7 @@ describe('GET /transfers', () => {
|
|
|
256
420
|
|
|
257
421
|
type Entry = z.output<typeof Transfers.schema.getTransfers.Transfer>
|
|
258
422
|
const keyOf = (entry: Entry) =>
|
|
259
|
-
`${entry.blockNumber}:${entry.transactionHash}:${entry.sourceToken.address}:${entry.sender}:${entry.recipient}:${entry.
|
|
423
|
+
`${entry.blockNumber}:${entry.transactionHash}:${entry.sourceToken.address}:${entry.sender}:${entry.recipient}:${entry.sourceAmount.baseUnits}`
|
|
260
424
|
expect(page.data.map(keyOf)).toEqual(head.data.slice(5, 10).map(keyOf))
|
|
261
425
|
// Offset pages still return a keyset cursor so clients can switch to deep
|
|
262
426
|
// cursor traversal from a positional jump.
|
|
@@ -408,6 +572,10 @@ describe('GET /transfers', () => {
|
|
|
408
572
|
expect(transfer.recipient).toBe(crossToken.recipient)
|
|
409
573
|
expect(transfer.sourceToken.address).toBe(crossToken.sourceToken)
|
|
410
574
|
expect(transfer.destinationToken?.address).toBe(crossToken.destinationToken)
|
|
575
|
+
expect(transfer.sourceAmount.baseUnits).toMatch(/^\d+$/)
|
|
576
|
+
expect(transfer.destinationAmount?.baseUnits).toMatch(/^\d+$/)
|
|
577
|
+
expect('amount' in transfer.sourceToken).toBe(false)
|
|
578
|
+
expect('amount' in transfer.destinationToken!).toBe(false)
|
|
411
579
|
|
|
412
580
|
// Without `include=crossToken`, the transaction stays as its raw per-log
|
|
413
581
|
// legs (more than one row) and no row carries `destinationToken`.
|
|
@@ -494,8 +662,8 @@ describe('GET /transfers', () => {
|
|
|
494
662
|
expect(response.status).toBe(200)
|
|
495
663
|
expect(body.data.length).toBeGreaterThan(0)
|
|
496
664
|
for (const entry of body.data)
|
|
497
|
-
expect(entry.
|
|
498
|
-
amount: core_Value.format(BigInt(entry.
|
|
665
|
+
expect(entry.sourceAmount.valuation).toEqual({
|
|
666
|
+
amount: core_Value.format(BigInt(entry.sourceAmount.baseUnits), 6),
|
|
499
667
|
currency: 'USD',
|
|
500
668
|
})
|
|
501
669
|
expect(body.meta?.pricing).toBeUndefined()
|
|
@@ -517,13 +685,13 @@ describe('GET /transfers', () => {
|
|
|
517
685
|
expect(body.data.length).toBeGreaterThan(0)
|
|
518
686
|
// USD -> AUD at the fixed 1.6 cross rate, floored to 6 dp.
|
|
519
687
|
for (const entry of body.data)
|
|
520
|
-
expect(entry.
|
|
521
|
-
amount: core_Value.format((BigInt(entry.
|
|
688
|
+
expect(entry.sourceAmount.valuation).toEqual({
|
|
689
|
+
amount: core_Value.format((BigInt(entry.sourceAmount.baseUnits) * 16n) / 10n, 6),
|
|
522
690
|
currency: 'AUD',
|
|
523
691
|
})
|
|
524
692
|
expect(body.meta?.pricing).toMatchInlineSnapshot(`
|
|
525
693
|
{
|
|
526
|
-
"asOf": "2026-01-
|
|
694
|
+
"asOf": "2026-01-01T00:00:00.000Z",
|
|
527
695
|
"basis": "nominal",
|
|
528
696
|
"source": "fixed",
|
|
529
697
|
}
|
|
@@ -560,7 +728,7 @@ describe('GET /transfers', () => {
|
|
|
560
728
|
|
|
561
729
|
expect(response.status).toBe(200)
|
|
562
730
|
expect(body.data.length).toBeGreaterThan(0)
|
|
563
|
-
for (const entry of body.data) expect(entry.
|
|
731
|
+
for (const entry of body.data) expect(entry.sourceAmount.valuation).toBeNull()
|
|
564
732
|
},
|
|
565
733
|
)
|
|
566
734
|
|