@oracle-agent/oracle 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/SETUP.md +4 -4
- package/dist/action-receipts.mjs +1 -0
- package/dist/action-semantics.mjs +1 -0
- package/dist/address-book.mjs +2 -0
- package/dist/bin/desk-server.mjs +59 -0
- package/dist/bin/oracle-data-mcp.mjs +8 -0
- package/dist/bin/oracle-equities.mjs +20 -0
- package/dist/bin/oracle-init.mjs +24 -0
- package/dist/bin/oracle-public-server.mjs +5 -0
- package/dist/bin/oracle-route.mjs +22 -0
- package/dist/bin/oracle-scan.mjs +18 -0
- package/dist/bin/oracle-upgrade.mjs +21 -0
- package/dist/bin/oracle.mjs +61 -0
- package/dist/chains.mjs +1 -0
- package/dist/cli/commands/auth.mjs +16 -0
- package/dist/cli/commands/bootstrap.mjs +18 -0
- package/dist/cli/commands/chain.mjs +21 -0
- package/dist/cli/commands/chat.mjs +45 -0
- package/dist/cli/commands/credential.mjs +1 -0
- package/dist/cli/commands/data-mcp.mjs +2 -0
- package/dist/cli/commands/data.mjs +12 -0
- package/dist/cli/commands/doctor.mjs +5 -0
- package/dist/cli/commands/equities.mjs +15 -0
- package/dist/cli/commands/farm.mjs +59 -0
- package/dist/cli/commands/fees.mjs +10 -0
- package/dist/cli/commands/follow.mjs +28 -0
- package/dist/cli/commands/harness.mjs +15 -0
- package/dist/cli/commands/help.mjs +1 -0
- package/dist/cli/commands/init.mjs +3 -0
- package/dist/cli/commands/mcp.mjs +36 -0
- package/dist/cli/commands/model.mjs +54 -0
- package/dist/cli/commands/plugins.mjs +43 -0
- package/dist/cli/commands/prepare.mjs +2 -0
- package/dist/cli/commands/public.mjs +3 -0
- package/dist/cli/commands/resolve.mjs +23 -0
- package/dist/cli/commands/route.mjs +2 -0
- package/dist/cli/commands/runner.mjs +1 -0
- package/dist/cli/commands/scan.mjs +2 -0
- package/dist/cli/commands/setup.mjs +34 -0
- package/dist/cli/commands/sign.mjs +4 -0
- package/dist/cli/commands/signer.mjs +1 -0
- package/dist/cli/commands/swap.mjs +36 -0
- package/dist/cli/commands/upgrade.mjs +2 -0
- package/dist/cli/commands/vault.mjs +1 -0
- package/dist/cli/commands/version.mjs +2 -0
- package/dist/data/desk-data.mjs +55 -0
- package/dist/data/names.mjs +2 -0
- package/dist/data/providers/farming.mjs +2 -0
- package/dist/equities/index.mjs +1 -0
- package/dist/index.mjs +60 -0
- package/dist/nft-gas-war-guard.mjs +1 -0
- package/dist/onboarding/harness-configs.mjs +4 -0
- package/dist/portfolio-risk.mjs +1 -0
- package/dist/prepare-envelope.mjs +1 -0
- package/dist/public-api/agent-grants.mjs +3 -0
- package/dist/public-control/policy-schema.mjs +1 -0
- package/dist/router/index.mjs +2 -0
- package/dist/scanner/index.mjs +4 -0
- package/dist/signals/index.mjs +1 -0
- package/dist/watch-preferences.mjs +1 -0
- package/package.json +32 -78
- package/public/oracle-splash/_variants/ice.html +5 -18
- package/public/oracle-splash/_variants/ivory.html +5 -18
- package/public/oracle-splash/index.html +114 -34
- package/CONTRIBUTING.md +0 -99
- package/bin/desk-server.mjs +0 -473
- package/bin/oracle-data-mcp.mjs +0 -1252
- package/bin/oracle-equities.mjs +0 -168
- package/bin/oracle-init.mjs +0 -525
- package/bin/oracle-public-server.mjs +0 -36
- package/bin/oracle-route.mjs +0 -254
- package/bin/oracle-scan.mjs +0 -192
- package/bin/oracle-upgrade.mjs +0 -42
- package/bin/oracle.mjs +0 -34
- package/docs/adding-a-chain.md +0 -229
- package/docs/architecture.md +0 -139
- package/docs/buzz-integration.md +0 -115
- package/docs/cli.md +0 -143
- package/docs/connectors.md +0 -13
- package/docs/hyperliquid-builder-code.md +0 -50
- package/docs/locals-only-fee-waiver.md +0 -34
- package/docs/oracle-pack-standard.md +0 -29
- package/docs/profiles.md +0 -151
- package/docs/public-surface.md +0 -206
- package/examples/add-a-chain.mjs +0 -65
- package/examples/oracle-pack-template.mjs +0 -38
- package/examples/research-a-token.mjs +0 -70
- package/plugins/oracle-owner-gate/__init__.py +0 -288
- package/plugins/oracle-owner-gate/plugin.yaml +0 -9
- package/protocols/templates/safe-erc20/SECURITY.md +0 -16
- package/protocols/templates/safe-erc20/foundry.toml +0 -12
- package/protocols/templates/safe-erc20/remappings.txt +0 -2
- package/protocols/templates/safe-erc20/src/SafeERC20.sol +0 -53
- package/protocols/templates/safe-erc20/test/SafeERC20.t.sol +0 -72
- package/scripts/adversarial-bench.mjs +0 -114
- package/scripts/build-dist.mjs +0 -119
- package/scripts/build-inscription.py +0 -230
- package/scripts/check-doc-drift.mjs +0 -123
- package/scripts/check-test-count.mjs +0 -105
- package/scripts/e2e-hl-markets.mjs +0 -21
- package/scripts/e2e-hl-perps.mjs +0 -48
- package/scripts/e2e-hypercore-staking.mjs +0 -128
- package/scripts/e2e-solana-bitcoin.mjs +0 -183
- package/scripts/guard-publish.mjs +0 -22
- package/scripts/protocol-template-gate.mjs +0 -15
- package/scripts/public-api-scan.mjs +0 -23
- package/scripts/publish-dist.mjs +0 -67
- package/scripts/record-hero-cli.sh +0 -80
- package/scripts/secret-scan.mjs +0 -169
- package/scripts/smoke-dist-bins.mjs +0 -89
- package/scripts/verify-dist.mjs +0 -29
- package/scripts/verify-v3-venues.mjs +0 -233
- package/skins/oracle.yaml +0 -56
- package/src/action-receipts.mjs +0 -265
- package/src/action-semantics.mjs +0 -62
- package/src/address-book.mjs +0 -208
- package/src/agent-auth.mjs +0 -206
- package/src/approval-guard.mjs +0 -282
- package/src/attestation-secret.mjs +0 -94
- package/src/audit-log.mjs +0 -196
- package/src/auth/oauth.mjs +0 -672
- package/src/auto-slippage.mjs +0 -378
- package/src/capability-posture.mjs +0 -125
- package/src/cards.mjs +0 -422
- package/src/chains.mjs +0 -62
- package/src/cli/chain-catalog.mjs +0 -215
- package/src/cli/chain-state.mjs +0 -74
- package/src/cli/commands/auth.mjs +0 -193
- package/src/cli/commands/bootstrap.mjs +0 -87
- package/src/cli/commands/chain.mjs +0 -143
- package/src/cli/commands/chat.mjs +0 -465
- package/src/cli/commands/credential.mjs +0 -9
- package/src/cli/commands/data-mcp.mjs +0 -18
- package/src/cli/commands/data.mjs +0 -75
- package/src/cli/commands/doctor.mjs +0 -136
- package/src/cli/commands/equities.mjs +0 -41
- package/src/cli/commands/farm.mjs +0 -129
- package/src/cli/commands/fees.mjs +0 -109
- package/src/cli/commands/follow.mjs +0 -130
- package/src/cli/commands/harness.mjs +0 -63
- package/src/cli/commands/help.mjs +0 -7
- package/src/cli/commands/init.mjs +0 -27
- package/src/cli/commands/mcp.mjs +0 -154
- package/src/cli/commands/model.mjs +0 -106
- package/src/cli/commands/plugins.mjs +0 -275
- package/src/cli/commands/prepare.mjs +0 -12
- package/src/cli/commands/public.mjs +0 -20
- package/src/cli/commands/resolve.mjs +0 -64
- package/src/cli/commands/route.mjs +0 -12
- package/src/cli/commands/runner.mjs +0 -9
- package/src/cli/commands/scan.mjs +0 -12
- package/src/cli/commands/setup.mjs +0 -294
- package/src/cli/commands/sign.mjs +0 -21
- package/src/cli/commands/signer.mjs +0 -9
- package/src/cli/commands/swap.mjs +0 -102
- package/src/cli/commands/upgrade.mjs +0 -12
- package/src/cli/commands/vault.mjs +0 -9
- package/src/cli/commands/version.mjs +0 -22
- package/src/cli/first-run.mjs +0 -23
- package/src/cli/kernel.mjs +0 -262
- package/src/cli/mcp-targets/chatgpt.mjs +0 -51
- package/src/cli/mcp-targets/claude-code.mjs +0 -39
- package/src/cli/mcp-targets/claude-desktop.mjs +0 -24
- package/src/cli/mcp-targets/codex.mjs +0 -41
- package/src/cli/mcp-targets/cursor.mjs +0 -32
- package/src/cli/mcp-targets/shared.mjs +0 -71
- package/src/cli/mcp-targets/vscode.mjs +0 -39
- package/src/cli/messaging-platforms.mjs +0 -217
- package/src/cli/model-config.mjs +0 -78
- package/src/cli/operator-dispatch.mjs +0 -194
- package/src/cli/oracle-harness.py +0 -415
- package/src/cli/paths.mjs +0 -107
- package/src/cli/runtime.mjs +0 -351
- package/src/cli/setup-state.mjs +0 -168
- package/src/cli/spawn-child.mjs +0 -43
- package/src/data/catalog.mjs +0 -858
- package/src/data/desk-data.mjs +0 -877
- package/src/data/http.mjs +0 -253
- package/src/data/names.mjs +0 -188
- package/src/data/provider-endpoint.mjs +0 -94
- package/src/data/providers/aave-v3.mjs +0 -139
- package/src/data/providers/across-bridge.mjs +0 -41
- package/src/data/providers/aerodrome.mjs +0 -245
- package/src/data/providers/aevo.mjs +0 -33
- package/src/data/providers/approval-indexer.mjs +0 -133
- package/src/data/providers/approvals.mjs +0 -594
- package/src/data/providers/babylon-staking.mjs +0 -149
- package/src/data/providers/balancer.mjs +0 -209
- package/src/data/providers/beefy-yields.mjs +0 -45
- package/src/data/providers/birdeye-tokens.mjs +0 -33
- package/src/data/providers/bisq-markets.mjs +0 -145
- package/src/data/providers/bitcoin-esplora.mjs +0 -216
- package/src/data/providers/bitcoin-meta.mjs +0 -378
- package/src/data/providers/blockscout.mjs +0 -14
- package/src/data/providers/bob-rootstock.mjs +0 -198
- package/src/data/providers/braiins-insights.mjs +0 -103
- package/src/data/providers/bridge-aggregator.mjs +0 -259
- package/src/data/providers/bridges.mjs +0 -242
- package/src/data/providers/bungee.mjs +0 -29
- package/src/data/providers/celestia-da.mjs +0 -38
- package/src/data/providers/changenow.mjs +0 -135
- package/src/data/providers/compound.mjs +0 -32
- package/src/data/providers/connext.mjs +0 -28
- package/src/data/providers/convex.mjs +0 -30
- package/src/data/providers/cow-protocol.mjs +0 -110
- package/src/data/providers/cowswap.mjs +0 -415
- package/src/data/providers/curve.mjs +0 -201
- package/src/data/providers/das-assets.mjs +0 -59
- package/src/data/providers/debridge.mjs +0 -109
- package/src/data/providers/defillama.mjs +0 -88
- package/src/data/providers/dexscreener.mjs +0 -43
- package/src/data/providers/drift.mjs +0 -32
- package/src/data/providers/dydx.mjs +0 -41
- package/src/data/providers/evm-rpc.mjs +0 -225
- package/src/data/providers/farming.mjs +0 -291
- package/src/data/providers/geckoterminal.mjs +0 -34
- package/src/data/providers/gmx.mjs +0 -496
- package/src/data/providers/hiro-stacks.mjs +0 -150
- package/src/data/providers/hl-assets.mjs +0 -165
- package/src/data/providers/hl-info.mjs +0 -108
- package/src/data/providers/hl-markets.mjs +0 -210
- package/src/data/providers/hl-outcome.mjs +0 -61
- package/src/data/providers/hl-perps.mjs +0 -419
- package/src/data/providers/hl-staking.mjs +0 -353
- package/src/data/providers/hl-ws.mjs +0 -119
- package/src/data/providers/hyperevm-dex.mjs +0 -49
- package/src/data/providers/jito-mev.mjs +0 -117
- package/src/data/providers/jupiter-venues.mjs +0 -119
- package/src/data/providers/jupiter.mjs +0 -315
- package/src/data/providers/kamino-strategies.mjs +0 -143
- package/src/data/providers/l2beat-tvl.mjs +0 -42
- package/src/data/providers/lifi.mjs +0 -166
- package/src/data/providers/liquid-esplora.mjs +0 -152
- package/src/data/providers/magiceden-ordinals.mjs +0 -141
- package/src/data/providers/magiceden-sol.mjs +0 -406
- package/src/data/providers/mempool-lightning.mjs +0 -144
- package/src/data/providers/mempool-mining.mjs +0 -142
- package/src/data/providers/meteora-dlmm.mjs +0 -168
- package/src/data/providers/morpho.mjs +0 -177
- package/src/data/providers/nft-gallery.mjs +0 -163
- package/src/data/providers/nft-portfolio.mjs +0 -494
- package/src/data/providers/odos.mjs +0 -156
- package/src/data/providers/ondo.mjs +0 -31
- package/src/data/providers/oneinch.mjs +0 -174
- package/src/data/providers/opensea-multichain.mjs +0 -136
- package/src/data/providers/opensea-nft.mjs +0 -371
- package/src/data/providers/orca.mjs +0 -29
- package/src/data/providers/ordinals-runes.mjs +0 -180
- package/src/data/providers/pancakeswap.mjs +0 -28
- package/src/data/providers/paraswap.mjs +0 -125
- package/src/data/providers/pendle.mjs +0 -188
- package/src/data/providers/poly-clob.mjs +0 -283
- package/src/data/providers/poly-public.mjs +0 -96
- package/src/data/providers/poly-ws.mjs +0 -103
- package/src/data/providers/polygon-staking.mjs +0 -227
- package/src/data/providers/portfolio-history.mjs +0 -394
- package/src/data/providers/portfolio.mjs +0 -600
- package/src/data/providers/pyth-price-feeds.mjs +0 -172
- package/src/data/providers/raydium.mjs +0 -35
- package/src/data/providers/relayscan-mev.mjs +0 -72
- package/src/data/providers/rfq.mjs +0 -15
- package/src/data/providers/rh-agent.mjs +0 -59
- package/src/data/providers/sanctum-lst.mjs +0 -180
- package/src/data/providers/satflow.mjs +0 -341
- package/src/data/providers/signed-material-guard.mjs +0 -108
- package/src/data/providers/solana-rpc.mjs +0 -197
- package/src/data/providers/solend-lending.mjs +0 -166
- package/src/data/providers/spark-lend.mjs +0 -42
- package/src/data/providers/squid-router.mjs +0 -29
- package/src/data/providers/stargate.mjs +0 -29
- package/src/data/providers/sushiswap.mjs +0 -30
- package/src/data/providers/symbiotic-restaking.mjs +0 -41
- package/src/data/providers/thorchain.mjs +0 -36
- package/src/data/providers/traderjoe.mjs +0 -28
- package/src/data/providers/twap.mjs +0 -152
- package/src/data/providers/uniswap-v3.mjs +0 -457
- package/src/data/providers/uniswap-v4.mjs +0 -414
- package/src/data/providers/vertex.mjs +0 -41
- package/src/data/providers/wallet-watch.mjs +0 -264
- package/src/data/providers/wormhole.mjs +0 -28
- package/src/data/providers/yearn.mjs +0 -31
- package/src/data/providers/zerox.mjs +0 -167
- package/src/data/public-api-scan.mjs +0 -61
- package/src/data/quote-placeholder.mjs +0 -31
- package/src/desk/swap-prepare.mjs +0 -194
- package/src/equities/bridge.mjs +0 -388
- package/src/equities/fixtures/bridge_routes.json +0 -159
- package/src/equities/fixtures/dst_transitions.json +0 -29
- package/src/equities/fixtures/equity_dex_pools.json +0 -154
- package/src/equities/fixtures/hip3_l2books.json +0 -2711
- package/src/equities/fixtures/nyse_holidays.json +0 -1
- package/src/equities/fixtures/oracles.json +0 -100
- package/src/equities/fixtures/rh_pool_state.json +0 -244
- package/src/equities/fixtures/solana_xstocks.json +0 -704
- package/src/equities/fixtures/ton_xstocks.json +0 -35
- package/src/equities/fixtures/universe.json +0 -411
- package/src/equities/fixtures/venues.json +0 -3542
- package/src/equities/fixtures.mjs +0 -405
- package/src/equities/funding.mjs +0 -142
- package/src/equities/index.mjs +0 -166
- package/src/equities/liveness.mjs +0 -148
- package/src/equities/marketHours.mjs +0 -249
- package/src/equities/normalize.mjs +0 -295
- package/src/equities/num.mjs +0 -146
- package/src/equities/prepare.mjs +0 -95
- package/src/equities/rank.mjs +0 -260
- package/src/equities/sourceNum.mjs +0 -69
- package/src/equities/types.mjs +0 -220
- package/src/equities/venues/arcus-perp.mjs +0 -84
- package/src/equities/venues/arcus-spot.mjs +0 -105
- package/src/equities/venues/hyperliquid-hip3.mjs +0 -224
- package/src/equities/venues/rh-uniswap.mjs +0 -216
- package/src/equities/venues/solana-xstocks.mjs +0 -139
- package/src/equities/venues/ton-stonfi.mjs +0 -139
- package/src/exact-integer.mjs +0 -72
- package/src/exec-policy.mjs +0 -419
- package/src/flags.mjs +0 -15
- package/src/fresh-window.mjs +0 -76
- package/src/gmx-attestation.mjs +0 -176
- package/src/index.mjs +0 -107
- package/src/licensing/locals-only.mjs +0 -25
- package/src/nft-gas-war-guard.mjs +0 -139
- package/src/onboarding/agent-keys.mjs +0 -161
- package/src/onboarding/harness-configs.mjs +0 -102
- package/src/onboarding/index.mjs +0 -18
- package/src/onboarding/tiers.mjs +0 -139
- package/src/oracle-env.mjs +0 -55
- package/src/package-manifest.mjs +0 -42
- package/src/plugins/agent-plugin-loader.mjs +0 -225
- package/src/portfolio-risk.mjs +0 -170
- package/src/prepare-envelope.mjs +0 -204
- package/src/profile-upgrade.mjs +0 -277
- package/src/protocol-execution.mjs +0 -84
- package/src/protocol-templates/gate.mjs +0 -179
- package/src/protocol-templates/prepare-deploy.mjs +0 -81
- package/src/public-api/agent-grants.mjs +0 -57
- package/src/public-api/buzz-integration.mjs +0 -267
- package/src/public-api/connect-agent.mjs +0 -424
- package/src/public-api/grants.mjs +0 -142
- package/src/public-api/http.mjs +0 -707
- package/src/public-api/metering.mjs +0 -111
- package/src/public-control/aa-adapter.mjs +0 -402
- package/src/public-control/build-registry.mjs +0 -227
- package/src/public-control/bundler-client.mjs +0 -357
- package/src/public-control/grant-indexer.mjs +0 -296
- package/src/public-control/policy-render.mjs +0 -69
- package/src/public-control/policy-schema.mjs +0 -322
- package/src/public-control/runtime-config.mjs +0 -275
- package/src/public-control/session-key-model.mjs +0 -379
- package/src/public-control/session-orchestrator.mjs +0 -418
- package/src/rfq/intent.mjs +0 -180
- package/src/rfq/sources.mjs +0 -181
- package/src/route-attestation.mjs +0 -132
- package/src/router/best-execution.mjs +0 -326
- package/src/router/index.mjs +0 -250
- package/src/router/integrator-fee.mjs +0 -135
- package/src/router/prepare-bridge.mjs +0 -293
- package/src/router/prepare-route.mjs +0 -398
- package/src/router/proposal.mjs +0 -311
- package/src/router/risk-classifier.mjs +0 -119
- package/src/router/route-sources.mjs +0 -364
- package/src/scanner/chains.config.mjs +0 -543
- package/src/scanner/contract.mjs +0 -270
- package/src/scanner/evm-scanner.mjs +0 -394
- package/src/scanner/index.mjs +0 -9
- package/src/scanner/v2-venue.mjs +0 -351
- package/src/scanner/v3-venue.mjs +0 -308
- package/src/scopes.mjs +0 -44
- package/src/sell-simulation.mjs +0 -167
- package/src/signals/engine.mjs +0 -146
- package/src/signals/index.mjs +0 -1
- package/src/token-transfer-guard.mjs +0 -188
- package/src/tui/app.mjs +0 -438
- package/src/tui/backend.mjs +0 -215
- package/src/tui/format.mjs +0 -272
- package/src/tui/gateway-client.mjs +0 -177
- package/src/tui/input.mjs +0 -501
- package/src/tui/memory.mjs +0 -105
- package/src/tui/meta-learn.mjs +0 -85
- package/src/tui/renderer.mjs +0 -443
- package/src/tui/scheduler.mjs +0 -205
- package/src/tui/skills-loader.mjs +0 -140
- package/src/tui/standalone-client.mjs +0 -752
- package/src/tui/theme.mjs +0 -81
- package/src/vault-attestation.mjs +0 -146
- package/src/venues.mjs +0 -206
- package/src/watch-preferences.mjs +0 -85
- /package/{profiles → dist/assets/profiles}/_template/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/_template/profile.json +0 -0
- /package/{profiles → dist/assets/profiles}/bitcoin-agent/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/bitcoin-agent/profile.json +0 -0
- /package/{profiles → dist/assets/profiles}/hyperliquid-agent/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/hyperliquid-agent/profile.json +0 -0
- /package/{profiles → dist/assets/profiles}/oracle/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/oracle/profile.json +0 -0
- /package/{profiles → dist/assets/profiles}/polymarket-agent/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/polymarket-agent/profile.json +0 -0
- /package/{profiles → dist/assets/profiles}/profile.schema.json +0 -0
- /package/{profiles → dist/assets/profiles}/protocol-builder/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/protocol-builder/profile.json +0 -0
- /package/{profiles → dist/assets/profiles}/robinhood-agent/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/robinhood-agent/profile.json +0 -0
- /package/{profiles → dist/assets/profiles}/solana-agent/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/solana-agent/profile.json +0 -0
- /package/{profiles → dist/assets/profiles}/stable-agent/SOUL.md +0 -0
- /package/{profiles → dist/assets/profiles}/stable-agent/profile.json +0 -0
- /package/{skills → dist/assets/skills}/balance/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/bitcoin-l1.md +0 -0
- /package/{skills → dist/assets/skills}/chain-defi-ecosystem-profiling.md +0 -0
- /package/{skills → dist/assets/skills}/evm-contract-research.md +0 -0
- /package/{skills → dist/assets/skills}/hyperliquid.md +0 -0
- /package/{skills → dist/assets/skills}/multichain-exec-desk.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-action-semantics/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-best-execution/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-best-execution.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-bitcoin/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-chain-graphs-telegram-cards/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-chat/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-chat/chain.SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-chat/setup.SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-circuit-breaker/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-contract-research/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-desk/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-desk.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-dex-launch/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-equities/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-grants/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-hypercore-staking/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-hyperliquid/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-meme-token-sniper/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-multichain-nft-launch/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-multichain-token-launch/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-nft-gacha-launch/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-nft-mint-gas-war/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-polymarket/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-protocol-builder/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-protocol-security/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-public-product/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-receipts/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-receipts.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-rfq-tokenized-assets/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-smart-wallet-scanner/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-solana/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-solana-nft/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/oracle-token-research/SKILL.md +0 -0
- /package/{skills → dist/assets/skills}/solana.md +0 -0
package/src/router/index.mjs
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
// Public entry point: find the best swap or bridge route.
|
|
2
|
-
//
|
|
3
|
-
// Returns a ranked comparison, not just a winner. Showing the runners-up and the
|
|
4
|
-
// spread is what lets a caller sanity-check the result instead of trusting it -- and
|
|
5
|
-
// a one-source "best route" is not a comparison at all, so that case is labelled
|
|
6
|
-
// rather than presented as if it beat something.
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
gatherRoutes, rankRoutes, QUALITY, measurePriceImpact, applyImpactCeiling,
|
|
10
|
-
} from "./best-execution.mjs";
|
|
11
|
-
import { swapCandidates, bridgeCandidates, nativeUsd } from "./route-sources.mjs";
|
|
12
|
-
import { llamaPrices } from "../data/providers/defillama.mjs";
|
|
13
|
-
|
|
14
|
-
/** DefiLlama key for an ERC-20 on a given chain. */
|
|
15
|
-
const LLAMA_CHAIN = {
|
|
16
|
-
1: "ethereum",
|
|
17
|
-
10: "optimism",
|
|
18
|
-
56: "bsc",
|
|
19
|
-
137: "polygon",
|
|
20
|
-
8453: "base",
|
|
21
|
-
42161: "arbitrum",
|
|
22
|
-
43114: "avax",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
async function destPrice(chainId, token, opts) {
|
|
26
|
-
const chain = LLAMA_CHAIN[Number(chainId)];
|
|
27
|
-
if (!chain) return { priceUsd: null, decimals: null };
|
|
28
|
-
const key = `${chain}:${token}`;
|
|
29
|
-
try {
|
|
30
|
-
const res = await llamaPrices([key], opts);
|
|
31
|
-
const hit = res?.coins?.[key];
|
|
32
|
-
const p = Number(hit?.price);
|
|
33
|
-
const d = Number(hit?.decimals);
|
|
34
|
-
return {
|
|
35
|
-
priceUsd: Number.isFinite(p) ? p : null,
|
|
36
|
-
decimals: Number.isFinite(d) ? d : null,
|
|
37
|
-
};
|
|
38
|
-
} catch {
|
|
39
|
-
return { priceUsd: null, decimals: null };
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Best same-chain swap route.
|
|
45
|
-
*
|
|
46
|
-
* @param {object} p
|
|
47
|
-
* @param {number} p.chainId
|
|
48
|
-
* @param {string} p.tokenIn
|
|
49
|
-
* @param {string} p.tokenOut
|
|
50
|
-
* @param {string|bigint} p.amountIn raw units of tokenIn
|
|
51
|
-
* @param {string} [p.taker] address used for quoting only
|
|
52
|
-
* @param {number} [p.decimalsIn] enables ParaSwap
|
|
53
|
-
* @param {number} [p.decimalsOut] enables ParaSwap + net ranking
|
|
54
|
-
*/
|
|
55
|
-
export async function bestSwapRoute(p, opts = {}) {
|
|
56
|
-
const { chainId, tokenIn, tokenOut, amountIn } = p;
|
|
57
|
-
if (chainId == null || !tokenIn || !tokenOut || amountIn == null) {
|
|
58
|
-
throw new Error("bestSwapRoute requires chainId, tokenIn, tokenOut, amountIn");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Prices and quotes run CONCURRENTLY, not one after the other.
|
|
62
|
-
//
|
|
63
|
-
// The obvious shape -- await the prices, then fan out the quotes -- adds the price
|
|
64
|
-
// latency to every single comparison. Measured on Base: ~890ms of DefiLlama in
|
|
65
|
-
// front of ~3.3s of quotes, for 4.7s total when the true floor is 3.3s.
|
|
66
|
-
//
|
|
67
|
-
// Neither price is needed to ASK for a quote:
|
|
68
|
-
// * destPrice is only used at RANK time, after every quote has returned
|
|
69
|
-
// * nativePriceUsd is only used by the 0x adapter to convert its native-wei gas
|
|
70
|
-
// figure, so it is passed as a PROMISE and awaited inside that one adapter
|
|
71
|
-
//
|
|
72
|
-
// So the whole price fetch hides behind the slowest quote instead of preceding it.
|
|
73
|
-
const nativePricePromise = nativeUsd(chainId, opts);
|
|
74
|
-
const destPromise = destPrice(chainId, tokenOut, opts);
|
|
75
|
-
|
|
76
|
-
const candidates = swapCandidates({
|
|
77
|
-
...p,
|
|
78
|
-
nativePriceUsd: nativePricePromise,
|
|
79
|
-
decimalsOut: p.decimalsOut,
|
|
80
|
-
destDecimalsPromise: destPromise,
|
|
81
|
-
opts,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const [routes, dest] = await Promise.all([
|
|
85
|
-
gatherRoutes(candidates, { timeoutMs: opts.timeoutMs ?? 12_000 }),
|
|
86
|
-
destPromise,
|
|
87
|
-
]);
|
|
88
|
-
|
|
89
|
-
// Swallow an unused rejection so a failed price lookup cannot surface as an
|
|
90
|
-
// unhandled rejection when no adapter happened to await it.
|
|
91
|
-
nativePricePromise.catch(() => {});
|
|
92
|
-
|
|
93
|
-
const ranked = rankRoutes(routes, {
|
|
94
|
-
destPriceUsd: dest.priceUsd,
|
|
95
|
-
destDecimals: p.decimalsOut ?? dest.decimals,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Liquidity check. Ranking alone will happily crown a quote taken against a
|
|
99
|
-
// drained pool, because a thin venue returns a well-formed number rather than
|
|
100
|
-
// an error. Re-quote the winner at 1/1000th size and compare per-unit price:
|
|
101
|
-
// real slippage decays smoothly, a dead pool falls off a cliff.
|
|
102
|
-
//
|
|
103
|
-
// Opt out with `maxImpactPct: null` for callers that genuinely want the raw
|
|
104
|
-
// ranking (analytics, spread display) rather than an executable route.
|
|
105
|
-
const maxImpactPct = opts.maxImpactPct === undefined ? 25 : opts.maxImpactPct;
|
|
106
|
-
if (maxImpactPct != null && ranked.best) {
|
|
107
|
-
const winner = ranked.best.source;
|
|
108
|
-
const probe = async (amt) => {
|
|
109
|
-
const sub = swapCandidates({
|
|
110
|
-
...p,
|
|
111
|
-
amountIn: amt.toString(),
|
|
112
|
-
nativePriceUsd: nativePricePromise,
|
|
113
|
-
decimalsOut: p.decimalsOut,
|
|
114
|
-
destDecimalsPromise: destPromise,
|
|
115
|
-
opts,
|
|
116
|
-
}).filter((c) => c.source === winner);
|
|
117
|
-
if (!sub.length) return null;
|
|
118
|
-
const got = await gatherRoutes(sub, { timeoutMs: opts.timeoutMs ?? 12_000 });
|
|
119
|
-
return got?.[0]?.amountOut ?? null;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const impact = await measurePriceImpact(probe, amountIn);
|
|
123
|
-
if (impact) {
|
|
124
|
-
const guarded = applyImpactCeiling(ranked, { [winner]: impact }, { maxImpactPct });
|
|
125
|
-
return {
|
|
126
|
-
kind: "swap",
|
|
127
|
-
chainId,
|
|
128
|
-
tokenIn,
|
|
129
|
-
tokenOut,
|
|
130
|
-
amountIn: String(amountIn),
|
|
131
|
-
...guarded,
|
|
132
|
-
// Provenance travels with the guarded result too. Omitting it here made
|
|
133
|
-
// the CLI print "sources: undefined/undefined answered" and a bare
|
|
134
|
-
// "undefined" note on exactly the swaps that DID get impact-measured.
|
|
135
|
-
sourcesTried: candidates.length,
|
|
136
|
-
sourcesAnswered: routes.filter((r) => r.quality !== QUALITY.FAILED).length,
|
|
137
|
-
note: describe(guarded, candidates.length),
|
|
138
|
-
priceImpact: { [winner]: Number(impact.impactPct.toFixed(2)) },
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
kind: "swap",
|
|
145
|
-
chainId,
|
|
146
|
-
tokenIn,
|
|
147
|
-
tokenOut,
|
|
148
|
-
amountIn: String(amountIn),
|
|
149
|
-
...ranked,
|
|
150
|
-
sourcesTried: candidates.length,
|
|
151
|
-
sourcesAnswered: routes.filter((r) => r.quality !== QUALITY.FAILED).length,
|
|
152
|
-
note: describe(ranked, candidates.length),
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Best cross-chain bridge route.
|
|
158
|
-
*
|
|
159
|
-
* Duration is reported, never folded into the score. A route that saves $2 and takes
|
|
160
|
-
* 30 minutes is not strictly better than one costing $2 more that lands in 20
|
|
161
|
-
* seconds -- that trade-off belongs to the caller, and burying it in a single number
|
|
162
|
-
* would hide the only thing distinguishing two similar quotes.
|
|
163
|
-
*/
|
|
164
|
-
export async function bestBridgeRoute(p, opts = {}) {
|
|
165
|
-
const { fromChainId, toChainId, tokenIn, tokenOut, amountIn } = p;
|
|
166
|
-
if (fromChainId == null || toChainId == null || !tokenIn || !tokenOut || amountIn == null) {
|
|
167
|
-
throw new Error(
|
|
168
|
-
"bestBridgeRoute requires fromChainId, toChainId, tokenIn, tokenOut, amountIn",
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
if (Number(fromChainId) === Number(toChainId)) {
|
|
172
|
-
throw new Error("bestBridgeRoute is for cross-chain; use bestSwapRoute for same-chain");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Same concurrency rule as swaps: the destination price is only needed at RANK
|
|
176
|
-
// time, so fetching it before the quotes would add its latency to every bridge
|
|
177
|
-
// comparison for no reason.
|
|
178
|
-
const destPromise = destPrice(toChainId, tokenOut, opts);
|
|
179
|
-
const candidates = bridgeCandidates({ ...p, opts });
|
|
180
|
-
const [routes, dest] = await Promise.all([
|
|
181
|
-
gatherRoutes(candidates, { timeoutMs: opts.timeoutMs ?? 15_000 }),
|
|
182
|
-
destPromise,
|
|
183
|
-
]);
|
|
184
|
-
const ranked = rankRoutes(routes, {
|
|
185
|
-
destPriceUsd: dest.priceUsd,
|
|
186
|
-
destDecimals: p.decimalsOut ?? dest.decimals,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const durations = ranked.routes
|
|
190
|
-
.map((r) => ({ source: r.source, seconds: r.meta?.durationSeconds ?? null }))
|
|
191
|
-
.filter((d) => d.seconds != null);
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
kind: "bridge",
|
|
195
|
-
fromChainId,
|
|
196
|
-
toChainId,
|
|
197
|
-
tokenIn,
|
|
198
|
-
tokenOut,
|
|
199
|
-
amountIn: String(amountIn),
|
|
200
|
-
...ranked,
|
|
201
|
-
durations,
|
|
202
|
-
sourcesTried: candidates.length,
|
|
203
|
-
sourcesAnswered: routes.filter((r) => r.quality !== QUALITY.FAILED).length,
|
|
204
|
-
note: describe(ranked, candidates.length),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function describe(ranked, tried) {
|
|
209
|
-
if (!ranked.best) return "no source returned a usable route";
|
|
210
|
-
const answered = ranked.routes.length;
|
|
211
|
-
if (answered === 1) {
|
|
212
|
-
return (
|
|
213
|
-
`only 1 of ${tried} sources answered, so this is the ONLY route, not a proven ` +
|
|
214
|
-
"best one. Treat it as unbenchmarked."
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
if (ranked.improvementBps != null) {
|
|
218
|
-
const bps = ranked.improvementBps;
|
|
219
|
-
const pct = bps / 100;
|
|
220
|
-
// Below 0.01bps the winner is a coin flip against quote noise, so claiming
|
|
221
|
-
// an edge in ANY unit is a false precision claim. Say they are tied.
|
|
222
|
-
if (bps < 0.01) {
|
|
223
|
-
return (
|
|
224
|
-
`best of ${answered} routes, ranked on ${ranked.rankedOn}: ` +
|
|
225
|
-
`effectively tied with the runner-up (${ranked.spreadBasis}), ` +
|
|
226
|
-
"inside quote noise. Re-quote before you size up."
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
// A sub-bp edge is still an edge and on size it is real money, so report it
|
|
230
|
-
// in bps rather than letting the percentage round it away to 0.00%.
|
|
231
|
-
const margin = pct >= 0.01 ? `${pct.toFixed(2)}%` : `${bps.toFixed(2)}bps`;
|
|
232
|
-
return (
|
|
233
|
-
`best of ${answered} routes, ranked on ${ranked.rankedOn}: ` +
|
|
234
|
-
`${margin} better than the runner-up ` +
|
|
235
|
-
`(${ranked.spreadBasis})`
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
// No spread claim when the top two are not measured the same way. Saying "X% better"
|
|
239
|
-
// off a mismatched pair is worse than saying nothing.
|
|
240
|
-
return (
|
|
241
|
-
`best of ${answered} routes, ranked on ${ranked.rankedOn}. No spread quoted: the ` +
|
|
242
|
-
"top routes report cost differently, so the margin is not comparable."
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export { QUALITY };
|
|
247
|
-
|
|
248
|
-
// Exported for tests: the note text is a user-facing claim about execution
|
|
249
|
-
// quality, so it needs deterministic coverage independent of live quotes.
|
|
250
|
-
export { describe as formatComparisonNote };
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integrator fee policy.
|
|
3
|
-
*
|
|
4
|
-
* One place decides whether a route carries a fee, so the answer cannot drift
|
|
5
|
-
* between providers. Three rules, in order:
|
|
6
|
-
*
|
|
7
|
-
* 1. OFF unless explicitly configured. No silent default skim.
|
|
8
|
-
* 2. Locals Only holders pay ZERO Oracle integrator fees. Ownership changes
|
|
9
|
-
* only this rate and never changes product access.
|
|
10
|
-
* 3. Whatever is charged must be disclosable. `describeFee()` returns the
|
|
11
|
-
* exact bps and recipient so the caller can render it BEFORE the user
|
|
12
|
-
* signs. Oracle's pitch is "decoded before you sign" — an undisclosed fee
|
|
13
|
-
* would cost more credibility than the basis points are worth.
|
|
14
|
-
*
|
|
15
|
-
* Note on the status quo this replaces: LI.FI already applies its own 25 bps
|
|
16
|
-
* to every route Oracle sends and keeps all of it, because Oracle never
|
|
17
|
-
* identified itself as an integrator. Verified live 2026-08-06 — an
|
|
18
|
-
* unregistered quote returns feeCosts [{ name: "LIFI Fixed Fee", percentage:
|
|
19
|
-
* "0.0025" }]. So this is not a new cost to the user on that path; it is
|
|
20
|
-
* Oracle claiming a share of a fee that is already being charged.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const MAX_BPS = 100; // 1%. A hard ceiling so a typo cannot ship a 50% fee.
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 10 bps. Deliberately at the low end: LI.FI's own default is 25 bps and
|
|
27
|
-
* Matcha runs 15, so this stays invisible to anyone comparison-shopping.
|
|
28
|
-
*
|
|
29
|
-
* The fee's job is not revenue maximisation — Locals Only holders pay zero, so
|
|
30
|
-
* a small non-holder fee makes the NFT obviously worth holding. A large one
|
|
31
|
-
* would just teach people to route around Oracle.
|
|
32
|
-
*/
|
|
33
|
-
export const DEFAULT_FEE_BPS = 10;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Per-action pricing. A same-chain swap is the most price-shopped action in
|
|
37
|
-
* crypto — every aggregator quotes it and users compare to the basis point —
|
|
38
|
-
* so it stays cheapest. The others are not comparison-shopped the same way and
|
|
39
|
-
* deliver more work per transaction.
|
|
40
|
-
*
|
|
41
|
-
* swap 10 bps below LI.FI 25 and Matcha 15
|
|
42
|
-
* bridge 15 bps multi-chain routing, longer settlement, more failure modes
|
|
43
|
-
* perps 5 bps venues already charge taker fees; stacking is punitive
|
|
44
|
-
* nft 0 marketplace fees are already heavy
|
|
45
|
-
*
|
|
46
|
-
* A holder pays zero on every one of these.
|
|
47
|
-
*/
|
|
48
|
-
export const FEE_TIERS = Object.freeze({
|
|
49
|
-
swap: 10,
|
|
50
|
-
bridge: 15,
|
|
51
|
-
perps: 5,
|
|
52
|
-
nft: 0,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
export function tierBps(action) {
|
|
56
|
-
const key = String(action || "swap").toLowerCase();
|
|
57
|
-
return Object.prototype.hasOwnProperty.call(FEE_TIERS, key) ? FEE_TIERS[key] : DEFAULT_FEE_BPS;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const FEE_ENV = "ORACLE_INTEGRATOR_FEE_BPS";
|
|
61
|
-
export const FEE_RECIPIENT_ENV = "ORACLE_INTEGRATOR_FEE_RECIPIENT";
|
|
62
|
-
export const INTEGRATOR_ENV = "ORACLE_INTEGRATOR_ID";
|
|
63
|
-
|
|
64
|
-
export const DEFAULT_INTEGRATOR_ID = "oracle";
|
|
65
|
-
|
|
66
|
-
function parseBps(raw, { fallback = 0 } = {}) {
|
|
67
|
-
// Absent means "use the house default". An explicitly EMPTY string means the
|
|
68
|
-
// operator set it to nothing on purpose, which is an instruction to charge
|
|
69
|
-
// nothing — not an invitation to fall back.
|
|
70
|
-
if (raw === undefined || raw === null) return fallback;
|
|
71
|
-
if (String(raw).trim() === "") return 0;
|
|
72
|
-
const n = Number(raw);
|
|
73
|
-
if (!Number.isFinite(n) || n <= 0) return 0;
|
|
74
|
-
return Math.min(Math.floor(n), MAX_BPS);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Resolve the fee that applies to one route.
|
|
79
|
-
*
|
|
80
|
-
* `isHolder` is passed in rather than looked up here: this module must stay
|
|
81
|
-
* synchronous and side-effect free so it can be called from inside quote
|
|
82
|
-
* builders without turning them into async chain-readers.
|
|
83
|
-
*/
|
|
84
|
-
export function resolveFee({ env = process.env, isHolder = false, action = "swap" } = {}) {
|
|
85
|
-
const recipient = String(env[FEE_RECIPIENT_ENV] || "").trim();
|
|
86
|
-
// Setting a recipient is the opt-in. Once that exists, bps defaults to 10
|
|
87
|
-
// rather than forcing every deployment to restate the house number.
|
|
88
|
-
const bps = parseBps(env[FEE_ENV], { fallback: recipient ? tierBps(action) : 0 });
|
|
89
|
-
const integrator = String(env[INTEGRATOR_ENV] || DEFAULT_INTEGRATOR_ID).trim();
|
|
90
|
-
|
|
91
|
-
// Holder check comes first. A zero-bps tier (nft) would otherwise return
|
|
92
|
-
// "not-configured" for a holder, which is a true zero with a misleading
|
|
93
|
-
// reason — the UI would say "No Oracle fee" instead of crediting the NFT.
|
|
94
|
-
if (isHolder) {
|
|
95
|
-
return { bps: 0, recipient, integrator, action, applies: false, reason: "locals-only-holder" };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!bps) {
|
|
99
|
-
return { bps: 0, recipient: "", integrator, action, applies: false, reason: "not-configured" };
|
|
100
|
-
}
|
|
101
|
-
if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
|
|
102
|
-
// Fail closed. A fee with nowhere to go is a fee that silently vanishes
|
|
103
|
-
// into the aggregator, which is the exact situation this module exists to
|
|
104
|
-
// end.
|
|
105
|
-
return { bps: 0, recipient: "", integrator, action, applies: false, reason: "no-recipient" };
|
|
106
|
-
}
|
|
107
|
-
return { bps, recipient, integrator, action, applies: true, reason: "configured" };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Human-readable disclosure. Render this next to the quote, before signing. */
|
|
111
|
-
export function describeFee(fee) {
|
|
112
|
-
if (!fee?.applies) {
|
|
113
|
-
if (fee?.reason === "locals-only-holder") return "No Oracle fee (Locals Only holder).";
|
|
114
|
-
return "No Oracle fee.";
|
|
115
|
-
}
|
|
116
|
-
return `Oracle routing fee ${fee.bps} bps (${(fee.bps / 100).toFixed(2)}%) to ${fee.recipient}.`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** LI.FI wants a decimal fraction plus a registered integrator id. */
|
|
120
|
-
export function lifiFeeParams(fee) {
|
|
121
|
-
if (!fee?.applies) return {};
|
|
122
|
-
return { integrator: fee.integrator, fee: String(fee.bps / 10_000) };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** ParaSwap wants integer bps and a partner string. */
|
|
126
|
-
export function paraswapFeeParams(fee) {
|
|
127
|
-
if (!fee?.applies) return {};
|
|
128
|
-
return {
|
|
129
|
-
partner: fee.integrator,
|
|
130
|
-
partnerFeeBps: String(fee.bps),
|
|
131
|
-
partnerAddress: fee.recipient,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export const FEE_MAX_BPS = MAX_BPS;
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
// Prepare a cross-chain bridge route.
|
|
2
|
-
//
|
|
3
|
-
// Bridging differs from swapping in ways that matter for a signable artifact:
|
|
4
|
-
//
|
|
5
|
-
// 1. IT IS OFTEN MULTIPLE TRANSACTIONS. Relay can return an approval and a deposit
|
|
6
|
-
// as separate items. A caller handed a bare "transaction" would sign the first
|
|
7
|
-
// and believe they were done, leaving funds approved but not bridged. So the
|
|
8
|
-
// artifact is always a LIST, and its length is stated.
|
|
9
|
-
//
|
|
10
|
-
// 2. THE DESTINATION CHAIN IS PART OF THE TRUST DECISION. Every transaction here
|
|
11
|
-
// executes on the ORIGIN chain, but the value lands elsewhere. Signing an
|
|
12
|
-
// origin-chain tx that credits the wrong destination is unrecoverable, so
|
|
13
|
-
// toChainId is echoed alongside the destination address.
|
|
14
|
-
//
|
|
15
|
-
// 3. TIME IS A REAL COST. A bridge that saves $2 and takes 30 minutes is not
|
|
16
|
-
// obviously better than one costing $2 more that lands in 20 seconds. Duration
|
|
17
|
-
// is surfaced, never folded into a score.
|
|
18
|
-
//
|
|
19
|
-
// 4. FINALITY IS NOT ATOMIC. The origin transaction succeeding does NOT mean funds
|
|
20
|
-
// arrived. That gap is where users panic, so it is stated explicitly rather than
|
|
21
|
-
// left implied.
|
|
22
|
-
//
|
|
23
|
-
// Custody is unchanged: artifacts only. This module cannot sign or broadcast.
|
|
24
|
-
|
|
25
|
-
import { bestBridgeRoute } from "./index.mjs";
|
|
26
|
-
import { stampPrepared } from "../prepare-envelope.mjs";
|
|
27
|
-
|
|
28
|
-
const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Placeholder addresses: fine for quoting, never for preparing.
|
|
32
|
-
* Same reasoning as the swap path -- some venues catch this and some do not.
|
|
33
|
-
*/
|
|
34
|
-
const NON_TAKER_ADDRESSES = new Set(
|
|
35
|
-
[
|
|
36
|
-
"0x0000000000000000000000000000000000000000",
|
|
37
|
-
"0x000000000000000000000000000000000000dead",
|
|
38
|
-
"0x0000000000000000000000000000000000000001",
|
|
39
|
-
"0x00000000000000000000000000000000000a1ce5",
|
|
40
|
-
].map((a) => a.toLowerCase()),
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const DEFAULT_DRIFT_TOLERANCE_BPS = 100;
|
|
44
|
-
|
|
45
|
-
const PREPARERS = {
|
|
46
|
-
relay: async ({ fromChainId, toChainId, tokenIn, tokenOut, amountIn, taker }, opts) => {
|
|
47
|
-
const { relayPrepare } = await import("../data/providers/bridges.mjs");
|
|
48
|
-
const p = await relayPrepare(
|
|
49
|
-
{
|
|
50
|
-
user: taker,
|
|
51
|
-
originChainId: fromChainId,
|
|
52
|
-
destinationChainId: toChainId,
|
|
53
|
-
originCurrency: tokenIn,
|
|
54
|
-
destinationCurrency: tokenOut,
|
|
55
|
-
amount: String(amountIn),
|
|
56
|
-
},
|
|
57
|
-
opts,
|
|
58
|
-
);
|
|
59
|
-
return {
|
|
60
|
-
transactions: p.transactions || [],
|
|
61
|
-
amountOut: p.quote?.details?.currencyOut?.amount ?? null,
|
|
62
|
-
minOut: p.quote?.details?.currencyOut?.minimumAmount ?? null,
|
|
63
|
-
durationSeconds: p.quote?.details?.timeEstimate ?? null,
|
|
64
|
-
};
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
lifi: async ({ fromChainId, toChainId, tokenIn, tokenOut, amountIn, taker, isHolder }, opts) => {
|
|
68
|
-
const { lifiPrepare } = await import("../data/providers/lifi.mjs");
|
|
69
|
-
const p = await lifiPrepare(
|
|
70
|
-
{
|
|
71
|
-
fromChain: fromChainId,
|
|
72
|
-
toChain: toChainId,
|
|
73
|
-
fromToken: tokenIn,
|
|
74
|
-
toToken: tokenOut,
|
|
75
|
-
fromAmount: amountIn,
|
|
76
|
-
fromAddress: taker,
|
|
77
|
-
// A bridge is not a same-chain swap: more routing, longer settlement,
|
|
78
|
-
// more failure modes. Priced on its own tier.
|
|
79
|
-
isHolder,
|
|
80
|
-
feeAction: "bridge",
|
|
81
|
-
},
|
|
82
|
-
opts,
|
|
83
|
-
);
|
|
84
|
-
// LI.FI returns a single transactionRequest. Normalized into a list so callers
|
|
85
|
-
// handle one shape regardless of source -- a single-item list is honest, a bare
|
|
86
|
-
// object next to Relay's list is a trap.
|
|
87
|
-
return {
|
|
88
|
-
transactions: p.transaction ? [p.transaction] : [],
|
|
89
|
-
requiresApproval: p.requiresApproval ?? null,
|
|
90
|
-
amountOut: p.quote?.estimate?.toAmount ?? null,
|
|
91
|
-
minOut: p.quote?.estimate?.toAmountMin ?? null,
|
|
92
|
-
durationSeconds: p.quote?.estimate?.executionDuration ?? null,
|
|
93
|
-
};
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export function supportedBridgePreparers() {
|
|
98
|
-
return Object.keys(PREPARERS);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Compare bridge routes, then prepare the winner.
|
|
103
|
-
*
|
|
104
|
-
* @param {object} p same shape as bestBridgeRoute, plus:
|
|
105
|
-
* @param {string} p.taker the real wallet that will sign
|
|
106
|
-
* @param {string} [p.source] force a specific source
|
|
107
|
-
* @param {number} [p.driftToleranceBps]
|
|
108
|
-
*/
|
|
109
|
-
export async function prepareBestBridgeRoute(p, opts = {}) {
|
|
110
|
-
const { taker, fromChainId, toChainId } = p;
|
|
111
|
-
|
|
112
|
-
if (!taker) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
"prepareBestBridgeRoute requires taker: bridge transactions are built FOR an " +
|
|
115
|
-
"address, and the quote-only placeholder must never end up in one",
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
if (!ADDRESS_RE.test(taker)) {
|
|
119
|
-
throw new Error(`taker must be a valid 20-byte address, got "${taker}"`);
|
|
120
|
-
}
|
|
121
|
-
if (NON_TAKER_ADDRESSES.has(taker.toLowerCase())) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`taker "${taker}" is a placeholder/burn address. Bridging to it would send ` +
|
|
124
|
-
"funds to an address nobody controls, on a chain where you cannot undo it.",
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const comparison = await bestBridgeRoute(p, opts);
|
|
129
|
-
if (!comparison.best) {
|
|
130
|
-
return { ok: false, reason: "no source returned a usable bridge route", comparison };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const chosenSource = p.source || comparison.best.source;
|
|
134
|
-
const chosen = comparison.routes.find((r) => r.source === chosenSource);
|
|
135
|
-
if (!chosen) {
|
|
136
|
-
return { ok: false, reason: `source "${chosenSource}" returned no usable route`, comparison };
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const prepare = PREPARERS[chosenSource];
|
|
140
|
-
if (!prepare) {
|
|
141
|
-
const alt = comparison.routes.find((r) => PREPARERS[r.source] && r.source !== chosenSource);
|
|
142
|
-
return {
|
|
143
|
-
ok: false,
|
|
144
|
-
reason:
|
|
145
|
-
`${chosenSource} won the comparison but has no bridge prepare path in this ` +
|
|
146
|
-
`build. ` +
|
|
147
|
-
(alt
|
|
148
|
-
? `Next preparable route is ${alt.source}; pass source:"${alt.source}".`
|
|
149
|
-
: "No route in this comparison can be prepared."),
|
|
150
|
-
// Machine-readable siblings of the message above. A caller should not have to
|
|
151
|
-
// regex prose to find the fallback -- and the CLI/agent path reads these.
|
|
152
|
-
failureKind: "no-prepare-path",
|
|
153
|
-
...(alt ? { alternative: alt.source } : {}),
|
|
154
|
-
winner: chosenSource,
|
|
155
|
-
comparison,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
let prepared;
|
|
160
|
-
try {
|
|
161
|
-
prepared = await prepare(p, opts);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
const detail =
|
|
164
|
-
typeof err?.body === "string" ? err.body : err?.body ? JSON.stringify(err.body) : "";
|
|
165
|
-
const msg = [String(err?.message || err), detail].filter(Boolean).join(" :: ");
|
|
166
|
-
|
|
167
|
-
const allowanceIssue = /allowance/i.test(msg);
|
|
168
|
-
const balanceIssue = !allowanceIssue && /not enough|insufficient|balance/i.test(msg);
|
|
169
|
-
const alt = comparison.routes.find((r) => PREPARERS[r.source] && r.source !== chosenSource);
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
ok: false,
|
|
173
|
-
reason: allowanceIssue
|
|
174
|
-
? `${chosenSource} will not build calldata until the token approval exists ` +
|
|
175
|
-
`on-chain. Approve first, then prepare again. Underlying: ${msg}`
|
|
176
|
-
: balanceIssue
|
|
177
|
-
? `${chosenSource} refused to build: ${msg}. Quoting works for any address; ` +
|
|
178
|
-
`preparing needs the real funded wallet on chain ${fromChainId}.`
|
|
179
|
-
: `${chosenSource} bridge prepare failed: ${msg}`,
|
|
180
|
-
failureKind: allowanceIssue
|
|
181
|
-
? "approval-required-first"
|
|
182
|
-
: balanceIssue
|
|
183
|
-
? "taker-not-funded"
|
|
184
|
-
: "provider-error",
|
|
185
|
-
...(alt ? { alternative: alt.source } : {}),
|
|
186
|
-
comparison,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const txs = prepared.transactions || [];
|
|
191
|
-
if (!txs.length) {
|
|
192
|
-
return {
|
|
193
|
-
ok: false,
|
|
194
|
-
reason: `${chosenSource} returned no executable transaction for this route`,
|
|
195
|
-
failureKind: "provider-error",
|
|
196
|
-
comparison,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Every transaction must execute on the ORIGIN chain. A wrong chainId here means a
|
|
201
|
-
// wallet could be prompted on the wrong network, and the user would be signing
|
|
202
|
-
// something they cannot reason about.
|
|
203
|
-
const wrongChain = txs.filter(
|
|
204
|
-
(t) => t.chainId != null && Number(t.chainId) !== Number(fromChainId),
|
|
205
|
-
);
|
|
206
|
-
if (wrongChain.length) {
|
|
207
|
-
return {
|
|
208
|
-
ok: false,
|
|
209
|
-
reason:
|
|
210
|
-
`${chosenSource} returned a transaction for chain ${wrongChain[0].chainId} but ` +
|
|
211
|
-
`the origin chain is ${fromChainId}. Refusing to hand over a mismatched ` +
|
|
212
|
-
`artifact.`,
|
|
213
|
-
failureKind: "chain-mismatch",
|
|
214
|
-
comparison,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Drift against the comparison. Same reasoning as swaps: the comparison is stale.
|
|
219
|
-
const tolerance = p.driftToleranceBps ?? DEFAULT_DRIFT_TOLERANCE_BPS;
|
|
220
|
-
let driftBps = null;
|
|
221
|
-
let driftWarning = null;
|
|
222
|
-
if (prepared.amountOut != null && chosen.grossOut != null) {
|
|
223
|
-
try {
|
|
224
|
-
const then = BigInt(chosen.grossOut);
|
|
225
|
-
const now = BigInt(prepared.amountOut);
|
|
226
|
-
if (then > 0n) {
|
|
227
|
-
driftBps = Number(((now - then) * 10_000n) / then);
|
|
228
|
-
if (driftBps < -tolerance) {
|
|
229
|
-
driftWarning =
|
|
230
|
-
`route moved ${(driftBps / 100).toFixed(2)}% against you since the ` +
|
|
231
|
-
`comparison (tolerance ${(tolerance / 100).toFixed(2)}%). Re-compare ` +
|
|
232
|
-
`before signing.`;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
} catch {
|
|
236
|
-
/* no fabricated drift */
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const warnings = [
|
|
241
|
-
...(driftWarning ? [driftWarning] : []),
|
|
242
|
-
...(comparison.warnings || []),
|
|
243
|
-
// The single most common bridging surprise.
|
|
244
|
-
`Bridging is NOT atomic: the origin transaction succeeding does not mean funds ` +
|
|
245
|
-
`arrived on chain ${toChainId}. Expect ~${
|
|
246
|
-
prepared.durationSeconds != null ? `${prepared.durationSeconds}s` : "minutes"
|
|
247
|
-
} before the destination credits, and do not re-send if it is pending.`,
|
|
248
|
-
];
|
|
249
|
-
|
|
250
|
-
if (txs.length > 1) {
|
|
251
|
-
warnings.push(
|
|
252
|
-
`This route needs ${txs.length} transactions signed IN ORDER. Signing only the ` +
|
|
253
|
-
`first leaves funds approved but not bridged.`,
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return stampPrepared({
|
|
258
|
-
ok: true,
|
|
259
|
-
chosen: {
|
|
260
|
-
source: chosenSource,
|
|
261
|
-
wasWinner: chosenSource === comparison.best.source,
|
|
262
|
-
netOut: chosen.netOut,
|
|
263
|
-
grossOut: chosen.grossOut,
|
|
264
|
-
gasUsd: chosen.gasUsd,
|
|
265
|
-
},
|
|
266
|
-
artifactKind: "unsigned-transaction-sequence",
|
|
267
|
-
fromChainId: Number(fromChainId),
|
|
268
|
-
toChainId: Number(toChainId),
|
|
269
|
-
transactionCount: txs.length,
|
|
270
|
-
transactions: txs,
|
|
271
|
-
requiresApproval: prepared.requiresApproval ?? null,
|
|
272
|
-
minOut: prepared.minOut ?? null,
|
|
273
|
-
durationSeconds: prepared.durationSeconds ?? null,
|
|
274
|
-
// Where value goes on the origin chain. The first destination is the bridge entry.
|
|
275
|
-
destination: txs[0]?.to ?? null,
|
|
276
|
-
unsigned: true,
|
|
277
|
-
signedBy: "user-wallet",
|
|
278
|
-
driftBps,
|
|
279
|
-
warnings,
|
|
280
|
-
runnersUp: comparison.routes
|
|
281
|
-
.filter((r) => r.source !== chosenSource)
|
|
282
|
-
.map((r) => ({ source: r.source, netOut: r.netOut, gasUsd: r.gasUsd })),
|
|
283
|
-
comparison: {
|
|
284
|
-
rankedOn: comparison.rankedOn,
|
|
285
|
-
sourcesAnswered: comparison.sourcesAnswered,
|
|
286
|
-
sourcesTried: comparison.sourcesTried,
|
|
287
|
-
improvementBps: comparison.improvementBps,
|
|
288
|
-
},
|
|
289
|
-
note:
|
|
290
|
-
`UNSIGNED. ${txs.length} transaction(s) on chain ${fromChainId}, crediting ` +
|
|
291
|
-
`chain ${toChainId}. Verify every destination address before signing.`,
|
|
292
|
-
}, { provider: "router", kind: "best-bridge" });
|
|
293
|
-
}
|