tapimo 0.4.4 → 0.5.1

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 (229) hide show
  1. package/dist/App.d.ts +4701 -3416
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +24 -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 +18 -1
  8. package/dist/Scope.d.ts.map +1 -1
  9. package/dist/Scope.js +23 -3
  10. package/dist/Scope.js.map +1 -1
  11. package/dist/Webhooks.d.ts +1 -1
  12. package/dist/Webhooks.d.ts.map +1 -1
  13. package/dist/Webhooks.js +5 -10
  14. package/dist/Webhooks.js.map +1 -1
  15. package/dist/admin/App.d.ts +6 -34
  16. package/dist/admin/App.d.ts.map +1 -1
  17. package/dist/admin/apps/api-keys.d.ts +3 -3
  18. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  19. package/dist/admin/apps/api-keys.js +4 -1
  20. package/dist/admin/apps/api-keys.js.map +1 -1
  21. package/dist/admin/apps/scopes.d.ts +6 -34
  22. package/dist/admin/apps/scopes.d.ts.map +1 -1
  23. package/dist/admin/apps/scopes.js +2 -2
  24. package/dist/admin/apps/scopes.js.map +1 -1
  25. package/dist/admin/apps/verified-tokens.d.ts +57 -57
  26. package/dist/admin/ui.generated.d.ts +1 -1
  27. package/dist/admin/ui.generated.js +33 -33
  28. package/dist/admin/ui.generated.js.map +1 -1
  29. package/dist/apps/data/App.d.ts +4574 -4277
  30. package/dist/apps/data/App.d.ts.map +1 -1
  31. package/dist/apps/data/App.js +155 -77
  32. package/dist/apps/data/App.js.map +1 -1
  33. package/dist/apps/data/FundingQuoteProviders.d.ts +109 -0
  34. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -0
  35. package/dist/apps/data/FundingQuoteProviders.js +464 -0
  36. package/dist/apps/data/FundingQuoteProviders.js.map +1 -0
  37. package/dist/apps/data/index.d.ts +1 -1
  38. package/dist/apps/data/index.js +1 -1
  39. package/dist/apps/data/routes/activities.d.ts +26 -26
  40. package/dist/apps/data/routes/addresses.d.ts +38 -38
  41. package/dist/apps/data/routes/balances.d.ts +45 -45
  42. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  43. package/dist/apps/data/routes/balances.js +67 -149
  44. package/dist/apps/data/routes/balances.js.map +1 -1
  45. package/dist/apps/data/routes/blocks.d.ts +2 -3
  46. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  47. package/dist/apps/data/routes/blocks.js +3 -5
  48. package/dist/apps/data/routes/blocks.js.map +1 -1
  49. package/dist/apps/data/routes/coingecko.d.ts +177 -176
  50. package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
  51. package/dist/apps/data/routes/coingecko.js +3 -8
  52. package/dist/apps/data/routes/coingecko.js.map +1 -1
  53. package/dist/apps/data/routes/funding.d.ts +131 -228
  54. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  55. package/dist/apps/data/routes/funding.js +152 -219
  56. package/dist/apps/data/routes/funding.js.map +1 -1
  57. package/dist/apps/data/routes/indexer.d.ts +34 -34
  58. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  59. package/dist/apps/data/routes/indexer.js +9 -2
  60. package/dist/apps/data/routes/indexer.js.map +1 -1
  61. package/dist/apps/data/routes/receipts.d.ts +228 -228
  62. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  63. package/dist/apps/data/routes/rpc.js +86 -16
  64. package/dist/apps/data/routes/rpc.js.map +1 -1
  65. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  66. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  67. package/dist/apps/data/routes/webhooks.d.ts +340 -0
  68. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  69. package/dist/apps/data/routes/webhooks.js +13 -1
  70. package/dist/apps/data/routes/webhooks.js.map +1 -1
  71. package/dist/apps/index.d.ts +1 -1
  72. package/dist/apps/index.js +1 -1
  73. package/dist/apps/management/App.d.ts +1833 -851
  74. package/dist/apps/management/App.d.ts.map +1 -1
  75. package/dist/apps/management/App.js +4 -0
  76. package/dist/apps/management/App.js.map +1 -1
  77. package/dist/apps/management/routes/api-keys.d.ts +41 -41
  78. package/dist/apps/management/routes/api-keys.js +2 -2
  79. package/dist/apps/management/routes/api-keys.js.map +1 -1
  80. package/dist/apps/management/routes/billing.d.ts +48 -48
  81. package/dist/apps/management/routes/invitations.d.ts +132 -132
  82. package/dist/apps/management/routes/invite-links.d.ts +1100 -0
  83. package/dist/apps/management/routes/invite-links.d.ts.map +1 -0
  84. package/dist/apps/management/routes/invite-links.js +411 -0
  85. package/dist/apps/management/routes/invite-links.js.map +1 -0
  86. package/dist/apps/management/routes/members.d.ts +41 -41
  87. package/dist/apps/management/routes/orgs.d.ts +92 -92
  88. package/dist/apps/management/routes/projects.d.ts +9 -9
  89. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  90. package/dist/apps/management/routes/scopes.js +2 -2
  91. package/dist/apps/management/routes/scopes.js.map +1 -1
  92. package/dist/apps/management/routes/usage.d.ts +24 -24
  93. package/dist/apps/mcp.js +2 -2
  94. package/dist/apps/mcp.js.map +1 -1
  95. package/dist/apps/relay/App.d.ts +2 -2
  96. package/dist/apps/relay/App.d.ts.map +1 -1
  97. package/dist/apps/relay/App.js +44 -6
  98. package/dist/apps/relay/App.js.map +1 -1
  99. package/dist/db/Db.d.ts +3 -0
  100. package/dist/db/Db.d.ts.map +1 -1
  101. package/dist/db/Db.js +57 -0
  102. package/dist/db/Db.js.map +1 -1
  103. package/dist/db/Schema.d.ts +31 -0
  104. package/dist/db/Schema.d.ts.map +1 -1
  105. package/dist/db/Schema.js +31 -0
  106. package/dist/db/Schema.js.map +1 -1
  107. package/dist/db/tables/inviteLinks.d.ts +42 -0
  108. package/dist/db/tables/inviteLinks.d.ts.map +1 -0
  109. package/dist/db/tables/inviteLinks.js +148 -0
  110. package/dist/db/tables/inviteLinks.js.map +1 -0
  111. package/dist/db/tables/organizations.d.ts.map +1 -1
  112. package/dist/db/tables/organizations.js +2 -0
  113. package/dist/db/tables/organizations.js.map +1 -1
  114. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  115. package/dist/db/tables/sponsoredTransactions.js +6 -2
  116. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  117. package/dist/handlers/relay.d.ts.map +1 -1
  118. package/dist/handlers/relay.js +13 -3
  119. package/dist/handlers/relay.js.map +1 -1
  120. package/dist/internal/Auth.d.ts +9 -1
  121. package/dist/internal/Auth.d.ts.map +1 -1
  122. package/dist/internal/Auth.js +138 -10
  123. package/dist/internal/Auth.js.map +1 -1
  124. package/dist/internal/EdgeCache.d.ts +3 -1
  125. package/dist/internal/EdgeCache.d.ts.map +1 -1
  126. package/dist/internal/EdgeCache.js +17 -2
  127. package/dist/internal/EdgeCache.js.map +1 -1
  128. package/dist/internal/{FundingRoutes.d.ts → FundingQuotes.d.ts} +240 -369
  129. package/dist/internal/FundingQuotes.d.ts.map +1 -0
  130. package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +144 -183
  131. package/dist/internal/FundingQuotes.js.map +1 -0
  132. package/dist/internal/Log.d.ts +18 -12
  133. package/dist/internal/Log.d.ts.map +1 -1
  134. package/dist/internal/Log.js +2 -0
  135. package/dist/internal/Log.js.map +1 -1
  136. package/dist/internal/Tidx.d.ts +9 -3
  137. package/dist/internal/Tidx.d.ts.map +1 -1
  138. package/dist/internal/Tidx.js +13 -7
  139. package/dist/internal/Tidx.js.map +1 -1
  140. package/dist/internal/Viem.d.ts +17 -2
  141. package/dist/internal/Viem.d.ts.map +1 -1
  142. package/dist/internal/Viem.js +30 -3
  143. package/dist/internal/Viem.js.map +1 -1
  144. package/dist/internal/index.d.ts +1 -1
  145. package/dist/internal/index.js +1 -1
  146. package/package.json +2 -2
  147. package/src/ApiKeys.test.ts +25 -0
  148. package/src/App.test-d.ts +2 -0
  149. package/src/App.test.ts +13 -2
  150. package/src/App.ts +37 -5
  151. package/src/Assets.ts +3 -3
  152. package/src/Client.test-d.ts +21 -11
  153. package/src/Scope.test.ts +1 -0
  154. package/src/Scope.ts +32 -4
  155. package/src/Webhooks.ts +5 -10
  156. package/src/admin/apps/api-keys.test.ts +27 -0
  157. package/src/admin/apps/api-keys.ts +8 -1
  158. package/src/admin/apps/scopes.test.ts +7 -2
  159. package/src/admin/apps/scopes.ts +2 -2
  160. package/src/admin/ui/src/components/Dialog.tsx +8 -2
  161. package/src/admin/ui/src/lib/scopes.test.ts +19 -0
  162. package/src/admin/ui/src/lib/scopes.ts +12 -0
  163. package/src/admin/ui/src/routes/api-keys.tsx +128 -27
  164. package/src/admin/ui.generated.ts +33 -33
  165. package/src/apps/composition.test.ts +77 -0
  166. package/src/apps/data/App.test.ts +354 -0
  167. package/src/apps/data/App.ts +178 -91
  168. package/src/apps/data/FundingQuoteProviders.test.ts +577 -0
  169. package/src/apps/data/FundingQuoteProviders.ts +633 -0
  170. package/src/apps/data/index.ts +1 -1
  171. package/src/apps/data/routes/balances.test.ts +64 -18
  172. package/src/apps/data/routes/balances.ts +76 -177
  173. package/src/apps/data/routes/blocks.ts +3 -5
  174. package/src/apps/data/routes/coingecko.ts +3 -8
  175. package/src/apps/data/routes/funding.test.ts +304 -192
  176. package/src/apps/data/routes/funding.ts +166 -286
  177. package/src/apps/data/routes/indexer.test.ts +173 -15
  178. package/src/apps/data/routes/indexer.ts +8 -2
  179. package/src/apps/data/routes/rpc.test.ts +219 -0
  180. package/src/apps/data/routes/rpc.ts +92 -17
  181. package/src/apps/data/routes/webhooks.test.ts +21 -0
  182. package/src/apps/data/routes/webhooks.ts +13 -1
  183. package/src/apps/index.ts +1 -1
  184. package/src/apps/management/App.ts +4 -0
  185. package/src/apps/management/Billing.test.ts +2 -2
  186. package/src/apps/management/routes/api-keys.test.ts +47 -3
  187. package/src/apps/management/routes/api-keys.ts +2 -2
  188. package/src/apps/management/routes/invite-links.test.ts +385 -0
  189. package/src/apps/management/routes/invite-links.ts +570 -0
  190. package/src/apps/management/routes/scopes.test.ts +3 -2
  191. package/src/apps/management/routes/scopes.ts +4 -2
  192. package/src/apps/mcp.test.ts +18 -0
  193. package/src/apps/mcp.ts +2 -2
  194. package/src/apps/relay/App.test.ts +328 -7
  195. package/src/apps/relay/App.ts +42 -6
  196. package/src/apps/relay/Sponsorships.test.ts +2 -2
  197. package/src/db/Db.test.ts +5 -5
  198. package/src/db/Db.ts +65 -0
  199. package/src/db/Schema.ts +39 -0
  200. package/src/db/tables/billingSettings.test.ts +2 -2
  201. package/src/db/tables/inviteLinks.ts +187 -0
  202. package/src/db/tables/organizations.test.ts +2 -2
  203. package/src/db/tables/organizations.ts +2 -0
  204. package/src/db/tables/sponsoredTransactions.test.ts +19 -2
  205. package/src/db/tables/sponsoredTransactions.ts +21 -14
  206. package/src/db/tables/stripeCustomers.test.ts +2 -2
  207. package/src/handlers/relay.ts +16 -4
  208. package/src/internal/Auth.test.ts +132 -22
  209. package/src/internal/Auth.ts +181 -14
  210. package/src/internal/EdgeCache.test.ts +24 -3
  211. package/src/internal/EdgeCache.ts +18 -2
  212. package/src/internal/FundingQuotes.test.ts +268 -0
  213. package/src/internal/{FundingRoutes.ts → FundingQuotes.ts} +216 -277
  214. package/src/internal/Log.ts +21 -14
  215. package/src/internal/Tidx.test.ts +48 -0
  216. package/src/internal/Tidx.ts +21 -7
  217. package/src/internal/Viem.test.ts +46 -0
  218. package/src/internal/Viem.ts +48 -6
  219. package/src/internal/index.test.ts +1 -1
  220. package/src/internal/index.ts +1 -1
  221. package/dist/apps/data/FundingRouteProviders.d.ts +0 -36
  222. package/dist/apps/data/FundingRouteProviders.d.ts.map +0 -1
  223. package/dist/apps/data/FundingRouteProviders.js +0 -245
  224. package/dist/apps/data/FundingRouteProviders.js.map +0 -1
  225. package/dist/internal/FundingRoutes.d.ts.map +0 -1
  226. package/dist/internal/FundingRoutes.js.map +0 -1
  227. package/src/apps/data/FundingRouteProviders.test.ts +0 -225
  228. package/src/apps/data/FundingRouteProviders.ts +0 -312
  229. package/src/internal/FundingRoutes.test.ts +0 -213
@@ -0,0 +1,633 @@
1
+ import * as z from 'zod/mini'
2
+
3
+ import * as FundingQuotes from '../../internal/FundingQuotes.js'
4
+
5
+ type Fetch = typeof globalThis.fetch
6
+
7
+ const defaultFetch: Fetch = (input, init) => globalThis.fetch(input, init)
8
+
9
+ const defaultAcrossBaseUrl = 'https://app.across.to/api'
10
+ const defaultRelayBaseUrl = 'https://api.relay.link'
11
+ const defaultRhinoBaseUrl = 'https://api.rhino.fi/bridge'
12
+ const providerOperations = {
13
+ across: 'swap/approval',
14
+ relay: 'quote/v2',
15
+ rhino: 'quote/bridge-swap/public',
16
+ } as const
17
+
18
+ namespace schema {
19
+ const IntegerString = z.string().check(z.regex(/^\d+$/))
20
+
21
+ const AcrossToken = z.object({
22
+ address: z.string(),
23
+ chainId: z.number().check(z.int(), z.nonnegative()),
24
+ decimals: z.number().check(z.int(), z.nonnegative()),
25
+ })
26
+
27
+ export const AcrossQuote = z.object({
28
+ amountType: z.literal('exactInput'),
29
+ expectedFillTime: z.number().check(z.int(), z.nonnegative()),
30
+ expectedOutputAmount: IntegerString,
31
+ inputAmount: IntegerString,
32
+ inputToken: AcrossToken,
33
+ minOutputAmount: IntegerString,
34
+ outputToken: AcrossToken,
35
+ quoteExpiryTimestamp: z.number().check(z.int(), z.nonnegative()),
36
+ })
37
+
38
+ export const RhinoQuote = z.object({
39
+ _tag: z.literal('bridge'),
40
+ chainIn: z.string(),
41
+ chainOut: z.string(),
42
+ estimatedDuration: z.optional(z.nullable(z.number().check(z.nonnegative()))),
43
+ payAmount: z.string().check(z.regex(/^\d+(\.\d+)?$/)),
44
+ receiveAmount: z.string().check(z.regex(/^\d+(\.\d+)?$/)),
45
+ token: z.string(),
46
+ })
47
+ }
48
+
49
+ /** Creates the live funding quote adapters configured for this deployment. */
50
+ export function createAdapters(options: createAdapters.Options = {}) {
51
+ return {
52
+ ...(options.across ? { across: across(options.across) } : {}),
53
+ ...(options.relay ? { relay: relay(options.relay) } : {}),
54
+ ...(options.rhino ? { rhino: rhino(options.rhino) } : {}),
55
+ } satisfies FundingQuotes.ProviderAdapters
56
+ }
57
+
58
+ export declare namespace createAdapters {
59
+ /** Provider adapter factory options. Omit a provider to leave it unconfigured. */
60
+ type Options = {
61
+ /** Across quote adapter options. */
62
+ across?: across.Options | false | undefined
63
+ /** Relay quote adapter options. */
64
+ relay?: relay.Options | false | undefined
65
+ /** Rhino quote adapter options. */
66
+ rhino?: rhino.Options | false | undefined
67
+ }
68
+ }
69
+
70
+ /** Resolves all-or-none Across deployment configuration. */
71
+ export function configureAcross(options: configureAcross.Options = {}): across.Options | undefined {
72
+ const configured = [options.apiKey, options.integratorId, options.userAddress].filter(Boolean)
73
+ if (configured.length === 0) return undefined
74
+ if (!options.apiKey || !options.integratorId || !options.userAddress)
75
+ throw new ProviderConfigurationError()
76
+ return {
77
+ apiKey: options.apiKey,
78
+ integratorId: options.integratorId,
79
+ userAddress: options.userAddress,
80
+ }
81
+ }
82
+
83
+ export declare namespace configureAcross {
84
+ /** Optional deployment bindings required together to enable Across. */
85
+ type Options = {
86
+ /** Across API key binding. */
87
+ apiKey?: string | undefined
88
+ /** Across integrator id binding. */
89
+ integratorId?: string | undefined
90
+ /** Across quote-user binding. */
91
+ userAddress?: string | undefined
92
+ }
93
+ }
94
+
95
+ /** Creates an Across quote adapter backed by `/swap/approval`. */
96
+ export function across(options: across.Options): FundingQuotes.ProviderAdapter {
97
+ assertAcrossConfiguration(options)
98
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultAcrossBaseUrl)
99
+ const fetcher = options.fetch ?? defaultFetch
100
+ const now = options.now ?? (() => new Date())
101
+
102
+ return {
103
+ async probeQuote({ candidate }, signal) {
104
+ const destinationChainId = eip155ChainId(candidate.destinationChain)
105
+ const originChainId = providerChainId(candidate.sourceChain, 'across')
106
+ const url = new URL(`${baseUrl}/swap/approval`)
107
+ url.search = new URLSearchParams({
108
+ amount: candidate.sourceAmount.amount,
109
+ depositor: options.userAddress,
110
+ destinationChainId,
111
+ inputToken: candidate.sourceToken.address,
112
+ integratorId: options.integratorId,
113
+ originChainId,
114
+ outputToken: candidate.destinationToken.address,
115
+ recipient: options.userAddress,
116
+ skipOriginTxEstimation: 'true',
117
+ strictTradeType: 'true',
118
+ tradeType: 'exactInput',
119
+ }).toString()
120
+
121
+ try {
122
+ const quote = await requestJson(url, {
123
+ fetcher,
124
+ headers: { Authorization: `Bearer ${options.apiKey}` },
125
+ signal,
126
+ })
127
+ return formatAcrossResult(candidate, quote, now())
128
+ } catch (cause) {
129
+ const unavailable = providerUnavailable(cause, [
130
+ 'AMOUNT_TOO_HIGH',
131
+ 'AMOUNT_TOO_LOW',
132
+ 'INSUFFICIENT_LIQUIDITY',
133
+ 'NO_QUOTES',
134
+ 'NO_ROUTE',
135
+ 'UNSUPPORTED_ROUTE',
136
+ ])
137
+ if (!unavailable) throw cause
138
+ return unavailableResult({
139
+ detail: 'across:swap-approval',
140
+ message: unavailable,
141
+ now: now(),
142
+ source: 'providerQuote',
143
+ })
144
+ }
145
+ },
146
+ }
147
+ }
148
+
149
+ export declare namespace across {
150
+ /** Across quote adapter options. */
151
+ type Options = {
152
+ /** Across API key sent as a bearer token. */
153
+ apiKey: string
154
+ /** Override for tests or Across test environments. */
155
+ baseUrl?: string | undefined
156
+ /** Fetch implementation override for tests. */
157
+ fetch?: Fetch | undefined
158
+ /** Across integrator id sent as the required query parameter. */
159
+ integratorId: string
160
+ /** Clock override for deterministic tests. */
161
+ now?: (() => Date) | undefined
162
+ /** Probe wallet address used as the Across depositor and recipient. */
163
+ userAddress: string
164
+ }
165
+ }
166
+
167
+ /** Creates a Relay quote adapter backed by `/quote/v2`. */
168
+ export function relay(options: relay.Options): FundingQuotes.ProviderAdapter {
169
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultRelayBaseUrl)
170
+ const fetcher = options.fetch ?? defaultFetch
171
+ const now = options.now ?? (() => new Date())
172
+
173
+ return {
174
+ async probeQuote({ candidate }, signal) {
175
+ const originChainId = providerChainId(candidate.sourceChain, 'relay')
176
+ const destinationChainId = eip155ChainId(candidate.destinationChain)
177
+ const headers = relayHeaders(options.apiKey)
178
+
179
+ try {
180
+ const quote = await requestJson(new URL(`${baseUrl}/quote/v2`), {
181
+ body: {
182
+ amount: candidate.sourceAmount.amount,
183
+ destinationChainId: Number(destinationChainId),
184
+ destinationCurrency: candidate.destinationToken.address,
185
+ originChainId: Number(originChainId),
186
+ originCurrency: candidate.sourceToken.address,
187
+ recipient: options.recipientAddress ?? options.userAddress,
188
+ tradeType: 'EXACT_INPUT',
189
+ user: options.userAddress,
190
+ },
191
+ fetcher,
192
+ headers,
193
+ method: 'POST',
194
+ signal,
195
+ })
196
+ return formatRelayResult(candidate, quote, now())
197
+ } catch (cause) {
198
+ const unavailable = providerUnavailable(cause, [
199
+ 'NO_QUOTES',
200
+ 'NO_ROUTE',
201
+ 'UNSUPPORTED_ROUTE',
202
+ ])
203
+ if (!unavailable) throw cause
204
+ return unavailableResult({
205
+ detail: 'relay:quote-v2',
206
+ message: unavailable,
207
+ now: now(),
208
+ source: 'providerQuote',
209
+ })
210
+ }
211
+ },
212
+ }
213
+ }
214
+
215
+ export declare namespace relay {
216
+ /** Relay quote adapter options. */
217
+ type Options = {
218
+ /** Probe wallet address required by Relay's quote API. */
219
+ userAddress: string
220
+ /** Optional recipient override; defaults to `userAddress`. */
221
+ recipientAddress?: string | undefined
222
+ /** Optional Relay API key for higher rate limits. */
223
+ apiKey?: string | undefined
224
+ /** Override for tests or Relay testnet environments. */
225
+ baseUrl?: string | undefined
226
+ /** Fetch implementation override for tests. */
227
+ fetch?: Fetch | undefined
228
+ /** Clock override for deterministic tests. */
229
+ now?: (() => Date) | undefined
230
+ }
231
+ }
232
+
233
+ /** Creates a Rhino quote adapter backed by the public bridge-and-swap quote endpoint. */
234
+ export function rhino(options: rhino.Options = {}): FundingQuotes.ProviderAdapter {
235
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultRhinoBaseUrl)
236
+ const fetcher = options.fetch ?? defaultFetch
237
+ const now = options.now ?? (() => new Date())
238
+
239
+ return {
240
+ async probeQuote({ cache, candidate }, signal) {
241
+ const chainIn = providerChainReference(candidate.sourceChain, 'rhino')
242
+ const tokenOut = rhinoToken(candidate.destinationToken)
243
+ const url = new URL(`${baseUrl}/quote/bridge-swap/public`)
244
+ url.search = new URLSearchParams({
245
+ amount: candidate.sourceAmount.formatted,
246
+ amountNative: '0',
247
+ chainIn,
248
+ chainOut: 'TEMPO',
249
+ mode: 'pay',
250
+ tokenIn: candidate.sourceToken.symbol,
251
+ tokenOut,
252
+ }).toString()
253
+
254
+ try {
255
+ const quote = await requestJson(url, {
256
+ fetcher,
257
+ headers: cache.bypass ? { 'cache-control': 'no-cache' } : undefined,
258
+ signal,
259
+ })
260
+ return formatRhinoResult(candidate, quote, { chainIn, now: now(), tokenOut })
261
+ } catch (cause) {
262
+ const unavailable = providerUnavailable(cause, ['NoRouteFoundError'])
263
+ if (!unavailable) throw cause
264
+ return unavailableResult({
265
+ detail: 'rhino:bridge-swap-public',
266
+ message: unavailable,
267
+ now: now(),
268
+ source: 'providerQuote',
269
+ })
270
+ }
271
+ },
272
+ }
273
+ }
274
+
275
+ export declare namespace rhino {
276
+ /** Rhino public quote adapter options. */
277
+ type Options = {
278
+ /** Override for tests or Rhino test environments. */
279
+ baseUrl?: string | undefined
280
+ /** Fetch implementation override for tests. */
281
+ fetch?: Fetch | undefined
282
+ /** Clock override for deterministic tests. */
283
+ now?: (() => Date) | undefined
284
+ }
285
+ }
286
+
287
+ /** Returns bounded provider failure details suitable for structured request logs. */
288
+ export function providerFailure(options: providerFailure.Options): providerFailure.Result {
289
+ const operation = providerOperations[options.provider]
290
+ if (
291
+ options.cause instanceof FundingQuotes.ProviderAdapterTimeoutError ||
292
+ (options.cause instanceof DOMException && options.cause.name === 'AbortError')
293
+ )
294
+ return { failure: 'timeout', id: options.provider, operation }
295
+ if (options.cause instanceof ProviderResponseError) {
296
+ const code = providerErrorCode(options.cause.body)
297
+ return {
298
+ ...(code === undefined ? {} : { code }),
299
+ failure: 'http',
300
+ id: options.provider,
301
+ operation,
302
+ status: options.cause.status,
303
+ }
304
+ }
305
+ if (options.cause instanceof ProviderPayloadError)
306
+ return { failure: 'payload', id: options.provider, operation }
307
+ if (options.cause instanceof TypeError)
308
+ return { failure: 'network', id: options.provider, operation }
309
+ return { failure: 'unknown', id: options.provider, operation }
310
+ }
311
+
312
+ export declare namespace providerFailure {
313
+ /** Inputs used to classify a provider failure. */
314
+ type Options = FundingQuotes.ProviderFailure
315
+
316
+ /** Bounded provider failure fields. */
317
+ type Result = {
318
+ /** Stable provider error code, when one is safe to retain. */
319
+ code?: string | undefined
320
+ /** Failure boundary that rejected the provider request. */
321
+ failure: 'http' | 'network' | 'payload' | 'timeout' | 'unknown'
322
+ /** Provider identifier. */
323
+ id: FundingQuotes.ProviderId
324
+ /** Provider operation identifier. */
325
+ operation: (typeof providerOperations)[FundingQuotes.ProviderId]
326
+ /** Upstream HTTP status, when a response was received. */
327
+ status?: number | undefined
328
+ }
329
+ }
330
+
331
+ function formatAcrossResult(
332
+ candidate: FundingQuotes.QuoteCandidate,
333
+ quote: unknown,
334
+ now: Date,
335
+ ): FundingQuotes.QuoteProbeResult {
336
+ const parsed = schema.AcrossQuote.safeParse(quote)
337
+ if (!parsed.success) throw new ProviderPayloadError()
338
+ const response = parsed.data
339
+ const expiresAt = new Date(response.quoteExpiryTimestamp * 1_000)
340
+ if (
341
+ response.inputAmount !== candidate.sourceAmount.amount ||
342
+ !matchesToken(response.inputToken, candidate.sourceChain, candidate.sourceToken) ||
343
+ !matchesToken(response.outputToken, candidate.destinationChain, candidate.destinationToken) ||
344
+ BigInt(response.expectedOutputAmount) <= 0n ||
345
+ BigInt(response.minOutputAmount) <= 0n ||
346
+ BigInt(response.minOutputAmount) > BigInt(response.expectedOutputAmount) ||
347
+ Number.isNaN(expiresAt.getTime()) ||
348
+ response.quoteExpiryTimestamp * 1_000 <= now.getTime()
349
+ )
350
+ throw new ProviderPayloadError()
351
+
352
+ return {
353
+ expiresAt: expiresAt.toISOString(),
354
+ minimumOutputAmount: response.minOutputAmount,
355
+ outputAmount: response.expectedOutputAmount,
356
+ outputAmountFormatted: decimalFromBaseUnits(
357
+ response.expectedOutputAmount,
358
+ candidate.destinationToken.decimals,
359
+ ),
360
+ quality: {
361
+ estimatedSeconds: response.expectedFillTime,
362
+ liquiditySource: 'providerQuote',
363
+ sourceDetail: 'across:swap-approval',
364
+ tier: 'liquid',
365
+ },
366
+ sampledAt: now.toISOString(),
367
+ status: 'available',
368
+ }
369
+ }
370
+
371
+ function formatRelayResult(
372
+ candidate: FundingQuotes.QuoteCandidate,
373
+ quote: unknown,
374
+ now: Date,
375
+ ): FundingQuotes.QuoteProbeResult {
376
+ const details = record(record(quote)['details'])
377
+ const currencyOut = record(details['currencyOut'])
378
+ const outputAmount = decimalString(currencyOut['amount'])
379
+ const status = outputAmount
380
+ ? { status: 'available' as const, tier: 'liquid' as const }
381
+ : { status: 'unknown' as const, tier: 'unknown' as const }
382
+
383
+ return {
384
+ minimumOutputAmount: decimalString(currencyOut['minimumAmount']),
385
+ outputAmount,
386
+ outputAmountFormatted: outputAmount
387
+ ? decimalFromBaseUnits(outputAmount, candidate.destinationToken.decimals)
388
+ : undefined,
389
+ quality: {
390
+ estimatedSeconds: nonnegativeInteger(details['timeEstimate']),
391
+ liquiditySource: 'providerQuote',
392
+ sourceDetail: 'relay:quote-v2',
393
+ tier: status.tier,
394
+ },
395
+ sampledAt: now.toISOString(),
396
+ status: status.status,
397
+ }
398
+ }
399
+
400
+ function formatRhinoResult(
401
+ candidate: FundingQuotes.QuoteCandidate,
402
+ quote: unknown,
403
+ options: { chainIn: string; now: Date; tokenOut: string },
404
+ ): FundingQuotes.QuoteProbeResult {
405
+ const parsed = schema.RhinoQuote.safeParse(quote)
406
+ if (!parsed.success) throw new ProviderPayloadError()
407
+ const response = parsed.data
408
+ const inputAmount = baseUnitsFromDecimal(response.payAmount, candidate.sourceToken.decimals)
409
+ const outputAmount = baseUnitsFromDecimal(
410
+ response.receiveAmount,
411
+ candidate.destinationToken.decimals,
412
+ )
413
+ if (
414
+ response.chainIn !== options.chainIn ||
415
+ response.chainOut !== 'TEMPO' ||
416
+ response.token !== options.tokenOut ||
417
+ inputAmount !== candidate.sourceAmount.amount ||
418
+ outputAmount === undefined ||
419
+ BigInt(outputAmount) <= 0n
420
+ )
421
+ throw new ProviderPayloadError()
422
+
423
+ return {
424
+ outputAmount,
425
+ outputAmountFormatted: decimalFromBaseUnits(outputAmount, candidate.destinationToken.decimals),
426
+ quality: {
427
+ ...(response.estimatedDuration === undefined || response.estimatedDuration === null
428
+ ? {}
429
+ : { estimatedSeconds: Math.ceil(response.estimatedDuration / 1_000) }),
430
+ liquiditySource: 'providerQuote',
431
+ sourceDetail: 'rhino:bridge-swap-public',
432
+ tier: 'liquid',
433
+ },
434
+ sampledAt: options.now.toISOString(),
435
+ status: 'available',
436
+ }
437
+ }
438
+
439
+ function unavailableResult(options: {
440
+ detail: string
441
+ message: string
442
+ now: Date
443
+ source: string
444
+ }): FundingQuotes.QuoteProbeResult {
445
+ return {
446
+ quality: {
447
+ liquiditySource: options.source,
448
+ sourceDetail: `${options.detail}:${options.message}`,
449
+ tier: 'unavailable',
450
+ },
451
+ sampledAt: options.now.toISOString(),
452
+ status: 'unavailable',
453
+ }
454
+ }
455
+
456
+ async function requestJson(
457
+ url: URL,
458
+ options: {
459
+ body?: unknown
460
+ fetcher: Fetch
461
+ headers?: Record<string, string> | undefined
462
+ method?: string | undefined
463
+ signal: AbortSignal
464
+ },
465
+ ) {
466
+ const headers = { accept: 'application/json', ...options.headers }
467
+ const init: RequestInit = {
468
+ headers:
469
+ options.body === undefined ? headers : { ...headers, 'content-type': 'application/json' },
470
+ method: options.method ?? 'GET',
471
+ signal: options.signal,
472
+ }
473
+ if (options.body !== undefined) init.body = JSON.stringify(options.body)
474
+ const response = await options.fetcher(url, init)
475
+ const text = await response.text()
476
+ const body = parseJson(text)
477
+ if (!response.ok) throw new ProviderResponseError(response.status, body)
478
+ return body
479
+ }
480
+
481
+ function relayHeaders(apiKey: string | undefined): Record<string, string> | undefined {
482
+ return apiKey ? { 'x-api-key': apiKey } : undefined
483
+ }
484
+
485
+ function providerUnavailable(cause: unknown, codes: readonly string[]) {
486
+ if (!(cause instanceof ProviderResponseError) || cause.status < 400 || cause.status >= 500)
487
+ return undefined
488
+ const code = providerErrorCode(cause.body)
489
+ if (code && codes.includes(code)) return code
490
+ return undefined
491
+ }
492
+
493
+ function providerErrorCode(input: unknown) {
494
+ const body = record(input)
495
+ const code =
496
+ stringValue(body['_tag']) ?? stringValue(body['code']) ?? stringValue(body['errorCode'])
497
+ return code && /^[A-Za-z0-9_.:-]{1,64}$/.test(code) ? code : undefined
498
+ }
499
+
500
+ function providerChainId(
501
+ quoteChain: FundingQuotes.QuoteCandidate['sourceChain'],
502
+ provider: string,
503
+ ) {
504
+ const providerIds = quoteChain.providerIds as Record<string, string> | undefined
505
+ const id = providerIds?.[provider]
506
+ if (id && /^\d+$/.test(id)) return id
507
+ return eip155ChainId(quoteChain)
508
+ }
509
+
510
+ function providerChainReference(
511
+ quoteChain: FundingQuotes.QuoteCandidate['sourceChain'],
512
+ provider: string,
513
+ ) {
514
+ const providerIds = quoteChain.providerIds as Record<string, string> | undefined
515
+ const id = providerIds?.[provider]
516
+ if (!id) throw new UnsupportedProviderChainError()
517
+ return id
518
+ }
519
+
520
+ function rhinoToken(token: FundingQuotes.TokenRef) {
521
+ const tokens: Record<string, string> = {
522
+ '0x20c00000000000000000000014f22ca97301eb73': 'USDT',
523
+ '0x20c000000000000000000000b9537d11c60e8b50': 'USDC',
524
+ }
525
+ const id = tokens[token.address.toLowerCase()]
526
+ if (!id) throw new UnsupportedProviderTokenError()
527
+ return id
528
+ }
529
+
530
+ function matchesToken(
531
+ token: z.output<typeof schema.AcrossQuote>['inputToken'],
532
+ chain: FundingQuotes.ChainRef,
533
+ expected: FundingQuotes.TokenRef,
534
+ ) {
535
+ return (
536
+ token.address.toLowerCase() === expected.address.toLowerCase() &&
537
+ token.chainId === Number(eip155ChainId(chain)) &&
538
+ token.decimals === expected.decimals
539
+ )
540
+ }
541
+
542
+ function eip155ChainId(chain: { id: string; kind: string }) {
543
+ if (chain.kind !== 'evm') throw new UnsupportedProviderChainError()
544
+ const match = /^eip155:(\d+)$/.exec(chain.id)
545
+ if (!match?.[1]) throw new UnsupportedProviderChainError()
546
+ return match[1]
547
+ }
548
+
549
+ function normalizeBaseUrl(input: string) {
550
+ return input.replace(/\/+$/, '')
551
+ }
552
+
553
+ function parseJson(input: string) {
554
+ if (!input) return null
555
+ try {
556
+ return JSON.parse(input) as unknown
557
+ } catch {
558
+ return input
559
+ }
560
+ }
561
+
562
+ function record(input: unknown): Record<string, unknown> {
563
+ return input && typeof input === 'object' && !Array.isArray(input)
564
+ ? (input as Record<string, unknown>)
565
+ : {}
566
+ }
567
+
568
+ function stringValue(input: unknown) {
569
+ return typeof input === 'string' && input.length > 0 ? input : undefined
570
+ }
571
+
572
+ function decimalString(input: unknown) {
573
+ if (typeof input === 'string' && /^\d+(\.\d+)?$/.test(input)) return input
574
+ if (typeof input === 'number' && Number.isFinite(input) && input >= 0) return String(input)
575
+ return undefined
576
+ }
577
+
578
+ function nonnegativeInteger(input: unknown) {
579
+ if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0) return input
580
+ if (typeof input === 'string' && /^\d+$/.test(input)) return Number(input)
581
+ return undefined
582
+ }
583
+
584
+ function decimalFromBaseUnits(baseUnits: string, decimals: number) {
585
+ const padded = baseUnits.padStart(decimals + 1, '0')
586
+ const whole = decimals === 0 ? padded : padded.slice(0, -decimals)
587
+ const fraction = decimals === 0 ? '' : padded.slice(-decimals).replace(/0+$/, '')
588
+ return fraction ? `${whole}.${fraction}` : whole
589
+ }
590
+
591
+ function baseUnitsFromDecimal(value: string, decimals: number) {
592
+ if (!/^\d+(\.\d+)?$/.test(value)) return undefined
593
+ const [whole, fraction = ''] = value.split('.')
594
+ const significantFraction = fraction.replace(/0+$/, '')
595
+ if (significantFraction.length > decimals) return undefined
596
+ return BigInt(`${whole}${significantFraction.padEnd(decimals, '0')}`).toString()
597
+ }
598
+
599
+ function assertAcrossConfiguration(options: across.Options) {
600
+ if (
601
+ options.apiKey.length === 0 ||
602
+ !/^0x[\da-fA-F]{4}$/.test(options.integratorId) ||
603
+ !/^0x[\da-fA-F]{40}$/.test(options.userAddress)
604
+ )
605
+ throw new ProviderConfigurationError()
606
+ }
607
+
608
+ class ProviderConfigurationError extends Error {
609
+ override name = 'FundingQuoteProviders.ProviderConfigurationError'
610
+ }
611
+
612
+ class ProviderPayloadError extends Error {
613
+ override name = 'FundingQuoteProviders.ProviderPayloadError'
614
+ }
615
+
616
+ class ProviderResponseError extends Error {
617
+ override name = 'FundingQuoteProviders.ProviderResponseError'
618
+
619
+ constructor(
620
+ readonly status: number,
621
+ readonly body: unknown,
622
+ ) {
623
+ super(`Provider request failed with status ${status}`)
624
+ }
625
+ }
626
+
627
+ class UnsupportedProviderChainError extends Error {
628
+ override name = 'FundingQuoteProviders.UnsupportedProviderChainError'
629
+ }
630
+
631
+ class UnsupportedProviderTokenError extends Error {
632
+ override name = 'FundingQuoteProviders.UnsupportedProviderTokenError'
633
+ }
@@ -1,3 +1,3 @@
1
1
  export { data } from './App.js'
2
- export * as FundingRouteProviders from './FundingRouteProviders.js'
2
+ export * as FundingQuoteProviders from './FundingQuoteProviders.js'
3
3
  export * as Sponsorships from '../relay/Sponsorships.js'