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
|
@@ -1,381 +1,224 @@
|
|
|
1
1
|
import { Schema } from 'tapimo'
|
|
2
2
|
|
|
3
|
+
import type * as FundingQuotes from '../../../internal/FundingQuotes.js'
|
|
3
4
|
import * as TestApp from '../../../../test/App.js'
|
|
4
|
-
import type * as Log from '../../../internal/Log.js'
|
|
5
|
-
import * as FundingRouteProviders from '../FundingRouteProviders.js'
|
|
6
5
|
import * as Funding from './funding.js'
|
|
7
6
|
|
|
8
|
-
describe('GET /funding/
|
|
9
|
-
test('returns
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
query: {
|
|
13
|
-
destinationToken: 'USDC.e',
|
|
14
|
-
sourceAmount: '1000000',
|
|
15
|
-
sourceChain: 'base',
|
|
16
|
-
sourceToken: 'USDC',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
TestApp.auth,
|
|
20
|
-
)
|
|
7
|
+
describe('GET /funding/quotes', () => {
|
|
8
|
+
test('returns successful live quotes with output amounts', async () => {
|
|
9
|
+
const app = TestApp.create({ funding: { adapters: { relay: relayAdapter() } } })
|
|
10
|
+
const response = await app.request(quoteUrl(), { headers: TestApp.auth.headers })
|
|
21
11
|
expect(response.status).toBe(200)
|
|
22
|
-
|
|
12
|
+
expect(response.headers.get('cache-control')).toBe('no-store')
|
|
13
|
+
const body = await TestApp.json(response, Funding.schema.quoteFunding.Response)
|
|
23
14
|
|
|
24
|
-
expect(body.data.
|
|
25
|
-
'across',
|
|
26
|
-
'relay',
|
|
27
|
-
'bridge',
|
|
28
|
-
'bungee',
|
|
29
|
-
])
|
|
15
|
+
expect(body.data).toHaveLength(1)
|
|
30
16
|
expect(body.data[0]).toMatchObject({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
id: 'base-usdc-tempo-usdce-relay',
|
|
18
|
+
provider: { id: 'relay' },
|
|
19
|
+
quality: { liquiditySource: 'providerQuote', tier: 'liquid' },
|
|
20
|
+
quote: {
|
|
21
|
+
inputAmount: '1000000',
|
|
22
|
+
outputAmount: '990000',
|
|
23
|
+
outputAmountFormatted: '0.99',
|
|
24
|
+
},
|
|
35
25
|
sourceAmount: { amount: '1000000', formatted: '1' },
|
|
36
|
-
sourceChain: { id: 'eip155:8453', name: 'Base' },
|
|
37
|
-
sourceToken: { symbol: 'USDC' },
|
|
38
|
-
status: 'unknown',
|
|
39
26
|
})
|
|
40
27
|
})
|
|
41
28
|
|
|
42
|
-
test('
|
|
29
|
+
test('queries only the requested provider', async () => {
|
|
30
|
+
let calls = 0
|
|
43
31
|
const app = TestApp.create({
|
|
44
|
-
|
|
32
|
+
funding: {
|
|
45
33
|
adapters: {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
expect(input.cache.bypass).toBe(true)
|
|
50
|
-
expect(input.include).toEqual(['quotes'])
|
|
51
|
-
expect(input.route.id).toBe('base-usdc-tempo-usdce-across')
|
|
52
|
-
expect(input.sourceAmount.amount).toBe('1000000')
|
|
53
|
-
expect(input.sourceChain.id).toBe('eip155:8453')
|
|
54
|
-
expect(input.sourceToken.symbol).toBe('USDC')
|
|
55
|
-
return {
|
|
56
|
-
expiresAt: '2026-01-01T00:00:30Z',
|
|
57
|
-
limits: {
|
|
58
|
-
instantSourceAmount: '1000000000',
|
|
59
|
-
maxSourceAmount: '100000000000',
|
|
60
|
-
minSourceAmount: '500000',
|
|
61
|
-
},
|
|
62
|
-
minimumOutputAmount: '980000',
|
|
63
|
-
outputAmount: '990000',
|
|
64
|
-
outputAmountFormatted: '0.99',
|
|
65
|
-
quality: {
|
|
66
|
-
availableVolumeUsd: '1000.00',
|
|
67
|
-
estimatedSeconds: 4,
|
|
68
|
-
feeBps: 12,
|
|
69
|
-
historicalVolumeUsd24h: '100000.00',
|
|
70
|
-
instantVolumeUsd: '1000.00',
|
|
71
|
-
liquiditySource: 'providerQuote',
|
|
72
|
-
score: 91,
|
|
73
|
-
sourceDetail: 'providerEndpoint',
|
|
74
|
-
tier: 'liquid',
|
|
75
|
-
},
|
|
76
|
-
sampledAt: '2026-01-01T00:00:00Z',
|
|
77
|
-
status: 'available',
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
},
|
|
34
|
+
relay: relayAdapter(() => {
|
|
35
|
+
calls += 1
|
|
36
|
+
}),
|
|
81
37
|
},
|
|
82
38
|
},
|
|
83
39
|
})
|
|
40
|
+
const response = await app.request(quoteUrl({ ...baseUsdcQuery, provider: 'relay' }), {
|
|
41
|
+
headers: TestApp.auth.headers,
|
|
42
|
+
})
|
|
84
43
|
|
|
85
|
-
const response = await app.request(
|
|
86
|
-
'/v1/funding/routes?sourceChain=base&sourceToken=USDC&sourceAmount=1000000&provider=across&include=quotes',
|
|
87
|
-
{ headers: { ...TestApp.auth.headers, 'Cache-Control': 'no-cache' } },
|
|
88
|
-
)
|
|
89
44
|
expect(response.status).toBe(200)
|
|
90
|
-
|
|
45
|
+
expect(calls).toBe(1)
|
|
46
|
+
})
|
|
91
47
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
id: 'base-usdc-tempo-usdce-across',
|
|
96
|
-
quality: {
|
|
97
|
-
availableVolumeUsd: '1000.00',
|
|
98
|
-
liquiditySource: 'providerQuote',
|
|
99
|
-
sourceDetail: 'providerEndpoint',
|
|
100
|
-
tier: 'liquid',
|
|
101
|
-
},
|
|
102
|
-
quote: {
|
|
103
|
-
expiresAt: '2026-01-01T00:00:30Z',
|
|
104
|
-
inputAmount: '1000000',
|
|
105
|
-
limits: {
|
|
106
|
-
instantSourceAmount: '1000000000',
|
|
107
|
-
maxSourceAmount: '100000000000',
|
|
108
|
-
minSourceAmount: '500000',
|
|
109
|
-
},
|
|
110
|
-
minimumOutputAmount: '980000',
|
|
111
|
-
outputAmount: '990000',
|
|
112
|
-
outputAmountFormatted: '0.99',
|
|
113
|
-
sampledAt: '2026-01-01T00:00:00Z',
|
|
114
|
-
},
|
|
115
|
-
status: 'available',
|
|
48
|
+
test('returns an empty list when no provider is configured', async () => {
|
|
49
|
+
const response = await TestApp.create().request(quoteUrl(), {
|
|
50
|
+
headers: TestApp.auth.headers,
|
|
116
51
|
})
|
|
52
|
+
expect(response.status).toBe(200)
|
|
53
|
+
const body = await TestApp.json(response, Funding.schema.quoteFunding.Response)
|
|
54
|
+
expect(body.data).toEqual([])
|
|
117
55
|
})
|
|
118
56
|
|
|
119
|
-
test('
|
|
120
|
-
const
|
|
121
|
-
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
57
|
+
test('returns an empty list when the provider reports no quote', async () => {
|
|
58
|
+
const app = TestApp.create({
|
|
59
|
+
funding: {
|
|
60
|
+
adapters: {
|
|
61
|
+
relay: {
|
|
62
|
+
async probeQuote() {
|
|
63
|
+
return { sampledAt: '2026-01-01T00:00:00Z', status: 'unavailable' }
|
|
64
|
+
},
|
|
65
|
+
},
|
|
128
66
|
},
|
|
129
67
|
},
|
|
130
|
-
TestApp.auth,
|
|
131
|
-
)
|
|
132
|
-
expect(response.status).toBe(200)
|
|
133
|
-
const body = await TestApp.json(response, Funding.schema.listFundingRoutes.Response)
|
|
134
|
-
|
|
135
|
-
expect(body.data).toHaveLength(1)
|
|
136
|
-
expect(body.data[0]?.sourceAmount).toMatchObject({
|
|
137
|
-
amount: '1250000',
|
|
138
|
-
formatted: '1.25',
|
|
139
68
|
})
|
|
69
|
+
const response = await app.request(quoteUrl(), { headers: TestApp.auth.headers })
|
|
70
|
+
expect(response.status).toBe(200)
|
|
71
|
+
const body = await TestApp.json(response, Funding.schema.quoteFunding.Response)
|
|
72
|
+
expect(body.data).toEqual([])
|
|
140
73
|
})
|
|
141
74
|
|
|
142
|
-
test('
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
},
|
|
152
|
-
TestApp.auth,
|
|
153
|
-
)
|
|
154
|
-
expect(exactResponse.status).toBe(200)
|
|
155
|
-
const exactBody = await TestApp.json(exactResponse, Funding.schema.listFundingRoutes.Response)
|
|
156
|
-
expect(exactBody.data.map((route) => route.provider.id)).toEqual(['bridge'])
|
|
157
|
-
|
|
158
|
-
const lowercasedSolanaResponse = await TestApp.client().v1.funding.routes.$get(
|
|
159
|
-
{
|
|
160
|
-
query: {
|
|
161
|
-
sourceAmount: '1000000',
|
|
162
|
-
sourceChain: 'solana',
|
|
163
|
-
sourceToken: solanaMint.toLowerCase(),
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
TestApp.auth,
|
|
167
|
-
)
|
|
168
|
-
expect(lowercasedSolanaResponse.status).toBe(200)
|
|
169
|
-
const lowercasedSolanaBody = await TestApp.json(
|
|
170
|
-
lowercasedSolanaResponse,
|
|
171
|
-
Funding.schema.listFundingRoutes.Response,
|
|
172
|
-
)
|
|
173
|
-
expect(lowercasedSolanaBody.data).toEqual([])
|
|
174
|
-
|
|
175
|
-
const lowercasedTronResponse = await TestApp.client().v1.funding.routes.$get(
|
|
176
|
-
{
|
|
177
|
-
query: {
|
|
178
|
-
sourceAmount: '1000000',
|
|
179
|
-
sourceChain: 'tron',
|
|
180
|
-
sourceToken: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'.toLowerCase(),
|
|
75
|
+
test('returns 502 when every matching provider fails', async () => {
|
|
76
|
+
const app = TestApp.create({
|
|
77
|
+
funding: {
|
|
78
|
+
adapters: {
|
|
79
|
+
relay: {
|
|
80
|
+
async probeQuote() {
|
|
81
|
+
throw new Error('upstream unavailable')
|
|
82
|
+
},
|
|
83
|
+
},
|
|
181
84
|
},
|
|
182
85
|
},
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
expect(
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
Funding.schema.listFundingRoutes.Response,
|
|
189
|
-
)
|
|
190
|
-
expect(lowercasedTronBody.data).toEqual([])
|
|
86
|
+
})
|
|
87
|
+
const response = await app.request(quoteUrl(), { headers: TestApp.auth.headers })
|
|
88
|
+
expect(response.status).toBe(502)
|
|
89
|
+
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
90
|
+
expect(body.error.code).toBe('upstream_error')
|
|
191
91
|
})
|
|
192
92
|
|
|
193
|
-
test('
|
|
194
|
-
const
|
|
195
|
-
{
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
93
|
+
test('rejects successful provider results without output amounts', async () => {
|
|
94
|
+
const app = TestApp.create({
|
|
95
|
+
funding: {
|
|
96
|
+
adapters: {
|
|
97
|
+
relay: {
|
|
98
|
+
async probeQuote() {
|
|
99
|
+
return { sampledAt: '2026-01-01T00:00:00Z', status: 'available' }
|
|
100
|
+
},
|
|
101
|
+
},
|
|
201
102
|
},
|
|
202
103
|
},
|
|
203
|
-
|
|
204
|
-
)
|
|
205
|
-
expect(response.status).toBe(
|
|
206
|
-
const body = await TestApp.json(response, Funding.schema.listFundingRoutes.Response)
|
|
207
|
-
expect(body.data).toEqual([])
|
|
104
|
+
})
|
|
105
|
+
const response = await app.request(quoteUrl(), { headers: TestApp.auth.headers })
|
|
106
|
+
expect(response.status).toBe(502)
|
|
208
107
|
})
|
|
209
108
|
|
|
210
|
-
test('rejects
|
|
211
|
-
const response = await TestApp.
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
sourceAmount: '1.0000001',
|
|
215
|
-
sourceAmountUnits: 'decimal',
|
|
216
|
-
sourceChain: 'base',
|
|
217
|
-
sourceToken: 'USDC',
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
TestApp.auth,
|
|
109
|
+
test('rejects invalid query parameters', async () => {
|
|
110
|
+
const response = await TestApp.create().request(
|
|
111
|
+
quoteUrl({ sourceAmount: '1000000', sourceChain: 'base' }),
|
|
112
|
+
{ headers: TestApp.auth.headers },
|
|
221
113
|
)
|
|
222
114
|
expect(response.status).toBe(400)
|
|
223
115
|
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
224
116
|
expect(body.error.code).toBe('query_invalid')
|
|
225
117
|
})
|
|
226
118
|
|
|
227
|
-
test('rejects unsupported
|
|
119
|
+
test('rejects unsupported providers', async () => {
|
|
228
120
|
const response = await TestApp.create().request(
|
|
229
|
-
'
|
|
121
|
+
quoteUrl({ ...baseUsdcQuery, provider: 'bridge' }),
|
|
230
122
|
{ headers: TestApp.auth.headers },
|
|
231
123
|
)
|
|
232
124
|
expect(response.status).toBe(400)
|
|
233
125
|
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
234
126
|
expect(body.error.code).toBe('query_invalid')
|
|
235
127
|
})
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
describe('POST /funding/routes/quote', () => {
|
|
239
|
-
test('rejects invalid depositor addresses', async () => {
|
|
240
|
-
const response = await TestApp.create().request('/v1/funding/routes/quote', {
|
|
241
|
-
body: JSON.stringify({
|
|
242
|
-
depositor: 'invalid',
|
|
243
|
-
provider: 'across',
|
|
244
|
-
sourceAmount: '1000000',
|
|
245
|
-
sourceChain: 'base',
|
|
246
|
-
sourceToken: 'USDC',
|
|
247
|
-
}),
|
|
248
|
-
headers: { ...TestApp.auth.headers, 'content-type': 'application/json' },
|
|
249
|
-
method: 'POST',
|
|
250
|
-
})
|
|
251
128
|
|
|
129
|
+
test('rejects source chains without a live quote adapter', async () => {
|
|
130
|
+
const response = await TestApp.create().request(
|
|
131
|
+
quoteUrl({ ...baseUsdcQuery, sourceChain: 'solana' }),
|
|
132
|
+
{ headers: TestApp.auth.headers },
|
|
133
|
+
)
|
|
252
134
|
expect(response.status).toBe(400)
|
|
253
|
-
|
|
254
|
-
expect(
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
test('returns a stable error when Across quoting is not configured', async () => {
|
|
258
|
-
const response = await TestApp.create().request('/v1/funding/routes/quote', {
|
|
259
|
-
body: JSON.stringify(quoteRequest()),
|
|
260
|
-
headers: { ...TestApp.auth.headers, 'content-type': 'application/json' },
|
|
261
|
-
method: 'POST',
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
expect(response.status).toBe(501)
|
|
265
|
-
expect(response.headers.get('cache-control')).toBe('no-store')
|
|
266
|
-
expect(await response.json()).toMatchObject({ error: { code: 'provider_unconfigured' } })
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
test('records bounded Across failure details in the request log', async () => {
|
|
270
|
-
const entries: Log.Entry[] = []
|
|
271
|
-
const response = await TestApp.create({
|
|
272
|
-
fundingRoutes: {
|
|
273
|
-
async acrossQuote() {
|
|
274
|
-
throw FundingRouteProviders.mapAcrossQuoteError({
|
|
275
|
-
body: { code: 'AUTH_FAILED', detail: 'not logged' },
|
|
276
|
-
status: 401,
|
|
277
|
-
})
|
|
278
|
-
},
|
|
279
|
-
},
|
|
280
|
-
logger: (entry) => void entries.push(entry),
|
|
281
|
-
}).request('/v1/funding/routes/quote', {
|
|
282
|
-
body: JSON.stringify(quoteRequest()),
|
|
283
|
-
headers: { ...TestApp.auth.headers, 'content-type': 'application/json' },
|
|
284
|
-
method: 'POST',
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
expect(response.status).toBe(502)
|
|
288
|
-
expect(entries[0]).toMatchObject({
|
|
289
|
-
errorCode: 'provider_error',
|
|
290
|
-
provider: {
|
|
291
|
-
code: 'AUTH_FAILED',
|
|
292
|
-
failure: 'http',
|
|
293
|
-
id: 'across',
|
|
294
|
-
operation: 'swap/approval',
|
|
295
|
-
status: 401,
|
|
296
|
-
},
|
|
297
|
-
})
|
|
298
|
-
expect(JSON.stringify(entries[0])).not.toContain('not logged')
|
|
135
|
+
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
136
|
+
expect(body.error.code).toBe('query_invalid')
|
|
299
137
|
})
|
|
300
138
|
|
|
301
|
-
test('
|
|
302
|
-
const response = await TestApp.create().request(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
expect(operation).toMatchObject({
|
|
309
|
-
operationId: 'quoteFundingRoute',
|
|
310
|
-
responses: { 200: expect.any(Object), 400: expect.any(Object), 502: expect.any(Object) },
|
|
311
|
-
})
|
|
312
|
-
expect(JSON.stringify(operation)).not.toMatch(/approvalTxns|checks|swapTx/)
|
|
139
|
+
test('does not expose the removed routes endpoint', async () => {
|
|
140
|
+
const response = await TestApp.create().request(
|
|
141
|
+
'/v1/funding/routes?sourceChain=base&sourceToken=USDC&sourceAmount=1000000',
|
|
142
|
+
{ headers: TestApp.auth.headers },
|
|
143
|
+
)
|
|
144
|
+
expect(response.status).toBe(404)
|
|
313
145
|
})
|
|
314
146
|
})
|
|
315
147
|
|
|
316
148
|
describe('GET /funding/chains', () => {
|
|
317
|
-
test('lists
|
|
149
|
+
test('lists only inputs supported by quote providers', async () => {
|
|
318
150
|
const response = await TestApp.client().v1.funding.chains.$get(undefined, TestApp.auth)
|
|
319
151
|
expect(response.status).toBe(200)
|
|
320
152
|
const body = await TestApp.json(response, Funding.schema.listFundingChains.Response)
|
|
321
153
|
|
|
322
|
-
expect(body.data.map((chain) => chain.id)).toEqual([
|
|
323
|
-
|
|
324
|
-
'
|
|
325
|
-
'eip155:56',
|
|
326
|
-
'solana:mainnet',
|
|
327
|
-
'tron:mainnet',
|
|
328
|
-
])
|
|
329
|
-
expect(body.data.find((chain) => chain.id === 'solana:mainnet')?.tokens).toEqual(
|
|
330
|
-
expect.arrayContaining([expect.objectContaining({ symbol: 'USDC', standard: 'SPL' })]),
|
|
154
|
+
expect(body.data.map((chain) => chain.id)).toEqual(['eip155:1', 'eip155:8453', 'eip155:56'])
|
|
155
|
+
expect(body.data.flatMap((chain) => chain.tokens.map((token) => token.symbol))).toEqual(
|
|
156
|
+
expect.arrayContaining(['USDC', 'USDT']),
|
|
331
157
|
)
|
|
332
158
|
})
|
|
333
159
|
})
|
|
334
160
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
sourceToken: 'USDC',
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
describe('GET /providers', () => {
|
|
346
|
-
test('lists providers filtered by capability', async () => {
|
|
347
|
-
const response = await TestApp.client().v1.providers.$get(
|
|
348
|
-
{ query: { capability: 'fundingRoutes' } },
|
|
349
|
-
TestApp.auth,
|
|
350
|
-
)
|
|
161
|
+
describe('GET /funding/providers', () => {
|
|
162
|
+
test('lists quote providers and their configuration status', async () => {
|
|
163
|
+
const app = TestApp.create({ funding: { adapters: { relay: relayAdapter() } } })
|
|
164
|
+
const response = await app.request('/v1/funding/providers', {
|
|
165
|
+
headers: TestApp.auth.headers,
|
|
166
|
+
})
|
|
351
167
|
expect(response.status).toBe(200)
|
|
352
168
|
const body = await TestApp.json(response, Funding.schema.listProviders.Response)
|
|
353
169
|
|
|
354
|
-
expect(body.data
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
true,
|
|
363
|
-
)
|
|
364
|
-
expect(body.data.map((provider) => provider.status)).toEqual([
|
|
365
|
-
'inventoryOnly',
|
|
366
|
-
'inventoryOnly',
|
|
367
|
-
'inventoryOnly',
|
|
368
|
-
'inventoryOnly',
|
|
369
|
-
'planned',
|
|
170
|
+
expect(body.data).toEqual([
|
|
171
|
+
{
|
|
172
|
+
id: 'relay',
|
|
173
|
+
kind: 'solverNetwork',
|
|
174
|
+
name: 'Relay',
|
|
175
|
+
requiresApiKey: false,
|
|
176
|
+
status: 'configured',
|
|
177
|
+
},
|
|
370
178
|
])
|
|
371
179
|
})
|
|
372
180
|
|
|
373
|
-
test('
|
|
374
|
-
const response = await TestApp.create().request('/v1/providers
|
|
181
|
+
test('does not expose the removed generic provider endpoint', async () => {
|
|
182
|
+
const response = await TestApp.create().request('/v1/providers', {
|
|
375
183
|
headers: TestApp.auth.headers,
|
|
376
184
|
})
|
|
377
|
-
expect(response.status).toBe(
|
|
378
|
-
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
379
|
-
expect(body.error.code).toBe('query_invalid')
|
|
185
|
+
expect(response.status).toBe(404)
|
|
380
186
|
})
|
|
381
187
|
})
|
|
188
|
+
|
|
189
|
+
const baseUsdcQuery = {
|
|
190
|
+
sourceAmount: '1000000',
|
|
191
|
+
sourceChain: 'base',
|
|
192
|
+
sourceToken: 'USDC',
|
|
193
|
+
} as const
|
|
194
|
+
|
|
195
|
+
type QuoteQuery = Record<string, string>
|
|
196
|
+
|
|
197
|
+
function quoteUrl(query: QuoteQuery = baseUsdcQuery) {
|
|
198
|
+
return `/v1/funding/quotes?${new URLSearchParams(
|
|
199
|
+
Object.entries(query).map(([key, value]) => [key, String(value)]),
|
|
200
|
+
)}`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function relayAdapter(onProbe: () => void = () => {}): FundingQuotes.ProviderAdapter {
|
|
204
|
+
return {
|
|
205
|
+
async probeQuote(input, signal) {
|
|
206
|
+
onProbe()
|
|
207
|
+
expect(signal.aborted).toBe(false)
|
|
208
|
+
expect(input.candidate.id).toBe('base-usdc-tempo-usdce-relay')
|
|
209
|
+
return {
|
|
210
|
+
minimumOutputAmount: '980000',
|
|
211
|
+
outputAmount: '990000',
|
|
212
|
+
outputAmountFormatted: '0.99',
|
|
213
|
+
quality: {
|
|
214
|
+
estimatedSeconds: 4,
|
|
215
|
+
liquiditySource: 'providerQuote',
|
|
216
|
+
sourceDetail: 'relay:quote-v2',
|
|
217
|
+
tier: 'liquid',
|
|
218
|
+
},
|
|
219
|
+
sampledAt: '2026-01-01T00:00:00Z',
|
|
220
|
+
status: 'available',
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
}
|