@oracle-agent/oracle 0.13.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (449) hide show
  1. package/CONTRIBUTING.md +99 -0
  2. package/README.md +20 -14
  3. package/SECURITY.md +6 -8
  4. package/SETUP.md +25 -27
  5. package/bin/desk-server.mjs +473 -0
  6. package/bin/oracle-data-mcp.mjs +1252 -0
  7. package/bin/oracle-equities.mjs +168 -0
  8. package/bin/oracle-init.mjs +525 -0
  9. package/bin/oracle-public-server.mjs +36 -0
  10. package/bin/oracle-route.mjs +254 -0
  11. package/bin/oracle-scan.mjs +192 -0
  12. package/bin/oracle-upgrade.mjs +42 -0
  13. package/bin/oracle.mjs +34 -0
  14. package/docs/adding-a-chain.md +229 -0
  15. package/docs/architecture.md +139 -0
  16. package/docs/buzz-integration.md +115 -0
  17. package/docs/cli.md +143 -0
  18. package/docs/connectors.md +13 -0
  19. package/docs/hyperliquid-builder-code.md +50 -0
  20. package/docs/locals-only-fee-waiver.md +34 -0
  21. package/docs/oracle-pack-standard.md +29 -0
  22. package/docs/profiles.md +151 -0
  23. package/docs/public-surface.md +206 -0
  24. package/examples/add-a-chain.mjs +65 -0
  25. package/examples/oracle-pack-template.mjs +38 -0
  26. package/examples/research-a-token.mjs +70 -0
  27. package/package.json +78 -32
  28. package/plugins/oracle-owner-gate/__init__.py +288 -0
  29. package/plugins/oracle-owner-gate/plugin.yaml +9 -0
  30. package/{dist/assets/profiles → profiles}/oracle/SOUL.md +0 -14
  31. package/protocols/templates/safe-erc20/SECURITY.md +16 -0
  32. package/protocols/templates/safe-erc20/foundry.toml +12 -0
  33. package/protocols/templates/safe-erc20/remappings.txt +2 -0
  34. package/protocols/templates/safe-erc20/src/SafeERC20.sol +53 -0
  35. package/protocols/templates/safe-erc20/test/SafeERC20.t.sol +72 -0
  36. package/public/oracle-splash/_variants/ice.html +3 -3
  37. package/public/oracle-splash/_variants/ivory.html +3 -3
  38. package/public/oracle-splash/downloads/index.html +34 -223
  39. package/public/oracle-splash/index.html +11 -11
  40. package/scripts/adversarial-bench.mjs +114 -0
  41. package/scripts/build-dist.mjs +119 -0
  42. package/scripts/build-inscription.py +230 -0
  43. package/scripts/check-doc-drift.mjs +123 -0
  44. package/scripts/check-test-count.mjs +105 -0
  45. package/scripts/e2e-hl-markets.mjs +21 -0
  46. package/scripts/e2e-hl-perps.mjs +48 -0
  47. package/scripts/e2e-hypercore-staking.mjs +128 -0
  48. package/scripts/e2e-solana-bitcoin.mjs +183 -0
  49. package/scripts/guard-publish.mjs +22 -0
  50. package/scripts/protocol-template-gate.mjs +15 -0
  51. package/scripts/public-api-scan.mjs +23 -0
  52. package/scripts/publish-dist.mjs +67 -0
  53. package/scripts/record-hero-cli.sh +80 -0
  54. package/scripts/secret-scan.mjs +169 -0
  55. package/scripts/smoke-dist-bins.mjs +89 -0
  56. package/scripts/verify-dist.mjs +29 -0
  57. package/scripts/verify-v3-venues.mjs +233 -0
  58. package/skins/oracle.yaml +56 -0
  59. package/src/action-receipts.mjs +265 -0
  60. package/src/action-semantics.mjs +62 -0
  61. package/src/address-book.mjs +208 -0
  62. package/src/agent-auth.mjs +206 -0
  63. package/src/approval-guard.mjs +282 -0
  64. package/src/attestation-secret.mjs +94 -0
  65. package/src/audit-log.mjs +196 -0
  66. package/src/auth/oauth.mjs +672 -0
  67. package/src/auto-slippage.mjs +378 -0
  68. package/src/capability-posture.mjs +125 -0
  69. package/src/cards.mjs +422 -0
  70. package/src/chains.mjs +62 -0
  71. package/src/cli/chain-catalog.mjs +215 -0
  72. package/src/cli/chain-state.mjs +74 -0
  73. package/src/cli/commands/auth.mjs +193 -0
  74. package/src/cli/commands/bootstrap.mjs +87 -0
  75. package/src/cli/commands/chain.mjs +143 -0
  76. package/src/cli/commands/chat.mjs +465 -0
  77. package/src/cli/commands/credential.mjs +9 -0
  78. package/src/cli/commands/data-mcp.mjs +18 -0
  79. package/src/cli/commands/data.mjs +75 -0
  80. package/src/cli/commands/doctor.mjs +136 -0
  81. package/src/cli/commands/equities.mjs +41 -0
  82. package/src/cli/commands/farm.mjs +129 -0
  83. package/src/cli/commands/fees.mjs +109 -0
  84. package/src/cli/commands/follow.mjs +130 -0
  85. package/src/cli/commands/harness.mjs +63 -0
  86. package/src/cli/commands/help.mjs +7 -0
  87. package/src/cli/commands/init.mjs +27 -0
  88. package/src/cli/commands/mcp.mjs +154 -0
  89. package/src/cli/commands/model.mjs +106 -0
  90. package/src/cli/commands/plugins.mjs +275 -0
  91. package/src/cli/commands/prepare.mjs +12 -0
  92. package/src/cli/commands/public.mjs +20 -0
  93. package/src/cli/commands/resolve.mjs +64 -0
  94. package/src/cli/commands/route.mjs +12 -0
  95. package/src/cli/commands/runner.mjs +9 -0
  96. package/src/cli/commands/scan.mjs +12 -0
  97. package/src/cli/commands/setup.mjs +294 -0
  98. package/src/cli/commands/sign.mjs +21 -0
  99. package/src/cli/commands/signer.mjs +9 -0
  100. package/src/cli/commands/swap.mjs +102 -0
  101. package/src/cli/commands/upgrade.mjs +12 -0
  102. package/src/cli/commands/vault.mjs +9 -0
  103. package/src/cli/commands/version.mjs +22 -0
  104. package/src/cli/first-run.mjs +23 -0
  105. package/src/cli/kernel.mjs +262 -0
  106. package/src/cli/mcp-targets/chatgpt.mjs +51 -0
  107. package/src/cli/mcp-targets/claude-code.mjs +39 -0
  108. package/src/cli/mcp-targets/claude-desktop.mjs +24 -0
  109. package/src/cli/mcp-targets/codex.mjs +41 -0
  110. package/src/cli/mcp-targets/cursor.mjs +32 -0
  111. package/src/cli/mcp-targets/shared.mjs +71 -0
  112. package/src/cli/mcp-targets/vscode.mjs +39 -0
  113. package/src/cli/messaging-platforms.mjs +217 -0
  114. package/src/cli/model-config.mjs +78 -0
  115. package/src/cli/operator-dispatch.mjs +194 -0
  116. package/src/cli/oracle-harness.py +415 -0
  117. package/src/cli/paths.mjs +107 -0
  118. package/src/cli/runtime.mjs +351 -0
  119. package/src/cli/setup-state.mjs +168 -0
  120. package/src/cli/spawn-child.mjs +43 -0
  121. package/src/data/catalog.mjs +858 -0
  122. package/src/data/desk-data.mjs +877 -0
  123. package/src/data/http.mjs +253 -0
  124. package/src/data/names.mjs +188 -0
  125. package/src/data/provider-endpoint.mjs +94 -0
  126. package/src/data/providers/aave-v3.mjs +139 -0
  127. package/src/data/providers/across-bridge.mjs +41 -0
  128. package/src/data/providers/aerodrome.mjs +245 -0
  129. package/src/data/providers/aevo.mjs +33 -0
  130. package/src/data/providers/approval-indexer.mjs +133 -0
  131. package/src/data/providers/approvals.mjs +594 -0
  132. package/src/data/providers/babylon-staking.mjs +149 -0
  133. package/src/data/providers/balancer.mjs +209 -0
  134. package/src/data/providers/beefy-yields.mjs +45 -0
  135. package/src/data/providers/birdeye-tokens.mjs +33 -0
  136. package/src/data/providers/bisq-markets.mjs +145 -0
  137. package/src/data/providers/bitcoin-esplora.mjs +216 -0
  138. package/src/data/providers/bitcoin-meta.mjs +378 -0
  139. package/src/data/providers/blockscout.mjs +14 -0
  140. package/src/data/providers/bob-rootstock.mjs +198 -0
  141. package/src/data/providers/braiins-insights.mjs +103 -0
  142. package/src/data/providers/bridge-aggregator.mjs +259 -0
  143. package/src/data/providers/bridges.mjs +242 -0
  144. package/src/data/providers/bungee.mjs +29 -0
  145. package/src/data/providers/celestia-da.mjs +38 -0
  146. package/src/data/providers/changenow.mjs +135 -0
  147. package/src/data/providers/compound.mjs +32 -0
  148. package/src/data/providers/connext.mjs +28 -0
  149. package/src/data/providers/convex.mjs +30 -0
  150. package/src/data/providers/cow-protocol.mjs +110 -0
  151. package/src/data/providers/cowswap.mjs +415 -0
  152. package/src/data/providers/curve.mjs +201 -0
  153. package/src/data/providers/das-assets.mjs +59 -0
  154. package/src/data/providers/debridge.mjs +109 -0
  155. package/src/data/providers/defillama.mjs +88 -0
  156. package/src/data/providers/dexscreener.mjs +43 -0
  157. package/src/data/providers/drift.mjs +32 -0
  158. package/src/data/providers/dydx.mjs +41 -0
  159. package/src/data/providers/evm-rpc.mjs +225 -0
  160. package/src/data/providers/farming.mjs +291 -0
  161. package/src/data/providers/geckoterminal.mjs +34 -0
  162. package/src/data/providers/gmx.mjs +496 -0
  163. package/src/data/providers/hiro-stacks.mjs +150 -0
  164. package/src/data/providers/hl-assets.mjs +165 -0
  165. package/src/data/providers/hl-info.mjs +108 -0
  166. package/src/data/providers/hl-markets.mjs +210 -0
  167. package/src/data/providers/hl-outcome.mjs +61 -0
  168. package/src/data/providers/hl-perps.mjs +419 -0
  169. package/src/data/providers/hl-staking.mjs +353 -0
  170. package/src/data/providers/hl-ws.mjs +119 -0
  171. package/src/data/providers/hyperevm-dex.mjs +49 -0
  172. package/src/data/providers/jito-mev.mjs +117 -0
  173. package/src/data/providers/jupiter-venues.mjs +119 -0
  174. package/src/data/providers/jupiter.mjs +315 -0
  175. package/src/data/providers/kamino-strategies.mjs +143 -0
  176. package/src/data/providers/l2beat-tvl.mjs +42 -0
  177. package/src/data/providers/lifi.mjs +166 -0
  178. package/src/data/providers/liquid-esplora.mjs +152 -0
  179. package/src/data/providers/magiceden-ordinals.mjs +141 -0
  180. package/src/data/providers/magiceden-sol.mjs +406 -0
  181. package/src/data/providers/mempool-lightning.mjs +144 -0
  182. package/src/data/providers/mempool-mining.mjs +142 -0
  183. package/src/data/providers/meteora-dlmm.mjs +168 -0
  184. package/src/data/providers/morpho.mjs +177 -0
  185. package/src/data/providers/nft-gallery.mjs +163 -0
  186. package/src/data/providers/nft-portfolio.mjs +494 -0
  187. package/src/data/providers/odos.mjs +156 -0
  188. package/src/data/providers/ondo.mjs +31 -0
  189. package/src/data/providers/oneinch.mjs +174 -0
  190. package/src/data/providers/opensea-multichain.mjs +136 -0
  191. package/src/data/providers/opensea-nft.mjs +371 -0
  192. package/src/data/providers/orca.mjs +29 -0
  193. package/src/data/providers/ordinals-runes.mjs +180 -0
  194. package/src/data/providers/pancakeswap.mjs +28 -0
  195. package/src/data/providers/paraswap.mjs +125 -0
  196. package/src/data/providers/pendle.mjs +188 -0
  197. package/src/data/providers/poly-clob.mjs +283 -0
  198. package/src/data/providers/poly-public.mjs +96 -0
  199. package/src/data/providers/poly-ws.mjs +103 -0
  200. package/src/data/providers/polygon-staking.mjs +227 -0
  201. package/src/data/providers/portfolio-history.mjs +394 -0
  202. package/src/data/providers/portfolio.mjs +600 -0
  203. package/src/data/providers/pyth-price-feeds.mjs +172 -0
  204. package/src/data/providers/raydium.mjs +35 -0
  205. package/src/data/providers/relayscan-mev.mjs +72 -0
  206. package/src/data/providers/rfq.mjs +15 -0
  207. package/src/data/providers/rh-agent.mjs +59 -0
  208. package/src/data/providers/sanctum-lst.mjs +180 -0
  209. package/src/data/providers/satflow.mjs +341 -0
  210. package/src/data/providers/signed-material-guard.mjs +108 -0
  211. package/src/data/providers/solana-rpc.mjs +197 -0
  212. package/src/data/providers/solend-lending.mjs +166 -0
  213. package/src/data/providers/spark-lend.mjs +42 -0
  214. package/src/data/providers/squid-router.mjs +29 -0
  215. package/src/data/providers/stargate.mjs +29 -0
  216. package/src/data/providers/sushiswap.mjs +30 -0
  217. package/src/data/providers/symbiotic-restaking.mjs +41 -0
  218. package/src/data/providers/thorchain.mjs +36 -0
  219. package/src/data/providers/traderjoe.mjs +28 -0
  220. package/src/data/providers/twap.mjs +152 -0
  221. package/src/data/providers/uniswap-v3.mjs +457 -0
  222. package/src/data/providers/uniswap-v4.mjs +414 -0
  223. package/src/data/providers/vertex.mjs +41 -0
  224. package/src/data/providers/wallet-watch.mjs +264 -0
  225. package/src/data/providers/wormhole.mjs +28 -0
  226. package/src/data/providers/yearn.mjs +31 -0
  227. package/src/data/providers/zerox.mjs +167 -0
  228. package/src/data/public-api-scan.mjs +61 -0
  229. package/src/data/quote-placeholder.mjs +31 -0
  230. package/src/desk/swap-prepare.mjs +194 -0
  231. package/src/equities/bridge.mjs +388 -0
  232. package/src/equities/fixtures/bridge_routes.json +159 -0
  233. package/src/equities/fixtures/dst_transitions.json +29 -0
  234. package/src/equities/fixtures/equity_dex_pools.json +154 -0
  235. package/src/equities/fixtures/hip3_l2books.json +2711 -0
  236. package/src/equities/fixtures/nyse_holidays.json +1 -0
  237. package/src/equities/fixtures/oracles.json +100 -0
  238. package/src/equities/fixtures/rh_pool_state.json +244 -0
  239. package/src/equities/fixtures/solana_xstocks.json +704 -0
  240. package/src/equities/fixtures/ton_xstocks.json +35 -0
  241. package/src/equities/fixtures/universe.json +411 -0
  242. package/src/equities/fixtures/venues.json +3542 -0
  243. package/src/equities/fixtures.mjs +405 -0
  244. package/src/equities/funding.mjs +142 -0
  245. package/src/equities/index.mjs +166 -0
  246. package/src/equities/liveness.mjs +148 -0
  247. package/src/equities/marketHours.mjs +249 -0
  248. package/src/equities/normalize.mjs +295 -0
  249. package/src/equities/num.mjs +146 -0
  250. package/src/equities/prepare.mjs +95 -0
  251. package/src/equities/rank.mjs +260 -0
  252. package/src/equities/sourceNum.mjs +69 -0
  253. package/src/equities/types.mjs +220 -0
  254. package/src/equities/venues/arcus-perp.mjs +84 -0
  255. package/src/equities/venues/arcus-spot.mjs +105 -0
  256. package/src/equities/venues/hyperliquid-hip3.mjs +224 -0
  257. package/src/equities/venues/rh-uniswap.mjs +216 -0
  258. package/src/equities/venues/solana-xstocks.mjs +139 -0
  259. package/src/equities/venues/ton-stonfi.mjs +139 -0
  260. package/src/exact-integer.mjs +72 -0
  261. package/src/exec-policy.mjs +419 -0
  262. package/src/flags.mjs +15 -0
  263. package/src/fresh-window.mjs +76 -0
  264. package/src/gmx-attestation.mjs +176 -0
  265. package/src/index.mjs +107 -0
  266. package/src/licensing/locals-only.mjs +25 -0
  267. package/src/nft-gas-war-guard.mjs +139 -0
  268. package/src/onboarding/agent-keys.mjs +161 -0
  269. package/src/onboarding/harness-configs.mjs +102 -0
  270. package/src/onboarding/index.mjs +18 -0
  271. package/src/onboarding/tiers.mjs +139 -0
  272. package/src/oracle-env.mjs +55 -0
  273. package/src/package-manifest.mjs +42 -0
  274. package/src/plugins/agent-plugin-loader.mjs +225 -0
  275. package/src/portfolio-risk.mjs +170 -0
  276. package/src/prepare-envelope.mjs +204 -0
  277. package/src/profile-upgrade.mjs +277 -0
  278. package/src/protocol-execution.mjs +84 -0
  279. package/src/protocol-templates/gate.mjs +179 -0
  280. package/src/protocol-templates/prepare-deploy.mjs +81 -0
  281. package/src/public-api/agent-grants.mjs +57 -0
  282. package/src/public-api/buzz-integration.mjs +267 -0
  283. package/src/public-api/connect-agent.mjs +424 -0
  284. package/src/public-api/grants.mjs +142 -0
  285. package/src/public-api/http.mjs +707 -0
  286. package/src/public-api/metering.mjs +111 -0
  287. package/src/public-control/aa-adapter.mjs +402 -0
  288. package/src/public-control/build-registry.mjs +227 -0
  289. package/src/public-control/bundler-client.mjs +357 -0
  290. package/src/public-control/grant-indexer.mjs +296 -0
  291. package/src/public-control/policy-render.mjs +69 -0
  292. package/src/public-control/policy-schema.mjs +322 -0
  293. package/src/public-control/runtime-config.mjs +275 -0
  294. package/src/public-control/session-key-model.mjs +379 -0
  295. package/src/public-control/session-orchestrator.mjs +418 -0
  296. package/src/rfq/intent.mjs +180 -0
  297. package/src/rfq/sources.mjs +181 -0
  298. package/src/route-attestation.mjs +132 -0
  299. package/src/router/best-execution.mjs +326 -0
  300. package/src/router/index.mjs +250 -0
  301. package/src/router/integrator-fee.mjs +135 -0
  302. package/src/router/prepare-bridge.mjs +293 -0
  303. package/src/router/prepare-route.mjs +398 -0
  304. package/src/router/proposal.mjs +311 -0
  305. package/src/router/risk-classifier.mjs +119 -0
  306. package/src/router/route-sources.mjs +364 -0
  307. package/src/scanner/chains.config.mjs +543 -0
  308. package/src/scanner/contract.mjs +270 -0
  309. package/src/scanner/evm-scanner.mjs +394 -0
  310. package/src/scanner/index.mjs +9 -0
  311. package/src/scanner/v2-venue.mjs +351 -0
  312. package/src/scanner/v3-venue.mjs +308 -0
  313. package/src/scopes.mjs +44 -0
  314. package/src/sell-simulation.mjs +167 -0
  315. package/src/signals/engine.mjs +146 -0
  316. package/src/signals/index.mjs +1 -0
  317. package/src/token-transfer-guard.mjs +188 -0
  318. package/src/tui/app.mjs +438 -0
  319. package/src/tui/backend.mjs +215 -0
  320. package/src/tui/format.mjs +272 -0
  321. package/src/tui/gateway-client.mjs +177 -0
  322. package/src/tui/input.mjs +501 -0
  323. package/src/tui/memory.mjs +105 -0
  324. package/src/tui/meta-learn.mjs +85 -0
  325. package/src/tui/renderer.mjs +443 -0
  326. package/src/tui/scheduler.mjs +205 -0
  327. package/src/tui/skills-loader.mjs +140 -0
  328. package/src/tui/standalone-client.mjs +752 -0
  329. package/src/tui/theme.mjs +81 -0
  330. package/src/vault-attestation.mjs +146 -0
  331. package/src/venues.mjs +206 -0
  332. package/src/watch-preferences.mjs +85 -0
  333. package/dist/action-receipts.mjs +0 -1
  334. package/dist/action-semantics.mjs +0 -1
  335. package/dist/address-book.mjs +0 -2
  336. package/dist/bin/desk-server.mjs +0 -59
  337. package/dist/bin/oracle-data-mcp.mjs +0 -8
  338. package/dist/bin/oracle-equities.mjs +0 -20
  339. package/dist/bin/oracle-init.mjs +0 -24
  340. package/dist/bin/oracle-public-server.mjs +0 -5
  341. package/dist/bin/oracle-route.mjs +0 -22
  342. package/dist/bin/oracle-scan.mjs +0 -18
  343. package/dist/bin/oracle-upgrade.mjs +0 -21
  344. package/dist/bin/oracle.mjs +0 -76
  345. package/dist/chains.mjs +0 -1
  346. package/dist/cli/commands/auth.mjs +0 -16
  347. package/dist/cli/commands/bootstrap.mjs +0 -18
  348. package/dist/cli/commands/chain.mjs +0 -21
  349. package/dist/cli/commands/chat.mjs +0 -45
  350. package/dist/cli/commands/credential.mjs +0 -1
  351. package/dist/cli/commands/data-mcp.mjs +0 -2
  352. package/dist/cli/commands/data.mjs +0 -12
  353. package/dist/cli/commands/doctor.mjs +0 -5
  354. package/dist/cli/commands/equities.mjs +0 -15
  355. package/dist/cli/commands/farm.mjs +0 -59
  356. package/dist/cli/commands/follow.mjs +0 -28
  357. package/dist/cli/commands/gate.mjs +0 -13
  358. package/dist/cli/commands/harness.mjs +0 -15
  359. package/dist/cli/commands/help.mjs +0 -1
  360. package/dist/cli/commands/init.mjs +0 -3
  361. package/dist/cli/commands/mcp.mjs +0 -60
  362. package/dist/cli/commands/model.mjs +0 -54
  363. package/dist/cli/commands/plugins.mjs +0 -38
  364. package/dist/cli/commands/prepare.mjs +0 -2
  365. package/dist/cli/commands/public.mjs +0 -3
  366. package/dist/cli/commands/resolve.mjs +0 -23
  367. package/dist/cli/commands/route.mjs +0 -2
  368. package/dist/cli/commands/runner.mjs +0 -1
  369. package/dist/cli/commands/scan.mjs +0 -2
  370. package/dist/cli/commands/setup.mjs +0 -34
  371. package/dist/cli/commands/sign.mjs +0 -21
  372. package/dist/cli/commands/signer.mjs +0 -1
  373. package/dist/cli/commands/swap.mjs +0 -36
  374. package/dist/cli/commands/upgrade.mjs +0 -2
  375. package/dist/cli/commands/vault.mjs +0 -1
  376. package/dist/cli/commands/version.mjs +0 -2
  377. package/dist/data/desk-data.mjs +0 -55
  378. package/dist/data/names.mjs +0 -2
  379. package/dist/data/providers/farming.mjs +0 -2
  380. package/dist/equities/index.mjs +0 -1
  381. package/dist/index.mjs +0 -60
  382. package/dist/nft-gas-war-guard.mjs +0 -1
  383. package/dist/onboarding/harness-configs.mjs +0 -4
  384. package/dist/portfolio-risk.mjs +0 -1
  385. package/dist/prepare-envelope.mjs +0 -1
  386. package/dist/public-api/agent-grants.mjs +0 -3
  387. package/dist/public-control/policy-schema.mjs +0 -1
  388. package/dist/router/index.mjs +0 -2
  389. package/dist/scanner/index.mjs +0 -4
  390. package/dist/signals/index.mjs +0 -1
  391. package/dist/watch-preferences.mjs +0 -1
  392. /package/{dist/assets/profiles → profiles}/_template/SOUL.md +0 -0
  393. /package/{dist/assets/profiles → profiles}/_template/profile.json +0 -0
  394. /package/{dist/assets/profiles → profiles}/bitcoin-agent/SOUL.md +0 -0
  395. /package/{dist/assets/profiles → profiles}/bitcoin-agent/profile.json +0 -0
  396. /package/{dist/assets/profiles → profiles}/hyperliquid-agent/SOUL.md +0 -0
  397. /package/{dist/assets/profiles → profiles}/hyperliquid-agent/profile.json +0 -0
  398. /package/{dist/assets/profiles → profiles}/oracle/profile.json +0 -0
  399. /package/{dist/assets/profiles → profiles}/polymarket-agent/SOUL.md +0 -0
  400. /package/{dist/assets/profiles → profiles}/polymarket-agent/profile.json +0 -0
  401. /package/{dist/assets/profiles → profiles}/profile.schema.json +0 -0
  402. /package/{dist/assets/profiles → profiles}/protocol-builder/SOUL.md +0 -0
  403. /package/{dist/assets/profiles → profiles}/protocol-builder/profile.json +0 -0
  404. /package/{dist/assets/profiles → profiles}/robinhood-agent/SOUL.md +0 -0
  405. /package/{dist/assets/profiles → profiles}/robinhood-agent/profile.json +0 -0
  406. /package/{dist/assets/profiles → profiles}/solana-agent/SOUL.md +0 -0
  407. /package/{dist/assets/profiles → profiles}/solana-agent/profile.json +0 -0
  408. /package/{dist/assets/profiles → profiles}/stable-agent/SOUL.md +0 -0
  409. /package/{dist/assets/profiles → profiles}/stable-agent/profile.json +0 -0
  410. /package/{dist/assets/skills → skills}/balance/SKILL.md +0 -0
  411. /package/{dist/assets/skills → skills}/bitcoin-l1.md +0 -0
  412. /package/{dist/assets/skills → skills}/chain-defi-ecosystem-profiling.md +0 -0
  413. /package/{dist/assets/skills → skills}/evm-contract-research.md +0 -0
  414. /package/{dist/assets/skills → skills}/hyperliquid.md +0 -0
  415. /package/{dist/assets/skills → skills}/multichain-exec-desk.md +0 -0
  416. /package/{dist/assets/skills → skills}/oracle-action-semantics/SKILL.md +0 -0
  417. /package/{dist/assets/skills → skills}/oracle-best-execution/SKILL.md +0 -0
  418. /package/{dist/assets/skills → skills}/oracle-best-execution.md +0 -0
  419. /package/{dist/assets/skills → skills}/oracle-bitcoin/SKILL.md +0 -0
  420. /package/{dist/assets/skills → skills}/oracle-chain-graphs-telegram-cards/SKILL.md +0 -0
  421. /package/{dist/assets/skills → skills}/oracle-chat/SKILL.md +0 -0
  422. /package/{dist/assets/skills → skills}/oracle-chat/chain.SKILL.md +0 -0
  423. /package/{dist/assets/skills → skills}/oracle-chat/setup.SKILL.md +0 -0
  424. /package/{dist/assets/skills → skills}/oracle-circuit-breaker/SKILL.md +0 -0
  425. /package/{dist/assets/skills → skills}/oracle-contract-research/SKILL.md +0 -0
  426. /package/{dist/assets/skills → skills}/oracle-desk/SKILL.md +0 -0
  427. /package/{dist/assets/skills → skills}/oracle-desk.md +0 -0
  428. /package/{dist/assets/skills → skills}/oracle-dex-launch/SKILL.md +0 -0
  429. /package/{dist/assets/skills → skills}/oracle-equities/SKILL.md +0 -0
  430. /package/{dist/assets/skills → skills}/oracle-grants/SKILL.md +0 -0
  431. /package/{dist/assets/skills → skills}/oracle-hypercore-staking/SKILL.md +0 -0
  432. /package/{dist/assets/skills → skills}/oracle-hyperliquid/SKILL.md +0 -0
  433. /package/{dist/assets/skills → skills}/oracle-meme-token-sniper/SKILL.md +0 -0
  434. /package/{dist/assets/skills → skills}/oracle-multichain-nft-launch/SKILL.md +0 -0
  435. /package/{dist/assets/skills → skills}/oracle-multichain-token-launch/SKILL.md +0 -0
  436. /package/{dist/assets/skills → skills}/oracle-nft-gacha-launch/SKILL.md +0 -0
  437. /package/{dist/assets/skills → skills}/oracle-nft-mint-gas-war/SKILL.md +0 -0
  438. /package/{dist/assets/skills → skills}/oracle-polymarket/SKILL.md +0 -0
  439. /package/{dist/assets/skills → skills}/oracle-protocol-builder/SKILL.md +0 -0
  440. /package/{dist/assets/skills → skills}/oracle-protocol-security/SKILL.md +0 -0
  441. /package/{dist/assets/skills → skills}/oracle-public-product/SKILL.md +0 -0
  442. /package/{dist/assets/skills → skills}/oracle-receipts/SKILL.md +0 -0
  443. /package/{dist/assets/skills → skills}/oracle-receipts.md +0 -0
  444. /package/{dist/assets/skills → skills}/oracle-rfq-tokenized-assets/SKILL.md +0 -0
  445. /package/{dist/assets/skills → skills}/oracle-smart-wallet-scanner/SKILL.md +0 -0
  446. /package/{dist/assets/skills → skills}/oracle-solana/SKILL.md +0 -0
  447. /package/{dist/assets/skills → skills}/oracle-solana-nft/SKILL.md +0 -0
  448. /package/{dist/assets/skills → skills}/oracle-token-research/SKILL.md +0 -0
  449. /package/{dist/assets/skills → skills}/solana.md +0 -0
@@ -0,0 +1,296 @@
1
+ // Oracle Control — grant audit + active-permission indexer (Slice F).
2
+ //
3
+ // Deterministic, storage-agnostic reconstruction of grant state from an
4
+ // append-only event log. Oracle Control is DETERMINISTIC AUTHORIZATION: this
5
+ // module never guesses, never consults Oracle Router (advisory), and never
6
+ // keeps its own mutable state. The chain (or whatever emits the event log)
7
+ // is the sole source of truth; this file is a pure projection of that log
8
+ // plus an explicit reference `now`.
9
+ //
10
+ // Hard boundaries for this file:
11
+ // - No imports outside src/public-control/policy-schema.mjs (grant identity
12
+ // + expiry helpers are reused from there, never duplicated).
13
+ // - No import from the private executor stack, no RPC/chain calls, no I/O.
14
+ // - Pure functions only: reconstructGrantIndex(events, opts) always returns
15
+ // the same result for the same (events, opts.now) — same events replayed
16
+ // after a "restart" (fresh process, no cache, no prior in-memory state)
17
+ // reconstruct the identical active set. Input array order never matters.
18
+ // - Audit/output records are built from an explicit field whitelist only.
19
+ // Unknown/extra properties on raw events (or on a rejected grant payload)
20
+ // are NEVER copied into output, so a poisoned event can't smuggle a
21
+ // secret (API key, session key, keystore path, ...) into the index.
22
+ //
23
+ // Event log shape (each entry is a plain object; unknown extra fields are
24
+ // ignored, never echoed):
25
+ // { type: "grantCreated", at: <unix seconds>, grant: <raw grant>, grantId?: <string> }
26
+ // { type: "grantActivated", at: <unix seconds>, grantId: <string> }
27
+ // { type: "grantRevoked", at: <unix seconds>, grantId: <string>, reason?: <string> }
28
+ // { type: "grantExpired", at: <unix seconds>, grantId: <string> }
29
+ //
30
+ // `grantId` on a grantCreated event is optional: when omitted it is derived
31
+ // from the grant payload itself via policy-schema's grantId() (content
32
+ // addressed identity). When supplied it is cross-checked against the derived
33
+ // id and the event is rejected on mismatch (fail closed, no id spoofing).
34
+ //
35
+ // Status precedence (revocation beats expiry beats activity — mirrors the
36
+ // same precedence used by session-key-model.mjs's sessionStatus): a grant
37
+ // that was revoked and later would also have expired still reports revoked.
38
+ // REVOKED > EXPIRED > ACTIVE > PENDING > UNKNOWN
39
+ // Only ACTIVE grants are ever included in the `active` set.
40
+
41
+ import { normalizeGrant, grantId as computeGrantId, isExpired, GrantValidationError } from "./policy-schema.mjs";
42
+
43
+ export const GRANT_EVENT_TYPES = Object.freeze([
44
+ "grantCreated",
45
+ "grantActivated",
46
+ "grantRevoked",
47
+ "grantExpired",
48
+ ]);
49
+
50
+ export const GRANT_STATUS = Object.freeze({
51
+ ACTIVE: "active",
52
+ PENDING: "pending",
53
+ REVOKED: "revoked",
54
+ EXPIRED: "expired",
55
+ UNKNOWN: "unknown",
56
+ INVALID: "invalid",
57
+ });
58
+
59
+ /** Fields ever allowed into an audit/output record. Nothing outside this
60
+ * whitelist can reach the returned index, no matter what a raw event or
61
+ * grant payload carries. All of these are public-by-design fields already
62
+ * enforced by policy-schema.mjs's REQUIRED_FIELDS. */
63
+ const GRANT_SUMMARY_FIELDS = Object.freeze([
64
+ "chainId",
65
+ "agentAddress",
66
+ "accountAddress",
67
+ "actions",
68
+ "targets",
69
+ "maxValueWei",
70
+ "maxGasWei",
71
+ "maxSlippageBps",
72
+ "expiresAt",
73
+ "nonce",
74
+ "revocationKey",
75
+ ]);
76
+
77
+ function isPlainObject(v) {
78
+ return v != null && typeof v === "object" && !Array.isArray(v);
79
+ }
80
+
81
+ function toUnixSeconds(v) {
82
+ if (typeof v === "bigint") return Number(v);
83
+ if (typeof v === "number" && Number.isFinite(v)) return v;
84
+ if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v.trim()))) {
85
+ return Number(v.trim());
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function toSafeString(v, maxLen = 512) {
91
+ if (typeof v !== "string") return null;
92
+ const s = v.trim();
93
+ if (!s) return null;
94
+ return s.length > maxLen ? s.slice(0, maxLen) : s;
95
+ }
96
+
97
+ function grantSummary(normalizedGrant) {
98
+ const out = {};
99
+ for (const field of GRANT_SUMMARY_FIELDS) out[field] = normalizedGrant[field];
100
+ return out;
101
+ }
102
+
103
+ /**
104
+ * Parse one raw event into a strictly-whitelisted internal shape. Never
105
+ * throws. Extra/unknown properties on `raw` are silently dropped (this is
106
+ * the point — it's the no-secret-leak boundary for whatever noise a chain
107
+ * indexer/relay might attach to an event object).
108
+ *
109
+ * Returns { ok: true, event } or { ok: false, error: { type, at, grantId, message } }.
110
+ */
111
+ function parseEvent(raw, opts) {
112
+ if (!isPlainObject(raw)) {
113
+ return { ok: false, error: { type: null, at: null, grantId: null, message: "event must be a plain object" } };
114
+ }
115
+
116
+ const type = typeof raw.type === "string" ? raw.type : null;
117
+ const at = toUnixSeconds(raw.at);
118
+ const grantIdHint = typeof raw.grantId === "string" && raw.grantId.trim() ? raw.grantId.trim() : null;
119
+
120
+ if (!GRANT_EVENT_TYPES.includes(type)) {
121
+ return { ok: false, error: { type, at, grantId: grantIdHint, message: `unknown event type "${String(type)}"` } };
122
+ }
123
+ if (at == null) {
124
+ return { ok: false, error: { type, at: null, grantId: grantIdHint, message: "event.at must be a unix-seconds number" } };
125
+ }
126
+
127
+ if (type === "grantCreated") {
128
+ if (!isPlainObject(raw.grant)) {
129
+ return { ok: false, error: { type, at, grantId: grantIdHint, message: "grantCreated requires a grant payload" } };
130
+ }
131
+ let normalized;
132
+ try {
133
+ // Identity + shape validation only — deliberately WITHOUT opts.now, so
134
+ // a grant that has since expired can still be identified/reconstructed
135
+ // (liveness is evaluated separately against the caller's `now`).
136
+ normalized = normalizeGrant(raw.grant, { allowWildcardActions: opts.allowWildcardActions });
137
+ } catch (e) {
138
+ const message = e instanceof GrantValidationError ? e.message : String(e?.message || e);
139
+ return { ok: false, error: { type, at, grantId: grantIdHint, message } };
140
+ }
141
+ const derivedId = computeGrantId(raw.grant, { allowWildcardActions: opts.allowWildcardActions });
142
+ if (grantIdHint && grantIdHint !== derivedId) {
143
+ return {
144
+ ok: false,
145
+ error: { type, at, grantId: grantIdHint, message: "grantId does not match content-derived grant identity (fail closed)" },
146
+ };
147
+ }
148
+ return { ok: true, event: { type, at, grantId: derivedId, grant: normalized } };
149
+ }
150
+
151
+ // grantActivated / grantRevoked / grantExpired all reference an existing
152
+ // grant purely by id — they never carry grant material.
153
+ if (!grantIdHint) {
154
+ return { ok: false, error: { type, at, grantId: null, message: `${type} requires a grantId` } };
155
+ }
156
+ const reason = type === "grantRevoked" ? toSafeString(raw.reason) : null;
157
+ return { ok: true, event: { type, at, grantId: grantIdHint, reason } };
158
+ }
159
+
160
+ function minOf(list) {
161
+ return list.length ? list.reduce((a, b) => Math.min(a, b)) : null;
162
+ }
163
+
164
+ /**
165
+ * Reconstruct the deterministic grant index from a raw event log.
166
+ *
167
+ * @param {object[]} events raw event log (any order, any origin)
168
+ * @param {object} opts
169
+ * @param {number} opts.now REQUIRED unix-seconds reference time. All
170
+ * reconstruction is relative to this instant;
171
+ * pass the same `now` to get the same result.
172
+ * @param {boolean} [opts.allowWildcardActions] forwarded to policy-schema.
173
+ *
174
+ * @returns {{
175
+ * active: object[], grants currently ACTIVE, sorted by grantId
176
+ * historical: object[], everything else (pending/revoked/expired/unknown/invalid), sorted by grantId
177
+ * all: object[], active + historical, sorted by grantId
178
+ * invalidEvents: object[] malformed/rejected raw events (whitelisted fields only)
179
+ * }}
180
+ */
181
+ export function reconstructGrantIndex(events, opts = {}) {
182
+ const now = toUnixSeconds(opts.now);
183
+ if (now == null) throw new Error("grant-indexer: opts.now (unix seconds) is required");
184
+ if (!Array.isArray(events)) throw new Error("grant-indexer: events must be an array");
185
+
186
+ const invalidEvents = [];
187
+ /** grantId -> { created: [], activated: [], revoked: [], expired: [] } */
188
+ const byId = new Map();
189
+
190
+ function bucket(id) {
191
+ if (!byId.has(id)) {
192
+ byId.set(id, { created: [], activated: [], revoked: [], expired: [] });
193
+ }
194
+ return byId.get(id);
195
+ }
196
+
197
+ for (const raw of events) {
198
+ const parsed = parseEvent(raw, opts);
199
+ if (!parsed.ok) {
200
+ invalidEvents.push(parsed.error);
201
+ continue;
202
+ }
203
+ const ev = parsed.event;
204
+ // Chain is source of truth for what has already happened: an event dated
205
+ // after the reconstruction instant hasn't occurred *yet* from this
206
+ // vantage point, so it is excluded rather than applied early.
207
+ if (ev.at > now) continue;
208
+
209
+ const b = bucket(ev.grantId);
210
+ if (ev.type === "grantCreated") {
211
+ // Same content-derived id => identical normalized grant; duplicate
212
+ // creations are idempotent. Keep the earliest-by-time occurrence so
213
+ // createdAt is stable and order-independent.
214
+ const existing = b.created[0];
215
+ if (!existing || ev.at < existing.at) b.created = [ev];
216
+ } else if (ev.type === "grantActivated") {
217
+ b.activated.push(ev);
218
+ } else if (ev.type === "grantRevoked") {
219
+ b.revoked.push(ev);
220
+ } else if (ev.type === "grantExpired") {
221
+ b.expired.push(ev);
222
+ }
223
+ }
224
+
225
+ const records = [];
226
+ for (const [id, b] of byId.entries()) {
227
+ const createdEvent = b.created[0] || null;
228
+ const createdAt = createdEvent ? createdEvent.at : null;
229
+ const activatedAt = minOf(b.activated.map((e) => e.at));
230
+ const revokedAt = minOf(b.revoked.map((e) => e.at));
231
+ const explicitExpiredAt = minOf(b.expired.map((e) => e.at));
232
+
233
+ const grant = createdEvent ? createdEvent.grant : null;
234
+ const schemaExpired = grant ? isExpired(grant, now) : false;
235
+ const expiredAt = explicitExpiredAt != null ? explicitExpiredAt : schemaExpired ? grant.expiresAt : null;
236
+
237
+ let status;
238
+ if (!createdEvent) {
239
+ // Lifecycle events (activated/revoked/expired) referencing a grantId
240
+ // we never saw a valid grantCreated for. Never active — there is no
241
+ // grant content to authorize against.
242
+ status = GRANT_STATUS.UNKNOWN;
243
+ } else if (b.revoked.length > 0) {
244
+ status = GRANT_STATUS.REVOKED;
245
+ } else if (explicitExpiredAt != null || schemaExpired) {
246
+ status = GRANT_STATUS.EXPIRED;
247
+ } else if (b.activated.length > 0) {
248
+ status = GRANT_STATUS.ACTIVE;
249
+ } else {
250
+ status = GRANT_STATUS.PENDING;
251
+ }
252
+
253
+ const record = {
254
+ grantId: id,
255
+ status,
256
+ createdAt,
257
+ activatedAt,
258
+ revokedAt,
259
+ expiredAt,
260
+ reason: b.revoked.length > 0 ? toSafeString(b.revoked.map((e) => e.reason).find(Boolean) || null) : null,
261
+ grant: grant ? grantSummary(grant) : null,
262
+ };
263
+ records.push(record);
264
+ }
265
+
266
+ records.sort((a, b) => (a.grantId < b.grantId ? -1 : a.grantId > b.grantId ? 1 : 0));
267
+ invalidEvents.sort((a, b) => {
268
+ const at = (a.at ?? -Infinity) - (b.at ?? -Infinity);
269
+ if (at !== 0) return at;
270
+ const ai = a.grantId || "";
271
+ const bi = b.grantId || "";
272
+ return ai < bi ? -1 : ai > bi ? 1 : 0;
273
+ });
274
+
275
+ const active = records.filter((r) => r.status === GRANT_STATUS.ACTIVE);
276
+ const historical = records.filter((r) => r.status !== GRANT_STATUS.ACTIVE);
277
+
278
+ return Object.freeze({
279
+ active: Object.freeze(active),
280
+ historical: Object.freeze(historical),
281
+ all: Object.freeze(records),
282
+ invalidEvents: Object.freeze(invalidEvents),
283
+ });
284
+ }
285
+
286
+ /** Convenience: is a single grant id active right now, per the given log? */
287
+ export function isGrantActive(events, grantId, opts = {}) {
288
+ const { active } = reconstructGrantIndex(events, opts);
289
+ return active.some((r) => r.grantId === grantId);
290
+ }
291
+
292
+ /** Convenience: look up one grant's reconstructed record (or null). */
293
+ export function getGrantRecord(events, grantId, opts = {}) {
294
+ const { all } = reconstructGrantIndex(events, opts);
295
+ return all.find((r) => r.grantId === grantId) || null;
296
+ }
@@ -0,0 +1,69 @@
1
+ // Oracle Control — human-readable grant renderer (Slice A).
2
+ //
3
+ // Renders a validated public grant into deterministic plain text a user can
4
+ // review before approving. Rendering is a PRESENTATION of the deterministic
5
+ // policy only — it never adds, removes, or reinterprets authority, and it only
6
+ // ever surfaces public grant material (no keys, tokens, paths, or secrets).
7
+ //
8
+ // Output is byte-for-byte deterministic for a given grant: fixed field order,
9
+ // fixed labels, UTC timestamps derived purely from expiresAt, and the sha256
10
+ // grant id from the canonical form. Tests assert exact output.
11
+
12
+ import { normalizeGrant, grantId, isReadonlyAction, GRANT_VERSION } from "./policy-schema.mjs";
13
+
14
+ const LABEL_WIDTH = 14;
15
+
16
+ function row(label, value) {
17
+ return `${(label + ":").padEnd(LABEL_WIDTH)}${value}`;
18
+ }
19
+
20
+ /** Deterministic UTC ISO string from unix seconds. */
21
+ function isoUtc(unixSeconds) {
22
+ return new Date(unixSeconds * 1000).toISOString();
23
+ }
24
+
25
+ /** "50 bps (0.50%)" — deterministic fixed-2 percentage. */
26
+ export function formatBps(bps) {
27
+ return `${bps} bps (${(bps / 100).toFixed(2)}%)`;
28
+ }
29
+
30
+ /** Group wei digits for readability: 1000000 -> "1,000,000". Deterministic. */
31
+ export function formatWei(weiString) {
32
+ const s = String(weiString);
33
+ return `${s.replace(/\B(?=(\d{3})+(?!\d))/g, ",")} wei`;
34
+ }
35
+
36
+ /**
37
+ * Render a grant as deterministic human-readable text.
38
+ * Validates/normalizes first (throws GrantValidationError on bad input), so a
39
+ * grant that cannot be rendered is a grant that cannot be authorized.
40
+ */
41
+ export function renderGrant(input, opts = {}) {
42
+ const g = normalizeGrant(input, opts);
43
+ const id = grantId(g);
44
+ const readonlyOnly = g.actions.every((a) => isReadonlyAction(a));
45
+
46
+ const lines = [
47
+ "ORACLE CONTROL GRANT (deterministic authorization)",
48
+ "==================================================",
49
+ row("Grant ID", id),
50
+ row("Version", String(GRANT_VERSION)),
51
+ row("Chain", String(g.chainId)),
52
+ row("Agent", g.agentAddress),
53
+ row("Account", g.accountAddress),
54
+ row("Actions", g.actions.join(", ")),
55
+ row("Targets", g.targets.length ? g.targets.join(", ") : "(none — read/simulate only)"),
56
+ row("Max value", formatWei(g.maxValueWei)),
57
+ row("Max gas", formatWei(g.maxGasWei)),
58
+ row("Max slippage", formatBps(g.maxSlippageBps)),
59
+ row("Expires", `${isoUtc(g.expiresAt)} (unix ${g.expiresAt})`),
60
+ row("Nonce", g.nonce),
61
+ row("Revocation", g.revocationKey),
62
+ "--------------------------------------------------",
63
+ readonlyOnly
64
+ ? "Scope: read/simulate only — this grant cannot move funds."
65
+ : "Scope: includes state-changing actions bounded by the caps above.",
66
+ "Self-custodial: this grant contains public data only and can be revoked at any time via the revocation key.",
67
+ ];
68
+ return lines.join("\n");
69
+ }
@@ -0,0 +1,322 @@
1
+ // Oracle Control — public grant policy schema (Slice A).
2
+ //
3
+ // Oracle Control is DETERMINISTIC AUTHORIZATION: a grant either validates or it
4
+ // does not, with no advisory judgement involved (that is Oracle Router's job,
5
+ // and Router output must never gate anything here).
6
+ //
7
+ // This module is dependency-free (node builtins only, no npm packages) and
8
+ // fully deterministic: same input object => same normalized grant => same
9
+ // canonical string => same grant id, regardless of key order, address casing,
10
+ // array ordering, or numeric representation (number / bigint / decimal string).
11
+ //
12
+ // Self-custody boundary: a grant only ever references PUBLIC material —
13
+ // chain id, the agent's public address, the user's own account address,
14
+ // action scopes, target contracts, spend/gas/slippage ceilings, expiry,
15
+ // nonce, and a public revocation key identifier. Nothing here may carry or
16
+ // derive private keys, bearer tokens, keystore paths, or session secrets.
17
+ //
18
+ // Fail-closed rules enforced by validateGrant / normalizeGrant:
19
+ // - every required field must be present (expiry, chain, agent, account, ...)
20
+ // - unknown fields are rejected (no smuggling extra authority)
21
+ // - broad wildcard actions are rejected by default ("*", "*:*", "verb:*")
22
+ // - empty targets are only allowed when EVERY action is read/simulate scoped
23
+ // - expiresAt is required and, when a reference `now` is supplied, must be
24
+ // strictly in the future
25
+
26
+ import { createHash } from "node:crypto";
27
+
28
+ export const GRANT_VERSION = 1;
29
+
30
+ /** Every field is required. Order here is documentation only; canonical form
31
+ * always sorts keys alphabetically. */
32
+ export const REQUIRED_FIELDS = Object.freeze([
33
+ "chainId",
34
+ "agentAddress",
35
+ "accountAddress",
36
+ "actions",
37
+ "targets",
38
+ "maxValueWei",
39
+ "maxGasWei",
40
+ "maxSlippageBps",
41
+ "expiresAt",
42
+ "nonce",
43
+ "revocationKey",
44
+ ]);
45
+
46
+ /** Action verbs that never touch state; only these may have empty targets. */
47
+ export const READONLY_ACTION_VERBS = Object.freeze(["read", "simulate"]);
48
+
49
+ export const MAX_SLIPPAGE_BPS = 10_000;
50
+ export const MAX_GRANT_TTL_SECONDS = 24 * 60 * 60;
51
+ export const MAX_NONCE_LENGTH = 128;
52
+ export const MAX_REVOCATION_KEY_LENGTH = 256;
53
+ export const MAX_ACTIONS = 64;
54
+ export const MAX_TARGETS = 256;
55
+
56
+ const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
57
+ // verb:resource — lowercase verb, resource may carry dots/dashes/underscores.
58
+ const ACTION_RE = /^[a-z][a-z0-9_-]*:[a-z0-9._-]+$/;
59
+ // wildcard-bearing action (only accepted when explicitly opted in, and never
60
+ // with a wildcard verb).
61
+ const ACTION_WILDCARD_RE = /^[a-z][a-z0-9_-]*:\*$/;
62
+ const NONCE_RE = /^[A-Za-z0-9._:-]+$/;
63
+ const REVOCATION_KEY_RE = /^[A-Za-z0-9._:-]+$/;
64
+ const DECIMAL_RE = /^(0|[1-9][0-9]*)$/;
65
+
66
+ export class GrantValidationError extends Error {
67
+ constructor(errors) {
68
+ const lines = errors.map((e) => `${e.field}: ${e.message}`);
69
+ super(`invalid grant (${errors.length} error${errors.length === 1 ? "" : "s"}): ${lines.join("; ")}`);
70
+ this.name = "GrantValidationError";
71
+ this.errors = errors;
72
+ }
73
+ }
74
+
75
+ function isPlainObject(v) {
76
+ return v != null && typeof v === "object" && !Array.isArray(v);
77
+ }
78
+
79
+ /** Parse a non-negative integer amount (number | bigint | decimal string)
80
+ * into a canonical decimal string. Returns null when unparseable. */
81
+ function toAmountString(v) {
82
+ if (typeof v === "bigint") {
83
+ return v >= 0n ? v.toString(10) : null;
84
+ }
85
+ if (typeof v === "number") {
86
+ if (!Number.isSafeInteger(v) || v < 0) return null;
87
+ return String(v);
88
+ }
89
+ if (typeof v === "string") {
90
+ const s = v.trim();
91
+ if (!DECIMAL_RE.test(s)) return null;
92
+ return s;
93
+ }
94
+ return null;
95
+ }
96
+
97
+ function toSafeInt(v) {
98
+ if (typeof v === "bigint") {
99
+ if (v < 0n || v > BigInt(Number.MAX_SAFE_INTEGER)) return null;
100
+ return Number(v);
101
+ }
102
+ if (typeof v === "number") {
103
+ if (!Number.isSafeInteger(v) || v < 0) return null;
104
+ return v;
105
+ }
106
+ if (typeof v === "string") {
107
+ const s = v.trim();
108
+ if (!DECIMAL_RE.test(s)) return null;
109
+ const n = Number(s);
110
+ return Number.isSafeInteger(n) ? n : null;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ /** True when the action verb is read-only (read/simulate). */
116
+ export function isReadonlyAction(action) {
117
+ const verb = String(action).split(":")[0];
118
+ return READONLY_ACTION_VERBS.includes(verb);
119
+ }
120
+
121
+ /**
122
+ * Validate a grant. Never throws; returns { ok, errors, grant } where `grant`
123
+ * is the normalized form when ok, else null.
124
+ *
125
+ * Options:
126
+ * allowWildcardActions (default false) — permit "verb:*" actions. Broad
127
+ * wildcards ("*", "*:*", wildcard verbs) are ALWAYS
128
+ * rejected regardless of this flag.
129
+ * now (unix seconds) — when provided, expiresAt must be
130
+ * strictly greater than it. Pass explicitly for
131
+ * deterministic validation; omitted means no liveness
132
+ * check (pure shape validation).
133
+ */
134
+ export function validateGrant(input, opts = {}) {
135
+ const errors = [];
136
+ const push = (field, message) => errors.push({ field, message });
137
+
138
+ if (!isPlainObject(input)) {
139
+ push("grant", "grant must be a plain object");
140
+ return { ok: false, errors, grant: null };
141
+ }
142
+
143
+ // Fail closed on unknown fields.
144
+ for (const key of Object.keys(input)) {
145
+ if (!REQUIRED_FIELDS.includes(key)) push(key, "unknown field is not allowed");
146
+ }
147
+ for (const key of REQUIRED_FIELDS) {
148
+ if (!(key in input) || input[key] == null) push(key, "required field is missing");
149
+ }
150
+ if (errors.length) return { ok: false, errors, grant: null };
151
+
152
+ const out = {};
153
+
154
+ // chainId
155
+ const chainId = toSafeInt(input.chainId);
156
+ if (chainId == null || chainId <= 0) push("chainId", "must be a positive integer chain id");
157
+ else out.chainId = chainId;
158
+
159
+ // addresses
160
+ for (const field of ["agentAddress", "accountAddress"]) {
161
+ const v = input[field];
162
+ if (typeof v !== "string" || !ADDRESS_RE.test(v.trim())) {
163
+ push(field, "must be a 0x-prefixed 20-byte hex address");
164
+ } else {
165
+ out[field] = v.trim().toLowerCase();
166
+ }
167
+ }
168
+
169
+ // actions
170
+ if (!Array.isArray(input.actions) || input.actions.length === 0) {
171
+ push("actions", "must be a non-empty array of action scopes");
172
+ } else if (input.actions.length > MAX_ACTIONS) {
173
+ push("actions", `too many actions (max ${MAX_ACTIONS})`);
174
+ } else {
175
+ const actions = [];
176
+ for (const raw of input.actions) {
177
+ const a = typeof raw === "string" ? raw.trim() : null;
178
+ if (!a) {
179
+ push("actions", "action entries must be non-empty strings");
180
+ continue;
181
+ }
182
+ if (a.includes("*")) {
183
+ if (opts.allowWildcardActions === true && ACTION_WILDCARD_RE.test(a)) {
184
+ actions.push(a);
185
+ } else {
186
+ push("actions", `broad wildcard action "${a}" is rejected (fail closed)`);
187
+ }
188
+ continue;
189
+ }
190
+ if (!ACTION_RE.test(a)) {
191
+ push("actions", `malformed action "${a}" (expected verb:resource)`);
192
+ continue;
193
+ }
194
+ actions.push(a);
195
+ }
196
+ out.actions = [...new Set(actions)].sort();
197
+ }
198
+
199
+ // targets
200
+ if (!Array.isArray(input.targets)) {
201
+ push("targets", "must be an array of contract addresses");
202
+ } else if (input.targets.length > MAX_TARGETS) {
203
+ push("targets", `too many targets (max ${MAX_TARGETS})`);
204
+ } else {
205
+ const targets = [];
206
+ for (const raw of input.targets) {
207
+ const t = typeof raw === "string" ? raw.trim() : null;
208
+ if (!t || !ADDRESS_RE.test(t)) {
209
+ push("targets", `target "${raw}" must be a 0x-prefixed 20-byte hex address (wildcards not allowed)`);
210
+ continue;
211
+ }
212
+ targets.push(t.toLowerCase());
213
+ }
214
+ out.targets = [...new Set(targets)].sort();
215
+ if (out.targets.length === 0 && Array.isArray(out.actions)) {
216
+ const nonReadonly = out.actions.filter((a) => !isReadonlyAction(a));
217
+ if (nonReadonly.length > 0 || out.actions.length === 0) {
218
+ push(
219
+ "targets",
220
+ `empty targets are only allowed for read/simulate scopes (blocked by: ${nonReadonly.join(", ") || "no valid actions"})`
221
+ );
222
+ }
223
+ }
224
+ }
225
+
226
+ // amounts
227
+ for (const field of ["maxValueWei", "maxGasWei"]) {
228
+ const amt = toAmountString(input[field]);
229
+ if (amt == null) push(field, "must be a non-negative integer wei amount");
230
+ else out[field] = amt;
231
+ }
232
+
233
+ // slippage
234
+ const bps = toSafeInt(input.maxSlippageBps);
235
+ if (bps == null || bps > MAX_SLIPPAGE_BPS) {
236
+ push("maxSlippageBps", `must be an integer between 0 and ${MAX_SLIPPAGE_BPS}`);
237
+ } else {
238
+ out.maxSlippageBps = bps;
239
+ }
240
+
241
+ // expiry — required (checked above); must be a positive unix-seconds int and,
242
+ // when `now` is supplied, strictly in the future.
243
+ // Date-renderable ceiling in unix seconds. JS Date maxes at 8.64e15 ms, so a
244
+ // seconds value above 8.64e12 overflows the renderer (isoUtc does *1000).
245
+ // Also well within the AA lane's uint48 bound, so a schema-valid expiry always
246
+ // fits both the on-chain validUntil field and the human render. Reject rather
247
+ // than render a bogus far-future date (fail closed on the seconds-vs-ms mistake).
248
+ const MAX_UNIX_SECONDS = 8_640_000_000_000;
249
+ const expiresAt = toSafeInt(input.expiresAt);
250
+ if (expiresAt == null || expiresAt <= 0) {
251
+ push("expiresAt", "must be a positive unix-seconds timestamp");
252
+ } else if (expiresAt > MAX_UNIX_SECONDS) {
253
+ push("expiresAt", "exceeds the max unix-seconds timestamp (pass seconds, not milliseconds)");
254
+ } else {
255
+ out.expiresAt = expiresAt;
256
+ if (opts.now != null) {
257
+ const now = toSafeInt(opts.now);
258
+ if (now == null) push("expiresAt", "opts.now must be a unix-seconds integer");
259
+ else if (expiresAt <= now) push("expiresAt", `grant is expired (expiresAt ${expiresAt} <= now ${now})`);
260
+ else if (expiresAt - now > MAX_GRANT_TTL_SECONDS) {
261
+ push("expiresAt", `grant exceeds the hard 24-hour TTL (${MAX_GRANT_TTL_SECONDS} seconds)`);
262
+ }
263
+ }
264
+ }
265
+
266
+ // nonce
267
+ const nonce =
268
+ typeof input.nonce === "string"
269
+ ? input.nonce.trim()
270
+ : toSafeInt(input.nonce) != null
271
+ ? String(toSafeInt(input.nonce))
272
+ : null;
273
+ if (!nonce || nonce.length > MAX_NONCE_LENGTH || !NONCE_RE.test(nonce)) {
274
+ push("nonce", `must be a non-empty string (max ${MAX_NONCE_LENGTH} chars, [A-Za-z0-9._:-])`);
275
+ } else {
276
+ out.nonce = nonce;
277
+ }
278
+
279
+ // revocationKey — a PUBLIC identifier used to revoke this grant; never a secret.
280
+ const rk = typeof input.revocationKey === "string" ? input.revocationKey.trim() : null;
281
+ if (!rk || rk.length > MAX_REVOCATION_KEY_LENGTH || !REVOCATION_KEY_RE.test(rk)) {
282
+ push("revocationKey", `must be a non-empty public identifier (max ${MAX_REVOCATION_KEY_LENGTH} chars, [A-Za-z0-9._:-])`);
283
+ } else {
284
+ out.revocationKey = rk;
285
+ }
286
+
287
+ if (errors.length) return { ok: false, errors, grant: null };
288
+ return { ok: true, errors: [], grant: out };
289
+ }
290
+
291
+ /** Validate + normalize; throws GrantValidationError on any failure. */
292
+ export function normalizeGrant(input, opts = {}) {
293
+ const { ok, errors, grant } = validateGrant(input, opts);
294
+ if (!ok) throw new GrantValidationError(errors);
295
+ return grant;
296
+ }
297
+
298
+ /**
299
+ * Deterministic canonical JSON for a grant: validates/normalizes first, then
300
+ * serializes with alphabetically sorted keys (arrays already sorted + deduped
301
+ * by normalization). Identical grants — regardless of input key order, address
302
+ * casing, or numeric representation — always produce the identical string.
303
+ */
304
+ export function canonicalizeGrant(input, opts = {}) {
305
+ const g = normalizeGrant(input, opts);
306
+ const keys = Object.keys(g).sort();
307
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${JSON.stringify(g[k])}`);
308
+ return `{"version":${GRANT_VERSION},${parts.join(",")}}`;
309
+ }
310
+
311
+ /** sha256 hex of the canonical form — stable public identifier for a grant. */
312
+ export function grantId(input, opts = {}) {
313
+ return createHash("sha256").update(canonicalizeGrant(input, opts), "utf8").digest("hex");
314
+ }
315
+
316
+ /** Deterministic expiry check against an explicit unix-seconds `now`. */
317
+ export function isExpired(grant, nowSeconds) {
318
+ const now = toSafeInt(nowSeconds);
319
+ const exp = toSafeInt(isPlainObject(grant) ? grant.expiresAt : null);
320
+ if (now == null || exp == null) return true; // fail closed
321
+ return exp <= now;
322
+ }