@elizaos/plugin-steward-app 2.0.3-beta.6 → 2.0.3-beta.7

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 (219) hide show
  1. package/dist/ApprovalQueue.d.ts +18 -0
  2. package/dist/ApprovalQueue.d.ts.map +1 -0
  3. package/dist/ApprovalQueue.js +420 -0
  4. package/dist/ApprovalQueue.js.map +1 -0
  5. package/dist/StewardLogo.d.ts +11 -0
  6. package/dist/StewardLogo.d.ts.map +1 -0
  7. package/dist/StewardLogo.js +36 -0
  8. package/dist/StewardLogo.js.map +1 -0
  9. package/dist/StewardView.d.ts +13 -0
  10. package/dist/StewardView.d.ts.map +1 -0
  11. package/dist/StewardView.helpers.d.ts +15 -0
  12. package/dist/StewardView.helpers.d.ts.map +1 -0
  13. package/dist/StewardView.helpers.js +45 -0
  14. package/dist/StewardView.helpers.js.map +1 -0
  15. package/dist/StewardView.interact.d.ts +2 -0
  16. package/dist/StewardView.interact.d.ts.map +1 -0
  17. package/dist/StewardView.interact.js +54 -0
  18. package/dist/StewardView.interact.js.map +1 -0
  19. package/dist/StewardView.js +249 -0
  20. package/dist/StewardView.js.map +1 -0
  21. package/dist/TransactionHistory.d.ts +22 -0
  22. package/dist/TransactionHistory.d.ts.map +1 -0
  23. package/dist/TransactionHistory.js +361 -0
  24. package/dist/TransactionHistory.js.map +1 -0
  25. package/dist/__fixtures__/steward-sdk-fixtures.d.ts +10 -0
  26. package/dist/__fixtures__/steward-sdk-fixtures.d.ts.map +1 -0
  27. package/dist/__fixtures__/steward-sdk-fixtures.js +60 -0
  28. package/dist/__fixtures__/steward-sdk-fixtures.js.map +1 -0
  29. package/dist/actions/wallet-action-shared.d.ts +15 -0
  30. package/dist/actions/wallet-action-shared.d.ts.map +1 -0
  31. package/dist/actions/wallet-action-shared.js +16 -0
  32. package/dist/actions/wallet-action-shared.js.map +1 -0
  33. package/dist/api/binance-skill-helpers.d.ts +21 -0
  34. package/dist/api/binance-skill-helpers.d.ts.map +1 -0
  35. package/dist/api/binance-skill-helpers.js +790 -0
  36. package/dist/api/binance-skill-helpers.js.map +1 -0
  37. package/dist/api/bsc-trade.d.ts +36 -0
  38. package/dist/api/bsc-trade.d.ts.map +1 -0
  39. package/dist/api/bsc-trade.js +796 -0
  40. package/dist/api/bsc-trade.js.map +1 -0
  41. package/dist/api/trade-safety.d.ts +35 -0
  42. package/dist/api/trade-safety.d.ts.map +1 -0
  43. package/dist/api/trade-safety.js +56 -0
  44. package/dist/api/trade-safety.js.map +1 -0
  45. package/dist/api/tx-service.d.ts +53 -0
  46. package/dist/api/tx-service.d.ts.map +1 -0
  47. package/dist/api/tx-service.js +206 -0
  48. package/dist/api/tx-service.js.map +1 -0
  49. package/dist/api/wallet-bsc-routes.d.ts +63 -0
  50. package/dist/api/wallet-bsc-routes.d.ts.map +1 -0
  51. package/dist/api/wallet-bsc-routes.js +337 -0
  52. package/dist/api/wallet-bsc-routes.js.map +1 -0
  53. package/dist/api/wallet-capability.d.ts +2 -0
  54. package/dist/api/wallet-capability.d.ts.map +1 -0
  55. package/dist/api/wallet-capability.js +15 -0
  56. package/dist/api/wallet-capability.js.map +1 -0
  57. package/dist/api/wallet-dex-prices.d.ts +43 -0
  58. package/dist/api/wallet-dex-prices.d.ts.map +1 -0
  59. package/dist/api/wallet-dex-prices.js +132 -0
  60. package/dist/api/wallet-dex-prices.js.map +1 -0
  61. package/dist/api/wallet-evm-balance.d.ts +72 -0
  62. package/dist/api/wallet-evm-balance.d.ts.map +1 -0
  63. package/dist/api/wallet-evm-balance.js +697 -0
  64. package/dist/api/wallet-evm-balance.js.map +1 -0
  65. package/dist/api/wallet-routes.d.ts +27 -0
  66. package/dist/api/wallet-routes.d.ts.map +1 -0
  67. package/dist/api/wallet-routes.js +556 -0
  68. package/dist/api/wallet-routes.js.map +1 -0
  69. package/dist/api/wallet-rpc.d.ts +73 -0
  70. package/dist/api/wallet-rpc.d.ts.map +1 -0
  71. package/dist/api/wallet-rpc.js +460 -0
  72. package/dist/api/wallet-rpc.js.map +1 -0
  73. package/dist/api/wallet-trade-routes.d.ts +104 -0
  74. package/dist/api/wallet-trade-routes.d.ts.map +1 -0
  75. package/dist/api/wallet-trade-routes.js +353 -0
  76. package/dist/api/wallet-trade-routes.js.map +1 -0
  77. package/dist/api/wallet-trading-profile.d.ts +31 -0
  78. package/dist/api/wallet-trading-profile.d.ts.map +1 -0
  79. package/dist/api/wallet-trading-profile.js +500 -0
  80. package/dist/api/wallet-trading-profile.js.map +1 -0
  81. package/dist/api/wallet.d.ts +60 -0
  82. package/dist/api/wallet.d.ts.map +1 -0
  83. package/dist/api/wallet.js +617 -0
  84. package/dist/api/wallet.js.map +1 -0
  85. package/dist/chain-utils.d.ts +10 -0
  86. package/dist/chain-utils.d.ts.map +1 -0
  87. package/dist/chain-utils.js +81 -0
  88. package/dist/chain-utils.js.map +1 -0
  89. package/dist/components/StewardSpatialView.d.ts +74 -0
  90. package/dist/components/StewardSpatialView.d.ts.map +1 -0
  91. package/dist/components/StewardSpatialView.js +309 -0
  92. package/dist/components/StewardSpatialView.js.map +1 -0
  93. package/dist/index.d.ts +20 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +77 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/plugin.d.ts +21 -0
  98. package/dist/plugin.d.ts.map +1 -0
  99. package/dist/plugin.js +319 -0
  100. package/dist/plugin.js.map +1 -0
  101. package/dist/providers/steward-balance.d.ts +12 -0
  102. package/dist/providers/steward-balance.d.ts.map +1 -0
  103. package/dist/providers/steward-balance.js +85 -0
  104. package/dist/providers/steward-balance.js.map +1 -0
  105. package/dist/providers/steward-receive-address.d.ts +12 -0
  106. package/dist/providers/steward-receive-address.d.ts.map +1 -0
  107. package/dist/providers/steward-receive-address.js +47 -0
  108. package/dist/providers/steward-receive-address.js.map +1 -0
  109. package/dist/register-routes.d.ts +2 -0
  110. package/dist/register-routes.d.ts.map +1 -0
  111. package/dist/register-routes.js +6 -0
  112. package/dist/register-routes.js.map +1 -0
  113. package/dist/register-terminal-view.d.ts +15 -0
  114. package/dist/register-terminal-view.d.ts.map +1 -0
  115. package/dist/register-terminal-view.js +34 -0
  116. package/dist/register-terminal-view.js.map +1 -0
  117. package/dist/routes/steward-bridge.d.ts +202 -0
  118. package/dist/routes/steward-bridge.d.ts.map +1 -0
  119. package/dist/routes/steward-bridge.js +776 -0
  120. package/dist/routes/steward-bridge.js.map +1 -0
  121. package/dist/routes/steward-compat-routes.d.ts +21 -0
  122. package/dist/routes/steward-compat-routes.d.ts.map +1 -0
  123. package/dist/routes/steward-compat-routes.js +350 -0
  124. package/dist/routes/steward-compat-routes.js.map +1 -0
  125. package/dist/routes/wallet-browser-compat-routes.d.ts +6 -0
  126. package/dist/routes/wallet-browser-compat-routes.d.ts.map +1 -0
  127. package/dist/routes/wallet-browser-compat-routes.js +402 -0
  128. package/dist/routes/wallet-browser-compat-routes.js.map +1 -0
  129. package/dist/routes/wallet-bsc-core-routes.d.ts +15 -0
  130. package/dist/routes/wallet-bsc-core-routes.d.ts.map +1 -0
  131. package/dist/routes/wallet-bsc-core-routes.js +59 -0
  132. package/dist/routes/wallet-bsc-core-routes.js.map +1 -0
  133. package/dist/routes/wallet-compat-routes.d.ts +13 -0
  134. package/dist/routes/wallet-compat-routes.d.ts.map +1 -0
  135. package/dist/routes/wallet-compat-routes.js +206 -0
  136. package/dist/routes/wallet-compat-routes.js.map +1 -0
  137. package/dist/routes/wallet-core-routes.d.ts +16 -0
  138. package/dist/routes/wallet-core-routes.d.ts.map +1 -0
  139. package/dist/routes/wallet-core-routes.js +48 -0
  140. package/dist/routes/wallet-core-routes.js.map +1 -0
  141. package/dist/routes/wallet-trade-compat-routes.d.ts +11 -0
  142. package/dist/routes/wallet-trade-compat-routes.d.ts.map +1 -0
  143. package/dist/routes/wallet-trade-compat-routes.js +570 -0
  144. package/dist/routes/wallet-trade-compat-routes.js.map +1 -0
  145. package/dist/security/hydrate-wallet-keys-from-platform-store.d.ts +7 -0
  146. package/dist/security/hydrate-wallet-keys-from-platform-store.d.ts.map +1 -0
  147. package/dist/security/hydrate-wallet-keys-from-platform-store.js +43 -0
  148. package/dist/security/hydrate-wallet-keys-from-platform-store.js.map +1 -0
  149. package/dist/security/wallet-os-store-actions.d.ts +14 -0
  150. package/dist/security/wallet-os-store-actions.d.ts.map +1 -0
  151. package/dist/security/wallet-os-store-actions.js +63 -0
  152. package/dist/security/wallet-os-store-actions.js.map +1 -0
  153. package/dist/services/steward-credentials.d.ts +2 -0
  154. package/dist/services/steward-credentials.d.ts.map +1 -0
  155. package/dist/services/steward-credentials.js +2 -0
  156. package/dist/services/steward-credentials.js.map +1 -0
  157. package/dist/services/steward-evm-account.d.ts +75 -0
  158. package/dist/services/steward-evm-account.d.ts.map +1 -0
  159. package/dist/services/steward-evm-account.js +279 -0
  160. package/dist/services/steward-evm-account.js.map +1 -0
  161. package/dist/services/steward-evm-bridge.d.ts +36 -0
  162. package/dist/services/steward-evm-bridge.d.ts.map +1 -0
  163. package/dist/services/steward-evm-bridge.js +78 -0
  164. package/dist/services/steward-evm-bridge.js.map +1 -0
  165. package/dist/services/steward-sidecar/health-check.d.ts +2 -0
  166. package/dist/services/steward-sidecar/health-check.d.ts.map +1 -0
  167. package/dist/services/steward-sidecar/health-check.js +2 -0
  168. package/dist/services/steward-sidecar/health-check.js.map +1 -0
  169. package/dist/services/steward-sidecar/helpers.d.ts +2 -0
  170. package/dist/services/steward-sidecar/helpers.d.ts.map +1 -0
  171. package/dist/services/steward-sidecar/helpers.js +2 -0
  172. package/dist/services/steward-sidecar/helpers.js.map +1 -0
  173. package/dist/services/steward-sidecar/process-management.d.ts +2 -0
  174. package/dist/services/steward-sidecar/process-management.d.ts.map +1 -0
  175. package/dist/services/steward-sidecar/process-management.js +2 -0
  176. package/dist/services/steward-sidecar/process-management.js.map +1 -0
  177. package/dist/services/steward-sidecar/types.d.ts +2 -0
  178. package/dist/services/steward-sidecar/types.d.ts.map +1 -0
  179. package/dist/services/steward-sidecar/types.js +2 -0
  180. package/dist/services/steward-sidecar/types.js.map +1 -0
  181. package/dist/services/steward-sidecar/wallet-setup.d.ts +2 -0
  182. package/dist/services/steward-sidecar/wallet-setup.d.ts.map +1 -0
  183. package/dist/services/steward-sidecar/wallet-setup.js +2 -0
  184. package/dist/services/steward-sidecar/wallet-setup.js.map +1 -0
  185. package/dist/services/steward-sidecar.d.ts +2 -0
  186. package/dist/services/steward-sidecar.d.ts.map +1 -0
  187. package/dist/services/steward-sidecar.js +2 -0
  188. package/dist/services/steward-sidecar.js.map +1 -0
  189. package/dist/services/steward-wallet.d.ts +25 -0
  190. package/dist/services/steward-wallet.d.ts.map +1 -0
  191. package/dist/services/steward-wallet.js +333 -0
  192. package/dist/services/steward-wallet.js.map +1 -0
  193. package/dist/steward-ui-state.d.ts +14 -0
  194. package/dist/steward-ui-state.d.ts.map +1 -0
  195. package/dist/steward-ui-state.js +46 -0
  196. package/dist/steward-ui-state.js.map +1 -0
  197. package/dist/steward-view-bundle.d.ts +3 -0
  198. package/dist/steward-view-bundle.d.ts.map +1 -0
  199. package/dist/steward-view-bundle.js +7 -0
  200. package/dist/steward-view-bundle.js.map +1 -0
  201. package/dist/types/bsc-trade.d.ts +180 -0
  202. package/dist/types/bsc-trade.d.ts.map +1 -0
  203. package/dist/types/bsc-trade.js +1 -0
  204. package/dist/types/bsc-trade.js.map +1 -0
  205. package/dist/types/index.d.ts +3 -0
  206. package/dist/types/index.d.ts.map +1 -0
  207. package/dist/types/index.js +3 -0
  208. package/dist/types/index.js.map +1 -0
  209. package/dist/types/steward.d.ts +83 -0
  210. package/dist/types/steward.d.ts.map +1 -0
  211. package/dist/types/steward.js +1 -0
  212. package/dist/types/steward.js.map +1 -0
  213. package/dist/ui.d.ts +7 -0
  214. package/dist/ui.d.ts.map +1 -0
  215. package/dist/ui.js +7 -0
  216. package/dist/ui.js.map +1 -0
  217. package/dist/views/bundle.js +601 -0
  218. package/dist/views/bundle.js.map +1 -0
  219. package/package.json +8 -8
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/api/bsc-trade.ts"],"sourcesContent":["/**\n * BSC trade preflight + quote helpers.\n *\n * Safety-first scope:\n * - No execution/signing here.\n * - Validate wallet/rpc/chain/gas/token before producing a quote.\n */\n\nimport type {\n BscTradePreflightResponse,\n BscTradeQuoteRequest,\n BscTradeQuoteResponse,\n BscTradeRoutePreference,\n BscTradeRouteProvider,\n BscTradeSide,\n BscUnsignedApprovalTx,\n BscUnsignedTradeTx,\n} from \"@elizaos/core\";\nimport { logger } from \"@elizaos/core\";\nimport * as ethers from \"ethers\";\nimport {\n normalizeRpcUrl,\n resolveBscRpcUrls as resolveWalletBscRpcUrls,\n resolveWalletNetworkMode,\n} from \"./wallet-rpc.js\";\n\nconst FETCH_TIMEOUT_MS = 15_000;\nconst BSC_MAINNET_CHAIN_ID = 56;\nconst BSC_TESTNET_CHAIN_ID = 97;\nconst MIN_GAS_BNB = \"0.005\";\nconst DEFAULT_SLIPPAGE_BPS = 300;\nconst MAX_SLIPPAGE_BPS = 1_000;\nconst SLIPPAGE_WARNING_THRESHOLD_BPS = 300;\nconst ZEROX_API_BASE_URL = \"https://bsc.api.0x.org\";\nconst ZEROX_QUOTE_TIMEOUT_MS = 8_000;\n\nexport const PANCAKE_SWAP_V2_ROUTER = ethers.getAddress(\n \"0x10ED43C718714eb63d5aA57B78B54704E256024E\",\n);\nexport const BSC_WBNB_FALLBACK = ethers.getAddress(\n \"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c\",\n);\nexport const BSC_TESTNET_EXPLORER_BASE_URL = \"https://testnet.bscscan.com\";\n\nconst ROUTER_IFACE = new ethers.Interface([\n \"function WETH() view returns (address)\",\n \"function getAmountsOut(uint256 amountIn, address[] calldata path) view returns (uint256[] memory amounts)\",\n \"function swapExactETHForTokensSupportingFeeOnTransferTokens(uint amountOutMin, address[] path, address to, uint deadline)\",\n \"function swapExactTokensForETHSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] path, address to, uint deadline)\",\n]);\n\nconst ERC20_IFACE = new ethers.Interface([\n \"function symbol() view returns (string)\",\n \"function decimals() view returns (uint8)\",\n \"function balanceOf(address owner) view returns (uint256)\",\n \"function approve(address spender, uint256 amount) returns (bool)\",\n]);\n\nexport interface BscTradeRpcConfig {\n rpcUrls?: string[] | null;\n nodeRealBscRpcUrl?: string | null;\n quickNodeBscRpcUrl?: string | null;\n bscRpcUrl?: string | null;\n cloudManagedAccess?: boolean | null;\n}\n\nexport interface BuildBscTradePreflightInput extends BscTradeRpcConfig {\n walletAddress: string | null;\n tokenAddress?: string | null;\n}\n\nexport interface BuildBscTradeQuoteInput extends BscTradeRpcConfig {\n walletAddress: string | null;\n request: BscTradeQuoteRequest;\n}\n\ninterface RpcCallResult<T> {\n result: T;\n rpcUrl: string;\n}\n\ninterface JsonRpcResponse<T> {\n result?: T;\n error?: {\n code?: number;\n message?: string;\n };\n}\n\ninterface ZeroXQuoteResponse {\n to?: string;\n data?: string;\n value?: string;\n allowanceTarget?: string;\n buyAmount?: string;\n sellAmount?: string;\n}\n\ninterface BscExecutionContext {\n walletNetwork: \"mainnet\" | \"testnet\";\n chainId: number;\n routerAddress: string;\n wrappedNativeFallback: string | null;\n explorerBaseUrl: string;\n}\n\nfunction resolveBscExecutionContext(): BscExecutionContext {\n const walletNetwork = resolveWalletNetworkMode();\n if (walletNetwork === \"mainnet\") {\n return {\n walletNetwork,\n chainId: BSC_MAINNET_CHAIN_ID,\n routerAddress: PANCAKE_SWAP_V2_ROUTER,\n wrappedNativeFallback: BSC_WBNB_FALLBACK,\n explorerBaseUrl: \"https://bscscan.com\",\n };\n }\n\n const configuredChainIdRaw = process.env.BSC_TESTNET_CHAIN_ID?.trim();\n const configuredChainId = configuredChainIdRaw\n ? Number.parseInt(configuredChainIdRaw, 10)\n : BSC_TESTNET_CHAIN_ID;\n const chainId =\n Number.isFinite(configuredChainId) && configuredChainId > 0\n ? configuredChainId\n : BSC_TESTNET_CHAIN_ID;\n\n const routerAddress = normalizeAddress(\n process.env.BSC_TESTNET_SWAP_ROUTER_ADDRESS ??\n process.env.BSC_SWAP_ROUTER_ADDRESS,\n );\n if (!routerAddress) {\n throw new Error(\n \"BSC testnet router not configured. Set BSC_TESTNET_SWAP_ROUTER_ADDRESS.\",\n );\n }\n const wrappedNativeFallback = normalizeAddress(\n process.env.BSC_TESTNET_WRAPPED_NATIVE_ADDRESS ??\n process.env.BSC_WRAPPED_NATIVE_ADDRESS,\n );\n\n const explorerBaseUrlRaw =\n process.env.BSC_TESTNET_EXPLORER_BASE_URL ?? BSC_TESTNET_EXPLORER_BASE_URL;\n const explorerBaseUrl = (() => {\n try {\n const parsed = new URL(explorerBaseUrlRaw);\n return parsed.toString().replace(/\\/+$/, \"\");\n } catch {\n return BSC_TESTNET_EXPLORER_BASE_URL;\n }\n })();\n\n return {\n walletNetwork,\n chainId,\n routerAddress,\n wrappedNativeFallback,\n explorerBaseUrl,\n };\n}\n\nexport function resolveBscRpcUrls(input: BscTradeRpcConfig): string[] {\n const walletNetwork = resolveWalletNetworkMode();\n const candidates = [\n ...(input.rpcUrls ?? []).map((url) => normalizeRpcUrl(url)),\n normalizeRpcUrl(process.env.BSC_TESTNET_RPC_URL),\n normalizeRpcUrl(\n input.nodeRealBscRpcUrl !== undefined\n ? input.nodeRealBscRpcUrl\n : process.env.NODEREAL_BSC_RPC_URL,\n ),\n normalizeRpcUrl(\n input.quickNodeBscRpcUrl !== undefined\n ? input.quickNodeBscRpcUrl\n : process.env.QUICKNODE_BSC_RPC_URL,\n ),\n // Standard plugin env key used across elizaOS EVM tooling.\n normalizeRpcUrl(\n input.bscRpcUrl !== undefined ? input.bscRpcUrl : process.env.BSC_RPC_URL,\n ),\n ...resolveWalletBscRpcUrls({\n cloudManagedAccess: input.cloudManagedAccess,\n walletNetwork,\n }),\n ].filter((v): v is string => Boolean(v));\n\n return [...new Set(candidates)];\n}\n\nexport function resolvePrimaryBscRpcUrl(\n input: BscTradeRpcConfig,\n): string | null {\n const urls = resolveBscRpcUrls(input);\n if (urls.length === 0) return null;\n const primary = urls[0];\n try {\n const parsed = new URL(primary);\n if (parsed.protocol === \"http:\") {\n logger.warn(\n `BSC RPC URL uses http: (${parsed.host}) — MITM risk for trade execution. Use https: in production.`,\n );\n }\n } catch {\n // URL parsing failed; normalizeRpcUrl already validated it, so this shouldn't happen\n }\n return primary;\n}\n\nfunction hostLabel(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return \"rpc\";\n }\n}\n\nfunction normalizeAddress(value: string | null | undefined): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n try {\n return ethers.getAddress(trimmed);\n } catch {\n return null;\n }\n}\n\nfunction parseRpcChainId(value: string): number | null {\n if (!value || typeof value !== \"string\") return null;\n if (!value.startsWith(\"0x\")) return null;\n const parsed = Number.parseInt(value, 16);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction clampSlippageBps(value: number | undefined): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n return DEFAULT_SLIPPAGE_BPS;\n }\n const rounded = Math.round(value);\n if (rounded < 1) return 1;\n if (rounded > MAX_SLIPPAGE_BPS) {\n console.warn(\n `[bsc-trade] Slippage ${rounded} bps exceeds max ${MAX_SLIPPAGE_BPS} bps, clamping`,\n );\n return MAX_SLIPPAGE_BPS;\n }\n if (rounded > SLIPPAGE_WARNING_THRESHOLD_BPS) {\n console.warn(\n `[bsc-trade] High slippage requested: ${rounded} bps (${(rounded / 100).toFixed(1)}%)`,\n );\n }\n return rounded;\n}\n\nfunction clampDeadlineSeconds(value: number | undefined): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return 600;\n const rounded = Math.round(value);\n if (rounded < 60) return 60;\n if (rounded > 3600) return 3600;\n return rounded;\n}\n\nfunction parsePositiveDecimal(value: string): number {\n const amount = Number.parseFloat(value);\n if (!Number.isFinite(amount) || amount <= 0) {\n throw new Error(\"Amount must be a positive number.\");\n }\n return amount;\n}\n\nfunction resolveRouteProviderPreference(\n value: string | null | undefined,\n): BscTradeRoutePreference {\n if (value === \"pancakeswap-v2\" || value === \"0x\" || value === \"auto\") {\n return value;\n }\n return \"auto\";\n}\n\nfunction resolveRouteProviderOrder(\n requested: BscTradeRoutePreference,\n): BscTradeRouteProvider[] {\n if (requested === \"0x\") {\n return [\"0x\", \"pancakeswap-v2\"];\n }\n if (requested === \"pancakeswap-v2\") {\n return [\"pancakeswap-v2\"];\n }\n return [\"0x\", \"pancakeswap-v2\"];\n}\n\nfunction formatPrice(amountIn: string, amountOut: string): string {\n const inNum = Number.parseFloat(amountIn);\n const outNum = Number.parseFloat(amountOut);\n if (!Number.isFinite(inNum) || !Number.isFinite(outNum) || inNum <= 0) {\n return \"0\";\n }\n const price = outNum / inNum;\n if (price >= 1000) return price.toFixed(2);\n if (price >= 1) return price.toFixed(4);\n if (price >= 0.001) return price.toFixed(6);\n return price.toExponential(4);\n}\n\nasync function rpcCallWithFallback<T>(\n rpcUrls: string[],\n method: string,\n params: unknown[],\n): Promise<RpcCallResult<T>> {\n if (rpcUrls.length === 0) {\n throw new Error(\"No BSC RPC endpoints configured.\");\n }\n\n const payload = JSON.stringify({\n jsonrpc: \"2.0\",\n id: 1,\n method,\n params,\n });\n\n let lastError = \"Unknown RPC error\";\n for (const rpcUrl of rpcUrls) {\n try {\n const response = await fetch(rpcUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n body: payload,\n });\n const raw = await response.text();\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${raw.slice(0, 180)}`);\n }\n let parsed: JsonRpcResponse<T>;\n try {\n parsed = JSON.parse(raw) as JsonRpcResponse<T>;\n } catch {\n throw new Error(`Invalid JSON response: ${raw.slice(0, 180)}`);\n }\n if (parsed.error) {\n throw new Error(\n parsed.error.message ?? `RPC error ${parsed.error.code}`,\n );\n }\n if (parsed.result === undefined || parsed.result === null) {\n throw new Error(\"RPC returned empty result.\");\n }\n return { result: parsed.result, rpcUrl };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n lastError = `${hostLabel(rpcUrl)}: ${message}`;\n }\n }\n\n throw new Error(lastError);\n}\n\nasync function ethCall(\n rpcUrls: string[],\n to: string,\n data: string,\n): Promise<RpcCallResult<string>> {\n return rpcCallWithFallback<string>(rpcUrls, \"eth_call\", [\n { to, data },\n \"latest\",\n ]);\n}\n\nasync function readWrappedNativeAddress(\n rpcUrls: string[],\n context: BscExecutionContext,\n): Promise<string> {\n const encoded = ROUTER_IFACE.encodeFunctionData(\"WETH\", []);\n try {\n const call = await ethCall(rpcUrls, context.routerAddress, encoded);\n const decoded = ROUTER_IFACE.decodeFunctionResult(\"WETH\", call.result);\n const wrappedNative = decoded[0];\n\n if (typeof wrappedNative !== \"string\" || !wrappedNative) {\n throw new Error(\"Router WETH() returned an invalid address.\");\n }\n\n return ethers.getAddress(wrappedNative);\n } catch (err) {\n if (context.wrappedNativeFallback) {\n return context.wrappedNativeFallback;\n }\n throw err;\n }\n}\n\nexport async function readTokenDecimals(\n rpcUrls: string[],\n tokenAddress: string,\n): Promise<number> {\n const encoded = ERC20_IFACE.encodeFunctionData(\"decimals\", []);\n const call = await ethCall(rpcUrls, tokenAddress, encoded);\n const decoded = ERC20_IFACE.decodeFunctionResult(\"decimals\", call.result);\n const decimals = decoded[0];\n\n if (typeof decimals !== \"bigint\") return 18;\n\n const parsed = Number(decimals);\n if (!Number.isFinite(parsed) || parsed < 0) return 18;\n\n return parsed;\n}\n\nasync function readTokenSymbol(\n rpcUrls: string[],\n tokenAddress: string,\n): Promise<string> {\n const encoded = ERC20_IFACE.encodeFunctionData(\"symbol\", []);\n const call = await ethCall(rpcUrls, tokenAddress, encoded);\n const decoded = ERC20_IFACE.decodeFunctionResult(\"symbol\", call.result);\n const symbol = decoded[0];\n\n if (typeof symbol === \"string\" && symbol.trim()) {\n return symbol.trim().slice(0, 16);\n }\n\n return `TKN-${tokenAddress.slice(2, 6).toUpperCase()}`;\n}\n\nasync function readTokenBalanceWei(\n rpcUrls: string[],\n tokenAddress: string,\n walletAddress: string,\n): Promise<bigint> {\n const encoded = ERC20_IFACE.encodeFunctionData(\"balanceOf\", [walletAddress]);\n const call = await ethCall(rpcUrls, tokenAddress, encoded);\n const decoded = ERC20_IFACE.decodeFunctionResult(\"balanceOf\", call.result);\n const balance = decoded[0];\n if (typeof balance !== \"bigint\") {\n throw new Error(\"Token balance response is invalid.\");\n }\n return balance;\n}\n\nasync function fetchZeroXQuote(input: {\n side: BscTradeSide;\n walletAddress: string;\n tokenAddress: string;\n wrappedNativeAddress: string;\n amountInWei: bigint;\n slippageBps: number;\n}): Promise<ZeroXQuoteResponse> {\n const apiBase = normalizeRpcUrl(process.env.ZEROX_BSC_API_BASE_URL)\n ? (normalizeRpcUrl(process.env.ZEROX_BSC_API_BASE_URL) as string)\n : ZEROX_API_BASE_URL;\n const sellToken =\n input.side === \"buy\" ? input.wrappedNativeAddress : input.tokenAddress;\n const buyToken =\n input.side === \"buy\" ? input.tokenAddress : input.wrappedNativeAddress;\n const quoteUrl = new URL(\"/swap/v1/quote\", apiBase);\n quoteUrl.searchParams.set(\"sellToken\", sellToken);\n quoteUrl.searchParams.set(\"buyToken\", buyToken);\n quoteUrl.searchParams.set(\"sellAmount\", input.amountInWei.toString());\n quoteUrl.searchParams.set(\n \"slippagePercentage\",\n (input.slippageBps / 10_000).toString(),\n );\n quoteUrl.searchParams.set(\"takerAddress\", input.walletAddress);\n const apiKey = process.env.ZEROX_API_KEY?.trim();\n const response = await fetch(quoteUrl.toString(), {\n method: \"GET\",\n headers: apiKey ? { \"0x-api-key\": apiKey } : undefined,\n signal: AbortSignal.timeout(ZEROX_QUOTE_TIMEOUT_MS),\n });\n const raw = await response.text();\n if (!response.ok) {\n throw new Error(`0x HTTP ${response.status}: ${raw.slice(0, 180)}`);\n }\n let parsed: ZeroXQuoteResponse;\n try {\n parsed = JSON.parse(raw) as ZeroXQuoteResponse;\n } catch {\n throw new Error(`0x quote returned invalid JSON: ${raw.slice(0, 180)}`);\n }\n\n const to = normalizeAddress(parsed.to);\n const data =\n typeof parsed.data === \"string\" && parsed.data.startsWith(\"0x\")\n ? parsed.data\n : null;\n const buyAmount =\n typeof parsed.buyAmount === \"string\" && /^\\d+$/.test(parsed.buyAmount)\n ? BigInt(parsed.buyAmount)\n : null;\n const sellAmount =\n typeof parsed.sellAmount === \"string\" && /^\\d+$/.test(parsed.sellAmount)\n ? BigInt(parsed.sellAmount)\n : null;\n const value =\n typeof parsed.value === \"string\" && /^\\d+$/.test(parsed.value)\n ? parsed.value\n : \"0\";\n\n if (!to || !data || !buyAmount || !sellAmount) {\n throw new Error(\"0x quote missing required transaction fields.\");\n }\n\n return {\n to,\n data,\n value,\n allowanceTarget:\n normalizeAddress(parsed.allowanceTarget ?? null) ?? undefined,\n buyAmount: buyAmount.toString(),\n sellAmount: sellAmount.toString(),\n };\n}\n\nexport async function buildBscTradePreflight(\n input: BuildBscTradePreflightInput,\n): Promise<BscTradePreflightResponse> {\n const context = resolveBscExecutionContext();\n const checks = {\n walletReady: false,\n rpcReady: false,\n chainReady: false,\n gasReady: false,\n tokenAddressValid: true,\n };\n const reasons: string[] = [];\n const walletAddress = normalizeAddress(input.walletAddress);\n const tokenAddressRaw = (input.tokenAddress ?? \"\").trim();\n const tokenAddress = tokenAddressRaw\n ? normalizeAddress(tokenAddressRaw)\n : null;\n const rpcUrls = resolveBscRpcUrls(input);\n\n let chainId: number | null = null;\n let bnbBalance: string | null = null;\n let activeRpcUrl: string | null = null;\n\n checks.walletReady = Boolean(walletAddress);\n if (!checks.walletReady) {\n reasons.push(\"Wallet not ready. Create or connect an EVM wallet first.\");\n }\n\n if (tokenAddressRaw && !tokenAddress) {\n checks.tokenAddressValid = false;\n reasons.push(\"Token address format is invalid.\");\n }\n\n if (rpcUrls.length === 0) {\n reasons.push(\n \"BSC RPC not configured. Connect Eliza Cloud or set NODEREAL_BSC_RPC_URL, QUICKNODE_BSC_RPC_URL, or BSC_RPC_URL.\",\n );\n } else {\n try {\n const chainResponse = await rpcCallWithFallback<string>(\n rpcUrls,\n \"eth_chainId\",\n [],\n );\n activeRpcUrl = chainResponse.rpcUrl;\n checks.rpcReady = true;\n chainId = parseRpcChainId(chainResponse.result);\n checks.chainReady = chainId === context.chainId;\n if (!checks.chainReady) {\n reasons.push(\n chainId === null\n ? \"Unable to read chain id from RPC.\"\n : `RPC chain mismatch. Expected BSC (${context.chainId}), got ${chainId}.`,\n );\n }\n } catch (err) {\n reasons.push(\n `BSC RPC unavailable: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n if (checks.walletReady && checks.rpcReady) {\n try {\n const rpcCandidates = activeRpcUrl\n ? [activeRpcUrl, ...rpcUrls.filter((url) => url !== activeRpcUrl)]\n : rpcUrls;\n const balanceResponse = await rpcCallWithFallback<string>(\n rpcCandidates,\n \"eth_getBalance\",\n [walletAddress, \"latest\"],\n );\n if (!activeRpcUrl) activeRpcUrl = balanceResponse.rpcUrl;\n const balanceWei = BigInt(balanceResponse.result);\n bnbBalance = ethers.formatEther(balanceWei);\n checks.gasReady = balanceWei >= ethers.parseEther(MIN_GAS_BNB);\n if (!checks.gasReady) {\n reasons.push(\n `Insufficient BNB gas. Keep at least ${MIN_GAS_BNB} BNB available.`,\n );\n }\n } catch (err) {\n reasons.push(\n `Failed to read wallet balance: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n if (tokenAddressRaw && tokenAddress && checks.rpcReady && checks.chainReady) {\n try {\n const rpcCandidates = activeRpcUrl\n ? [activeRpcUrl, ...rpcUrls.filter((url) => url !== activeRpcUrl)]\n : rpcUrls;\n const codeResponse = await rpcCallWithFallback<string>(\n rpcCandidates,\n \"eth_getCode\",\n [tokenAddress, \"latest\"],\n );\n if (!activeRpcUrl) activeRpcUrl = codeResponse.rpcUrl;\n const code = codeResponse.result.trim().toLowerCase();\n if (code === \"0x\" || code === \"0x0\") {\n checks.tokenAddressValid = false;\n reasons.push(\"Token contract not found on BSC.\");\n }\n } catch (err) {\n checks.tokenAddressValid = false;\n reasons.push(\n `Token contract check failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n const ok =\n checks.walletReady &&\n checks.rpcReady &&\n checks.chainReady &&\n checks.gasReady &&\n checks.tokenAddressValid;\n\n return {\n ok,\n walletAddress,\n rpcUrlHost: activeRpcUrl ? hostLabel(activeRpcUrl) : null,\n chainId,\n bnbBalance,\n minGasBnb: MIN_GAS_BNB,\n checks,\n reasons,\n };\n}\n\nexport async function buildBscTradeQuote(\n input: BuildBscTradeQuoteInput,\n): Promise<BscTradeQuoteResponse> {\n const context = resolveBscExecutionContext();\n const side = input.request.side as BscTradeSide;\n if (side !== \"buy\" && side !== \"sell\") {\n throw new Error('Unsupported trade side. Use \"buy\" or \"sell\".');\n }\n\n const tokenAddress = normalizeAddress(input.request.tokenAddress);\n if (!tokenAddress) {\n throw new Error(\"Token address is required.\");\n }\n\n const amountInput = input.request.amount.trim();\n parsePositiveDecimal(amountInput);\n\n const slippageBps = clampSlippageBps(input.request.slippageBps);\n const routeProviderRequested = resolveRouteProviderPreference(\n input.request.routeProvider,\n );\n const preflight = await buildBscTradePreflight({\n walletAddress: input.walletAddress,\n tokenAddress,\n rpcUrls: input.rpcUrls,\n nodeRealBscRpcUrl: input.nodeRealBscRpcUrl,\n quickNodeBscRpcUrl: input.quickNodeBscRpcUrl,\n bscRpcUrl: input.bscRpcUrl,\n cloudManagedAccess: input.cloudManagedAccess,\n });\n if (!preflight.ok) {\n throw new Error(preflight.reasons[0] ?? \"Trade preflight failed.\");\n }\n\n const rpcUrls = resolveBscRpcUrls(input);\n if (rpcUrls.length === 0) {\n throw new Error(\"BSC RPC unavailable.\");\n }\n\n const wrappedNativeAddress = await readWrappedNativeAddress(rpcUrls, context);\n const tokenDecimals = await readTokenDecimals(rpcUrls, tokenAddress);\n const tokenSymbol = await readTokenSymbol(rpcUrls, tokenAddress);\n\n const amountInWei =\n side === \"buy\"\n ? ethers.parseEther(amountInput)\n : ethers.parseUnits(amountInput, tokenDecimals);\n\n if (side === \"sell\") {\n if (!preflight.walletAddress) {\n throw new Error(\"Wallet not ready for sell quote.\");\n }\n const tokenBalanceWei = await readTokenBalanceWei(\n rpcUrls,\n tokenAddress,\n preflight.walletAddress,\n );\n if (amountInWei > tokenBalanceWei) {\n throw new Error(\"Insufficient token balance for sell amount.\");\n }\n }\n\n if (side === \"buy\" && preflight.bnbBalance) {\n const walletBalanceWei = ethers.parseEther(preflight.bnbBalance);\n const gasReserveWei = ethers.parseEther(MIN_GAS_BNB);\n if (amountInWei + gasReserveWei > walletBalanceWei) {\n throw new Error(\n `Insufficient BNB for amount + gas reserve (${MIN_GAS_BNB} BNB).`,\n );\n }\n }\n\n let routeProvider: BscTradeRouteProvider = \"pancakeswap-v2\";\n let routeProviderFallbackUsed = false;\n const routeProviderNotes: string[] = [];\n let amountOutWei: bigint | null = null;\n let route: string[] = [];\n let swapTargetAddress: string | undefined;\n let swapCallData: string | undefined;\n let swapValueWei: string | undefined;\n let allowanceTarget: string | undefined;\n\n const providerErrors: string[] = [];\n for (const provider of resolveRouteProviderOrder(routeProviderRequested)) {\n try {\n if (provider === \"0x\") {\n if (!preflight.walletAddress) {\n throw new Error(\"wallet address is required for 0x route\");\n }\n const zeroX = await fetchZeroXQuote({\n side,\n walletAddress: preflight.walletAddress,\n tokenAddress,\n wrappedNativeAddress,\n amountInWei,\n slippageBps,\n });\n const buyAmount = BigInt(zeroX.buyAmount ?? \"0\");\n if (buyAmount <= 0n) {\n throw new Error(\"0x quote returned zero output amount\");\n }\n amountOutWei = buyAmount;\n routeProvider = \"0x\";\n route =\n side === \"buy\"\n ? [wrappedNativeAddress, tokenAddress]\n : [tokenAddress, wrappedNativeAddress];\n swapTargetAddress = zeroX.to;\n swapCallData = zeroX.data;\n swapValueWei = zeroX.value ?? \"0\";\n allowanceTarget = zeroX.allowanceTarget;\n break;\n }\n\n const candidateRoute =\n side === \"buy\"\n ? [wrappedNativeAddress, tokenAddress]\n : [tokenAddress, wrappedNativeAddress];\n const quoteCall = ROUTER_IFACE.encodeFunctionData(\"getAmountsOut\", [\n amountInWei,\n candidateRoute,\n ]);\n const quoteResponse = await ethCall(\n rpcUrls,\n context.routerAddress,\n quoteCall,\n );\n const decoded = ROUTER_IFACE.decodeFunctionResult(\n \"getAmountsOut\",\n quoteResponse.result,\n );\n const amountsOut = decoded[0];\n if (!Array.isArray(amountsOut) || amountsOut.length < 2) {\n throw new Error(\"router returned an invalid quote\");\n }\n const finalAmount = amountsOut[amountsOut.length - 1];\n if (typeof finalAmount !== \"bigint\" || finalAmount <= 0n) {\n throw new Error(\"router quote output is invalid\");\n }\n\n amountOutWei = finalAmount;\n routeProvider = \"pancakeswap-v2\";\n route = candidateRoute;\n break;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n providerErrors.push(`${provider}: ${message}`);\n }\n }\n\n if (!amountOutWei) {\n throw new Error(\n `Trade quote failed across providers (${providerErrors.join(\" | \")})`,\n );\n }\n\n if (routeProviderRequested !== routeProvider) {\n routeProviderFallbackUsed = true;\n routeProviderNotes.push(\n `Requested ${routeProviderRequested}, used ${routeProvider}.`,\n );\n }\n if (providerErrors.length > 0) {\n routeProviderNotes.push(...providerErrors);\n }\n\n let minReceiveWei = (amountOutWei * BigInt(10_000 - slippageBps)) / 10_000n;\n if (minReceiveWei === 0n && amountOutWei > 0n) {\n minReceiveWei = 1n;\n }\n const outDecimals = side === \"buy\" ? tokenDecimals : 18;\n const inSymbol = side === \"buy\" ? \"BNB\" : tokenSymbol;\n const outSymbol = side === \"buy\" ? tokenSymbol : \"BNB\";\n const amountInFormatted =\n side === \"buy\"\n ? ethers.formatEther(amountInWei)\n : ethers.formatUnits(amountInWei, tokenDecimals);\n const amountOutFormatted = ethers.formatUnits(amountOutWei, outDecimals);\n const minReceiveFormatted = ethers.formatUnits(minReceiveWei, outDecimals);\n\n return {\n ok: true,\n side,\n routeProvider,\n routeProviderRequested,\n routeProviderFallbackUsed,\n routeProviderNotes:\n routeProviderNotes.length > 0 ? routeProviderNotes : undefined,\n routerAddress:\n routeProvider === \"0x\"\n ? (swapTargetAddress ?? context.routerAddress)\n : context.routerAddress,\n wrappedNativeAddress,\n tokenAddress,\n slippageBps,\n route,\n quoteIn: {\n symbol: inSymbol,\n amount: amountInFormatted,\n amountWei: amountInWei.toString(),\n },\n quoteOut: {\n symbol: outSymbol,\n amount: amountOutFormatted,\n amountWei: amountOutWei.toString(),\n },\n minReceive: {\n symbol: outSymbol,\n amount: minReceiveFormatted,\n amountWei: minReceiveWei.toString(),\n },\n price: formatPrice(amountInFormatted, amountOutFormatted),\n preflight,\n swapTargetAddress,\n swapCallData,\n swapValueWei,\n allowanceTarget,\n quotedAt: Date.now(),\n };\n}\n\n/**\n * Assert that the quote's routerAddress matches the expected PancakeSwap V2 router.\n * Prevents a compromised or tampered quote from directing funds to an arbitrary address.\n */\nfunction assertRouterAddress(\n quote: BscTradeQuoteResponse,\n context: BscExecutionContext,\n): void {\n if (quote.routeProvider === \"0x\") {\n return;\n }\n if (quote.routerAddress !== context.routerAddress) {\n throw new Error(\n `Unexpected router address in quote: ${quote.routerAddress}. Expected router ${context.routerAddress}.`,\n );\n }\n}\n\nexport function buildBscBuyUnsignedTx(\n quote: BscTradeQuoteResponse,\n recipientAddress: string | null,\n deadlineSeconds?: number,\n): BscUnsignedTradeTx {\n const context = resolveBscExecutionContext();\n assertRouterAddress(quote, context);\n if (quote.side !== \"buy\") {\n throw new Error(\"Only buy execution is currently supported.\");\n }\n const normalizedRecipient = normalizeAddress(recipientAddress);\n if (!normalizedRecipient) {\n throw new Error(\"Recipient wallet address is required.\");\n }\n if (quote.routeProvider === \"0x\") {\n if (!quote.swapTargetAddress || !quote.swapCallData) {\n throw new Error(\"0x quote is missing swap transaction payload.\");\n }\n return {\n chainId: context.chainId,\n from: normalizedRecipient,\n to: quote.swapTargetAddress,\n data: quote.swapCallData,\n valueWei: quote.swapValueWei ?? quote.quoteIn.amountWei,\n deadline:\n Math.floor(Date.now() / 1000) + clampDeadlineSeconds(deadlineSeconds),\n explorerUrl: context.explorerBaseUrl,\n };\n }\n const now = Math.floor(Date.now() / 1000);\n const deadline = now + clampDeadlineSeconds(deadlineSeconds);\n const data = ROUTER_IFACE.encodeFunctionData(\n \"swapExactETHForTokensSupportingFeeOnTransferTokens\",\n [\n BigInt(quote.minReceive.amountWei),\n quote.route,\n normalizedRecipient,\n deadline,\n ],\n );\n\n return {\n chainId: context.chainId,\n from: normalizedRecipient,\n to: quote.routerAddress,\n data,\n valueWei: quote.quoteIn.amountWei,\n deadline,\n explorerUrl: context.explorerBaseUrl,\n };\n}\n\nexport function buildBscSellUnsignedTx(\n quote: BscTradeQuoteResponse,\n recipientAddress: string | null,\n deadlineSeconds?: number,\n): BscUnsignedTradeTx {\n const context = resolveBscExecutionContext();\n assertRouterAddress(quote, context);\n if (quote.side !== \"sell\") {\n throw new Error(\"Only sell execution is supported for this payload.\");\n }\n const normalizedRecipient = normalizeAddress(recipientAddress);\n if (!normalizedRecipient) {\n throw new Error(\"Recipient wallet address is required.\");\n }\n if (quote.routeProvider === \"0x\") {\n if (!quote.swapTargetAddress || !quote.swapCallData) {\n throw new Error(\"0x quote is missing swap transaction payload.\");\n }\n return {\n chainId: context.chainId,\n from: normalizedRecipient,\n to: quote.swapTargetAddress,\n data: quote.swapCallData,\n valueWei: quote.swapValueWei ?? \"0\",\n deadline:\n Math.floor(Date.now() / 1000) + clampDeadlineSeconds(deadlineSeconds),\n explorerUrl: context.explorerBaseUrl,\n };\n }\n const now = Math.floor(Date.now() / 1000);\n const deadline = now + clampDeadlineSeconds(deadlineSeconds);\n const data = ROUTER_IFACE.encodeFunctionData(\n \"swapExactTokensForETHSupportingFeeOnTransferTokens\",\n [\n BigInt(quote.quoteIn.amountWei),\n BigInt(quote.minReceive.amountWei),\n quote.route,\n normalizedRecipient,\n deadline,\n ],\n );\n\n return {\n chainId: context.chainId,\n from: normalizedRecipient,\n to: quote.routerAddress,\n data,\n valueWei: \"0\",\n deadline,\n explorerUrl: context.explorerBaseUrl,\n };\n}\n\nexport function buildBscApproveUnsignedTx(\n tokenAddress: string,\n ownerAddress: string | null,\n spenderAddress: string,\n amountWei: string,\n): BscUnsignedApprovalTx {\n const context = resolveBscExecutionContext();\n const normalizedToken = normalizeAddress(tokenAddress);\n if (!normalizedToken) {\n throw new Error(\"Token address is invalid for approval payload.\");\n }\n const normalizedOwner = normalizeAddress(ownerAddress);\n if (!normalizedOwner) {\n throw new Error(\"Owner wallet address is required for approval payload.\");\n }\n const normalizedSpender = normalizeAddress(spenderAddress);\n if (!normalizedSpender) {\n throw new Error(\"Spender address is invalid for approval payload.\");\n }\n let amount: bigint;\n try {\n amount = BigInt(amountWei);\n } catch {\n throw new Error(\n `Invalid approval amount: expected integer string, got \"${String(amountWei).slice(0, 20)}\"`,\n );\n }\n if (amount <= 0n) {\n throw new Error(\"Approval amount must be greater than zero.\");\n }\n const data = ERC20_IFACE.encodeFunctionData(\"approve\", [\n normalizedSpender,\n amount,\n ]);\n\n return {\n chainId: context.chainId,\n from: normalizedOwner,\n to: normalizedToken,\n data,\n valueWei: \"0\",\n explorerUrl: context.explorerBaseUrl,\n spender: normalizedSpender,\n amountWei: amount.toString(),\n };\n}\n\nexport function resolveBscApprovalSpender(\n quote: Pick<\n BscTradeQuoteResponse,\n \"routeProvider\" | \"allowanceTarget\" | \"routerAddress\"\n >,\n): string {\n if (quote.routeProvider === \"0x\" && quote.allowanceTarget) {\n return quote.allowanceTarget;\n }\n return quote.routerAddress;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Pure trade safety utilities — no heavy dependencies.
3
+ * Extracted so they can be unit-tested without pulling in the full server.
4
+ */
5
+ /** Maximum number of autonomous agent trades allowed per calendar day. */
6
+ export declare const AGENT_AUTO_MAX_DAILY_TRADES = 25;
7
+ /** Maximum age of a trade quote before it is considered stale. */
8
+ export declare const QUOTE_MAX_AGE_MS = 60000;
9
+ /** Tracks autonomous trade count for rate-limiting in agent-auto mode. */
10
+ export declare const agentAutoDailyTrades: {
11
+ count: number;
12
+ resetDate: string;
13
+ };
14
+ export declare function getAgentAutoTradeDate(): string;
15
+ /**
16
+ * Record an autonomous agent trade. Returns true if allowed, false if
17
+ * the daily limit has been reached. Resets the counter on a new calendar day.
18
+ */
19
+ export declare function recordAgentAutoTrade(log?: (msg: string) => void): boolean;
20
+ export type TradePermissionMode = "user-sign-only" | "agent-auto" | "manual-local-key" | "disabled";
21
+ type LocalTradeExecutionOptions = {
22
+ consumeAgentQuota?: boolean;
23
+ };
24
+ /**
25
+ * Returns true if local-key execution is permitted for the given actor.
26
+ */
27
+ export declare function canUseLocalTradeExecution(mode: TradePermissionMode, isAgent: boolean, log?: (msg: string) => void, options?: LocalTradeExecutionOptions): boolean;
28
+ /**
29
+ * Assert that a trade quote is still fresh. Throws if the quote is older
30
+ * than QUOTE_MAX_AGE_MS. Silently passes if `quotedAt` is undefined
31
+ * (backwards compatibility with quotes that lack the field).
32
+ */
33
+ export declare function assertQuoteFresh(quotedAt: number | undefined, now?: number): void;
34
+ export {};
35
+ //# sourceMappingURL=trade-safety.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trade-safety.d.ts","sourceRoot":"","sources":["../../src/api/trade-safety.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB;;;CAA8B,CAAC;AAEhE,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAiBzE;AAED,MAAM,MAAM,mBAAmB,GAC3B,gBAAgB,GAChB,YAAY,GACZ,kBAAkB,GAClB,UAAU,CAAC;AAEf,KAAK,0BAA0B,GAAG;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,OAAO,EAChB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAC3B,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAgBT;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,GAAG,GAAE,MAAmB,GACvB,IAAI,CAIN"}
@@ -0,0 +1,56 @@
1
+ const AGENT_AUTO_MAX_DAILY_TRADES = 25;
2
+ const QUOTE_MAX_AGE_MS = 6e4;
3
+ const agentAutoDailyTrades = { count: 0, resetDate: "" };
4
+ function getAgentAutoTradeDate() {
5
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6
+ }
7
+ function recordAgentAutoTrade(log) {
8
+ const today = getAgentAutoTradeDate();
9
+ if (agentAutoDailyTrades.resetDate !== today) {
10
+ agentAutoDailyTrades.count = 0;
11
+ agentAutoDailyTrades.resetDate = today;
12
+ }
13
+ if (agentAutoDailyTrades.count >= AGENT_AUTO_MAX_DAILY_TRADES) {
14
+ log?.(
15
+ `[trade] Agent-auto daily trade limit reached (${AGENT_AUTO_MAX_DAILY_TRADES}). Rejecting autonomous trade.`
16
+ );
17
+ return false;
18
+ }
19
+ agentAutoDailyTrades.count += 1;
20
+ log?.(
21
+ `[trade] Agent-auto autonomous trade ${agentAutoDailyTrades.count}/${AGENT_AUTO_MAX_DAILY_TRADES} for ${today}`
22
+ );
23
+ return true;
24
+ }
25
+ function canUseLocalTradeExecution(mode, isAgent, log, options = {}) {
26
+ if (mode === "agent-auto") {
27
+ if (isAgent) {
28
+ if (options.consumeAgentQuota === false) {
29
+ const today = getAgentAutoTradeDate();
30
+ if (agentAutoDailyTrades.resetDate !== today) {
31
+ return true;
32
+ }
33
+ return agentAutoDailyTrades.count < AGENT_AUTO_MAX_DAILY_TRADES;
34
+ }
35
+ return recordAgentAutoTrade(log);
36
+ }
37
+ return true;
38
+ }
39
+ if (mode === "manual-local-key") return !isAgent;
40
+ return false;
41
+ }
42
+ function assertQuoteFresh(quotedAt, now = Date.now()) {
43
+ if (quotedAt && now - quotedAt > QUOTE_MAX_AGE_MS) {
44
+ throw new Error("Quote expired \u2014 please request a fresh quote");
45
+ }
46
+ }
47
+ export {
48
+ AGENT_AUTO_MAX_DAILY_TRADES,
49
+ QUOTE_MAX_AGE_MS,
50
+ agentAutoDailyTrades,
51
+ assertQuoteFresh,
52
+ canUseLocalTradeExecution,
53
+ getAgentAutoTradeDate,
54
+ recordAgentAutoTrade
55
+ };
56
+ //# sourceMappingURL=trade-safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/api/trade-safety.ts"],"sourcesContent":["/**\n * Pure trade safety utilities — no heavy dependencies.\n * Extracted so they can be unit-tested without pulling in the full server.\n */\n\n/** Maximum number of autonomous agent trades allowed per calendar day. */\nexport const AGENT_AUTO_MAX_DAILY_TRADES = 25;\n\n/** Maximum age of a trade quote before it is considered stale. */\nexport const QUOTE_MAX_AGE_MS = 60_000; // 60 seconds\n\n/** Tracks autonomous trade count for rate-limiting in agent-auto mode. */\nexport const agentAutoDailyTrades = { count: 0, resetDate: \"\" };\n\nexport function getAgentAutoTradeDate(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\n/**\n * Record an autonomous agent trade. Returns true if allowed, false if\n * the daily limit has been reached. Resets the counter on a new calendar day.\n */\nexport function recordAgentAutoTrade(log?: (msg: string) => void): boolean {\n const today = getAgentAutoTradeDate();\n if (agentAutoDailyTrades.resetDate !== today) {\n agentAutoDailyTrades.count = 0;\n agentAutoDailyTrades.resetDate = today;\n }\n if (agentAutoDailyTrades.count >= AGENT_AUTO_MAX_DAILY_TRADES) {\n log?.(\n `[trade] Agent-auto daily trade limit reached (${AGENT_AUTO_MAX_DAILY_TRADES}). Rejecting autonomous trade.`,\n );\n return false;\n }\n agentAutoDailyTrades.count += 1;\n log?.(\n `[trade] Agent-auto autonomous trade ${agentAutoDailyTrades.count}/${AGENT_AUTO_MAX_DAILY_TRADES} for ${today}`,\n );\n return true;\n}\n\nexport type TradePermissionMode =\n | \"user-sign-only\"\n | \"agent-auto\"\n | \"manual-local-key\"\n | \"disabled\";\n\ntype LocalTradeExecutionOptions = {\n consumeAgentQuota?: boolean;\n};\n\n/**\n * Returns true if local-key execution is permitted for the given actor.\n */\nexport function canUseLocalTradeExecution(\n mode: TradePermissionMode,\n isAgent: boolean,\n log?: (msg: string) => void,\n options: LocalTradeExecutionOptions = {},\n): boolean {\n if (mode === \"agent-auto\") {\n if (isAgent) {\n if (options.consumeAgentQuota === false) {\n const today = getAgentAutoTradeDate();\n if (agentAutoDailyTrades.resetDate !== today) {\n return true;\n }\n return agentAutoDailyTrades.count < AGENT_AUTO_MAX_DAILY_TRADES;\n }\n return recordAgentAutoTrade(log);\n }\n return true;\n }\n if (mode === \"manual-local-key\") return !isAgent;\n return false;\n}\n\n/**\n * Assert that a trade quote is still fresh. Throws if the quote is older\n * than QUOTE_MAX_AGE_MS. Silently passes if `quotedAt` is undefined\n * (backwards compatibility with quotes that lack the field).\n */\nexport function assertQuoteFresh(\n quotedAt: number | undefined,\n now: number = Date.now(),\n): void {\n if (quotedAt && now - quotedAt > QUOTE_MAX_AGE_MS) {\n throw new Error(\"Quote expired — please request a fresh quote\");\n }\n}\n"],"mappings":"AAMO,MAAM,8BAA8B;AAGpC,MAAM,mBAAmB;AAGzB,MAAM,uBAAuB,EAAE,OAAO,GAAG,WAAW,GAAG;AAEvD,SAAS,wBAAgC;AAC9C,UAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC7C;AAMO,SAAS,qBAAqB,KAAsC;AACzE,QAAM,QAAQ,sBAAsB;AACpC,MAAI,qBAAqB,cAAc,OAAO;AAC5C,yBAAqB,QAAQ;AAC7B,yBAAqB,YAAY;AAAA,EACnC;AACA,MAAI,qBAAqB,SAAS,6BAA6B;AAC7D;AAAA,MACE,iDAAiD,2BAA2B;AAAA,IAC9E;AACA,WAAO;AAAA,EACT;AACA,uBAAqB,SAAS;AAC9B;AAAA,IACE,uCAAuC,qBAAqB,KAAK,IAAI,2BAA2B,QAAQ,KAAK;AAAA,EAC/G;AACA,SAAO;AACT;AAeO,SAAS,0BACd,MACA,SACA,KACA,UAAsC,CAAC,GAC9B;AACT,MAAI,SAAS,cAAc;AACzB,QAAI,SAAS;AACX,UAAI,QAAQ,sBAAsB,OAAO;AACvC,cAAM,QAAQ,sBAAsB;AACpC,YAAI,qBAAqB,cAAc,OAAO;AAC5C,iBAAO;AAAA,QACT;AACA,eAAO,qBAAqB,QAAQ;AAAA,MACtC;AACA,aAAO,qBAAqB,GAAG;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AACA,MAAI,SAAS,mBAAoB,QAAO,CAAC;AACzC,SAAO;AACT;AAOO,SAAS,iBACd,UACA,MAAc,KAAK,IAAI,GACjB;AACN,MAAI,YAAY,MAAM,WAAW,kBAAkB;AACjD,UAAM,IAAI,MAAM,mDAA8C;AAAA,EAChE;AACF;","names":[]}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Ethereum transaction signing and contract interaction layer.
3
+ *
4
+ * Provides the missing transaction capability to Eliza's wallet system,
5
+ * which currently only handles key generation and balance fetching.
6
+ * Used by the registry and drop services for on-chain operations.
7
+ */
8
+ import * as ethers from "ethers";
9
+ export interface JsonRpcEndpointProbeResult {
10
+ ok: boolean;
11
+ reason?: string;
12
+ }
13
+ export declare function normalizeJsonRpcUrl(rpcUrl: string): string;
14
+ export declare function probeJsonRpcEndpoint(rpcUrl: string, timeoutMs?: number): Promise<JsonRpcEndpointProbeResult>;
15
+ export declare class TxService {
16
+ private readonly provider;
17
+ private readonly wallet;
18
+ private readonly rpcUrl;
19
+ constructor(rpcUrl: string, privateKey: string);
20
+ /**
21
+ * Get fresh nonce for the wallet address.
22
+ * Always fetches from blockchain using a fresh provider to avoid caching issues.
23
+ * This ensures we always get the correct nonce even after failed transactions.
24
+ */
25
+ getFreshNonce(): Promise<number>;
26
+ get address(): string;
27
+ getBalance(): Promise<bigint>;
28
+ getBalanceFormatted(): Promise<string>;
29
+ getChainId(): Promise<number>;
30
+ getContract(address: string, abi: ethers.InterfaceAbi): ethers.Contract;
31
+ getReadOnlyContract(address: string, abi: ethers.InterfaceAbi): ethers.Contract;
32
+ estimateGas(tx: ethers.TransactionRequest): Promise<bigint>;
33
+ getFeeData(): Promise<ethers.FeeData>;
34
+ /**
35
+ * Wait for a transaction to be mined and return the receipt.
36
+ * Throws if the transaction fails or times out.
37
+ */
38
+ waitForTransaction(txHash: string, confirmations?: number, timeoutMs?: number): Promise<ethers.TransactionReceipt>;
39
+ /**
40
+ * Estimate the gas cost in ETH for a contract call.
41
+ * Useful for showing users how much gas they'll need.
42
+ */
43
+ estimateGasCostEth(tx: ethers.TransactionRequest): Promise<string>;
44
+ /**
45
+ * Check whether the wallet has enough balance for a given value + estimated gas.
46
+ */
47
+ hasEnoughBalance(value: bigint, gasEstimate: bigint): Promise<boolean>;
48
+ /**
49
+ * Log a summary of the tx service state for diagnostics.
50
+ */
51
+ logStatus(): Promise<void>;
52
+ }
53
+ //# sourceMappingURL=tx-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tx-service.d.ts","sourceRoot":"","sources":["../../src/api/tx-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAaD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAsB1D;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,SAAS,SAAQ,GAChB,OAAO,CAAC,0BAA0B,CAAC,CAoDrC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;IAwB9C;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAsBtC,IAAI,OAAO,IAAI,MAAM,CAEpB;IAEK,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKtC,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAKnC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,QAAQ;IAIvE,mBAAmB,CACjB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,CAAC,YAAY,GACvB,MAAM,CAAC,QAAQ;IAIZ,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI3D,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;IAI3C;;;OAGG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,aAAa,GAAE,MAAU,EACzB,SAAS,GAAE,MAAgB,GAC1B,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC;IAiCrC;;;OAGG;IACG,kBAAkB,CAAC,EAAE,EAAE,MAAM,CAAC,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IAQxE;;OAEG;IACG,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ5E;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CASjC"}
@@ -0,0 +1,206 @@
1
+ import { createIntegrationTelemetrySpan } from "@elizaos/agent";
2
+ import { logger } from "@elizaos/core";
3
+ import * as ethers from "ethers";
4
+ function isValidPrivateKey(key) {
5
+ const normalized = key.startsWith("0x") ? key.slice(2) : key;
6
+ if (normalized.length !== 64) return false;
7
+ return /^[0-9a-fA-F]+$/.test(normalized);
8
+ }
9
+ function normalizeJsonRpcUrl(rpcUrl) {
10
+ const trimmed = rpcUrl.trim();
11
+ if (!trimmed) {
12
+ throw new Error("EVM JSON-RPC URL is required.");
13
+ }
14
+ let parsed;
15
+ try {
16
+ parsed = new URL(trimmed);
17
+ } catch {
18
+ throw new Error(
19
+ `Invalid EVM JSON-RPC URL "${trimmed}": expected an http(s) URL.`
20
+ );
21
+ }
22
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
23
+ throw new Error(
24
+ `Invalid EVM JSON-RPC URL "${trimmed}": expected http: or https:.`
25
+ );
26
+ }
27
+ return parsed.toString();
28
+ }
29
+ async function probeJsonRpcEndpoint(rpcUrl, timeoutMs = 2e3) {
30
+ const normalizedRpcUrl = normalizeJsonRpcUrl(rpcUrl);
31
+ const controller = new AbortController();
32
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
33
+ try {
34
+ const response = await fetch(normalizedRpcUrl, {
35
+ method: "POST",
36
+ headers: { "content-type": "application/json" },
37
+ body: JSON.stringify({
38
+ jsonrpc: "2.0",
39
+ id: 1,
40
+ method: "eth_chainId",
41
+ params: []
42
+ }),
43
+ signal: controller.signal
44
+ });
45
+ if (!response.ok) {
46
+ return {
47
+ ok: false,
48
+ reason: `HTTP ${response.status} ${response.statusText}`.trim()
49
+ };
50
+ }
51
+ const body = await response.json();
52
+ if (typeof body.result === "string" && /^0x[0-9a-fA-F]+$/.test(body.result)) {
53
+ return { ok: true };
54
+ }
55
+ const errorMessage = typeof body.error?.message === "string" ? body.error.message : "missing eth_chainId result";
56
+ return { ok: false, reason: errorMessage };
57
+ } catch (err) {
58
+ const message = err instanceof Error && err.name === "AbortError" ? `timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : String(err);
59
+ return { ok: false, reason: message };
60
+ } finally {
61
+ clearTimeout(timeout);
62
+ }
63
+ }
64
+ class TxService {
65
+ provider;
66
+ wallet;
67
+ rpcUrl;
68
+ constructor(rpcUrl, privateKey) {
69
+ if (!isValidPrivateKey(privateKey)) {
70
+ const preview = privateKey.length > 10 ? `${privateKey.slice(0, 6)}...${privateKey.slice(-4)}` : "(empty or too short)";
71
+ throw new Error(
72
+ `Invalid EVM_PRIVATE_KEY: expected 64-character hex string, got ${preview}. Please set a valid private key in your environment or .env file.`
73
+ );
74
+ }
75
+ this.rpcUrl = normalizeJsonRpcUrl(rpcUrl);
76
+ this.provider = new ethers.JsonRpcProvider(this.rpcUrl);
77
+ const normalizedKey = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
78
+ this.wallet = new ethers.Wallet(normalizedKey, this.provider);
79
+ }
80
+ /**
81
+ * Get fresh nonce for the wallet address.
82
+ * Always fetches from blockchain using a fresh provider to avoid caching issues.
83
+ * This ensures we always get the correct nonce even after failed transactions.
84
+ */
85
+ async getFreshNonce() {
86
+ const span = createIntegrationTelemetrySpan({
87
+ boundary: "wallet",
88
+ operation: "rpc_get_nonce"
89
+ });
90
+ const freshProvider = new ethers.JsonRpcProvider(this.rpcUrl);
91
+ try {
92
+ const nonce = await freshProvider.getTransactionCount(
93
+ this.wallet.address,
94
+ "pending"
95
+ );
96
+ span.success();
97
+ return nonce;
98
+ } catch (err) {
99
+ span.failure({ error: err });
100
+ throw err;
101
+ } finally {
102
+ freshProvider.destroy();
103
+ }
104
+ }
105
+ get address() {
106
+ return this.wallet.address;
107
+ }
108
+ async getBalance() {
109
+ return this.provider.getBalance(this.wallet.address);
110
+ }
111
+ async getBalanceFormatted() {
112
+ const balance = await this.getBalance();
113
+ return ethers.formatEther(balance);
114
+ }
115
+ async getChainId() {
116
+ const network = await this.provider.getNetwork();
117
+ return Number(network.chainId);
118
+ }
119
+ getContract(address, abi) {
120
+ return new ethers.Contract(address, abi, this.wallet);
121
+ }
122
+ getReadOnlyContract(address, abi) {
123
+ return new ethers.Contract(address, abi, this.provider);
124
+ }
125
+ async estimateGas(tx) {
126
+ return this.provider.estimateGas(tx);
127
+ }
128
+ async getFeeData() {
129
+ return this.provider.getFeeData();
130
+ }
131
+ /**
132
+ * Wait for a transaction to be mined and return the receipt.
133
+ * Throws if the transaction fails or times out.
134
+ */
135
+ async waitForTransaction(txHash, confirmations = 1, timeoutMs = 12e4) {
136
+ const span = createIntegrationTelemetrySpan({
137
+ boundary: "wallet",
138
+ operation: "rpc_wait_for_transaction",
139
+ timeoutMs
140
+ });
141
+ let receipt;
142
+ try {
143
+ receipt = await this.provider.waitForTransaction(
144
+ txHash,
145
+ confirmations,
146
+ timeoutMs
147
+ );
148
+ } catch (err) {
149
+ span.failure({ error: err });
150
+ throw err;
151
+ }
152
+ if (!receipt) {
153
+ const err = new Error(
154
+ `Transaction ${txHash} timed out after ${timeoutMs}ms`
155
+ );
156
+ span.failure({ error: err, errorKind: "timeout" });
157
+ throw err;
158
+ }
159
+ if (receipt.status === 0) {
160
+ const err = new Error(`Transaction ${txHash} reverted`);
161
+ span.failure({ error: err, errorKind: "tx_reverted" });
162
+ throw err;
163
+ }
164
+ span.success();
165
+ return receipt;
166
+ }
167
+ /**
168
+ * Estimate the gas cost in ETH for a contract call.
169
+ * Useful for showing users how much gas they'll need.
170
+ */
171
+ async estimateGasCostEth(tx) {
172
+ const gasLimit = await this.estimateGas(tx);
173
+ const feeData = await this.getFeeData();
174
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 0n;
175
+ const costWei = gasLimit * gasPrice;
176
+ return ethers.formatEther(costWei);
177
+ }
178
+ /**
179
+ * Check whether the wallet has enough balance for a given value + estimated gas.
180
+ */
181
+ async hasEnoughBalance(value, gasEstimate) {
182
+ const balance = await this.getBalance();
183
+ const feeData = await this.getFeeData();
184
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 0n;
185
+ const gasCost = gasEstimate * gasPrice;
186
+ return balance >= value + gasCost;
187
+ }
188
+ /**
189
+ * Log a summary of the tx service state for diagnostics.
190
+ */
191
+ async logStatus() {
192
+ const [balance, chainId] = await Promise.all([
193
+ this.getBalanceFormatted(),
194
+ this.getChainId()
195
+ ]);
196
+ logger.info(
197
+ `[tx-service] address=${this.address} chain=${chainId} balance=${balance} ETH`
198
+ );
199
+ }
200
+ }
201
+ export {
202
+ TxService,
203
+ normalizeJsonRpcUrl,
204
+ probeJsonRpcEndpoint
205
+ };
206
+ //# sourceMappingURL=tx-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/api/tx-service.ts"],"sourcesContent":["/**\n * Ethereum transaction signing and contract interaction layer.\n *\n * Provides the missing transaction capability to Eliza's wallet system,\n * which currently only handles key generation and balance fetching.\n * Used by the registry and drop services for on-chain operations.\n */\n\nimport { createIntegrationTelemetrySpan } from \"@elizaos/agent\";\nimport { logger } from \"@elizaos/core\";\nimport * as ethers from \"ethers\";\n\nexport interface JsonRpcEndpointProbeResult {\n ok: boolean;\n reason?: string;\n}\n\n/**\n * Validate that a private key is a valid 32-byte hex string.\n */\nfunction isValidPrivateKey(key: string): boolean {\n const normalized = key.startsWith(\"0x\") ? key.slice(2) : key;\n // Must be 64 hex characters (32 bytes)\n if (normalized.length !== 64) return false;\n // Must be valid hex\n return /^[0-9a-fA-F]+$/.test(normalized);\n}\n\nexport function normalizeJsonRpcUrl(rpcUrl: string): string {\n const trimmed = rpcUrl.trim();\n if (!trimmed) {\n throw new Error(\"EVM JSON-RPC URL is required.\");\n }\n\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new Error(\n `Invalid EVM JSON-RPC URL \"${trimmed}\": expected an http(s) URL.`,\n );\n }\n\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(\n `Invalid EVM JSON-RPC URL \"${trimmed}\": expected http: or https:.`,\n );\n }\n\n return parsed.toString();\n}\n\nexport async function probeJsonRpcEndpoint(\n rpcUrl: string,\n timeoutMs = 2_000,\n): Promise<JsonRpcEndpointProbeResult> {\n const normalizedRpcUrl = normalizeJsonRpcUrl(rpcUrl);\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(normalizedRpcUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"eth_chainId\",\n params: [],\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n return {\n ok: false,\n reason: `HTTP ${response.status} ${response.statusText}`.trim(),\n };\n }\n\n const body = (await response.json()) as {\n result?: unknown;\n error?: { message?: unknown };\n };\n if (\n typeof body.result === \"string\" &&\n /^0x[0-9a-fA-F]+$/.test(body.result)\n ) {\n return { ok: true };\n }\n\n const errorMessage =\n typeof body.error?.message === \"string\"\n ? body.error.message\n : \"missing eth_chainId result\";\n return { ok: false, reason: errorMessage };\n } catch (err) {\n const message =\n err instanceof Error && err.name === \"AbortError\"\n ? `timed out after ${timeoutMs}ms`\n : err instanceof Error\n ? err.message\n : String(err);\n return { ok: false, reason: message };\n } finally {\n clearTimeout(timeout);\n }\n}\n\nexport class TxService {\n private readonly provider: ethers.JsonRpcProvider;\n private readonly wallet: ethers.Wallet;\n private readonly rpcUrl: string;\n\n constructor(rpcUrl: string, privateKey: string) {\n // Validate private key before attempting to create wallet\n if (!isValidPrivateKey(privateKey)) {\n const preview =\n privateKey.length > 10\n ? `${privateKey.slice(0, 6)}...${privateKey.slice(-4)}`\n : \"(empty or too short)\";\n throw new Error(\n `Invalid EVM_PRIVATE_KEY: expected 64-character hex string, got ${preview}. ` +\n `Please set a valid private key in your environment or .env file.`,\n );\n }\n\n this.rpcUrl = normalizeJsonRpcUrl(rpcUrl);\n this.provider = new ethers.JsonRpcProvider(this.rpcUrl);\n\n const normalizedKey = privateKey.startsWith(\"0x\")\n ? privateKey\n : `0x${privateKey}`;\n\n // Create wallet with provider\n this.wallet = new ethers.Wallet(normalizedKey, this.provider);\n }\n\n /**\n * Get fresh nonce for the wallet address.\n * Always fetches from blockchain using a fresh provider to avoid caching issues.\n * This ensures we always get the correct nonce even after failed transactions.\n */\n async getFreshNonce(): Promise<number> {\n const span = createIntegrationTelemetrySpan({\n boundary: \"wallet\",\n operation: \"rpc_get_nonce\",\n });\n // Use a fresh provider for each nonce lookup to avoid ethers.js v6 caching\n const freshProvider = new ethers.JsonRpcProvider(this.rpcUrl);\n try {\n const nonce = await freshProvider.getTransactionCount(\n this.wallet.address,\n \"pending\",\n );\n span.success();\n return nonce;\n } catch (err) {\n span.failure({ error: err });\n throw err;\n } finally {\n freshProvider.destroy();\n }\n }\n\n get address(): string {\n return this.wallet.address;\n }\n\n async getBalance(): Promise<bigint> {\n return this.provider.getBalance(this.wallet.address);\n }\n\n async getBalanceFormatted(): Promise<string> {\n const balance = await this.getBalance();\n return ethers.formatEther(balance);\n }\n\n async getChainId(): Promise<number> {\n const network = await this.provider.getNetwork();\n return Number(network.chainId);\n }\n\n getContract(address: string, abi: ethers.InterfaceAbi): ethers.Contract {\n return new ethers.Contract(address, abi, this.wallet);\n }\n\n getReadOnlyContract(\n address: string,\n abi: ethers.InterfaceAbi,\n ): ethers.Contract {\n return new ethers.Contract(address, abi, this.provider);\n }\n\n async estimateGas(tx: ethers.TransactionRequest): Promise<bigint> {\n return this.provider.estimateGas(tx);\n }\n\n async getFeeData(): Promise<ethers.FeeData> {\n return this.provider.getFeeData();\n }\n\n /**\n * Wait for a transaction to be mined and return the receipt.\n * Throws if the transaction fails or times out.\n */\n async waitForTransaction(\n txHash: string,\n confirmations: number = 1,\n timeoutMs: number = 120_000,\n ): Promise<ethers.TransactionReceipt> {\n const span = createIntegrationTelemetrySpan({\n boundary: \"wallet\",\n operation: \"rpc_wait_for_transaction\",\n timeoutMs,\n });\n let receipt: ethers.TransactionReceipt | null;\n try {\n receipt = await this.provider.waitForTransaction(\n txHash,\n confirmations,\n timeoutMs,\n );\n } catch (err) {\n span.failure({ error: err });\n throw err;\n }\n if (!receipt) {\n const err = new Error(\n `Transaction ${txHash} timed out after ${timeoutMs}ms`,\n );\n span.failure({ error: err, errorKind: \"timeout\" });\n throw err;\n }\n if (receipt.status === 0) {\n const err = new Error(`Transaction ${txHash} reverted`);\n span.failure({ error: err, errorKind: \"tx_reverted\" });\n throw err;\n }\n span.success();\n return receipt;\n }\n\n /**\n * Estimate the gas cost in ETH for a contract call.\n * Useful for showing users how much gas they'll need.\n */\n async estimateGasCostEth(tx: ethers.TransactionRequest): Promise<string> {\n const gasLimit = await this.estimateGas(tx);\n const feeData = await this.getFeeData();\n const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 0n;\n const costWei = gasLimit * gasPrice;\n return ethers.formatEther(costWei);\n }\n\n /**\n * Check whether the wallet has enough balance for a given value + estimated gas.\n */\n async hasEnoughBalance(value: bigint, gasEstimate: bigint): Promise<boolean> {\n const balance = await this.getBalance();\n const feeData = await this.getFeeData();\n const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 0n;\n const gasCost = gasEstimate * gasPrice;\n return balance >= value + gasCost;\n }\n\n /**\n * Log a summary of the tx service state for diagnostics.\n */\n async logStatus(): Promise<void> {\n const [balance, chainId] = await Promise.all([\n this.getBalanceFormatted(),\n this.getChainId(),\n ]);\n logger.info(\n `[tx-service] address=${this.address} chain=${chainId} balance=${balance} ETH`,\n );\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * BSC wallet trade routes: preflight, quote, tx-status, trading profile,
3
+ * transfer/execute, and production-defaults.
4
+ *
5
+ * Extracted from server.ts to reduce file size.
6
+ */
7
+ import type http from "node:http";
8
+ import type { ElizaConfig } from "@elizaos/agent";
9
+ import type { ReadJsonBodyOptions } from "@elizaos/core";
10
+ export interface WalletBscRouteDeps {
11
+ getWalletAddresses: () => {
12
+ evmAddress: string | null;
13
+ solanaAddress: string | null;
14
+ };
15
+ resolveWalletRpcReadiness: (config: ElizaConfig) => {
16
+ bscRpcUrls: string[];
17
+ cloudManagedAccess: boolean;
18
+ };
19
+ resolvePrimaryBscRpcUrl: (args: {
20
+ rpcUrls: string[];
21
+ cloudManagedAccess: boolean;
22
+ }) => string | null;
23
+ buildBscTradePreflight: (args: {
24
+ walletAddress: string | null;
25
+ tokenAddress?: string;
26
+ rpcUrls: string[];
27
+ cloudManagedAccess: boolean;
28
+ }) => Promise<unknown>;
29
+ buildBscTradeQuote: (args: {
30
+ walletAddress: string | null;
31
+ rpcUrls: string[];
32
+ cloudManagedAccess: boolean;
33
+ request: {
34
+ side: "buy" | "sell";
35
+ tokenAddress: string;
36
+ amount: string;
37
+ slippageBps?: number;
38
+ routeProvider?: "auto" | "pancakeswap-v2" | "0x";
39
+ };
40
+ }) => Promise<unknown>;
41
+ updateWalletTradeLedgerEntryStatus: (hash: string, update: unknown) => unknown;
42
+ loadWalletTradingProfile: (opts: unknown) => unknown;
43
+ resolveTradePermissionMode: (config: ElizaConfig) => unknown;
44
+ isAgentAutomationRequest: (req: http.IncomingMessage) => boolean;
45
+ canUseLocalTradeExecution: (mode: unknown, isAgentRequest: boolean) => boolean;
46
+ saveElizaConfig: (config: ElizaConfig) => void;
47
+ }
48
+ export interface WalletBscRouteContext {
49
+ req: http.IncomingMessage;
50
+ res: http.ServerResponse;
51
+ method: string;
52
+ pathname: string;
53
+ url: URL;
54
+ state: {
55
+ config: ElizaConfig;
56
+ };
57
+ json: (res: http.ServerResponse, data: unknown, status?: number) => void;
58
+ error: (res: http.ServerResponse, message: string, status?: number) => void;
59
+ readJsonBody: <T extends object>(req: http.IncomingMessage, res: http.ServerResponse, options?: ReadJsonBodyOptions) => Promise<T | null>;
60
+ deps: WalletBscRouteDeps;
61
+ }
62
+ export declare function handleWalletBscRoutes(ctx: WalletBscRouteContext): Promise<boolean>;
63
+ //# sourceMappingURL=wallet-bsc-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet-bsc-routes.d.ts","sourceRoot":"","sources":["../../src/api/wallet-bsc-routes.ts"],"names":[],"mappings":"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"}