@oracle-agent/oracle 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. package/README.md +2 -2
  2. package/SETUP.md +4 -4
  3. package/dist/action-receipts.mjs +1 -0
  4. package/dist/action-semantics.mjs +1 -0
  5. package/dist/address-book.mjs +2 -0
  6. package/dist/bin/desk-server.mjs +59 -0
  7. package/dist/bin/oracle-data-mcp.mjs +8 -0
  8. package/dist/bin/oracle-equities.mjs +20 -0
  9. package/dist/bin/oracle-init.mjs +24 -0
  10. package/dist/bin/oracle-public-server.mjs +5 -0
  11. package/dist/bin/oracle-route.mjs +22 -0
  12. package/dist/bin/oracle-scan.mjs +18 -0
  13. package/dist/bin/oracle-upgrade.mjs +21 -0
  14. package/dist/bin/oracle.mjs +61 -0
  15. package/dist/chains.mjs +1 -0
  16. package/dist/cli/commands/auth.mjs +16 -0
  17. package/dist/cli/commands/bootstrap.mjs +18 -0
  18. package/dist/cli/commands/chain.mjs +21 -0
  19. package/dist/cli/commands/chat.mjs +45 -0
  20. package/dist/cli/commands/credential.mjs +1 -0
  21. package/dist/cli/commands/data-mcp.mjs +2 -0
  22. package/dist/cli/commands/data.mjs +12 -0
  23. package/dist/cli/commands/doctor.mjs +5 -0
  24. package/dist/cli/commands/equities.mjs +15 -0
  25. package/dist/cli/commands/farm.mjs +59 -0
  26. package/dist/cli/commands/fees.mjs +10 -0
  27. package/dist/cli/commands/follow.mjs +28 -0
  28. package/dist/cli/commands/harness.mjs +15 -0
  29. package/dist/cli/commands/help.mjs +1 -0
  30. package/dist/cli/commands/init.mjs +3 -0
  31. package/dist/cli/commands/mcp.mjs +36 -0
  32. package/dist/cli/commands/model.mjs +54 -0
  33. package/dist/cli/commands/plugins.mjs +43 -0
  34. package/dist/cli/commands/prepare.mjs +2 -0
  35. package/dist/cli/commands/public.mjs +3 -0
  36. package/dist/cli/commands/resolve.mjs +23 -0
  37. package/dist/cli/commands/route.mjs +2 -0
  38. package/dist/cli/commands/runner.mjs +1 -0
  39. package/dist/cli/commands/scan.mjs +2 -0
  40. package/dist/cli/commands/setup.mjs +34 -0
  41. package/dist/cli/commands/sign.mjs +4 -0
  42. package/dist/cli/commands/signer.mjs +1 -0
  43. package/dist/cli/commands/swap.mjs +36 -0
  44. package/dist/cli/commands/upgrade.mjs +2 -0
  45. package/dist/cli/commands/vault.mjs +1 -0
  46. package/dist/cli/commands/version.mjs +2 -0
  47. package/dist/data/desk-data.mjs +55 -0
  48. package/dist/data/names.mjs +2 -0
  49. package/dist/data/providers/farming.mjs +2 -0
  50. package/dist/equities/index.mjs +1 -0
  51. package/dist/index.mjs +60 -0
  52. package/dist/nft-gas-war-guard.mjs +1 -0
  53. package/dist/onboarding/harness-configs.mjs +4 -0
  54. package/dist/portfolio-risk.mjs +1 -0
  55. package/dist/prepare-envelope.mjs +1 -0
  56. package/dist/public-api/agent-grants.mjs +3 -0
  57. package/dist/public-control/policy-schema.mjs +1 -0
  58. package/dist/router/index.mjs +2 -0
  59. package/dist/scanner/index.mjs +4 -0
  60. package/dist/signals/index.mjs +1 -0
  61. package/dist/watch-preferences.mjs +1 -0
  62. package/package.json +32 -78
  63. package/public/oracle-splash/_variants/ice.html +5 -18
  64. package/public/oracle-splash/_variants/ivory.html +5 -18
  65. package/public/oracle-splash/index.html +114 -34
  66. package/CONTRIBUTING.md +0 -99
  67. package/bin/desk-server.mjs +0 -473
  68. package/bin/oracle-data-mcp.mjs +0 -1252
  69. package/bin/oracle-equities.mjs +0 -168
  70. package/bin/oracle-init.mjs +0 -525
  71. package/bin/oracle-public-server.mjs +0 -36
  72. package/bin/oracle-route.mjs +0 -254
  73. package/bin/oracle-scan.mjs +0 -192
  74. package/bin/oracle-upgrade.mjs +0 -42
  75. package/bin/oracle.mjs +0 -34
  76. package/docs/adding-a-chain.md +0 -229
  77. package/docs/architecture.md +0 -139
  78. package/docs/buzz-integration.md +0 -115
  79. package/docs/cli.md +0 -143
  80. package/docs/connectors.md +0 -13
  81. package/docs/hyperliquid-builder-code.md +0 -50
  82. package/docs/locals-only-fee-waiver.md +0 -34
  83. package/docs/oracle-pack-standard.md +0 -29
  84. package/docs/profiles.md +0 -151
  85. package/docs/public-surface.md +0 -206
  86. package/examples/add-a-chain.mjs +0 -65
  87. package/examples/oracle-pack-template.mjs +0 -38
  88. package/examples/research-a-token.mjs +0 -70
  89. package/plugins/oracle-owner-gate/__init__.py +0 -288
  90. package/plugins/oracle-owner-gate/plugin.yaml +0 -9
  91. package/protocols/templates/safe-erc20/SECURITY.md +0 -16
  92. package/protocols/templates/safe-erc20/foundry.toml +0 -12
  93. package/protocols/templates/safe-erc20/remappings.txt +0 -2
  94. package/protocols/templates/safe-erc20/src/SafeERC20.sol +0 -53
  95. package/protocols/templates/safe-erc20/test/SafeERC20.t.sol +0 -72
  96. package/scripts/adversarial-bench.mjs +0 -114
  97. package/scripts/build-dist.mjs +0 -119
  98. package/scripts/build-inscription.py +0 -230
  99. package/scripts/check-doc-drift.mjs +0 -123
  100. package/scripts/check-test-count.mjs +0 -105
  101. package/scripts/e2e-hl-markets.mjs +0 -21
  102. package/scripts/e2e-hl-perps.mjs +0 -48
  103. package/scripts/e2e-hypercore-staking.mjs +0 -128
  104. package/scripts/e2e-solana-bitcoin.mjs +0 -183
  105. package/scripts/guard-publish.mjs +0 -22
  106. package/scripts/protocol-template-gate.mjs +0 -15
  107. package/scripts/public-api-scan.mjs +0 -23
  108. package/scripts/publish-dist.mjs +0 -67
  109. package/scripts/record-hero-cli.sh +0 -80
  110. package/scripts/secret-scan.mjs +0 -168
  111. package/scripts/smoke-dist-bins.mjs +0 -89
  112. package/scripts/verify-dist.mjs +0 -29
  113. package/scripts/verify-v3-venues.mjs +0 -233
  114. package/skins/oracle.yaml +0 -56
  115. package/src/action-receipts.mjs +0 -265
  116. package/src/action-semantics.mjs +0 -62
  117. package/src/address-book.mjs +0 -208
  118. package/src/agent-auth.mjs +0 -206
  119. package/src/approval-guard.mjs +0 -282
  120. package/src/attestation-secret.mjs +0 -94
  121. package/src/audit-log.mjs +0 -196
  122. package/src/auth/oauth.mjs +0 -672
  123. package/src/auto-slippage.mjs +0 -378
  124. package/src/capability-posture.mjs +0 -125
  125. package/src/cards.mjs +0 -422
  126. package/src/chains.mjs +0 -62
  127. package/src/cli/chain-catalog.mjs +0 -215
  128. package/src/cli/chain-state.mjs +0 -74
  129. package/src/cli/commands/auth.mjs +0 -193
  130. package/src/cli/commands/bootstrap.mjs +0 -87
  131. package/src/cli/commands/chain.mjs +0 -143
  132. package/src/cli/commands/chat.mjs +0 -465
  133. package/src/cli/commands/credential.mjs +0 -9
  134. package/src/cli/commands/data-mcp.mjs +0 -18
  135. package/src/cli/commands/data.mjs +0 -75
  136. package/src/cli/commands/doctor.mjs +0 -136
  137. package/src/cli/commands/equities.mjs +0 -41
  138. package/src/cli/commands/farm.mjs +0 -129
  139. package/src/cli/commands/fees.mjs +0 -109
  140. package/src/cli/commands/follow.mjs +0 -130
  141. package/src/cli/commands/harness.mjs +0 -63
  142. package/src/cli/commands/help.mjs +0 -7
  143. package/src/cli/commands/init.mjs +0 -27
  144. package/src/cli/commands/mcp.mjs +0 -154
  145. package/src/cli/commands/model.mjs +0 -106
  146. package/src/cli/commands/plugins.mjs +0 -241
  147. package/src/cli/commands/prepare.mjs +0 -12
  148. package/src/cli/commands/public.mjs +0 -20
  149. package/src/cli/commands/resolve.mjs +0 -64
  150. package/src/cli/commands/route.mjs +0 -12
  151. package/src/cli/commands/runner.mjs +0 -9
  152. package/src/cli/commands/scan.mjs +0 -12
  153. package/src/cli/commands/setup.mjs +0 -294
  154. package/src/cli/commands/sign.mjs +0 -21
  155. package/src/cli/commands/signer.mjs +0 -9
  156. package/src/cli/commands/swap.mjs +0 -102
  157. package/src/cli/commands/upgrade.mjs +0 -12
  158. package/src/cli/commands/vault.mjs +0 -9
  159. package/src/cli/commands/version.mjs +0 -22
  160. package/src/cli/first-run.mjs +0 -23
  161. package/src/cli/kernel.mjs +0 -262
  162. package/src/cli/mcp-targets/chatgpt.mjs +0 -51
  163. package/src/cli/mcp-targets/claude-code.mjs +0 -39
  164. package/src/cli/mcp-targets/claude-desktop.mjs +0 -24
  165. package/src/cli/mcp-targets/codex.mjs +0 -41
  166. package/src/cli/mcp-targets/cursor.mjs +0 -32
  167. package/src/cli/mcp-targets/shared.mjs +0 -71
  168. package/src/cli/mcp-targets/vscode.mjs +0 -39
  169. package/src/cli/messaging-platforms.mjs +0 -217
  170. package/src/cli/model-config.mjs +0 -78
  171. package/src/cli/operator-dispatch.mjs +0 -257
  172. package/src/cli/oracle-harness.py +0 -415
  173. package/src/cli/paths.mjs +0 -107
  174. package/src/cli/runtime.mjs +0 -351
  175. package/src/cli/setup-state.mjs +0 -168
  176. package/src/cli/spawn-child.mjs +0 -43
  177. package/src/data/catalog.mjs +0 -858
  178. package/src/data/desk-data.mjs +0 -877
  179. package/src/data/http.mjs +0 -253
  180. package/src/data/names.mjs +0 -188
  181. package/src/data/provider-endpoint.mjs +0 -94
  182. package/src/data/providers/aave-v3.mjs +0 -139
  183. package/src/data/providers/across-bridge.mjs +0 -41
  184. package/src/data/providers/aerodrome.mjs +0 -245
  185. package/src/data/providers/aevo.mjs +0 -33
  186. package/src/data/providers/approval-indexer.mjs +0 -133
  187. package/src/data/providers/approvals.mjs +0 -594
  188. package/src/data/providers/babylon-staking.mjs +0 -149
  189. package/src/data/providers/balancer.mjs +0 -209
  190. package/src/data/providers/beefy-yields.mjs +0 -45
  191. package/src/data/providers/birdeye-tokens.mjs +0 -33
  192. package/src/data/providers/bisq-markets.mjs +0 -145
  193. package/src/data/providers/bitcoin-esplora.mjs +0 -216
  194. package/src/data/providers/bitcoin-meta.mjs +0 -378
  195. package/src/data/providers/blockscout.mjs +0 -14
  196. package/src/data/providers/bob-rootstock.mjs +0 -198
  197. package/src/data/providers/braiins-insights.mjs +0 -103
  198. package/src/data/providers/bridge-aggregator.mjs +0 -259
  199. package/src/data/providers/bridges.mjs +0 -242
  200. package/src/data/providers/bungee.mjs +0 -29
  201. package/src/data/providers/celestia-da.mjs +0 -38
  202. package/src/data/providers/changenow.mjs +0 -135
  203. package/src/data/providers/compound.mjs +0 -32
  204. package/src/data/providers/connext.mjs +0 -28
  205. package/src/data/providers/convex.mjs +0 -30
  206. package/src/data/providers/cow-protocol.mjs +0 -110
  207. package/src/data/providers/cowswap.mjs +0 -415
  208. package/src/data/providers/curve.mjs +0 -201
  209. package/src/data/providers/das-assets.mjs +0 -59
  210. package/src/data/providers/debridge.mjs +0 -109
  211. package/src/data/providers/defillama.mjs +0 -88
  212. package/src/data/providers/dexscreener.mjs +0 -43
  213. package/src/data/providers/drift.mjs +0 -32
  214. package/src/data/providers/dydx.mjs +0 -41
  215. package/src/data/providers/evm-rpc.mjs +0 -225
  216. package/src/data/providers/farming.mjs +0 -291
  217. package/src/data/providers/geckoterminal.mjs +0 -34
  218. package/src/data/providers/gmx.mjs +0 -496
  219. package/src/data/providers/hiro-stacks.mjs +0 -150
  220. package/src/data/providers/hl-assets.mjs +0 -165
  221. package/src/data/providers/hl-info.mjs +0 -108
  222. package/src/data/providers/hl-markets.mjs +0 -210
  223. package/src/data/providers/hl-outcome.mjs +0 -61
  224. package/src/data/providers/hl-perps.mjs +0 -419
  225. package/src/data/providers/hl-staking.mjs +0 -353
  226. package/src/data/providers/hl-ws.mjs +0 -119
  227. package/src/data/providers/hyperevm-dex.mjs +0 -49
  228. package/src/data/providers/jito-mev.mjs +0 -117
  229. package/src/data/providers/jupiter-venues.mjs +0 -119
  230. package/src/data/providers/jupiter.mjs +0 -315
  231. package/src/data/providers/kamino-strategies.mjs +0 -143
  232. package/src/data/providers/l2beat-tvl.mjs +0 -42
  233. package/src/data/providers/lifi.mjs +0 -166
  234. package/src/data/providers/liquid-esplora.mjs +0 -152
  235. package/src/data/providers/magiceden-ordinals.mjs +0 -141
  236. package/src/data/providers/magiceden-sol.mjs +0 -406
  237. package/src/data/providers/mempool-lightning.mjs +0 -144
  238. package/src/data/providers/mempool-mining.mjs +0 -142
  239. package/src/data/providers/meteora-dlmm.mjs +0 -168
  240. package/src/data/providers/morpho.mjs +0 -177
  241. package/src/data/providers/nft-gallery.mjs +0 -163
  242. package/src/data/providers/nft-portfolio.mjs +0 -494
  243. package/src/data/providers/odos.mjs +0 -156
  244. package/src/data/providers/ondo.mjs +0 -31
  245. package/src/data/providers/oneinch.mjs +0 -174
  246. package/src/data/providers/opensea-multichain.mjs +0 -136
  247. package/src/data/providers/opensea-nft.mjs +0 -371
  248. package/src/data/providers/orca.mjs +0 -29
  249. package/src/data/providers/ordinals-runes.mjs +0 -180
  250. package/src/data/providers/pancakeswap.mjs +0 -28
  251. package/src/data/providers/paraswap.mjs +0 -125
  252. package/src/data/providers/pendle.mjs +0 -188
  253. package/src/data/providers/poly-clob.mjs +0 -283
  254. package/src/data/providers/poly-public.mjs +0 -96
  255. package/src/data/providers/poly-ws.mjs +0 -103
  256. package/src/data/providers/polygon-staking.mjs +0 -227
  257. package/src/data/providers/portfolio-history.mjs +0 -394
  258. package/src/data/providers/portfolio.mjs +0 -600
  259. package/src/data/providers/pyth-price-feeds.mjs +0 -172
  260. package/src/data/providers/raydium.mjs +0 -35
  261. package/src/data/providers/relayscan-mev.mjs +0 -72
  262. package/src/data/providers/rfq.mjs +0 -15
  263. package/src/data/providers/rh-agent.mjs +0 -59
  264. package/src/data/providers/sanctum-lst.mjs +0 -180
  265. package/src/data/providers/satflow.mjs +0 -341
  266. package/src/data/providers/signed-material-guard.mjs +0 -108
  267. package/src/data/providers/solana-rpc.mjs +0 -197
  268. package/src/data/providers/solend-lending.mjs +0 -166
  269. package/src/data/providers/spark-lend.mjs +0 -42
  270. package/src/data/providers/squid-router.mjs +0 -29
  271. package/src/data/providers/stargate.mjs +0 -29
  272. package/src/data/providers/sushiswap.mjs +0 -30
  273. package/src/data/providers/symbiotic-restaking.mjs +0 -41
  274. package/src/data/providers/thorchain.mjs +0 -36
  275. package/src/data/providers/traderjoe.mjs +0 -28
  276. package/src/data/providers/twap.mjs +0 -152
  277. package/src/data/providers/uniswap-v3.mjs +0 -457
  278. package/src/data/providers/uniswap-v4.mjs +0 -414
  279. package/src/data/providers/vertex.mjs +0 -41
  280. package/src/data/providers/wallet-watch.mjs +0 -264
  281. package/src/data/providers/wormhole.mjs +0 -28
  282. package/src/data/providers/yearn.mjs +0 -31
  283. package/src/data/providers/zerox.mjs +0 -167
  284. package/src/data/public-api-scan.mjs +0 -61
  285. package/src/data/quote-placeholder.mjs +0 -31
  286. package/src/desk/swap-prepare.mjs +0 -194
  287. package/src/equities/bridge.mjs +0 -388
  288. package/src/equities/fixtures/bridge_routes.json +0 -159
  289. package/src/equities/fixtures/dst_transitions.json +0 -29
  290. package/src/equities/fixtures/equity_dex_pools.json +0 -154
  291. package/src/equities/fixtures/hip3_l2books.json +0 -2711
  292. package/src/equities/fixtures/nyse_holidays.json +0 -1
  293. package/src/equities/fixtures/oracles.json +0 -100
  294. package/src/equities/fixtures/rh_pool_state.json +0 -244
  295. package/src/equities/fixtures/solana_xstocks.json +0 -704
  296. package/src/equities/fixtures/ton_xstocks.json +0 -35
  297. package/src/equities/fixtures/universe.json +0 -411
  298. package/src/equities/fixtures/venues.json +0 -3542
  299. package/src/equities/fixtures.mjs +0 -405
  300. package/src/equities/funding.mjs +0 -142
  301. package/src/equities/index.mjs +0 -166
  302. package/src/equities/liveness.mjs +0 -148
  303. package/src/equities/marketHours.mjs +0 -249
  304. package/src/equities/normalize.mjs +0 -295
  305. package/src/equities/num.mjs +0 -146
  306. package/src/equities/prepare.mjs +0 -95
  307. package/src/equities/rank.mjs +0 -260
  308. package/src/equities/sourceNum.mjs +0 -69
  309. package/src/equities/types.mjs +0 -220
  310. package/src/equities/venues/arcus-perp.mjs +0 -84
  311. package/src/equities/venues/arcus-spot.mjs +0 -105
  312. package/src/equities/venues/hyperliquid-hip3.mjs +0 -224
  313. package/src/equities/venues/rh-uniswap.mjs +0 -216
  314. package/src/equities/venues/solana-xstocks.mjs +0 -139
  315. package/src/equities/venues/ton-stonfi.mjs +0 -139
  316. package/src/exact-integer.mjs +0 -72
  317. package/src/exec-policy.mjs +0 -419
  318. package/src/flags.mjs +0 -15
  319. package/src/fresh-window.mjs +0 -76
  320. package/src/gmx-attestation.mjs +0 -176
  321. package/src/index.mjs +0 -107
  322. package/src/licensing/locals-only.mjs +0 -25
  323. package/src/nft-gas-war-guard.mjs +0 -139
  324. package/src/onboarding/agent-keys.mjs +0 -161
  325. package/src/onboarding/harness-configs.mjs +0 -102
  326. package/src/onboarding/index.mjs +0 -18
  327. package/src/onboarding/tiers.mjs +0 -139
  328. package/src/oracle-env.mjs +0 -55
  329. package/src/package-manifest.mjs +0 -42
  330. package/src/plugins/agent-plugin-loader.mjs +0 -225
  331. package/src/portfolio-risk.mjs +0 -170
  332. package/src/prepare-envelope.mjs +0 -204
  333. package/src/profile-upgrade.mjs +0 -277
  334. package/src/protocol-execution.mjs +0 -84
  335. package/src/protocol-templates/gate.mjs +0 -179
  336. package/src/protocol-templates/prepare-deploy.mjs +0 -81
  337. package/src/public-api/agent-grants.mjs +0 -57
  338. package/src/public-api/buzz-integration.mjs +0 -267
  339. package/src/public-api/connect-agent.mjs +0 -424
  340. package/src/public-api/grants.mjs +0 -142
  341. package/src/public-api/http.mjs +0 -707
  342. package/src/public-api/metering.mjs +0 -111
  343. package/src/public-control/aa-adapter.mjs +0 -402
  344. package/src/public-control/build-registry.mjs +0 -227
  345. package/src/public-control/bundler-client.mjs +0 -357
  346. package/src/public-control/grant-indexer.mjs +0 -296
  347. package/src/public-control/policy-render.mjs +0 -69
  348. package/src/public-control/policy-schema.mjs +0 -322
  349. package/src/public-control/runtime-config.mjs +0 -275
  350. package/src/public-control/session-key-model.mjs +0 -379
  351. package/src/public-control/session-orchestrator.mjs +0 -418
  352. package/src/rfq/intent.mjs +0 -180
  353. package/src/rfq/sources.mjs +0 -181
  354. package/src/route-attestation.mjs +0 -132
  355. package/src/router/best-execution.mjs +0 -326
  356. package/src/router/index.mjs +0 -250
  357. package/src/router/integrator-fee.mjs +0 -135
  358. package/src/router/prepare-bridge.mjs +0 -293
  359. package/src/router/prepare-route.mjs +0 -398
  360. package/src/router/proposal.mjs +0 -311
  361. package/src/router/risk-classifier.mjs +0 -119
  362. package/src/router/route-sources.mjs +0 -364
  363. package/src/scanner/chains.config.mjs +0 -543
  364. package/src/scanner/contract.mjs +0 -270
  365. package/src/scanner/evm-scanner.mjs +0 -394
  366. package/src/scanner/index.mjs +0 -9
  367. package/src/scanner/v2-venue.mjs +0 -351
  368. package/src/scanner/v3-venue.mjs +0 -308
  369. package/src/scopes.mjs +0 -44
  370. package/src/sell-simulation.mjs +0 -167
  371. package/src/signals/engine.mjs +0 -146
  372. package/src/signals/index.mjs +0 -1
  373. package/src/token-transfer-guard.mjs +0 -188
  374. package/src/tui/app.mjs +0 -438
  375. package/src/tui/backend.mjs +0 -215
  376. package/src/tui/format.mjs +0 -272
  377. package/src/tui/gateway-client.mjs +0 -177
  378. package/src/tui/input.mjs +0 -501
  379. package/src/tui/memory.mjs +0 -105
  380. package/src/tui/meta-learn.mjs +0 -85
  381. package/src/tui/renderer.mjs +0 -443
  382. package/src/tui/scheduler.mjs +0 -205
  383. package/src/tui/skills-loader.mjs +0 -140
  384. package/src/tui/standalone-client.mjs +0 -752
  385. package/src/tui/theme.mjs +0 -81
  386. package/src/vault-attestation.mjs +0 -146
  387. package/src/venues.mjs +0 -206
  388. package/src/watch-preferences.mjs +0 -85
  389. /package/{profiles → dist/assets/profiles}/_template/SOUL.md +0 -0
  390. /package/{profiles → dist/assets/profiles}/_template/profile.json +0 -0
  391. /package/{profiles → dist/assets/profiles}/bitcoin-agent/SOUL.md +0 -0
  392. /package/{profiles → dist/assets/profiles}/bitcoin-agent/profile.json +0 -0
  393. /package/{profiles → dist/assets/profiles}/hyperliquid-agent/SOUL.md +0 -0
  394. /package/{profiles → dist/assets/profiles}/hyperliquid-agent/profile.json +0 -0
  395. /package/{profiles → dist/assets/profiles}/oracle/SOUL.md +0 -0
  396. /package/{profiles → dist/assets/profiles}/oracle/profile.json +0 -0
  397. /package/{profiles → dist/assets/profiles}/polymarket-agent/SOUL.md +0 -0
  398. /package/{profiles → dist/assets/profiles}/polymarket-agent/profile.json +0 -0
  399. /package/{profiles → dist/assets/profiles}/profile.schema.json +0 -0
  400. /package/{profiles → dist/assets/profiles}/protocol-builder/SOUL.md +0 -0
  401. /package/{profiles → dist/assets/profiles}/protocol-builder/profile.json +0 -0
  402. /package/{profiles → dist/assets/profiles}/robinhood-agent/SOUL.md +0 -0
  403. /package/{profiles → dist/assets/profiles}/robinhood-agent/profile.json +0 -0
  404. /package/{profiles → dist/assets/profiles}/solana-agent/SOUL.md +0 -0
  405. /package/{profiles → dist/assets/profiles}/solana-agent/profile.json +0 -0
  406. /package/{profiles → dist/assets/profiles}/stable-agent/SOUL.md +0 -0
  407. /package/{profiles → dist/assets/profiles}/stable-agent/profile.json +0 -0
  408. /package/{skills → dist/assets/skills}/balance/SKILL.md +0 -0
  409. /package/{skills → dist/assets/skills}/bitcoin-l1.md +0 -0
  410. /package/{skills → dist/assets/skills}/chain-defi-ecosystem-profiling.md +0 -0
  411. /package/{skills → dist/assets/skills}/evm-contract-research.md +0 -0
  412. /package/{skills → dist/assets/skills}/hyperliquid.md +0 -0
  413. /package/{skills → dist/assets/skills}/multichain-exec-desk.md +0 -0
  414. /package/{skills → dist/assets/skills}/oracle-action-semantics/SKILL.md +0 -0
  415. /package/{skills → dist/assets/skills}/oracle-best-execution/SKILL.md +0 -0
  416. /package/{skills → dist/assets/skills}/oracle-best-execution.md +0 -0
  417. /package/{skills → dist/assets/skills}/oracle-bitcoin/SKILL.md +0 -0
  418. /package/{skills → dist/assets/skills}/oracle-chain-graphs-telegram-cards/SKILL.md +0 -0
  419. /package/{skills → dist/assets/skills}/oracle-chat/SKILL.md +0 -0
  420. /package/{skills → dist/assets/skills}/oracle-chat/chain.SKILL.md +0 -0
  421. /package/{skills → dist/assets/skills}/oracle-chat/setup.SKILL.md +0 -0
  422. /package/{skills → dist/assets/skills}/oracle-circuit-breaker/SKILL.md +0 -0
  423. /package/{skills → dist/assets/skills}/oracle-contract-research/SKILL.md +0 -0
  424. /package/{skills → dist/assets/skills}/oracle-desk/SKILL.md +0 -0
  425. /package/{skills → dist/assets/skills}/oracle-desk.md +0 -0
  426. /package/{skills → dist/assets/skills}/oracle-dex-launch/SKILL.md +0 -0
  427. /package/{skills → dist/assets/skills}/oracle-equities/SKILL.md +0 -0
  428. /package/{skills → dist/assets/skills}/oracle-grants/SKILL.md +0 -0
  429. /package/{skills → dist/assets/skills}/oracle-hypercore-staking/SKILL.md +0 -0
  430. /package/{skills → dist/assets/skills}/oracle-hyperliquid/SKILL.md +0 -0
  431. /package/{skills → dist/assets/skills}/oracle-meme-token-sniper/SKILL.md +0 -0
  432. /package/{skills → dist/assets/skills}/oracle-multichain-nft-launch/SKILL.md +0 -0
  433. /package/{skills → dist/assets/skills}/oracle-multichain-token-launch/SKILL.md +0 -0
  434. /package/{skills → dist/assets/skills}/oracle-nft-gacha-launch/SKILL.md +0 -0
  435. /package/{skills → dist/assets/skills}/oracle-nft-mint-gas-war/SKILL.md +0 -0
  436. /package/{skills → dist/assets/skills}/oracle-polymarket/SKILL.md +0 -0
  437. /package/{skills → dist/assets/skills}/oracle-protocol-builder/SKILL.md +0 -0
  438. /package/{skills → dist/assets/skills}/oracle-protocol-security/SKILL.md +0 -0
  439. /package/{skills → dist/assets/skills}/oracle-public-product/SKILL.md +0 -0
  440. /package/{skills → dist/assets/skills}/oracle-receipts/SKILL.md +0 -0
  441. /package/{skills → dist/assets/skills}/oracle-receipts.md +0 -0
  442. /package/{skills → dist/assets/skills}/oracle-rfq-tokenized-assets/SKILL.md +0 -0
  443. /package/{skills → dist/assets/skills}/oracle-smart-wallet-scanner/SKILL.md +0 -0
  444. /package/{skills → dist/assets/skills}/oracle-solana/SKILL.md +0 -0
  445. /package/{skills → dist/assets/skills}/oracle-solana-nft/SKILL.md +0 -0
  446. /package/{skills → dist/assets/skills}/oracle-token-research/SKILL.md +0 -0
  447. /package/{skills → dist/assets/skills}/solana.md +0 -0
@@ -1,166 +0,0 @@
1
- // Public API for cross-chain on-chain equities best execution.
2
- // Prepare-only. Fixtures-backed offline by default; live probes are separate.
3
-
4
- import { parseDecimal } from './num.mjs';
5
- import { rank as rankQuotes } from './rank.mjs';
6
- import { prepare as prepareRh } from './prepare.mjs';
7
- import { filterQuotes } from './liveness.mjs';
8
- import { quoteAll as hip3All, quote as hip3Quote, listDexs } from './venues/hyperliquid-hip3.mjs';
9
- import { quoteAll as spotAll } from './venues/arcus-spot.mjs';
10
- import { quoteAll as perpAll } from './venues/arcus-perp.mjs';
11
- import { quoteAll as rhAll, quote as rhQuote, listPools } from './venues/rh-uniswap.mjs';
12
- import { quoteAll as solAll, quote as solQuote, listTokens as solTokens } from './venues/solana-xstocks.mjs';
13
- import { quoteAll as tonAll, quote as tonQuote, listTokens as tonTokens } from './venues/ton-stonfi.mjs';
14
- import { getVenues } from './fixtures.mjs';
15
-
16
- export { rank as rankEquityQuotes } from './rank.mjs';
17
- export { prepare as prepareEquity } from './prepare.mjs';
18
- export { validateQuote, validateRankResult } from './types.mjs';
19
- export { loadFixtures, getVenues, getUniverse } from './fixtures.mjs';
20
-
21
- function sizeArg(sizeUsd) {
22
- if (sizeUsd == null || sizeUsd === '') return null;
23
- if (typeof sizeUsd === 'object' && sizeUsd && typeof sizeUsd.mantissa === 'bigint') return sizeUsd;
24
- return parseDecimal(String(sizeUsd));
25
- }
26
-
27
- /**
28
- * Collect fixture-backed quotes for one equity ticker across all venues.
29
- */
30
- export function collectEquityQuotes(ticker, { sizeUsd = null } = {}) {
31
- if (typeof ticker !== 'string' || !ticker.trim()) {
32
- throw new TypeError('ticker is required');
33
- }
34
- const t = ticker.trim().toUpperCase();
35
- const size = sizeArg(sizeUsd);
36
- const out = [];
37
-
38
- const xyz = hip3Quote({ symbol: `xyz:${t}`, sizeUsd: size });
39
- if (xyz) out.push(xyz);
40
- else {
41
- for (const q of hip3All({ ticker: t, sizeUsd: size })) out.push(q);
42
- }
43
- for (const q of perpAll({ sizeUsd: size })) if (q.symbol === t) out.push(q);
44
- for (const q of spotAll({ sizeUsd: size })) if (q.symbol === t) out.push(q);
45
- const rh = rhQuote({ symbol: t, sizeUsd: size });
46
- if (rh) out.push(rh);
47
- const sol = solQuote({ symbol: t, sizeUsd: size });
48
- if (sol) out.push(sol);
49
- const ton = tonQuote({ symbol: t, sizeUsd: size });
50
- if (ton) out.push(ton);
51
- return out;
52
- }
53
-
54
- /**
55
- * Rank equities venues for a ticker. Offline/fixtures by default.
56
- *
57
- * @param {object} p
58
- * @param {string} p.ticker e.g. NVDA, SPY, TSLA
59
- * @param {string|number} [p.sizeUsd]
60
- * @param {number} [p.horizonHours] funding-adjusted heterogeneous ranking
61
- */
62
- export function bestEquityRoute(p = {}) {
63
- const ticker = String(p.ticker || p.symbol || '').trim().toUpperCase();
64
- if (!ticker) throw new Error('bestEquityRoute requires ticker');
65
- const sizeUsd = sizeArg(p.sizeUsd ?? p.size ?? null);
66
- const quotes = collectEquityQuotes(ticker, { sizeUsd });
67
- if (quotes.length === 0) {
68
- return {
69
- ticker,
70
- rankedOn: 'gross',
71
- ranked: [],
72
- winner: null,
73
- runnersUp: [],
74
- improvementBps: null,
75
- sourcesAnswered: 0,
76
- sourcesTried: 0,
77
- failed: [],
78
- excluded: [],
79
- bestPreparable: null,
80
- instrumentMix: 'homogeneous',
81
- note: 'no venue answered for this ticker in the pinned fixture capture',
82
- };
83
- }
84
- // Offline fixtures use their own capturedAt. Advance 1s so freshness passes.
85
- const nowMs = p.nowMs ?? (quotes[0].capturedAt + 1000);
86
- const result = rankQuotes(quotes, {
87
- nowMs,
88
- horizonHours: p.horizonHours != null ? Number(p.horizonHours) : undefined,
89
- skipLiveness: p.skipLiveness === true,
90
- });
91
- return { ticker, ...result };
92
- }
93
-
94
- /**
95
- * Inventory + liveness snapshot for equities venues (fixtures).
96
- */
97
- export function equityVenues() {
98
- const inventory = [
99
- { venue: 'hyperliquid_hip3', tier: 'quote-only', chain: 'hyperliquid', n: hip3All().length },
100
- { venue: 'arcus_perp', tier: 'quote-only', chain: 'robinhood-4663', n: perpAll().length },
101
- { venue: 'arcus_spot', tier: 'quote-only', chain: 'robinhood-4663', n: spotAll().length },
102
- { venue: 'rh_uniswap', tier: 'prepare', chain: 4663, n: listPools().length },
103
- { venue: 'solana_xstocks', tier: 'quote-only', chain: 'solana', n: solTokens().length },
104
- { venue: 'ton_stonfi', tier: 'quote-only', chain: 'ton', n: tonTokens().length },
105
- ];
106
- const all = [...hip3All(), ...perpAll(), ...spotAll(), ...rhAll(), ...solAll(), ...tonAll()];
107
- const nowMs = (all[0]?.capturedAt ?? Date.now()) + 1000;
108
- const { survivors, excluded } = filterQuotes(all, { nowMs });
109
- return {
110
- inventory,
111
- hip3Dexs: listDexs(),
112
- liveness: {
113
- survivors: survivors.length,
114
- excluded: excluded.length,
115
- byReason: excluded.reduce((acc, e) => {
116
- acc[e.reason] = (acc[e.reason] ?? 0) + 1;
117
- return acc;
118
- }, {}),
119
- },
120
- capturedAt: getVenues().capturedAt,
121
- custody: 'prepare-only',
122
- note: 'quote-only venues are ranked for discovery; only rh_uniswap is preparable in v1',
123
- };
124
- }
125
-
126
- /**
127
- * Prepare unsigned RH Uniswap artifact for an equity ticker.
128
- * Requires a real recipient wallet. Never signs.
129
- */
130
- export function prepareEquityRoute(p = {}) {
131
- const ticker = String(p.ticker || p.symbol || '').trim().toUpperCase();
132
- const recipient = p.recipient || p.taker;
133
- if (!ticker) throw new Error('prepareEquityRoute requires ticker');
134
- if (!recipient) throw new Error('prepareEquityRoute requires recipient (signing wallet)');
135
- const sizeUsd = sizeArg(p.sizeUsd ?? p.size ?? null);
136
- const q = rhQuote({ symbol: ticker, sizeUsd });
137
- if (!q) {
138
- return {
139
- ok: false,
140
- failureKind: 'no-prepare-path',
141
- ticker,
142
- note: 'no RH Uniswap prepare-tier route for this ticker in fixtures',
143
- };
144
- }
145
- try {
146
- const artifact = prepareRh({
147
- symbol: ticker,
148
- sizeUsd,
149
- recipient,
150
- nowMs: p.nowMs ?? q.capturedAt + 1000,
151
- });
152
- return { ok: true, ticker, artifact, bestPreparableVenue: 'rh_uniswap' };
153
- } catch (err) {
154
- return {
155
- ok: false,
156
- failureKind: 'prepare-refused',
157
- ticker,
158
- error: String(err.message || err),
159
- };
160
- }
161
- }
162
-
163
- /** JSON-safe clone (bigint -> string). */
164
- export function toJsonSafe(value) {
165
- return JSON.parse(JSON.stringify(value, (_, v) => (typeof v === 'bigint' ? v.toString() : v)));
166
- }
@@ -1,148 +0,0 @@
1
- // Liveness gate. This is the data integrity layer that adapters deliberately
2
- // do not implement. Adapters report source truth, including dormant venues and
3
- // crossed books. This module decides what is trustworthy enough to rank.
4
- //
5
- // Three outcome buckets (never collapse them):
6
- // ranked-eligible quotes -> survive
7
- // excluded[] -> answered, but untrustworthy (dormant, crossed, stale)
8
- // failed[] -> reserved for sources that did not answer (not used here)
9
- //
10
- // Every exclusion carries a machine readable reason and evidence.
11
-
12
- import { cmp } from './num.mjs';
13
- import { sessionState } from './marketHours.mjs';
14
-
15
- export const REASONS = Object.freeze({
16
- DORMANT_VENUE: 'dormant-venue',
17
- CROSSED_BOOK: 'crossed-book',
18
- STALE_QUOTE: 'stale-quote',
19
- STALE_ORACLE: 'stale-oracle',
20
- });
21
-
22
- // Defaults. Live quotes older than this are excluded. Fixtures carry a pinned
23
- // capturedAt so offline tests are deterministic against a frozen "now".
24
- export const DEFAULT_QUOTE_FRESHNESS_MS = 120_000;
25
- // Oracle maxStaleness on chain is 86400s (lending grade). Far too loose for
26
- // ranking. We apply our own bound during core session.
27
- export const DEFAULT_ORACLE_FRESHNESS_MS = 600_000;
28
-
29
- function exclude(list, entry) {
30
- list.push(Object.freeze({
31
- venue: entry.venue,
32
- symbol: entry.symbol ?? null,
33
- reason: entry.reason,
34
- evidence: entry.evidence ?? null,
35
- }));
36
- }
37
-
38
- function isDormantQuote(quote) {
39
- // HIP-3: vol24h in raw. Arcus: vol24h in raw. Spot: vol24h in raw.
40
- // A missing volume is treated as unknown, not dormant. Only an explicit zero
41
- // (or sub-epsilon) volume marks the venue/market dormant.
42
- const vol = quote.raw?.vol24h;
43
- if (vol === 0 || vol === 0.0) return true;
44
- if (typeof vol === 'number' && Number.isFinite(vol) && vol <= 0) return true;
45
- return false;
46
- }
47
-
48
- function isCrossed(quote) {
49
- if (!quote.bid || !quote.ask) return false;
50
- return cmp(quote.bid, quote.ask) > 0;
51
- }
52
-
53
- function isStale(quote, nowMs, freshnessMs) {
54
- if (!Number.isSafeInteger(quote.capturedAt)) return false;
55
- return nowMs - quote.capturedAt > freshnessMs;
56
- }
57
-
58
- /**
59
- * Filter a list of validating Quotes into survivors and exclusions.
60
- *
61
- * @param {object[]} quotes
62
- * @param {object} [opts]
63
- * @param {number} [opts.nowMs] clock, defaults to Date.now(); tests inject
64
- * @param {number} [opts.quoteFreshnessMs]
65
- * @param {number} [opts.oracleFreshnessMs]
66
- * @param {boolean} [opts.includeDormant] if true, dormant markets pass with a tag
67
- * @returns {{ survivors: object[], excluded: object[] }}
68
- */
69
- export function filterQuotes(quotes, opts = {}) {
70
- if (!Array.isArray(quotes)) throw new TypeError('quotes must be an array');
71
-
72
- const nowMs = opts.nowMs ?? Date.now();
73
- const quoteFreshnessMs = opts.quoteFreshnessMs ?? DEFAULT_QUOTE_FRESHNESS_MS;
74
- const oracleFreshnessMs = opts.oracleFreshnessMs ?? DEFAULT_ORACLE_FRESHNESS_MS;
75
- const includeDormant = opts.includeDormant === true;
76
-
77
- const survivors = [];
78
- const excluded = [];
79
-
80
- for (const quote of quotes) {
81
- // Dormant venue / market.
82
- if (isDormantQuote(quote)) {
83
- if (includeDormant) {
84
- survivors.push(Object.freeze({ ...quote, staleData: true }));
85
- } else {
86
- exclude(excluded, {
87
- venue: quote.venue,
88
- symbol: quote.symbolRaw ?? quote.symbol,
89
- reason: REASONS.DORMANT_VENUE,
90
- evidence: { vol24h: quote.raw?.vol24h ?? null },
91
- });
92
- }
93
- continue;
94
- }
95
-
96
- // Crossed book.
97
- if (isCrossed(quote)) {
98
- exclude(excluded, {
99
- venue: quote.venue,
100
- symbol: quote.symbolRaw ?? quote.symbol,
101
- reason: REASONS.CROSSED_BOOK,
102
- evidence: {
103
- bid: quote.bid ? String(quote.bid.mantissa) + 'e-' + quote.bid.scale : null,
104
- ask: quote.ask ? String(quote.ask.mantissa) + 'e-' + quote.ask.scale : null,
105
- },
106
- });
107
- continue;
108
- }
109
-
110
- // Stale quote.
111
- if (isStale(quote, nowMs, quoteFreshnessMs)) {
112
- exclude(excluded, {
113
- venue: quote.venue,
114
- symbol: quote.symbolRaw ?? quote.symbol,
115
- reason: REASONS.STALE_QUOTE,
116
- evidence: {
117
- capturedAt: quote.capturedAt,
118
- nowMs,
119
- ageMs: nowMs - quote.capturedAt,
120
- freshnessMs: quoteFreshnessMs,
121
- },
122
- });
123
- continue;
124
- }
125
-
126
- // Stale oracle reference (only when the quote carries one as its mid/mark source
127
- // identity is the oracle itself). RH oracle-only reads are reference tier and
128
- // ranked separately; when a quote's only price is an oracle print past our
129
- // bound during core session, exclude it.
130
- if (quote.tier === 'read-only' && quote.raw?.oracleRaw !== undefined) {
131
- const session = sessionState(nowMs);
132
- const bound = session === 'core' ? oracleFreshnessMs : oracleFreshnessMs * 2;
133
- if (isStale(quote, nowMs, bound)) {
134
- exclude(excluded, {
135
- venue: quote.venue,
136
- symbol: quote.symbolRaw ?? quote.symbol,
137
- reason: REASONS.STALE_ORACLE,
138
- evidence: { capturedAt: quote.capturedAt, nowMs, session },
139
- });
140
- continue;
141
- }
142
- }
143
-
144
- survivors.push(quote);
145
- }
146
-
147
- return { survivors, excluded };
148
- }
@@ -1,249 +0,0 @@
1
- // US equity market session classification.
2
- //
3
- // A price printed at 3am is not the same quality of fact as a price printed at
4
- // noon. This module says which of three sessions an instant falls in so the
5
- // rest of the product can price staleness risk honestly.
6
- //
7
- // 'core' [09:30, 16:00) ET real price discovery, tightest spreads
8
- // 'extended' [04:00, 09:30) ET pre market, and [16:00, 20:00) ET post market
9
- // thin books, wider spreads, higher staleness risk
10
- // 'dark' everything else, plus every weekend and every NYSE holiday
11
- //
12
- // The original brief modelled 04:00 to 20:00 as one uniform lit window. That is
13
- // wrong: it matches the Arcus regularTradingHours flag but not market reality,
14
- // because staleness risk differs materially between core and pre/post. Core and
15
- // extended are therefore distinct states here.
16
- //
17
- // BOUNDARY CONVENTION: every interval is half open, [start, end), in ET wall
18
- // clock. 09:30:00.000 ET exactly is 'core'; 16:00:00.000 ET exactly is
19
- // 'extended'; 04:00:00.000 ET is 'extended' and 20:00:00.000 ET is 'dark'.
20
- //
21
- // ET is derived from UTC using fixtures/dst_transitions.json only. No UTC offset
22
- // is hardcoded and no host timezone database is consulted, so this module is
23
- // deterministic and offline. Instants outside the fixture's coverage throw
24
- // rather than guess an offset: unknown is not zero.
25
-
26
- import { getDstTransitions, getNyseHolidays } from './fixtures.mjs';
27
-
28
- const MS_PER_SECOND = 1000;
29
- const MS_PER_MINUTE = 60 * MS_PER_SECOND;
30
- const MS_PER_HOUR = 60 * MS_PER_MINUTE;
31
- const MS_PER_DAY = 24 * MS_PER_HOUR;
32
-
33
- // Eastern Standard Time is UTC-5, Eastern Daylight Time is UTC-4. These are the
34
- // two possible ET offsets, selected per instant by the fixture, never assumed.
35
- const EST_OFFSET_MS = -5 * MS_PER_HOUR;
36
- const EDT_OFFSET_MS = -4 * MS_PER_HOUR;
37
-
38
- // ET wall clock minutes since midnight.
39
- const PRE_MARKET_OPEN_MIN = 4 * 60; // 04:00
40
- const CORE_OPEN_MIN = 9 * 60 + 30; // 09:30
41
- const CORE_CLOSE_MIN = 16 * 60; // 16:00
42
- const POST_MARKET_CLOSE_MIN = 20 * 60; // 20:00
43
-
44
- const SESSION_EDGES_MIN = Object.freeze([
45
- PRE_MARKET_OPEN_MIN,
46
- CORE_OPEN_MIN,
47
- CORE_CLOSE_MIN,
48
- POST_MARKET_CLOSE_MIN,
49
- ]);
50
-
51
- export const SESSION_STATES = Object.freeze(['core', 'extended', 'dark']);
52
-
53
- // The fixture stores startUtc / endUtc as epoch SECONDS (verified: 1772953200
54
- // decodes to 2026-03-08T07:00:00Z, the 2026 spring forward; as milliseconds it
55
- // would decode to 1970). Normalized to milliseconds once, at load.
56
- let dstIndex;
57
- let holidaySet;
58
-
59
- function buildDstIndex() {
60
- const rows = getDstTransitions().dstTransitions.map((entry) => ({
61
- year: entry.year,
62
- startMs: entry.startUtc * MS_PER_SECOND,
63
- endMs: entry.endUtc * MS_PER_SECOND,
64
- }));
65
- rows.sort((a, b) => a.startMs - b.startMs);
66
-
67
- // Coverage runs from the start of the first covered year to the end of the
68
- // last covered year, in UTC. Outside that range we have no ground truth.
69
- const firstYear = rows[0].year;
70
- const lastYear = rows[rows.length - 1].year;
71
- return Object.freeze({
72
- rows: Object.freeze(rows),
73
- firstYear,
74
- lastYear,
75
- coverageStartMs: Date.UTC(firstYear, 0, 1),
76
- coverageEndMs: Date.UTC(lastYear + 1, 0, 1),
77
- });
78
- }
79
-
80
- function dst() {
81
- dstIndex ??= buildDstIndex();
82
- return dstIndex;
83
- }
84
-
85
- // holidayDayIndex entries are epoch days counted in UTC: 20454 * 86400000 is
86
- // 1970-01-01 + 20454 days = 2026-01-01, which matches the aligned dates[0].
87
- // Verified positionally against all 50 entries of the dates array.
88
- function holidays() {
89
- if (holidaySet === undefined) {
90
- holidaySet = new Set(getNyseHolidays().holidayDayIndex);
91
- }
92
- return holidaySet;
93
- }
94
-
95
- function assertEpochMs(epochMs, label) {
96
- if (!Number.isSafeInteger(epochMs)) {
97
- throw new TypeError(`${label}: epochMs must be a safe integer number of milliseconds`);
98
- }
99
- }
100
-
101
- function assertCovered(epochMs, label) {
102
- const { coverageStartMs, coverageEndMs, firstYear, lastYear } = dst();
103
- if (epochMs < coverageStartMs || epochMs >= coverageEndMs) {
104
- throw new RangeError(
105
- `${label}: ${new Date(epochMs).toISOString()} is outside DST fixture coverage ` +
106
- `(${firstYear} through ${lastYear}). Refusing to assume a UTC offset.`,
107
- );
108
- }
109
- }
110
-
111
- // True when the instant falls inside a fixture daylight saving window. The
112
- // fixture boundaries are the exact UTC instants the offset changes, so the
113
- // comparison is half open on both ends of the DST window too.
114
- function isDaylightTime(epochMs) {
115
- for (const row of dst().rows) {
116
- if (epochMs >= row.startMs && epochMs < row.endMs) return true;
117
- }
118
- return false;
119
- }
120
-
121
- function etOffsetMs(epochMs) {
122
- return isDaylightTime(epochMs) ? EDT_OFFSET_MS : EST_OFFSET_MS;
123
- }
124
-
125
- // Decompose an instant into ET civil fields without any host timezone lookup.
126
- // Shifting the epoch by the ET offset and then reading UTC getters yields ET
127
- // wall clock, which is exactly what the session rules are written against.
128
- function etFields(epochMs) {
129
- const shifted = epochMs + etOffsetMs(epochMs);
130
- const dayIndex = Math.floor(shifted / MS_PER_DAY);
131
- const msIntoDay = shifted - dayIndex * MS_PER_DAY;
132
- const date = new Date(shifted);
133
- return {
134
- dayIndex,
135
- minuteOfDay: Math.floor(msIntoDay / MS_PER_MINUTE),
136
- msIntoDay,
137
- weekday: date.getUTCDay(), // 0 Sunday through 6 Saturday, in ET
138
- };
139
- }
140
-
141
- function isTradingDay({ dayIndex, weekday }) {
142
- if (weekday === 0 || weekday === 6) return false;
143
- return !holidays().has(dayIndex);
144
- }
145
-
146
- function stateForMinuteOfDay(minuteOfDay) {
147
- if (minuteOfDay < PRE_MARKET_OPEN_MIN) return 'dark';
148
- if (minuteOfDay < CORE_OPEN_MIN) return 'extended';
149
- if (minuteOfDay < CORE_CLOSE_MIN) return 'core';
150
- if (minuteOfDay < POST_MARKET_CLOSE_MIN) return 'extended';
151
- return 'dark';
152
- }
153
-
154
- /**
155
- * Which session the instant falls in.
156
- * @param {number} epochMs epoch milliseconds, UTC
157
- * @returns {'core'|'extended'|'dark'}
158
- */
159
- export function sessionState(epochMs) {
160
- assertEpochMs(epochMs, 'sessionState');
161
- assertCovered(epochMs, 'sessionState');
162
-
163
- const fields = etFields(epochMs);
164
- if (!isTradingDay(fields)) return 'dark';
165
- return stateForMinuteOfDay(fields.minuteOfDay);
166
- }
167
-
168
- /**
169
- * Convenience predicate: no lit venue is quoting into real price discovery.
170
- * @param {number} epochMs epoch milliseconds, UTC
171
- * @returns {boolean}
172
- */
173
- export function isDark(epochMs) {
174
- assertEpochMs(epochMs, 'isDark');
175
- return sessionState(epochMs) === 'dark';
176
- }
177
-
178
- // Convert an ET civil day plus an ET minute of day back to an epoch instant.
179
- // The offset depends on the instant itself, so this resolves the offset by fixed
180
- // point: guess with one offset, recompute the offset at the candidate instant,
181
- // and re-derive. Two passes converge for every real ET wall time, and the
182
- // verify step below rejects the wall times that do not exist (the skipped hour
183
- // on spring forward day). Session edges are 04:00 and later so they are never
184
- // inside the 02:00 to 03:00 gap, but the guard is kept rather than assumed away.
185
- function etWallToEpochMs(dayIndex, minuteOfDay) {
186
- const naive = dayIndex * MS_PER_DAY + minuteOfDay * MS_PER_MINUTE;
187
- let candidate = naive - EST_OFFSET_MS;
188
- for (let pass = 0; pass < 2; pass += 1) {
189
- const next = naive - etOffsetMs(candidate);
190
- if (next === candidate) break;
191
- candidate = next;
192
- }
193
- const check = etFields(candidate);
194
- if (check.dayIndex !== dayIndex || check.minuteOfDay !== minuteOfDay) return null;
195
- return candidate;
196
- }
197
-
198
- /**
199
- * The next instant at which the session state changes, at or after epochMs.
200
- *
201
- * Scans forward day by day over real ET calendar days so weekends, holidays,
202
- * and DST changes are all handled by the same code path that classifies a
203
- * single instant. Returns { at, from, to } where `at` is the first millisecond
204
- * of the new state (edges are inclusive starts, matching sessionState).
205
- *
206
- * @param {number} epochMs epoch milliseconds, UTC
207
- * @returns {{at: number, from: 'core'|'extended'|'dark', to: 'core'|'extended'|'dark'}}
208
- */
209
- export function nextTransition(epochMs) {
210
- assertEpochMs(epochMs, 'nextTransition');
211
- assertCovered(epochMs, 'nextTransition');
212
-
213
- const from = sessionState(epochMs);
214
- const startFields = etFields(epochMs);
215
-
216
- // A dark stretch can span a long weekend plus a holiday, and the fixture has
217
- // no gap longer than a few days, so a bounded scan is enough. 400 days covers
218
- // any run of dark days the calendar can produce inside fixture coverage.
219
- const MAX_DAYS_SCANNED = 400;
220
-
221
- for (let offset = 0; offset < MAX_DAYS_SCANNED; offset += 1) {
222
- const dayIndex = startFields.dayIndex + offset;
223
- const edges = [];
224
-
225
- // Day boundary itself is a candidate edge: the state can change at ET
226
- // midnight when a lit day rolls into a dark weekend day or vice versa.
227
- if (offset > 0) edges.push(0);
228
- for (const edgeMin of SESSION_EDGES_MIN) edges.push(edgeMin);
229
-
230
- for (const edgeMin of edges) {
231
- const at = etWallToEpochMs(dayIndex, edgeMin);
232
- if (at === null) continue; // nonexistent wall time on a spring forward day
233
- if (at <= epochMs) continue;
234
- if (at >= dst().coverageEndMs) {
235
- throw new RangeError(
236
- `nextTransition: next boundary after ${new Date(epochMs).toISOString()} falls ` +
237
- `outside DST fixture coverage (through ${dst().lastYear}).`,
238
- );
239
- }
240
- const to = sessionState(at);
241
- if (to !== from) return Object.freeze({ at, from, to });
242
- }
243
- }
244
-
245
- throw new RangeError(
246
- `nextTransition: no session change found within ${MAX_DAYS_SCANNED} days of ` +
247
- `${new Date(epochMs).toISOString()}`,
248
- );
249
- }