perp-cli 0.12.15 → 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/README.md +278 -31
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +286 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +74 -84
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +286 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +939 -245
- package/dist/commands/history.js +72 -45
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +316 -337
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +219 -6
- package/dist/exchanges/aster.js +1023 -153
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +166 -0
- package/dist/exchanges/ondo/adapter.js +835 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +122 -0
- package/dist/exchanges/ondo/funds.js +290 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +288 -4
- package/dist/exchanges/pacifica.js +1013 -76
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1383 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +34 -0
- package/dist/exchanges/venues/ondo.js +140 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +28 -0
- package/dist/exchanges/venues/qfex.js +132 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +157 -682
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +14 -0
- package/dist/landing.js +36 -0
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +820 -402
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +338 -134
- package/skills/perp-cli/references/agent-operations.md +181 -92
- package/skills/perp-cli/references/commands.md +475 -132
- package/skills/perp-cli/references/strategies.md +44 -38
- package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
- package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +33 -16
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ondo Perps spot adapter (design §12, §12.4).
|
|
3
|
+
*
|
|
4
|
+
* Same host and HMAC API key as perps (shares OndoAdapter's OndoClient).
|
|
5
|
+
* Account model "separate": the Spot wallet ("spot") next to the Perps margin
|
|
6
|
+
* wallet ("margin"); POST /v1/spot/transfers moves funds between them. Docs
|
|
7
|
+
* (docs.ondoperps.xyz/api-reference/spot-api-guide, read 2026-10-05):
|
|
8
|
+
* "The wallet identifiers are `spot` for Spot trading, `margin` for Perps
|
|
9
|
+
* collateral, and `main` for the main wallet. Funds in another wallet do not
|
|
10
|
+
* automatically fund a Spot order."
|
|
11
|
+
* "`POST /v1/spot/transfers` requires `transfer`. It accepts `main` ↔ `spot`
|
|
12
|
+
* and `margin` ↔ `spot` transfers on the same account."
|
|
13
|
+
*
|
|
14
|
+
* Paths and shapes were first taken from the production web app (route table
|
|
15
|
+
* module 10215, spot trade chunk 7609, balances parser 82886) and live public
|
|
16
|
+
* GETs (2026-10-01); the Spot API guide now documents them:
|
|
17
|
+
* public GET /v1/markets result.spot.tradingPairs
|
|
18
|
+
* public GET /v1/spot/ticker [{market,disabled,lastPrice,bid,ask,baseVolume,quoteVolume,...}]
|
|
19
|
+
* public GET /v1/spot/depth ?market=TSLA-USDC&depth= → {market,time,asks,bids}
|
|
20
|
+
* auth GET /v1/spot/balances [{coin,total,free,usdValue}]
|
|
21
|
+
* auth POST /v1/spot/orders {market,type,side,size,postOnly,price?,timeInForce?}
|
|
22
|
+
* auth GET /v1/spot/orders ?activeOnly=true&market=&limit=&cursor=
|
|
23
|
+
* auth DELETE /v1/spot/orders/{id}
|
|
24
|
+
* auth DELETE /v1/spot/orders ?market=
|
|
25
|
+
* auth POST /v1/spot/transfers {symbol,amount,from:{wallet},to:{wallet}}
|
|
26
|
+
*
|
|
27
|
+
* Delayed placement (POST /v1/spot/orders/delayed) is NOT implemented. The guide
|
|
28
|
+
* says "If the server rejects placement with a message directing you to a delayed
|
|
29
|
+
* path, sign a new request using that exact path and a fresh timestamp", but it
|
|
30
|
+
* documents neither an error_code nor the message for that rejection ("treat
|
|
31
|
+
* `error` as descriptive text whose wording can change"), so there is no
|
|
32
|
+
* documented signal to switch paths on. A rejection that names the delayed path
|
|
33
|
+
* is reported as CAPABILITY_UNSUPPORTED (definitely not placed) — see submitFailure.
|
|
34
|
+
*
|
|
35
|
+
* Market orders: IOC LIMIT at
|
|
36
|
+
* alignProtectionPrice(slippagePrice(mid, side, bps), quoteIncrement, side)
|
|
37
|
+
* which is the web app's own pattern. bps = the caller's resolveSlippage()
|
|
38
|
+
* value (design §13.2). The mid comes from the spot
|
|
39
|
+
* ticker's bid/ask; a one-sided book is PRICE_STALE (no lastPrice fallback).
|
|
40
|
+
*
|
|
41
|
+
* Unverified (needs a funded key):
|
|
42
|
+
* - whether API-key HMAC auth is accepted on /v1/spot/* (the web app uses a
|
|
43
|
+
* JWT). Auth rejections there map to NOT_IMPLEMENTED with remediation;
|
|
44
|
+
* - balance `coin` naming/units. The app indexes balances by the
|
|
45
|
+
* tokenConfig id / pair base ("TSLA", not the custodied "TSLAon"), and
|
|
46
|
+
* tokenConfig marks equities as ledgerUnit "shares" (displayed units, not
|
|
47
|
+
* raw GM-token units). Values are passed through as reported;
|
|
48
|
+
* - whether DELETE /v1/spot/orders returns a `result` (ignored either way);
|
|
49
|
+
* - minimum order size (not published).
|
|
50
|
+
*/
|
|
51
|
+
import type { ResolvedSlippage } from "../slippage.js";
|
|
52
|
+
import type { SpotAdapter, SpotBalance, SpotCancelAllCapable, SpotMarketInfo, SpotOpenOrdersCapable, SpotOrderResult, SpotTransferCapable } from "../spot-interface.js";
|
|
53
|
+
import type { OndoAdapter } from "./adapter.js";
|
|
54
|
+
/** OpenAPI SpotWalletKey.wallet (rest-spec.json: main | margin | spot); spot ↔ perp uses these two. */
|
|
55
|
+
type Wallet = "spot" | "margin";
|
|
56
|
+
export declare class OndoSpotAdapter implements SpotAdapter, SpotTransferCapable, SpotOpenOrdersCapable, SpotCancelAllCapable {
|
|
57
|
+
readonly name = "ondo";
|
|
58
|
+
private readonly perp;
|
|
59
|
+
/** BASE (upper-cased) → enabled pair */
|
|
60
|
+
private enabled;
|
|
61
|
+
/** wire market ("TSLA-USDC") → pair, incl. disabled (for mapping order rows) */
|
|
62
|
+
private byMarket;
|
|
63
|
+
private initialized;
|
|
64
|
+
constructor(perp: OndoAdapter);
|
|
65
|
+
private get client();
|
|
66
|
+
init(): Promise<void>;
|
|
67
|
+
private loadPairs;
|
|
68
|
+
/** bare base ("tsla") or "TSLA/USDC" → enabled pair (wire "TSLA-USDC"). */
|
|
69
|
+
private resolve;
|
|
70
|
+
/** wire "TSLA-USDC" → "TSLA/USDC". */
|
|
71
|
+
private displaySymbol;
|
|
72
|
+
/**
|
|
73
|
+
* Authenticated spot calls. HMAC acceptance on /v1/spot/* is unverified: a
|
|
74
|
+
* 401/403 that is not a scope/IP/clock problem is reported as NOT_IMPLEMENTED
|
|
75
|
+
* (the key works on perps but the spot route refuses it).
|
|
76
|
+
*/
|
|
77
|
+
private authed;
|
|
78
|
+
private ticker;
|
|
79
|
+
/** Book mid from the ticker. One-sided / empty book → PRICE_STALE (no lastPrice fallback). */
|
|
80
|
+
private mid;
|
|
81
|
+
private toResult;
|
|
82
|
+
private place;
|
|
83
|
+
getSpotMarkets(): Promise<SpotMarketInfo[]>;
|
|
84
|
+
getSpotOrderbook(symbol: string): Promise<{
|
|
85
|
+
bids: [string, string][];
|
|
86
|
+
asks: [string, string][];
|
|
87
|
+
}>;
|
|
88
|
+
getSpotBalances(): Promise<SpotBalance[]>;
|
|
89
|
+
/** IOC limit at the protection price for the caller's resolved slippage bound. */
|
|
90
|
+
spotMarketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
|
|
91
|
+
slippage: ResolvedSlippage;
|
|
92
|
+
}): Promise<SpotOrderResult>;
|
|
93
|
+
/** tif: GTC (default) | IOC | ALO (= GTC + postOnly). */
|
|
94
|
+
spotLimitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
|
|
95
|
+
tif?: string;
|
|
96
|
+
}): Promise<SpotOrderResult>;
|
|
97
|
+
spotCancelOrder(symbol: string, orderId: string): Promise<SpotOrderResult>;
|
|
98
|
+
getSpotOpenOrders(symbol: string | null): Promise<Array<{
|
|
99
|
+
orderId: string;
|
|
100
|
+
symbol: string;
|
|
101
|
+
side: "buy" | "sell";
|
|
102
|
+
price: string;
|
|
103
|
+
size: string;
|
|
104
|
+
filled: string;
|
|
105
|
+
}>>;
|
|
106
|
+
spotCancelAllOrders(symbol: string | null): Promise<{
|
|
107
|
+
venue: "ondo";
|
|
108
|
+
market: string | null;
|
|
109
|
+
cancelled: "all";
|
|
110
|
+
}>;
|
|
111
|
+
transferToSpot(amount: string, asset: string): Promise<{
|
|
112
|
+
venue: "ondo";
|
|
113
|
+
asset: string;
|
|
114
|
+
amount: string;
|
|
115
|
+
from: Wallet;
|
|
116
|
+
to: Wallet;
|
|
117
|
+
}>;
|
|
118
|
+
transferToPerp(amount: string, asset: string): Promise<{
|
|
119
|
+
venue: "ondo";
|
|
120
|
+
asset: string;
|
|
121
|
+
amount: string;
|
|
122
|
+
from: Wallet;
|
|
123
|
+
to: Wallet;
|
|
124
|
+
}>;
|
|
125
|
+
private transfer;
|
|
126
|
+
}
|
|
127
|
+
export {};
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ondo Perps spot adapter (design §12, §12.4).
|
|
3
|
+
*
|
|
4
|
+
* Same host and HMAC API key as perps (shares OndoAdapter's OndoClient).
|
|
5
|
+
* Account model "separate": the Spot wallet ("spot") next to the Perps margin
|
|
6
|
+
* wallet ("margin"); POST /v1/spot/transfers moves funds between them. Docs
|
|
7
|
+
* (docs.ondoperps.xyz/api-reference/spot-api-guide, read 2026-10-05):
|
|
8
|
+
* "The wallet identifiers are `spot` for Spot trading, `margin` for Perps
|
|
9
|
+
* collateral, and `main` for the main wallet. Funds in another wallet do not
|
|
10
|
+
* automatically fund a Spot order."
|
|
11
|
+
* "`POST /v1/spot/transfers` requires `transfer`. It accepts `main` ↔ `spot`
|
|
12
|
+
* and `margin` ↔ `spot` transfers on the same account."
|
|
13
|
+
*
|
|
14
|
+
* Paths and shapes were first taken from the production web app (route table
|
|
15
|
+
* module 10215, spot trade chunk 7609, balances parser 82886) and live public
|
|
16
|
+
* GETs (2026-10-01); the Spot API guide now documents them:
|
|
17
|
+
* public GET /v1/markets result.spot.tradingPairs
|
|
18
|
+
* public GET /v1/spot/ticker [{market,disabled,lastPrice,bid,ask,baseVolume,quoteVolume,...}]
|
|
19
|
+
* public GET /v1/spot/depth ?market=TSLA-USDC&depth= → {market,time,asks,bids}
|
|
20
|
+
* auth GET /v1/spot/balances [{coin,total,free,usdValue}]
|
|
21
|
+
* auth POST /v1/spot/orders {market,type,side,size,postOnly,price?,timeInForce?}
|
|
22
|
+
* auth GET /v1/spot/orders ?activeOnly=true&market=&limit=&cursor=
|
|
23
|
+
* auth DELETE /v1/spot/orders/{id}
|
|
24
|
+
* auth DELETE /v1/spot/orders ?market=
|
|
25
|
+
* auth POST /v1/spot/transfers {symbol,amount,from:{wallet},to:{wallet}}
|
|
26
|
+
*
|
|
27
|
+
* Delayed placement (POST /v1/spot/orders/delayed) is NOT implemented. The guide
|
|
28
|
+
* says "If the server rejects placement with a message directing you to a delayed
|
|
29
|
+
* path, sign a new request using that exact path and a fresh timestamp", but it
|
|
30
|
+
* documents neither an error_code nor the message for that rejection ("treat
|
|
31
|
+
* `error` as descriptive text whose wording can change"), so there is no
|
|
32
|
+
* documented signal to switch paths on. A rejection that names the delayed path
|
|
33
|
+
* is reported as CAPABILITY_UNSUPPORTED (definitely not placed) — see submitFailure.
|
|
34
|
+
*
|
|
35
|
+
* Market orders: IOC LIMIT at
|
|
36
|
+
* alignProtectionPrice(slippagePrice(mid, side, bps), quoteIncrement, side)
|
|
37
|
+
* which is the web app's own pattern. bps = the caller's resolveSlippage()
|
|
38
|
+
* value (design §13.2). The mid comes from the spot
|
|
39
|
+
* ticker's bid/ask; a one-sided book is PRICE_STALE (no lastPrice fallback).
|
|
40
|
+
*
|
|
41
|
+
* Unverified (needs a funded key):
|
|
42
|
+
* - whether API-key HMAC auth is accepted on /v1/spot/* (the web app uses a
|
|
43
|
+
* JWT). Auth rejections there map to NOT_IMPLEMENTED with remediation;
|
|
44
|
+
* - balance `coin` naming/units. The app indexes balances by the
|
|
45
|
+
* tokenConfig id / pair base ("TSLA", not the custodied "TSLAon"), and
|
|
46
|
+
* tokenConfig marks equities as ledgerUnit "shares" (displayed units, not
|
|
47
|
+
* raw GM-token units). Values are passed through as reported;
|
|
48
|
+
* - whether DELETE /v1/spot/orders returns a `result` (ignored either way);
|
|
49
|
+
* - minimum order size (not published).
|
|
50
|
+
*/
|
|
51
|
+
import { PerpError, isDefiniteOrderFailure, orderNotSent, orderOutcomeUnknown } from "../../errors.js";
|
|
52
|
+
import { alignProtectionPrice, floorSize, formatComputedPrice, requireOnTick, slippagePrice, subDecimal } from "../precision.js";
|
|
53
|
+
import { unsupported } from "../trading-v2.js";
|
|
54
|
+
import { fetchOndoSpotTradingPairs } from "./public.js";
|
|
55
|
+
const QUOTE = "USDC";
|
|
56
|
+
const DEPTH_LEVELS = 50;
|
|
57
|
+
const ORDERS_PAGE_LIMIT = 100;
|
|
58
|
+
const ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
59
|
+
const SPOT_AUTH_REMEDIATION = "Ondo spot endpoints (/v1/spot/*) are undocumented and did not accept the API key (HMAC). " +
|
|
60
|
+
"Trade spot in the Ondo Perps web app, or verify the key works on spot (perp wallet agent approve ondo) and report the error_code";
|
|
61
|
+
/** Ondo (Go) encodes an empty list as JSON null; that is "no rows". */
|
|
62
|
+
function listOf(v) {
|
|
63
|
+
return v === null ? [] : v;
|
|
64
|
+
}
|
|
65
|
+
function decimalsOf(step) {
|
|
66
|
+
const frac = step.split(".")[1];
|
|
67
|
+
return frac ? frac.replace(/0+$/, "").length : 0;
|
|
68
|
+
}
|
|
69
|
+
function isPositive(s) {
|
|
70
|
+
return Number(s) > 0;
|
|
71
|
+
}
|
|
72
|
+
export class OndoSpotAdapter {
|
|
73
|
+
name = "ondo";
|
|
74
|
+
perp;
|
|
75
|
+
/** BASE (upper-cased) → enabled pair */
|
|
76
|
+
enabled = new Map();
|
|
77
|
+
/** wire market ("TSLA-USDC") → pair, incl. disabled (for mapping order rows) */
|
|
78
|
+
byMarket = new Map();
|
|
79
|
+
initialized = false;
|
|
80
|
+
constructor(perp) {
|
|
81
|
+
this.perp = perp;
|
|
82
|
+
}
|
|
83
|
+
get client() {
|
|
84
|
+
return this.perp.restClient;
|
|
85
|
+
}
|
|
86
|
+
async init() {
|
|
87
|
+
if (this.initialized)
|
|
88
|
+
return;
|
|
89
|
+
await this.loadPairs();
|
|
90
|
+
this.initialized = true;
|
|
91
|
+
}
|
|
92
|
+
async loadPairs() {
|
|
93
|
+
const pairs = await fetchOndoSpotTradingPairs(this.client);
|
|
94
|
+
this.enabled.clear();
|
|
95
|
+
this.byMarket.clear();
|
|
96
|
+
for (const p of pairs) {
|
|
97
|
+
this.byMarket.set(p.market, p);
|
|
98
|
+
if (p.disabled !== true && p.pair.quote === QUOTE)
|
|
99
|
+
this.enabled.set(p.pair.base.toUpperCase(), p);
|
|
100
|
+
}
|
|
101
|
+
return pairs.filter(p => p.disabled !== true && p.pair.quote === QUOTE);
|
|
102
|
+
}
|
|
103
|
+
/** bare base ("tsla") or "TSLA/USDC" → enabled pair (wire "TSLA-USDC"). */
|
|
104
|
+
async resolve(symbol) {
|
|
105
|
+
await this.init();
|
|
106
|
+
const [rawBase, rawQuote, extra] = symbol.trim().split("/");
|
|
107
|
+
const base = (rawBase ?? "").toUpperCase();
|
|
108
|
+
if (base === "" || extra !== undefined)
|
|
109
|
+
throw new PerpError("INVALID_PARAMS", `Invalid spot symbol "${symbol}" (use TSLA or TSLA/USDC)`);
|
|
110
|
+
if (rawQuote !== undefined && rawQuote.toUpperCase() !== QUOTE) {
|
|
111
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Ondo spot only quotes ${QUOTE} (got ${symbol})`, { exchange: "ondo" });
|
|
112
|
+
}
|
|
113
|
+
const pair = this.enabled.get(base);
|
|
114
|
+
if (pair === undefined) {
|
|
115
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `No enabled Ondo spot market for ${symbol}`, {
|
|
116
|
+
exchange: "ondo",
|
|
117
|
+
remediation: `Available: ${[...this.enabled.values()].map(p => `${p.pair.base}/${QUOTE}`).join(", ") || "none"}`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return pair;
|
|
121
|
+
}
|
|
122
|
+
/** wire "TSLA-USDC" → "TSLA/USDC". */
|
|
123
|
+
displaySymbol(market) {
|
|
124
|
+
const p = this.byMarket.get(market);
|
|
125
|
+
if (p !== undefined)
|
|
126
|
+
return `${p.pair.base}/${p.pair.quote}`;
|
|
127
|
+
const i = market.lastIndexOf("-");
|
|
128
|
+
if (i <= 0)
|
|
129
|
+
throw new PerpError("EXCHANGE_ERROR", `Ondo spot: unexpected market "${market}"`);
|
|
130
|
+
return `${market.slice(0, i)}/${market.slice(i + 1)}`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Authenticated spot calls. HMAC acceptance on /v1/spot/* is unverified: a
|
|
134
|
+
* 401/403 that is not a scope/IP/clock problem is reported as NOT_IMPLEMENTED
|
|
135
|
+
* (the key works on perps but the spot route refuses it).
|
|
136
|
+
*/
|
|
137
|
+
async authed(fn) {
|
|
138
|
+
try {
|
|
139
|
+
return await fn();
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
if (e instanceof PerpError) {
|
|
143
|
+
const d = (e.structured.details ?? {});
|
|
144
|
+
const http = d.httpStatus;
|
|
145
|
+
const venueCode = typeof d.venueCode === "string" ? d.venueCode : null;
|
|
146
|
+
const authHttp = http === 401 || http === 403;
|
|
147
|
+
const keyRejected = venueCode === "auth_missing" || venueCode === "auth_invalid" || e.structured.code === "EXCHANGE_ERROR";
|
|
148
|
+
if (authHttp && keyRejected) {
|
|
149
|
+
throw new PerpError("NOT_IMPLEMENTED", `Ondo spot rejected API-key auth: ${e.message}`, {
|
|
150
|
+
exchange: "ondo",
|
|
151
|
+
venueCode,
|
|
152
|
+
httpStatus: http,
|
|
153
|
+
remediation: SPOT_AUTH_REMEDIATION,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw e;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async ticker() {
|
|
161
|
+
const rows = await this.client.publicGet("/v1/spot/ticker", null);
|
|
162
|
+
return new Map(listOf(rows).map(r => [r.market, r]));
|
|
163
|
+
}
|
|
164
|
+
/** Book mid from the ticker. One-sided / empty book → PRICE_STALE (no lastPrice fallback). */
|
|
165
|
+
async mid(market) {
|
|
166
|
+
const t = (await this.ticker()).get(market);
|
|
167
|
+
if (t === undefined)
|
|
168
|
+
throw new PerpError("PRICE_STALE", `Ondo spot: ${market} missing from /v1/spot/ticker`, { exchange: "ondo" });
|
|
169
|
+
const bid = Number(t.bid);
|
|
170
|
+
const ask = Number(t.ask);
|
|
171
|
+
if (!(bid > 0) || !(ask > 0) || ask < bid) {
|
|
172
|
+
throw new PerpError("PRICE_STALE", `Ondo spot: no two-sided quote for ${market} (bid ${t.bid}, ask ${t.ask})`, {
|
|
173
|
+
exchange: "ondo",
|
|
174
|
+
remediation: "Use a limit order (perp spot limit) instead of a market order",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return String((bid + ask) / 2);
|
|
178
|
+
}
|
|
179
|
+
toResult(o, venueRaw, remediation) {
|
|
180
|
+
const filled = isPositive(o.filledSize);
|
|
181
|
+
let status;
|
|
182
|
+
switch (o.status) {
|
|
183
|
+
case "fullyfilled":
|
|
184
|
+
status = "filled";
|
|
185
|
+
break;
|
|
186
|
+
case "canceled":
|
|
187
|
+
status = filled ? "partial" : "cancelled";
|
|
188
|
+
break;
|
|
189
|
+
case "open":
|
|
190
|
+
case "untriggered":
|
|
191
|
+
status = filled ? "partial" : "open";
|
|
192
|
+
break;
|
|
193
|
+
case "pending":
|
|
194
|
+
status = "pending";
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
throw new PerpError("EXCHANGE_ERROR", `Ondo spot: unknown order status "${String(o.status)}"`);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
venue: "ondo",
|
|
201
|
+
symbol: this.displaySymbol(o.market),
|
|
202
|
+
orderId: o.orderId,
|
|
203
|
+
status,
|
|
204
|
+
filledSize: o.filledSize,
|
|
205
|
+
avgPrice: filled ? formatComputedPrice(Number(o.filledCost) / Number(o.filledSize)) : null,
|
|
206
|
+
finality: status === "pending" ? "pending" : "final",
|
|
207
|
+
receivedToken: null,
|
|
208
|
+
remediation,
|
|
209
|
+
venueRaw,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async place(body, requested, size) {
|
|
213
|
+
let order;
|
|
214
|
+
try {
|
|
215
|
+
order = await this.authed(() => this.client.post("/v1/spot/orders", body));
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
throw submitFailure(e, `spot ${String(body.side)} ${size} ${String(body.market)}`);
|
|
219
|
+
}
|
|
220
|
+
// Size flooring is surfaced, never silent (L2).
|
|
221
|
+
const raw = subDecimal(requested, size) === "0" ? order : { requestedSize: requested, placedSize: size, response: order };
|
|
222
|
+
return { order, raw };
|
|
223
|
+
}
|
|
224
|
+
// ── Market data ──
|
|
225
|
+
async getSpotMarkets() {
|
|
226
|
+
const [pairs, tick] = await Promise.all([this.loadPairs(), this.ticker()]);
|
|
227
|
+
this.initialized = true;
|
|
228
|
+
return pairs.map((p) => {
|
|
229
|
+
const t = tick.get(p.market);
|
|
230
|
+
if (t === undefined)
|
|
231
|
+
throw new PerpError("EXCHANGE_ERROR", `Ondo spot: ${p.market} missing from /v1/spot/ticker`, { exchange: "ondo" });
|
|
232
|
+
const bid = Number(t.bid);
|
|
233
|
+
const ask = Number(t.ask);
|
|
234
|
+
return {
|
|
235
|
+
symbol: `${p.pair.base}/${QUOTE}`,
|
|
236
|
+
baseToken: p.pair.base,
|
|
237
|
+
quoteToken: QUOTE,
|
|
238
|
+
// Mid of a two-sided book; one-sided / empty book → null (no last-trade substitute, Rule #2).
|
|
239
|
+
markPrice: bid > 0 && ask > 0 ? formatComputedPrice((bid + ask) / 2) : null,
|
|
240
|
+
volume24h: t.quoteVolume,
|
|
241
|
+
sizeDecimals: decimalsOf(p.baseIncrement),
|
|
242
|
+
priceDecimals: decimalsOf(p.quoteIncrement),
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async getSpotOrderbook(symbol) {
|
|
247
|
+
const pair = await this.resolve(symbol);
|
|
248
|
+
const book = await this.client.publicGet("/v1/spot/depth", { market: pair.market, depth: DEPTH_LEVELS });
|
|
249
|
+
return { bids: listOf(book.bids), asks: listOf(book.asks) };
|
|
250
|
+
}
|
|
251
|
+
// ── Account ──
|
|
252
|
+
async getSpotBalances() {
|
|
253
|
+
const rows = await this.authed(() => this.client.get("/v1/spot/balances", null));
|
|
254
|
+
return listOf(rows).map((r) => {
|
|
255
|
+
if (r.free === undefined || r.free === "") {
|
|
256
|
+
throw new PerpError("EXCHANGE_ERROR", `Ondo spot: balance row for ${r.coin} has no "free" amount`, { exchange: "ondo" });
|
|
257
|
+
}
|
|
258
|
+
// `coin` / units passed through as reported (see header: unverified).
|
|
259
|
+
return { token: r.coin, total: r.total, available: r.free, held: subDecimal(r.total, r.free), shared: false };
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// ── Trading ──
|
|
263
|
+
/** IOC limit at the protection price for the caller's resolved slippage bound. */
|
|
264
|
+
async spotMarketOrder(symbol, side, size, opts) {
|
|
265
|
+
const bps = opts.slippage.bps;
|
|
266
|
+
// Pre-send (market, size, protection price): a failure is a definite "not placed".
|
|
267
|
+
let pair;
|
|
268
|
+
let qty;
|
|
269
|
+
let mid;
|
|
270
|
+
let price;
|
|
271
|
+
try {
|
|
272
|
+
pair = await this.resolve(symbol);
|
|
273
|
+
qty = floorSize(String(size), pair.baseIncrement);
|
|
274
|
+
mid = await this.mid(pair.market);
|
|
275
|
+
price = alignProtectionPrice(slippagePrice(mid, side, bps), pair.quoteIncrement, side);
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
throw orderNotSent("ondo", `spot ${side} ${size} ${symbol}`, e);
|
|
279
|
+
}
|
|
280
|
+
const { order, raw } = await this.place({
|
|
281
|
+
market: pair.market, type: "limit", side, size: qty, price, timeInForce: "IOC", postOnly: false,
|
|
282
|
+
}, String(size), qty);
|
|
283
|
+
const appliedBps = Math.round(Math.abs(Number(price) / Number(mid) - 1) * 10_000 * 100) / 100;
|
|
284
|
+
const r = this.toResult(order, { response: raw, protection: { referencePrice: mid, protectionPrice: price, appliedBps } }, null);
|
|
285
|
+
if (r.status === "cancelled") {
|
|
286
|
+
return { ...r, remediation: `No fill within ${bps} bps of mid ${mid} (limit ${price}); widen --slippage-bps or use a limit order` };
|
|
287
|
+
}
|
|
288
|
+
return r;
|
|
289
|
+
}
|
|
290
|
+
/** tif: GTC (default) | IOC | ALO (= GTC + postOnly). */
|
|
291
|
+
async spotLimitOrder(symbol, side, price, size, opts) {
|
|
292
|
+
const tif = (opts?.tif ?? "GTC").trim().toUpperCase();
|
|
293
|
+
if (tif !== "GTC" && tif !== "IOC" && tif !== "ALO") {
|
|
294
|
+
throw unsupported("Ondo Perps spot", tif === "FOK" ? "fok" : "timeInForce", tif === "FOK" ? "fill-or-kill is not offered and is never emulated" : `time-in-force ${opts?.tif} is not offered`, "Use --tif GTC, IOC or ALO");
|
|
295
|
+
}
|
|
296
|
+
const pair = await this.resolve(symbol);
|
|
297
|
+
const px = requireOnTick(String(price), pair.quoteIncrement);
|
|
298
|
+
const qty = floorSize(String(size), pair.baseIncrement);
|
|
299
|
+
const body = {
|
|
300
|
+
market: pair.market, type: "limit", side, size: qty, price: px,
|
|
301
|
+
timeInForce: tif === "IOC" ? "IOC" : "GTC",
|
|
302
|
+
postOnly: tif === "ALO",
|
|
303
|
+
};
|
|
304
|
+
const { order, raw } = await this.place(body, String(size), qty);
|
|
305
|
+
return this.toResult(order, raw, null);
|
|
306
|
+
}
|
|
307
|
+
async spotCancelOrder(symbol, orderId) {
|
|
308
|
+
await this.resolve(symbol);
|
|
309
|
+
if (!ID_RE.test(orderId)) {
|
|
310
|
+
throw new PerpError("INVALID_PARAMS", `invalid order id "${orderId}" (allowed: A-Z a-z 0-9 _ -, max 64)`);
|
|
311
|
+
}
|
|
312
|
+
const order = await this.authed(() => this.client.delete(`/v1/spot/orders/${orderId}`, null));
|
|
313
|
+
return this.toResult(order, order, null);
|
|
314
|
+
}
|
|
315
|
+
async getSpotOpenOrders(symbol) {
|
|
316
|
+
const market = symbol === null ? null : (await this.resolve(symbol)).market;
|
|
317
|
+
await this.init();
|
|
318
|
+
const out = [];
|
|
319
|
+
let cursor = null;
|
|
320
|
+
do {
|
|
321
|
+
const page = await this.authed(() => this.client.getPage("/v1/spot/orders", { activeOnly: true, market, limit: ORDERS_PAGE_LIMIT, cursor }));
|
|
322
|
+
out.push(...listOf(page.result));
|
|
323
|
+
cursor = page.nextCursor;
|
|
324
|
+
} while (cursor !== null);
|
|
325
|
+
return out.map(o => ({
|
|
326
|
+
orderId: o.orderId,
|
|
327
|
+
symbol: this.displaySymbol(o.market),
|
|
328
|
+
side: o.side,
|
|
329
|
+
price: o.price,
|
|
330
|
+
size: o.size,
|
|
331
|
+
filled: o.filledSize,
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
async spotCancelAllOrders(symbol) {
|
|
335
|
+
const market = symbol === null ? null : (await this.resolve(symbol)).market;
|
|
336
|
+
await this.authed(() => this.client.deleteNoResult("/v1/spot/orders", { market }));
|
|
337
|
+
return { venue: "ondo", market, cancelled: "all" };
|
|
338
|
+
}
|
|
339
|
+
// ── Spot ↔ perp transfer (POST /v1/spot/transfers, wallet "spot" ↔ "margin") ──
|
|
340
|
+
// Docs (spot-api-guide): "The optional `id` inside `from` and `to` defaults to the
|
|
341
|
+
// authenticated account ID." — not sent.
|
|
342
|
+
async transferToSpot(amount, asset) {
|
|
343
|
+
return this.transfer(amount, asset, "margin", "spot");
|
|
344
|
+
}
|
|
345
|
+
async transferToPerp(amount, asset) {
|
|
346
|
+
return this.transfer(amount, asset, "spot", "margin");
|
|
347
|
+
}
|
|
348
|
+
async transfer(amount, asset, from, to) {
|
|
349
|
+
if (!/^\d+(\.\d+)?$/.test(amount) || !isPositive(amount)) {
|
|
350
|
+
throw new PerpError("INVALID_PARAMS", `Transfer amount must be a positive decimal (got "${amount}")`);
|
|
351
|
+
}
|
|
352
|
+
const symbol = asset.trim();
|
|
353
|
+
if (!/^[A-Za-z0-9]{1,20}$/.test(symbol))
|
|
354
|
+
throw new PerpError("INVALID_PARAMS", `Invalid transfer asset "${asset}"`);
|
|
355
|
+
// A POST answered 5xx / unreadable / not answered is already ORDER_PENDING from OndoClient
|
|
356
|
+
// (never resubmit); a 4xx venue error is a definite refusal.
|
|
357
|
+
await this.authed(() => this.client.postNoResult("/v1/spot/transfers", {
|
|
358
|
+
symbol, amount, from: { wallet: from }, to: { wallet: to },
|
|
359
|
+
}));
|
|
360
|
+
return { venue: "ondo", asset: symbol, amount, from, to };
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Classify a POST /v1/spot/orders failure (design §12.3). Definite: a definite-code
|
|
365
|
+
* PerpError (auth, signer, validation) or a venue error envelope on HTTP 4xx (generic
|
|
366
|
+
* EXCHANGE_ERROR → ORDER_REJECTED). RATE_LIMITED is kept (rejected before processing).
|
|
367
|
+
* Unknown → ORDER_PENDING: network failure, HTTP 5xx, a non-JSON / result-less body,
|
|
368
|
+
* or any non-PerpError (e.g. the body read failing after the request went out).
|
|
369
|
+
*/
|
|
370
|
+
function submitFailure(e, what) {
|
|
371
|
+
if (isDefiniteOrderFailure(e))
|
|
372
|
+
return e;
|
|
373
|
+
if (e instanceof PerpError) {
|
|
374
|
+
if (e.structured.code === "RATE_LIMITED")
|
|
375
|
+
return e;
|
|
376
|
+
const d = (e.structured.details ?? {});
|
|
377
|
+
const http = typeof d.httpStatus === "number" ? d.httpStatus : null;
|
|
378
|
+
const envelope = "venueCode" in d; // ondoError(): the venue answered with a JSON error envelope
|
|
379
|
+
if (envelope && http !== null && http >= 400 && http < 500) {
|
|
380
|
+
// The venue directs this account to POST /v1/spot/orders/delayed. Not implemented:
|
|
381
|
+
// the docs give no error_code / wording for that rejection (see the file header).
|
|
382
|
+
if (/delayed/i.test(`${e.message} ${String(d.venueCode)}`)) {
|
|
383
|
+
return new PerpError("CAPABILITY_UNSUPPORTED", `Ondo spot ${what} was rejected: the venue requires the delayed placement path (${e.message})`, {
|
|
384
|
+
exchange: "ondo", httpStatus: http, venueCode: d.venueCode,
|
|
385
|
+
remediation: "Delayed spot placement (POST /v1/spot/orders/delayed) is not implemented — nothing was placed. Place this order in the Ondo Perps web app",
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return new PerpError("ORDER_REJECTED", e.message, { exchange: "ondo", httpStatus: http, venueCode: d.venueCode });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return orderOutcomeUnknown("ondo", what, e, "Do NOT resubmit — the order may have been placed. Check: perp -e ondo trade spot-orders / spot-balance");
|
|
392
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperliquid Outcome Markets (HIP-4) — common adapter interface.
|
|
3
|
+
*
|
|
4
|
+
* Outcome markets are fully-collateralized binary/range contracts that settle
|
|
5
|
+
* within a fixed range. Asset id encoding: `100,000,000 + (10 * outcome + side)`.
|
|
6
|
+
*
|
|
7
|
+
* Facts:
|
|
8
|
+
* - Quote token: PER OUTCOME — `outcomeMeta.outcomes[].quoteToken` (live 2026-10-12: every listed
|
|
9
|
+
* outcome "USDC"; docs info-endpoint/spot settledOutcome spec: `"quoteToken": "USDC"`). Never assumed.
|
|
10
|
+
* - Min order value: 10 quote tokens (docs error-responses MinTradeSpotNtl: "Order must have minimum
|
|
11
|
+
* value of 10 {quote_token}.")
|
|
12
|
+
* - No leverage / no liquidation
|
|
13
|
+
* - Order/cancel action shape identical to spot
|
|
14
|
+
* - Coin name: `#<encoding>` (l2Book/candle/allMids), `+<encoding>` (spot balance)
|
|
15
|
+
*/
|
|
16
|
+
export interface OutcomeSideInfo {
|
|
17
|
+
/** Side index within the outcome (0, 1, ...) */
|
|
18
|
+
side: number;
|
|
19
|
+
/** Human name (e.g., "Yes", "No") */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Encoding = 10 * outcome + side */
|
|
22
|
+
encoding: number;
|
|
23
|
+
/** Asset id = 100,000,000 + encoding */
|
|
24
|
+
assetId: number;
|
|
25
|
+
/** Last known mid price (quote token per share, e.g., "0.583") — undefined if unloaded */
|
|
26
|
+
mid?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface OutcomeMarketInfo {
|
|
29
|
+
/** Outcome id from `outcomeMeta.outcomes[].outcome` */
|
|
30
|
+
outcome: number;
|
|
31
|
+
/** Quote token of this outcome (outcomeMeta `quoteToken`, e.g. "USDC"). */
|
|
32
|
+
quoteToken: string;
|
|
33
|
+
/** Display name (e.g., "Recurring") */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Raw description string (e.g., "class:priceBinary|underlying:BTC|expiry:20260504-0600|targetPrice:78213|period:1d") */
|
|
36
|
+
description: string;
|
|
37
|
+
/** Parsed: class (e.g., "priceBinary") */
|
|
38
|
+
class?: string;
|
|
39
|
+
/** Parsed: underlying asset (e.g., "BTC") */
|
|
40
|
+
underlying?: string;
|
|
41
|
+
/** Parsed: expiry as ms-epoch */
|
|
42
|
+
expiryMs?: number;
|
|
43
|
+
/** Parsed: target price (USD) */
|
|
44
|
+
targetPrice?: number;
|
|
45
|
+
/** Parsed: settlement period (e.g., "1d") */
|
|
46
|
+
period?: string;
|
|
47
|
+
/** All sides for this outcome */
|
|
48
|
+
sides: OutcomeSideInfo[];
|
|
49
|
+
}
|
|
50
|
+
export interface OutcomePosition {
|
|
51
|
+
outcome: number;
|
|
52
|
+
side: number;
|
|
53
|
+
/** Side display name (resolved from outcomeMeta) */
|
|
54
|
+
sideName: string;
|
|
55
|
+
/** 10 * outcome + side */
|
|
56
|
+
encoding: number;
|
|
57
|
+
/** Shares held (from spotClearinghouseState `total`) */
|
|
58
|
+
size: string;
|
|
59
|
+
/** Cumulative quote token paid in (from `entryNtl`) */
|
|
60
|
+
entryNotional: string;
|
|
61
|
+
/** Last known mid price — undefined if unloaded */
|
|
62
|
+
markPrice?: string;
|
|
63
|
+
/** mark * size - entryNotional, or undefined if mark missing */
|
|
64
|
+
unrealizedPnl?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface OutcomeOrderbook {
|
|
67
|
+
outcome: number;
|
|
68
|
+
side: number;
|
|
69
|
+
/** [px, sz] tuples sorted highest-first */
|
|
70
|
+
bids: [string, string][];
|
|
71
|
+
/** [px, sz] tuples sorted lowest-first */
|
|
72
|
+
asks: [string, string][];
|
|
73
|
+
/** Server time-ms when book was captured */
|
|
74
|
+
time: number;
|
|
75
|
+
}
|
|
76
|
+
export interface OutcomeViewSide extends OutcomeSideInfo {
|
|
77
|
+
bids: [string, string][];
|
|
78
|
+
asks: [string, string][];
|
|
79
|
+
bestBid?: string;
|
|
80
|
+
bestAsk?: string;
|
|
81
|
+
/** Implied probability of THIS side winning, derived from mid */
|
|
82
|
+
impliedProb?: number;
|
|
83
|
+
}
|
|
84
|
+
export interface OutcomeViewUnderlying {
|
|
85
|
+
/** Underlying symbol from description (e.g. "BTC") */
|
|
86
|
+
symbol: string;
|
|
87
|
+
/** Source perp symbol used to fetch mark (typically same as `symbol`, may include venue prefix) */
|
|
88
|
+
source: string;
|
|
89
|
+
markPrice?: string;
|
|
90
|
+
targetPrice?: number;
|
|
91
|
+
/** markPrice - targetPrice (USD) */
|
|
92
|
+
gap?: number;
|
|
93
|
+
/** (markPrice - targetPrice) / targetPrice * 100 */
|
|
94
|
+
gapPct?: number;
|
|
95
|
+
/** If markPrice were to settle now: which side is winning ("yes"|"no") or null when ambiguous */
|
|
96
|
+
inTheMoney?: "yes" | "no" | null;
|
|
97
|
+
}
|
|
98
|
+
export interface OutcomeView {
|
|
99
|
+
outcome: number;
|
|
100
|
+
name: string;
|
|
101
|
+
description: string;
|
|
102
|
+
class?: string;
|
|
103
|
+
expiryMs?: number;
|
|
104
|
+
/** ms until expiry; negative if already expired; undefined if expiry unknown */
|
|
105
|
+
msToExpiry?: number;
|
|
106
|
+
period?: string;
|
|
107
|
+
underlying: OutcomeViewUnderlying | null;
|
|
108
|
+
sides: OutcomeViewSide[];
|
|
109
|
+
/** Sum of side mids; ~1.0 for fair binary, deviation hints at arbitrage. */
|
|
110
|
+
midSum?: number;
|
|
111
|
+
/** Server time-ms when the view was assembled */
|
|
112
|
+
serverTime: number;
|
|
113
|
+
}
|
|
114
|
+
export interface OutcomeAdapter {
|
|
115
|
+
readonly name: string;
|
|
116
|
+
init(): Promise<void>;
|
|
117
|
+
/** List active outcome markets. Empty array means none live. */
|
|
118
|
+
getMarkets(): Promise<OutcomeMarketInfo[]>;
|
|
119
|
+
/** Positions held by the active account. */
|
|
120
|
+
getPositions(): Promise<OutcomePosition[]>;
|
|
121
|
+
/** L2 orderbook for one (outcome, side). */
|
|
122
|
+
getOrderbook(outcome: number, side: number): Promise<OutcomeOrderbook>;
|
|
123
|
+
/** Combined view: all sides' books in parallel + underlying mark price gap + expiry. */
|
|
124
|
+
getView(outcome: number, depth?: number): Promise<OutcomeView>;
|
|
125
|
+
/** The outcome's quote token (outcomeMeta `quoteToken`); unknown outcome / missing → throws. */
|
|
126
|
+
quoteTokenOf(outcome: number): string;
|
|
127
|
+
/** Place a limit order. Throws INVALID_PARAMS if `price * size < 10` quote tokens. */
|
|
128
|
+
placeOrder(opts: {
|
|
129
|
+
outcome: number;
|
|
130
|
+
side: number;
|
|
131
|
+
isBuy: boolean;
|
|
132
|
+
price: string;
|
|
133
|
+
size: string;
|
|
134
|
+
tif?: "Gtc" | "Ioc" | "Alo";
|
|
135
|
+
}): Promise<unknown>;
|
|
136
|
+
/** Cancel a resting order. */
|
|
137
|
+
cancelOrder(outcome: number, side: number, oid: number): Promise<unknown>;
|
|
138
|
+
/** All open outcome orders for the active account. */
|
|
139
|
+
getOpenOrders(): Promise<unknown[]>;
|
|
140
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperliquid Outcome Markets (HIP-4) — common adapter interface.
|
|
3
|
+
*
|
|
4
|
+
* Outcome markets are fully-collateralized binary/range contracts that settle
|
|
5
|
+
* within a fixed range. Asset id encoding: `100,000,000 + (10 * outcome + side)`.
|
|
6
|
+
*
|
|
7
|
+
* Facts:
|
|
8
|
+
* - Quote token: PER OUTCOME — `outcomeMeta.outcomes[].quoteToken` (live 2026-10-12: every listed
|
|
9
|
+
* outcome "USDC"; docs info-endpoint/spot settledOutcome spec: `"quoteToken": "USDC"`). Never assumed.
|
|
10
|
+
* - Min order value: 10 quote tokens (docs error-responses MinTradeSpotNtl: "Order must have minimum
|
|
11
|
+
* value of 10 {quote_token}.")
|
|
12
|
+
* - No leverage / no liquidation
|
|
13
|
+
* - Order/cancel action shape identical to spot
|
|
14
|
+
* - Coin name: `#<encoding>` (l2Book/candle/allMids), `+<encoding>` (spot balance)
|
|
15
|
+
*/
|
|
16
|
+
export {};
|