@oracle-agent/oracle 0.13.1 → 0.14.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.
- package/CONTRIBUTING.md +99 -0
- package/README.md +20 -14
- package/SECURITY.md +6 -8
- package/SETUP.md +25 -27
- package/bin/desk-server.mjs +473 -0
- package/bin/oracle-data-mcp.mjs +1252 -0
- package/bin/oracle-equities.mjs +168 -0
- package/bin/oracle-init.mjs +525 -0
- package/bin/oracle-public-server.mjs +36 -0
- package/bin/oracle-route.mjs +254 -0
- package/bin/oracle-scan.mjs +192 -0
- package/bin/oracle-upgrade.mjs +42 -0
- package/bin/oracle.mjs +34 -0
- package/docs/adding-a-chain.md +229 -0
- package/docs/architecture.md +139 -0
- package/docs/buzz-integration.md +115 -0
- package/docs/cli.md +143 -0
- package/docs/connectors.md +13 -0
- package/docs/hyperliquid-builder-code.md +50 -0
- package/docs/locals-only-fee-waiver.md +34 -0
- package/docs/oracle-pack-standard.md +29 -0
- package/docs/profiles.md +151 -0
- package/docs/public-surface.md +206 -0
- package/examples/add-a-chain.mjs +65 -0
- package/examples/oracle-pack-template.mjs +38 -0
- package/examples/research-a-token.mjs +70 -0
- package/package.json +78 -32
- package/plugins/oracle-owner-gate/__init__.py +288 -0
- package/plugins/oracle-owner-gate/plugin.yaml +9 -0
- package/{dist/assets/profiles → profiles}/oracle/SOUL.md +0 -14
- package/protocols/templates/safe-erc20/SECURITY.md +16 -0
- package/protocols/templates/safe-erc20/foundry.toml +12 -0
- package/protocols/templates/safe-erc20/remappings.txt +2 -0
- package/protocols/templates/safe-erc20/src/SafeERC20.sol +53 -0
- package/protocols/templates/safe-erc20/test/SafeERC20.t.sol +72 -0
- package/public/oracle-splash/_variants/ice.html +3 -3
- package/public/oracle-splash/_variants/ivory.html +3 -3
- package/public/oracle-splash/downloads/index.html +34 -223
- package/public/oracle-splash/index.html +11 -11
- package/scripts/adversarial-bench.mjs +114 -0
- package/scripts/build-dist.mjs +119 -0
- package/scripts/build-inscription.py +230 -0
- package/scripts/check-doc-drift.mjs +123 -0
- package/scripts/check-test-count.mjs +105 -0
- package/scripts/e2e-hl-markets.mjs +21 -0
- package/scripts/e2e-hl-perps.mjs +48 -0
- package/scripts/e2e-hypercore-staking.mjs +128 -0
- package/scripts/e2e-solana-bitcoin.mjs +183 -0
- package/scripts/guard-publish.mjs +22 -0
- package/scripts/protocol-template-gate.mjs +15 -0
- package/scripts/public-api-scan.mjs +23 -0
- package/scripts/publish-dist.mjs +67 -0
- package/scripts/record-hero-cli.sh +80 -0
- package/scripts/secret-scan.mjs +168 -0
- package/scripts/smoke-dist-bins.mjs +89 -0
- package/scripts/verify-dist.mjs +29 -0
- package/scripts/verify-v3-venues.mjs +233 -0
- package/skins/oracle.yaml +56 -0
- package/src/action-receipts.mjs +265 -0
- package/src/action-semantics.mjs +62 -0
- package/src/address-book.mjs +208 -0
- package/src/agent-auth.mjs +206 -0
- package/src/approval-guard.mjs +282 -0
- package/src/attestation-secret.mjs +94 -0
- package/src/audit-log.mjs +196 -0
- package/src/auth/oauth.mjs +672 -0
- package/src/auto-slippage.mjs +378 -0
- package/src/capability-posture.mjs +125 -0
- package/src/cards.mjs +422 -0
- package/src/chains.mjs +62 -0
- package/src/cli/chain-catalog.mjs +215 -0
- package/src/cli/chain-state.mjs +74 -0
- package/src/cli/commands/auth.mjs +193 -0
- package/src/cli/commands/bootstrap.mjs +87 -0
- package/src/cli/commands/chain.mjs +143 -0
- package/src/cli/commands/chat.mjs +465 -0
- package/src/cli/commands/credential.mjs +9 -0
- package/src/cli/commands/data-mcp.mjs +18 -0
- package/src/cli/commands/data.mjs +75 -0
- package/src/cli/commands/doctor.mjs +136 -0
- package/src/cli/commands/equities.mjs +41 -0
- package/src/cli/commands/farm.mjs +129 -0
- package/src/cli/commands/fees.mjs +109 -0
- package/src/cli/commands/follow.mjs +130 -0
- package/src/cli/commands/harness.mjs +63 -0
- package/src/cli/commands/help.mjs +7 -0
- package/src/cli/commands/init.mjs +27 -0
- package/src/cli/commands/mcp.mjs +154 -0
- package/src/cli/commands/model.mjs +106 -0
- package/src/cli/commands/plugins.mjs +241 -0
- package/src/cli/commands/prepare.mjs +12 -0
- package/src/cli/commands/public.mjs +20 -0
- package/src/cli/commands/resolve.mjs +64 -0
- package/src/cli/commands/route.mjs +12 -0
- package/src/cli/commands/runner.mjs +9 -0
- package/src/cli/commands/scan.mjs +12 -0
- package/src/cli/commands/setup.mjs +294 -0
- package/src/cli/commands/sign.mjs +21 -0
- package/src/cli/commands/signer.mjs +9 -0
- package/src/cli/commands/swap.mjs +102 -0
- package/src/cli/commands/upgrade.mjs +12 -0
- package/src/cli/commands/vault.mjs +9 -0
- package/src/cli/commands/version.mjs +22 -0
- package/src/cli/first-run.mjs +23 -0
- package/src/cli/kernel.mjs +262 -0
- package/src/cli/mcp-targets/chatgpt.mjs +51 -0
- package/src/cli/mcp-targets/claude-code.mjs +39 -0
- package/src/cli/mcp-targets/claude-desktop.mjs +24 -0
- package/src/cli/mcp-targets/codex.mjs +41 -0
- package/src/cli/mcp-targets/cursor.mjs +32 -0
- package/src/cli/mcp-targets/shared.mjs +71 -0
- package/src/cli/mcp-targets/vscode.mjs +39 -0
- package/src/cli/messaging-platforms.mjs +217 -0
- package/src/cli/model-config.mjs +78 -0
- package/src/cli/operator-dispatch.mjs +257 -0
- package/src/cli/oracle-harness.py +415 -0
- package/src/cli/paths.mjs +107 -0
- package/src/cli/runtime.mjs +351 -0
- package/src/cli/setup-state.mjs +168 -0
- package/src/cli/spawn-child.mjs +43 -0
- package/src/data/catalog.mjs +858 -0
- package/src/data/desk-data.mjs +877 -0
- package/src/data/http.mjs +253 -0
- package/src/data/names.mjs +188 -0
- package/src/data/provider-endpoint.mjs +94 -0
- package/src/data/providers/aave-v3.mjs +139 -0
- package/src/data/providers/across-bridge.mjs +41 -0
- package/src/data/providers/aerodrome.mjs +245 -0
- package/src/data/providers/aevo.mjs +33 -0
- package/src/data/providers/approval-indexer.mjs +133 -0
- package/src/data/providers/approvals.mjs +594 -0
- package/src/data/providers/babylon-staking.mjs +149 -0
- package/src/data/providers/balancer.mjs +209 -0
- package/src/data/providers/beefy-yields.mjs +45 -0
- package/src/data/providers/birdeye-tokens.mjs +33 -0
- package/src/data/providers/bisq-markets.mjs +145 -0
- package/src/data/providers/bitcoin-esplora.mjs +216 -0
- package/src/data/providers/bitcoin-meta.mjs +378 -0
- package/src/data/providers/blockscout.mjs +14 -0
- package/src/data/providers/bob-rootstock.mjs +198 -0
- package/src/data/providers/braiins-insights.mjs +103 -0
- package/src/data/providers/bridge-aggregator.mjs +259 -0
- package/src/data/providers/bridges.mjs +242 -0
- package/src/data/providers/bungee.mjs +29 -0
- package/src/data/providers/celestia-da.mjs +38 -0
- package/src/data/providers/changenow.mjs +135 -0
- package/src/data/providers/compound.mjs +32 -0
- package/src/data/providers/connext.mjs +28 -0
- package/src/data/providers/convex.mjs +30 -0
- package/src/data/providers/cow-protocol.mjs +110 -0
- package/src/data/providers/cowswap.mjs +415 -0
- package/src/data/providers/curve.mjs +201 -0
- package/src/data/providers/das-assets.mjs +59 -0
- package/src/data/providers/debridge.mjs +109 -0
- package/src/data/providers/defillama.mjs +88 -0
- package/src/data/providers/dexscreener.mjs +43 -0
- package/src/data/providers/drift.mjs +32 -0
- package/src/data/providers/dydx.mjs +41 -0
- package/src/data/providers/evm-rpc.mjs +225 -0
- package/src/data/providers/farming.mjs +291 -0
- package/src/data/providers/geckoterminal.mjs +34 -0
- package/src/data/providers/gmx.mjs +496 -0
- package/src/data/providers/hiro-stacks.mjs +150 -0
- package/src/data/providers/hl-assets.mjs +165 -0
- package/src/data/providers/hl-info.mjs +108 -0
- package/src/data/providers/hl-markets.mjs +210 -0
- package/src/data/providers/hl-outcome.mjs +61 -0
- package/src/data/providers/hl-perps.mjs +419 -0
- package/src/data/providers/hl-staking.mjs +353 -0
- package/src/data/providers/hl-ws.mjs +119 -0
- package/src/data/providers/hyperevm-dex.mjs +49 -0
- package/src/data/providers/jito-mev.mjs +117 -0
- package/src/data/providers/jupiter-venues.mjs +119 -0
- package/src/data/providers/jupiter.mjs +315 -0
- package/src/data/providers/kamino-strategies.mjs +143 -0
- package/src/data/providers/l2beat-tvl.mjs +42 -0
- package/src/data/providers/lifi.mjs +166 -0
- package/src/data/providers/liquid-esplora.mjs +152 -0
- package/src/data/providers/magiceden-ordinals.mjs +141 -0
- package/src/data/providers/magiceden-sol.mjs +406 -0
- package/src/data/providers/mempool-lightning.mjs +144 -0
- package/src/data/providers/mempool-mining.mjs +142 -0
- package/src/data/providers/meteora-dlmm.mjs +168 -0
- package/src/data/providers/morpho.mjs +177 -0
- package/src/data/providers/nft-gallery.mjs +163 -0
- package/src/data/providers/nft-portfolio.mjs +494 -0
- package/src/data/providers/odos.mjs +156 -0
- package/src/data/providers/ondo.mjs +31 -0
- package/src/data/providers/oneinch.mjs +174 -0
- package/src/data/providers/opensea-multichain.mjs +136 -0
- package/src/data/providers/opensea-nft.mjs +371 -0
- package/src/data/providers/orca.mjs +29 -0
- package/src/data/providers/ordinals-runes.mjs +180 -0
- package/src/data/providers/pancakeswap.mjs +28 -0
- package/src/data/providers/paraswap.mjs +125 -0
- package/src/data/providers/pendle.mjs +188 -0
- package/src/data/providers/poly-clob.mjs +283 -0
- package/src/data/providers/poly-public.mjs +96 -0
- package/src/data/providers/poly-ws.mjs +103 -0
- package/src/data/providers/polygon-staking.mjs +227 -0
- package/src/data/providers/portfolio-history.mjs +394 -0
- package/src/data/providers/portfolio.mjs +600 -0
- package/src/data/providers/pyth-price-feeds.mjs +172 -0
- package/src/data/providers/raydium.mjs +35 -0
- package/src/data/providers/relayscan-mev.mjs +72 -0
- package/src/data/providers/rfq.mjs +15 -0
- package/src/data/providers/rh-agent.mjs +59 -0
- package/src/data/providers/sanctum-lst.mjs +180 -0
- package/src/data/providers/satflow.mjs +341 -0
- package/src/data/providers/signed-material-guard.mjs +108 -0
- package/src/data/providers/solana-rpc.mjs +197 -0
- package/src/data/providers/solend-lending.mjs +166 -0
- package/src/data/providers/spark-lend.mjs +42 -0
- package/src/data/providers/squid-router.mjs +29 -0
- package/src/data/providers/stargate.mjs +29 -0
- package/src/data/providers/sushiswap.mjs +30 -0
- package/src/data/providers/symbiotic-restaking.mjs +41 -0
- package/src/data/providers/thorchain.mjs +36 -0
- package/src/data/providers/traderjoe.mjs +28 -0
- package/src/data/providers/twap.mjs +152 -0
- package/src/data/providers/uniswap-v3.mjs +457 -0
- package/src/data/providers/uniswap-v4.mjs +414 -0
- package/src/data/providers/vertex.mjs +41 -0
- package/src/data/providers/wallet-watch.mjs +264 -0
- package/src/data/providers/wormhole.mjs +28 -0
- package/src/data/providers/yearn.mjs +31 -0
- package/src/data/providers/zerox.mjs +167 -0
- package/src/data/public-api-scan.mjs +61 -0
- package/src/data/quote-placeholder.mjs +31 -0
- package/src/desk/swap-prepare.mjs +194 -0
- package/src/equities/bridge.mjs +388 -0
- package/src/equities/fixtures/bridge_routes.json +159 -0
- package/src/equities/fixtures/dst_transitions.json +29 -0
- package/src/equities/fixtures/equity_dex_pools.json +154 -0
- package/src/equities/fixtures/hip3_l2books.json +2711 -0
- package/src/equities/fixtures/nyse_holidays.json +1 -0
- package/src/equities/fixtures/oracles.json +100 -0
- package/src/equities/fixtures/rh_pool_state.json +244 -0
- package/src/equities/fixtures/solana_xstocks.json +704 -0
- package/src/equities/fixtures/ton_xstocks.json +35 -0
- package/src/equities/fixtures/universe.json +411 -0
- package/src/equities/fixtures/venues.json +3542 -0
- package/src/equities/fixtures.mjs +405 -0
- package/src/equities/funding.mjs +142 -0
- package/src/equities/index.mjs +166 -0
- package/src/equities/liveness.mjs +148 -0
- package/src/equities/marketHours.mjs +249 -0
- package/src/equities/normalize.mjs +295 -0
- package/src/equities/num.mjs +146 -0
- package/src/equities/prepare.mjs +95 -0
- package/src/equities/rank.mjs +260 -0
- package/src/equities/sourceNum.mjs +69 -0
- package/src/equities/types.mjs +220 -0
- package/src/equities/venues/arcus-perp.mjs +84 -0
- package/src/equities/venues/arcus-spot.mjs +105 -0
- package/src/equities/venues/hyperliquid-hip3.mjs +224 -0
- package/src/equities/venues/rh-uniswap.mjs +216 -0
- package/src/equities/venues/solana-xstocks.mjs +139 -0
- package/src/equities/venues/ton-stonfi.mjs +139 -0
- package/src/exact-integer.mjs +72 -0
- package/src/exec-policy.mjs +419 -0
- package/src/flags.mjs +15 -0
- package/src/fresh-window.mjs +76 -0
- package/src/gmx-attestation.mjs +176 -0
- package/src/index.mjs +107 -0
- package/src/licensing/locals-only.mjs +25 -0
- package/src/nft-gas-war-guard.mjs +139 -0
- package/src/onboarding/agent-keys.mjs +161 -0
- package/src/onboarding/harness-configs.mjs +102 -0
- package/src/onboarding/index.mjs +18 -0
- package/src/onboarding/tiers.mjs +139 -0
- package/src/oracle-env.mjs +55 -0
- package/src/package-manifest.mjs +42 -0
- package/src/plugins/agent-plugin-loader.mjs +225 -0
- package/src/portfolio-risk.mjs +170 -0
- package/src/prepare-envelope.mjs +204 -0
- package/src/profile-upgrade.mjs +277 -0
- package/src/protocol-execution.mjs +84 -0
- package/src/protocol-templates/gate.mjs +179 -0
- package/src/protocol-templates/prepare-deploy.mjs +81 -0
- package/src/public-api/agent-grants.mjs +57 -0
- package/src/public-api/buzz-integration.mjs +267 -0
- package/src/public-api/connect-agent.mjs +424 -0
- package/src/public-api/grants.mjs +142 -0
- package/src/public-api/http.mjs +707 -0
- package/src/public-api/metering.mjs +111 -0
- package/src/public-control/aa-adapter.mjs +402 -0
- package/src/public-control/build-registry.mjs +227 -0
- package/src/public-control/bundler-client.mjs +357 -0
- package/src/public-control/grant-indexer.mjs +296 -0
- package/src/public-control/policy-render.mjs +69 -0
- package/src/public-control/policy-schema.mjs +322 -0
- package/src/public-control/runtime-config.mjs +275 -0
- package/src/public-control/session-key-model.mjs +379 -0
- package/src/public-control/session-orchestrator.mjs +418 -0
- package/src/rfq/intent.mjs +180 -0
- package/src/rfq/sources.mjs +181 -0
- package/src/route-attestation.mjs +132 -0
- package/src/router/best-execution.mjs +326 -0
- package/src/router/index.mjs +250 -0
- package/src/router/integrator-fee.mjs +135 -0
- package/src/router/prepare-bridge.mjs +293 -0
- package/src/router/prepare-route.mjs +398 -0
- package/src/router/proposal.mjs +311 -0
- package/src/router/risk-classifier.mjs +119 -0
- package/src/router/route-sources.mjs +364 -0
- package/src/scanner/chains.config.mjs +543 -0
- package/src/scanner/contract.mjs +270 -0
- package/src/scanner/evm-scanner.mjs +394 -0
- package/src/scanner/index.mjs +9 -0
- package/src/scanner/v2-venue.mjs +351 -0
- package/src/scanner/v3-venue.mjs +308 -0
- package/src/scopes.mjs +44 -0
- package/src/sell-simulation.mjs +167 -0
- package/src/signals/engine.mjs +146 -0
- package/src/signals/index.mjs +1 -0
- package/src/token-transfer-guard.mjs +188 -0
- package/src/tui/app.mjs +438 -0
- package/src/tui/backend.mjs +215 -0
- package/src/tui/format.mjs +272 -0
- package/src/tui/gateway-client.mjs +177 -0
- package/src/tui/input.mjs +501 -0
- package/src/tui/memory.mjs +105 -0
- package/src/tui/meta-learn.mjs +85 -0
- package/src/tui/renderer.mjs +443 -0
- package/src/tui/scheduler.mjs +205 -0
- package/src/tui/skills-loader.mjs +140 -0
- package/src/tui/standalone-client.mjs +752 -0
- package/src/tui/theme.mjs +81 -0
- package/src/vault-attestation.mjs +146 -0
- package/src/venues.mjs +206 -0
- package/src/watch-preferences.mjs +85 -0
- package/dist/action-receipts.mjs +0 -1
- package/dist/action-semantics.mjs +0 -1
- package/dist/address-book.mjs +0 -2
- package/dist/bin/desk-server.mjs +0 -59
- package/dist/bin/oracle-data-mcp.mjs +0 -8
- package/dist/bin/oracle-equities.mjs +0 -20
- package/dist/bin/oracle-init.mjs +0 -24
- package/dist/bin/oracle-public-server.mjs +0 -5
- package/dist/bin/oracle-route.mjs +0 -22
- package/dist/bin/oracle-scan.mjs +0 -18
- package/dist/bin/oracle-upgrade.mjs +0 -21
- package/dist/bin/oracle.mjs +0 -76
- package/dist/chains.mjs +0 -1
- package/dist/cli/commands/auth.mjs +0 -16
- package/dist/cli/commands/bootstrap.mjs +0 -18
- package/dist/cli/commands/chain.mjs +0 -21
- package/dist/cli/commands/chat.mjs +0 -45
- package/dist/cli/commands/credential.mjs +0 -1
- package/dist/cli/commands/data-mcp.mjs +0 -2
- package/dist/cli/commands/data.mjs +0 -12
- package/dist/cli/commands/doctor.mjs +0 -5
- package/dist/cli/commands/equities.mjs +0 -15
- package/dist/cli/commands/farm.mjs +0 -59
- package/dist/cli/commands/follow.mjs +0 -28
- package/dist/cli/commands/gate.mjs +0 -13
- package/dist/cli/commands/harness.mjs +0 -15
- package/dist/cli/commands/help.mjs +0 -1
- package/dist/cli/commands/init.mjs +0 -3
- package/dist/cli/commands/mcp.mjs +0 -60
- package/dist/cli/commands/model.mjs +0 -54
- package/dist/cli/commands/plugins.mjs +0 -38
- package/dist/cli/commands/prepare.mjs +0 -2
- package/dist/cli/commands/public.mjs +0 -3
- package/dist/cli/commands/resolve.mjs +0 -23
- package/dist/cli/commands/route.mjs +0 -2
- package/dist/cli/commands/runner.mjs +0 -1
- package/dist/cli/commands/scan.mjs +0 -2
- package/dist/cli/commands/setup.mjs +0 -34
- package/dist/cli/commands/sign.mjs +0 -21
- package/dist/cli/commands/signer.mjs +0 -1
- package/dist/cli/commands/swap.mjs +0 -36
- package/dist/cli/commands/upgrade.mjs +0 -2
- package/dist/cli/commands/vault.mjs +0 -1
- package/dist/cli/commands/version.mjs +0 -2
- package/dist/data/desk-data.mjs +0 -55
- package/dist/data/names.mjs +0 -2
- package/dist/data/providers/farming.mjs +0 -2
- package/dist/equities/index.mjs +0 -1
- package/dist/index.mjs +0 -60
- package/dist/nft-gas-war-guard.mjs +0 -1
- package/dist/onboarding/harness-configs.mjs +0 -4
- package/dist/portfolio-risk.mjs +0 -1
- package/dist/prepare-envelope.mjs +0 -1
- package/dist/public-api/agent-grants.mjs +0 -3
- package/dist/public-control/policy-schema.mjs +0 -1
- package/dist/router/index.mjs +0 -2
- package/dist/scanner/index.mjs +0 -4
- package/dist/signals/index.mjs +0 -1
- package/dist/watch-preferences.mjs +0 -1
- /package/{dist/assets/profiles → profiles}/_template/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/_template/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/bitcoin-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/bitcoin-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/hyperliquid-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/hyperliquid-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/oracle/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/polymarket-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/polymarket-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/profile.schema.json +0 -0
- /package/{dist/assets/profiles → profiles}/protocol-builder/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/protocol-builder/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/robinhood-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/robinhood-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/solana-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/solana-agent/profile.json +0 -0
- /package/{dist/assets/profiles → profiles}/stable-agent/SOUL.md +0 -0
- /package/{dist/assets/profiles → profiles}/stable-agent/profile.json +0 -0
- /package/{dist/assets/skills → skills}/balance/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/bitcoin-l1.md +0 -0
- /package/{dist/assets/skills → skills}/chain-defi-ecosystem-profiling.md +0 -0
- /package/{dist/assets/skills → skills}/evm-contract-research.md +0 -0
- /package/{dist/assets/skills → skills}/hyperliquid.md +0 -0
- /package/{dist/assets/skills → skills}/multichain-exec-desk.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-action-semantics/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-best-execution/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-best-execution.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-bitcoin/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-chain-graphs-telegram-cards/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-chat/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-chat/chain.SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-chat/setup.SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-circuit-breaker/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-contract-research/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-desk/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-desk.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-dex-launch/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-equities/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-grants/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-hypercore-staking/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-hyperliquid/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-meme-token-sniper/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-multichain-nft-launch/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-multichain-token-launch/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-nft-gacha-launch/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-nft-mint-gas-war/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-polymarket/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-protocol-builder/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-protocol-security/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-public-product/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-receipts/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-receipts.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-rfq-tokenized-assets/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-smart-wallet-scanner/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-solana/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-solana-nft/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/oracle-token-research/SKILL.md +0 -0
- /package/{dist/assets/skills → skills}/solana.md +0 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
// Path join (not new URL('./fixtures/')) so bundlers that cannot resolve bare
|
|
6
|
+
// directory URLs (Next/Turbopack) still find the JSON fixtures next to this module.
|
|
7
|
+
const DEFAULT_FIXTURE_DIR = join(dirname(fileURLToPath(import.meta.url)), 'fixtures');
|
|
8
|
+
const FILES = Object.freeze({
|
|
9
|
+
venues: 'venues.json',
|
|
10
|
+
universe: 'universe.json',
|
|
11
|
+
oracles: 'oracles.json',
|
|
12
|
+
nyseHolidays: 'nyse_holidays.json',
|
|
13
|
+
dstTransitions: 'dst_transitions.json',
|
|
14
|
+
equityDexPools: 'equity_dex_pools.json',
|
|
15
|
+
rhPoolState: 'rh_pool_state.json',
|
|
16
|
+
solanaXstocks: 'solana_xstocks.json',
|
|
17
|
+
tonXstocks: 'ton_xstocks.json',
|
|
18
|
+
hip3L2Books: 'hip3_l2books.json',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function fixtureError(filename, message) {
|
|
26
|
+
throw new TypeError(`Fixture fixtures/${filename}: ${message}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function requireRecord(value, filename, path) {
|
|
30
|
+
if (!isRecord(value)) fixtureError(filename, `${path} must be an object`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function requireString(value, filename, path) {
|
|
34
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
35
|
+
fixtureError(filename, `${path} must be a nonempty string`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function requireSafeInteger(value, filename, path) {
|
|
40
|
+
if (!Number.isSafeInteger(value)) fixtureError(filename, `${path} must be a safe integer`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function requireMapOfRecords(value, filename, path) {
|
|
44
|
+
requireRecord(value, filename, path);
|
|
45
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
46
|
+
requireString(key, filename, `${path} key`);
|
|
47
|
+
requireRecord(entry, filename, `${path}.${key}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateVenues(data, filename) {
|
|
52
|
+
requireRecord(data, filename, 'root');
|
|
53
|
+
requireSafeInteger(data.capturedAt, filename, 'capturedAt');
|
|
54
|
+
requireRecord(data.venues, filename, 'venues');
|
|
55
|
+
|
|
56
|
+
const required = [
|
|
57
|
+
['hyperliquid_hip3', 'dexs'],
|
|
58
|
+
['arcus', 'markets'],
|
|
59
|
+
['arcus_spot', 'pairs'],
|
|
60
|
+
['rh_uniswap', 'pools'],
|
|
61
|
+
['rh_chainlink_oracles', 'assets'],
|
|
62
|
+
];
|
|
63
|
+
for (const [venueName, collection] of required) {
|
|
64
|
+
const venue = data.venues[venueName];
|
|
65
|
+
requireRecord(venue, filename, `venues.${venueName}`);
|
|
66
|
+
requireString(venue.type, filename, `venues.${venueName}.type`);
|
|
67
|
+
requireString(venue.chain, filename, `venues.${venueName}.chain`);
|
|
68
|
+
requireString(venue.tier, filename, `venues.${venueName}.tier`);
|
|
69
|
+
requireMapOfRecords(
|
|
70
|
+
venue[collection],
|
|
71
|
+
filename,
|
|
72
|
+
`venues.${venueName}.${collection}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const [dex, markets] of Object.entries(data.venues.hyperliquid_hip3.dexs)) {
|
|
77
|
+
requireMapOfRecords(markets, filename, `venues.hyperliquid_hip3.dexs.${dex}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validateUniverse(data, filename) {
|
|
82
|
+
requireMapOfRecords(data, filename, 'root');
|
|
83
|
+
if (Object.keys(data).length === 0) fixtureError(filename, 'root must not be empty');
|
|
84
|
+
|
|
85
|
+
for (const [symbol, entry] of Object.entries(data)) {
|
|
86
|
+
if (!Array.isArray(entry.venues)) {
|
|
87
|
+
fixtureError(filename, `${symbol}.venues must be an array`);
|
|
88
|
+
}
|
|
89
|
+
entry.venues.forEach((venue, index) =>
|
|
90
|
+
requireString(venue, filename, `${symbol}.venues[${index}]`),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateOracles(data, filename) {
|
|
96
|
+
requireRecord(data, filename, 'root');
|
|
97
|
+
for (const key of [
|
|
98
|
+
'chainId',
|
|
99
|
+
'block',
|
|
100
|
+
'oracleScaleExp',
|
|
101
|
+
'usdgDecimals',
|
|
102
|
+
'equityDecimals',
|
|
103
|
+
]) {
|
|
104
|
+
requireSafeInteger(data[key], filename, key);
|
|
105
|
+
}
|
|
106
|
+
requireString(data.usdg, filename, 'usdg');
|
|
107
|
+
requireMapOfRecords(data.assets, filename, 'assets');
|
|
108
|
+
|
|
109
|
+
for (const [symbol, asset] of Object.entries(data.assets)) {
|
|
110
|
+
requireString(asset.oracle, filename, `assets.${symbol}.oracle`);
|
|
111
|
+
requireString(asset.token, filename, `assets.${symbol}.token`);
|
|
112
|
+
if (typeof asset.rawPrice !== 'string' || !/^\d+$/.test(asset.rawPrice)) {
|
|
113
|
+
fixtureError(filename, `assets.${symbol}.rawPrice must be an integer string`);
|
|
114
|
+
}
|
|
115
|
+
if (typeof asset.usd !== 'number' || !Number.isFinite(asset.usd) || asset.usd < 0) {
|
|
116
|
+
fixtureError(filename, `assets.${symbol}.usd must be a nonnegative finite source number`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function validateNyseHolidays(data, filename) {
|
|
122
|
+
requireRecord(data, filename, 'root');
|
|
123
|
+
for (const key of ['holidayDayIndex', 'names', 'dates']) {
|
|
124
|
+
if (!Array.isArray(data[key])) fixtureError(filename, `${key} must be an array`);
|
|
125
|
+
}
|
|
126
|
+
const count = data.holidayDayIndex.length;
|
|
127
|
+
if (data.names.length !== count || data.dates.length !== count) {
|
|
128
|
+
fixtureError(filename, 'holidayDayIndex, names, and dates must have equal lengths');
|
|
129
|
+
}
|
|
130
|
+
data.holidayDayIndex.forEach((value, index) =>
|
|
131
|
+
requireSafeInteger(value, filename, `holidayDayIndex[${index}]`),
|
|
132
|
+
);
|
|
133
|
+
data.names.forEach((value, index) => requireString(value, filename, `names[${index}]`));
|
|
134
|
+
data.dates.forEach((value, index) => {
|
|
135
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
136
|
+
fixtureError(filename, `dates[${index}] must use YYYY-MM-DD`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function validateDstTransitions(data, filename) {
|
|
142
|
+
requireRecord(data, filename, 'root');
|
|
143
|
+
if (!Array.isArray(data.dstTransitions) || data.dstTransitions.length === 0) {
|
|
144
|
+
fixtureError(filename, 'dstTransitions must be a nonempty array');
|
|
145
|
+
}
|
|
146
|
+
const years = new Set();
|
|
147
|
+
data.dstTransitions.forEach((entry, index) => {
|
|
148
|
+
requireRecord(entry, filename, `dstTransitions[${index}]`);
|
|
149
|
+
for (const key of ['year', 'startUtc', 'endUtc']) {
|
|
150
|
+
requireSafeInteger(entry[key], filename, `dstTransitions[${index}].${key}`);
|
|
151
|
+
}
|
|
152
|
+
if (entry.startUtc >= entry.endUtc) {
|
|
153
|
+
fixtureError(filename, `dstTransitions[${index}] startUtc must be before endUtc`);
|
|
154
|
+
}
|
|
155
|
+
if (years.has(entry.year)) {
|
|
156
|
+
fixtureError(filename, `dstTransitions contains duplicate year ${entry.year}`);
|
|
157
|
+
}
|
|
158
|
+
years.add(entry.year);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function validateEquityDexPools(data, filename) {
|
|
163
|
+
requireMapOfRecords(data, filename, 'root');
|
|
164
|
+
if (Object.keys(data).length === 0) fixtureError(filename, 'root must not be empty');
|
|
165
|
+
|
|
166
|
+
for (const [symbol, pool] of Object.entries(data)) {
|
|
167
|
+
for (const key of ['addr', 'quote', 'quoteAddr', 'dex', 'pair']) {
|
|
168
|
+
requireString(pool[key], filename, `${symbol}.${key}`);
|
|
169
|
+
}
|
|
170
|
+
if (typeof pool.liq !== 'number' || !Number.isFinite(pool.liq) || pool.liq < 0) {
|
|
171
|
+
fixtureError(filename, `${symbol}.liq must be a nonnegative finite source number`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// The original capture recorded pool ADDRESSES and USD liquidity but NO price field,
|
|
177
|
+
// so a pool price could not be derived from it at all. This state file closes that gap:
|
|
178
|
+
// sqrtPriceX96 read live from chain 4663 via pair.slot0() for V3 and
|
|
179
|
+
// StateView.getSlot0(poolId) for V4. quoteIsToken0 records currency orientation, which
|
|
180
|
+
// for V4 is derived from the currency0 < currency1 address ordering invariant rather
|
|
181
|
+
// than guessed, because four pools (GOOGL, AMZN, SPY, TSLA) sort the equity below USDG
|
|
182
|
+
// and would otherwise price inverted by a factor of ~1e19.
|
|
183
|
+
function validateRhPoolState(data, filename) {
|
|
184
|
+
requireRecord(data, filename, 'root');
|
|
185
|
+
requireSafeInteger(data.capturedAt, filename, 'capturedAt');
|
|
186
|
+
requireSafeInteger(data.block, filename, 'block');
|
|
187
|
+
requireSafeInteger(data.chainId, filename, 'chainId');
|
|
188
|
+
requireString(data.gasPriceWei, filename, 'gasPriceWei');
|
|
189
|
+
requireString(data.stateView, filename, 'stateView');
|
|
190
|
+
requireMapOfRecords(data.pools, filename, 'pools');
|
|
191
|
+
if (Object.keys(data.pools).length === 0) fixtureError(filename, 'pools must not be empty');
|
|
192
|
+
|
|
193
|
+
for (const [symbol, pool] of Object.entries(data.pools)) {
|
|
194
|
+
for (const key of ['pair', 'version', 'quote', 'token', 'sqrtPriceX96']) {
|
|
195
|
+
requireString(pool[key], filename, `pools.${symbol}.${key}`);
|
|
196
|
+
}
|
|
197
|
+
if (pool.version !== 'v3' && pool.version !== 'v4') {
|
|
198
|
+
fixtureError(filename, `pools.${symbol}.version must be v3 or v4`);
|
|
199
|
+
}
|
|
200
|
+
if (typeof pool.quoteIsToken0 !== 'boolean') {
|
|
201
|
+
fixtureError(filename, `pools.${symbol}.quoteIsToken0 must be a boolean`);
|
|
202
|
+
}
|
|
203
|
+
if (!/^[0-9]+$/.test(pool.sqrtPriceX96) || BigInt(pool.sqrtPriceX96) <= 0n) {
|
|
204
|
+
fixtureError(filename, `pools.${symbol}.sqrtPriceX96 must be a positive integer string`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Live HIP-3 l2Book snapshots. Unlocks size-aware impact for coins that have a book.
|
|
210
|
+
function validateHip3L2Books(data, filename) {
|
|
211
|
+
requireRecord(data, filename, 'root');
|
|
212
|
+
requireSafeInteger(data.capturedAt, filename, 'capturedAt');
|
|
213
|
+
requireString(data.source, filename, 'source');
|
|
214
|
+
requireRecord(data.books, filename, 'books');
|
|
215
|
+
if (Object.keys(data.books).length === 0) fixtureError(filename, 'books must not be empty');
|
|
216
|
+
for (const [coin, book] of Object.entries(data.books)) {
|
|
217
|
+
requireRecord(book, filename, `books.${coin}`);
|
|
218
|
+
if (!Array.isArray(book.bids) || !Array.isArray(book.asks)) {
|
|
219
|
+
fixtureError(filename, `books.${coin} must have bids and asks arrays`);
|
|
220
|
+
}
|
|
221
|
+
for (const [i, lvl] of book.bids.entries()) {
|
|
222
|
+
requireRecord(lvl, filename, `books.${coin}.bids[${i}]`);
|
|
223
|
+
requireString(lvl.px, filename, `books.${coin}.bids[${i}].px`);
|
|
224
|
+
requireString(lvl.sz, filename, `books.${coin}.bids[${i}].sz`);
|
|
225
|
+
}
|
|
226
|
+
for (const [i, lvl] of book.asks.entries()) {
|
|
227
|
+
requireRecord(lvl, filename, `books.${coin}.asks[${i}]`);
|
|
228
|
+
requireString(lvl.px, filename, `books.${coin}.asks[${i}].px`);
|
|
229
|
+
requireString(lvl.sz, filename, `books.${coin}.asks[${i}].sz`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Solana xStocks capture from DexScreener. liquidity.usd and priceUsd are
|
|
235
|
+
// third party floats: converting them to fixed point does not make them exact.
|
|
236
|
+
// Impact at size is modeled, never measured from these fields alone.
|
|
237
|
+
function validateSolanaXstocks(data, filename) {
|
|
238
|
+
requireRecord(data, filename, 'root');
|
|
239
|
+
requireSafeInteger(data.capturedAt, filename, 'capturedAt');
|
|
240
|
+
requireString(data.source, filename, 'source');
|
|
241
|
+
requireString(data.chain, filename, 'chain');
|
|
242
|
+
requireMapOfRecords(data.tokens, filename, 'tokens');
|
|
243
|
+
requireRecord(data.pairs, filename, 'pairs');
|
|
244
|
+
if (Object.keys(data.tokens).length === 0) fixtureError(filename, 'tokens must not be empty');
|
|
245
|
+
|
|
246
|
+
for (const [symbol, token] of Object.entries(data.tokens)) {
|
|
247
|
+
requireString(token.symbolRaw, filename, `tokens.${symbol}.symbolRaw`);
|
|
248
|
+
requireString(token.mint, filename, `tokens.${symbol}.mint`);
|
|
249
|
+
if (!Object.hasOwn(data.pairs, symbol)) {
|
|
250
|
+
fixtureError(filename, `pairs.${symbol} is required for token ${symbol}`);
|
|
251
|
+
}
|
|
252
|
+
if (!Array.isArray(data.pairs[symbol]) || data.pairs[symbol].length === 0) {
|
|
253
|
+
fixtureError(filename, `pairs.${symbol} must be a nonempty array`);
|
|
254
|
+
}
|
|
255
|
+
for (const [i, pair] of data.pairs[symbol].entries()) {
|
|
256
|
+
requireRecord(pair, filename, `pairs.${symbol}[${i}]`);
|
|
257
|
+
requireString(pair.pairAddress, filename, `pairs.${symbol}[${i}].pairAddress`);
|
|
258
|
+
requireString(pair.dexId, filename, `pairs.${symbol}[${i}].dexId`);
|
|
259
|
+
if (typeof pair.priceUsd !== 'number' || !Number.isFinite(pair.priceUsd) || pair.priceUsd <= 0) {
|
|
260
|
+
fixtureError(filename, `pairs.${symbol}[${i}].priceUsd must be a positive finite number`);
|
|
261
|
+
}
|
|
262
|
+
if (typeof pair.liquidityUsd !== 'number' || !Number.isFinite(pair.liquidityUsd) || pair.liquidityUsd < 0) {
|
|
263
|
+
fixtureError(filename, `pairs.${symbol}[${i}].liquidityUsd must be a nonnegative finite number`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// TON xStocks (ston.fi and other TON dexs) via DexScreener. Same float caveats
|
|
270
|
+
// as Solana. Live capture may be thin (SPY-only is a valid snapshot).
|
|
271
|
+
function validateTonXstocks(data, filename) {
|
|
272
|
+
requireRecord(data, filename, 'root');
|
|
273
|
+
requireSafeInteger(data.capturedAt, filename, 'capturedAt');
|
|
274
|
+
requireString(data.source, filename, 'source');
|
|
275
|
+
requireString(data.chain, filename, 'chain');
|
|
276
|
+
if (data.chain !== 'ton') fixtureError(filename, 'chain must be "ton"');
|
|
277
|
+
requireMapOfRecords(data.tokens, filename, 'tokens');
|
|
278
|
+
requireRecord(data.pairs, filename, 'pairs');
|
|
279
|
+
if (Object.keys(data.tokens).length === 0) fixtureError(filename, 'tokens must not be empty');
|
|
280
|
+
|
|
281
|
+
for (const [symbol, token] of Object.entries(data.tokens)) {
|
|
282
|
+
requireString(token.symbolRaw, filename, `tokens.${symbol}.symbolRaw`);
|
|
283
|
+
requireString(token.mint, filename, `tokens.${symbol}.mint`);
|
|
284
|
+
if (!Object.hasOwn(data.pairs, symbol)) {
|
|
285
|
+
fixtureError(filename, `pairs.${symbol} is required for token ${symbol}`);
|
|
286
|
+
}
|
|
287
|
+
if (!Array.isArray(data.pairs[symbol]) || data.pairs[symbol].length === 0) {
|
|
288
|
+
fixtureError(filename, `pairs.${symbol} must be a nonempty array`);
|
|
289
|
+
}
|
|
290
|
+
for (const [i, pair] of data.pairs[symbol].entries()) {
|
|
291
|
+
requireRecord(pair, filename, `pairs.${symbol}[${i}]`);
|
|
292
|
+
requireString(pair.pairAddress, filename, `pairs.${symbol}[${i}].pairAddress`);
|
|
293
|
+
requireString(pair.dexId, filename, `pairs.${symbol}[${i}].dexId`);
|
|
294
|
+
if (typeof pair.priceUsd !== 'number' || !Number.isFinite(pair.priceUsd) || pair.priceUsd <= 0) {
|
|
295
|
+
fixtureError(filename, `pairs.${symbol}[${i}].priceUsd must be a positive finite number`);
|
|
296
|
+
}
|
|
297
|
+
if (typeof pair.liquidityUsd !== 'number' || !Number.isFinite(pair.liquidityUsd) || pair.liquidityUsd < 0) {
|
|
298
|
+
fixtureError(filename, `pairs.${symbol}[${i}].liquidityUsd must be a nonnegative finite number`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const VALIDATORS = Object.freeze({
|
|
305
|
+
venues: validateVenues,
|
|
306
|
+
universe: validateUniverse,
|
|
307
|
+
oracles: validateOracles,
|
|
308
|
+
nyseHolidays: validateNyseHolidays,
|
|
309
|
+
dstTransitions: validateDstTransitions,
|
|
310
|
+
equityDexPools: validateEquityDexPools,
|
|
311
|
+
rhPoolState: validateRhPoolState,
|
|
312
|
+
solanaXstocks: validateSolanaXstocks,
|
|
313
|
+
tonXstocks: validateTonXstocks,
|
|
314
|
+
hip3L2Books: validateHip3L2Books,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
function readFixture(fixtureDir, key) {
|
|
318
|
+
const filename = FILES[key];
|
|
319
|
+
const path = join(fixtureDir, filename);
|
|
320
|
+
let source;
|
|
321
|
+
try {
|
|
322
|
+
source = readFileSync(path, 'utf8');
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (error?.code === 'ENOENT') {
|
|
325
|
+
throw new Error(`Fixture fixtures/${filename} is missing at ${path}`);
|
|
326
|
+
}
|
|
327
|
+
throw new Error(`Fixture fixtures/${filename} could not be read: ${error.message}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let data;
|
|
331
|
+
try {
|
|
332
|
+
data = JSON.parse(source);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
throw new SyntaxError(`Fixture fixtures/${filename} contains invalid JSON: ${error.message}`);
|
|
335
|
+
}
|
|
336
|
+
VALIDATORS[key](data, filename);
|
|
337
|
+
return data;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function deepFreeze(value) {
|
|
341
|
+
if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
342
|
+
Object.freeze(value);
|
|
343
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
344
|
+
}
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function loadFixtures({ fixtureDir = DEFAULT_FIXTURE_DIR } = {}) {
|
|
349
|
+
if (typeof fixtureDir !== 'string' || fixtureDir.length === 0) {
|
|
350
|
+
throw new TypeError('fixtureDir must be a nonempty path string');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const fixtures = {};
|
|
354
|
+
for (const key of Object.keys(FILES)) {
|
|
355
|
+
fixtures[key] = readFixture(fixtureDir, key);
|
|
356
|
+
}
|
|
357
|
+
return deepFreeze(fixtures);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let defaultFixtures;
|
|
361
|
+
|
|
362
|
+
function defaults() {
|
|
363
|
+
defaultFixtures ??= loadFixtures();
|
|
364
|
+
return defaultFixtures;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function getVenues() {
|
|
368
|
+
return defaults().venues;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function getUniverse() {
|
|
372
|
+
return defaults().universe;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function getOracles() {
|
|
376
|
+
return defaults().oracles;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function getNyseHolidays() {
|
|
380
|
+
return defaults().nyseHolidays;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function getDstTransitions() {
|
|
384
|
+
return defaults().dstTransitions;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function getEquityDexPools() {
|
|
388
|
+
return defaults().equityDexPools;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function getRhPoolState() {
|
|
392
|
+
return defaults().rhPoolState;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function getSolanaXstocks() {
|
|
396
|
+
return defaults().solanaXstocks;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function getTonXstocks() {
|
|
400
|
+
return defaults().tonXstocks;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function getHip3L2Books() {
|
|
404
|
+
return defaults().hip3L2Books;
|
|
405
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Funding and carry cost over a holding period.
|
|
2
|
+
//
|
|
3
|
+
// carryBps(hourlyFundingRate, horizonHours) uses simple hourly summation.
|
|
4
|
+
// Compounding is ignored at these magnitudes; the error bound is documented in
|
|
5
|
+
// the returned carry block. Funding held constant is a FORECAST and is always
|
|
6
|
+
// labelled confidence: "estimate". It never silently flips a route to
|
|
7
|
+
// costAccounted: true.
|
|
8
|
+
//
|
|
9
|
+
// Arcus dark windows use the locked SOFR + 0.5% hourly constant for dark hours
|
|
10
|
+
// inside the horizon. That requires marketHours to count dark hours.
|
|
11
|
+
|
|
12
|
+
import { div, fromScaledInteger, mul, parseDecimal, toString } from './num.mjs';
|
|
13
|
+
import { DARK_WINDOW_FUNDING_HOURLY } from './venues/arcus-perp.mjs';
|
|
14
|
+
import { isDark } from './marketHours.mjs';
|
|
15
|
+
|
|
16
|
+
const MS_PER_HOUR = 3_600_000;
|
|
17
|
+
const HOURS_PER_YEAR = 24 * 365;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Simple (non compounding) carry in basis points over a horizon.
|
|
21
|
+
* carryBps = hourlyRate * horizonHours * 10000
|
|
22
|
+
*
|
|
23
|
+
* Truncation is toward zero via BigInt division. At a 30 day horizon and a
|
|
24
|
+
* 1e-5 hourly rate the ignored compounding error is under 0.5bp, documented
|
|
25
|
+
* in the returned block.
|
|
26
|
+
*/
|
|
27
|
+
export function carryBps(hourlyFundingRate, horizonHours, resultScale = 8) {
|
|
28
|
+
if (hourlyFundingRate === null || hourlyFundingRate === undefined) {
|
|
29
|
+
throw new TypeError('hourlyFundingRate is required');
|
|
30
|
+
}
|
|
31
|
+
if (!Number.isFinite(horizonHours) || horizonHours < 0) {
|
|
32
|
+
throw new RangeError('horizonHours must be a nonnegative finite number');
|
|
33
|
+
}
|
|
34
|
+
if (horizonHours === 0) {
|
|
35
|
+
return parseDecimal('0');
|
|
36
|
+
}
|
|
37
|
+
const hours = parseDecimal(String(horizonHours));
|
|
38
|
+
const tenThousand = fromScaledInteger(10000n, 0);
|
|
39
|
+
return mul(mul(hourlyFundingRate, hours), tenThousand);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build a visible carry block for a quote over a horizon.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} quote a validating Quote (perp expected)
|
|
46
|
+
* @param {number} horizonHours
|
|
47
|
+
* @param {object} [opts]
|
|
48
|
+
* @param {number} [opts.nowMs] start of the horizon for dark-window counting
|
|
49
|
+
* @param {boolean} [opts.applyDarkWindowLock] when true, dark hours use SOFR lock
|
|
50
|
+
*/
|
|
51
|
+
export function carryForQuote(quote, horizonHours, opts = {}) {
|
|
52
|
+
if (quote.instrument !== 'perp') {
|
|
53
|
+
return {
|
|
54
|
+
model: 'current-funding-held-constant',
|
|
55
|
+
horizonHours,
|
|
56
|
+
carryBps: parseDecimal('0'),
|
|
57
|
+
carryBpsDisplay: '0',
|
|
58
|
+
confidence: 'estimate',
|
|
59
|
+
note: 'spot has no funding carry',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
64
|
+
const applyDark = opts.applyDarkWindowLock === true;
|
|
65
|
+
let rate = quote.fundingHourly;
|
|
66
|
+
|
|
67
|
+
if (applyDark && rate !== null) {
|
|
68
|
+
// Split the horizon into core/extended hours at the live rate and dark hours
|
|
69
|
+
// at the SOFR lock. Count dark hours by sampling each hour boundary.
|
|
70
|
+
const darkHours = countDarkHours(nowMs, horizonHours);
|
|
71
|
+
const litHours = horizonHours - darkHours;
|
|
72
|
+
const darkRate = parseDecimal(DARK_WINDOW_FUNDING_HOURLY);
|
|
73
|
+
const litCarry = carryBps(rate, litHours);
|
|
74
|
+
const darkCarry = carryBps(darkRate, darkHours);
|
|
75
|
+
// Sum the two carry components.
|
|
76
|
+
const scale = Math.max(litCarry.scale, darkCarry.scale);
|
|
77
|
+
const total = {
|
|
78
|
+
mantissa:
|
|
79
|
+
litCarry.mantissa * 10n ** BigInt(scale - litCarry.scale) +
|
|
80
|
+
darkCarry.mantissa * 10n ** BigInt(scale - darkCarry.scale),
|
|
81
|
+
scale,
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
model: 'current-funding-held-constant',
|
|
85
|
+
horizonHours,
|
|
86
|
+
carryBps: total,
|
|
87
|
+
carryBpsDisplay: toString(total),
|
|
88
|
+
confidence: 'estimate',
|
|
89
|
+
darkHours,
|
|
90
|
+
litHours,
|
|
91
|
+
darkWindowRate: DARK_WINDOW_FUNDING_HOURLY,
|
|
92
|
+
compounding: 'ignored',
|
|
93
|
+
compoundingErrorBoundNote:
|
|
94
|
+
'simple sum; at 30d and 1e-5/h the ignored compound is under 0.5bp',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (rate === null) {
|
|
99
|
+
return {
|
|
100
|
+
model: 'current-funding-held-constant',
|
|
101
|
+
horizonHours,
|
|
102
|
+
carryBps: null,
|
|
103
|
+
carryBpsDisplay: null,
|
|
104
|
+
confidence: 'estimate',
|
|
105
|
+
note: 'funding rate unknown',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const c = carryBps(rate, horizonHours);
|
|
110
|
+
return {
|
|
111
|
+
model: 'current-funding-held-constant',
|
|
112
|
+
horizonHours,
|
|
113
|
+
carryBps: c,
|
|
114
|
+
carryBpsDisplay: toString(c),
|
|
115
|
+
confidence: 'estimate',
|
|
116
|
+
compounding: 'ignored',
|
|
117
|
+
compoundingErrorBoundNote:
|
|
118
|
+
'simple sum; at 30d and 1e-5/h the ignored compound is under 0.5bp',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function countDarkHours(startMs, horizonHours) {
|
|
123
|
+
if (horizonHours <= 0) return 0;
|
|
124
|
+
let dark = 0;
|
|
125
|
+
const steps = Math.floor(horizonHours);
|
|
126
|
+
for (let i = 0; i < steps; i += 1) {
|
|
127
|
+
if (isDark(startMs + i * MS_PER_HOUR)) dark += 1;
|
|
128
|
+
}
|
|
129
|
+
// Fractional last hour: count it dark if the start of that hour is dark.
|
|
130
|
+
if (horizonHours > steps) {
|
|
131
|
+
if (isDark(startMs + steps * MS_PER_HOUR)) dark += horizonHours - steps;
|
|
132
|
+
}
|
|
133
|
+
return dark;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Annualize an hourly rate for display. rate * hoursPerYear * 100 for percent.
|
|
137
|
+
export function annualizeHourly(hourlyRate, resultScale = 4) {
|
|
138
|
+
if (hourlyRate === null) return null;
|
|
139
|
+
const hours = fromScaledInteger(BigInt(HOURS_PER_YEAR), 0);
|
|
140
|
+
const hundred = fromScaledInteger(100n, 0);
|
|
141
|
+
return mul(mul(hourlyRate, hours), hundred);
|
|
142
|
+
}
|