tapimo 0.4.3 → 0.4.4
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 +2491 -2861
- package/dist/App.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- package/dist/apps/data/App.d.ts +2354 -2724
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +3 -110
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +1 -302
- package/dist/apps/data/FundingRouteProviders.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 +138 -730
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +7 -241
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- 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/management/App.d.ts +855 -855
- package/dist/apps/management/routes/api-keys.d.ts +40 -40
- package/dist/apps/management/routes/billing.d.ts +207 -207
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +30 -30
- package/dist/apps/management/routes/orgs.d.ts +92 -92
- package/dist/apps/management/routes/projects.d.ts +28 -28
- package/dist/apps/management/routes/usage.d.ts +24 -24
- 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/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/internal/Auth.d.ts +33 -22
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +40 -11
- package/dist/internal/Auth.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/FundingRoutes.d.ts +15 -213
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +1 -72
- package/dist/internal/FundingRoutes.js.map +1 -1
- 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 +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/App.test.ts +60 -4
- package/src/Client.test-d.ts +0 -47
- package/src/apps/data/FundingRouteProviders.test.ts +37 -297
- package/src/apps/data/FundingRouteProviders.ts +3 -435
- package/src/apps/data/routes/funding.test.ts +7 -109
- package/src/apps/data/routes/funding.ts +8 -323
- package/src/apps/data/routes/indexer.test.ts +2 -2
- package/src/apps/data/routes/webhooks.test.ts +2 -2
- package/src/apps/mcp.test.ts +55 -14
- package/src/apps/mcp.ts +3 -1
- package/src/cloudflare.ts +5 -0
- package/src/internal/Auth.test.ts +397 -16
- package/src/internal/Auth.ts +76 -32
- package/src/internal/FundingRoutes.test.ts +8 -34
- package/src/internal/FundingRoutes.ts +1 -174
- 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/index.test.ts +34 -0
- package/src/internal/index.ts +26 -0
package/src/App.test.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as http from 'node:http'
|
|
2
|
+
import { Challenge } from 'mppx'
|
|
2
3
|
import { ApiKeys, App, Metrics, RateLimit, Store } from 'tapimo'
|
|
3
4
|
import * as Docs from 'tapimo/docs'
|
|
4
5
|
import * as Tokens from './apps/data/routes/tokens.js'
|
|
5
6
|
import type * as Log from './internal/Log.js'
|
|
6
7
|
import * as TestApp from '../test/App.js'
|
|
7
8
|
import * as Runtime from '../test/runtime.js'
|
|
9
|
+
import * as Tempo from '../test/Tempo.js'
|
|
8
10
|
|
|
9
11
|
describe('create', () => {
|
|
10
12
|
test('composes default dependencies', async () => {
|
|
@@ -208,6 +210,53 @@ describe('create', () => {
|
|
|
208
210
|
`)
|
|
209
211
|
})
|
|
210
212
|
|
|
213
|
+
test('applies the configured global MPP rate limit', async () => {
|
|
214
|
+
const store = Store.memory()
|
|
215
|
+
const limit = { perMinute: 2 }
|
|
216
|
+
const app = TestApp.create({
|
|
217
|
+
mpp: { rateLimit: limit },
|
|
218
|
+
rateLimit: { store },
|
|
219
|
+
})
|
|
220
|
+
await exhaustQuota(RateLimit.memory({ store }), 'mpp:public:203.0.113.10', limit)
|
|
221
|
+
|
|
222
|
+
const response = await app.request(`/v1/tokens/${TestApp.token}`, {
|
|
223
|
+
headers: {
|
|
224
|
+
authorization: 'Payment malformed',
|
|
225
|
+
'x-forwarded-for': '203.0.113.10',
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
expect(response.status).toBe(429)
|
|
230
|
+
expect(response.headers.get('RateLimit-Limit')).toBe('2')
|
|
231
|
+
expect(response.headers.get('RateLimit-Scope')).toBe('mpp')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('endpoint MPP rate limit overrides the global limit', async () => {
|
|
235
|
+
const store = Store.memory()
|
|
236
|
+
const routeLimit = { perMinute: 1 }
|
|
237
|
+
const app = TestApp.create({
|
|
238
|
+
auth: {
|
|
239
|
+
overrides: {
|
|
240
|
+
'GET /v1/tokens/:token': { mpp: { rateLimit: routeLimit } },
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
mpp: { rateLimit: { perMinute: 2 } },
|
|
244
|
+
rateLimit: { store },
|
|
245
|
+
})
|
|
246
|
+
await exhaustQuota(RateLimit.memory({ store }), 'mpp:public:203.0.113.10', routeLimit)
|
|
247
|
+
|
|
248
|
+
const response = await app.request(`/v1/tokens/${TestApp.token}`, {
|
|
249
|
+
headers: {
|
|
250
|
+
authorization: 'Payment malformed',
|
|
251
|
+
'x-forwarded-for': '203.0.113.10',
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
expect(response.status).toBe(429)
|
|
256
|
+
expect(response.headers.get('RateLimit-Limit')).toBe('1')
|
|
257
|
+
expect(response.headers.get('RateLimit-Scope')).toBe('mpp')
|
|
258
|
+
})
|
|
259
|
+
|
|
211
260
|
test('serves Vocs API reference', async () => {
|
|
212
261
|
const app = TestApp.create({ docs: Docs.docs() })
|
|
213
262
|
const response = await app.request('/')
|
|
@@ -602,11 +651,13 @@ describe('create', () => {
|
|
|
602
651
|
auth,
|
|
603
652
|
cache: { store },
|
|
604
653
|
mpp: {
|
|
605
|
-
charge: {
|
|
606
|
-
currency: 'USD',
|
|
607
|
-
decimals: 2,
|
|
608
|
-
},
|
|
609
654
|
secretKey: 'secret_test_key_0123456789abcdef',
|
|
655
|
+
session: {
|
|
656
|
+
chainId: Tempo.chain.id,
|
|
657
|
+
decimals: 6,
|
|
658
|
+
getClient: () => Tempo.client,
|
|
659
|
+
recipient: Tempo.accounts[2].address,
|
|
660
|
+
},
|
|
610
661
|
},
|
|
611
662
|
rateLimit: {
|
|
612
663
|
now,
|
|
@@ -627,6 +678,11 @@ describe('create', () => {
|
|
|
627
678
|
})
|
|
628
679
|
const overQuota = await app.request('/v1/tokens/not-an-address')
|
|
629
680
|
expect(overQuota.status).toMatchInlineSnapshot(`402`)
|
|
681
|
+
expect(Challenge.fromHeaders(overQuota.headers).request['currency']).toBe(Tempo.currency)
|
|
682
|
+
|
|
683
|
+
const head = await app.request('/v1/tokens/not-an-address', { method: 'HEAD' })
|
|
684
|
+
expect(head.status).toMatchInlineSnapshot(`402`)
|
|
685
|
+
expect(Challenge.fromHeaders(head.headers).intent).toBe('session')
|
|
630
686
|
})
|
|
631
687
|
|
|
632
688
|
test('supports bearer API-key fallback', async () => {
|
package/src/Client.test-d.ts
CHANGED
|
@@ -1221,53 +1221,6 @@ describe('narrows responses by status', () => {
|
|
|
1221
1221
|
}
|
|
1222
1222
|
})
|
|
1223
1223
|
|
|
1224
|
-
test('POST /v1/funding/routes/quote', async () => {
|
|
1225
|
-
const client = Client.create()
|
|
1226
|
-
const res = await client.v1.funding.routes.quote.$post({
|
|
1227
|
-
json: {
|
|
1228
|
-
depositor: '0x1111111111111111111111111111111111111111',
|
|
1229
|
-
provider: 'across',
|
|
1230
|
-
sourceAmount: '1000000',
|
|
1231
|
-
sourceChain: 'base',
|
|
1232
|
-
sourceToken: 'USDC',
|
|
1233
|
-
},
|
|
1234
|
-
})
|
|
1235
|
-
|
|
1236
|
-
expectTypeOf(res.status).toEqualTypeOf<200 | 400 | 404 | 501 | 502 | PaidAccessStatus>()
|
|
1237
|
-
|
|
1238
|
-
switch (res.status) {
|
|
1239
|
-
case 200: {
|
|
1240
|
-
const json = await res.json()
|
|
1241
|
-
expectTypeOf(json).toEqualTypeOf<
|
|
1242
|
-
z.output<typeof Funding.schema.quoteFundingRoute.Response>
|
|
1243
|
-
>()
|
|
1244
|
-
return
|
|
1245
|
-
}
|
|
1246
|
-
case 400: {
|
|
1247
|
-
const json = await res.json()
|
|
1248
|
-
expectTypeOf(json.error.code).toEqualTypeOf<Access400<'body_invalid'>>()
|
|
1249
|
-
return
|
|
1250
|
-
}
|
|
1251
|
-
case 404: {
|
|
1252
|
-
const json = await res.json()
|
|
1253
|
-
expectTypeOf(json.error.code).toEqualTypeOf<'quote_unavailable' | 'route_not_found'>()
|
|
1254
|
-
return
|
|
1255
|
-
}
|
|
1256
|
-
case 501: {
|
|
1257
|
-
const json = await res.json()
|
|
1258
|
-
expectTypeOf(json.error.code).toEqualTypeOf<'provider_unconfigured'>()
|
|
1259
|
-
return
|
|
1260
|
-
}
|
|
1261
|
-
case 502: {
|
|
1262
|
-
const json = await res.json()
|
|
1263
|
-
expectTypeOf(json.error.code).toEqualTypeOf<
|
|
1264
|
-
'provider_error' | 'provider_rate_limited' | 'provider_timeout'
|
|
1265
|
-
>()
|
|
1266
|
-
return
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
})
|
|
1270
|
-
|
|
1271
1224
|
test('GET /v1/funding/chains', async () => {
|
|
1272
1225
|
const client = Client.create()
|
|
1273
1226
|
const res = await client.v1.funding.chains.$get()
|
|
@@ -4,249 +4,6 @@ import * as FundingRouteProviders from './FundingRouteProviders.js'
|
|
|
4
4
|
const now = () => new Date('2026-01-01T00:00:00.000Z')
|
|
5
5
|
|
|
6
6
|
describe('FundingRouteProviders', () => {
|
|
7
|
-
test('maps Across suggested-fees responses into quote probes', async () => {
|
|
8
|
-
const requests: RecordedRequest[] = []
|
|
9
|
-
const fetch = recordingFetch(requests, async () =>
|
|
10
|
-
jsonResponse({
|
|
11
|
-
estimatedFillTimeSec: 2,
|
|
12
|
-
limits: {
|
|
13
|
-
maxDeposit: '2000000',
|
|
14
|
-
maxDepositInstant: '1500000',
|
|
15
|
-
minDeposit: '500000',
|
|
16
|
-
},
|
|
17
|
-
outputAmount: '990000',
|
|
18
|
-
relayFeePct: '1200000000000000',
|
|
19
|
-
timestamp: '1767225600',
|
|
20
|
-
}),
|
|
21
|
-
)
|
|
22
|
-
const adapter = FundingRouteProviders.across({
|
|
23
|
-
apiKey: 'across-api-key',
|
|
24
|
-
baseUrl: 'https://across.test/api/',
|
|
25
|
-
fetch,
|
|
26
|
-
integratorId: 'tempo',
|
|
27
|
-
now,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const result = await adapter.probeQuote(
|
|
31
|
-
await probeInput('across'),
|
|
32
|
-
new AbortController().signal,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
expect(requests).toHaveLength(1)
|
|
36
|
-
expect(requests[0]?.url.pathname).toBe('/api/suggested-fees')
|
|
37
|
-
expect(requests[0]?.url.searchParams.get('originChainId')).toBe('8453')
|
|
38
|
-
expect(requests[0]?.url.searchParams.get('destinationChainId')).toBe('4217')
|
|
39
|
-
expect(requests[0]?.url.searchParams.get('amount')).toBe('1000000')
|
|
40
|
-
expect(requests[0]?.url.searchParams.get('integratorId')).toBe('tempo')
|
|
41
|
-
expect(requests[0]?.url.searchParams.get('allowUnmatchedDecimals')).toBe('true')
|
|
42
|
-
expect(new Headers(requests[0]?.init?.headers).get('authorization')).toBe(
|
|
43
|
-
'Bearer across-api-key',
|
|
44
|
-
)
|
|
45
|
-
expect(result).toMatchObject({
|
|
46
|
-
expiresAt: '2026-01-01T00:10:00.000Z',
|
|
47
|
-
limits: {
|
|
48
|
-
instantSourceAmount: '1500000',
|
|
49
|
-
maxSourceAmount: '2000000',
|
|
50
|
-
minSourceAmount: '500000',
|
|
51
|
-
},
|
|
52
|
-
outputAmount: '990000',
|
|
53
|
-
outputAmountFormatted: '0.99',
|
|
54
|
-
quality: {
|
|
55
|
-
availableVolumeUsd: '2',
|
|
56
|
-
estimatedSeconds: 2,
|
|
57
|
-
feeBps: 12,
|
|
58
|
-
instantVolumeUsd: '1.5',
|
|
59
|
-
liquiditySource: 'providerLimit',
|
|
60
|
-
sourceDetail: 'across:suggested-fees',
|
|
61
|
-
tier: 'liquid',
|
|
62
|
-
},
|
|
63
|
-
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
64
|
-
status: 'available',
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('maps recognized Across quote errors to unavailable probes', async () => {
|
|
69
|
-
const fetch = recordingFetch([], async () =>
|
|
70
|
-
jsonResponse({ code: 'AMOUNT_TOO_HIGH', message: 'Amount exceeds max deposit' }, 400),
|
|
71
|
-
)
|
|
72
|
-
const adapter = FundingRouteProviders.across({
|
|
73
|
-
apiKey: 'across-api-key',
|
|
74
|
-
fetch,
|
|
75
|
-
integratorId: 'tempo',
|
|
76
|
-
now,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const result = await adapter.probeQuote(
|
|
80
|
-
await probeInput('across'),
|
|
81
|
-
new AbortController().signal,
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
expect(result).toMatchObject({
|
|
85
|
-
quality: {
|
|
86
|
-
liquiditySource: 'providerLimit',
|
|
87
|
-
score: 0,
|
|
88
|
-
sourceDetail: 'across:suggested-fees:AMOUNT_TOO_HIGH',
|
|
89
|
-
tier: 'unavailable',
|
|
90
|
-
},
|
|
91
|
-
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
92
|
-
status: 'unavailable',
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
test('builds an authenticated exact-input Across quote request', async () => {
|
|
97
|
-
const route = await staticRoute('across')
|
|
98
|
-
const request = FundingRouteProviders.createAcrossQuoteRequest({
|
|
99
|
-
apiKey: 'across-api-key',
|
|
100
|
-
baseUrl: 'https://across.test/api/',
|
|
101
|
-
depositor: '0x1111111111111111111111111111111111111111',
|
|
102
|
-
integratorId: '0xdead',
|
|
103
|
-
recipient: '0x2222222222222222222222222222222222222222',
|
|
104
|
-
route,
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
expect(request.url.pathname).toBe('/api/swap/approval')
|
|
108
|
-
expect(request.url.searchParams.get('originChainId')).toBe('8453')
|
|
109
|
-
expect(request.url.searchParams.get('destinationChainId')).toBe('4217')
|
|
110
|
-
expect(request.url.searchParams.get('amount')).toBe('1000000')
|
|
111
|
-
expect(request.url.searchParams.get('integratorId')).toBe('0xdead')
|
|
112
|
-
expect(request.url.searchParams.get('tradeType')).toBe('exactInput')
|
|
113
|
-
expect(request.url.searchParams.get('skipOriginTxEstimation')).toBe('true')
|
|
114
|
-
expect(request.url.searchParams.get('strictTradeType')).toBe('true')
|
|
115
|
-
expect(request.url.searchParams.get('depositor')).toBe(
|
|
116
|
-
'0x1111111111111111111111111111111111111111',
|
|
117
|
-
)
|
|
118
|
-
expect(request.url.searchParams.get('recipient')).toBe(
|
|
119
|
-
'0x2222222222222222222222222222222222222222',
|
|
120
|
-
)
|
|
121
|
-
expect(new Headers(request.headers).get('authorization')).toBe('Bearer across-api-key')
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
test('calls the runtime fetch method with its global receiver', async () => {
|
|
125
|
-
const route = await staticRoute('across')
|
|
126
|
-
vi.useFakeTimers()
|
|
127
|
-
vi.setSystemTime(now())
|
|
128
|
-
const fetch = vi.fn(function (this: unknown) {
|
|
129
|
-
expect(this).toBe(globalThis)
|
|
130
|
-
return Promise.resolve(jsonResponse(acrossQuoteResponse(route)))
|
|
131
|
-
})
|
|
132
|
-
vi.stubGlobal('fetch', fetch)
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const quote = FundingRouteProviders.acrossQuote({
|
|
136
|
-
apiKey: 'across-api-key',
|
|
137
|
-
integratorId: '0xdead',
|
|
138
|
-
})
|
|
139
|
-
const result = await quote(
|
|
140
|
-
{ depositor: '0x1111111111111111111111111111111111111111', route },
|
|
141
|
-
new AbortController().signal,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
expect(result.outputAmount).toBe('990000')
|
|
145
|
-
expect(fetch).toHaveBeenCalledOnce()
|
|
146
|
-
} finally {
|
|
147
|
-
vi.unstubAllGlobals()
|
|
148
|
-
vi.useRealTimers()
|
|
149
|
-
}
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
test('normalizes an Across quote payload', async () => {
|
|
153
|
-
const route = await staticRoute('across')
|
|
154
|
-
const result = FundingRouteProviders.parseAcrossQuote({
|
|
155
|
-
body: acrossQuoteResponse(route),
|
|
156
|
-
now: now(),
|
|
157
|
-
route,
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
expect(result).toMatchObject({
|
|
161
|
-
estimatedSeconds: 2,
|
|
162
|
-
expiresAt: '2026-01-01T00:01:00.000Z',
|
|
163
|
-
fees: {
|
|
164
|
-
total: {
|
|
165
|
-
amount: '1200',
|
|
166
|
-
amountUsd: '0.0012',
|
|
167
|
-
bps: 12,
|
|
168
|
-
token: { chainId: 'eip155:8453', symbol: 'USDC' },
|
|
169
|
-
},
|
|
170
|
-
totalMax: {
|
|
171
|
-
amount: '1800',
|
|
172
|
-
amountUsd: '0.0018',
|
|
173
|
-
bps: 18,
|
|
174
|
-
token: { chainId: 'eip155:8453', symbol: 'USDC' },
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
minimumOutputAmount: '988000',
|
|
178
|
-
outputAmount: '990000',
|
|
179
|
-
sampledAt: '2026-01-01T00:00:00.000Z',
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
test('maps Across HTTP errors into stable provider errors', () => {
|
|
184
|
-
expect(
|
|
185
|
-
FundingRouteProviders.mapAcrossQuoteError({ body: { code: 'RATE_LIMITED' }, status: 429 }),
|
|
186
|
-
).toBeInstanceOf(FundingRoutes.ProviderRateLimitError)
|
|
187
|
-
expect(
|
|
188
|
-
FundingRouteProviders.mapAcrossQuoteError({ body: { code: 'NO_ROUTE' }, status: 400 }),
|
|
189
|
-
).toBeInstanceOf(FundingRoutes.ProviderQuoteUnavailableError)
|
|
190
|
-
const error = FundingRouteProviders.mapAcrossQuoteError({
|
|
191
|
-
body: { code: 'NOT_FOUND' },
|
|
192
|
-
status: 404,
|
|
193
|
-
})
|
|
194
|
-
expect(error).toMatchObject({ name: 'FundingRouteProviders.ProviderResponseError' })
|
|
195
|
-
expect(FundingRouteProviders.acrossQuoteFailure({ cause: error })).toEqual({
|
|
196
|
-
code: 'NOT_FOUND',
|
|
197
|
-
failure: 'http',
|
|
198
|
-
id: 'across',
|
|
199
|
-
operation: 'swap/approval',
|
|
200
|
-
status: 404,
|
|
201
|
-
})
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
test('rejects invalid Across configuration during adapter construction', () => {
|
|
205
|
-
expect(() =>
|
|
206
|
-
FundingRouteProviders.acrossQuote({ apiKey: 'across-api-key', integratorId: 'tempo' }),
|
|
207
|
-
).toThrowErrorMatchingInlineSnapshot(`[FundingRouteProviders.ProviderConfigurationError]`)
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
test('rejects malformed Across quote payloads', async () => {
|
|
211
|
-
const route = await staticRoute('across')
|
|
212
|
-
const failure = (() => {
|
|
213
|
-
try {
|
|
214
|
-
FundingRouteProviders.parseAcrossQuote({
|
|
215
|
-
body: { expectedOutputAmount: '990000' },
|
|
216
|
-
now: now(),
|
|
217
|
-
route,
|
|
218
|
-
})
|
|
219
|
-
throw new Error('Expected malformed payload to fail')
|
|
220
|
-
} catch (cause) {
|
|
221
|
-
return cause
|
|
222
|
-
}
|
|
223
|
-
})()
|
|
224
|
-
expect(failure).toMatchObject({ name: 'FundingRouteProviders.ProviderPayloadError' })
|
|
225
|
-
expect(FundingRouteProviders.acrossQuoteFailure({ cause: failure })).toEqual({
|
|
226
|
-
failure: 'payload',
|
|
227
|
-
id: 'across',
|
|
228
|
-
operation: 'swap/approval',
|
|
229
|
-
})
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
test('rejects inconsistent Across fee summaries', async () => {
|
|
233
|
-
const route = await staticRoute('across')
|
|
234
|
-
const body = acrossQuoteResponse(route)
|
|
235
|
-
body.fees.totalMax.amount = '1000'
|
|
236
|
-
expect(() => FundingRouteProviders.parseAcrossQuote({ body, now: now(), route })).toThrow(
|
|
237
|
-
expect.objectContaining({ name: 'FundingRouteProviders.ProviderPayloadError' }),
|
|
238
|
-
)
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
test('rejects mismatched Across fee tokens', async () => {
|
|
242
|
-
const route = await staticRoute('across')
|
|
243
|
-
const body = acrossQuoteResponse(route)
|
|
244
|
-
body.fees.total.token.address = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
|
|
245
|
-
expect(() => FundingRouteProviders.parseAcrossQuote({ body, now: now(), route })).toThrow(
|
|
246
|
-
expect.objectContaining({ name: 'FundingRouteProviders.ProviderPayloadError' }),
|
|
247
|
-
)
|
|
248
|
-
})
|
|
249
|
-
|
|
250
7
|
test('maps Relay liquidity and quote responses into quote probes', async () => {
|
|
251
8
|
const requests: RecordedRequest[] = []
|
|
252
9
|
const route = await staticRoute('relay')
|
|
@@ -319,6 +76,43 @@ describe('FundingRouteProviders', () => {
|
|
|
319
76
|
})
|
|
320
77
|
})
|
|
321
78
|
|
|
79
|
+
test('calls the runtime fetch method with its global receiver', async () => {
|
|
80
|
+
const route = await staticRoute('relay')
|
|
81
|
+
const fetch = vi.fn(function (this: unknown, input: Parameters<typeof globalThis.fetch>[0]) {
|
|
82
|
+
expect(this).toBe(globalThis)
|
|
83
|
+
const url = toUrl(input)
|
|
84
|
+
return Promise.resolve(
|
|
85
|
+
jsonResponse(
|
|
86
|
+
url.pathname === '/quote/v2'
|
|
87
|
+
? {
|
|
88
|
+
details: {
|
|
89
|
+
currencyOut: { amount: '990000', minimumAmount: '980000' },
|
|
90
|
+
timeEstimate: 15,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
: { liquidity: [] },
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
vi.stubGlobal('fetch', fetch)
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const adapter = FundingRouteProviders.relay({
|
|
101
|
+
now,
|
|
102
|
+
userAddress: '0x1111111111111111111111111111111111111111',
|
|
103
|
+
})
|
|
104
|
+
const result = await adapter.probeQuote(
|
|
105
|
+
probeInputFromRoute(route),
|
|
106
|
+
new AbortController().signal,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
expect(result.outputAmount).toBe('990000')
|
|
110
|
+
expect(fetch).toHaveBeenCalledTimes(2)
|
|
111
|
+
} finally {
|
|
112
|
+
vi.unstubAllGlobals()
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
322
116
|
test('keeps Relay quotes when the liquidity probe fails', async () => {
|
|
323
117
|
const route = await staticRoute('relay')
|
|
324
118
|
const fetch = recordingFetch([], async (request) => {
|
|
@@ -378,10 +172,6 @@ async function staticRoute(
|
|
|
378
172
|
return route as FundingRoutes.QuoteProbeInput['route']
|
|
379
173
|
}
|
|
380
174
|
|
|
381
|
-
async function probeInput(provider: FundingRoutes.ProviderId) {
|
|
382
|
-
return probeInputFromRoute(await staticRoute(provider))
|
|
383
|
-
}
|
|
384
|
-
|
|
385
175
|
function probeInputFromRoute(
|
|
386
176
|
route: FundingRoutes.QuoteProbeInput['route'],
|
|
387
177
|
): FundingRoutes.QuoteProbeInput {
|
|
@@ -406,56 +196,6 @@ function probeInputFromRoute(
|
|
|
406
196
|
}
|
|
407
197
|
}
|
|
408
198
|
|
|
409
|
-
function acrossQuoteResponse(route: FundingRoutes.QuoteProbeInput['route']) {
|
|
410
|
-
return {
|
|
411
|
-
amountType: 'exactInput',
|
|
412
|
-
approvalTxns: [{ data: '0x1234', to: '0x2222222222222222222222222222222222222222' }],
|
|
413
|
-
checks: { balance: { actual: '1000000', expected: '1000000' } },
|
|
414
|
-
expectedFillTime: 2,
|
|
415
|
-
expectedOutputAmount: '990000',
|
|
416
|
-
fees: {
|
|
417
|
-
total: {
|
|
418
|
-
amount: '1200',
|
|
419
|
-
amountUsd: '0.0012',
|
|
420
|
-
pct: '1200000000000000',
|
|
421
|
-
token: {
|
|
422
|
-
address: route.sourceToken.address,
|
|
423
|
-
chainId: 8453,
|
|
424
|
-
decimals: 6,
|
|
425
|
-
symbol: 'USDC',
|
|
426
|
-
},
|
|
427
|
-
},
|
|
428
|
-
totalMax: {
|
|
429
|
-
amount: '1800',
|
|
430
|
-
amountUsd: '0.0018',
|
|
431
|
-
pct: '1800000000000000',
|
|
432
|
-
token: {
|
|
433
|
-
address: route.sourceToken.address,
|
|
434
|
-
chainId: 8453,
|
|
435
|
-
decimals: 6,
|
|
436
|
-
symbol: 'USDC',
|
|
437
|
-
},
|
|
438
|
-
},
|
|
439
|
-
},
|
|
440
|
-
inputAmount: '1000000',
|
|
441
|
-
inputToken: {
|
|
442
|
-
address: route.sourceToken.address,
|
|
443
|
-
chainId: 8453,
|
|
444
|
-
decimals: 6,
|
|
445
|
-
symbol: 'USDC',
|
|
446
|
-
},
|
|
447
|
-
minOutputAmount: '988000',
|
|
448
|
-
outputToken: {
|
|
449
|
-
address: route.destinationToken.address,
|
|
450
|
-
chainId: 4217,
|
|
451
|
-
decimals: 6,
|
|
452
|
-
symbol: 'USDC.e',
|
|
453
|
-
},
|
|
454
|
-
quoteExpiryTimestamp: 1767225660,
|
|
455
|
-
swapTx: { data: '0xabcd', to: '0x3333333333333333333333333333333333333333' },
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
199
|
function recordingFetch(
|
|
460
200
|
requests: RecordedRequest[],
|
|
461
201
|
respond: (request: RecordedRequest) => Response | Promise<Response>,
|