tapimo 0.5.0 → 0.5.2

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 (197) hide show
  1. package/dist/App.d.ts +4854 -4829
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +20 -2
  4. package/dist/App.js.map +1 -1
  5. package/dist/Assets.js +3 -3
  6. package/dist/Assets.js.map +1 -1
  7. package/dist/Scope.d.ts.map +1 -1
  8. package/dist/Scope.js +1 -2
  9. package/dist/Scope.js.map +1 -1
  10. package/dist/Webhooks.d.ts +1 -1
  11. package/dist/Webhooks.d.ts.map +1 -1
  12. package/dist/Webhooks.js +5 -10
  13. package/dist/Webhooks.js.map +1 -1
  14. package/dist/admin/App.d.ts +6 -34
  15. package/dist/admin/App.d.ts.map +1 -1
  16. package/dist/admin/apps/api-keys.d.ts +3 -3
  17. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  18. package/dist/admin/apps/api-keys.js +4 -1
  19. package/dist/admin/apps/api-keys.js.map +1 -1
  20. package/dist/admin/apps/scopes.d.ts +6 -34
  21. package/dist/admin/apps/scopes.d.ts.map +1 -1
  22. package/dist/admin/apps/scopes.js +2 -2
  23. package/dist/admin/apps/scopes.js.map +1 -1
  24. package/dist/admin/apps/verified-tokens.d.ts +2 -2
  25. package/dist/admin/ui.generated.d.ts +1 -1
  26. package/dist/admin/ui.generated.js +33 -33
  27. package/dist/admin/ui.generated.js.map +1 -1
  28. package/dist/apps/data/App.d.ts +1398 -1405
  29. package/dist/apps/data/App.d.ts.map +1 -1
  30. package/dist/apps/data/App.js +25 -38
  31. package/dist/apps/data/App.js.map +1 -1
  32. package/dist/apps/data/FundingQuoteProviders.d.ts +108 -3
  33. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  34. package/dist/apps/data/FundingQuoteProviders.js +409 -2
  35. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  36. package/dist/apps/data/routes/activities.d.ts +245 -245
  37. package/dist/apps/data/routes/blocks.d.ts +123 -123
  38. package/dist/apps/data/routes/funding.d.ts +236 -146
  39. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  40. package/dist/apps/data/routes/funding.js +23 -12
  41. package/dist/apps/data/routes/funding.js.map +1 -1
  42. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  43. package/dist/apps/data/routes/indexer.js +9 -2
  44. package/dist/apps/data/routes/indexer.js.map +1 -1
  45. package/dist/apps/data/routes/rpc.d.ts +31 -31
  46. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  47. package/dist/apps/data/routes/transactions.d.ts +281 -281
  48. package/dist/apps/data/routes/transfers.d.ts +66 -73
  49. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  50. package/dist/apps/data/routes/transfers.js +21 -27
  51. package/dist/apps/data/routes/transfers.js.map +1 -1
  52. package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
  53. package/dist/apps/data/routes/webhooks.d.ts +35 -35
  54. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  55. package/dist/apps/data/routes/webhooks.js +176 -42
  56. package/dist/apps/data/routes/webhooks.js.map +1 -1
  57. package/dist/apps/management/App.d.ts +520 -504
  58. package/dist/apps/management/App.d.ts.map +1 -1
  59. package/dist/apps/management/Billing.d.ts.map +1 -1
  60. package/dist/apps/management/Billing.js +5 -4
  61. package/dist/apps/management/Billing.js.map +1 -1
  62. package/dist/apps/management/routes/api-keys.d.ts +120 -120
  63. package/dist/apps/management/routes/billing.d.ts +211 -211
  64. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  65. package/dist/apps/management/routes/billing.js +1 -1
  66. package/dist/apps/management/routes/billing.js.map +1 -1
  67. package/dist/apps/management/routes/invitations.d.ts +72 -72
  68. package/dist/apps/management/routes/invite-links.d.ts +159 -159
  69. package/dist/apps/management/routes/me.d.ts +17 -17
  70. package/dist/apps/management/routes/members.d.ts +80 -80
  71. package/dist/apps/management/routes/orgs.d.ts +59 -43
  72. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  73. package/dist/apps/management/routes/orgs.js +18 -1
  74. package/dist/apps/management/routes/orgs.js.map +1 -1
  75. package/dist/apps/management/routes/projects.d.ts +109 -109
  76. package/dist/apps/management/routes/scopes.d.ts +11 -11
  77. package/dist/apps/management/routes/usage.d.ts +79 -79
  78. package/dist/apps/mcp.js +2 -2
  79. package/dist/apps/mcp.js.map +1 -1
  80. package/dist/apps/relay/App.d.ts +2 -2
  81. package/dist/apps/relay/App.js +2 -2
  82. package/dist/apps/relay/Sponsorships.d.ts +3 -0
  83. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  84. package/dist/apps/relay/Sponsorships.js +15 -1
  85. package/dist/apps/relay/Sponsorships.js.map +1 -1
  86. package/dist/db/Db.d.ts.map +1 -1
  87. package/dist/db/Db.js +4 -0
  88. package/dist/db/Db.js.map +1 -1
  89. package/dist/db/Schema.js +1 -1
  90. package/dist/db/Schema.js.map +1 -1
  91. package/dist/db/tables/organizations.d.ts +6 -0
  92. package/dist/db/tables/organizations.d.ts.map +1 -1
  93. package/dist/db/tables/organizations.js +26 -0
  94. package/dist/db/tables/organizations.js.map +1 -1
  95. package/dist/db/tables/requestUsage.d.ts +2 -11
  96. package/dist/db/tables/requestUsage.d.ts.map +1 -1
  97. package/dist/db/tables/requestUsage.js +1 -20
  98. package/dist/db/tables/requestUsage.js.map +1 -1
  99. package/dist/db/tables/sponsoredTransactions.d.ts +2 -1
  100. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  101. package/dist/db/tables/sponsoredTransactions.js +52 -10
  102. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  103. package/dist/internal/Auth.d.ts +4 -0
  104. package/dist/internal/Auth.d.ts.map +1 -1
  105. package/dist/internal/Auth.js +15 -8
  106. package/dist/internal/Auth.js.map +1 -1
  107. package/dist/internal/Cache.d.ts.map +1 -1
  108. package/dist/internal/Cache.js +9 -1
  109. package/dist/internal/Cache.js.map +1 -1
  110. package/dist/internal/EdgeCache.d.ts.map +1 -1
  111. package/dist/internal/EdgeCache.js +12 -3
  112. package/dist/internal/EdgeCache.js.map +1 -1
  113. package/dist/internal/FundingQuotes.d.ts +176 -56
  114. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  115. package/dist/internal/FundingQuotes.js +100 -5
  116. package/dist/internal/FundingQuotes.js.map +1 -1
  117. package/dist/internal/Log.d.ts +18 -12
  118. package/dist/internal/Log.d.ts.map +1 -1
  119. package/dist/internal/Log.js +2 -0
  120. package/dist/internal/Log.js.map +1 -1
  121. package/dist/internal/RateLimit.d.ts +2 -0
  122. package/dist/internal/RateLimit.d.ts.map +1 -1
  123. package/dist/internal/RateLimit.js +4 -4
  124. package/dist/internal/RateLimit.js.map +1 -1
  125. package/dist/internal/Tidx.d.ts +9 -3
  126. package/dist/internal/Tidx.d.ts.map +1 -1
  127. package/dist/internal/Tidx.js +13 -7
  128. package/dist/internal/Tidx.js.map +1 -1
  129. package/dist/internal/Viem.d.ts +14 -1
  130. package/dist/internal/Viem.d.ts.map +1 -1
  131. package/dist/internal/Viem.js +19 -1
  132. package/dist/internal/Viem.js.map +1 -1
  133. package/package.json +7 -2
  134. package/src/ApiKeys.test.ts +2 -0
  135. package/src/App.test.ts +15 -4
  136. package/src/App.ts +31 -5
  137. package/src/Assets.ts +3 -3
  138. package/src/Client.test-d.ts +30 -3
  139. package/src/Scope.ts +4 -2
  140. package/src/Webhooks.ts +5 -10
  141. package/src/admin/apps/api-keys.test.ts +27 -0
  142. package/src/admin/apps/api-keys.ts +8 -1
  143. package/src/admin/apps/scopes.test.ts +7 -2
  144. package/src/admin/apps/scopes.ts +2 -2
  145. package/src/admin/ui/src/components/Dialog.tsx +8 -2
  146. package/src/admin/ui/src/lib/scopes.test.ts +19 -0
  147. package/src/admin/ui/src/lib/scopes.ts +12 -0
  148. package/src/admin/ui/src/routes/api-keys.tsx +128 -27
  149. package/src/admin/ui.generated.ts +33 -33
  150. package/src/apps/composition.test.ts +77 -0
  151. package/src/apps/data/App.ts +25 -45
  152. package/src/apps/data/FundingQuoteProviders.test.ts +615 -5
  153. package/src/apps/data/FundingQuoteProviders.ts +570 -4
  154. package/src/apps/data/routes/funding.test.ts +181 -7
  155. package/src/apps/data/routes/funding.ts +27 -12
  156. package/src/apps/data/routes/indexer.test.ts +82 -20
  157. package/src/apps/data/routes/indexer.ts +8 -2
  158. package/src/apps/data/routes/tokens.test.ts +54 -0
  159. package/src/apps/data/routes/transfers.test.ts +73 -1
  160. package/src/apps/data/routes/transfers.ts +24 -30
  161. package/src/apps/data/routes/webhooks.test.ts +203 -0
  162. package/src/apps/data/routes/webhooks.ts +191 -54
  163. package/src/apps/management/Billing.test.ts +52 -3
  164. package/src/apps/management/Billing.ts +5 -4
  165. package/src/apps/management/routes/billing.ts +6 -1
  166. package/src/apps/management/routes/orgs.ts +19 -1
  167. package/src/apps/mcp.test.ts +18 -0
  168. package/src/apps/mcp.ts +2 -2
  169. package/src/apps/relay/App.test.ts +6 -5
  170. package/src/apps/relay/App.ts +2 -2
  171. package/src/apps/relay/Sponsorships.test.ts +2 -2
  172. package/src/apps/relay/Sponsorships.ts +24 -1
  173. package/src/db/Db.test.ts +5 -5
  174. package/src/db/Db.ts +4 -0
  175. package/src/db/Schema.ts +1 -1
  176. package/src/db/tables/billingSettings.test.ts +2 -2
  177. package/src/db/tables/organizations.test.ts +32 -2
  178. package/src/db/tables/organizations.ts +28 -0
  179. package/src/db/tables/requestUsage.ts +3 -23
  180. package/src/db/tables/sponsoredTransactions.test.ts +60 -2
  181. package/src/db/tables/sponsoredTransactions.ts +67 -21
  182. package/src/db/tables/stripeCustomers.test.ts +2 -2
  183. package/src/internal/Auth.test.ts +63 -10
  184. package/src/internal/Auth.ts +21 -9
  185. package/src/internal/Cache.test.ts +19 -0
  186. package/src/internal/Cache.ts +13 -1
  187. package/src/internal/EdgeCache.test.ts +40 -0
  188. package/src/internal/EdgeCache.ts +11 -3
  189. package/src/internal/FundingQuotes.test.ts +130 -3
  190. package/src/internal/FundingQuotes.ts +113 -6
  191. package/src/internal/Log.ts +21 -14
  192. package/src/internal/RateLimit.test.ts +12 -0
  193. package/src/internal/RateLimit.ts +4 -4
  194. package/src/internal/Tidx.test.ts +48 -0
  195. package/src/internal/Tidx.ts +21 -7
  196. package/src/internal/Viem.test.ts +46 -0
  197. package/src/internal/Viem.ts +34 -1
@@ -1,23 +1,211 @@
1
+ import * as z from 'zod/mini'
2
+
1
3
  import * as FundingQuotes from '../../internal/FundingQuotes.js'
2
4
 
3
5
  type Fetch = typeof globalThis.fetch
4
6
 
5
7
  const defaultFetch: Fetch = (input, init) => globalThis.fetch(input, init)
6
8
 
9
+ const defaultAcrossBaseUrl = 'https://app.across.to/api'
7
10
  const defaultRelayBaseUrl = 'https://api.relay.link'
11
+ const defaultRhinoBaseUrl = 'https://api.rhino.fi/bridge'
12
+ const defaultSquidBaseUrl = 'https://v2.api.squidrouter.com/v2'
13
+ const providerOperations = {
14
+ across: 'swap/approval',
15
+ relay: 'quote/v2',
16
+ rhino: 'quote/bridge-swap/public',
17
+ squid: 'v2/route',
18
+ } as const
19
+
20
+ namespace schema {
21
+ const IntegerString = z.string().check(z.regex(/^\d+$/))
22
+
23
+ const AcrossToken = z.object({
24
+ address: z.string(),
25
+ chainId: z.number().check(z.int(), z.nonnegative()),
26
+ decimals: z.number().check(z.int(), z.nonnegative()),
27
+ })
28
+
29
+ export const AcrossQuote = z.object({
30
+ amountType: z.literal('exactInput'),
31
+ expectedFillTime: z.number().check(z.int(), z.nonnegative()),
32
+ expectedOutputAmount: IntegerString,
33
+ inputAmount: IntegerString,
34
+ inputToken: AcrossToken,
35
+ minOutputAmount: IntegerString,
36
+ outputToken: AcrossToken,
37
+ quoteExpiryTimestamp: z.number().check(z.int(), z.nonnegative()),
38
+ })
39
+
40
+ export const RhinoQuote = z.object({
41
+ _tag: z.literal('bridge'),
42
+ chainIn: z.string(),
43
+ chainOut: z.string(),
44
+ estimatedDuration: z.optional(z.nullable(z.number().check(z.nonnegative()))),
45
+ payAmount: z.string().check(z.regex(/^\d+(\.\d+)?$/)),
46
+ receiveAmount: z.string().check(z.regex(/^\d+(\.\d+)?$/)),
47
+ token: z.string(),
48
+ })
49
+
50
+ const SquidToken = z.object({
51
+ address: z.string(),
52
+ chainId: z.union([z.string(), z.number().check(z.int(), z.nonnegative())]),
53
+ decimals: z.number().check(z.int(), z.nonnegative()),
54
+ })
55
+
56
+ export const SquidQuote = z.object({
57
+ route: z.object({
58
+ estimate: z.object({
59
+ estimatedRouteDuration: z.number().check(z.nonnegative()),
60
+ fromAmount: IntegerString,
61
+ fromToken: SquidToken,
62
+ toAmount: IntegerString,
63
+ toAmountMin: IntegerString,
64
+ toToken: SquidToken,
65
+ }),
66
+ }),
67
+ })
68
+ }
8
69
 
9
70
  /** Creates the live funding quote adapters configured for this deployment. */
10
71
  export function createAdapters(options: createAdapters.Options = {}) {
11
- return (
12
- options.relay ? { relay: relay(options.relay) } : {}
13
- ) satisfies FundingQuotes.ProviderAdapters
72
+ return {
73
+ ...(options.across ? { across: across(options.across) } : {}),
74
+ ...(options.relay ? { relay: relay(options.relay) } : {}),
75
+ ...(options.rhino ? { rhino: rhino(options.rhino) } : {}),
76
+ ...(options.squid ? { squid: squid(options.squid) } : {}),
77
+ } satisfies FundingQuotes.ProviderAdapters
14
78
  }
15
79
 
16
80
  export declare namespace createAdapters {
17
81
  /** Provider adapter factory options. Omit a provider to leave it unconfigured. */
18
82
  type Options = {
83
+ /** Across quote adapter options. */
84
+ across?: across.Options | false | undefined
19
85
  /** Relay quote adapter options. */
20
86
  relay?: relay.Options | false | undefined
87
+ /** Rhino quote adapter options. */
88
+ rhino?: rhino.Options | false | undefined
89
+ /** Squid quote adapter options. */
90
+ squid?: squid.Options | false | undefined
91
+ }
92
+ }
93
+
94
+ /** Resolves all-or-none Across deployment configuration. */
95
+ export function configureAcross(options: configureAcross.Options = {}): across.Options | undefined {
96
+ const configured = [options.apiKey, options.integratorId, options.userAddress].filter(Boolean)
97
+ if (configured.length === 0) return undefined
98
+ if (!options.apiKey || !options.integratorId || !options.userAddress)
99
+ throw new ProviderConfigurationError()
100
+ return {
101
+ apiKey: options.apiKey,
102
+ integratorId: options.integratorId,
103
+ userAddress: options.userAddress,
104
+ }
105
+ }
106
+
107
+ export declare namespace configureAcross {
108
+ /** Optional deployment bindings required together to enable Across. */
109
+ type Options = {
110
+ /** Across API key binding. */
111
+ apiKey?: string | undefined
112
+ /** Across integrator id binding. */
113
+ integratorId?: string | undefined
114
+ /** Across quote-user binding. */
115
+ userAddress?: string | undefined
116
+ }
117
+ }
118
+
119
+ /** Resolves all-or-none Squid deployment configuration. */
120
+ export function configureSquid(options: configureSquid.Options = {}): squid.Options | undefined {
121
+ const configured = [options.integratorId, options.userAddress].filter(Boolean)
122
+ if (configured.length === 0) return undefined
123
+ if (!options.integratorId || !options.userAddress) throw new ProviderConfigurationError()
124
+ return {
125
+ integratorId: options.integratorId,
126
+ userAddress: options.userAddress,
127
+ }
128
+ }
129
+
130
+ export declare namespace configureSquid {
131
+ /** Optional deployment bindings required together to enable Squid. */
132
+ type Options = {
133
+ /** Squid integrator id binding. */
134
+ integratorId?: string | undefined
135
+ /** Squid quote-user binding. */
136
+ userAddress?: string | undefined
137
+ }
138
+ }
139
+
140
+ /** Creates an Across quote adapter backed by `/swap/approval`. */
141
+ export function across(options: across.Options): FundingQuotes.ProviderAdapter {
142
+ assertAcrossConfiguration(options)
143
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultAcrossBaseUrl)
144
+ const fetcher = options.fetch ?? defaultFetch
145
+ const now = options.now ?? (() => new Date())
146
+
147
+ return {
148
+ async probeQuote({ candidate }, signal) {
149
+ const destinationChainId = eip155ChainId(candidate.destinationChain)
150
+ const originChainId = providerChainId(candidate.sourceChain, 'across')
151
+ const url = new URL(`${baseUrl}/swap/approval`)
152
+ url.search = new URLSearchParams({
153
+ amount: candidate.sourceAmount.amount,
154
+ depositor: options.userAddress,
155
+ destinationChainId,
156
+ inputToken: candidate.sourceToken.address,
157
+ integratorId: options.integratorId,
158
+ originChainId,
159
+ outputToken: candidate.destinationToken.address,
160
+ recipient: options.userAddress,
161
+ skipOriginTxEstimation: 'true',
162
+ strictTradeType: 'true',
163
+ tradeType: 'exactInput',
164
+ }).toString()
165
+
166
+ try {
167
+ const quote = await requestJson(url, {
168
+ fetcher,
169
+ headers: { Authorization: `Bearer ${options.apiKey}` },
170
+ signal,
171
+ })
172
+ return formatAcrossResult(candidate, quote, now())
173
+ } catch (cause) {
174
+ const unavailable = providerUnavailable(cause, [
175
+ 'AMOUNT_TOO_HIGH',
176
+ 'AMOUNT_TOO_LOW',
177
+ 'INSUFFICIENT_LIQUIDITY',
178
+ 'NO_QUOTES',
179
+ 'NO_ROUTE',
180
+ 'UNSUPPORTED_ROUTE',
181
+ ])
182
+ if (!unavailable) throw cause
183
+ return unavailableResult({
184
+ detail: 'across:swap-approval',
185
+ message: unavailable,
186
+ now: now(),
187
+ source: 'providerQuote',
188
+ })
189
+ }
190
+ },
191
+ }
192
+ }
193
+
194
+ export declare namespace across {
195
+ /** Across quote adapter options. */
196
+ type Options = {
197
+ /** Across API key sent as a bearer token. */
198
+ apiKey: string
199
+ /** Override for tests or Across test environments. */
200
+ baseUrl?: string | undefined
201
+ /** Fetch implementation override for tests. */
202
+ fetch?: Fetch | undefined
203
+ /** Across integrator id sent as the required query parameter. */
204
+ integratorId: string
205
+ /** Clock override for deterministic tests. */
206
+ now?: (() => Date) | undefined
207
+ /** Probe wallet address used as the Across depositor and recipient. */
208
+ userAddress: string
21
209
  }
22
210
  }
23
211
 
@@ -87,6 +275,212 @@ export declare namespace relay {
87
275
  }
88
276
  }
89
277
 
278
+ /** Creates a Rhino quote adapter backed by the public bridge-and-swap quote endpoint. */
279
+ export function rhino(options: rhino.Options = {}): FundingQuotes.ProviderAdapter {
280
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultRhinoBaseUrl)
281
+ const fetcher = options.fetch ?? defaultFetch
282
+ const now = options.now ?? (() => new Date())
283
+
284
+ return {
285
+ async probeQuote({ cache, candidate }, signal) {
286
+ const chainIn = providerChainReference(candidate.sourceChain, 'rhino')
287
+ const tokenOut = rhinoToken(candidate.destinationToken)
288
+ const url = new URL(`${baseUrl}/quote/bridge-swap/public`)
289
+ url.search = new URLSearchParams({
290
+ amount: candidate.sourceAmount.formatted,
291
+ amountNative: '0',
292
+ chainIn,
293
+ chainOut: 'TEMPO',
294
+ mode: 'pay',
295
+ tokenIn: candidate.sourceToken.symbol,
296
+ tokenOut,
297
+ }).toString()
298
+
299
+ try {
300
+ const quote = await requestJson(url, {
301
+ fetcher,
302
+ headers: cache.bypass ? { 'cache-control': 'no-cache' } : undefined,
303
+ signal,
304
+ })
305
+ return formatRhinoResult(candidate, quote, { chainIn, now: now(), tokenOut })
306
+ } catch (cause) {
307
+ const unavailable = providerUnavailable(cause, ['NoRouteFoundError'])
308
+ if (!unavailable) throw cause
309
+ return unavailableResult({
310
+ detail: 'rhino:bridge-swap-public',
311
+ message: unavailable,
312
+ now: now(),
313
+ source: 'providerQuote',
314
+ })
315
+ }
316
+ },
317
+ }
318
+ }
319
+
320
+ export declare namespace rhino {
321
+ /** Rhino public quote adapter options. */
322
+ type Options = {
323
+ /** Override for tests or Rhino test environments. */
324
+ baseUrl?: string | undefined
325
+ /** Fetch implementation override for tests. */
326
+ fetch?: Fetch | undefined
327
+ /** Clock override for deterministic tests. */
328
+ now?: (() => Date) | undefined
329
+ }
330
+ }
331
+
332
+ /** Creates a Squid quote adapter backed by `/v2/route`. */
333
+ export function squid(options: squid.Options): FundingQuotes.ProviderAdapter {
334
+ assertSquidConfiguration(options)
335
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultSquidBaseUrl)
336
+ const fetcher = options.fetch ?? defaultFetch
337
+ const now = options.now ?? (() => new Date())
338
+
339
+ return {
340
+ async probeQuote({ candidate }, signal) {
341
+ const fromChain = eip155ChainId(candidate.sourceChain)
342
+ const toChain = eip155ChainId(candidate.destinationChain)
343
+
344
+ try {
345
+ const quote = await requestJson(new URL(`${baseUrl}/route`), {
346
+ body: {
347
+ fromAddress: options.userAddress,
348
+ fromAmount: candidate.sourceAmount.amount,
349
+ fromChain,
350
+ fromToken: candidate.sourceToken.address,
351
+ quoteOnly: true,
352
+ // V1 quotes use a fixed 1% tolerance; callers cannot customize execution policy.
353
+ slippage: 1,
354
+ toAddress: options.userAddress,
355
+ toChain,
356
+ toToken: candidate.destinationToken.address,
357
+ },
358
+ fetcher,
359
+ headers: { 'x-integrator-id': options.integratorId },
360
+ method: 'POST',
361
+ signal,
362
+ })
363
+ return formatSquidResult(candidate, quote, now())
364
+ } catch (cause) {
365
+ const unavailable =
366
+ providerUnavailable(cause, [
367
+ 'NO_ROUTE',
368
+ 'NO_ROUTE_FOUND',
369
+ 'ROUTE_NOT_FOUND',
370
+ 'UNSUPPORTED_ROUTE',
371
+ ]) ?? squidUnavailable(cause)
372
+ if (!unavailable) throw cause
373
+ return unavailableResult({
374
+ detail: 'squid:route-v2',
375
+ message: unavailable,
376
+ now: now(),
377
+ source: 'providerQuote',
378
+ })
379
+ }
380
+ },
381
+ }
382
+ }
383
+
384
+ export declare namespace squid {
385
+ /** Squid quote adapter options. */
386
+ type Options = {
387
+ /** Override for tests or Squid test environments. */
388
+ baseUrl?: string | undefined
389
+ /** Fetch implementation override for tests. */
390
+ fetch?: Fetch | undefined
391
+ /** Squid integrator id sent in the required request header. */
392
+ integratorId: string
393
+ /** Clock override for deterministic tests. */
394
+ now?: (() => Date) | undefined
395
+ /** Probe wallet address used as the Squid sender and recipient. */
396
+ userAddress: string
397
+ }
398
+ }
399
+
400
+ /** Returns bounded provider failure details suitable for structured request logs. */
401
+ export function providerFailure(options: providerFailure.Options): providerFailure.Result {
402
+ const operation = providerOperations[options.provider]
403
+ if (
404
+ options.cause instanceof FundingQuotes.ProviderAdapterTimeoutError ||
405
+ (options.cause instanceof DOMException && options.cause.name === 'AbortError')
406
+ )
407
+ return { failure: 'timeout', id: options.provider, operation }
408
+ if (options.cause instanceof ProviderResponseError) {
409
+ const code = providerErrorCode(options.cause.body)
410
+ return {
411
+ ...(code === undefined ? {} : { code }),
412
+ failure: 'http',
413
+ id: options.provider,
414
+ operation,
415
+ status: options.cause.status,
416
+ }
417
+ }
418
+ if (options.cause instanceof ProviderPayloadError)
419
+ return { failure: 'payload', id: options.provider, operation }
420
+ if (options.cause instanceof TypeError)
421
+ return { failure: 'network', id: options.provider, operation }
422
+ return { failure: 'unknown', id: options.provider, operation }
423
+ }
424
+
425
+ export declare namespace providerFailure {
426
+ /** Inputs used to classify a provider failure. */
427
+ type Options = FundingQuotes.ProviderFailure
428
+
429
+ /** Bounded provider failure fields. */
430
+ type Result = {
431
+ /** Stable provider error code, when one is safe to retain. */
432
+ code?: string | undefined
433
+ /** Failure boundary that rejected the provider request. */
434
+ failure: 'http' | 'network' | 'payload' | 'timeout' | 'unknown'
435
+ /** Provider identifier. */
436
+ id: FundingQuotes.ProviderId
437
+ /** Provider operation identifier. */
438
+ operation: (typeof providerOperations)[FundingQuotes.ProviderId]
439
+ /** Upstream HTTP status, when a response was received. */
440
+ status?: number | undefined
441
+ }
442
+ }
443
+
444
+ function formatAcrossResult(
445
+ candidate: FundingQuotes.QuoteCandidate,
446
+ quote: unknown,
447
+ now: Date,
448
+ ): FundingQuotes.QuoteProbeResult {
449
+ const parsed = schema.AcrossQuote.safeParse(quote)
450
+ if (!parsed.success) throw new ProviderPayloadError()
451
+ const response = parsed.data
452
+ const expiresAt = new Date(response.quoteExpiryTimestamp * 1_000)
453
+ if (
454
+ response.inputAmount !== candidate.sourceAmount.amount ||
455
+ !matchesToken(response.inputToken, candidate.sourceChain, candidate.sourceToken) ||
456
+ !matchesToken(response.outputToken, candidate.destinationChain, candidate.destinationToken) ||
457
+ BigInt(response.expectedOutputAmount) <= 0n ||
458
+ BigInt(response.minOutputAmount) <= 0n ||
459
+ BigInt(response.minOutputAmount) > BigInt(response.expectedOutputAmount) ||
460
+ Number.isNaN(expiresAt.getTime()) ||
461
+ response.quoteExpiryTimestamp * 1_000 <= now.getTime()
462
+ )
463
+ throw new ProviderPayloadError()
464
+
465
+ return {
466
+ expiresAt: expiresAt.toISOString(),
467
+ minimumOutputAmount: response.minOutputAmount,
468
+ outputAmount: response.expectedOutputAmount,
469
+ outputAmountFormatted: decimalFromBaseUnits(
470
+ response.expectedOutputAmount,
471
+ candidate.destinationToken.decimals,
472
+ ),
473
+ quality: {
474
+ estimatedSeconds: response.expectedFillTime,
475
+ liquiditySource: 'providerQuote',
476
+ sourceDetail: 'across:swap-approval',
477
+ tier: 'liquid',
478
+ },
479
+ sampledAt: now.toISOString(),
480
+ status: 'available',
481
+ }
482
+ }
483
+
90
484
  function formatRelayResult(
91
485
  candidate: FundingQuotes.QuoteCandidate,
92
486
  quote: unknown,
@@ -116,6 +510,81 @@ function formatRelayResult(
116
510
  }
117
511
  }
118
512
 
513
+ function formatRhinoResult(
514
+ candidate: FundingQuotes.QuoteCandidate,
515
+ quote: unknown,
516
+ options: { chainIn: string; now: Date; tokenOut: string },
517
+ ): FundingQuotes.QuoteProbeResult {
518
+ const parsed = schema.RhinoQuote.safeParse(quote)
519
+ if (!parsed.success) throw new ProviderPayloadError()
520
+ const response = parsed.data
521
+ const inputAmount = baseUnitsFromDecimal(response.payAmount, candidate.sourceToken.decimals)
522
+ const outputAmount = baseUnitsFromDecimal(
523
+ response.receiveAmount,
524
+ candidate.destinationToken.decimals,
525
+ )
526
+ if (
527
+ response.chainIn !== options.chainIn ||
528
+ response.chainOut !== 'TEMPO' ||
529
+ response.token !== options.tokenOut ||
530
+ inputAmount !== candidate.sourceAmount.amount ||
531
+ outputAmount === undefined ||
532
+ BigInt(outputAmount) <= 0n
533
+ )
534
+ throw new ProviderPayloadError()
535
+
536
+ return {
537
+ outputAmount,
538
+ outputAmountFormatted: decimalFromBaseUnits(outputAmount, candidate.destinationToken.decimals),
539
+ quality: {
540
+ ...(response.estimatedDuration === undefined || response.estimatedDuration === null
541
+ ? {}
542
+ : { estimatedSeconds: Math.ceil(response.estimatedDuration / 1_000) }),
543
+ liquiditySource: 'providerQuote',
544
+ sourceDetail: 'rhino:bridge-swap-public',
545
+ tier: 'liquid',
546
+ },
547
+ sampledAt: options.now.toISOString(),
548
+ status: 'available',
549
+ }
550
+ }
551
+
552
+ function formatSquidResult(
553
+ candidate: FundingQuotes.QuoteCandidate,
554
+ quote: unknown,
555
+ now: Date,
556
+ ): FundingQuotes.QuoteProbeResult {
557
+ const parsed = schema.SquidQuote.safeParse(quote)
558
+ if (!parsed.success) throw new ProviderPayloadError()
559
+ const response = parsed.data.route.estimate
560
+ if (
561
+ response.fromAmount !== candidate.sourceAmount.amount ||
562
+ !matchesSquidToken(response.fromToken, candidate.sourceChain, candidate.sourceToken) ||
563
+ !matchesSquidToken(response.toToken, candidate.destinationChain, candidate.destinationToken) ||
564
+ BigInt(response.toAmount) <= 0n ||
565
+ BigInt(response.toAmountMin) <= 0n ||
566
+ BigInt(response.toAmountMin) > BigInt(response.toAmount)
567
+ )
568
+ throw new ProviderPayloadError()
569
+
570
+ return {
571
+ minimumOutputAmount: response.toAmountMin,
572
+ outputAmount: response.toAmount,
573
+ outputAmountFormatted: decimalFromBaseUnits(
574
+ response.toAmount,
575
+ candidate.destinationToken.decimals,
576
+ ),
577
+ quality: {
578
+ estimatedSeconds: Math.ceil(response.estimatedRouteDuration),
579
+ liquiditySource: 'providerQuote',
580
+ sourceDetail: 'squid:route-v2',
581
+ tier: 'liquid',
582
+ },
583
+ sampledAt: now.toISOString(),
584
+ status: 'available',
585
+ }
586
+ }
587
+
119
588
  function unavailableResult(options: {
120
589
  detail: string
121
590
  message: string
@@ -172,10 +641,29 @@ function providerUnavailable(cause: unknown, codes: readonly string[]) {
172
641
 
173
642
  function providerErrorCode(input: unknown) {
174
643
  const body = record(input)
175
- const code = stringValue(body['code']) ?? stringValue(body['errorCode'])
644
+ const error = record(body['error'])
645
+ const code =
646
+ stringValue(body['_tag']) ??
647
+ stringValue(body['code']) ??
648
+ stringValue(body['errorCode']) ??
649
+ stringValue(body['errorType']) ??
650
+ stringValue(body['type']) ??
651
+ stringValue(error['code']) ??
652
+ stringValue(error['type'])
176
653
  return code && /^[A-Za-z0-9_.:-]{1,64}$/.test(code) ? code : undefined
177
654
  }
178
655
 
656
+ function squidUnavailable(cause: unknown) {
657
+ if (!(cause instanceof ProviderResponseError) || cause.status !== 400) return undefined
658
+ const body = record(cause.body)
659
+ if (
660
+ body['type'] === 'BAD_REQUEST' &&
661
+ body['message'] === 'Please increase the swap amount and try again.'
662
+ )
663
+ return 'AMOUNT_TOO_LOW'
664
+ return undefined
665
+ }
666
+
179
667
  function providerChainId(
180
668
  quoteChain: FundingQuotes.QuoteCandidate['sourceChain'],
181
669
  provider: string,
@@ -186,6 +674,50 @@ function providerChainId(
186
674
  return eip155ChainId(quoteChain)
187
675
  }
188
676
 
677
+ function providerChainReference(
678
+ quoteChain: FundingQuotes.QuoteCandidate['sourceChain'],
679
+ provider: string,
680
+ ) {
681
+ const providerIds = quoteChain.providerIds as Record<string, string> | undefined
682
+ const id = providerIds?.[provider]
683
+ if (!id) throw new UnsupportedProviderChainError()
684
+ return id
685
+ }
686
+
687
+ function rhinoToken(token: FundingQuotes.TokenRef) {
688
+ const tokens: Record<string, string> = {
689
+ '0x20c00000000000000000000014f22ca97301eb73': 'USDT',
690
+ '0x20c000000000000000000000b9537d11c60e8b50': 'USDC',
691
+ }
692
+ const id = tokens[token.address.toLowerCase()]
693
+ if (!id) throw new UnsupportedProviderTokenError()
694
+ return id
695
+ }
696
+
697
+ function matchesToken(
698
+ token: z.output<typeof schema.AcrossQuote>['inputToken'],
699
+ chain: FundingQuotes.ChainRef,
700
+ expected: FundingQuotes.TokenRef,
701
+ ) {
702
+ return (
703
+ token.address.toLowerCase() === expected.address.toLowerCase() &&
704
+ token.chainId === Number(eip155ChainId(chain)) &&
705
+ token.decimals === expected.decimals
706
+ )
707
+ }
708
+
709
+ function matchesSquidToken(
710
+ token: z.output<typeof schema.SquidQuote>['route']['estimate']['fromToken'],
711
+ chain: FundingQuotes.ChainRef,
712
+ expected: FundingQuotes.TokenRef,
713
+ ) {
714
+ return (
715
+ token.address.toLowerCase() === expected.address.toLowerCase() &&
716
+ String(token.chainId) === eip155ChainId(chain) &&
717
+ token.decimals === expected.decimals
718
+ )
719
+ }
720
+
189
721
  function eip155ChainId(chain: { id: string; kind: string }) {
190
722
  if (chain.kind !== 'evm') throw new UnsupportedProviderChainError()
191
723
  const match = /^eip155:(\d+)$/.exec(chain.id)
@@ -235,6 +767,36 @@ function decimalFromBaseUnits(baseUnits: string, decimals: number) {
235
767
  return fraction ? `${whole}.${fraction}` : whole
236
768
  }
237
769
 
770
+ function baseUnitsFromDecimal(value: string, decimals: number) {
771
+ if (!/^\d+(\.\d+)?$/.test(value)) return undefined
772
+ const [whole, fraction = ''] = value.split('.')
773
+ const significantFraction = fraction.replace(/0+$/, '')
774
+ if (significantFraction.length > decimals) return undefined
775
+ return BigInt(`${whole}${significantFraction.padEnd(decimals, '0')}`).toString()
776
+ }
777
+
778
+ function assertAcrossConfiguration(options: across.Options) {
779
+ if (
780
+ options.apiKey.length === 0 ||
781
+ !/^0x[\da-fA-F]{4}$/.test(options.integratorId) ||
782
+ !/^0x[\da-fA-F]{40}$/.test(options.userAddress)
783
+ )
784
+ throw new ProviderConfigurationError()
785
+ }
786
+
787
+ function assertSquidConfiguration(options: squid.Options) {
788
+ if (options.integratorId.length === 0 || !/^0x[\da-fA-F]{40}$/.test(options.userAddress))
789
+ throw new ProviderConfigurationError()
790
+ }
791
+
792
+ class ProviderConfigurationError extends Error {
793
+ override name = 'FundingQuoteProviders.ProviderConfigurationError'
794
+ }
795
+
796
+ class ProviderPayloadError extends Error {
797
+ override name = 'FundingQuoteProviders.ProviderPayloadError'
798
+ }
799
+
238
800
  class ProviderResponseError extends Error {
239
801
  override name = 'FundingQuoteProviders.ProviderResponseError'
240
802
 
@@ -249,3 +811,7 @@ class ProviderResponseError extends Error {
249
811
  class UnsupportedProviderChainError extends Error {
250
812
  override name = 'FundingQuoteProviders.UnsupportedProviderChainError'
251
813
  }
814
+
815
+ class UnsupportedProviderTokenError extends Error {
816
+ override name = 'FundingQuoteProviders.UnsupportedProviderTokenError'
817
+ }