tapimo 0.4.4 → 0.5.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 +7403 -6127
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +4 -0
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +18 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +24 -3
- package/dist/Scope.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +115 -115
- package/dist/apps/data/App.d.ts +1838 -1534
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +135 -44
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/{FundingRouteProviders.d.ts → FundingQuoteProviders.d.ts} +7 -7
- package/dist/apps/data/{FundingRouteProviders.d.ts.map → FundingQuoteProviders.d.ts.map} +1 -1
- package/dist/apps/data/{FundingRouteProviders.js → FundingQuoteProviders.js} +36 -91
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -1
- package/dist/apps/data/index.js +1 -1
- package/dist/apps/data/routes/activities.d.ts +242 -242
- package/dist/apps/data/routes/addresses.d.ts +227 -227
- package/dist/apps/data/routes/balances.d.ts +12 -12
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +67 -149
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +125 -126
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +3 -5
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +55 -54
- package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
- package/dist/apps/data/routes/coingecko.js +3 -8
- package/dist/apps/data/routes/coingecko.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +207 -388
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +143 -221
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +86 -16
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +33 -33
- package/dist/apps/data/routes/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts +58 -58
- package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
- package/dist/apps/data/routes/webhooks.d.ts +340 -0
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +13 -1
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/management/App.d.ts +1090 -108
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +4 -0
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +118 -118
- package/dist/apps/management/routes/api-keys.js +2 -2
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +207 -207
- package/dist/apps/management/routes/invite-links.d.ts +1100 -0
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -0
- package/dist/apps/management/routes/invite-links.js +411 -0
- package/dist/apps/management/routes/invite-links.js.map +1 -0
- package/dist/apps/management/routes/me.d.ts +17 -17
- package/dist/apps/management/routes/members.d.ts +71 -71
- package/dist/apps/management/routes/orgs.d.ts +12 -12
- package/dist/apps/management/routes/projects.d.ts +70 -70
- package/dist/apps/management/routes/scopes.d.ts +11 -11
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +2 -2
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +75 -75
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +42 -4
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +3 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +57 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +31 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +31 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +42 -0
- package/dist/db/tables/inviteLinks.d.ts.map +1 -0
- package/dist/db/tables/inviteLinks.js +148 -0
- package/dist/db/tables/inviteLinks.js.map +1 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +2 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +13 -3
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +9 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +130 -10
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +3 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +17 -2
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/{FundingRoutes.d.ts → FundingQuotes.d.ts} +185 -428
- package/dist/internal/FundingQuotes.d.ts.map +1 -0
- package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +93 -218
- package/dist/internal/FundingQuotes.js.map +1 -0
- package/dist/internal/Viem.d.ts +3 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +11 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.js +1 -1
- package/package.json +1 -1
- package/src/ApiKeys.test.ts +23 -0
- package/src/App.test-d.ts +2 -0
- package/src/App.ts +6 -0
- package/src/Client.test-d.ts +20 -11
- package/src/Scope.test.ts +1 -0
- package/src/Scope.ts +30 -4
- package/src/apps/data/App.test.ts +354 -0
- package/src/apps/data/App.ts +159 -52
- package/src/apps/data/FundingQuoteProviders.test.ts +151 -0
- package/src/apps/data/{FundingRouteProviders.ts → FundingQuoteProviders.ts} +40 -101
- package/src/apps/data/index.ts +1 -1
- package/src/apps/data/routes/balances.test.ts +64 -18
- package/src/apps/data/routes/balances.ts +76 -177
- package/src/apps/data/routes/blocks.ts +3 -5
- package/src/apps/data/routes/coingecko.ts +3 -8
- package/src/apps/data/routes/funding.test.ts +151 -206
- package/src/apps/data/routes/funding.ts +156 -291
- package/src/apps/data/routes/indexer.test.ts +97 -1
- package/src/apps/data/routes/rpc.test.ts +219 -0
- package/src/apps/data/routes/rpc.ts +92 -17
- package/src/apps/data/routes/webhooks.test.ts +21 -0
- package/src/apps/data/routes/webhooks.ts +13 -1
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +4 -0
- package/src/apps/management/routes/api-keys.test.ts +47 -3
- package/src/apps/management/routes/api-keys.ts +2 -2
- package/src/apps/management/routes/invite-links.test.ts +385 -0
- package/src/apps/management/routes/invite-links.ts +570 -0
- package/src/apps/management/routes/scopes.test.ts +3 -2
- package/src/apps/management/routes/scopes.ts +4 -2
- package/src/apps/relay/App.test.ts +324 -4
- package/src/apps/relay/App.ts +40 -4
- package/src/db/Db.ts +65 -0
- package/src/db/Schema.ts +39 -0
- package/src/db/tables/inviteLinks.ts +187 -0
- package/src/db/tables/organizations.ts +2 -0
- package/src/handlers/relay.ts +16 -4
- package/src/internal/Auth.test.ts +132 -22
- package/src/internal/Auth.ts +171 -14
- package/src/internal/EdgeCache.test.ts +24 -3
- package/src/internal/EdgeCache.ts +18 -2
- package/src/internal/FundingQuotes.test.ts +174 -0
- package/src/internal/{FundingRoutes.ts → FundingQuotes.ts} +153 -312
- package/src/internal/Viem.ts +14 -5
- package/src/internal/index.test.ts +1 -1
- package/src/internal/index.ts +1 -1
- package/dist/apps/data/FundingRouteProviders.js.map +0 -1
- package/dist/internal/FundingRoutes.d.ts.map +0 -1
- package/dist/internal/FundingRoutes.js.map +0 -1
- package/src/apps/data/FundingRouteProviders.test.ts +0 -225
- package/src/internal/FundingRoutes.test.ts +0 -213
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as FundingQuotes from '../../internal/FundingQuotes.js'
|
|
2
2
|
|
|
3
3
|
type Fetch = typeof globalThis.fetch
|
|
4
4
|
|
|
@@ -6,41 +6,41 @@ const defaultFetch: Fetch = (input, init) => globalThis.fetch(input, init)
|
|
|
6
6
|
|
|
7
7
|
const defaultRelayBaseUrl = 'https://api.relay.link'
|
|
8
8
|
|
|
9
|
-
/** Creates the live funding
|
|
9
|
+
/** Creates the live funding quote adapters configured for this deployment. */
|
|
10
10
|
export function createAdapters(options: createAdapters.Options = {}) {
|
|
11
11
|
return (
|
|
12
12
|
options.relay ? { relay: relay(options.relay) } : {}
|
|
13
|
-
) satisfies
|
|
13
|
+
) satisfies FundingQuotes.ProviderAdapters
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export declare namespace createAdapters {
|
|
17
|
-
/** Provider adapter factory options. Omit a provider to
|
|
17
|
+
/** Provider adapter factory options. Omit a provider to leave it unconfigured. */
|
|
18
18
|
type Options = {
|
|
19
19
|
/** Relay quote adapter options. */
|
|
20
20
|
relay?: relay.Options | false | undefined
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/** Creates a Relay quote adapter backed by `/
|
|
25
|
-
export function relay(options: relay.Options):
|
|
24
|
+
/** Creates a Relay quote adapter backed by `/quote/v2`. */
|
|
25
|
+
export function relay(options: relay.Options): FundingQuotes.ProviderAdapter {
|
|
26
26
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultRelayBaseUrl)
|
|
27
27
|
const fetcher = options.fetch ?? defaultFetch
|
|
28
28
|
const now = options.now ?? (() => new Date())
|
|
29
29
|
|
|
30
30
|
return {
|
|
31
|
-
async probeQuote({
|
|
32
|
-
const originChainId = providerChainId(
|
|
33
|
-
const destinationChainId = eip155ChainId(
|
|
31
|
+
async probeQuote({ candidate }, signal) {
|
|
32
|
+
const originChainId = providerChainId(candidate.sourceChain, 'relay')
|
|
33
|
+
const destinationChainId = eip155ChainId(candidate.destinationChain)
|
|
34
34
|
const headers = relayHeaders(options.apiKey)
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
37
|
const quote = await requestJson(new URL(`${baseUrl}/quote/v2`), {
|
|
38
38
|
body: {
|
|
39
|
-
amount:
|
|
39
|
+
amount: candidate.sourceAmount.amount,
|
|
40
40
|
destinationChainId: Number(destinationChainId),
|
|
41
|
-
destinationCurrency:
|
|
41
|
+
destinationCurrency: candidate.destinationToken.address,
|
|
42
42
|
originChainId: Number(originChainId),
|
|
43
|
-
originCurrency:
|
|
43
|
+
originCurrency: candidate.sourceToken.address,
|
|
44
44
|
recipient: options.recipientAddress ?? options.userAddress,
|
|
45
45
|
tradeType: 'EXACT_INPUT',
|
|
46
46
|
user: options.userAddress,
|
|
@@ -50,12 +50,7 @@ export function relay(options: relay.Options): FundingRoutes.ProviderAdapter {
|
|
|
50
50
|
method: 'POST',
|
|
51
51
|
signal,
|
|
52
52
|
})
|
|
53
|
-
|
|
54
|
-
liquidityUrl.searchParams.set('chainId', destinationChainId)
|
|
55
|
-
const liquidity = await requestJson(liquidityUrl, { fetcher, headers, signal }).catch(
|
|
56
|
-
() => undefined,
|
|
57
|
-
)
|
|
58
|
-
return formatRelayResult(route, quote, liquidity, now())
|
|
53
|
+
return formatRelayResult(candidate, quote, now())
|
|
59
54
|
} catch (cause) {
|
|
60
55
|
const unavailable = providerUnavailable(cause, [
|
|
61
56
|
'NO_QUOTES',
|
|
@@ -93,68 +88,43 @@ export declare namespace relay {
|
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
function formatRelayResult(
|
|
96
|
-
|
|
91
|
+
candidate: FundingQuotes.QuoteCandidate,
|
|
97
92
|
quote: unknown,
|
|
98
|
-
liquidity: unknown,
|
|
99
93
|
now: Date,
|
|
100
|
-
):
|
|
94
|
+
): FundingQuotes.QuoteProbeResult {
|
|
101
95
|
const details = record(record(quote)['details'])
|
|
102
96
|
const currencyOut = record(details['currencyOut'])
|
|
103
97
|
const outputAmount = decimalString(currencyOut['amount'])
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const liquidityStatus = relayLiquidityStatus({ destinationLiquidity, outputAmount })
|
|
98
|
+
const status = outputAmount
|
|
99
|
+
? { status: 'available' as const, tier: 'liquid' as const }
|
|
100
|
+
: { status: 'unknown' as const, tier: 'unknown' as const }
|
|
108
101
|
|
|
109
102
|
return {
|
|
110
|
-
limits: null,
|
|
111
103
|
minimumOutputAmount: decimalString(currencyOut['minimumAmount']),
|
|
112
104
|
outputAmount,
|
|
113
105
|
outputAmountFormatted: outputAmount
|
|
114
|
-
? decimalFromBaseUnits(outputAmount,
|
|
115
|
-
:
|
|
106
|
+
? decimalFromBaseUnits(outputAmount, candidate.destinationToken.decimals)
|
|
107
|
+
: undefined,
|
|
116
108
|
quality: {
|
|
117
|
-
availableVolumeUsd: availableVolumeUsd,
|
|
118
109
|
estimatedSeconds: nonnegativeInteger(details['timeEstimate']),
|
|
119
|
-
feeBps: null,
|
|
120
|
-
historicalVolumeUsd24h: null,
|
|
121
|
-
instantVolumeUsd: availableVolumeUsd,
|
|
122
110
|
liquiditySource: 'providerQuote',
|
|
123
|
-
score: scoreFor(liquidityStatus.tier),
|
|
124
111
|
sourceDetail: 'relay:quote-v2',
|
|
125
|
-
tier:
|
|
112
|
+
tier: status.tier,
|
|
126
113
|
},
|
|
127
114
|
sampledAt: now.toISOString(),
|
|
128
|
-
status:
|
|
115
|
+
status: status.status,
|
|
129
116
|
}
|
|
130
117
|
}
|
|
131
118
|
|
|
132
|
-
function relayLiquidityStatus(options: {
|
|
133
|
-
destinationLiquidity: string | undefined
|
|
134
|
-
outputAmount: string | undefined
|
|
135
|
-
}): { status: FundingRoutes.RouteStatus; tier: FundingRoutes.QualityTier } {
|
|
136
|
-
if (!options.outputAmount) return { status: 'unknown', tier: 'unknown' }
|
|
137
|
-
if (!options.destinationLiquidity) return { status: 'unknown', tier: 'unknown' }
|
|
138
|
-
if (compareIntegerStrings(options.destinationLiquidity, options.outputAmount) < 0)
|
|
139
|
-
return { status: 'thin', tier: 'thin' }
|
|
140
|
-
return { status: 'available', tier: 'liquid' }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
119
|
function unavailableResult(options: {
|
|
144
120
|
detail: string
|
|
145
121
|
message: string
|
|
146
122
|
now: Date
|
|
147
123
|
source: string
|
|
148
|
-
}):
|
|
124
|
+
}): FundingQuotes.QuoteProbeResult {
|
|
149
125
|
return {
|
|
150
126
|
quality: {
|
|
151
|
-
availableVolumeUsd: null,
|
|
152
|
-
estimatedSeconds: null,
|
|
153
|
-
feeBps: null,
|
|
154
|
-
historicalVolumeUsd24h: null,
|
|
155
|
-
instantVolumeUsd: null,
|
|
156
127
|
liquiditySource: options.source,
|
|
157
|
-
score: 0,
|
|
158
128
|
sourceDetail: `${options.detail}:${options.message}`,
|
|
159
129
|
tier: 'unavailable',
|
|
160
130
|
},
|
|
@@ -163,20 +133,6 @@ function unavailableResult(options: {
|
|
|
163
133
|
}
|
|
164
134
|
}
|
|
165
135
|
|
|
166
|
-
function relayLiquidity(input: unknown, route: FundingRoutes.RouteCandidate) {
|
|
167
|
-
const response = record(input)
|
|
168
|
-
const rows = Array.isArray(response['liquidity']) ? response['liquidity'] : []
|
|
169
|
-
const destinationChainId = eip155ChainId(route.destinationChain)
|
|
170
|
-
return rows.map(record).find((row) => {
|
|
171
|
-
const rowChainId = integerString(row['chainId'])
|
|
172
|
-
const rowAddress = typeof row['address'] === 'string' ? row['address'].toLowerCase() : undefined
|
|
173
|
-
return (
|
|
174
|
-
rowChainId === destinationChainId &&
|
|
175
|
-
rowAddress === route.destinationToken.address.toLowerCase()
|
|
176
|
-
)
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
|
|
180
136
|
async function requestJson(
|
|
181
137
|
url: URL,
|
|
182
138
|
options: {
|
|
@@ -220,38 +176,23 @@ function providerErrorCode(input: unknown) {
|
|
|
220
176
|
return code && /^[A-Za-z0-9_.:-]{1,64}$/.test(code) ? code : undefined
|
|
221
177
|
}
|
|
222
178
|
|
|
223
|
-
class ProviderResponseError extends Error {
|
|
224
|
-
override name = 'FundingRouteProviders.ProviderResponseError'
|
|
225
|
-
|
|
226
|
-
constructor(
|
|
227
|
-
readonly status: number,
|
|
228
|
-
readonly body: unknown,
|
|
229
|
-
) {
|
|
230
|
-
super(`Provider request failed with status ${status}`)
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
179
|
function providerChainId(
|
|
235
|
-
|
|
180
|
+
quoteChain: FundingQuotes.QuoteCandidate['sourceChain'],
|
|
236
181
|
provider: string,
|
|
237
182
|
) {
|
|
238
|
-
const providerIds =
|
|
183
|
+
const providerIds = quoteChain.providerIds as Record<string, string> | undefined
|
|
239
184
|
const id = providerIds?.[provider]
|
|
240
185
|
if (id && /^\d+$/.test(id)) return id
|
|
241
|
-
return eip155ChainId(
|
|
186
|
+
return eip155ChainId(quoteChain)
|
|
242
187
|
}
|
|
243
188
|
|
|
244
189
|
function eip155ChainId(chain: { id: string; kind: string }) {
|
|
245
|
-
if (chain.kind !== 'evm') throw new
|
|
190
|
+
if (chain.kind !== 'evm') throw new UnsupportedProviderChainError()
|
|
246
191
|
const match = /^eip155:(\d+)$/.exec(chain.id)
|
|
247
|
-
if (!match?.[1]) throw new
|
|
192
|
+
if (!match?.[1]) throw new UnsupportedProviderChainError()
|
|
248
193
|
return match[1]
|
|
249
194
|
}
|
|
250
195
|
|
|
251
|
-
class UnsupportedProviderRouteError extends Error {
|
|
252
|
-
override name = 'FundingRouteProviders.UnsupportedProviderRouteError'
|
|
253
|
-
}
|
|
254
|
-
|
|
255
196
|
function normalizeBaseUrl(input: string) {
|
|
256
197
|
return input.replace(/\/+$/, '')
|
|
257
198
|
}
|
|
@@ -281,16 +222,10 @@ function decimalString(input: unknown) {
|
|
|
281
222
|
return undefined
|
|
282
223
|
}
|
|
283
224
|
|
|
284
|
-
function integerString(input: unknown) {
|
|
285
|
-
if (typeof input === 'string' && /^\d+$/.test(input)) return input
|
|
286
|
-
if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0) return String(input)
|
|
287
|
-
return undefined
|
|
288
|
-
}
|
|
289
|
-
|
|
290
225
|
function nonnegativeInteger(input: unknown) {
|
|
291
226
|
if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0) return input
|
|
292
227
|
if (typeof input === 'string' && /^\d+$/.test(input)) return Number(input)
|
|
293
|
-
return
|
|
228
|
+
return undefined
|
|
294
229
|
}
|
|
295
230
|
|
|
296
231
|
function decimalFromBaseUnits(baseUnits: string, decimals: number) {
|
|
@@ -300,13 +235,17 @@ function decimalFromBaseUnits(baseUnits: string, decimals: number) {
|
|
|
300
235
|
return fraction ? `${whole}.${fraction}` : whole
|
|
301
236
|
}
|
|
302
237
|
|
|
303
|
-
|
|
304
|
-
|
|
238
|
+
class ProviderResponseError extends Error {
|
|
239
|
+
override name = 'FundingQuoteProviders.ProviderResponseError'
|
|
240
|
+
|
|
241
|
+
constructor(
|
|
242
|
+
readonly status: number,
|
|
243
|
+
readonly body: unknown,
|
|
244
|
+
) {
|
|
245
|
+
super(`Provider request failed with status ${status}`)
|
|
246
|
+
}
|
|
305
247
|
}
|
|
306
248
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (tier === 'thin') return 65
|
|
310
|
-
if (tier === 'unavailable') return 0
|
|
311
|
-
return null
|
|
249
|
+
class UnsupportedProviderChainError extends Error {
|
|
250
|
+
override name = 'FundingQuoteProviders.UnsupportedProviderChainError'
|
|
312
251
|
}
|
package/src/apps/data/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Schema } from 'tapimo'
|
|
2
2
|
import { Value as core_Value } from 'ox'
|
|
3
|
+
import { Actions } from 'viem/tempo'
|
|
3
4
|
|
|
4
5
|
import * as TestApp from '../../../../test/App.js'
|
|
5
6
|
import * as Runtime from '../../../../test/runtime.js'
|
|
7
|
+
import * as TestViem from '../../../../test/Viem.js'
|
|
6
8
|
import * as Balances from './balances.js'
|
|
7
9
|
import * as Tokens from './tokens.js'
|
|
8
10
|
|
|
@@ -45,7 +47,7 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
45
47
|
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
46
48
|
|
|
47
49
|
expect(response.status).toBe(200)
|
|
48
|
-
expect(response.headers.get('server-timing')?.includes('
|
|
50
|
+
expect(response.headers.get('server-timing')?.includes('balances_discovery;dur=')).toBe(true)
|
|
49
51
|
expect(Array.isArray(body.data)).toBe(true)
|
|
50
52
|
expect(body.data.length).toBeLessThanOrEqual(5)
|
|
51
53
|
// Pagination fields stay at the response root; cursor pagination has no
|
|
@@ -72,16 +74,48 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
72
74
|
),
|
|
73
75
|
).toBe(true)
|
|
74
76
|
// Results are restricted to TIP-20 tokens (deterministic `0x20c0…` factory
|
|
75
|
-
// addresses), so non-TIP-20 contracts in
|
|
76
|
-
// excluded.
|
|
77
|
+
// addresses), so non-TIP-20 contracts in the delta history are excluded.
|
|
77
78
|
expect(body.data.every((entry) => entry.token.address.startsWith('0x20c0'))).toBe(true)
|
|
78
79
|
// Amounts are returned largest-first.
|
|
79
80
|
const amounts = body.data.map((entry) => BigInt(entry.amount))
|
|
80
81
|
expect(amounts.every((amount, index) => index === 0 || amounts[index - 1]! >= amount)).toBe(
|
|
81
82
|
true,
|
|
82
83
|
)
|
|
84
|
+
|
|
85
|
+
const rpc = TestViem.getClient()
|
|
86
|
+
const current = await Promise.all(
|
|
87
|
+
body.data.map((entry) =>
|
|
88
|
+
Actions.token.getBalance(rpc, { account: holder, token: entry.token.address }),
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
expect(body.data.map((entry) => entry.amount)).toEqual(
|
|
92
|
+
current.map((balance) => balance.amount.toString()),
|
|
93
|
+
)
|
|
83
94
|
})
|
|
84
95
|
|
|
96
|
+
test.skipIf(runtime.mode !== 'localnet')(
|
|
97
|
+
'discovers freshly transferred assets from deltas',
|
|
98
|
+
async () => {
|
|
99
|
+
const fixture = runtime.fixtures?.crossTokenTransfer
|
|
100
|
+
if (!fixture) throw new Error('expected cross-token transfer fixture')
|
|
101
|
+
const client = app()
|
|
102
|
+
|
|
103
|
+
const response = await client.v1.addresses[':address'].balances.$get(
|
|
104
|
+
{ param: { address: fixture.recipient }, query: { limit: '5' } },
|
|
105
|
+
TestApp.auth,
|
|
106
|
+
)
|
|
107
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
108
|
+
|
|
109
|
+
expect(response.status).toBe(200)
|
|
110
|
+
expect(body.data).toContainEqual(
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
amount: '1000000',
|
|
113
|
+
id: fixture.destinationToken,
|
|
114
|
+
}),
|
|
115
|
+
)
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
85
119
|
test('embeds an exact balance total on demand via include=totalCount', async () => {
|
|
86
120
|
const client = app()
|
|
87
121
|
const holder = await holderWithBalance(client)
|
|
@@ -93,15 +127,14 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
93
127
|
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
94
128
|
|
|
95
129
|
expect(response.status).toBe(200)
|
|
96
|
-
expect(response.headers.get('server-timing')?.includes('
|
|
97
|
-
true,
|
|
98
|
-
)
|
|
130
|
+
expect(response.headers.get('server-timing')?.includes('balances_discovery;dur=')).toBe(true)
|
|
99
131
|
expect(Number.isInteger(body.meta?.totalCount)).toBe(true)
|
|
100
132
|
expect(body.meta!.totalCount).toBeGreaterThanOrEqual(body.data.length)
|
|
101
|
-
// The count
|
|
133
|
+
// The count comes from the corrected candidate set and is never capped.
|
|
102
134
|
expect(body.meta!.totalCountCapped).toBe(false)
|
|
103
135
|
|
|
104
|
-
// Without the include, `meta` is omitted
|
|
136
|
+
// Without the include, `meta` is omitted. The corrected candidate set
|
|
137
|
+
// already supplies the count, so no second indexer query is needed.
|
|
105
138
|
const bareResponse = await client.v1.addresses[':address'].balances.$get(
|
|
106
139
|
{ param: { address: holder }, query: { limit: '5' } },
|
|
107
140
|
TestApp.auth,
|
|
@@ -109,8 +142,8 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
109
142
|
const bare = await TestApp.json(bareResponse, Balances.schema.getAddressBalances.Response)
|
|
110
143
|
expect(bareResponse.status).toBe(200)
|
|
111
144
|
expect(bare.meta).toBeUndefined()
|
|
112
|
-
expect(bareResponse.headers.get('server-timing')?.includes('
|
|
113
|
-
|
|
145
|
+
expect(bareResponse.headers.get('server-timing')?.includes('balances_discovery;dur=')).toBe(
|
|
146
|
+
true,
|
|
114
147
|
)
|
|
115
148
|
})
|
|
116
149
|
|
|
@@ -138,6 +171,27 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
138
171
|
`)
|
|
139
172
|
})
|
|
140
173
|
|
|
174
|
+
test('surfaces a 502 when RPC correction is unavailable', async () => {
|
|
175
|
+
const client = app({ rpc: { url: () => 'http://127.0.0.1:1' } })
|
|
176
|
+
const holder = await holderWithBalance(client)
|
|
177
|
+
const response = await client.v1.addresses[':address'].balances.$get(
|
|
178
|
+
{ param: { address: holder }, query: {} },
|
|
179
|
+
TestApp.auth,
|
|
180
|
+
)
|
|
181
|
+
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
182
|
+
const { requestId, error } = body
|
|
183
|
+
const { message, ...stableError } = error
|
|
184
|
+
|
|
185
|
+
expect(response.status).toMatchInlineSnapshot(`502`)
|
|
186
|
+
expect(typeof requestId).toMatchInlineSnapshot(`"string"`)
|
|
187
|
+
expect(typeof message).toMatchInlineSnapshot(`"string"`)
|
|
188
|
+
expect(stableError).toMatchInlineSnapshot(`
|
|
189
|
+
{
|
|
190
|
+
"code": "upstream_error",
|
|
191
|
+
}
|
|
192
|
+
`)
|
|
193
|
+
})
|
|
194
|
+
|
|
141
195
|
test('rejects invalid account addresses', async () => {
|
|
142
196
|
// The typed client requires `address: `Hex.Hex`` so an invalid literal
|
|
143
197
|
// cannot pass through it; exercise the validator via `app.request`.
|
|
@@ -250,14 +304,6 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
250
304
|
},
|
|
251
305
|
TestApp.auth,
|
|
252
306
|
)
|
|
253
|
-
if (runtime.mode === 'testnet' && response.status === 502) {
|
|
254
|
-
// Hosted TIDX can intermittently reject this live filtered balance query
|
|
255
|
-
// with a 422. Localnet still exercises the behavior strictly; on testnet,
|
|
256
|
-
// accept the documented upstream-error envelope instead of flaking CI.
|
|
257
|
-
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
258
|
-
expect(body.error.code).toBe('upstream_error')
|
|
259
|
-
return
|
|
260
|
-
}
|
|
261
307
|
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
262
308
|
expect(response.status).toBe(200)
|
|
263
309
|
|