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
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { Hono, type Context } from 'hono'
|
|
2
|
+
import { Value as core_Value } from 'ox'
|
|
3
|
+
import { BaseError, ContractFunctionRevertedError, ContractFunctionZeroDataError } from 'viem'
|
|
4
|
+
import { Actions } from 'viem/tempo'
|
|
5
|
+
import * as z from 'zod/mini'
|
|
6
|
+
|
|
7
|
+
import type * as App from '../../../App.js'
|
|
8
|
+
import * as Auth from '../../../internal/Auth.js'
|
|
9
|
+
import * as Cache from '../../../internal/Cache.js'
|
|
10
|
+
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
11
|
+
import * as Response from '../../../internal/Response.js'
|
|
12
|
+
import * as Schema from '../../../internal/Schema.js'
|
|
13
|
+
import * as Store from '../../../internal/Store.js'
|
|
14
|
+
import * as Timing from '../../../internal/Timing.js'
|
|
15
|
+
import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
|
|
16
|
+
import * as FxOracle from '../FxOracle.js'
|
|
17
|
+
|
|
18
|
+
// How many balance reads to fan out per batch. Concurrent reads in one
|
|
19
|
+
// macrotask collapse into a single deployless multicall; small chunks keep
|
|
20
|
+
// each call under the Tempo RPC's per-call size limit.
|
|
21
|
+
const tokenReadChunkSize = 20
|
|
22
|
+
|
|
23
|
+
/** Zod schemas owned by the valuation handlers. */
|
|
24
|
+
export namespace schema {
|
|
25
|
+
/** A holding's nominal value in the requested denomination. */
|
|
26
|
+
export const Value = z
|
|
27
|
+
.object({
|
|
28
|
+
amount: z
|
|
29
|
+
.string()
|
|
30
|
+
.check(
|
|
31
|
+
z.regex(/^\d+(\.\d+)?$/),
|
|
32
|
+
z.describe('The holding’s nominal value, as a decimal string in `currency`.'),
|
|
33
|
+
z.meta({ examples: ['1.50'] }),
|
|
34
|
+
),
|
|
35
|
+
currency: z
|
|
36
|
+
.string()
|
|
37
|
+
.check(
|
|
38
|
+
z.describe('The denomination currency of this value.'),
|
|
39
|
+
z.meta({ examples: ['USD'] }),
|
|
40
|
+
),
|
|
41
|
+
})
|
|
42
|
+
.check(z.describe('The holding’s nominal value in the requested denomination.'))
|
|
43
|
+
|
|
44
|
+
/** Rate provenance for a converted valuation. */
|
|
45
|
+
export const Pricing = z
|
|
46
|
+
.object({
|
|
47
|
+
asOf: z
|
|
48
|
+
.string()
|
|
49
|
+
.check(
|
|
50
|
+
z.describe('Publication date of the rate set used for conversion.'),
|
|
51
|
+
z.meta({ examples: ['2026-07-20'] }),
|
|
52
|
+
),
|
|
53
|
+
basis: z
|
|
54
|
+
.literal('nominal')
|
|
55
|
+
.check(
|
|
56
|
+
z.describe('Valuation basis: one token unit counts as one unit of its display currency.'),
|
|
57
|
+
z.meta({ examples: ['nominal'] }),
|
|
58
|
+
),
|
|
59
|
+
source: z
|
|
60
|
+
.string()
|
|
61
|
+
.check(
|
|
62
|
+
z.describe('FX rate oracle that supplied the conversion rates.'),
|
|
63
|
+
z.meta({ examples: ['ecb'] }),
|
|
64
|
+
),
|
|
65
|
+
})
|
|
66
|
+
.check(z.describe('Rate provenance for a converted valuation.'))
|
|
67
|
+
|
|
68
|
+
/** Schemas for the getAddressValuation operation. */
|
|
69
|
+
export namespace getAddressValuation {
|
|
70
|
+
/** Path parameters for address valuation requests. */
|
|
71
|
+
export const Params = z
|
|
72
|
+
.object({
|
|
73
|
+
address: Schema.Address.check(
|
|
74
|
+
z.describe('The account address whose holdings you want to value.'),
|
|
75
|
+
),
|
|
76
|
+
})
|
|
77
|
+
.check(z.describe('Path parameters for valuing an account’s verified holdings.'))
|
|
78
|
+
|
|
79
|
+
/** Query parameters for address valuation requests. */
|
|
80
|
+
export const Query = z
|
|
81
|
+
.strictObject({
|
|
82
|
+
chainId: Schema.ChainIdQuery,
|
|
83
|
+
denomination: Schema.Denomination,
|
|
84
|
+
})
|
|
85
|
+
.check(z.describe('Query parameters for valuing an account’s verified holdings.'))
|
|
86
|
+
|
|
87
|
+
/** The nominal valuation of an account's verified holdings. */
|
|
88
|
+
export const Response = z
|
|
89
|
+
.object({
|
|
90
|
+
address: Schema.Address.check(z.describe('The account address this valuation covers.')),
|
|
91
|
+
amount: z
|
|
92
|
+
.string()
|
|
93
|
+
.check(
|
|
94
|
+
z.regex(/^\d+(\.\d+)?$/),
|
|
95
|
+
z.describe(
|
|
96
|
+
'The total value of the account’s verified holdings, as a decimal string in `currency`.',
|
|
97
|
+
),
|
|
98
|
+
z.meta({ examples: ['160.50'] }),
|
|
99
|
+
),
|
|
100
|
+
currency: z
|
|
101
|
+
.string()
|
|
102
|
+
.check(
|
|
103
|
+
z.describe('The denomination currency of `amount`.'),
|
|
104
|
+
z.meta({ examples: ['USD'] }),
|
|
105
|
+
),
|
|
106
|
+
id: z
|
|
107
|
+
.string()
|
|
108
|
+
.check(
|
|
109
|
+
z.describe('A stable resource ID for this valuation, equal to the account address.'),
|
|
110
|
+
z.meta({ examples: ['0xbe058e1c4df8a4366a387bf595b284246a93039e'] }),
|
|
111
|
+
),
|
|
112
|
+
pricing: z
|
|
113
|
+
.nullable(Pricing)
|
|
114
|
+
.check(
|
|
115
|
+
z.describe(
|
|
116
|
+
'Rate provenance, or `null` when every valued holding was already denominated in `currency`.',
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
unpriced: z
|
|
120
|
+
.array(z.string())
|
|
121
|
+
.check(
|
|
122
|
+
z.describe(
|
|
123
|
+
'Display currencies excluded from `amount` because the FX oracle has no rate for them.',
|
|
124
|
+
),
|
|
125
|
+
z.meta({ examples: [['BTC']] }),
|
|
126
|
+
),
|
|
127
|
+
})
|
|
128
|
+
.check(z.describe('The nominal valuation of an account’s verified token holdings.'))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Creates address-scoped valuation handlers, mounted under the `/addresses`
|
|
134
|
+
* composer. Exposes `GET /:address/valuation`, the holdings valuation.
|
|
135
|
+
*/
|
|
136
|
+
export function addresses(options: addresses.Options = {}) {
|
|
137
|
+
const oracle = options.fx?.oracle ?? FxOracle.ecb()
|
|
138
|
+
return new Hono<App.Environment>().get(
|
|
139
|
+
'/v1/addresses/:address/valuation',
|
|
140
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
141
|
+
OpenApi.validate('param', schema.getAddressValuation.Params, {
|
|
142
|
+
code: 'address_invalid',
|
|
143
|
+
message: 'Invalid account address',
|
|
144
|
+
}),
|
|
145
|
+
OpenApi.validate('query', schema.getAddressValuation.Query, {
|
|
146
|
+
code: 'query_invalid',
|
|
147
|
+
message: 'Invalid query parameters',
|
|
148
|
+
}),
|
|
149
|
+
OpenApi.describeRoute({
|
|
150
|
+
description:
|
|
151
|
+
'Values every verified TIP-20 token an account holds as one unit of its display currency, converts each currency into the requested denomination using the configured FX rates, and returns the total.',
|
|
152
|
+
operationId: 'getAddressValuation',
|
|
153
|
+
responses: OpenApi.responses({
|
|
154
|
+
errors: { 502: 'Could not read balances or exchange rates from an upstream service.' },
|
|
155
|
+
success: {
|
|
156
|
+
description: 'The valuation of this account’s verified holdings.',
|
|
157
|
+
schema: schema.getAddressValuation.Response,
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
summary: 'Get address valuation',
|
|
161
|
+
tags: ['Balances'],
|
|
162
|
+
}),
|
|
163
|
+
Cache.response<App.Environment>({
|
|
164
|
+
cacheControl: Cache.policies.noStore,
|
|
165
|
+
name: 'tempo-api:addresses:v1',
|
|
166
|
+
key: (c) => Cache.urlKey(c, schema.getAddressValuation.Query),
|
|
167
|
+
}),
|
|
168
|
+
async (c) => {
|
|
169
|
+
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
170
|
+
if (OpenApi.narrowValidation)
|
|
171
|
+
return OpenApi.validationError(c, {
|
|
172
|
+
code: 'address_invalid',
|
|
173
|
+
message: 'Invalid request parameters',
|
|
174
|
+
})
|
|
175
|
+
if (OpenApi.narrowValidation)
|
|
176
|
+
return OpenApi.validationError(c, {
|
|
177
|
+
code: 'query_invalid',
|
|
178
|
+
message: 'Invalid request parameters',
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const { address } = c.req.valid('param')
|
|
182
|
+
const query = c.req.valid('query')
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const valuation = await getAddressValuation(c, {
|
|
186
|
+
address,
|
|
187
|
+
chainId: query.chainId ?? c.get('chainId'),
|
|
188
|
+
denomination: query.denomination ?? 'USD',
|
|
189
|
+
oracle,
|
|
190
|
+
})
|
|
191
|
+
return c.json(Response.validated(schema.getAddressValuation.Response, valuation), 200)
|
|
192
|
+
} catch (cause) {
|
|
193
|
+
if (cause instanceof UnsupportedDenominationError)
|
|
194
|
+
return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
|
|
195
|
+
return Response.upstream(c, cause)
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export declare namespace addresses {
|
|
202
|
+
/** Options for the address-scoped valuation handlers. */
|
|
203
|
+
type Options = {
|
|
204
|
+
/** FX configuration backing currency conversion. */
|
|
205
|
+
fx?: Fx | undefined
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** FX configuration for valuation handlers. */
|
|
209
|
+
type Fx = {
|
|
210
|
+
/** FX rate oracle. Defaults to `FxOracle.ecb()`. */
|
|
211
|
+
oracle?: FxOracle.Oracle | undefined
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function getAddressValuation(
|
|
216
|
+
c: Context<App.Environment>,
|
|
217
|
+
options: getAddressValuation.Options,
|
|
218
|
+
) {
|
|
219
|
+
const { address, chainId, denomination, oracle } = options
|
|
220
|
+
const [set, ratesCause] = await rateSet(c, oracle).then(
|
|
221
|
+
(set) => [set, undefined] as const,
|
|
222
|
+
(cause: unknown) => [undefined, cause] as const,
|
|
223
|
+
)
|
|
224
|
+
if (set && denomination !== set.base && set.rates[denomination] === undefined)
|
|
225
|
+
throw new UnsupportedDenominationError(denomination, oracle.name)
|
|
226
|
+
const snapshot = await VerifiedTokens.snapshot(c, chainId)
|
|
227
|
+
|
|
228
|
+
type Holding = { amount: bigint; token: VerifiedTokens.Snapshot['list'][number] }
|
|
229
|
+
const holdings = await Timing.time(c, 'valuation_balances', async () => {
|
|
230
|
+
const client = c.get('getClient')(chainId)
|
|
231
|
+
if (snapshot.list.length === 0) return []
|
|
232
|
+
// Pin every chunk to one chain-state snapshot.
|
|
233
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 })
|
|
234
|
+
const rows: Holding[] = []
|
|
235
|
+
for (let index = 0; index < snapshot.list.length; index += tokenReadChunkSize) {
|
|
236
|
+
const group = snapshot.list.slice(index, index + tokenReadChunkSize)
|
|
237
|
+
// Concurrent reads enter viem's shared batch window and become one
|
|
238
|
+
// deployless multicall. A failed read fails the request rather than
|
|
239
|
+
// serving a partial total.
|
|
240
|
+
const results = await Promise.all(
|
|
241
|
+
group.map(async (token) => {
|
|
242
|
+
try {
|
|
243
|
+
const { amount } = await Actions.token.getBalance(client, {
|
|
244
|
+
account: address,
|
|
245
|
+
blockNumber,
|
|
246
|
+
decimals: token.decimals,
|
|
247
|
+
token: token.address,
|
|
248
|
+
})
|
|
249
|
+
return amount === 0n ? undefined : { amount, token }
|
|
250
|
+
} catch (error) {
|
|
251
|
+
// A verified token that is undeployed (zero data) or uninitialized
|
|
252
|
+
// on this chain cannot be held; count it as an empty holding
|
|
253
|
+
// rather than failing the valuation.
|
|
254
|
+
const empty =
|
|
255
|
+
error instanceof BaseError &&
|
|
256
|
+
error.walk(
|
|
257
|
+
(cause) =>
|
|
258
|
+
cause instanceof ContractFunctionZeroDataError ||
|
|
259
|
+
(cause instanceof ContractFunctionRevertedError &&
|
|
260
|
+
cause.data?.errorName === 'Uninitialized'),
|
|
261
|
+
)
|
|
262
|
+
if (empty) return undefined
|
|
263
|
+
throw error
|
|
264
|
+
}
|
|
265
|
+
}),
|
|
266
|
+
)
|
|
267
|
+
rows.push(...results.filter((row) => row !== undefined))
|
|
268
|
+
}
|
|
269
|
+
return rows
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// Aggregate holdings by display currency at a shared 18-dp scale so tokens
|
|
273
|
+
// with different decimals sum exactly.
|
|
274
|
+
type Group = { currency: string; sum: bigint }
|
|
275
|
+
const groups = new Map<string, Group>()
|
|
276
|
+
for (const { amount, token } of holdings) {
|
|
277
|
+
const group = groups.get(token.currency.toLowerCase()) ?? { currency: token.currency, sum: 0n }
|
|
278
|
+
group.sum +=
|
|
279
|
+
token.decimals <= FxOracle.precision
|
|
280
|
+
? amount * 10n ** BigInt(FxOracle.precision - token.decimals)
|
|
281
|
+
: amount / 10n ** BigInt(token.decimals - FxOracle.precision)
|
|
282
|
+
groups.set(token.currency.toLowerCase(), group)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let total = 0n
|
|
286
|
+
const unpriced: string[] = []
|
|
287
|
+
let pricing: z.output<typeof schema.Pricing> | null = null
|
|
288
|
+
|
|
289
|
+
const pending = [...groups.values()].filter(
|
|
290
|
+
(group) => group.currency.toUpperCase() !== denomination,
|
|
291
|
+
)
|
|
292
|
+
for (const group of groups.values())
|
|
293
|
+
if (group.currency.toUpperCase() === denomination) total += group.sum
|
|
294
|
+
|
|
295
|
+
if (pending.length > 0) {
|
|
296
|
+
if (!set) throw ratesCause
|
|
297
|
+
for (const group of pending) {
|
|
298
|
+
const rate = FxOracle.rate(set, { from: group.currency.toUpperCase(), to: denomination })
|
|
299
|
+
if (rate === undefined) unpriced.push(group.currency)
|
|
300
|
+
else total += (group.sum * rate) / 10n ** BigInt(FxOracle.precision)
|
|
301
|
+
}
|
|
302
|
+
pricing = { asOf: set.asOf, basis: 'nominal', source: oracle.name }
|
|
303
|
+
}
|
|
304
|
+
unpriced.sort()
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
address,
|
|
308
|
+
// Floor to 6 dp, the TIP-20 native precision; same-currency sums stay exact.
|
|
309
|
+
amount: core_Value.format(total / 10n ** BigInt(FxOracle.precision - 6), 6),
|
|
310
|
+
currency: denomination,
|
|
311
|
+
id: address,
|
|
312
|
+
pricing,
|
|
313
|
+
unpriced,
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
declare namespace getAddressValuation {
|
|
318
|
+
type Options = {
|
|
319
|
+
address: z.output<typeof Schema.Address>
|
|
320
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
321
|
+
/** Currency the valuation is denominated in (uppercase). */
|
|
322
|
+
denomination: string
|
|
323
|
+
/** FX rate oracle backing currency conversion. */
|
|
324
|
+
oracle: FxOracle.Oracle
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Loads the oracle's rate set through the request's store cache. */
|
|
329
|
+
export function rateSet(c: Context<App.Environment>, oracle: FxOracle.Oracle) {
|
|
330
|
+
return Timing.time(c, 'valuation_rates', () =>
|
|
331
|
+
Store.memoize(() => oracle.rates(), {
|
|
332
|
+
key: `fx:v1:${oracle.name}`,
|
|
333
|
+
store: c.get('store'),
|
|
334
|
+
ttl: oracle.ttl,
|
|
335
|
+
}),
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Loads rates best-effort to validate the denomination and value a page.
|
|
341
|
+
* Returns `undefined` when unavailable or unnecessary; a known unsupported denomination still throws.
|
|
342
|
+
*/
|
|
343
|
+
export async function ratesFor(
|
|
344
|
+
c: Context<App.Environment>,
|
|
345
|
+
options: ratesFor.Options,
|
|
346
|
+
): Promise<FxOracle.RateSet | undefined> {
|
|
347
|
+
const { denomination, oracle } = options
|
|
348
|
+
const rates = await rateSet(c, oracle).catch(() => undefined)
|
|
349
|
+
if (!rates) return undefined
|
|
350
|
+
if (denomination !== rates.base && rates.rates[denomination] === undefined)
|
|
351
|
+
throw new UnsupportedDenominationError(denomination, oracle.name)
|
|
352
|
+
const conversion = [...options.currencies].some(
|
|
353
|
+
(currency) => currency.toUpperCase() !== denomination,
|
|
354
|
+
)
|
|
355
|
+
if (!conversion) return undefined
|
|
356
|
+
return rates
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export declare namespace ratesFor {
|
|
360
|
+
/** Options for loading a page's valuation rates. */
|
|
361
|
+
type Options = {
|
|
362
|
+
/** Display currencies of the curated holdings appearing on the page. */
|
|
363
|
+
currencies: Iterable<string>
|
|
364
|
+
/** Currency values are denominated in (uppercase). */
|
|
365
|
+
denomination: string
|
|
366
|
+
/** FX rate oracle backing currency conversion. */
|
|
367
|
+
oracle: FxOracle.Oracle
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Builds the `pricing` provenance block for a consulted rate set. */
|
|
372
|
+
export function pricing(rates: FxOracle.RateSet, oracle: FxOracle.Oracle) {
|
|
373
|
+
return { asOf: rates.asOf, basis: 'nominal' as const, source: oracle.name }
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Values one holding in the requested denomination via its curated display
|
|
378
|
+
* currency. Returns `null` when the token is unverified, its currency has no
|
|
379
|
+
* rate, or the rate set is unavailable.
|
|
380
|
+
*/
|
|
381
|
+
export function valuationFor(options: valuationFor.Options) {
|
|
382
|
+
const { amount, denomination, rates, token } = options
|
|
383
|
+
if (!token) return null
|
|
384
|
+
const rate = (() => {
|
|
385
|
+
if (token.currency.toUpperCase() === denomination) return 10n ** BigInt(FxOracle.precision)
|
|
386
|
+
if (!rates) return undefined
|
|
387
|
+
return FxOracle.rate(rates, { from: token.currency.toUpperCase(), to: denomination })
|
|
388
|
+
})()
|
|
389
|
+
if (rate === undefined) return null
|
|
390
|
+
|
|
391
|
+
// Normalize to the shared 18-dp scale, convert, then floor to 6 dp.
|
|
392
|
+
const scaled =
|
|
393
|
+
token.decimals <= FxOracle.precision
|
|
394
|
+
? amount * 10n ** BigInt(FxOracle.precision - token.decimals)
|
|
395
|
+
: amount / 10n ** BigInt(token.decimals - FxOracle.precision)
|
|
396
|
+
const value = (scaled * rate) / 10n ** BigInt(FxOracle.precision)
|
|
397
|
+
return {
|
|
398
|
+
amount: core_Value.format(value / 10n ** BigInt(FxOracle.precision - 6), 6),
|
|
399
|
+
currency: denomination,
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export declare namespace valuationFor {
|
|
404
|
+
/** Options for valuing one holding. */
|
|
405
|
+
type Options = {
|
|
406
|
+
/** Holding amount in the token's base units. */
|
|
407
|
+
amount: bigint
|
|
408
|
+
/** Currency the value is denominated in (uppercase). */
|
|
409
|
+
denomination: string
|
|
410
|
+
/** Oracle rate set, when loaded. */
|
|
411
|
+
rates: FxOracle.RateSet | undefined
|
|
412
|
+
/** Display currency and decimal scale backing the holding, when known. */
|
|
413
|
+
token: Pick<VerifiedTokens.Snapshot['list'][number], 'currency' | 'decimals'> | undefined
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Thrown when the requested denomination is not priced by the FX oracle. */
|
|
418
|
+
export class UnsupportedDenominationError extends Error {
|
|
419
|
+
override name = 'Valuation.UnsupportedDenominationError'
|
|
420
|
+
constructor(denomination: string, oracle: string) {
|
|
421
|
+
super(`Denomination "${denomination}" is not priced by the "${oracle}" FX oracle`)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
@@ -21,6 +21,7 @@ export namespace schema {
|
|
|
21
21
|
z.describe(
|
|
22
22
|
'The currency label for this token, such as `USD` for USD-denominated stablecoins.',
|
|
23
23
|
),
|
|
24
|
+
z.meta({ examples: ['USD'] }),
|
|
24
25
|
),
|
|
25
26
|
decimals: z
|
|
26
27
|
.number()
|
|
@@ -31,16 +32,27 @@ export namespace schema {
|
|
|
31
32
|
z.describe(
|
|
32
33
|
'The number of decimal places the token uses; Tempo stablecoins typically use 6.',
|
|
33
34
|
),
|
|
35
|
+
z.meta({ examples: [6] }),
|
|
34
36
|
),
|
|
35
37
|
logoUri: z
|
|
36
38
|
.optional(z.url({ protocol: /^https$/ }))
|
|
37
|
-
.check(
|
|
38
|
-
|
|
39
|
+
.check(
|
|
40
|
+
z.describe('The curated HTTPS URL for this token’s logo image, when one is set.'),
|
|
41
|
+
z.meta({ examples: ['https://assets.tempo.xyz/icons/usdc.svg'] }),
|
|
42
|
+
),
|
|
43
|
+
name: z
|
|
44
|
+
.string()
|
|
45
|
+
.check(
|
|
46
|
+
z.minLength(1),
|
|
47
|
+
z.describe('The token’s human-readable name.'),
|
|
48
|
+
z.meta({ examples: ['USD Coin'] }),
|
|
49
|
+
),
|
|
39
50
|
symbol: z
|
|
40
51
|
.string()
|
|
41
52
|
.check(
|
|
42
53
|
z.minLength(1),
|
|
43
54
|
z.describe('The short ticker symbol wallets and apps show for this token.'),
|
|
55
|
+
z.meta({ examples: ['USDC'] }),
|
|
44
56
|
),
|
|
45
57
|
}
|
|
46
58
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Db, Webhooks as Core } from 'tapimo'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
2
3
|
|
|
3
4
|
import * as Tidx from '../../../internal/Tidx.js'
|
|
4
5
|
import * as Viem from '../../../internal/Viem.js'
|
|
@@ -8,6 +9,9 @@ import * as Runtime from '../../../../test/runtime.js'
|
|
|
8
9
|
import * as Tempo from '../../../../test/Tempo.js'
|
|
9
10
|
import * as Webhooks from './webhooks.js'
|
|
10
11
|
|
|
12
|
+
/** Humanized transaction payload delivered by transaction webhooks. */
|
|
13
|
+
type TransactionEvent = z.output<typeof Webhooks.schema.TransactionEvent>
|
|
14
|
+
|
|
11
15
|
/** API key with both webhook scopes, owned by `org_test`. */
|
|
12
16
|
const writer = {
|
|
13
17
|
id: 'key_writer',
|
|
@@ -336,23 +340,24 @@ describe('subscription limits', () => {
|
|
|
336
340
|
test('rejects creation past the configured per-owner cap with 403 limit_exceeded', async () => {
|
|
337
341
|
const app = TestApp.client({
|
|
338
342
|
auth: { keys: [writer, other] },
|
|
339
|
-
webhook: { maxPerOwner: 2 },
|
|
343
|
+
webhook: { maxPerOwner: 2, supportedChainIds: [4217, 42431] },
|
|
340
344
|
})
|
|
345
|
+
const request = { chainId: 4217, ...validBody }
|
|
341
346
|
|
|
342
347
|
// Fill the writer's quota.
|
|
343
348
|
for (let i = 0; i < 2; i++) {
|
|
344
|
-
const ok = await app.v1.webhooks.$post({ json:
|
|
349
|
+
const ok = await app.v1.webhooks.$post({ json: request }, as(writer))
|
|
345
350
|
expect(ok.status).toMatchInlineSnapshot(`200`)
|
|
346
351
|
}
|
|
347
352
|
|
|
348
353
|
// The third create is over the cap.
|
|
349
|
-
const over = await app.v1.webhooks.$post({ json:
|
|
354
|
+
const over = await app.v1.webhooks.$post({ json: request }, as(writer))
|
|
350
355
|
expect(over.status).toMatchInlineSnapshot(`403`)
|
|
351
356
|
const body = (await over.json()) as { error?: { code?: string } }
|
|
352
357
|
expect(body.error?.code).toMatchInlineSnapshot(`"limit_exceeded"`)
|
|
353
358
|
|
|
354
359
|
// The cap is per-owner: a different org is unaffected.
|
|
355
|
-
const otherOrg = await app.v1.webhooks.$post({ json:
|
|
360
|
+
const otherOrg = await app.v1.webhooks.$post({ json: request }, as(other))
|
|
356
361
|
expect(otherOrg.status).toMatchInlineSnapshot(`200`)
|
|
357
362
|
})
|
|
358
363
|
})
|
|
@@ -671,6 +676,36 @@ describe('slack destination', () => {
|
|
|
671
676
|
expect(response.status).toMatchInlineSnapshot(`400`)
|
|
672
677
|
expect(body.error?.code).toMatchInlineSnapshot(`"url_invalid"`)
|
|
673
678
|
})
|
|
679
|
+
|
|
680
|
+
test('requires recreation before changing a provider destination to a URL', async () => {
|
|
681
|
+
const app = client([writer])
|
|
682
|
+
const created = await app.v1.webhooks.$post(
|
|
683
|
+
{
|
|
684
|
+
json: {
|
|
685
|
+
destination: {
|
|
686
|
+
type: 'slack',
|
|
687
|
+
url: 'https://hooks.slack.com/services/T000/B000/xxxx',
|
|
688
|
+
},
|
|
689
|
+
eventType: 'token:transfer',
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
as(writer),
|
|
693
|
+
)
|
|
694
|
+
const { id } = await TestApp.json(created, Webhooks.schema.createWebhook.Response)
|
|
695
|
+
|
|
696
|
+
const response = await app.v1.webhooks[':id{wh_[A-Za-z0-9_-]+}'].$patch(
|
|
697
|
+
{
|
|
698
|
+
json: { destination: { type: 'url', url: 'https://example.com/webhooks' } },
|
|
699
|
+
param: { id },
|
|
700
|
+
},
|
|
701
|
+
as(writer),
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
expect(response.status).toBe(400)
|
|
705
|
+
expect(await response.json()).toMatchObject({
|
|
706
|
+
error: { code: 'destination_transition_invalid' },
|
|
707
|
+
})
|
|
708
|
+
})
|
|
674
709
|
})
|
|
675
710
|
|
|
676
711
|
describe('betterstack destination', () => {
|
|
@@ -1729,11 +1764,18 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1729
1764
|
})
|
|
1730
1765
|
|
|
1731
1766
|
expect(result.scanned).toMatchInlineSnapshot(`1`)
|
|
1732
|
-
const tx = dispatched[0]?.envelope.data as
|
|
1767
|
+
const tx = dispatched[0]?.envelope.data as TransactionEvent | undefined
|
|
1733
1768
|
expect(tx?.hash).toMatchInlineSnapshot(
|
|
1734
1769
|
`"0x3d24a706cc2f6f4c96620bef1f61ddb23040ff77c22c8db42918c7c424bbf9d3"`,
|
|
1735
1770
|
)
|
|
1736
1771
|
expect(tx?.sender).toBe(windowSender)
|
|
1772
|
+
if (tx?.meta.rpc.feeToken) {
|
|
1773
|
+
expect(tx.feeToken?.address).toBe(tx.meta.rpc.feeToken)
|
|
1774
|
+
expect(typeof tx.feeToken?.currency).toBe('string')
|
|
1775
|
+
expect(typeof tx.feeToken?.decimals).toBe('number')
|
|
1776
|
+
expect(typeof tx.feeToken?.name).toBe('string')
|
|
1777
|
+
expect(typeof tx.feeToken?.symbol).toBe('string')
|
|
1778
|
+
} else expect(tx?.feeToken).toBeUndefined()
|
|
1737
1779
|
})
|
|
1738
1780
|
|
|
1739
1781
|
test('transaction:included matches exact empty calldata through the poller', async () => {
|
|
@@ -1821,13 +1863,19 @@ describe.skipIf(Runtime.get().mode === 'localnet')('poll', () => {
|
|
|
1821
1863
|
})
|
|
1822
1864
|
|
|
1823
1865
|
expect(result.scanned).toMatchInlineSnapshot(`1`)
|
|
1824
|
-
const data = dispatched[0]?.envelope.data as
|
|
1825
|
-
| { hash: string; meta?: { receipt?: { status: string; transactionHash: string } } }
|
|
1826
|
-
| undefined
|
|
1866
|
+
const data = dispatched[0]?.envelope.data as TransactionEvent | undefined
|
|
1827
1867
|
// The receipt is embedded under `meta.receipt`, matching the read endpoint's
|
|
1828
1868
|
// `include=receipt` shape, and refers to the same transaction.
|
|
1829
1869
|
expect(data?.meta?.receipt?.status).toMatchInlineSnapshot(`"success"`)
|
|
1830
1870
|
expect(data?.meta?.receipt?.transactionHash).toBe(data?.hash)
|
|
1871
|
+
const receipt = data?.meta.receipt
|
|
1872
|
+
if (receipt?.meta.rpc.feeToken) {
|
|
1873
|
+
expect(receipt.feeToken?.address).toBe(receipt.meta.rpc.feeToken)
|
|
1874
|
+
expect(typeof receipt.feeToken?.currency).toBe('string')
|
|
1875
|
+
expect(typeof receipt.feeToken?.decimals).toBe('number')
|
|
1876
|
+
expect(typeof receipt.feeToken?.name).toBe('string')
|
|
1877
|
+
expect(typeof receipt.feeToken?.symbol).toBe('string')
|
|
1878
|
+
} else expect(receipt?.feeToken).toBeUndefined()
|
|
1831
1879
|
})
|
|
1832
1880
|
|
|
1833
1881
|
// End-to-end through the *default* transport: `poll` with no injected
|