@oracle-agent/oracle 0.11.0 → 0.12.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 (308) hide show
  1. package/LICENSE +115 -201
  2. package/README.md +66 -84
  3. package/SECURITY.md +37 -12
  4. package/SETUP.md +207 -272
  5. package/dist/action-receipts.mjs +1 -0
  6. package/dist/action-semantics.mjs +1 -0
  7. package/dist/address-book.mjs +2 -0
  8. package/dist/bin/desk-server.mjs +14 -0
  9. package/dist/bin/oracle-data-mcp.mjs +8 -0
  10. package/dist/bin/oracle-init.mjs +24 -0
  11. package/dist/bin/oracle-public-server.mjs +5 -0
  12. package/dist/bin/oracle-route.mjs +22 -0
  13. package/dist/bin/oracle-scan.mjs +18 -0
  14. package/dist/bin/oracle-upgrade.mjs +21 -0
  15. package/dist/bin/oracle.mjs +68 -0
  16. package/dist/chains.mjs +1 -0
  17. package/dist/cli/commands/auth.mjs +16 -0
  18. package/dist/cli/commands/bootstrap.mjs +18 -0
  19. package/dist/cli/commands/chain.mjs +21 -0
  20. package/dist/cli/commands/chat.mjs +35 -0
  21. package/dist/cli/commands/credential.mjs +1 -0
  22. package/dist/cli/commands/data-mcp.mjs +2 -0
  23. package/dist/cli/commands/data.mjs +12 -0
  24. package/dist/cli/commands/doctor.mjs +5 -0
  25. package/dist/cli/commands/gate.mjs +13 -0
  26. package/dist/cli/commands/help.mjs +1 -0
  27. package/dist/cli/commands/init.mjs +3 -0
  28. package/dist/cli/commands/mcp.mjs +33 -0
  29. package/dist/cli/commands/model.mjs +44 -0
  30. package/dist/cli/commands/prepare.mjs +2 -0
  31. package/dist/cli/commands/public.mjs +3 -0
  32. package/dist/cli/commands/route.mjs +2 -0
  33. package/dist/cli/commands/runner.mjs +1 -0
  34. package/dist/cli/commands/scan.mjs +2 -0
  35. package/dist/cli/commands/setup.mjs +34 -0
  36. package/dist/cli/commands/sign.mjs +4 -0
  37. package/dist/cli/commands/signer.mjs +1 -0
  38. package/dist/cli/commands/upgrade.mjs +2 -0
  39. package/dist/cli/commands/vault.mjs +1 -0
  40. package/dist/cli/commands/version.mjs +2 -0
  41. package/dist/data/desk-data.mjs +12 -0
  42. package/dist/index.mjs +17 -0
  43. package/dist/nft-gas-war-guard.mjs +1 -0
  44. package/dist/onboarding/harness-configs.mjs +4 -0
  45. package/dist/portfolio-risk.mjs +1 -0
  46. package/dist/prepare-envelope.mjs +1 -0
  47. package/dist/public-control/policy-schema.mjs +1 -0
  48. package/dist/router/index.mjs +2 -0
  49. package/dist/scanner/index.mjs +4 -0
  50. package/dist/signals/index.mjs +1 -0
  51. package/dist/watch-preferences.mjs +1 -0
  52. package/package.json +27 -65
  53. package/public/oracle-splash/index.html +11 -1
  54. package/CONTRIBUTING.md +0 -98
  55. package/bin/desk-server.mjs +0 -440
  56. package/bin/oracle-data-mcp.mjs +0 -1019
  57. package/bin/oracle-init.mjs +0 -496
  58. package/bin/oracle-public-server.mjs +0 -36
  59. package/bin/oracle-route.mjs +0 -254
  60. package/bin/oracle-scan.mjs +0 -192
  61. package/bin/oracle-upgrade.mjs +0 -42
  62. package/bin/oracle.mjs +0 -33
  63. package/docs/adding-a-chain.md +0 -229
  64. package/docs/architecture.md +0 -138
  65. package/docs/cli.md +0 -47
  66. package/docs/connectors.md +0 -13
  67. package/docs/oracle-pack-standard.md +0 -29
  68. package/docs/profiles.md +0 -151
  69. package/docs/public-surface.md +0 -133
  70. package/examples/add-a-chain.mjs +0 -65
  71. package/examples/oracle-pack-template.mjs +0 -38
  72. package/examples/research-a-token.mjs +0 -70
  73. package/plugins/oracle-owner-gate/__init__.py +0 -227
  74. package/plugins/oracle-owner-gate/plugin.yaml +0 -9
  75. package/profiles/_template/SOUL.md +0 -54
  76. package/profiles/_template/profile.json +0 -22
  77. package/profiles/bitcoin-agent/SOUL.md +0 -31
  78. package/profiles/bitcoin-agent/profile.json +0 -32
  79. package/profiles/hyperliquid-agent/SOUL.md +0 -34
  80. package/profiles/hyperliquid-agent/profile.json +0 -37
  81. package/profiles/oracle/SOUL.md +0 -107
  82. package/profiles/oracle/profile.json +0 -39
  83. package/profiles/polymarket-agent/SOUL.md +0 -35
  84. package/profiles/polymarket-agent/profile.json +0 -34
  85. package/profiles/profile.schema.json +0 -90
  86. package/profiles/protocol-builder/SOUL.md +0 -57
  87. package/profiles/protocol-builder/profile.json +0 -39
  88. package/profiles/robinhood-agent/SOUL.md +0 -53
  89. package/profiles/robinhood-agent/profile.json +0 -40
  90. package/profiles/solana-agent/SOUL.md +0 -37
  91. package/profiles/solana-agent/profile.json +0 -37
  92. package/profiles/stable-agent/SOUL.md +0 -43
  93. package/profiles/stable-agent/profile.json +0 -37
  94. package/protocols/templates/safe-erc20/SECURITY.md +0 -16
  95. package/protocols/templates/safe-erc20/foundry.toml +0 -12
  96. package/protocols/templates/safe-erc20/remappings.txt +0 -2
  97. package/protocols/templates/safe-erc20/src/SafeERC20.sol +0 -53
  98. package/protocols/templates/safe-erc20/test/SafeERC20.t.sol +0 -72
  99. package/scripts/adversarial-bench.mjs +0 -114
  100. package/scripts/build-inscription.py +0 -230
  101. package/scripts/check-doc-drift.mjs +0 -123
  102. package/scripts/check-test-count.mjs +0 -105
  103. package/scripts/e2e-hl-markets.mjs +0 -21
  104. package/scripts/e2e-hl-perps.mjs +0 -48
  105. package/scripts/e2e-hypercore-staking.mjs +0 -128
  106. package/scripts/e2e-solana-bitcoin.mjs +0 -183
  107. package/scripts/protocol-template-gate.mjs +0 -15
  108. package/scripts/public-api-scan.mjs +0 -23
  109. package/scripts/secret-scan.mjs +0 -181
  110. package/scripts/verify-v3-venues.mjs +0 -192
  111. package/skills/balance/SKILL.md +0 -176
  112. package/skills/oracle-action-semantics/SKILL.md +0 -40
  113. package/skills/oracle-best-execution/SKILL.md +0 -127
  114. package/skills/oracle-bitcoin/SKILL.md +0 -53
  115. package/skills/oracle-chain-graphs-telegram-cards/SKILL.md +0 -59
  116. package/skills/oracle-chat/SKILL.md +0 -61
  117. package/skills/oracle-chat/chain.SKILL.md +0 -31
  118. package/skills/oracle-chat/setup.SKILL.md +0 -37
  119. package/skills/oracle-circuit-breaker/SKILL.md +0 -51
  120. package/skills/oracle-contract-research/SKILL.md +0 -55
  121. package/skills/oracle-desk/SKILL.md +0 -58
  122. package/skills/oracle-dex-launch/SKILL.md +0 -38
  123. package/skills/oracle-grants/SKILL.md +0 -69
  124. package/skills/oracle-hypercore-staking/SKILL.md +0 -57
  125. package/skills/oracle-hyperliquid/SKILL.md +0 -56
  126. package/skills/oracle-meme-token-sniper/SKILL.md +0 -73
  127. package/skills/oracle-multichain-nft-launch/SKILL.md +0 -338
  128. package/skills/oracle-multichain-token-launch/SKILL.md +0 -300
  129. package/skills/oracle-nft-gacha-launch/SKILL.md +0 -48
  130. package/skills/oracle-nft-mint-gas-war/SKILL.md +0 -63
  131. package/skills/oracle-polymarket/SKILL.md +0 -60
  132. package/skills/oracle-protocol-builder/SKILL.md +0 -59
  133. package/skills/oracle-protocol-security/SKILL.md +0 -60
  134. package/skills/oracle-public-product/SKILL.md +0 -44
  135. package/skills/oracle-receipts/SKILL.md +0 -52
  136. package/skills/oracle-rfq-tokenized-assets/SKILL.md +0 -69
  137. package/skills/oracle-smart-wallet-scanner/SKILL.md +0 -49
  138. package/skills/oracle-solana/SKILL.md +0 -65
  139. package/skills/oracle-solana-nft/SKILL.md +0 -54
  140. package/skills/oracle-token-research/SKILL.md +0 -67
  141. package/skins/oracle.yaml +0 -56
  142. package/src/action-receipts.mjs +0 -265
  143. package/src/action-semantics.mjs +0 -62
  144. package/src/address-book.mjs +0 -208
  145. package/src/agent-auth.mjs +0 -206
  146. package/src/approval-guard.mjs +0 -282
  147. package/src/attestation-secret.mjs +0 -88
  148. package/src/audit-log.mjs +0 -196
  149. package/src/auth/oauth.mjs +0 -672
  150. package/src/auto-slippage.mjs +0 -378
  151. package/src/capability-posture.mjs +0 -125
  152. package/src/chains.mjs +0 -62
  153. package/src/cli/chain-catalog.mjs +0 -215
  154. package/src/cli/chain-state.mjs +0 -74
  155. package/src/cli/commands/auth.mjs +0 -193
  156. package/src/cli/commands/bootstrap.mjs +0 -87
  157. package/src/cli/commands/chain.mjs +0 -143
  158. package/src/cli/commands/chat.mjs +0 -434
  159. package/src/cli/commands/credential.mjs +0 -9
  160. package/src/cli/commands/data-mcp.mjs +0 -18
  161. package/src/cli/commands/data.mjs +0 -75
  162. package/src/cli/commands/doctor.mjs +0 -136
  163. package/src/cli/commands/help.mjs +0 -7
  164. package/src/cli/commands/init.mjs +0 -27
  165. package/src/cli/commands/mcp.mjs +0 -142
  166. package/src/cli/commands/model.mjs +0 -99
  167. package/src/cli/commands/prepare.mjs +0 -12
  168. package/src/cli/commands/public.mjs +0 -20
  169. package/src/cli/commands/route.mjs +0 -12
  170. package/src/cli/commands/runner.mjs +0 -9
  171. package/src/cli/commands/scan.mjs +0 -12
  172. package/src/cli/commands/setup.mjs +0 -275
  173. package/src/cli/commands/sign.mjs +0 -21
  174. package/src/cli/commands/signer.mjs +0 -9
  175. package/src/cli/commands/upgrade.mjs +0 -12
  176. package/src/cli/commands/vault.mjs +0 -9
  177. package/src/cli/commands/version.mjs +0 -22
  178. package/src/cli/first-run.mjs +0 -23
  179. package/src/cli/kernel.mjs +0 -239
  180. package/src/cli/mcp-targets/chatgpt.mjs +0 -51
  181. package/src/cli/mcp-targets/claude-code.mjs +0 -39
  182. package/src/cli/mcp-targets/claude-desktop.mjs +0 -24
  183. package/src/cli/mcp-targets/codex.mjs +0 -41
  184. package/src/cli/mcp-targets/shared.mjs +0 -61
  185. package/src/cli/messaging-platforms.mjs +0 -209
  186. package/src/cli/model-config.mjs +0 -70
  187. package/src/cli/operator-dispatch.mjs +0 -258
  188. package/src/cli/oracle-harness.py +0 -415
  189. package/src/cli/paths.mjs +0 -83
  190. package/src/cli/runtime.mjs +0 -351
  191. package/src/cli/setup-state.mjs +0 -168
  192. package/src/cli/spawn-child.mjs +0 -43
  193. package/src/data/catalog.mjs +0 -585
  194. package/src/data/desk-data.mjs +0 -695
  195. package/src/data/http.mjs +0 -253
  196. package/src/data/provider-endpoint.mjs +0 -94
  197. package/src/data/providers/aerodrome.mjs +0 -245
  198. package/src/data/providers/balancer.mjs +0 -209
  199. package/src/data/providers/bitcoin-esplora.mjs +0 -216
  200. package/src/data/providers/bitcoin-meta.mjs +0 -378
  201. package/src/data/providers/blockscout.mjs +0 -14
  202. package/src/data/providers/bridges.mjs +0 -242
  203. package/src/data/providers/cowswap.mjs +0 -415
  204. package/src/data/providers/curve.mjs +0 -201
  205. package/src/data/providers/defillama.mjs +0 -88
  206. package/src/data/providers/dexscreener.mjs +0 -43
  207. package/src/data/providers/evm-rpc.mjs +0 -225
  208. package/src/data/providers/geckoterminal.mjs +0 -34
  209. package/src/data/providers/gmx.mjs +0 -496
  210. package/src/data/providers/hl-assets.mjs +0 -165
  211. package/src/data/providers/hl-info.mjs +0 -108
  212. package/src/data/providers/hl-markets.mjs +0 -210
  213. package/src/data/providers/hl-outcome.mjs +0 -61
  214. package/src/data/providers/hl-perps.mjs +0 -387
  215. package/src/data/providers/hl-staking.mjs +0 -353
  216. package/src/data/providers/hl-ws.mjs +0 -119
  217. package/src/data/providers/hyperevm-dex.mjs +0 -49
  218. package/src/data/providers/jupiter-venues.mjs +0 -119
  219. package/src/data/providers/jupiter.mjs +0 -315
  220. package/src/data/providers/lifi.mjs +0 -151
  221. package/src/data/providers/magiceden-sol.mjs +0 -406
  222. package/src/data/providers/morpho.mjs +0 -177
  223. package/src/data/providers/nft-gallery.mjs +0 -163
  224. package/src/data/providers/nft-portfolio.mjs +0 -494
  225. package/src/data/providers/odos.mjs +0 -156
  226. package/src/data/providers/oneinch.mjs +0 -174
  227. package/src/data/providers/opensea-multichain.mjs +0 -136
  228. package/src/data/providers/opensea-nft.mjs +0 -371
  229. package/src/data/providers/paraswap.mjs +0 -118
  230. package/src/data/providers/pendle.mjs +0 -188
  231. package/src/data/providers/poly-clob.mjs +0 -283
  232. package/src/data/providers/poly-public.mjs +0 -96
  233. package/src/data/providers/poly-ws.mjs +0 -103
  234. package/src/data/providers/portfolio-history.mjs +0 -394
  235. package/src/data/providers/portfolio.mjs +0 -594
  236. package/src/data/providers/rfq.mjs +0 -15
  237. package/src/data/providers/rh-agent.mjs +0 -59
  238. package/src/data/providers/satflow.mjs +0 -341
  239. package/src/data/providers/signed-material-guard.mjs +0 -108
  240. package/src/data/providers/solana-rpc.mjs +0 -197
  241. package/src/data/providers/uniswap-v3.mjs +0 -349
  242. package/src/data/providers/uniswap-v4.mjs +0 -414
  243. package/src/data/providers/zerox.mjs +0 -167
  244. package/src/data/public-api-scan.mjs +0 -61
  245. package/src/data/quote-placeholder.mjs +0 -31
  246. package/src/exact-integer.mjs +0 -72
  247. package/src/exec-policy.mjs +0 -455
  248. package/src/flags.mjs +0 -15
  249. package/src/fresh-window.mjs +0 -76
  250. package/src/gmx-attestation.mjs +0 -176
  251. package/src/index.mjs +0 -100
  252. package/src/nft-gas-war-guard.mjs +0 -139
  253. package/src/onboarding/agent-keys.mjs +0 -161
  254. package/src/onboarding/harness-configs.mjs +0 -102
  255. package/src/onboarding/index.mjs +0 -18
  256. package/src/onboarding/tiers.mjs +0 -139
  257. package/src/oracle-env.mjs +0 -47
  258. package/src/portfolio-risk.mjs +0 -170
  259. package/src/prepare-envelope.mjs +0 -204
  260. package/src/profile-upgrade.mjs +0 -277
  261. package/src/protocol-execution.mjs +0 -84
  262. package/src/protocol-templates/gate.mjs +0 -179
  263. package/src/protocol-templates/prepare-deploy.mjs +0 -81
  264. package/src/public-api/buzz-integration.mjs +0 -256
  265. package/src/public-api/connect-agent.mjs +0 -416
  266. package/src/public-api/grants.mjs +0 -142
  267. package/src/public-api/http.mjs +0 -389
  268. package/src/public-control/aa-adapter.mjs +0 -402
  269. package/src/public-control/build-registry.mjs +0 -227
  270. package/src/public-control/bundler-client.mjs +0 -357
  271. package/src/public-control/grant-indexer.mjs +0 -296
  272. package/src/public-control/policy-render.mjs +0 -69
  273. package/src/public-control/policy-schema.mjs +0 -318
  274. package/src/public-control/runtime-config.mjs +0 -275
  275. package/src/public-control/session-key-model.mjs +0 -374
  276. package/src/public-control/session-orchestrator.mjs +0 -412
  277. package/src/rfq/intent.mjs +0 -180
  278. package/src/rfq/sources.mjs +0 -181
  279. package/src/route-attestation.mjs +0 -132
  280. package/src/router/best-execution.mjs +0 -326
  281. package/src/router/index.mjs +0 -226
  282. package/src/router/prepare-bridge.mjs +0 -289
  283. package/src/router/prepare-route.mjs +0 -398
  284. package/src/router/proposal.mjs +0 -311
  285. package/src/router/risk-classifier.mjs +0 -119
  286. package/src/router/route-sources.mjs +0 -364
  287. package/src/scanner/chains.config.mjs +0 -430
  288. package/src/scanner/contract.mjs +0 -270
  289. package/src/scanner/evm-scanner.mjs +0 -394
  290. package/src/scanner/index.mjs +0 -9
  291. package/src/scanner/v2-venue.mjs +0 -335
  292. package/src/scanner/v3-venue.mjs +0 -290
  293. package/src/scopes.mjs +0 -44
  294. package/src/sell-simulation.mjs +0 -167
  295. package/src/signals/engine.mjs +0 -146
  296. package/src/signals/index.mjs +0 -1
  297. package/src/token-transfer-guard.mjs +0 -188
  298. package/src/tui/app.mjs +0 -438
  299. package/src/tui/backend.mjs +0 -145
  300. package/src/tui/format.mjs +0 -272
  301. package/src/tui/gateway-client.mjs +0 -177
  302. package/src/tui/input.mjs +0 -501
  303. package/src/tui/renderer.mjs +0 -443
  304. package/src/tui/standalone-client.mjs +0 -666
  305. package/src/tui/theme.mjs +0 -81
  306. package/src/vault-attestation.mjs +0 -146
  307. package/src/venues.mjs +0 -206
  308. package/src/watch-preferences.mjs +0 -85
@@ -1,226 +0,0 @@
1
- // Public entry point: find the best swap or bridge route.
2
- //
3
- // Returns a ranked comparison, not just a winner. Showing the runners-up and the
4
- // spread is what lets a caller sanity-check the result instead of trusting it -- and
5
- // a one-source "best route" is not a comparison at all, so that case is labelled
6
- // rather than presented as if it beat something.
7
-
8
- import {
9
- gatherRoutes, rankRoutes, QUALITY, measurePriceImpact, applyImpactCeiling,
10
- } from "./best-execution.mjs";
11
- import { swapCandidates, bridgeCandidates, nativeUsd } from "./route-sources.mjs";
12
- import { llamaPrices } from "../data/providers/defillama.mjs";
13
-
14
- /** DefiLlama key for an ERC-20 on a given chain. */
15
- const LLAMA_CHAIN = {
16
- 1: "ethereum",
17
- 10: "optimism",
18
- 56: "bsc",
19
- 137: "polygon",
20
- 8453: "base",
21
- 42161: "arbitrum",
22
- 43114: "avax",
23
- };
24
-
25
- async function destPrice(chainId, token, opts) {
26
- const chain = LLAMA_CHAIN[Number(chainId)];
27
- if (!chain) return { priceUsd: null, decimals: null };
28
- const key = `${chain}:${token}`;
29
- try {
30
- const res = await llamaPrices([key], opts);
31
- const hit = res?.coins?.[key];
32
- const p = Number(hit?.price);
33
- const d = Number(hit?.decimals);
34
- return {
35
- priceUsd: Number.isFinite(p) ? p : null,
36
- decimals: Number.isFinite(d) ? d : null,
37
- };
38
- } catch {
39
- return { priceUsd: null, decimals: null };
40
- }
41
- }
42
-
43
- /**
44
- * Best same-chain swap route.
45
- *
46
- * @param {object} p
47
- * @param {number} p.chainId
48
- * @param {string} p.tokenIn
49
- * @param {string} p.tokenOut
50
- * @param {string|bigint} p.amountIn raw units of tokenIn
51
- * @param {string} [p.taker] address used for quoting only
52
- * @param {number} [p.decimalsIn] enables ParaSwap
53
- * @param {number} [p.decimalsOut] enables ParaSwap + net ranking
54
- */
55
- export async function bestSwapRoute(p, opts = {}) {
56
- const { chainId, tokenIn, tokenOut, amountIn } = p;
57
- if (chainId == null || !tokenIn || !tokenOut || amountIn == null) {
58
- throw new Error("bestSwapRoute requires chainId, tokenIn, tokenOut, amountIn");
59
- }
60
-
61
- // Prices and quotes run CONCURRENTLY, not one after the other.
62
- //
63
- // The obvious shape -- await the prices, then fan out the quotes -- adds the price
64
- // latency to every single comparison. Measured on Base: ~890ms of DefiLlama in
65
- // front of ~3.3s of quotes, for 4.7s total when the true floor is 3.3s.
66
- //
67
- // Neither price is needed to ASK for a quote:
68
- // * destPrice is only used at RANK time, after every quote has returned
69
- // * nativePriceUsd is only used by the 0x adapter to convert its native-wei gas
70
- // figure, so it is passed as a PROMISE and awaited inside that one adapter
71
- //
72
- // So the whole price fetch hides behind the slowest quote instead of preceding it.
73
- const nativePricePromise = nativeUsd(chainId, opts);
74
- const destPromise = destPrice(chainId, tokenOut, opts);
75
-
76
- const candidates = swapCandidates({
77
- ...p,
78
- nativePriceUsd: nativePricePromise,
79
- decimalsOut: p.decimalsOut,
80
- destDecimalsPromise: destPromise,
81
- opts,
82
- });
83
-
84
- const [routes, dest] = await Promise.all([
85
- gatherRoutes(candidates, { timeoutMs: opts.timeoutMs ?? 12_000 }),
86
- destPromise,
87
- ]);
88
-
89
- // Swallow an unused rejection so a failed price lookup cannot surface as an
90
- // unhandled rejection when no adapter happened to await it.
91
- nativePricePromise.catch(() => {});
92
-
93
- const ranked = rankRoutes(routes, {
94
- destPriceUsd: dest.priceUsd,
95
- destDecimals: p.decimalsOut ?? dest.decimals,
96
- });
97
-
98
- // Liquidity check. Ranking alone will happily crown a quote taken against a
99
- // drained pool, because a thin venue returns a well-formed number rather than
100
- // an error. Re-quote the winner at 1/1000th size and compare per-unit price:
101
- // real slippage decays smoothly, a dead pool falls off a cliff.
102
- //
103
- // Opt out with `maxImpactPct: null` for callers that genuinely want the raw
104
- // ranking (analytics, spread display) rather than an executable route.
105
- const maxImpactPct = opts.maxImpactPct === undefined ? 25 : opts.maxImpactPct;
106
- if (maxImpactPct != null && ranked.best) {
107
- const winner = ranked.best.source;
108
- const probe = async (amt) => {
109
- const sub = swapCandidates({
110
- ...p,
111
- amountIn: amt.toString(),
112
- nativePriceUsd: nativePricePromise,
113
- decimalsOut: p.decimalsOut,
114
- destDecimalsPromise: destPromise,
115
- opts,
116
- }).filter((c) => c.source === winner);
117
- if (!sub.length) return null;
118
- const got = await gatherRoutes(sub, { timeoutMs: opts.timeoutMs ?? 12_000 });
119
- return got?.[0]?.amountOut ?? null;
120
- };
121
-
122
- const impact = await measurePriceImpact(probe, amountIn);
123
- if (impact) {
124
- const guarded = applyImpactCeiling(ranked, { [winner]: impact }, { maxImpactPct });
125
- return {
126
- kind: "swap",
127
- chainId,
128
- tokenIn,
129
- tokenOut,
130
- amountIn: String(amountIn),
131
- ...guarded,
132
- priceImpact: { [winner]: Number(impact.impactPct.toFixed(2)) },
133
- };
134
- }
135
- }
136
-
137
- return {
138
- kind: "swap",
139
- chainId,
140
- tokenIn,
141
- tokenOut,
142
- amountIn: String(amountIn),
143
- ...ranked,
144
- sourcesTried: candidates.length,
145
- sourcesAnswered: routes.filter((r) => r.quality !== QUALITY.FAILED).length,
146
- note: describe(ranked, candidates.length),
147
- };
148
- }
149
-
150
- /**
151
- * Best cross-chain bridge route.
152
- *
153
- * Duration is reported, never folded into the score. A route that saves $2 and takes
154
- * 30 minutes is not strictly better than one costing $2 more that lands in 20
155
- * seconds -- that trade-off belongs to the caller, and burying it in a single number
156
- * would hide the only thing distinguishing two similar quotes.
157
- */
158
- export async function bestBridgeRoute(p, opts = {}) {
159
- const { fromChainId, toChainId, tokenIn, tokenOut, amountIn } = p;
160
- if (fromChainId == null || toChainId == null || !tokenIn || !tokenOut || amountIn == null) {
161
- throw new Error(
162
- "bestBridgeRoute requires fromChainId, toChainId, tokenIn, tokenOut, amountIn",
163
- );
164
- }
165
- if (Number(fromChainId) === Number(toChainId)) {
166
- throw new Error("bestBridgeRoute is for cross-chain; use bestSwapRoute for same-chain");
167
- }
168
-
169
- // Same concurrency rule as swaps: the destination price is only needed at RANK
170
- // time, so fetching it before the quotes would add its latency to every bridge
171
- // comparison for no reason.
172
- const destPromise = destPrice(toChainId, tokenOut, opts);
173
- const candidates = bridgeCandidates({ ...p, opts });
174
- const [routes, dest] = await Promise.all([
175
- gatherRoutes(candidates, { timeoutMs: opts.timeoutMs ?? 15_000 }),
176
- destPromise,
177
- ]);
178
- const ranked = rankRoutes(routes, {
179
- destPriceUsd: dest.priceUsd,
180
- destDecimals: p.decimalsOut ?? dest.decimals,
181
- });
182
-
183
- const durations = ranked.routes
184
- .map((r) => ({ source: r.source, seconds: r.meta?.durationSeconds ?? null }))
185
- .filter((d) => d.seconds != null);
186
-
187
- return {
188
- kind: "bridge",
189
- fromChainId,
190
- toChainId,
191
- tokenIn,
192
- tokenOut,
193
- amountIn: String(amountIn),
194
- ...ranked,
195
- durations,
196
- sourcesTried: candidates.length,
197
- sourcesAnswered: routes.filter((r) => r.quality !== QUALITY.FAILED).length,
198
- note: describe(ranked, candidates.length),
199
- };
200
- }
201
-
202
- function describe(ranked, tried) {
203
- if (!ranked.best) return "no source returned a usable route";
204
- const answered = ranked.routes.length;
205
- if (answered === 1) {
206
- return (
207
- `only 1 of ${tried} sources answered, so this is the ONLY route, not a proven ` +
208
- "best one. Treat it as unbenchmarked."
209
- );
210
- }
211
- if (ranked.improvementBps != null) {
212
- return (
213
- `best of ${answered} routes, ranked on ${ranked.rankedOn}: ` +
214
- `${(ranked.improvementBps / 100).toFixed(2)}% better than the runner-up ` +
215
- `(${ranked.spreadBasis})`
216
- );
217
- }
218
- // No spread claim when the top two are not measured the same way. Saying "X% better"
219
- // off a mismatched pair is worse than saying nothing.
220
- return (
221
- `best of ${answered} routes, ranked on ${ranked.rankedOn}. No spread quoted: the ` +
222
- "top routes report cost differently, so the margin is not comparable."
223
- );
224
- }
225
-
226
- export { QUALITY };
@@ -1,289 +0,0 @@
1
- // Prepare a cross-chain bridge route.
2
- //
3
- // Bridging differs from swapping in ways that matter for a signable artifact:
4
- //
5
- // 1. IT IS OFTEN MULTIPLE TRANSACTIONS. Relay can return an approval and a deposit
6
- // as separate items. A caller handed a bare "transaction" would sign the first
7
- // and believe they were done, leaving funds approved but not bridged. So the
8
- // artifact is always a LIST, and its length is stated.
9
- //
10
- // 2. THE DESTINATION CHAIN IS PART OF THE TRUST DECISION. Every transaction here
11
- // executes on the ORIGIN chain, but the value lands elsewhere. Signing an
12
- // origin-chain tx that credits the wrong destination is unrecoverable, so
13
- // toChainId is echoed alongside the destination address.
14
- //
15
- // 3. TIME IS A REAL COST. A bridge that saves $2 and takes 30 minutes is not
16
- // obviously better than one costing $2 more that lands in 20 seconds. Duration
17
- // is surfaced, never folded into a score.
18
- //
19
- // 4. FINALITY IS NOT ATOMIC. The origin transaction succeeding does NOT mean funds
20
- // arrived. That gap is where users panic, so it is stated explicitly rather than
21
- // left implied.
22
- //
23
- // Custody is unchanged: artifacts only. This module cannot sign or broadcast.
24
-
25
- import { bestBridgeRoute } from "./index.mjs";
26
- import { stampPrepared } from "../prepare-envelope.mjs";
27
-
28
- const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
29
-
30
- /**
31
- * Placeholder addresses: fine for quoting, never for preparing.
32
- * Same reasoning as the swap path -- some venues catch this and some do not.
33
- */
34
- const NON_TAKER_ADDRESSES = new Set(
35
- [
36
- "0x0000000000000000000000000000000000000000",
37
- "0x000000000000000000000000000000000000dead",
38
- "0x0000000000000000000000000000000000000001",
39
- "0x00000000000000000000000000000000000a1ce5",
40
- ].map((a) => a.toLowerCase()),
41
- );
42
-
43
- const DEFAULT_DRIFT_TOLERANCE_BPS = 100;
44
-
45
- const PREPARERS = {
46
- relay: async ({ fromChainId, toChainId, tokenIn, tokenOut, amountIn, taker }, opts) => {
47
- const { relayPrepare } = await import("../data/providers/bridges.mjs");
48
- const p = await relayPrepare(
49
- {
50
- user: taker,
51
- originChainId: fromChainId,
52
- destinationChainId: toChainId,
53
- originCurrency: tokenIn,
54
- destinationCurrency: tokenOut,
55
- amount: String(amountIn),
56
- },
57
- opts,
58
- );
59
- return {
60
- transactions: p.transactions || [],
61
- amountOut: p.quote?.details?.currencyOut?.amount ?? null,
62
- minOut: p.quote?.details?.currencyOut?.minimumAmount ?? null,
63
- durationSeconds: p.quote?.details?.timeEstimate ?? null,
64
- };
65
- },
66
-
67
- lifi: async ({ fromChainId, toChainId, tokenIn, tokenOut, amountIn, taker }, opts) => {
68
- const { lifiPrepare } = await import("../data/providers/lifi.mjs");
69
- const p = await lifiPrepare(
70
- {
71
- fromChain: fromChainId,
72
- toChain: toChainId,
73
- fromToken: tokenIn,
74
- toToken: tokenOut,
75
- fromAmount: amountIn,
76
- fromAddress: taker,
77
- },
78
- opts,
79
- );
80
- // LI.FI returns a single transactionRequest. Normalized into a list so callers
81
- // handle one shape regardless of source -- a single-item list is honest, a bare
82
- // object next to Relay's list is a trap.
83
- return {
84
- transactions: p.transaction ? [p.transaction] : [],
85
- requiresApproval: p.requiresApproval ?? null,
86
- amountOut: p.quote?.estimate?.toAmount ?? null,
87
- minOut: p.quote?.estimate?.toAmountMin ?? null,
88
- durationSeconds: p.quote?.estimate?.executionDuration ?? null,
89
- };
90
- },
91
- };
92
-
93
- export function supportedBridgePreparers() {
94
- return Object.keys(PREPARERS);
95
- }
96
-
97
- /**
98
- * Compare bridge routes, then prepare the winner.
99
- *
100
- * @param {object} p same shape as bestBridgeRoute, plus:
101
- * @param {string} p.taker the real wallet that will sign
102
- * @param {string} [p.source] force a specific source
103
- * @param {number} [p.driftToleranceBps]
104
- */
105
- export async function prepareBestBridgeRoute(p, opts = {}) {
106
- const { taker, fromChainId, toChainId } = p;
107
-
108
- if (!taker) {
109
- throw new Error(
110
- "prepareBestBridgeRoute requires taker: bridge transactions are built FOR an " +
111
- "address, and the quote-only placeholder must never end up in one",
112
- );
113
- }
114
- if (!ADDRESS_RE.test(taker)) {
115
- throw new Error(`taker must be a valid 20-byte address, got "${taker}"`);
116
- }
117
- if (NON_TAKER_ADDRESSES.has(taker.toLowerCase())) {
118
- throw new Error(
119
- `taker "${taker}" is a placeholder/burn address. Bridging to it would send ` +
120
- "funds to an address nobody controls, on a chain where you cannot undo it.",
121
- );
122
- }
123
-
124
- const comparison = await bestBridgeRoute(p, opts);
125
- if (!comparison.best) {
126
- return { ok: false, reason: "no source returned a usable bridge route", comparison };
127
- }
128
-
129
- const chosenSource = p.source || comparison.best.source;
130
- const chosen = comparison.routes.find((r) => r.source === chosenSource);
131
- if (!chosen) {
132
- return { ok: false, reason: `source "${chosenSource}" returned no usable route`, comparison };
133
- }
134
-
135
- const prepare = PREPARERS[chosenSource];
136
- if (!prepare) {
137
- const alt = comparison.routes.find((r) => PREPARERS[r.source] && r.source !== chosenSource);
138
- return {
139
- ok: false,
140
- reason:
141
- `${chosenSource} won the comparison but has no bridge prepare path in this ` +
142
- `build. ` +
143
- (alt
144
- ? `Next preparable route is ${alt.source}; pass source:"${alt.source}".`
145
- : "No route in this comparison can be prepared."),
146
- // Machine-readable siblings of the message above. A caller should not have to
147
- // regex prose to find the fallback -- and the CLI/agent path reads these.
148
- failureKind: "no-prepare-path",
149
- ...(alt ? { alternative: alt.source } : {}),
150
- winner: chosenSource,
151
- comparison,
152
- };
153
- }
154
-
155
- let prepared;
156
- try {
157
- prepared = await prepare(p, opts);
158
- } catch (err) {
159
- const detail =
160
- typeof err?.body === "string" ? err.body : err?.body ? JSON.stringify(err.body) : "";
161
- const msg = [String(err?.message || err), detail].filter(Boolean).join(" :: ");
162
-
163
- const allowanceIssue = /allowance/i.test(msg);
164
- const balanceIssue = !allowanceIssue && /not enough|insufficient|balance/i.test(msg);
165
- const alt = comparison.routes.find((r) => PREPARERS[r.source] && r.source !== chosenSource);
166
-
167
- return {
168
- ok: false,
169
- reason: allowanceIssue
170
- ? `${chosenSource} will not build calldata until the token approval exists ` +
171
- `on-chain. Approve first, then prepare again. Underlying: ${msg}`
172
- : balanceIssue
173
- ? `${chosenSource} refused to build: ${msg}. Quoting works for any address; ` +
174
- `preparing needs the real funded wallet on chain ${fromChainId}.`
175
- : `${chosenSource} bridge prepare failed: ${msg}`,
176
- failureKind: allowanceIssue
177
- ? "approval-required-first"
178
- : balanceIssue
179
- ? "taker-not-funded"
180
- : "provider-error",
181
- ...(alt ? { alternative: alt.source } : {}),
182
- comparison,
183
- };
184
- }
185
-
186
- const txs = prepared.transactions || [];
187
- if (!txs.length) {
188
- return {
189
- ok: false,
190
- reason: `${chosenSource} returned no executable transaction for this route`,
191
- failureKind: "provider-error",
192
- comparison,
193
- };
194
- }
195
-
196
- // Every transaction must execute on the ORIGIN chain. A wrong chainId here means a
197
- // wallet could be prompted on the wrong network, and the user would be signing
198
- // something they cannot reason about.
199
- const wrongChain = txs.filter(
200
- (t) => t.chainId != null && Number(t.chainId) !== Number(fromChainId),
201
- );
202
- if (wrongChain.length) {
203
- return {
204
- ok: false,
205
- reason:
206
- `${chosenSource} returned a transaction for chain ${wrongChain[0].chainId} but ` +
207
- `the origin chain is ${fromChainId}. Refusing to hand over a mismatched ` +
208
- `artifact.`,
209
- failureKind: "chain-mismatch",
210
- comparison,
211
- };
212
- }
213
-
214
- // Drift against the comparison. Same reasoning as swaps: the comparison is stale.
215
- const tolerance = p.driftToleranceBps ?? DEFAULT_DRIFT_TOLERANCE_BPS;
216
- let driftBps = null;
217
- let driftWarning = null;
218
- if (prepared.amountOut != null && chosen.grossOut != null) {
219
- try {
220
- const then = BigInt(chosen.grossOut);
221
- const now = BigInt(prepared.amountOut);
222
- if (then > 0n) {
223
- driftBps = Number(((now - then) * 10_000n) / then);
224
- if (driftBps < -tolerance) {
225
- driftWarning =
226
- `route moved ${(driftBps / 100).toFixed(2)}% against you since the ` +
227
- `comparison (tolerance ${(tolerance / 100).toFixed(2)}%). Re-compare ` +
228
- `before signing.`;
229
- }
230
- }
231
- } catch {
232
- /* no fabricated drift */
233
- }
234
- }
235
-
236
- const warnings = [
237
- ...(driftWarning ? [driftWarning] : []),
238
- ...(comparison.warnings || []),
239
- // The single most common bridging surprise.
240
- `Bridging is NOT atomic: the origin transaction succeeding does not mean funds ` +
241
- `arrived on chain ${toChainId}. Expect ~${
242
- prepared.durationSeconds != null ? `${prepared.durationSeconds}s` : "minutes"
243
- } before the destination credits, and do not re-send if it is pending.`,
244
- ];
245
-
246
- if (txs.length > 1) {
247
- warnings.push(
248
- `This route needs ${txs.length} transactions signed IN ORDER. Signing only the ` +
249
- `first leaves funds approved but not bridged.`,
250
- );
251
- }
252
-
253
- return stampPrepared({
254
- ok: true,
255
- chosen: {
256
- source: chosenSource,
257
- wasWinner: chosenSource === comparison.best.source,
258
- netOut: chosen.netOut,
259
- grossOut: chosen.grossOut,
260
- gasUsd: chosen.gasUsd,
261
- },
262
- artifactKind: "unsigned-transaction-sequence",
263
- fromChainId: Number(fromChainId),
264
- toChainId: Number(toChainId),
265
- transactionCount: txs.length,
266
- transactions: txs,
267
- requiresApproval: prepared.requiresApproval ?? null,
268
- minOut: prepared.minOut ?? null,
269
- durationSeconds: prepared.durationSeconds ?? null,
270
- // Where value goes on the origin chain. The first destination is the bridge entry.
271
- destination: txs[0]?.to ?? null,
272
- unsigned: true,
273
- signedBy: "user-wallet",
274
- driftBps,
275
- warnings,
276
- runnersUp: comparison.routes
277
- .filter((r) => r.source !== chosenSource)
278
- .map((r) => ({ source: r.source, netOut: r.netOut, gasUsd: r.gasUsd })),
279
- comparison: {
280
- rankedOn: comparison.rankedOn,
281
- sourcesAnswered: comparison.sourcesAnswered,
282
- sourcesTried: comparison.sourcesTried,
283
- improvementBps: comparison.improvementBps,
284
- },
285
- note:
286
- `UNSIGNED. ${txs.length} transaction(s) on chain ${fromChainId}, crediting ` +
287
- `chain ${toChainId}. Verify every destination address before signing.`,
288
- }, { provider: "router", kind: "best-bridge" });
289
- }