@oracle-agent/oracle 0.14.1 → 0.16.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 (450) 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 +60 -0
  7. package/dist/bin/oracle-data-mcp.mjs +9 -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 +63 -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/strategy.mjs +18 -0
  44. package/dist/cli/commands/swap.mjs +36 -0
  45. package/dist/cli/commands/upgrade.mjs +2 -0
  46. package/dist/cli/commands/vault.mjs +1 -0
  47. package/dist/cli/commands/version.mjs +2 -0
  48. package/dist/data/desk-data.mjs +55 -0
  49. package/dist/data/names.mjs +2 -0
  50. package/dist/data/providers/farming.mjs +2 -0
  51. package/dist/equities/index.mjs +1 -0
  52. package/dist/index.mjs +60 -0
  53. package/dist/nft-gas-war-guard.mjs +1 -0
  54. package/dist/onboarding/harness-configs.mjs +4 -0
  55. package/dist/portfolio-risk.mjs +1 -0
  56. package/dist/prepare-envelope.mjs +1 -0
  57. package/dist/public-api/agent-grants.mjs +3 -0
  58. package/dist/public-control/policy-schema.mjs +1 -0
  59. package/dist/router/index.mjs +2 -0
  60. package/dist/scanner/index.mjs +4 -0
  61. package/dist/signals/index.mjs +1 -0
  62. package/dist/strategy/index.mjs +3 -0
  63. package/dist/watch-preferences.mjs +1 -0
  64. package/package.json +33 -78
  65. package/public/oracle-splash/_variants/ice.html +5 -18
  66. package/public/oracle-splash/_variants/ivory.html +5 -18
  67. package/public/oracle-splash/downloads/index.html +4 -4
  68. package/public/oracle-splash/index.html +113 -34
  69. package/CONTRIBUTING.md +0 -99
  70. package/bin/desk-server.mjs +0 -473
  71. package/bin/oracle-data-mcp.mjs +0 -1252
  72. package/bin/oracle-equities.mjs +0 -168
  73. package/bin/oracle-init.mjs +0 -525
  74. package/bin/oracle-public-server.mjs +0 -36
  75. package/bin/oracle-route.mjs +0 -254
  76. package/bin/oracle-scan.mjs +0 -192
  77. package/bin/oracle-upgrade.mjs +0 -42
  78. package/bin/oracle.mjs +0 -34
  79. package/docs/adding-a-chain.md +0 -229
  80. package/docs/architecture.md +0 -139
  81. package/docs/buzz-integration.md +0 -115
  82. package/docs/cli.md +0 -143
  83. package/docs/connectors.md +0 -13
  84. package/docs/hyperliquid-builder-code.md +0 -50
  85. package/docs/locals-only-fee-waiver.md +0 -34
  86. package/docs/oracle-pack-standard.md +0 -29
  87. package/docs/profiles.md +0 -151
  88. package/docs/public-surface.md +0 -206
  89. package/examples/add-a-chain.mjs +0 -65
  90. package/examples/oracle-pack-template.mjs +0 -38
  91. package/examples/research-a-token.mjs +0 -70
  92. package/plugins/oracle-owner-gate/__init__.py +0 -288
  93. package/plugins/oracle-owner-gate/plugin.yaml +0 -9
  94. package/protocols/templates/safe-erc20/SECURITY.md +0 -16
  95. package/protocols/templates/safe-erc20/foundry.toml +0 -12
  96. package/protocols/templates/safe-erc20/remappings.txt +0 -2
  97. package/protocols/templates/safe-erc20/src/SafeERC20.sol +0 -53
  98. package/protocols/templates/safe-erc20/test/SafeERC20.t.sol +0 -72
  99. package/scripts/adversarial-bench.mjs +0 -114
  100. package/scripts/build-dist.mjs +0 -119
  101. package/scripts/build-inscription.py +0 -230
  102. package/scripts/check-doc-drift.mjs +0 -123
  103. package/scripts/check-test-count.mjs +0 -105
  104. package/scripts/e2e-hl-markets.mjs +0 -21
  105. package/scripts/e2e-hl-perps.mjs +0 -48
  106. package/scripts/e2e-hypercore-staking.mjs +0 -128
  107. package/scripts/e2e-solana-bitcoin.mjs +0 -183
  108. package/scripts/guard-publish.mjs +0 -22
  109. package/scripts/protocol-template-gate.mjs +0 -15
  110. package/scripts/public-api-scan.mjs +0 -23
  111. package/scripts/publish-dist.mjs +0 -67
  112. package/scripts/record-hero-cli.sh +0 -80
  113. package/scripts/secret-scan.mjs +0 -169
  114. package/scripts/smoke-dist-bins.mjs +0 -89
  115. package/scripts/verify-dist.mjs +0 -29
  116. package/scripts/verify-v3-venues.mjs +0 -233
  117. package/skins/oracle.yaml +0 -56
  118. package/src/action-receipts.mjs +0 -265
  119. package/src/action-semantics.mjs +0 -62
  120. package/src/address-book.mjs +0 -208
  121. package/src/agent-auth.mjs +0 -206
  122. package/src/approval-guard.mjs +0 -282
  123. package/src/attestation-secret.mjs +0 -94
  124. package/src/audit-log.mjs +0 -196
  125. package/src/auth/oauth.mjs +0 -672
  126. package/src/auto-slippage.mjs +0 -378
  127. package/src/capability-posture.mjs +0 -125
  128. package/src/cards.mjs +0 -422
  129. package/src/chains.mjs +0 -62
  130. package/src/cli/chain-catalog.mjs +0 -215
  131. package/src/cli/chain-state.mjs +0 -74
  132. package/src/cli/commands/auth.mjs +0 -193
  133. package/src/cli/commands/bootstrap.mjs +0 -87
  134. package/src/cli/commands/chain.mjs +0 -143
  135. package/src/cli/commands/chat.mjs +0 -465
  136. package/src/cli/commands/credential.mjs +0 -9
  137. package/src/cli/commands/data-mcp.mjs +0 -18
  138. package/src/cli/commands/data.mjs +0 -75
  139. package/src/cli/commands/doctor.mjs +0 -136
  140. package/src/cli/commands/equities.mjs +0 -41
  141. package/src/cli/commands/farm.mjs +0 -129
  142. package/src/cli/commands/fees.mjs +0 -109
  143. package/src/cli/commands/follow.mjs +0 -130
  144. package/src/cli/commands/harness.mjs +0 -63
  145. package/src/cli/commands/help.mjs +0 -7
  146. package/src/cli/commands/init.mjs +0 -27
  147. package/src/cli/commands/mcp.mjs +0 -154
  148. package/src/cli/commands/model.mjs +0 -106
  149. package/src/cli/commands/plugins.mjs +0 -275
  150. package/src/cli/commands/prepare.mjs +0 -12
  151. package/src/cli/commands/public.mjs +0 -20
  152. package/src/cli/commands/resolve.mjs +0 -64
  153. package/src/cli/commands/route.mjs +0 -12
  154. package/src/cli/commands/runner.mjs +0 -9
  155. package/src/cli/commands/scan.mjs +0 -12
  156. package/src/cli/commands/setup.mjs +0 -294
  157. package/src/cli/commands/sign.mjs +0 -21
  158. package/src/cli/commands/signer.mjs +0 -9
  159. package/src/cli/commands/swap.mjs +0 -102
  160. package/src/cli/commands/upgrade.mjs +0 -12
  161. package/src/cli/commands/vault.mjs +0 -9
  162. package/src/cli/commands/version.mjs +0 -22
  163. package/src/cli/first-run.mjs +0 -23
  164. package/src/cli/kernel.mjs +0 -262
  165. package/src/cli/mcp-targets/chatgpt.mjs +0 -51
  166. package/src/cli/mcp-targets/claude-code.mjs +0 -39
  167. package/src/cli/mcp-targets/claude-desktop.mjs +0 -24
  168. package/src/cli/mcp-targets/codex.mjs +0 -41
  169. package/src/cli/mcp-targets/cursor.mjs +0 -32
  170. package/src/cli/mcp-targets/shared.mjs +0 -71
  171. package/src/cli/mcp-targets/vscode.mjs +0 -39
  172. package/src/cli/messaging-platforms.mjs +0 -217
  173. package/src/cli/model-config.mjs +0 -78
  174. package/src/cli/operator-dispatch.mjs +0 -194
  175. package/src/cli/oracle-harness.py +0 -415
  176. package/src/cli/paths.mjs +0 -107
  177. package/src/cli/runtime.mjs +0 -351
  178. package/src/cli/setup-state.mjs +0 -168
  179. package/src/cli/spawn-child.mjs +0 -43
  180. package/src/data/catalog.mjs +0 -858
  181. package/src/data/desk-data.mjs +0 -877
  182. package/src/data/http.mjs +0 -253
  183. package/src/data/names.mjs +0 -188
  184. package/src/data/provider-endpoint.mjs +0 -94
  185. package/src/data/providers/aave-v3.mjs +0 -139
  186. package/src/data/providers/across-bridge.mjs +0 -41
  187. package/src/data/providers/aerodrome.mjs +0 -245
  188. package/src/data/providers/aevo.mjs +0 -33
  189. package/src/data/providers/approval-indexer.mjs +0 -133
  190. package/src/data/providers/approvals.mjs +0 -594
  191. package/src/data/providers/babylon-staking.mjs +0 -149
  192. package/src/data/providers/balancer.mjs +0 -209
  193. package/src/data/providers/beefy-yields.mjs +0 -45
  194. package/src/data/providers/birdeye-tokens.mjs +0 -33
  195. package/src/data/providers/bisq-markets.mjs +0 -145
  196. package/src/data/providers/bitcoin-esplora.mjs +0 -216
  197. package/src/data/providers/bitcoin-meta.mjs +0 -378
  198. package/src/data/providers/blockscout.mjs +0 -14
  199. package/src/data/providers/bob-rootstock.mjs +0 -198
  200. package/src/data/providers/braiins-insights.mjs +0 -103
  201. package/src/data/providers/bridge-aggregator.mjs +0 -259
  202. package/src/data/providers/bridges.mjs +0 -242
  203. package/src/data/providers/bungee.mjs +0 -29
  204. package/src/data/providers/celestia-da.mjs +0 -38
  205. package/src/data/providers/changenow.mjs +0 -135
  206. package/src/data/providers/compound.mjs +0 -32
  207. package/src/data/providers/connext.mjs +0 -28
  208. package/src/data/providers/convex.mjs +0 -30
  209. package/src/data/providers/cow-protocol.mjs +0 -110
  210. package/src/data/providers/cowswap.mjs +0 -415
  211. package/src/data/providers/curve.mjs +0 -201
  212. package/src/data/providers/das-assets.mjs +0 -59
  213. package/src/data/providers/debridge.mjs +0 -109
  214. package/src/data/providers/defillama.mjs +0 -88
  215. package/src/data/providers/dexscreener.mjs +0 -43
  216. package/src/data/providers/drift.mjs +0 -32
  217. package/src/data/providers/dydx.mjs +0 -41
  218. package/src/data/providers/evm-rpc.mjs +0 -225
  219. package/src/data/providers/farming.mjs +0 -291
  220. package/src/data/providers/geckoterminal.mjs +0 -34
  221. package/src/data/providers/gmx.mjs +0 -496
  222. package/src/data/providers/hiro-stacks.mjs +0 -150
  223. package/src/data/providers/hl-assets.mjs +0 -165
  224. package/src/data/providers/hl-info.mjs +0 -108
  225. package/src/data/providers/hl-markets.mjs +0 -210
  226. package/src/data/providers/hl-outcome.mjs +0 -61
  227. package/src/data/providers/hl-perps.mjs +0 -419
  228. package/src/data/providers/hl-staking.mjs +0 -353
  229. package/src/data/providers/hl-ws.mjs +0 -119
  230. package/src/data/providers/hyperevm-dex.mjs +0 -49
  231. package/src/data/providers/jito-mev.mjs +0 -117
  232. package/src/data/providers/jupiter-venues.mjs +0 -119
  233. package/src/data/providers/jupiter.mjs +0 -315
  234. package/src/data/providers/kamino-strategies.mjs +0 -143
  235. package/src/data/providers/l2beat-tvl.mjs +0 -42
  236. package/src/data/providers/lifi.mjs +0 -166
  237. package/src/data/providers/liquid-esplora.mjs +0 -152
  238. package/src/data/providers/magiceden-ordinals.mjs +0 -141
  239. package/src/data/providers/magiceden-sol.mjs +0 -406
  240. package/src/data/providers/mempool-lightning.mjs +0 -144
  241. package/src/data/providers/mempool-mining.mjs +0 -142
  242. package/src/data/providers/meteora-dlmm.mjs +0 -168
  243. package/src/data/providers/morpho.mjs +0 -177
  244. package/src/data/providers/nft-gallery.mjs +0 -163
  245. package/src/data/providers/nft-portfolio.mjs +0 -494
  246. package/src/data/providers/odos.mjs +0 -156
  247. package/src/data/providers/ondo.mjs +0 -31
  248. package/src/data/providers/oneinch.mjs +0 -174
  249. package/src/data/providers/opensea-multichain.mjs +0 -136
  250. package/src/data/providers/opensea-nft.mjs +0 -371
  251. package/src/data/providers/orca.mjs +0 -29
  252. package/src/data/providers/ordinals-runes.mjs +0 -180
  253. package/src/data/providers/pancakeswap.mjs +0 -28
  254. package/src/data/providers/paraswap.mjs +0 -125
  255. package/src/data/providers/pendle.mjs +0 -188
  256. package/src/data/providers/poly-clob.mjs +0 -283
  257. package/src/data/providers/poly-public.mjs +0 -96
  258. package/src/data/providers/poly-ws.mjs +0 -103
  259. package/src/data/providers/polygon-staking.mjs +0 -227
  260. package/src/data/providers/portfolio-history.mjs +0 -394
  261. package/src/data/providers/portfolio.mjs +0 -600
  262. package/src/data/providers/pyth-price-feeds.mjs +0 -172
  263. package/src/data/providers/raydium.mjs +0 -35
  264. package/src/data/providers/relayscan-mev.mjs +0 -72
  265. package/src/data/providers/rfq.mjs +0 -15
  266. package/src/data/providers/rh-agent.mjs +0 -59
  267. package/src/data/providers/sanctum-lst.mjs +0 -180
  268. package/src/data/providers/satflow.mjs +0 -341
  269. package/src/data/providers/signed-material-guard.mjs +0 -108
  270. package/src/data/providers/solana-rpc.mjs +0 -197
  271. package/src/data/providers/solend-lending.mjs +0 -166
  272. package/src/data/providers/spark-lend.mjs +0 -42
  273. package/src/data/providers/squid-router.mjs +0 -29
  274. package/src/data/providers/stargate.mjs +0 -29
  275. package/src/data/providers/sushiswap.mjs +0 -30
  276. package/src/data/providers/symbiotic-restaking.mjs +0 -41
  277. package/src/data/providers/thorchain.mjs +0 -36
  278. package/src/data/providers/traderjoe.mjs +0 -28
  279. package/src/data/providers/twap.mjs +0 -152
  280. package/src/data/providers/uniswap-v3.mjs +0 -457
  281. package/src/data/providers/uniswap-v4.mjs +0 -414
  282. package/src/data/providers/vertex.mjs +0 -41
  283. package/src/data/providers/wallet-watch.mjs +0 -264
  284. package/src/data/providers/wormhole.mjs +0 -28
  285. package/src/data/providers/yearn.mjs +0 -31
  286. package/src/data/providers/zerox.mjs +0 -167
  287. package/src/data/public-api-scan.mjs +0 -61
  288. package/src/data/quote-placeholder.mjs +0 -31
  289. package/src/desk/swap-prepare.mjs +0 -194
  290. package/src/equities/bridge.mjs +0 -388
  291. package/src/equities/fixtures/bridge_routes.json +0 -159
  292. package/src/equities/fixtures/dst_transitions.json +0 -29
  293. package/src/equities/fixtures/equity_dex_pools.json +0 -154
  294. package/src/equities/fixtures/hip3_l2books.json +0 -2711
  295. package/src/equities/fixtures/nyse_holidays.json +0 -1
  296. package/src/equities/fixtures/oracles.json +0 -100
  297. package/src/equities/fixtures/rh_pool_state.json +0 -244
  298. package/src/equities/fixtures/solana_xstocks.json +0 -704
  299. package/src/equities/fixtures/ton_xstocks.json +0 -35
  300. package/src/equities/fixtures/universe.json +0 -411
  301. package/src/equities/fixtures/venues.json +0 -3542
  302. package/src/equities/fixtures.mjs +0 -405
  303. package/src/equities/funding.mjs +0 -142
  304. package/src/equities/index.mjs +0 -166
  305. package/src/equities/liveness.mjs +0 -148
  306. package/src/equities/marketHours.mjs +0 -249
  307. package/src/equities/normalize.mjs +0 -295
  308. package/src/equities/num.mjs +0 -146
  309. package/src/equities/prepare.mjs +0 -95
  310. package/src/equities/rank.mjs +0 -260
  311. package/src/equities/sourceNum.mjs +0 -69
  312. package/src/equities/types.mjs +0 -220
  313. package/src/equities/venues/arcus-perp.mjs +0 -84
  314. package/src/equities/venues/arcus-spot.mjs +0 -105
  315. package/src/equities/venues/hyperliquid-hip3.mjs +0 -224
  316. package/src/equities/venues/rh-uniswap.mjs +0 -216
  317. package/src/equities/venues/solana-xstocks.mjs +0 -139
  318. package/src/equities/venues/ton-stonfi.mjs +0 -139
  319. package/src/exact-integer.mjs +0 -72
  320. package/src/exec-policy.mjs +0 -419
  321. package/src/flags.mjs +0 -15
  322. package/src/fresh-window.mjs +0 -76
  323. package/src/gmx-attestation.mjs +0 -176
  324. package/src/index.mjs +0 -107
  325. package/src/licensing/locals-only.mjs +0 -25
  326. package/src/nft-gas-war-guard.mjs +0 -139
  327. package/src/onboarding/agent-keys.mjs +0 -161
  328. package/src/onboarding/harness-configs.mjs +0 -102
  329. package/src/onboarding/index.mjs +0 -18
  330. package/src/onboarding/tiers.mjs +0 -139
  331. package/src/oracle-env.mjs +0 -55
  332. package/src/package-manifest.mjs +0 -42
  333. package/src/plugins/agent-plugin-loader.mjs +0 -225
  334. package/src/portfolio-risk.mjs +0 -170
  335. package/src/prepare-envelope.mjs +0 -204
  336. package/src/profile-upgrade.mjs +0 -277
  337. package/src/protocol-execution.mjs +0 -84
  338. package/src/protocol-templates/gate.mjs +0 -179
  339. package/src/protocol-templates/prepare-deploy.mjs +0 -81
  340. package/src/public-api/agent-grants.mjs +0 -57
  341. package/src/public-api/buzz-integration.mjs +0 -267
  342. package/src/public-api/connect-agent.mjs +0 -424
  343. package/src/public-api/grants.mjs +0 -142
  344. package/src/public-api/http.mjs +0 -707
  345. package/src/public-api/metering.mjs +0 -111
  346. package/src/public-control/aa-adapter.mjs +0 -402
  347. package/src/public-control/build-registry.mjs +0 -227
  348. package/src/public-control/bundler-client.mjs +0 -357
  349. package/src/public-control/grant-indexer.mjs +0 -296
  350. package/src/public-control/policy-render.mjs +0 -69
  351. package/src/public-control/policy-schema.mjs +0 -322
  352. package/src/public-control/runtime-config.mjs +0 -275
  353. package/src/public-control/session-key-model.mjs +0 -379
  354. package/src/public-control/session-orchestrator.mjs +0 -418
  355. package/src/rfq/intent.mjs +0 -180
  356. package/src/rfq/sources.mjs +0 -181
  357. package/src/route-attestation.mjs +0 -132
  358. package/src/router/best-execution.mjs +0 -326
  359. package/src/router/index.mjs +0 -250
  360. package/src/router/integrator-fee.mjs +0 -135
  361. package/src/router/prepare-bridge.mjs +0 -293
  362. package/src/router/prepare-route.mjs +0 -398
  363. package/src/router/proposal.mjs +0 -311
  364. package/src/router/risk-classifier.mjs +0 -119
  365. package/src/router/route-sources.mjs +0 -364
  366. package/src/scanner/chains.config.mjs +0 -543
  367. package/src/scanner/contract.mjs +0 -270
  368. package/src/scanner/evm-scanner.mjs +0 -394
  369. package/src/scanner/index.mjs +0 -9
  370. package/src/scanner/v2-venue.mjs +0 -351
  371. package/src/scanner/v3-venue.mjs +0 -308
  372. package/src/scopes.mjs +0 -44
  373. package/src/sell-simulation.mjs +0 -167
  374. package/src/signals/engine.mjs +0 -146
  375. package/src/signals/index.mjs +0 -1
  376. package/src/token-transfer-guard.mjs +0 -188
  377. package/src/tui/app.mjs +0 -438
  378. package/src/tui/backend.mjs +0 -215
  379. package/src/tui/format.mjs +0 -272
  380. package/src/tui/gateway-client.mjs +0 -177
  381. package/src/tui/input.mjs +0 -501
  382. package/src/tui/memory.mjs +0 -105
  383. package/src/tui/meta-learn.mjs +0 -85
  384. package/src/tui/renderer.mjs +0 -443
  385. package/src/tui/scheduler.mjs +0 -205
  386. package/src/tui/skills-loader.mjs +0 -140
  387. package/src/tui/standalone-client.mjs +0 -752
  388. package/src/tui/theme.mjs +0 -81
  389. package/src/vault-attestation.mjs +0 -146
  390. package/src/venues.mjs +0 -206
  391. package/src/watch-preferences.mjs +0 -85
  392. /package/{profiles → dist/assets/profiles}/_template/SOUL.md +0 -0
  393. /package/{profiles → dist/assets/profiles}/_template/profile.json +0 -0
  394. /package/{profiles → dist/assets/profiles}/bitcoin-agent/SOUL.md +0 -0
  395. /package/{profiles → dist/assets/profiles}/bitcoin-agent/profile.json +0 -0
  396. /package/{profiles → dist/assets/profiles}/hyperliquid-agent/SOUL.md +0 -0
  397. /package/{profiles → dist/assets/profiles}/hyperliquid-agent/profile.json +0 -0
  398. /package/{profiles → dist/assets/profiles}/oracle/SOUL.md +0 -0
  399. /package/{profiles → dist/assets/profiles}/oracle/profile.json +0 -0
  400. /package/{profiles → dist/assets/profiles}/polymarket-agent/SOUL.md +0 -0
  401. /package/{profiles → dist/assets/profiles}/polymarket-agent/profile.json +0 -0
  402. /package/{profiles → dist/assets/profiles}/profile.schema.json +0 -0
  403. /package/{profiles → dist/assets/profiles}/protocol-builder/SOUL.md +0 -0
  404. /package/{profiles → dist/assets/profiles}/protocol-builder/profile.json +0 -0
  405. /package/{profiles → dist/assets/profiles}/robinhood-agent/SOUL.md +0 -0
  406. /package/{profiles → dist/assets/profiles}/robinhood-agent/profile.json +0 -0
  407. /package/{profiles → dist/assets/profiles}/solana-agent/SOUL.md +0 -0
  408. /package/{profiles → dist/assets/profiles}/solana-agent/profile.json +0 -0
  409. /package/{profiles → dist/assets/profiles}/stable-agent/SOUL.md +0 -0
  410. /package/{profiles → dist/assets/profiles}/stable-agent/profile.json +0 -0
  411. /package/{skills → dist/assets/skills}/balance/SKILL.md +0 -0
  412. /package/{skills → dist/assets/skills}/bitcoin-l1.md +0 -0
  413. /package/{skills → dist/assets/skills}/chain-defi-ecosystem-profiling.md +0 -0
  414. /package/{skills → dist/assets/skills}/evm-contract-research.md +0 -0
  415. /package/{skills → dist/assets/skills}/hyperliquid.md +0 -0
  416. /package/{skills → dist/assets/skills}/multichain-exec-desk.md +0 -0
  417. /package/{skills → dist/assets/skills}/oracle-action-semantics/SKILL.md +0 -0
  418. /package/{skills → dist/assets/skills}/oracle-best-execution/SKILL.md +0 -0
  419. /package/{skills → dist/assets/skills}/oracle-best-execution.md +0 -0
  420. /package/{skills → dist/assets/skills}/oracle-bitcoin/SKILL.md +0 -0
  421. /package/{skills → dist/assets/skills}/oracle-chain-graphs-telegram-cards/SKILL.md +0 -0
  422. /package/{skills → dist/assets/skills}/oracle-chat/SKILL.md +0 -0
  423. /package/{skills → dist/assets/skills}/oracle-chat/chain.SKILL.md +0 -0
  424. /package/{skills → dist/assets/skills}/oracle-chat/setup.SKILL.md +0 -0
  425. /package/{skills → dist/assets/skills}/oracle-circuit-breaker/SKILL.md +0 -0
  426. /package/{skills → dist/assets/skills}/oracle-contract-research/SKILL.md +0 -0
  427. /package/{skills → dist/assets/skills}/oracle-desk/SKILL.md +0 -0
  428. /package/{skills → dist/assets/skills}/oracle-desk.md +0 -0
  429. /package/{skills → dist/assets/skills}/oracle-dex-launch/SKILL.md +0 -0
  430. /package/{skills → dist/assets/skills}/oracle-equities/SKILL.md +0 -0
  431. /package/{skills → dist/assets/skills}/oracle-grants/SKILL.md +0 -0
  432. /package/{skills → dist/assets/skills}/oracle-hypercore-staking/SKILL.md +0 -0
  433. /package/{skills → dist/assets/skills}/oracle-hyperliquid/SKILL.md +0 -0
  434. /package/{skills → dist/assets/skills}/oracle-meme-token-sniper/SKILL.md +0 -0
  435. /package/{skills → dist/assets/skills}/oracle-multichain-nft-launch/SKILL.md +0 -0
  436. /package/{skills → dist/assets/skills}/oracle-multichain-token-launch/SKILL.md +0 -0
  437. /package/{skills → dist/assets/skills}/oracle-nft-gacha-launch/SKILL.md +0 -0
  438. /package/{skills → dist/assets/skills}/oracle-nft-mint-gas-war/SKILL.md +0 -0
  439. /package/{skills → dist/assets/skills}/oracle-polymarket/SKILL.md +0 -0
  440. /package/{skills → dist/assets/skills}/oracle-protocol-builder/SKILL.md +0 -0
  441. /package/{skills → dist/assets/skills}/oracle-protocol-security/SKILL.md +0 -0
  442. /package/{skills → dist/assets/skills}/oracle-public-product/SKILL.md +0 -0
  443. /package/{skills → dist/assets/skills}/oracle-receipts/SKILL.md +0 -0
  444. /package/{skills → dist/assets/skills}/oracle-receipts.md +0 -0
  445. /package/{skills → dist/assets/skills}/oracle-rfq-tokenized-assets/SKILL.md +0 -0
  446. /package/{skills → dist/assets/skills}/oracle-smart-wallet-scanner/SKILL.md +0 -0
  447. /package/{skills → dist/assets/skills}/oracle-solana/SKILL.md +0 -0
  448. /package/{skills → dist/assets/skills}/oracle-solana-nft/SKILL.md +0 -0
  449. /package/{skills → dist/assets/skills}/oracle-token-research/SKILL.md +0 -0
  450. /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
- }