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.
Files changed (192) hide show
  1. package/dist/App.d.ts +7222 -6316
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +4 -0
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +18 -1
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +24 -3
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/apps/verified-tokens.d.ts +116 -116
  10. package/dist/apps/data/App.d.ts +2541 -2607
  11. package/dist/apps/data/App.d.ts.map +1 -1
  12. package/dist/apps/data/App.js +135 -44
  13. package/dist/apps/data/App.js.map +1 -1
  14. package/dist/apps/data/FundingQuoteProviders.d.ts +36 -0
  15. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -0
  16. package/dist/apps/data/FundingQuoteProviders.js +190 -0
  17. package/dist/apps/data/FundingQuoteProviders.js.map +1 -0
  18. package/dist/apps/data/index.d.ts +1 -1
  19. package/dist/apps/data/index.js +1 -1
  20. package/dist/apps/data/routes/activities.d.ts +242 -242
  21. package/dist/apps/data/routes/addresses.d.ts +227 -227
  22. package/dist/apps/data/routes/balances.d.ts +12 -12
  23. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  24. package/dist/apps/data/routes/balances.js +67 -149
  25. package/dist/apps/data/routes/balances.js.map +1 -1
  26. package/dist/apps/data/routes/blocks.d.ts +125 -126
  27. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  28. package/dist/apps/data/routes/blocks.js +3 -5
  29. package/dist/apps/data/routes/blocks.js.map +1 -1
  30. package/dist/apps/data/routes/coingecko.d.ts +55 -54
  31. package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
  32. package/dist/apps/data/routes/coingecko.js +3 -8
  33. package/dist/apps/data/routes/coingecko.js.map +1 -1
  34. package/dist/apps/data/routes/exchanges.d.ts +455 -455
  35. package/dist/apps/data/routes/fee-amm.d.ts +92 -92
  36. package/dist/apps/data/routes/funding.d.ts +91 -864
  37. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  38. package/dist/apps/data/routes/funding.js +138 -450
  39. package/dist/apps/data/routes/funding.js.map +1 -1
  40. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  41. package/dist/apps/data/routes/rpc.js +86 -16
  42. package/dist/apps/data/routes/rpc.js.map +1 -1
  43. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  44. package/dist/apps/data/routes/webhooks.d.ts +340 -0
  45. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  46. package/dist/apps/data/routes/webhooks.js +13 -1
  47. package/dist/apps/data/routes/webhooks.js.map +1 -1
  48. package/dist/apps/index.d.ts +1 -1
  49. package/dist/apps/index.js +1 -1
  50. package/dist/apps/management/App.d.ts +1843 -861
  51. package/dist/apps/management/App.d.ts.map +1 -1
  52. package/dist/apps/management/App.js +4 -0
  53. package/dist/apps/management/App.js.map +1 -1
  54. package/dist/apps/management/routes/api-keys.d.ts +121 -121
  55. package/dist/apps/management/routes/api-keys.js +2 -2
  56. package/dist/apps/management/routes/api-keys.js.map +1 -1
  57. package/dist/apps/management/routes/billing.d.ts +2 -2
  58. package/dist/apps/management/routes/invite-links.d.ts +1100 -0
  59. package/dist/apps/management/routes/invite-links.d.ts.map +1 -0
  60. package/dist/apps/management/routes/invite-links.js +411 -0
  61. package/dist/apps/management/routes/invite-links.js.map +1 -0
  62. package/dist/apps/management/routes/me.d.ts +13 -13
  63. package/dist/apps/management/routes/members.d.ts +77 -77
  64. package/dist/apps/management/routes/orgs.d.ts +96 -96
  65. package/dist/apps/management/routes/projects.d.ts +62 -62
  66. package/dist/apps/management/routes/scopes.d.ts +11 -11
  67. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  68. package/dist/apps/management/routes/scopes.js +2 -2
  69. package/dist/apps/management/routes/scopes.js.map +1 -1
  70. package/dist/apps/management/routes/usage.d.ts +79 -79
  71. package/dist/apps/mcp.d.ts.map +1 -1
  72. package/dist/apps/mcp.js +3 -1
  73. package/dist/apps/mcp.js.map +1 -1
  74. package/dist/apps/relay/App.d.ts.map +1 -1
  75. package/dist/apps/relay/App.js +42 -4
  76. package/dist/apps/relay/App.js.map +1 -1
  77. package/dist/cloudflare.d.ts +2 -0
  78. package/dist/cloudflare.d.ts.map +1 -1
  79. package/dist/cloudflare.js +4 -0
  80. package/dist/cloudflare.js.map +1 -1
  81. package/dist/db/Db.d.ts +3 -0
  82. package/dist/db/Db.d.ts.map +1 -1
  83. package/dist/db/Db.js +57 -0
  84. package/dist/db/Db.js.map +1 -1
  85. package/dist/db/Schema.d.ts +31 -0
  86. package/dist/db/Schema.d.ts.map +1 -1
  87. package/dist/db/Schema.js +31 -0
  88. package/dist/db/Schema.js.map +1 -1
  89. package/dist/db/tables/inviteLinks.d.ts +42 -0
  90. package/dist/db/tables/inviteLinks.d.ts.map +1 -0
  91. package/dist/db/tables/inviteLinks.js +148 -0
  92. package/dist/db/tables/inviteLinks.js.map +1 -0
  93. package/dist/db/tables/organizations.d.ts.map +1 -1
  94. package/dist/db/tables/organizations.js +2 -0
  95. package/dist/db/tables/organizations.js.map +1 -1
  96. package/dist/handlers/relay.d.ts.map +1 -1
  97. package/dist/handlers/relay.js +13 -3
  98. package/dist/handlers/relay.js.map +1 -1
  99. package/dist/internal/Auth.d.ts +42 -23
  100. package/dist/internal/Auth.d.ts.map +1 -1
  101. package/dist/internal/Auth.js +169 -20
  102. package/dist/internal/Auth.js.map +1 -1
  103. package/dist/internal/EdgeCache.d.ts +3 -1
  104. package/dist/internal/EdgeCache.d.ts.map +1 -1
  105. package/dist/internal/EdgeCache.js +17 -2
  106. package/dist/internal/EdgeCache.js.map +1 -1
  107. package/dist/internal/Errors.d.ts +6 -0
  108. package/dist/internal/Errors.d.ts.map +1 -0
  109. package/dist/internal/Errors.js +16 -0
  110. package/dist/internal/Errors.js.map +1 -0
  111. package/dist/internal/FundingQuotes.d.ts +644 -0
  112. package/dist/internal/FundingQuotes.d.ts.map +1 -0
  113. package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +93 -289
  114. package/dist/internal/FundingQuotes.js.map +1 -0
  115. package/dist/internal/Store.d.ts +61 -16
  116. package/dist/internal/Store.d.ts.map +1 -1
  117. package/dist/internal/Store.js +72 -17
  118. package/dist/internal/Store.js.map +1 -1
  119. package/dist/internal/Viem.d.ts +2 -0
  120. package/dist/internal/Viem.d.ts.map +1 -1
  121. package/dist/internal/Viem.js +11 -2
  122. package/dist/internal/Viem.js.map +1 -1
  123. package/dist/internal/index.d.ts +27 -0
  124. package/dist/internal/index.d.ts.map +1 -0
  125. package/dist/internal/index.js +27 -0
  126. package/dist/internal/index.js.map +1 -0
  127. package/package.json +8 -3
  128. package/src/ApiKeys.test.ts +23 -0
  129. package/src/App.test-d.ts +2 -0
  130. package/src/App.test.ts +60 -4
  131. package/src/App.ts +6 -0
  132. package/src/Client.test-d.ts +16 -54
  133. package/src/Scope.test.ts +1 -0
  134. package/src/Scope.ts +30 -4
  135. package/src/apps/data/App.test.ts +354 -0
  136. package/src/apps/data/App.ts +159 -52
  137. package/src/apps/data/FundingQuoteProviders.test.ts +151 -0
  138. package/src/apps/data/FundingQuoteProviders.ts +251 -0
  139. package/src/apps/data/index.ts +1 -1
  140. package/src/apps/data/routes/balances.test.ts +64 -18
  141. package/src/apps/data/routes/balances.ts +76 -177
  142. package/src/apps/data/routes/blocks.ts +3 -5
  143. package/src/apps/data/routes/coingecko.ts +3 -8
  144. package/src/apps/data/routes/funding.test.ts +149 -306
  145. package/src/apps/data/routes/funding.ts +154 -604
  146. package/src/apps/data/routes/indexer.test.ts +99 -3
  147. package/src/apps/data/routes/rpc.test.ts +219 -0
  148. package/src/apps/data/routes/rpc.ts +92 -17
  149. package/src/apps/data/routes/webhooks.test.ts +23 -2
  150. package/src/apps/data/routes/webhooks.ts +13 -1
  151. package/src/apps/index.ts +1 -1
  152. package/src/apps/management/App.ts +4 -0
  153. package/src/apps/management/routes/api-keys.test.ts +47 -3
  154. package/src/apps/management/routes/api-keys.ts +2 -2
  155. package/src/apps/management/routes/invite-links.test.ts +385 -0
  156. package/src/apps/management/routes/invite-links.ts +570 -0
  157. package/src/apps/management/routes/scopes.test.ts +3 -2
  158. package/src/apps/management/routes/scopes.ts +4 -2
  159. package/src/apps/mcp.test.ts +55 -14
  160. package/src/apps/mcp.ts +3 -1
  161. package/src/apps/relay/App.test.ts +324 -4
  162. package/src/apps/relay/App.ts +40 -4
  163. package/src/cloudflare.ts +5 -0
  164. package/src/db/Db.ts +65 -0
  165. package/src/db/Schema.ts +39 -0
  166. package/src/db/tables/inviteLinks.ts +187 -0
  167. package/src/db/tables/organizations.ts +2 -0
  168. package/src/handlers/relay.ts +16 -4
  169. package/src/internal/Auth.test.ts +528 -37
  170. package/src/internal/Auth.ts +246 -45
  171. package/src/internal/EdgeCache.test.ts +24 -3
  172. package/src/internal/EdgeCache.ts +18 -2
  173. package/src/internal/FundingQuotes.test.ts +174 -0
  174. package/src/internal/FundingQuotes.ts +735 -0
  175. package/src/internal/Log.test.ts +4 -4
  176. package/src/internal/Store.test-d.ts +15 -0
  177. package/src/internal/Store.test.ts +153 -2
  178. package/src/internal/Store.ts +157 -41
  179. package/src/internal/Viem.ts +14 -5
  180. package/src/internal/index.test.ts +34 -0
  181. package/src/internal/index.ts +26 -0
  182. package/dist/apps/data/FundingRouteProviders.d.ts +0 -143
  183. package/dist/apps/data/FundingRouteProviders.d.ts.map +0 -1
  184. package/dist/apps/data/FundingRouteProviders.js +0 -546
  185. package/dist/apps/data/FundingRouteProviders.js.map +0 -1
  186. package/dist/internal/FundingRoutes.d.ts +0 -1085
  187. package/dist/internal/FundingRoutes.d.ts.map +0 -1
  188. package/dist/internal/FundingRoutes.js.map +0 -1
  189. package/src/apps/data/FundingRouteProviders.test.ts +0 -485
  190. package/src/apps/data/FundingRouteProviders.ts +0 -744
  191. package/src/internal/FundingRoutes.test.ts +0 -239
  192. 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/routes', () => {
9
- test('returns static inventory-backed route candidates', async () => {
10
- const response = await TestApp.client().v1.funding.routes.$get(
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
- const body = await TestApp.json(response, Funding.schema.listFundingRoutes.Response)
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.map((route) => route.provider.id)).toEqual([
25
- 'across',
26
- 'relay',
27
- 'bridge',
28
- 'bungee',
29
- ])
15
+ expect(body.data).toHaveLength(1)
30
16
  expect(body.data[0]).toMatchObject({
31
- destinationChain: { id: 'eip155:4217', name: 'Tempo' },
32
- destinationToken: { symbol: 'USDC.e' },
33
- id: 'base-usdc-tempo-usdce-across',
34
- quality: { liquiditySource: 'staticInventory', tier: 'unknown' },
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('enriches route candidates with configured provider adapters', async () => {
29
+ test('queries only the requested provider', async () => {
30
+ let calls = 0
43
31
  const app = TestApp.create({
44
- fundingRoutes: {
32
+ funding: {
45
33
  adapters: {
46
- across: {
47
- async probeQuote(input, signal) {
48
- expect(signal.aborted).toBe(false)
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
- const body = await TestApp.json(response, Funding.schema.listFundingRoutes.Response)
45
+ expect(calls).toBe(1)
46
+ })
91
47
 
92
- expect(body.data).toHaveLength(1)
93
- expect(body.data[0]).toMatchObject({
94
- errorCode: null,
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('converts decimal source amounts after resolving the source token', async () => {
120
- const response = await TestApp.client().v1.funding.routes.$get(
121
- {
122
- query: {
123
- provider: 'relay',
124
- sourceAmount: '1.25',
125
- sourceAmountUnits: 'decimal',
126
- sourceChain: 'eip155:8453',
127
- sourceToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
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('does not case-fold non-EVM source token identifiers', async () => {
143
- const solanaMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'
144
- const exactResponse = await TestApp.client().v1.funding.routes.$get(
145
- {
146
- query: {
147
- sourceAmount: '1000000',
148
- sourceChain: 'solana',
149
- sourceToken: solanaMint,
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
- TestApp.auth,
184
- )
185
- expect(lowercasedTronResponse.status).toBe(200)
186
- const lowercasedTronBody = await TestApp.json(
187
- lowercasedTronResponse,
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('returns no candidates for an unsupported source token', async () => {
194
- const response = await TestApp.client().v1.funding.routes.$get(
195
- {
196
- query: {
197
- sourceAmount: '1.234567890',
198
- sourceAmountUnits: 'decimal',
199
- sourceChain: 'base',
200
- sourceToken: 'DAI',
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
- TestApp.auth,
204
- )
205
- expect(response.status).toBe(200)
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 amounts that cannot be represented in source decimals', async () => {
211
- const response = await TestApp.client().v1.funding.routes.$get(
212
- {
213
- query: {
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 include parameters', async () => {
119
+ test('rejects unsupported providers', async () => {
228
120
  const response = await TestApp.create().request(
229
- '/v1/funding/routes?sourceChain=base&sourceToken=USDC&sourceAmount=1000000&include=debug',
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
- expect(response.headers.get('cache-control')).toBe('no-store')
254
- expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } })
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('publishes the normalized quote contract in OpenAPI', async () => {
302
- const response = await TestApp.create().request('/openapi.json')
303
- const document = (await response.json()) as {
304
- paths: Record<string, { post?: { operationId?: string; responses?: object } }>
305
- }
306
- const operation = document.paths['/v1/funding/routes/quote']?.post
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 source chain and stablecoin inventory', async () => {
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
- 'eip155:1',
324
- 'eip155:8453',
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
- function quoteRequest() {
336
- return {
337
- depositor: '0x1111111111111111111111111111111111111111',
338
- provider: 'across',
339
- sourceAmount: '1000000',
340
- sourceChain: 'base',
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.map((provider) => provider.id)).toEqual([
355
- 'across',
356
- 'relay',
357
- 'bridge',
358
- 'bungee',
359
- 'lifi',
360
- ])
361
- expect(body.data.every((provider) => provider.capabilities.includes('fundingRoutes'))).toBe(
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('rejects unsupported provider capabilities', async () => {
374
- const response = await TestApp.create().request('/v1/providers?capability=settlement', {
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(400)
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
+ }