tapimo 0.5.2 → 0.6.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 (299) hide show
  1. package/dist/App.d.ts +13987 -5755
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/admin/App.d.ts +910 -412
  5. package/dist/admin/App.d.ts.map +1 -1
  6. package/dist/admin/App.js +4 -0
  7. package/dist/admin/App.js.map +1 -1
  8. package/dist/admin/apps/early-access.d.ts +148 -0
  9. package/dist/admin/apps/early-access.d.ts.map +1 -0
  10. package/dist/admin/apps/early-access.js +133 -0
  11. package/dist/admin/apps/early-access.js.map +1 -0
  12. package/dist/admin/apps/verified-tokens.d.ts +643 -118
  13. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  14. package/dist/admin/apps/verified-tokens.js +214 -1
  15. package/dist/admin/apps/verified-tokens.js.map +1 -1
  16. package/dist/admin/ui.generated.d.ts +1 -1
  17. package/dist/admin/ui.generated.d.ts.map +1 -1
  18. package/dist/admin/ui.generated.js +45 -33
  19. package/dist/admin/ui.generated.js.map +1 -1
  20. package/dist/apps/data/App.d.ts +3733 -2428
  21. package/dist/apps/data/App.d.ts.map +1 -1
  22. package/dist/apps/data/App.js +11 -6
  23. package/dist/apps/data/App.js.map +1 -1
  24. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  25. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  26. package/dist/apps/data/FundingQuoteProviders.js +193 -2
  27. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  28. package/dist/apps/data/FxOracle.d.ts +71 -0
  29. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  30. package/dist/apps/data/FxOracle.js +97 -0
  31. package/dist/apps/data/FxOracle.js.map +1 -0
  32. package/dist/apps/data/index.d.ts +1 -0
  33. package/dist/apps/data/index.d.ts.map +1 -1
  34. package/dist/apps/data/index.js +1 -0
  35. package/dist/apps/data/index.js.map +1 -1
  36. package/dist/apps/data/routes/activities.d.ts +3575 -1358
  37. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  38. package/dist/apps/data/routes/activities.js +230 -88
  39. package/dist/apps/data/routes/activities.js.map +1 -1
  40. package/dist/apps/data/routes/addresses.d.ts +963 -342
  41. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  42. package/dist/apps/data/routes/addresses.js +11 -9
  43. package/dist/apps/data/routes/addresses.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +108 -57
  45. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.js +88 -45
  47. package/dist/apps/data/routes/balances.js.map +1 -1
  48. package/dist/apps/data/routes/coingecko.d.ts +169 -169
  49. package/dist/apps/data/routes/exchanges.d.ts +952 -201
  50. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  51. package/dist/apps/data/routes/exchanges.js +518 -76
  52. package/dist/apps/data/routes/exchanges.js.map +1 -1
  53. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  54. package/dist/apps/data/routes/funding.d.ts +8 -2
  55. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  56. package/dist/apps/data/routes/funding.js +32 -6
  57. package/dist/apps/data/routes/funding.js.map +1 -1
  58. package/dist/apps/data/routes/indexer.d.ts +5 -5
  59. package/dist/apps/data/routes/receipts.d.ts +392 -315
  60. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  61. package/dist/apps/data/routes/receipts.js +152 -77
  62. package/dist/apps/data/routes/receipts.js.map +1 -1
  63. package/dist/apps/data/routes/tokens.d.ts +166 -173
  64. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  65. package/dist/apps/data/routes/tokens.js +14 -12
  66. package/dist/apps/data/routes/tokens.js.map +1 -1
  67. package/dist/apps/data/routes/transactions.d.ts +277 -536
  68. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  69. package/dist/apps/data/routes/transactions.js +96 -77
  70. package/dist/apps/data/routes/transactions.js.map +1 -1
  71. package/dist/apps/data/routes/transfers.d.ts +82 -17
  72. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  73. package/dist/apps/data/routes/transfers.js +70 -13
  74. package/dist/apps/data/routes/transfers.js.map +1 -1
  75. package/dist/apps/data/routes/valuation.d.ts +307 -0
  76. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  77. package/dist/apps/data/routes/valuation.js +292 -0
  78. package/dist/apps/data/routes/valuation.js.map +1 -0
  79. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  80. package/dist/apps/data/routes/verified-tokens.js +7 -5
  81. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  82. package/dist/apps/data/routes/webhooks.d.ts +459 -479
  83. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  84. package/dist/apps/data/routes/webhooks.js +124 -25
  85. package/dist/apps/data/routes/webhooks.js.map +1 -1
  86. package/dist/apps/index.d.ts +1 -1
  87. package/dist/apps/index.d.ts.map +1 -1
  88. package/dist/apps/index.js +1 -1
  89. package/dist/apps/index.js.map +1 -1
  90. package/dist/apps/management/App.d.ts +8606 -1681
  91. package/dist/apps/management/App.d.ts.map +1 -1
  92. package/dist/apps/management/App.js +12 -1
  93. package/dist/apps/management/App.js.map +1 -1
  94. package/dist/apps/management/routes/api-keys.d.ts +23 -0
  95. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  96. package/dist/apps/management/routes/api-keys.js +5 -3
  97. package/dist/apps/management/routes/api-keys.js.map +1 -1
  98. package/dist/apps/management/routes/billing.d.ts +88 -48
  99. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  100. package/dist/apps/management/routes/billing.js +10 -2
  101. package/dist/apps/management/routes/billing.js.map +1 -1
  102. package/dist/apps/management/routes/faucet.d.ts +218 -0
  103. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  104. package/dist/apps/management/routes/faucet.js +139 -0
  105. package/dist/apps/management/routes/faucet.js.map +1 -0
  106. package/dist/apps/management/routes/invitations.d.ts +121 -121
  107. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  108. package/dist/apps/management/routes/invitations.js +3 -0
  109. package/dist/apps/management/routes/invitations.js.map +1 -1
  110. package/dist/apps/management/routes/invite-links.d.ts +197 -168
  111. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  112. package/dist/apps/management/routes/invite-links.js +26 -1
  113. package/dist/apps/management/routes/invite-links.js.map +1 -1
  114. package/dist/apps/management/routes/me.d.ts +2 -0
  115. package/dist/apps/management/routes/me.d.ts.map +1 -1
  116. package/dist/apps/management/routes/me.js +4 -0
  117. package/dist/apps/management/routes/me.js.map +1 -1
  118. package/dist/apps/management/routes/members.d.ts +49 -49
  119. package/dist/apps/management/routes/orgs.d.ts +91 -91
  120. package/dist/apps/management/routes/projects.d.ts +115 -115
  121. package/dist/apps/management/routes/usage.d.ts +35 -32
  122. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  123. package/dist/apps/management/routes/usage.js +4 -0
  124. package/dist/apps/management/routes/usage.js.map +1 -1
  125. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  126. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  127. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  128. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  129. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  130. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  131. package/dist/apps/management/routes/webhooks.js +573 -0
  132. package/dist/apps/management/routes/webhooks.js.map +1 -0
  133. package/dist/apps/mcp.d.ts +9 -2
  134. package/dist/apps/mcp.d.ts.map +1 -1
  135. package/dist/apps/mcp.js +9 -4
  136. package/dist/apps/mcp.js.map +1 -1
  137. package/dist/cli/Cli.d.ts.map +1 -1
  138. package/dist/cli/Cli.js +7 -2
  139. package/dist/cli/Cli.js.map +1 -1
  140. package/dist/db/Db.d.ts +4 -0
  141. package/dist/db/Db.d.ts.map +1 -1
  142. package/dist/db/Db.js +83 -0
  143. package/dist/db/Db.js.map +1 -1
  144. package/dist/db/Schema.d.ts +41 -0
  145. package/dist/db/Schema.d.ts.map +1 -1
  146. package/dist/db/Schema.js +45 -0
  147. package/dist/db/Schema.js.map +1 -1
  148. package/dist/db/tables/earlyAccess.d.ts +50 -0
  149. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  150. package/dist/db/tables/earlyAccess.js +63 -0
  151. package/dist/db/tables/earlyAccess.js.map +1 -0
  152. package/dist/db/tables/invitations.d.ts +12 -1
  153. package/dist/db/tables/invitations.d.ts.map +1 -1
  154. package/dist/db/tables/invitations.js +23 -0
  155. package/dist/db/tables/invitations.js.map +1 -1
  156. package/dist/db/tables/inviteLinks.d.ts +10 -2
  157. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  158. package/dist/db/tables/inviteLinks.js +10 -1
  159. package/dist/db/tables/inviteLinks.js.map +1 -1
  160. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  161. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  162. package/dist/db/tables/sponsoredTransactions.js +2 -0
  163. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  164. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  165. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  166. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  167. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  168. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  169. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  170. package/dist/db/tables/webhookSubscriptions.js +16 -0
  171. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  172. package/dist/internal/Auth.d.ts +43 -2
  173. package/dist/internal/Auth.d.ts.map +1 -1
  174. package/dist/internal/Auth.js +60 -45
  175. package/dist/internal/Auth.js.map +1 -1
  176. package/dist/internal/FundingQuotes.d.ts +69 -15
  177. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  178. package/dist/internal/FundingQuotes.js +74 -25
  179. package/dist/internal/FundingQuotes.js.map +1 -1
  180. package/dist/internal/Log.d.ts +19 -1
  181. package/dist/internal/Log.d.ts.map +1 -1
  182. package/dist/internal/Log.js +2 -0
  183. package/dist/internal/Log.js.map +1 -1
  184. package/dist/internal/MetricSink.d.ts.map +1 -1
  185. package/dist/internal/MetricSink.js +9 -0
  186. package/dist/internal/MetricSink.js.map +1 -1
  187. package/dist/internal/OpenApi.d.ts +2 -2
  188. package/dist/internal/OpenApi.d.ts.map +1 -1
  189. package/dist/internal/OpenApi.js +3 -0
  190. package/dist/internal/OpenApi.js.map +1 -1
  191. package/dist/internal/Schema.d.ts +14 -5
  192. package/dist/internal/Schema.d.ts.map +1 -1
  193. package/dist/internal/Schema.js +23 -5
  194. package/dist/internal/Schema.js.map +1 -1
  195. package/dist/internal/Value.d.ts +18 -0
  196. package/dist/internal/Value.d.ts.map +1 -1
  197. package/dist/internal/Value.js +11 -1
  198. package/dist/internal/Value.js.map +1 -1
  199. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  200. package/dist/internal/VerifiedTokens.js +10 -1
  201. package/dist/internal/VerifiedTokens.js.map +1 -1
  202. package/dist/internal/Viem.d.ts +1 -1
  203. package/dist/internal/Webhooks.d.ts +24 -3
  204. package/dist/internal/Webhooks.d.ts.map +1 -1
  205. package/dist/internal/Webhooks.js +34 -8
  206. package/dist/internal/Webhooks.js.map +1 -1
  207. package/package.json +2 -2
  208. package/src/App.ts +9 -1
  209. package/src/Client.test-d.ts +638 -6
  210. package/src/admin/App.ts +4 -0
  211. package/src/admin/apps/early-access.test.ts +125 -0
  212. package/src/admin/apps/early-access.ts +175 -0
  213. package/src/admin/apps/verified-tokens.test.ts +201 -0
  214. package/src/admin/apps/verified-tokens.ts +532 -286
  215. package/src/admin/ui/plugin.ts +2 -1
  216. package/src/admin/ui/src/lib/early-access.ts +63 -0
  217. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  218. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  219. package/src/admin/ui/src/routes/__root.tsx +2 -1
  220. package/src/admin/ui/src/routes/early-access.tsx +157 -0
  221. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  222. package/src/admin/ui.generated.ts +48 -33
  223. package/src/apps/data/App.ts +22 -14
  224. package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
  225. package/src/apps/data/FundingQuoteProviders.ts +257 -4
  226. package/src/apps/data/FxOracle.test.ts +118 -0
  227. package/src/apps/data/FxOracle.ts +153 -0
  228. package/src/apps/data/index.ts +1 -0
  229. package/src/apps/data/routes/activities.test.ts +173 -3
  230. package/src/apps/data/routes/activities.ts +403 -132
  231. package/src/apps/data/routes/addresses.ts +19 -9
  232. package/src/apps/data/routes/balances.test.ts +144 -11
  233. package/src/apps/data/routes/balances.ts +140 -51
  234. package/src/apps/data/routes/exchanges.test.ts +540 -9
  235. package/src/apps/data/routes/exchanges.ts +690 -103
  236. package/src/apps/data/routes/funding.test.ts +84 -0
  237. package/src/apps/data/routes/funding.ts +38 -6
  238. package/src/apps/data/routes/receipts.test.ts +125 -23
  239. package/src/apps/data/routes/receipts.ts +205 -100
  240. package/src/apps/data/routes/tokens.test.ts +43 -0
  241. package/src/apps/data/routes/tokens.ts +27 -12
  242. package/src/apps/data/routes/transactions.test.ts +72 -19
  243. package/src/apps/data/routes/transactions.ts +162 -92
  244. package/src/apps/data/routes/transfers.test.ts +146 -4
  245. package/src/apps/data/routes/transfers.ts +91 -13
  246. package/src/apps/data/routes/valuation.test.ts +422 -0
  247. package/src/apps/data/routes/valuation.ts +423 -0
  248. package/src/apps/data/routes/verified-tokens.ts +14 -2
  249. package/src/apps/data/routes/webhooks.test.ts +56 -8
  250. package/src/apps/data/routes/webhooks.ts +144 -27
  251. package/src/apps/index.ts +1 -1
  252. package/src/apps/management/App.ts +13 -0
  253. package/src/apps/management/routes/api-keys.test.ts +87 -0
  254. package/src/apps/management/routes/api-keys.ts +4 -3
  255. package/src/apps/management/routes/billing.test.ts +112 -1
  256. package/src/apps/management/routes/billing.ts +7 -2
  257. package/src/apps/management/routes/faucet.test.ts +79 -0
  258. package/src/apps/management/routes/faucet.ts +185 -0
  259. package/src/apps/management/routes/invitations.test.ts +172 -0
  260. package/src/apps/management/routes/invitations.ts +3 -0
  261. package/src/apps/management/routes/invite-links.test.ts +117 -0
  262. package/src/apps/management/routes/invite-links.ts +43 -1
  263. package/src/apps/management/routes/me.test.ts +140 -88
  264. package/src/apps/management/routes/me.ts +6 -0
  265. package/src/apps/management/routes/usage.test.ts +6 -0
  266. package/src/apps/management/routes/usage.ts +7 -0
  267. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  268. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  269. package/src/apps/management/routes/webhooks.test.ts +426 -0
  270. package/src/apps/management/routes/webhooks.ts +672 -0
  271. package/src/apps/mcp.test.ts +13 -0
  272. package/src/apps/mcp.ts +18 -5
  273. package/src/cli/Cli.test.ts +36 -0
  274. package/src/cli/Cli.ts +7 -2
  275. package/src/db/Db.test.ts +19 -0
  276. package/src/db/Db.ts +95 -0
  277. package/src/db/Schema.ts +53 -0
  278. package/src/db/tables/earlyAccess.ts +86 -0
  279. package/src/db/tables/invitations.ts +26 -1
  280. package/src/db/tables/inviteLinks.ts +22 -3
  281. package/src/db/tables/sponsoredTransactions.ts +4 -0
  282. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  283. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  284. package/src/db/tables/webhookSubscriptions.ts +31 -0
  285. package/src/internal/Auth.ts +73 -45
  286. package/src/internal/FundingQuotes.test.ts +143 -1
  287. package/src/internal/FundingQuotes.ts +95 -28
  288. package/src/internal/Log.test-d.ts +21 -0
  289. package/src/internal/Log.test.ts +50 -0
  290. package/src/internal/Log.ts +24 -1
  291. package/src/internal/MetricSink.test.ts +160 -0
  292. package/src/internal/MetricSink.ts +10 -0
  293. package/src/internal/OpenApi.ts +3 -0
  294. package/src/internal/Schema.ts +50 -5
  295. package/src/internal/Value.test.ts +20 -0
  296. package/src/internal/Value.ts +24 -1
  297. package/src/internal/VerifiedTokens.ts +9 -1
  298. package/src/internal/Webhooks.test.ts +140 -0
  299. package/src/internal/Webhooks.ts +53 -9
@@ -1,6 +1,7 @@
1
1
  import * as z from 'zod/mini'
2
2
 
3
3
  import * as FundingQuotes from '../../internal/FundingQuotes.js'
4
+ import * as Store from '../../internal/Store.js'
4
5
 
5
6
  type Fetch = typeof globalThis.fetch
6
7
 
@@ -10,13 +11,23 @@ const defaultAcrossBaseUrl = 'https://app.across.to/api'
10
11
  const defaultRelayBaseUrl = 'https://api.relay.link'
11
12
  const defaultRhinoBaseUrl = 'https://api.rhino.fi/bridge'
12
13
  const defaultSquidBaseUrl = 'https://v2.api.squidrouter.com/v2'
14
+ const defaultSymbiosisBaseUrl = 'https://api.symbiosis.finance/crosschain'
15
+ // Keep one upstream slot free for deployment smoke tests and operational probes.
16
+ const symbiosisRateLimit = 4
17
+ const symbiosisRateLimitWindowMs = 1_000
13
18
  const providerOperations = {
14
19
  across: 'swap/approval',
15
20
  relay: 'quote/v2',
16
21
  rhino: 'quote/bridge-swap/public',
17
22
  squid: 'v2/route',
23
+ symbiosis: 'v2/quote',
18
24
  } as const
19
25
 
26
+ /** Returns the stable upstream operation used by a funding quote provider. */
27
+ export function providerOperation(provider: FundingQuotes.ProviderId) {
28
+ return providerOperations[provider]
29
+ }
30
+
20
31
  namespace schema {
21
32
  const IntegerString = z.string().check(z.regex(/^\d+$/))
22
33
 
@@ -65,6 +76,30 @@ namespace schema {
65
76
  }),
66
77
  }),
67
78
  })
79
+
80
+ export const SymbiosisToken = z.object({
81
+ address: z.string(),
82
+ chainId: z.number().check(z.int(), z.nonnegative()),
83
+ decimals: z.number().check(z.int(), z.nonnegative()),
84
+ })
85
+
86
+ const SymbiosisTokenAmount = z.extend(SymbiosisToken, { amount: IntegerString })
87
+
88
+ export const SymbiosisQuote = z.object({
89
+ estimatedTime: z.optional(z.number().check(z.nonnegative())),
90
+ kind: z.union([z.literal('bridge'), z.literal('crosschain-swap'), z.literal('intent-swap')]),
91
+ routes: z
92
+ .array(
93
+ z.object({
94
+ tokens: z.array(SymbiosisToken).check(z.minLength(1)),
95
+ }),
96
+ )
97
+ .check(z.minLength(1)),
98
+ tokenAmountOut: SymbiosisTokenAmount,
99
+ tokenAmountOutMin: SymbiosisTokenAmount,
100
+ tx: z.object({ chainId: z.number().check(z.int(), z.nonnegative()) }),
101
+ type: z.union([z.literal('evm'), z.literal('tron')]),
102
+ })
68
103
  }
69
104
 
70
105
  /** Creates the live funding quote adapters configured for this deployment. */
@@ -74,6 +109,7 @@ export function createAdapters(options: createAdapters.Options = {}) {
74
109
  ...(options.relay ? { relay: relay(options.relay) } : {}),
75
110
  ...(options.rhino ? { rhino: rhino(options.rhino) } : {}),
76
111
  ...(options.squid ? { squid: squid(options.squid) } : {}),
112
+ ...(options.symbiosis ? { symbiosis: symbiosis(options.symbiosis) } : {}),
77
113
  } satisfies FundingQuotes.ProviderAdapters
78
114
  }
79
115
 
@@ -88,6 +124,8 @@ export declare namespace createAdapters {
88
124
  rhino?: rhino.Options | false | undefined
89
125
  /** Squid quote adapter options. */
90
126
  squid?: squid.Options | false | undefined
127
+ /** Symbiosis quote adapter options. */
128
+ symbiosis?: symbiosis.Options | false | undefined
91
129
  }
92
130
  }
93
131
 
@@ -137,6 +175,22 @@ export declare namespace configureSquid {
137
175
  }
138
176
  }
139
177
 
178
+ /** Resolves Symbiosis deployment configuration. */
179
+ export function configureSymbiosis(
180
+ options: configureSymbiosis.Options = {},
181
+ ): symbiosis.Options | undefined {
182
+ if (!options.userAddress) return undefined
183
+ return { userAddress: options.userAddress }
184
+ }
185
+
186
+ export declare namespace configureSymbiosis {
187
+ /** Optional deployment binding required to enable Symbiosis. */
188
+ type Options = {
189
+ /** Symbiosis quote-user binding. */
190
+ userAddress?: string | undefined
191
+ }
192
+ }
193
+
140
194
  /** Creates an Across quote adapter backed by `/swap/approval`. */
141
195
  export function across(options: across.Options): FundingQuotes.ProviderAdapter {
142
196
  assertAcrossConfiguration(options)
@@ -397,9 +451,87 @@ export declare namespace squid {
397
451
  }
398
452
  }
399
453
 
454
+ /** Creates a Symbiosis quote adapter backed by `/v2/quote`. */
455
+ export function symbiosis(options: symbiosis.Options): FundingQuotes.ProviderAdapter {
456
+ assertUserAddress(options.userAddress)
457
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultSymbiosisBaseUrl)
458
+ const fetcher = options.fetch ?? defaultFetch
459
+ const now = options.now ?? (() => new Date())
460
+ const rateLimitStore = options.rateLimitStore ?? Store.memory()
461
+
462
+ return {
463
+ async probeQuote({ candidate }, signal) {
464
+ const fromChainId = Number(providerChainId(candidate.sourceChain, 'symbiosis'))
465
+ const fromToken = symbiosisSourceToken(candidate.sourceChain, candidate.sourceToken)
466
+ const toChainId = Number(eip155ChainId(candidate.destinationChain))
467
+
468
+ try {
469
+ await consumeSymbiosisQuota(rateLimitStore, now())
470
+ const quote = await requestJson(new URL(`${baseUrl}/v2/quote`), {
471
+ body: {
472
+ from: options.userAddress,
473
+ slippage: 100,
474
+ to: options.userAddress,
475
+ tokenAmountIn: {
476
+ address: fromToken,
477
+ amount: candidate.sourceAmount.amount,
478
+ chainId: fromChainId,
479
+ decimals: candidate.sourceToken.decimals,
480
+ symbol: candidate.sourceToken.symbol,
481
+ },
482
+ tokenOut: {
483
+ address: candidate.destinationToken.address,
484
+ chainId: toChainId,
485
+ decimals: candidate.destinationToken.decimals,
486
+ symbol: candidate.destinationToken.symbol,
487
+ },
488
+ },
489
+ fetcher,
490
+ headers: { 'x-partner-id': 'tempo' },
491
+ method: 'POST',
492
+ signal,
493
+ })
494
+ return formatSymbiosisResult(candidate, quote, now())
495
+ } catch (cause) {
496
+ const unavailable =
497
+ providerUnavailable(cause, [
498
+ 'AMOUNT_TOO_LOW',
499
+ 'NO_ROUTE',
500
+ 'NO_ROUTE_FOUND',
501
+ 'ROUTE_NOT_FOUND',
502
+ 'UNSUPPORTED_ROUTE',
503
+ ]) ?? symbiosisUnavailable(cause)
504
+ if (!unavailable) throw cause
505
+ return unavailableResult({
506
+ detail: 'symbiosis:quote-v2',
507
+ message: unavailable,
508
+ now: now(),
509
+ source: 'providerQuote',
510
+ })
511
+ }
512
+ },
513
+ }
514
+ }
515
+
516
+ export declare namespace symbiosis {
517
+ /** Symbiosis quote adapter options. */
518
+ type Options = {
519
+ /** Override for tests or Symbiosis test environments. */
520
+ baseUrl?: string | undefined
521
+ /** Fetch implementation override for tests. */
522
+ fetch?: Fetch | undefined
523
+ /** Clock override for deterministic tests. */
524
+ now?: (() => Date) | undefined
525
+ /** Atomic store used to enforce the provider-wide quote quota. */
526
+ rateLimitStore?: Store.Store | undefined
527
+ /** Probe wallet address used as the Symbiosis sender and recipient. */
528
+ userAddress: string
529
+ }
530
+ }
531
+
400
532
  /** Returns bounded provider failure details suitable for structured request logs. */
401
533
  export function providerFailure(options: providerFailure.Options): providerFailure.Result {
402
- const operation = providerOperations[options.provider]
534
+ const operation = providerOperation(options.provider)
403
535
  if (
404
536
  options.cause instanceof FundingQuotes.ProviderAdapterTimeoutError ||
405
537
  (options.cause instanceof DOMException && options.cause.name === 'AbortError')
@@ -417,6 +549,8 @@ export function providerFailure(options: providerFailure.Options): providerFailu
417
549
  }
418
550
  if (options.cause instanceof ProviderPayloadError)
419
551
  return { failure: 'payload', id: options.provider, operation }
552
+ if (options.cause instanceof ProviderRateLimitError)
553
+ return { failure: 'rate_limit', id: options.provider, operation }
420
554
  if (options.cause instanceof TypeError)
421
555
  return { failure: 'network', id: options.provider, operation }
422
556
  return { failure: 'unknown', id: options.provider, operation }
@@ -431,7 +565,7 @@ export declare namespace providerFailure {
431
565
  /** Stable provider error code, when one is safe to retain. */
432
566
  code?: string | undefined
433
567
  /** Failure boundary that rejected the provider request. */
434
- failure: 'http' | 'network' | 'payload' | 'timeout' | 'unknown'
568
+ failure: 'http' | 'network' | 'payload' | 'rate_limit' | 'timeout' | 'unknown'
435
569
  /** Provider identifier. */
436
570
  id: FundingQuotes.ProviderId
437
571
  /** Provider operation identifier. */
@@ -585,6 +719,68 @@ function formatSquidResult(
585
719
  }
586
720
  }
587
721
 
722
+ function formatSymbiosisResult(
723
+ candidate: FundingQuotes.QuoteCandidate,
724
+ quote: unknown,
725
+ now: Date,
726
+ ): FundingQuotes.QuoteProbeResult {
727
+ const parsed = schema.SymbiosisQuote.safeParse(quote)
728
+ if (!parsed.success) throw new ProviderPayloadError()
729
+ const response = parsed.data
730
+ const sourceChainId = Number(providerChainId(candidate.sourceChain, 'symbiosis'))
731
+ const sourceToken = symbiosisSourceToken(candidate.sourceChain, candidate.sourceToken)
732
+ const firstToken = response.routes[0]!.tokens[0]!
733
+ const lastRoute = response.routes[response.routes.length - 1]!
734
+ const lastToken = lastRoute.tokens[lastRoute.tokens.length - 1]!
735
+ if (
736
+ response.tx.chainId !== sourceChainId ||
737
+ response.type !== candidate.sourceChain.kind ||
738
+ !matchesSymbiosisToken(firstToken, {
739
+ address: sourceToken,
740
+ chainId: sourceChainId,
741
+ decimals: candidate.sourceToken.decimals,
742
+ }) ||
743
+ !matchesSymbiosisToken(lastToken, {
744
+ address: candidate.destinationToken.address,
745
+ chainId: Number(eip155ChainId(candidate.destinationChain)),
746
+ decimals: candidate.destinationToken.decimals,
747
+ }) ||
748
+ !matchesSymbiosisToken(response.tokenAmountOut, {
749
+ address: candidate.destinationToken.address,
750
+ chainId: Number(eip155ChainId(candidate.destinationChain)),
751
+ decimals: candidate.destinationToken.decimals,
752
+ }) ||
753
+ !matchesSymbiosisToken(response.tokenAmountOutMin, {
754
+ address: candidate.destinationToken.address,
755
+ chainId: Number(eip155ChainId(candidate.destinationChain)),
756
+ decimals: candidate.destinationToken.decimals,
757
+ }) ||
758
+ BigInt(response.tokenAmountOut.amount) <= 0n ||
759
+ BigInt(response.tokenAmountOutMin.amount) <= 0n ||
760
+ BigInt(response.tokenAmountOutMin.amount) > BigInt(response.tokenAmountOut.amount)
761
+ )
762
+ throw new ProviderPayloadError()
763
+
764
+ return {
765
+ minimumOutputAmount: response.tokenAmountOutMin.amount,
766
+ outputAmount: response.tokenAmountOut.amount,
767
+ outputAmountFormatted: decimalFromBaseUnits(
768
+ response.tokenAmountOut.amount,
769
+ candidate.destinationToken.decimals,
770
+ ),
771
+ quality: {
772
+ ...(response.estimatedTime === undefined
773
+ ? {}
774
+ : { estimatedSeconds: Math.ceil(response.estimatedTime) }),
775
+ liquiditySource: 'providerQuote',
776
+ sourceDetail: 'symbiosis:quote-v2',
777
+ tier: 'liquid',
778
+ },
779
+ sampledAt: now.toISOString(),
780
+ status: 'available',
781
+ }
782
+ }
783
+
588
784
  function unavailableResult(options: {
589
785
  detail: string
590
786
  message: string
@@ -664,6 +860,22 @@ function squidUnavailable(cause: unknown) {
664
860
  return undefined
665
861
  }
666
862
 
863
+ function symbiosisUnavailable(cause: unknown) {
864
+ if (!(cause instanceof ProviderResponseError) || cause.status !== 400) return undefined
865
+ const body = record(cause.body)
866
+ if (body['message'] === 'Amount is too low to cover fees') return 'AMOUNT_TOO_LOW'
867
+ return undefined
868
+ }
869
+
870
+ async function consumeSymbiosisQuota(store: Store.Store, now: Date) {
871
+ const time = now.getTime()
872
+ const bucket = Math.floor(time / symbiosisRateLimitWindowMs)
873
+ const count = await Store.increment(store, `funding:symbiosis:quote:${bucket}`, {
874
+ ttl: symbiosisRateLimitWindowMs * 2,
875
+ })
876
+ if (count > symbiosisRateLimit) throw new ProviderRateLimitError()
877
+ }
878
+
667
879
  function providerChainId(
668
880
  quoteChain: FundingQuotes.QuoteCandidate['sourceChain'],
669
881
  provider: string,
@@ -694,6 +906,16 @@ function rhinoToken(token: FundingQuotes.TokenRef) {
694
906
  return id
695
907
  }
696
908
 
909
+ function symbiosisSourceToken(chain: FundingQuotes.ChainRef, token: FundingQuotes.TokenRef) {
910
+ if (chain.kind !== 'tron') return token.address
911
+ const tokens: Record<string, string> = {
912
+ TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t: '0xa614f803b6fd780986a42c78ec9c7f77e6ded13c',
913
+ }
914
+ const id = tokens[token.address]
915
+ if (!id) throw new UnsupportedProviderTokenError()
916
+ return id
917
+ }
918
+
697
919
  function matchesToken(
698
920
  token: z.output<typeof schema.AcrossQuote>['inputToken'],
699
921
  chain: FundingQuotes.ChainRef,
@@ -718,6 +940,29 @@ function matchesSquidToken(
718
940
  )
719
941
  }
720
942
 
943
+ function matchesSymbiosisToken(
944
+ token: z.output<typeof schema.SymbiosisToken>,
945
+ options: matchesSymbiosisToken.Options,
946
+ ) {
947
+ return (
948
+ token.address.toLowerCase() === options.address.toLowerCase() &&
949
+ token.chainId === options.chainId &&
950
+ token.decimals === options.decimals
951
+ )
952
+ }
953
+
954
+ declare namespace matchesSymbiosisToken {
955
+ /** Expected provider token identity. */
956
+ type Options = {
957
+ /** Provider-specific token address. */
958
+ address: string
959
+ /** Provider-specific chain id. */
960
+ chainId: number
961
+ /** Token decimal precision. */
962
+ decimals: number
963
+ }
964
+ }
965
+
721
966
  function eip155ChainId(chain: { id: string; kind: string }) {
722
967
  if (chain.kind !== 'evm') throw new UnsupportedProviderChainError()
723
968
  const match = /^eip155:(\d+)$/.exec(chain.id)
@@ -785,8 +1030,12 @@ function assertAcrossConfiguration(options: across.Options) {
785
1030
  }
786
1031
 
787
1032
  function assertSquidConfiguration(options: squid.Options) {
788
- if (options.integratorId.length === 0 || !/^0x[\da-fA-F]{40}$/.test(options.userAddress))
789
- throw new ProviderConfigurationError()
1033
+ if (options.integratorId.length === 0) throw new ProviderConfigurationError()
1034
+ assertUserAddress(options.userAddress)
1035
+ }
1036
+
1037
+ function assertUserAddress(userAddress: string) {
1038
+ if (!/^0x[\da-fA-F]{40}$/.test(userAddress)) throw new ProviderConfigurationError()
790
1039
  }
791
1040
 
792
1041
  class ProviderConfigurationError extends Error {
@@ -797,6 +1046,10 @@ class ProviderPayloadError extends Error {
797
1046
  override name = 'FundingQuoteProviders.ProviderPayloadError'
798
1047
  }
799
1048
 
1049
+ class ProviderRateLimitError extends Error {
1050
+ override name = 'FundingQuoteProviders.ProviderRateLimitError'
1051
+ }
1052
+
800
1053
  class ProviderResponseError extends Error {
801
1054
  override name = 'FundingQuoteProviders.ProviderResponseError'
802
1055
 
@@ -0,0 +1,118 @@
1
+ import * as FxOracle from './FxOracle.js'
2
+
3
+ const set = {
4
+ asOf: '2026-01-01',
5
+ base: 'EUR',
6
+ rates: { AUD: '1.6', USD: '1.25' },
7
+ } satisfies FxOracle.RateSet
8
+
9
+ function feedUrl(usd: string) {
10
+ const body = `<Envelope><Cube><Cube time="2026-01-01"><Cube currency="AUD" rate="1.6"/><Cube currency="USD" rate="${usd}"/></Cube></Cube></Envelope>`
11
+ return `data:application/xml,${encodeURIComponent(body)}`
12
+ }
13
+
14
+ describe('from', () => {
15
+ test('applies the default ttl and preserves overrides', () => {
16
+ const oracle = FxOracle.from({ name: 'test', rates: async () => set })
17
+ expect(oracle.name).toBe('test')
18
+ expect(oracle.ttl).toMatchInlineSnapshot(`3600000`)
19
+ expect(FxOracle.from({ name: 'test', rates: async () => set, ttl: 5 }).ttl).toBe(5)
20
+ })
21
+
22
+ test('resolves the definition rates', async () => {
23
+ const oracle = FxOracle.from({ name: 'test', rates: async () => set })
24
+ await expect(oracle.rates()).resolves.toEqual(set)
25
+ })
26
+
27
+ test('rethrows rates failures as RatesError', async () => {
28
+ const oracle = FxOracle.from({
29
+ name: 'test',
30
+ rates: async () => {
31
+ throw new Error('boom')
32
+ },
33
+ })
34
+ const error = await oracle.rates().catch((error: unknown) => error)
35
+ expect(error).toBeInstanceOf(FxOracle.RatesError)
36
+ expect(error).toMatchInlineSnapshot(
37
+ `[FxOracle.RatesError: FX oracle "test" failed to produce rates]`,
38
+ )
39
+ expect((error as Error).cause).toMatchInlineSnapshot(`[Error: boom]`)
40
+ })
41
+ })
42
+
43
+ describe('ecb', () => {
44
+ test('fetches and parses the daily reference rates', async () => {
45
+ const rates = await FxOracle.ecb({ url: feedUrl('1.25') }).rates()
46
+ expect(rates).toMatchInlineSnapshot(`
47
+ {
48
+ "asOf": "2026-01-01",
49
+ "base": "EUR",
50
+ "rates": {
51
+ "AUD": "1.6",
52
+ "USD": "1.25",
53
+ },
54
+ }
55
+ `)
56
+ })
57
+
58
+ test.each(['', '-1.25', '1..25'])('rejects malformed rate %j', async (value) => {
59
+ const error = await FxOracle.ecb({ url: feedUrl(value) })
60
+ .rates()
61
+ .catch((error: unknown) => error)
62
+ expect(error).toBeInstanceOf(FxOracle.RatesError)
63
+ expect((error as Error).cause).toMatchInlineSnapshot(
64
+ `[FxOracle.ParseError: Rate feed returned an unrecognized payload]`,
65
+ )
66
+ })
67
+
68
+ test('rejects non-positive rates', async () => {
69
+ const error = await FxOracle.ecb({ url: feedUrl('0.0') })
70
+ .rates()
71
+ .catch((error: unknown) => error)
72
+ expect(error).toBeInstanceOf(FxOracle.RatesError)
73
+ expect((error as Error).cause).toMatchInlineSnapshot(
74
+ `[FxOracle.ParseError: Rate feed returned an unrecognized payload]`,
75
+ )
76
+ })
77
+
78
+ test('rejects rates below fixed-point precision', async () => {
79
+ const error = await FxOracle.ecb({ url: feedUrl('0.0000000000000000001') })
80
+ .rates()
81
+ .catch((error: unknown) => error)
82
+ expect(error).toBeInstanceOf(FxOracle.RatesError)
83
+ expect((error as Error).cause).toMatchInlineSnapshot(
84
+ `[FxOracle.ParseError: Rate feed returned an unrecognized payload]`,
85
+ )
86
+ })
87
+
88
+ test('rejects with RatesError when the feed is unreachable', async () => {
89
+ const oracle = FxOracle.ecb({ url: 'http://127.0.0.1:1' })
90
+ const error = await oracle.rates().catch((error: unknown) => error)
91
+ expect(error).toBeInstanceOf(FxOracle.RatesError)
92
+ expect(error).toMatchInlineSnapshot(
93
+ `[FxOracle.RatesError: FX oracle "ecb" failed to produce rates]`,
94
+ )
95
+ })
96
+ })
97
+
98
+ describe('rate', () => {
99
+ test('derives cross rates through the base', () => {
100
+ expect(FxOracle.rate(set, { from: 'USD', to: 'AUD' })).toMatchInlineSnapshot(
101
+ `1280000000000000000n`,
102
+ )
103
+ expect(FxOracle.rate(set, { from: 'EUR', to: 'USD' })).toMatchInlineSnapshot(
104
+ `1250000000000000000n`,
105
+ )
106
+ expect(FxOracle.rate(set, { from: 'USD', to: 'EUR' })).toMatchInlineSnapshot(
107
+ `800000000000000000n`,
108
+ )
109
+ expect(FxOracle.rate(set, { from: 'USD', to: 'USD' })).toMatchInlineSnapshot(
110
+ `1000000000000000000n`,
111
+ )
112
+ })
113
+
114
+ test('returns undefined for currencies the set does not price', () => {
115
+ expect(FxOracle.rate(set, { from: 'BTC', to: 'USD' })).toBeUndefined()
116
+ expect(FxOracle.rate(set, { from: 'USD', to: 'XXX' })).toBeUndefined()
117
+ })
118
+ })
@@ -0,0 +1,153 @@
1
+ import { Value as core_Value } from 'ox'
2
+
3
+ import * as Ttl from '../../internal/Ttl.js'
4
+
5
+ /** A pluggable fiat FX rate oracle consumed by the valuation endpoint. */
6
+ export type Oracle = {
7
+ /** Oracle identifier, surfaced as the valuation's `pricing.source` (e.g. `ecb`). */
8
+ name: string
9
+ /** Fetches the oracle's current rate set. */
10
+ rates: () => Promise<RateSet>
11
+ /** Cache lifetime for a fetched rate set, in milliseconds. */
12
+ ttl: number
13
+ }
14
+
15
+ /** One oracle-published set of exchange rates quoted against a base currency. */
16
+ export type RateSet = {
17
+ /** Publication date of the rate set (e.g. `2026-07-20`). */
18
+ asOf: string
19
+ /** Currency the rates are quoted against (e.g. `EUR`). */
20
+ base: string
21
+ /** Currency code to units of that currency per one `base` unit, as decimal strings. */
22
+ rates: Record<string, string>
23
+ }
24
+
25
+ /** Fixed-point scale (decimal places) of cross rates returned by {@link rate}. */
26
+ export const precision = 18
27
+
28
+ /** Canonical ECB daily reference-rate feed (public, no key required). */
29
+ export const ecbUrl = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
30
+
31
+ /**
32
+ * Normalizes an oracle definition into an {@link Oracle}: applies the default
33
+ * `ttl` and rethrows `rates()` failures as {@link RatesError}.
34
+ */
35
+ export function from(oracle: from.Value): Oracle {
36
+ return {
37
+ name: oracle.name,
38
+ rates: async () => {
39
+ try {
40
+ return await oracle.rates()
41
+ } catch (cause) {
42
+ throw new RatesError(oracle.name, { cause })
43
+ }
44
+ },
45
+ ttl: oracle.ttl ?? Ttl.hours(1),
46
+ }
47
+ }
48
+
49
+ export declare namespace from {
50
+ /** Oracle definition accepted by {@link from}. */
51
+ type Value = Pick<Oracle, 'name' | 'rates'> & {
52
+ /** Cache lifetime for a fetched rate set, in milliseconds. @default `Ttl.hours(1)` */
53
+ ttl?: number | undefined
54
+ }
55
+ }
56
+
57
+ /**
58
+ * European Central Bank euro reference rates: one EUR-based set covering ~30
59
+ * currencies, published once per working day.
60
+ */
61
+ export function ecb(options: ecb.Options = {}): Oracle {
62
+ const { fetch = globalThis.fetch, url = ecbUrl } = options
63
+ return from({
64
+ name: 'ecb',
65
+ rates: async () => {
66
+ const response = await fetch(url, {
67
+ headers: { accept: 'application/xml' },
68
+ signal: AbortSignal.timeout(10_000),
69
+ })
70
+ if (!response.ok) throw new ResponseError(response.status)
71
+ const text = await response.text()
72
+
73
+ // The daily file is a flat, stable XML shape; extract the `Cube`
74
+ // attributes directly rather than pulling in an XML parser.
75
+ const asOf = text.match(/<Cube time=['"](\d{4}-\d{2}-\d{2})['"]/)?.[1]
76
+ const rates: Record<string, string> = {}
77
+ for (const match of text.matchAll(
78
+ /<Cube currency=['"]([A-Z]{3})['"] rate=['"]([^'"]*)['"]/g,
79
+ )) {
80
+ const [, code, value] = match
81
+ if (
82
+ !code ||
83
+ !value ||
84
+ !/^\d+(?:\.\d+)?$/.test(value) ||
85
+ core_Value.from(value, precision) <= 0n
86
+ )
87
+ throw new ParseError()
88
+ rates[code] = value
89
+ }
90
+ if (!asOf || rates['USD'] === undefined) throw new ParseError()
91
+ return { asOf, base: 'EUR', rates }
92
+ },
93
+ })
94
+ }
95
+
96
+ export declare namespace ecb {
97
+ /** Options for the ECB oracle. */
98
+ type Options = {
99
+ /** Fetch implementation used to retrieve the feed. @default `globalThis.fetch` */
100
+ fetch?: typeof globalThis.fetch | undefined
101
+ /** Feed URL. @default {@link ecbUrl} */
102
+ url?: string | undefined
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Derives the cross rate from one currency to another through the set's base,
108
+ * scaled to {@link precision} decimal places. Returns `undefined` when the set
109
+ * prices neither leg of a currency.
110
+ */
111
+ export function rate(set: RateSet, options: rate.Options): bigint | undefined {
112
+ const scaled = (code: string) => {
113
+ if (code === set.base) return 10n ** BigInt(precision)
114
+ const value = set.rates[code]
115
+ return value === undefined ? undefined : core_Value.from(value, precision)
116
+ }
117
+ const source = scaled(options.from)
118
+ const target = scaled(options.to)
119
+ if (source === undefined || target === undefined || source === 0n) return undefined
120
+ return (target * 10n ** BigInt(precision)) / source
121
+ }
122
+
123
+ export declare namespace rate {
124
+ /** Options for deriving a cross rate. */
125
+ type Options = {
126
+ /** Currency code to convert from. */
127
+ from: string
128
+ /** Currency code to convert to. */
129
+ to: string
130
+ }
131
+ }
132
+
133
+ /** Thrown when an oracle fails to produce its rate set. */
134
+ export class RatesError extends Error {
135
+ override name = 'FxOracle.RatesError'
136
+ constructor(oracle: string, options: { cause: unknown }) {
137
+ super(`FX oracle "${oracle}" failed to produce rates`, options)
138
+ }
139
+ }
140
+
141
+ class ParseError extends Error {
142
+ override name = 'FxOracle.ParseError'
143
+ constructor() {
144
+ super('Rate feed returned an unrecognized payload')
145
+ }
146
+ }
147
+
148
+ class ResponseError extends Error {
149
+ override name = 'FxOracle.ResponseError'
150
+ constructor(status: number) {
151
+ super(`Rate feed request failed with status ${status}`)
152
+ }
153
+ }
@@ -1,3 +1,4 @@
1
1
  export { data } from './App.js'
2
2
  export * as FundingQuoteProviders from './FundingQuoteProviders.js'
3
+ export * as FxOracle from './FxOracle.js'
3
4
  export * as Sponsorships from '../relay/Sponsorships.js'