tapimo 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +47 -39
- 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 +8050 -8019
- 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 +669 -669
- package/dist/apps/data/routes/fee-amm.d.ts +157 -143
- 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 +30 -30
- package/dist/apps/data/routes/receipts.d.ts +8 -8
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +12 -3
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +4 -4
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +5 -1
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +6 -6
- package/dist/apps/data/routes/transfers.d.ts +101 -90
- 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 +2 -2
- 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/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +34 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +2 -2
- package/dist/apps/data/routes/webhooks.js +14 -6
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- 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 +48 -34
- 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 +73 -73
- 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 +72 -72
- 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 +57 -10
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.js +1 -1
- package/dist/apps/relay/Sponsorships.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 +32 -2
- package/dist/db/tables/organizations.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 +13 -3
- package/dist/db/tables/sponsoredTransactions.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/EdgeCache.d.ts +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +3 -2
- package/dist/internal/EdgeCache.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/Schema.d.ts +2 -2
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.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 +49 -39
- 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 +14 -2
- 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/tokenlist.test.ts +15 -0
- package/src/apps/data/routes/tokenlist.ts +18 -5
- package/src/apps/data/routes/tokens.ts +5 -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/data/routes/verified-tokens.test.ts +51 -0
- package/src/apps/data/routes/verified-tokens.ts +52 -15
- package/src/apps/data/routes/webhooks.test.ts +56 -0
- package/src/apps/data/routes/webhooks.ts +17 -8
- 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 +90 -8
- package/src/apps/relay/App.ts +62 -10
- package/src/apps/relay/Sponsorships.test.ts +2 -0
- package/src/apps/relay/Sponsorships.ts +1 -1
- 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 +191 -0
- package/src/db/tables/organizations.ts +44 -3
- package/src/db/tables/sponsoredTransactions.test.ts +41 -2
- package/src/db/tables/sponsoredTransactions.ts +15 -3
- 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/EdgeCache.test.ts +18 -5
- package/src/internal/EdgeCache.ts +4 -2
- 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
- package/src/internal/Schema.test.ts +13 -0
- package/src/internal/Schema.ts +14 -7
|
@@ -33,37 +33,16 @@ export const signature = 'event Transfer(address indexed from, address indexed t
|
|
|
33
33
|
const memoSignature =
|
|
34
34
|
'event TransferWithMemo(address indexed from, address indexed to, uint256 amount, bytes32 indexed memo)'
|
|
35
35
|
|
|
36
|
+
const tokenFields = ['token.logoUri', 'token.verified'] as const
|
|
37
|
+
|
|
36
38
|
/** Zod schemas owned by the transfer handlers. */
|
|
37
39
|
export namespace schema {
|
|
38
|
-
/**
|
|
39
|
-
* The token moved by a transfer: its contract address and the transferred
|
|
40
|
-
* amount, plus the trimmed token reference spread in when requested via
|
|
41
|
-
* `include=token`. Reference fields are absent on the base (no-`include`)
|
|
42
|
-
* response and best-effort when requested (a row whose metadata is
|
|
43
|
-
* unavailable keeps just `address`/`amount`). Shared by every per-row
|
|
44
|
-
* transfer response across the API.
|
|
45
|
-
*
|
|
46
|
-
* The optional metadata fields are inlined here (rather than spread from
|
|
47
|
-
* `Tokens.schema.Token` via `z.partial`) so this module avoids referencing
|
|
48
|
-
* the `tokens` schema namespace at module-init time — `transfers` and
|
|
49
|
-
* `tokens` import each other for runtime helpers (`Transfers.query`,
|
|
50
|
-
* `Tokens.resolveToken`), and the schema-evaluation back-edge would
|
|
51
|
-
* otherwise deadlock the namespace.
|
|
52
|
-
*/
|
|
40
|
+
/** A transfer token reference with RPC metadata. Inlined to avoid the `transfers`/`tokens` module cycle. */
|
|
53
41
|
export const SourceToken = z
|
|
54
42
|
.object({
|
|
55
43
|
address: Schema.TokenAddress.check(z.describe('The TIP-20 token contract address on Tempo.')),
|
|
56
|
-
amount: z
|
|
57
|
-
.string()
|
|
58
|
-
.check(
|
|
59
|
-
z.regex(/^\d+$/),
|
|
60
|
-
z.describe(
|
|
61
|
-
'A non-negative whole number, given as a decimal string so very large token amounts keep full precision. Expressed in the smallest unit of the token.',
|
|
62
|
-
),
|
|
63
|
-
z.meta({ examples: ['1000000'] }),
|
|
64
|
-
),
|
|
65
44
|
currency: z
|
|
66
|
-
.
|
|
45
|
+
.string()
|
|
67
46
|
.check(
|
|
68
47
|
z.describe(
|
|
69
48
|
'The currency label for this token, such as `USD` for USD-denominated stablecoins.',
|
|
@@ -71,7 +50,8 @@ export namespace schema {
|
|
|
71
50
|
z.meta({ examples: ['USD'] }),
|
|
72
51
|
),
|
|
73
52
|
decimals: z
|
|
74
|
-
.
|
|
53
|
+
.number()
|
|
54
|
+
.check(z.int(), z.nonnegative())
|
|
75
55
|
.check(
|
|
76
56
|
z.describe(
|
|
77
57
|
'The number of decimal places the token uses; Tempo stablecoins typically use 6.',
|
|
@@ -79,7 +59,7 @@ export namespace schema {
|
|
|
79
59
|
z.meta({ examples: [6] }),
|
|
80
60
|
),
|
|
81
61
|
logoUri: z.optional(z.string()).check(
|
|
82
|
-
z.describe('A URL for the token’s logo image,
|
|
62
|
+
z.describe('A URL for the token’s logo image, returned with `include=token.logoUri`.'),
|
|
83
63
|
z.meta({
|
|
84
64
|
examples: [
|
|
85
65
|
'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
|
|
@@ -87,35 +67,41 @@ export namespace schema {
|
|
|
87
67
|
}),
|
|
88
68
|
),
|
|
89
69
|
name: z
|
|
90
|
-
.
|
|
70
|
+
.string()
|
|
91
71
|
.check(z.describe('The token’s human-readable name.'), z.meta({ examples: ['USD Coin'] })),
|
|
92
72
|
symbol: z
|
|
93
|
-
.
|
|
73
|
+
.string()
|
|
94
74
|
.check(
|
|
95
75
|
z.describe('The short ticker symbol wallets and apps show for this token.'),
|
|
96
76
|
z.meta({ examples: ['USDC'] }),
|
|
97
77
|
),
|
|
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
|
-
),
|
|
106
78
|
verified: z
|
|
107
79
|
.optional(z.boolean())
|
|
108
80
|
.check(
|
|
109
|
-
z.describe(
|
|
81
|
+
z.describe(
|
|
82
|
+
'Whether this token is in Tempo’s curated verified list, returned with `include=token.verified`.',
|
|
83
|
+
),
|
|
110
84
|
z.meta({ examples: [true] }),
|
|
111
85
|
),
|
|
112
86
|
})
|
|
113
87
|
.check(
|
|
114
88
|
z.describe(
|
|
115
|
-
'The token
|
|
89
|
+
'The token that moved in this transfer, with RPC metadata and optional curated fields.',
|
|
116
90
|
),
|
|
117
91
|
)
|
|
118
92
|
|
|
93
|
+
/** A token amount carrying its nominal value in the requested denomination. */
|
|
94
|
+
const ValuedAmount = z.extend(Schema.TokenAmount, {
|
|
95
|
+
valuation: z
|
|
96
|
+
.optional(z.nullable(Valuation.schema.Value))
|
|
97
|
+
.check(
|
|
98
|
+
z.describe(
|
|
99
|
+
'The amount’s nominal value in the requested `denomination`. `null` when the token is ' +
|
|
100
|
+
'unverified, its display currency has no rate, or rates were unavailable.',
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
})
|
|
104
|
+
|
|
119
105
|
/**
|
|
120
106
|
* Filter predicates shared verbatim by the `GET /transfers` list query
|
|
121
107
|
* ({@link getTransfers.Query}, which spreads `Predicates.shape`) and `transfer`
|
|
@@ -147,18 +133,17 @@ export namespace schema {
|
|
|
147
133
|
export namespace getTransfers {
|
|
148
134
|
/** Optional related resources that callers opt into via `include`. */
|
|
149
135
|
export const Include = z
|
|
150
|
-
.enum(['crossToken', 'memo',
|
|
136
|
+
.enum(['crossToken', 'memo', ...tokenFields, 'totalCount'])
|
|
151
137
|
.check(z.describe('Related resources to include only when you request them with `include`.'))
|
|
152
138
|
|
|
153
139
|
/**
|
|
154
140
|
* Parses a comma-separated `include` query value into a list of optional
|
|
155
|
-
* resources to embed. Extra lookups
|
|
156
|
-
*
|
|
157
|
-
* resource is requested, keeping the base transfer page fast.
|
|
141
|
+
* resources to embed. Extra lookups run only when their resource is
|
|
142
|
+
* requested, keeping the base transfer page fast.
|
|
158
143
|
*/
|
|
159
144
|
export const includeQuery = Schema.includeQuery(
|
|
160
145
|
Include,
|
|
161
|
-
'Comma-separated related resources to include, such as `token,memo,crossToken,totalCount`.',
|
|
146
|
+
'Comma-separated related resources to include, such as `token.logoUri,token.verified,memo,crossToken,totalCount`.',
|
|
162
147
|
)
|
|
163
148
|
|
|
164
149
|
/**
|
|
@@ -225,11 +210,18 @@ export namespace schema {
|
|
|
225
210
|
z.describe('The block number where this transfer was recorded.'),
|
|
226
211
|
z.meta({ examples: [23456789] }),
|
|
227
212
|
),
|
|
213
|
+
destinationAmount: z
|
|
214
|
+
.optional(ValuedAmount)
|
|
215
|
+
.check(
|
|
216
|
+
z.describe(
|
|
217
|
+
'Amount delivered to the recipient for a cross-token transfer. Omitted with `destinationToken` for a same-token transfer.',
|
|
218
|
+
),
|
|
219
|
+
),
|
|
228
220
|
destinationToken: z
|
|
229
221
|
.optional(schema.SourceToken)
|
|
230
222
|
.check(
|
|
231
223
|
z.describe(
|
|
232
|
-
'The token
|
|
224
|
+
'The token delivered to the recipient when it differs from `sourceToken`. Omitted for same-token transfers and returned only with `include=crossToken`.',
|
|
233
225
|
),
|
|
234
226
|
),
|
|
235
227
|
id: z.string().check(
|
|
@@ -260,7 +252,8 @@ export namespace schema {
|
|
|
260
252
|
z.describe('The account address that received the tokens.'),
|
|
261
253
|
),
|
|
262
254
|
sender: Schema.Address.check(z.describe('The account address that sent the tokens.')),
|
|
263
|
-
|
|
255
|
+
sourceAmount: ValuedAmount.check(z.describe('Amount associated with `sourceToken`.')),
|
|
256
|
+
sourceToken: schema.SourceToken.check(z.describe('The token that moved.')),
|
|
264
257
|
timestamp: z.iso
|
|
265
258
|
.datetime()
|
|
266
259
|
.check(
|
|
@@ -272,6 +265,14 @@ export namespace schema {
|
|
|
272
265
|
),
|
|
273
266
|
})
|
|
274
267
|
.check(
|
|
268
|
+
z.refine(
|
|
269
|
+
(data) =>
|
|
270
|
+
(data.destinationToken === undefined) === (data.destinationAmount === undefined),
|
|
271
|
+
{
|
|
272
|
+
error: '`destinationAmount` and `destinationToken` must be returned together.',
|
|
273
|
+
path: ['destinationAmount'],
|
|
274
|
+
},
|
|
275
|
+
),
|
|
275
276
|
z.describe(
|
|
276
277
|
'One TIP-20 token transfer event, optionally including a payment memo for reconciliation or invoices.',
|
|
277
278
|
),
|
|
@@ -309,6 +310,14 @@ export namespace schema {
|
|
|
309
310
|
}
|
|
310
311
|
}
|
|
311
312
|
|
|
313
|
+
type UnenrichedTransfer = Omit<
|
|
314
|
+
z.output<typeof schema.getTransfers.Transfer>,
|
|
315
|
+
'destinationAmount' | 'destinationToken' | 'sourceAmount' | 'sourceToken'
|
|
316
|
+
> & {
|
|
317
|
+
destinationToken?: CrossToken | undefined
|
|
318
|
+
sourceToken: CrossToken
|
|
319
|
+
}
|
|
320
|
+
|
|
312
321
|
/** Creates transfer handlers. */
|
|
313
322
|
export function transfers(options: transfers.Options = {}) {
|
|
314
323
|
const oracle = options.fx?.oracle ?? FxOracle.ecb()
|
|
@@ -323,7 +332,7 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
323
332
|
description: 'List token transfer events across Tempo.',
|
|
324
333
|
operationId: 'getTransfers',
|
|
325
334
|
responses: OpenApi.responses({
|
|
326
|
-
errors: { 502: 'The
|
|
335
|
+
errors: { 502: 'The indexer or Tempo RPC could not serve this request.' },
|
|
327
336
|
success: {
|
|
328
337
|
description: 'A page of token transfer events.',
|
|
329
338
|
schema: schema.getTransfers.Response,
|
|
@@ -334,7 +343,7 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
334
343
|
}),
|
|
335
344
|
Cache.response<App.Environment>({
|
|
336
345
|
cacheControl: Cache.policies.feed,
|
|
337
|
-
name: 'tempo-api:transfers:
|
|
346
|
+
name: 'tempo-api:transfers:v2',
|
|
338
347
|
key: (c) => Cache.urlKey(c, schema.getTransfers.Query),
|
|
339
348
|
}),
|
|
340
349
|
async (c) => {
|
|
@@ -388,12 +397,7 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
388
397
|
timing: 'transfers',
|
|
389
398
|
})
|
|
390
399
|
|
|
391
|
-
|
|
392
|
-
// metadata concurrently (sharing one verified-token fetch) and spread
|
|
393
|
-
// it into the row's `sourceToken`. Resolution is best-effort: a token
|
|
394
|
-
// whose metadata is unavailable keeps `sourceToken` as just
|
|
395
|
-
// `address`/`amount` on that row rather than failing the whole page.
|
|
396
|
-
let data: z.output<typeof schema.getTransfers.Transfer>[] = page.data.map((row) => ({
|
|
400
|
+
let data: UnenrichedTransfer[] = page.data.map((row) => ({
|
|
397
401
|
blockNumber: row.blockNumber,
|
|
398
402
|
id: `${row.transactionHash}-${row.logIndex}`,
|
|
399
403
|
logIndex: row.logIndex,
|
|
@@ -460,43 +464,45 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
460
464
|
data.length > 0
|
|
461
465
|
? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
462
466
|
: undefined
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
]),
|
|
470
|
-
),
|
|
471
|
-
)
|
|
472
|
-
|
|
467
|
+
const includeLogoUri = options.include.includes('token.logoUri')
|
|
468
|
+
const includeVerified = options.include.includes('token.verified')
|
|
469
|
+
const uniqueTokens = Array.from(
|
|
470
|
+
new Set(
|
|
471
|
+
data.flatMap((transfer) => [
|
|
472
|
+
transfer.sourceToken.address,
|
|
473
|
+
...(transfer.destinationToken ? [transfer.destinationToken.address] : []),
|
|
474
|
+
]),
|
|
475
|
+
),
|
|
476
|
+
)
|
|
477
|
+
const tokensByAddress = new Map(
|
|
478
|
+
await Timing.time(c, 'tokens', () =>
|
|
473
479
|
Promise.all(
|
|
474
|
-
uniqueTokens.map((token) =>
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
.
|
|
482
|
-
|
|
480
|
+
uniqueTokens.map(async (token) => {
|
|
481
|
+
const [logoUri, metadata] = await Promise.all([
|
|
482
|
+
includeLogoUri
|
|
483
|
+
? Timing.time(c, 'token_logo', () =>
|
|
484
|
+
Tokens.getTokenLogo(c, { address: token, chainId }),
|
|
485
|
+
).catch(() => undefined)
|
|
486
|
+
: undefined,
|
|
487
|
+
Timing.time(c, 'token_metadata', () =>
|
|
488
|
+
Tokens.getTokenMetadata(c, { address: token, chainId }),
|
|
489
|
+
),
|
|
490
|
+
])
|
|
491
|
+
return [
|
|
492
|
+
token.toLowerCase(),
|
|
493
|
+
{
|
|
494
|
+
...metadata,
|
|
495
|
+
logoUri: includeLogoUri
|
|
496
|
+
? (logoUri ??
|
|
497
|
+
snapshot?.byAddress.get(token.toLowerCase())?.logoUri ??
|
|
498
|
+
metadata.logoUri)
|
|
499
|
+
: undefined,
|
|
500
|
+
},
|
|
501
|
+
] as const
|
|
502
|
+
}),
|
|
483
503
|
),
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
const withToken = <T extends { address: z.output<typeof Schema.TokenAddress> }>(
|
|
487
|
-
entry: T,
|
|
488
|
-
) => {
|
|
489
|
-
const token = tokensByAddress.get(entry.address)
|
|
490
|
-
return token ? { ...entry, ...token } : entry
|
|
491
|
-
}
|
|
492
|
-
data = data.map((transfer) => ({
|
|
493
|
-
...transfer,
|
|
494
|
-
sourceToken: withToken(transfer.sourceToken),
|
|
495
|
-
...(transfer.destinationToken
|
|
496
|
-
? { destinationToken: withToken(transfer.destinationToken) }
|
|
497
|
-
: {}),
|
|
498
|
-
}))
|
|
499
|
-
}
|
|
504
|
+
),
|
|
505
|
+
)
|
|
500
506
|
|
|
501
507
|
// Value each leg via its curated display currency, in the requested
|
|
502
508
|
// denomination.
|
|
@@ -513,22 +519,55 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
513
519
|
denomination,
|
|
514
520
|
oracle,
|
|
515
521
|
})
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
522
|
+
const enrich = (leg: CrossToken) => {
|
|
523
|
+
const metadata = tokensByAddress.get(leg.address.toLowerCase())
|
|
524
|
+
if (!metadata) throw new Error(`Token metadata unavailable for ${leg.address}`)
|
|
525
|
+
return {
|
|
526
|
+
amount: {
|
|
527
|
+
...Value.tokenAmount({
|
|
528
|
+
baseUnits: leg.amount,
|
|
529
|
+
currency: metadata.currency,
|
|
530
|
+
decimals: metadata.decimals,
|
|
531
|
+
}),
|
|
532
|
+
valuation: Valuation.valuationFor({
|
|
533
|
+
amount: BigInt(leg.amount),
|
|
534
|
+
denomination,
|
|
535
|
+
rates,
|
|
536
|
+
token: snapshot?.byAddress.get(leg.address.toLowerCase()),
|
|
537
|
+
}),
|
|
538
|
+
},
|
|
539
|
+
token: {
|
|
540
|
+
address: leg.address,
|
|
541
|
+
currency: metadata.currency,
|
|
542
|
+
decimals: metadata.decimals,
|
|
543
|
+
...(metadata.logoUri !== undefined ? { logoUri: metadata.logoUri } : {}),
|
|
544
|
+
name: metadata.name,
|
|
545
|
+
symbol: metadata.symbol,
|
|
546
|
+
...(includeVerified && snapshot
|
|
547
|
+
? { verified: snapshot.byAddress.has(leg.address.toLowerCase()) }
|
|
548
|
+
: {}),
|
|
549
|
+
},
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const responseData: z.output<typeof schema.getTransfers.Transfer>[] = data.map(
|
|
553
|
+
({ destinationToken, sourceToken, ...transfer }) => {
|
|
554
|
+
const source = enrich(sourceToken)
|
|
555
|
+
if (!destinationToken)
|
|
556
|
+
return {
|
|
557
|
+
...transfer,
|
|
558
|
+
sourceAmount: source.amount,
|
|
559
|
+
sourceToken: source.token,
|
|
560
|
+
}
|
|
561
|
+
const destination = enrich(destinationToken)
|
|
562
|
+
return {
|
|
563
|
+
...transfer,
|
|
564
|
+
destinationAmount: destination.amount,
|
|
565
|
+
destinationToken: destination.token,
|
|
566
|
+
sourceAmount: source.amount,
|
|
567
|
+
sourceToken: source.token,
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
)
|
|
532
571
|
|
|
533
572
|
const counts = countPromise ? await countPromise : undefined
|
|
534
573
|
const meta = {
|
|
@@ -538,7 +577,7 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
538
577
|
|
|
539
578
|
return c.json(
|
|
540
579
|
Response.validated(schema.getTransfers.Response, {
|
|
541
|
-
data,
|
|
580
|
+
data: responseData,
|
|
542
581
|
...(Object.keys(meta).length > 0 ? { meta } : {}),
|
|
543
582
|
nextCursor: page.nextCursor,
|
|
544
583
|
}),
|
|
@@ -563,15 +602,13 @@ export declare namespace transfers {
|
|
|
563
602
|
|
|
564
603
|
/**
|
|
565
604
|
* Shared TIP-20 `Transfer` page query. Used by `GET /transfers` and the
|
|
566
|
-
* webhook poller. Builds
|
|
605
|
+
* webhook poller. Builds signature-decoded `Transfer` CTE queries, applies
|
|
567
606
|
* opaque keyset pagination on `(block_num, log_idx)`, and decodes rows into a
|
|
568
607
|
* strongly-typed intermediate shape that each caller maps to its own public
|
|
569
608
|
* response.
|
|
570
609
|
*
|
|
571
|
-
* `eitherSide`
|
|
572
|
-
*
|
|
573
|
-
* because composing it with the keyset cursor pre-WHERE in SQL is awkward at
|
|
574
|
-
* the call site; the helper takes the knob directly.
|
|
610
|
+
* `eitherSide` is exposed alongside one-sided `sender`/`recipient` filters
|
|
611
|
+
* because it requires two upstream scans. The helper owns their ordered merge.
|
|
575
612
|
*
|
|
576
613
|
* Memoization lives inside the helper under one canonical `transfers:v1:…`
|
|
577
614
|
* key derived from every filter — so equivalent filter combinations share a
|
|
@@ -661,11 +698,11 @@ export async function scan(deps: scan.Deps, options: scan.Options): Promise<scan
|
|
|
661
698
|
// translate to a SQL OFFSET of `(page - 1) * limit` rows.
|
|
662
699
|
const offset =
|
|
663
700
|
options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined
|
|
701
|
+
const address = options.eitherSide
|
|
664
702
|
|
|
665
|
-
// Transfer events filtered by any combination of contract /
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
// page-only and added on top of the shared filters below.
|
|
703
|
+
// Transfer events filtered by any combination of contract / sender /
|
|
704
|
+
// recipient / block / timestamp. The address shortcut is split below so
|
|
705
|
+
// each upstream query can use one indexed side.
|
|
669
706
|
const filters = filterClauses(options)
|
|
670
707
|
if (cursor !== undefined)
|
|
671
708
|
filters.push(
|
|
@@ -674,24 +711,41 @@ export async function scan(deps: scan.Deps, options: scan.Options): Promise<scan
|
|
|
674
711
|
{ literal: Cursor.literal(cursor[1]!, 'int'), name: 'log_idx', order },
|
|
675
712
|
]),
|
|
676
713
|
)
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
714
|
+
const addressScoped = address !== undefined
|
|
715
|
+
const window = addressScoped ? (offset ?? 0) + limit + 1 : limit + 1
|
|
716
|
+
const fetchRows = async (side?: 'from' | 'to') => {
|
|
717
|
+
const sideFilters =
|
|
718
|
+
side !== undefined && address !== undefined
|
|
719
|
+
? [`"${side}" = '${address}'`, ...filters]
|
|
720
|
+
: filters
|
|
721
|
+
const where = sideFilters.length > 0 ? `WHERE ${sideFilters.join(' AND ')}` : ''
|
|
722
|
+
const result = await tidx.fetch({
|
|
723
|
+
chainId,
|
|
724
|
+
...(tokenScoped ? { engine: 'clickhouse' } : { signatures: [signature] }),
|
|
725
|
+
query: `
|
|
726
|
+
SELECT "from", "to", ${tokenScoped ? 'token AS address, amount AS value' : 'address, value'}, tx_hash, block_num, log_idx, block_timestamp
|
|
727
|
+
FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
|
|
728
|
+
${where}
|
|
729
|
+
ORDER BY block_num ${direction}, log_idx ${direction}
|
|
730
|
+
LIMIT ${window}${!addressScoped && offset !== undefined ? ` OFFSET ${offset}` : ''}
|
|
731
|
+
` as string,
|
|
732
|
+
})
|
|
733
|
+
return result.rows
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// TIDX cannot plan `(from = X OR to = X)` over the decoded Transfer table.
|
|
737
|
+
// Merge two bounded, server-filtered streams while preserving global order.
|
|
738
|
+
const rows = addressScoped
|
|
739
|
+
? mergeTransferRows((await Promise.all([fetchRows('from'), fetchRows('to')])).flat(), {
|
|
740
|
+
limit: limit + 1,
|
|
741
|
+
offset: offset ?? 0,
|
|
742
|
+
order,
|
|
743
|
+
})
|
|
744
|
+
: await fetchRows()
|
|
691
745
|
|
|
692
746
|
// The next page anchors below the last fetched row's `(block, log_idx)`.
|
|
693
747
|
const page = Cursor.paginate({
|
|
694
|
-
rows
|
|
748
|
+
rows,
|
|
695
749
|
limit,
|
|
696
750
|
key: (row) => {
|
|
697
751
|
const block = Value.toNumber(row['block_num'])
|
|
@@ -738,6 +792,49 @@ export async function scan(deps: scan.Deps, options: scan.Options): Promise<scan
|
|
|
738
792
|
return { hasMore: page.hasMore, items, limit, nextCursor: page.nextCursor }
|
|
739
793
|
}
|
|
740
794
|
|
|
795
|
+
/** Deduplicates and globally orders bounded `from`/`to` transfer streams. */
|
|
796
|
+
function mergeTransferRows(
|
|
797
|
+
rows: readonly Record<string, unknown>[],
|
|
798
|
+
options: mergeTransferRows.Options,
|
|
799
|
+
): Record<string, unknown>[] {
|
|
800
|
+
const positioned: mergeTransferRows.PositionedRow[] = []
|
|
801
|
+
const seen = new Set<string>()
|
|
802
|
+
for (const row of rows) {
|
|
803
|
+
const blockNumber = Value.toNumber(row['block_num'])
|
|
804
|
+
const logIndex = Value.toNumber(row['log_idx'])
|
|
805
|
+
const transactionHash = Value.toText(row['tx_hash'])
|
|
806
|
+
if (blockNumber === undefined || logIndex === undefined || !transactionHash) continue
|
|
807
|
+
const key = eventKey(transactionHash, logIndex)
|
|
808
|
+
if (seen.has(key)) continue
|
|
809
|
+
seen.add(key)
|
|
810
|
+
positioned.push({ blockNumber, logIndex, row })
|
|
811
|
+
}
|
|
812
|
+
positioned.sort((a, b) => {
|
|
813
|
+
const comparison = a.blockNumber - b.blockNumber || a.logIndex - b.logIndex
|
|
814
|
+
return options.order === 'asc' ? comparison : -comparison
|
|
815
|
+
})
|
|
816
|
+
return positioned.slice(options.offset, options.offset + options.limit).map(({ row }) => row)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
declare namespace mergeTransferRows {
|
|
820
|
+
type Options = {
|
|
821
|
+
/** Maximum merged rows to return. */
|
|
822
|
+
limit: number
|
|
823
|
+
/** Number of globally ordered rows to skip. */
|
|
824
|
+
offset: number
|
|
825
|
+
/** Global transfer ordering. */
|
|
826
|
+
order: 'asc' | 'desc'
|
|
827
|
+
}
|
|
828
|
+
type PositionedRow = {
|
|
829
|
+
/** Transfer block number. */
|
|
830
|
+
blockNumber: number
|
|
831
|
+
/** Transfer log index. */
|
|
832
|
+
logIndex: number
|
|
833
|
+
/** Raw TIDX row. */
|
|
834
|
+
row: Record<string, unknown>
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
741
838
|
export declare namespace scan {
|
|
742
839
|
/** Dependencies for {@link scan}. */
|
|
743
840
|
type Deps = {
|
|
@@ -767,11 +864,9 @@ export declare namespace scan {
|
|
|
767
864
|
* physical `token` column; unscoped queries omit it and use the `Transfer` CTE.
|
|
768
865
|
*/
|
|
769
866
|
function filterClauses(options: filterClauses.Options): string[] {
|
|
770
|
-
const {
|
|
771
|
-
options
|
|
867
|
+
const { fromBlock, fromTimestamp, recipient, sender, toBlock, toTimestamp, token } = options
|
|
772
868
|
const filters: string[] = []
|
|
773
869
|
if (token !== undefined) filters.push(`token = '${token}'`)
|
|
774
|
-
if (eitherSide !== undefined) filters.push(`("from" = '${eitherSide}' OR "to" = '${eitherSide}')`)
|
|
775
870
|
if (sender !== undefined) filters.push(`"from" = '${sender}'`)
|
|
776
871
|
if (recipient !== undefined) filters.push(`"to" = '${recipient}'`)
|
|
777
872
|
if (fromBlock !== undefined) filters.push(`block_num >= ${fromBlock}`)
|
|
@@ -785,14 +880,7 @@ declare namespace filterClauses {
|
|
|
785
880
|
/** The non-pagination filter fields of {@link scan.Options}. */
|
|
786
881
|
type Options = Pick<
|
|
787
882
|
scan.Options,
|
|
788
|
-
| '
|
|
789
|
-
| 'fromBlock'
|
|
790
|
-
| 'fromTimestamp'
|
|
791
|
-
| 'recipient'
|
|
792
|
-
| 'sender'
|
|
793
|
-
| 'toBlock'
|
|
794
|
-
| 'toTimestamp'
|
|
795
|
-
| 'token'
|
|
883
|
+
'fromBlock' | 'fromTimestamp' | 'recipient' | 'sender' | 'toBlock' | 'toTimestamp' | 'token'
|
|
796
884
|
>
|
|
797
885
|
}
|
|
798
886
|
|
|
@@ -841,7 +929,7 @@ export declare namespace count {
|
|
|
841
929
|
/**
|
|
842
930
|
* Context-free core of {@link count}: counts token-scoped rows from
|
|
843
931
|
* `token_transfers`, otherwise the decoded `Transfer` CTE. The capped inner
|
|
844
|
-
* scan bounds work
|
|
932
|
+
* scan bounds work while preserving each source's established query engine.
|
|
845
933
|
*/
|
|
846
934
|
export async function countScan(
|
|
847
935
|
deps: scan.Deps,
|
|
@@ -851,56 +939,90 @@ export async function countScan(
|
|
|
851
939
|
const { tidx } = deps
|
|
852
940
|
const filters = filterClauses(options)
|
|
853
941
|
const tokenScoped = options.token !== undefined
|
|
854
|
-
const
|
|
942
|
+
const address = options.eitherSide
|
|
855
943
|
|
|
856
944
|
// Folded count: cross-token transfers collapse several `Transfer` legs into
|
|
857
945
|
// one row, so a raw `count(*)` would overcount. Scan the capped `Transfer`
|
|
858
946
|
// rows, classify each transaction, and subtract the folded-away legs. Bounded
|
|
859
947
|
// by `countCap` like the raw count, so it stays within the indexer's budget.
|
|
860
948
|
if (options.crossToken) {
|
|
949
|
+
const fetchRows = async (side?: 'from' | 'to') => {
|
|
950
|
+
const sideFilters =
|
|
951
|
+
side !== undefined && address !== undefined
|
|
952
|
+
? [`"${side}" = '${address}'`, ...filters]
|
|
953
|
+
: filters
|
|
954
|
+
const sideWhere = sideFilters.length > 0 ? `WHERE ${sideFilters.join(' AND ')}` : ''
|
|
955
|
+
const result = await tidx.fetch({
|
|
956
|
+
chainId,
|
|
957
|
+
...(tokenScoped ? { engine: 'clickhouse' } : { signatures: [signature] }),
|
|
958
|
+
query: `
|
|
959
|
+
SELECT tx_hash, block_num, log_idx, "from", "to", ${tokenScoped ? 'token AS address, amount AS value' : 'address, value'}
|
|
960
|
+
FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
|
|
961
|
+
${sideWhere}
|
|
962
|
+
ORDER BY block_num, log_idx
|
|
963
|
+
LIMIT ${Schema.countCap}
|
|
964
|
+
` as string,
|
|
965
|
+
})
|
|
966
|
+
return result.rows
|
|
967
|
+
}
|
|
968
|
+
const rows =
|
|
969
|
+
address !== undefined
|
|
970
|
+
? mergeTransferRows((await Promise.all([fetchRows('from'), fetchRows('to')])).flat(), {
|
|
971
|
+
limit: Schema.countCap,
|
|
972
|
+
offset: 0,
|
|
973
|
+
order: 'asc',
|
|
974
|
+
})
|
|
975
|
+
: await fetchRows()
|
|
976
|
+
const rawCount = rows.length
|
|
977
|
+
let suppressed = 0
|
|
978
|
+
for (const legs of legsByTransaction(rows).values()) {
|
|
979
|
+
const bundle = classifyCrossToken(legs)
|
|
980
|
+
if (bundle) suppressed += bundle.suppressLogIndexes.length
|
|
981
|
+
}
|
|
982
|
+
return { totalCountCapped: rawCount >= Schema.countCap, totalCount: rawCount - suppressed }
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const countRows = async (additionalFilters: readonly string[] = []) => {
|
|
986
|
+
const countFilters = [...additionalFilters, ...filters]
|
|
987
|
+
const countWhere = countFilters.length > 0 ? `WHERE ${countFilters.join(' AND ')}` : ''
|
|
861
988
|
const result = await tidx.fetch({
|
|
862
989
|
chainId,
|
|
863
990
|
...(tokenScoped ? { engine: 'clickhouse' } : { signatures: [signature] }),
|
|
864
991
|
query: `
|
|
865
|
-
SELECT
|
|
866
|
-
FROM
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
992
|
+
SELECT count(*) AS total
|
|
993
|
+
FROM (
|
|
994
|
+
SELECT 1
|
|
995
|
+
FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
|
|
996
|
+
${countWhere}
|
|
997
|
+
LIMIT ${Schema.countCap}
|
|
998
|
+
) AS capped
|
|
870
999
|
` as string,
|
|
871
1000
|
})
|
|
872
|
-
|
|
873
|
-
let suppressed = 0
|
|
874
|
-
for (const legs of legsByTransaction(result.rows).values()) {
|
|
875
|
-
const bundle = classifyCrossToken(legs)
|
|
876
|
-
if (bundle) suppressed += bundle.suppressLogIndexes.length
|
|
877
|
-
}
|
|
878
|
-
return { totalCountCapped: rawCount >= Schema.countCap, totalCount: rawCount - suppressed }
|
|
1001
|
+
return Value.toNumber(result.rows[0]?.['total']) ?? 0
|
|
879
1002
|
}
|
|
880
1003
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
})
|
|
1004
|
+
if (address !== undefined) {
|
|
1005
|
+
// Keep branches disjoint so self-transfers count once.
|
|
1006
|
+
const [sent, received] = await Promise.all([
|
|
1007
|
+
countRows([`"from" = '${address}'`]),
|
|
1008
|
+
countRows([`"to" = '${address}'`, `"from" != '${address}'`]),
|
|
1009
|
+
])
|
|
1010
|
+
const rawCount = sent + received
|
|
1011
|
+
return {
|
|
1012
|
+
totalCount: Math.min(rawCount, Schema.countCap),
|
|
1013
|
+
totalCountCapped:
|
|
1014
|
+
sent >= Schema.countCap || received >= Schema.countCap || rawCount >= Schema.countCap,
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
895
1017
|
|
|
896
|
-
const totalCount =
|
|
1018
|
+
const totalCount = await countRows()
|
|
897
1019
|
return { totalCountCapped: totalCount >= Schema.countCap, totalCount }
|
|
898
1020
|
}
|
|
899
1021
|
|
|
900
1022
|
export declare namespace countScan {
|
|
901
1023
|
/** Options for {@link countScan}: the shared filters plus the target chain. */
|
|
902
1024
|
type Options = filterClauses.Options &
|
|
903
|
-
Pick<scan.Options, 'chainId'> & {
|
|
1025
|
+
Pick<scan.Options, 'chainId' | 'eitherSide'> & {
|
|
904
1026
|
/** Count folded cross-token transfers instead of raw `Transfer` logs. */
|
|
905
1027
|
crossToken?: boolean | undefined
|
|
906
1028
|
}
|
|
@@ -984,7 +1106,7 @@ declare namespace memoMap {
|
|
|
984
1106
|
}
|
|
985
1107
|
}
|
|
986
1108
|
|
|
987
|
-
/** A
|
|
1109
|
+
/** A transfer leg before amount and token metadata are separated. */
|
|
988
1110
|
type CrossToken = { address: z.output<typeof Schema.Address>; amount: string }
|
|
989
1111
|
|
|
990
1112
|
/** A decoded `Transfer` leg used for cross-token classification. */
|