tapimo 0.4.3 → 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 +7222 -6316
- 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 +116 -116
- package/dist/apps/data/App.d.ts +2541 -2607
- 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/FundingQuoteProviders.d.ts +36 -0
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -0
- package/dist/apps/data/FundingQuoteProviders.js +190 -0
- 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/exchanges.d.ts +455 -455
- package/dist/apps/data/routes/fee-amm.d.ts +92 -92
- package/dist/apps/data/routes/funding.d.ts +91 -864
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +138 -450
- 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 +36 -36
- 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 +1843 -861
- 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 +121 -121
- 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 +2 -2
- 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 +13 -13
- package/dist/apps/management/routes/members.d.ts +77 -77
- package/dist/apps/management/routes/orgs.d.ts +96 -96
- package/dist/apps/management/routes/projects.d.ts +62 -62
- 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 +79 -79
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +3 -1
- package/dist/apps/mcp.js.map +1 -1
- 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/cloudflare.d.ts +2 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +4 -0
- package/dist/cloudflare.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 +42 -23
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +169 -20
- 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/Errors.d.ts +6 -0
- package/dist/internal/Errors.d.ts.map +1 -0
- package/dist/internal/Errors.js +16 -0
- package/dist/internal/Errors.js.map +1 -0
- package/dist/internal/FundingQuotes.d.ts +644 -0
- package/dist/internal/FundingQuotes.d.ts.map +1 -0
- package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +93 -289
- package/dist/internal/FundingQuotes.js.map +1 -0
- package/dist/internal/Store.d.ts +61 -16
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +72 -17
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Viem.d.ts +2 -0
- 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 +27 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +27 -0
- package/dist/internal/index.js.map +1 -0
- package/package.json +8 -3
- package/src/ApiKeys.test.ts +23 -0
- package/src/App.test-d.ts +2 -0
- package/src/App.test.ts +60 -4
- package/src/App.ts +6 -0
- package/src/Client.test-d.ts +16 -54
- 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/FundingQuoteProviders.ts +251 -0
- 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 +149 -306
- package/src/apps/data/routes/funding.ts +154 -604
- package/src/apps/data/routes/indexer.test.ts +99 -3
- 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 +23 -2
- 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/mcp.test.ts +55 -14
- package/src/apps/mcp.ts +3 -1
- package/src/apps/relay/App.test.ts +324 -4
- package/src/apps/relay/App.ts +40 -4
- package/src/cloudflare.ts +5 -0
- 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 +528 -37
- package/src/internal/Auth.ts +246 -45
- 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/FundingQuotes.ts +735 -0
- package/src/internal/Log.test.ts +4 -4
- package/src/internal/Store.test-d.ts +15 -0
- package/src/internal/Store.test.ts +153 -2
- package/src/internal/Store.ts +157 -41
- package/src/internal/Viem.ts +14 -5
- package/src/internal/index.test.ts +34 -0
- package/src/internal/index.ts +26 -0
- package/dist/apps/data/FundingRouteProviders.d.ts +0 -143
- package/dist/apps/data/FundingRouteProviders.d.ts.map +0 -1
- package/dist/apps/data/FundingRouteProviders.js +0 -546
- package/dist/apps/data/FundingRouteProviders.js.map +0 -1
- package/dist/internal/FundingRoutes.d.ts +0 -1085
- 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 -485
- package/src/apps/data/FundingRouteProviders.ts +0 -744
- package/src/internal/FundingRoutes.test.ts +0 -239
- package/src/internal/FundingRoutes.ts +0 -1067
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import * as FundingQuotes from '../../internal/FundingQuotes.js'
|
|
2
|
+
|
|
3
|
+
type Fetch = typeof globalThis.fetch
|
|
4
|
+
|
|
5
|
+
const defaultFetch: Fetch = (input, init) => globalThis.fetch(input, init)
|
|
6
|
+
|
|
7
|
+
const defaultRelayBaseUrl = 'https://api.relay.link'
|
|
8
|
+
|
|
9
|
+
/** Creates the live funding quote adapters configured for this deployment. */
|
|
10
|
+
export function createAdapters(options: createAdapters.Options = {}) {
|
|
11
|
+
return (
|
|
12
|
+
options.relay ? { relay: relay(options.relay) } : {}
|
|
13
|
+
) satisfies FundingQuotes.ProviderAdapters
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export declare namespace createAdapters {
|
|
17
|
+
/** Provider adapter factory options. Omit a provider to leave it unconfigured. */
|
|
18
|
+
type Options = {
|
|
19
|
+
/** Relay quote adapter options. */
|
|
20
|
+
relay?: relay.Options | false | undefined
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Creates a Relay quote adapter backed by `/quote/v2`. */
|
|
25
|
+
export function relay(options: relay.Options): FundingQuotes.ProviderAdapter {
|
|
26
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultRelayBaseUrl)
|
|
27
|
+
const fetcher = options.fetch ?? defaultFetch
|
|
28
|
+
const now = options.now ?? (() => new Date())
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
async probeQuote({ candidate }, signal) {
|
|
32
|
+
const originChainId = providerChainId(candidate.sourceChain, 'relay')
|
|
33
|
+
const destinationChainId = eip155ChainId(candidate.destinationChain)
|
|
34
|
+
const headers = relayHeaders(options.apiKey)
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const quote = await requestJson(new URL(`${baseUrl}/quote/v2`), {
|
|
38
|
+
body: {
|
|
39
|
+
amount: candidate.sourceAmount.amount,
|
|
40
|
+
destinationChainId: Number(destinationChainId),
|
|
41
|
+
destinationCurrency: candidate.destinationToken.address,
|
|
42
|
+
originChainId: Number(originChainId),
|
|
43
|
+
originCurrency: candidate.sourceToken.address,
|
|
44
|
+
recipient: options.recipientAddress ?? options.userAddress,
|
|
45
|
+
tradeType: 'EXACT_INPUT',
|
|
46
|
+
user: options.userAddress,
|
|
47
|
+
},
|
|
48
|
+
fetcher,
|
|
49
|
+
headers,
|
|
50
|
+
method: 'POST',
|
|
51
|
+
signal,
|
|
52
|
+
})
|
|
53
|
+
return formatRelayResult(candidate, quote, now())
|
|
54
|
+
} catch (cause) {
|
|
55
|
+
const unavailable = providerUnavailable(cause, [
|
|
56
|
+
'NO_QUOTES',
|
|
57
|
+
'NO_ROUTE',
|
|
58
|
+
'UNSUPPORTED_ROUTE',
|
|
59
|
+
])
|
|
60
|
+
if (!unavailable) throw cause
|
|
61
|
+
return unavailableResult({
|
|
62
|
+
detail: 'relay:quote-v2',
|
|
63
|
+
message: unavailable,
|
|
64
|
+
now: now(),
|
|
65
|
+
source: 'providerQuote',
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export declare namespace relay {
|
|
73
|
+
/** Relay quote adapter options. */
|
|
74
|
+
type Options = {
|
|
75
|
+
/** Probe wallet address required by Relay's quote API. */
|
|
76
|
+
userAddress: string
|
|
77
|
+
/** Optional recipient override; defaults to `userAddress`. */
|
|
78
|
+
recipientAddress?: string | undefined
|
|
79
|
+
/** Optional Relay API key for higher rate limits. */
|
|
80
|
+
apiKey?: string | undefined
|
|
81
|
+
/** Override for tests or Relay testnet environments. */
|
|
82
|
+
baseUrl?: string | undefined
|
|
83
|
+
/** Fetch implementation override for tests. */
|
|
84
|
+
fetch?: Fetch | undefined
|
|
85
|
+
/** Clock override for deterministic tests. */
|
|
86
|
+
now?: (() => Date) | undefined
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatRelayResult(
|
|
91
|
+
candidate: FundingQuotes.QuoteCandidate,
|
|
92
|
+
quote: unknown,
|
|
93
|
+
now: Date,
|
|
94
|
+
): FundingQuotes.QuoteProbeResult {
|
|
95
|
+
const details = record(record(quote)['details'])
|
|
96
|
+
const currencyOut = record(details['currencyOut'])
|
|
97
|
+
const outputAmount = decimalString(currencyOut['amount'])
|
|
98
|
+
const status = outputAmount
|
|
99
|
+
? { status: 'available' as const, tier: 'liquid' as const }
|
|
100
|
+
: { status: 'unknown' as const, tier: 'unknown' as const }
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
minimumOutputAmount: decimalString(currencyOut['minimumAmount']),
|
|
104
|
+
outputAmount,
|
|
105
|
+
outputAmountFormatted: outputAmount
|
|
106
|
+
? decimalFromBaseUnits(outputAmount, candidate.destinationToken.decimals)
|
|
107
|
+
: undefined,
|
|
108
|
+
quality: {
|
|
109
|
+
estimatedSeconds: nonnegativeInteger(details['timeEstimate']),
|
|
110
|
+
liquiditySource: 'providerQuote',
|
|
111
|
+
sourceDetail: 'relay:quote-v2',
|
|
112
|
+
tier: status.tier,
|
|
113
|
+
},
|
|
114
|
+
sampledAt: now.toISOString(),
|
|
115
|
+
status: status.status,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function unavailableResult(options: {
|
|
120
|
+
detail: string
|
|
121
|
+
message: string
|
|
122
|
+
now: Date
|
|
123
|
+
source: string
|
|
124
|
+
}): FundingQuotes.QuoteProbeResult {
|
|
125
|
+
return {
|
|
126
|
+
quality: {
|
|
127
|
+
liquiditySource: options.source,
|
|
128
|
+
sourceDetail: `${options.detail}:${options.message}`,
|
|
129
|
+
tier: 'unavailable',
|
|
130
|
+
},
|
|
131
|
+
sampledAt: options.now.toISOString(),
|
|
132
|
+
status: 'unavailable',
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function requestJson(
|
|
137
|
+
url: URL,
|
|
138
|
+
options: {
|
|
139
|
+
body?: unknown
|
|
140
|
+
fetcher: Fetch
|
|
141
|
+
headers?: Record<string, string> | undefined
|
|
142
|
+
method?: string | undefined
|
|
143
|
+
signal: AbortSignal
|
|
144
|
+
},
|
|
145
|
+
) {
|
|
146
|
+
const headers = { accept: 'application/json', ...options.headers }
|
|
147
|
+
const init: RequestInit = {
|
|
148
|
+
headers:
|
|
149
|
+
options.body === undefined ? headers : { ...headers, 'content-type': 'application/json' },
|
|
150
|
+
method: options.method ?? 'GET',
|
|
151
|
+
signal: options.signal,
|
|
152
|
+
}
|
|
153
|
+
if (options.body !== undefined) init.body = JSON.stringify(options.body)
|
|
154
|
+
const response = await options.fetcher(url, init)
|
|
155
|
+
const text = await response.text()
|
|
156
|
+
const body = parseJson(text)
|
|
157
|
+
if (!response.ok) throw new ProviderResponseError(response.status, body)
|
|
158
|
+
return body
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function relayHeaders(apiKey: string | undefined): Record<string, string> | undefined {
|
|
162
|
+
return apiKey ? { 'x-api-key': apiKey } : undefined
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function providerUnavailable(cause: unknown, codes: readonly string[]) {
|
|
166
|
+
if (!(cause instanceof ProviderResponseError) || cause.status < 400 || cause.status >= 500)
|
|
167
|
+
return undefined
|
|
168
|
+
const code = providerErrorCode(cause.body)
|
|
169
|
+
if (code && codes.includes(code)) return code
|
|
170
|
+
return undefined
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function providerErrorCode(input: unknown) {
|
|
174
|
+
const body = record(input)
|
|
175
|
+
const code = stringValue(body['code']) ?? stringValue(body['errorCode'])
|
|
176
|
+
return code && /^[A-Za-z0-9_.:-]{1,64}$/.test(code) ? code : undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function providerChainId(
|
|
180
|
+
quoteChain: FundingQuotes.QuoteCandidate['sourceChain'],
|
|
181
|
+
provider: string,
|
|
182
|
+
) {
|
|
183
|
+
const providerIds = quoteChain.providerIds as Record<string, string> | undefined
|
|
184
|
+
const id = providerIds?.[provider]
|
|
185
|
+
if (id && /^\d+$/.test(id)) return id
|
|
186
|
+
return eip155ChainId(quoteChain)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function eip155ChainId(chain: { id: string; kind: string }) {
|
|
190
|
+
if (chain.kind !== 'evm') throw new UnsupportedProviderChainError()
|
|
191
|
+
const match = /^eip155:(\d+)$/.exec(chain.id)
|
|
192
|
+
if (!match?.[1]) throw new UnsupportedProviderChainError()
|
|
193
|
+
return match[1]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function normalizeBaseUrl(input: string) {
|
|
197
|
+
return input.replace(/\/+$/, '')
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function parseJson(input: string) {
|
|
201
|
+
if (!input) return null
|
|
202
|
+
try {
|
|
203
|
+
return JSON.parse(input) as unknown
|
|
204
|
+
} catch {
|
|
205
|
+
return input
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function record(input: unknown): Record<string, unknown> {
|
|
210
|
+
return input && typeof input === 'object' && !Array.isArray(input)
|
|
211
|
+
? (input as Record<string, unknown>)
|
|
212
|
+
: {}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function stringValue(input: unknown) {
|
|
216
|
+
return typeof input === 'string' && input.length > 0 ? input : undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function decimalString(input: unknown) {
|
|
220
|
+
if (typeof input === 'string' && /^\d+(\.\d+)?$/.test(input)) return input
|
|
221
|
+
if (typeof input === 'number' && Number.isFinite(input) && input >= 0) return String(input)
|
|
222
|
+
return undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function nonnegativeInteger(input: unknown) {
|
|
226
|
+
if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0) return input
|
|
227
|
+
if (typeof input === 'string' && /^\d+$/.test(input)) return Number(input)
|
|
228
|
+
return undefined
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function decimalFromBaseUnits(baseUnits: string, decimals: number) {
|
|
232
|
+
const padded = baseUnits.padStart(decimals + 1, '0')
|
|
233
|
+
const whole = decimals === 0 ? padded : padded.slice(0, -decimals)
|
|
234
|
+
const fraction = decimals === 0 ? '' : padded.slice(-decimals).replace(/0+$/, '')
|
|
235
|
+
return fraction ? `${whole}.${fraction}` : whole
|
|
236
|
+
}
|
|
237
|
+
|
|
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
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
class UnsupportedProviderChainError extends Error {
|
|
250
|
+
override name = 'FundingQuoteProviders.UnsupportedProviderChainError'
|
|
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
|
|