perp-cli 0.14.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +138 -405
- package/README.zh.md +243 -0
- package/dist/agent-wallet/store.d.ts +3 -2
- package/dist/agent-wallet/store.js +2 -2
- package/dist/api/public/hyperliquid.js +4 -0
- package/dist/api/public/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +85 -15
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- package/dist/bot/apex/pulse.js +3 -0
- package/dist/bot/apex/radar.js +2 -1
- package/dist/bot/engine.d.ts +10 -1
- package/dist/bot/engine.js +14 -6
- package/dist/bot/indicators.d.ts +7 -0
- package/dist/bot/indicators.js +16 -0
- package/dist/bot/perp-order.d.ts +4 -4
- package/dist/bot/perp-order.js +5 -5
- package/dist/bot/quarantine.d.ts +1 -1
- package/dist/bot/quarantine.js +1 -1
- package/dist/bot/quote-rules.js +1 -1
- package/dist/bot/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +12 -5
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
- package/dist/bot/strategies/funding-arb-utils.js +53 -20
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
- package/dist/bot/strategies/liquidation-mm.js +8 -2
- package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
- package/dist/bot/strategies/momentum-breakout.js +11 -3
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +3 -3
- package/dist/bot/strategy-types.d.ts +2 -1
- package/dist/bot/tui/BotDashboard.d.ts +1 -1
- package/dist/bot/tui/BotDashboard.js +1 -1
- package/dist/cache.js +12 -6
- package/dist/cli-program.d.ts +15 -0
- package/dist/cli-program.js +126 -6
- package/dist/commands/account.js +168 -24
- package/dist/commands/agent-import.d.ts +7 -0
- package/dist/commands/agent-import.js +30 -1
- package/dist/commands/agent.d.ts +6 -3
- package/dist/commands/agent.js +160 -12
- package/dist/commands/arb/index.js +23 -81
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +175 -97
- package/dist/commands/arb.d.ts +20 -0
- package/dist/commands/arb.js +107 -163
- package/dist/commands/backtest.js +23 -21
- package/dist/commands/bot.js +1 -1
- package/dist/commands/builder.js +1 -1
- package/dist/commands/funds.js +113 -3
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +27 -7
- package/dist/commands/manage.js +23 -21
- package/dist/commands/market.d.ts +48 -0
- package/dist/commands/market.js +395 -100
- package/dist/commands/options.d.ts +14 -0
- package/dist/commands/options.js +151 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +455 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +83 -55
- package/dist/commands/rebalance.d.ts +1 -0
- package/dist/commands/rebalance.js +15 -0
- package/dist/commands/risk.d.ts +11 -2
- package/dist/commands/risk.js +39 -44
- package/dist/commands/trade.d.ts +1 -0
- package/dist/commands/trade.js +67 -62
- package/dist/commands/wallet.js +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +15 -4
- package/dist/constants.js +6 -0
- package/dist/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +3 -3
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +29 -1
- package/dist/errors.js +35 -3
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +16 -12
- package/dist/exchanges/arcus/adapter.js +45 -18
- package/dist/exchanges/arcus/agent.d.ts +1 -1
- package/dist/exchanges/arcus/agent.js +1 -1
- package/dist/exchanges/arcus/client.js +1 -1
- package/dist/exchanges/arcus/public.js +3 -2
- package/dist/exchanges/arcus/signing.d.ts +2 -3
- package/dist/exchanges/arcus/signing.js +1 -1
- package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
- package/dist/exchanges/arcus/spot-chain.js +1 -1
- package/dist/exchanges/arcus/spot-router.d.ts +1 -1
- package/dist/exchanges/arcus/spot-router.js +2 -2
- package/dist/exchanges/arcus/spot.d.ts +2 -3
- package/dist/exchanges/arcus/spot.js +2 -3
- package/dist/exchanges/aster-spot.d.ts +3 -3
- package/dist/exchanges/aster-spot.js +6 -9
- package/dist/exchanges/aster-typed-data.d.ts +1 -1
- package/dist/exchanges/aster-typed-data.js +1 -1
- package/dist/exchanges/aster.d.ts +30 -10
- package/dist/exchanges/aster.js +153 -53
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +8 -2
- package/dist/exchanges/canonical-symbols.js +46 -2
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +73 -0
- package/dist/exchanges/derive/options.js +379 -0
- package/dist/exchanges/extended/adapter.d.ts +96 -0
- package/dist/exchanges/extended/adapter.js +241 -0
- package/dist/exchanges/extended/client.d.ts +38 -0
- package/dist/exchanges/extended/client.js +88 -0
- package/dist/exchanges/extended/public.d.ts +133 -0
- package/dist/exchanges/extended/public.js +436 -0
- package/dist/exchanges/grvt/adapter.d.ts +100 -0
- package/dist/exchanges/grvt/adapter.js +283 -0
- package/dist/exchanges/grvt/client.d.ts +25 -0
- package/dist/exchanges/grvt/client.js +86 -0
- package/dist/exchanges/grvt/public.d.ts +151 -0
- package/dist/exchanges/grvt/public.js +438 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
- package/dist/exchanges/hyperliquid-spot.js +24 -16
- package/dist/exchanges/hyperliquid.d.ts +199 -22
- package/dist/exchanges/hyperliquid.js +550 -202
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +157 -13
- package/dist/exchanges/interface.js +47 -0
- package/dist/exchanges/ioc-outcome.d.ts +1 -1
- package/dist/exchanges/ioc-outcome.js +1 -1
- package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
- package/dist/exchanges/legacy-trading-v2.js +7 -2
- package/dist/exchanges/lighter-public.d.ts +67 -12
- package/dist/exchanges/lighter-public.js +250 -82
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +10 -5
- package/dist/exchanges/lighter-spot.js +105 -97
- package/dist/exchanges/lighter.d.ts +80 -21
- package/dist/exchanges/lighter.js +312 -105
- package/dist/exchanges/ondo/adapter.d.ts +38 -13
- package/dist/exchanges/ondo/adapter.js +86 -27
- package/dist/exchanges/ondo/agent.d.ts +1 -1
- package/dist/exchanges/ondo/agent.js +1 -1
- package/dist/exchanges/ondo/client.d.ts +1 -1
- package/dist/exchanges/ondo/client.js +2 -2
- package/dist/exchanges/ondo/public.d.ts +35 -3
- package/dist/exchanges/ondo/public.js +75 -32
- package/dist/exchanges/ondo/spot.d.ts +2 -2
- package/dist/exchanges/ondo/spot.js +3 -3
- package/dist/exchanges/options-interface.d.ts +158 -0
- package/dist/exchanges/options-interface.js +17 -0
- package/dist/exchanges/options-venues.d.ts +12 -0
- package/dist/exchanges/options-venues.js +35 -0
- package/dist/exchanges/outcome-interface.d.ts +4 -0
- package/dist/exchanges/pacifica-atomic.d.ts +211 -0
- package/dist/exchanges/pacifica-atomic.js +440 -0
- package/dist/exchanges/pacifica-decimal.d.ts +10 -0
- package/dist/exchanges/pacifica-decimal.js +51 -0
- package/dist/exchanges/pacifica-presigned.d.ts +53 -0
- package/dist/exchanges/pacifica-presigned.js +141 -0
- package/dist/exchanges/pacifica-print.d.ts +200 -0
- package/dist/exchanges/pacifica-print.js +417 -0
- package/dist/exchanges/pacifica-spot.d.ts +8 -2
- package/dist/exchanges/pacifica-spot.js +26 -18
- package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
- package/dist/exchanges/pacifica-typed-data.js +1 -1
- package/dist/exchanges/pacifica-vaults.d.ts +130 -0
- package/dist/exchanges/pacifica-vaults.js +277 -0
- package/dist/exchanges/pacifica.d.ts +108 -10
- package/dist/exchanges/pacifica.js +428 -171
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +69 -13
- package/dist/exchanges/qfex/adapter.js +102 -17
- package/dist/exchanges/qfex/agent.d.ts +1 -1
- package/dist/exchanges/qfex/agent.js +1 -1
- package/dist/exchanges/qfex/client.d.ts +1 -1
- package/dist/exchanges/qfex/client.js +1 -1
- package/dist/exchanges/qfex/errors.d.ts +1 -1
- package/dist/exchanges/qfex/errors.js +2 -2
- package/dist/exchanges/qfex/public.d.ts +65 -12
- package/dist/exchanges/qfex/public.js +131 -25
- package/dist/exchanges/qfex/signing.d.ts +2 -2
- package/dist/exchanges/qfex/signing.js +4 -5
- package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
- package/dist/exchanges/qfex/ws-transport.js +3 -3
- package/dist/exchanges/rate-limiter.d.ts +1 -1
- package/dist/exchanges/rate-limiter.js +5 -2
- package/dist/exchanges/registry.d.ts +1 -1
- package/dist/exchanges/registry.js +1 -1
- package/dist/exchanges/risex/adapter.d.ts +160 -0
- package/dist/exchanges/risex/adapter.js +842 -0
- package/dist/exchanges/risex/agent.d.ts +154 -0
- package/dist/exchanges/risex/agent.js +384 -0
- package/dist/exchanges/risex/client.d.ts +47 -0
- package/dist/exchanges/risex/client.js +174 -0
- package/dist/exchanges/risex/nonce.d.ts +43 -0
- package/dist/exchanges/risex/nonce.js +212 -0
- package/dist/exchanges/risex/public.d.ts +64 -0
- package/dist/exchanges/risex/public.js +150 -0
- package/dist/exchanges/risex/session.d.ts +71 -0
- package/dist/exchanges/risex/session.js +171 -0
- package/dist/exchanges/risex/typed-data.d.ts +173 -0
- package/dist/exchanges/risex/typed-data.js +240 -0
- package/dist/exchanges/risex/units.d.ts +29 -0
- package/dist/exchanges/risex/units.js +104 -0
- package/dist/exchanges/slippage.d.ts +1 -1
- package/dist/exchanges/slippage.js +1 -1
- package/dist/exchanges/spot-factory.d.ts +3 -3
- package/dist/exchanges/spot-factory.js +8 -8
- package/dist/exchanges/spot-interface.d.ts +5 -5
- package/dist/exchanges/spot-interface.js +2 -2
- package/dist/exchanges/spot-result.d.ts +3 -3
- package/dist/exchanges/spot-result.js +3 -3
- package/dist/exchanges/trading-v2.d.ts +3 -3
- package/dist/exchanges/trading-v2.js +9 -6
- package/dist/exchanges/txflow/adapter.d.ts +61 -0
- package/dist/exchanges/txflow/adapter.js +150 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +155 -0
- package/dist/exchanges/txflow/public.d.ts +87 -0
- package/dist/exchanges/txflow/public.js +355 -0
- package/dist/exchanges/unknown-symbol.d.ts +34 -0
- package/dist/exchanges/unknown-symbol.js +56 -0
- package/dist/exchanges/variational/adapter.d.ts +67 -0
- package/dist/exchanges/variational/adapter.js +137 -0
- package/dist/exchanges/variational/client.d.ts +21 -0
- package/dist/exchanges/variational/client.js +66 -0
- package/dist/exchanges/variational/public.d.ts +126 -0
- package/dist/exchanges/variational/public.js +430 -0
- package/dist/exchanges/venue-types.d.ts +52 -10
- package/dist/exchanges/venue-types.js +82 -11
- package/dist/exchanges/venue.d.ts +150 -11
- package/dist/exchanges/venue.js +126 -4
- package/dist/exchanges/venues/arcus.d.ts +1 -2
- package/dist/exchanges/venues/arcus.js +5 -4
- package/dist/exchanges/venues/aster.d.ts +2 -2
- package/dist/exchanges/venues/aster.js +6 -4
- package/dist/exchanges/venues/create-helpers.d.ts +1 -1
- package/dist/exchanges/venues/create-helpers.js +3 -3
- package/dist/exchanges/venues/extended.d.ts +21 -0
- package/dist/exchanges/venues/extended.js +96 -0
- package/dist/exchanges/venues/grvt.d.ts +30 -0
- package/dist/exchanges/venues/grvt.js +98 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
- package/dist/exchanges/venues/hyperliquid.js +19 -8
- package/dist/exchanges/venues/index.d.ts +7 -2
- package/dist/exchanges/venues/index.js +8 -2
- package/dist/exchanges/venues/lighter-family.d.ts +7 -9
- package/dist/exchanges/venues/lighter-family.js +9 -11
- package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
- package/dist/exchanges/venues/lighter-rh.js +20 -8
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +18 -6
- package/dist/exchanges/venues/ondo.d.ts +1 -1
- package/dist/exchanges/venues/ondo.js +5 -3
- package/dist/exchanges/venues/pacifica.d.ts +2 -2
- package/dist/exchanges/venues/pacifica.js +15 -4
- package/dist/exchanges/venues/qfex.d.ts +1 -1
- package/dist/exchanges/venues/qfex.js +4 -2
- package/dist/exchanges/venues/risex-release.d.ts +14 -0
- package/dist/exchanges/venues/risex-release.js +14 -0
- package/dist/exchanges/venues/risex.d.ts +31 -0
- package/dist/exchanges/venues/risex.js +131 -0
- package/dist/exchanges/venues/txflow.d.ts +18 -0
- package/dist/exchanges/venues/txflow.js +77 -0
- package/dist/exchanges/venues/variational.d.ts +21 -0
- package/dist/exchanges/venues/variational.js +85 -0
- package/dist/execution-log.d.ts +1 -1
- package/dist/funding/legacy-public.d.ts +6 -4
- package/dist/funding/legacy-public.js +42 -41
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +95 -6
- package/dist/funding/rates.js +99 -19
- package/dist/index.js +7 -6
- package/dist/jobs.d.ts +1 -0
- package/dist/jobs.js +25 -1
- package/dist/landing.d.ts +2 -0
- package/dist/landing.js +1 -1
- package/dist/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +64 -0
- package/dist/mcp-server.js +211 -68
- package/dist/pacifica/client.d.ts +69 -5
- package/dist/pacifica/client.js +181 -8
- package/dist/pacifica/rate-limit.d.ts +144 -0
- package/dist/pacifica/rate-limit.js +267 -0
- package/dist/pacifica/types/order.d.ts +2 -1
- package/dist/plan-executor.js +7 -2
- package/dist/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- package/dist/rebalance.js +1 -1
- package/dist/risk.d.ts +2 -1
- package/dist/risk.js +2 -4
- package/dist/settings.d.ts +32 -3
- package/dist/signer/ows-evm.d.ts +1 -1
- package/dist/signer/ows-evm.js +1 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/smart-order.js +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +36 -63
- package/dist/trade-validator.js +2 -2
- package/dist/utils.d.ts +22 -3
- package/dist/utils.js +11 -3
- package/dist/venue-health.d.ts +40 -0
- package/dist/venue-health.js +75 -0
- package/dist/withdraw-gate.d.ts +1 -1
- package/dist/withdraw-gate.js +1 -1
- package/dist/ws/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +980 -0
- package/dist/ws/index.js +4460 -0
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +996 -0
- package/dist/ws/node.js +4554 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +71 -10
- package/skills/perp-cli/SKILL.md +153 -36
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +22 -12
- package/skills/perp-cli/references/commands.md +143 -17
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +523 -0
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- package/dist/cli-spec.js +0 -81
package/dist/mcp-server.js
CHANGED
|
@@ -23,8 +23,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
23
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
24
|
import { z } from "zod";
|
|
25
25
|
import { hasCredentials, listConfiguredAccountVenues, resolveCredentials, NO_CLI_CREDENTIAL_OPTS } from "./config.js";
|
|
26
|
-
import { getVenue, listVenues, listVenuesWithRole, networkConfig, READ_ONLY_CREDENTIALS, requireCapabilities } from "./exchanges/venue.js";
|
|
26
|
+
import { getVenue, listPublicBalanceVenues, listPublicPositionVenues, listVenues, listVenuesWithRole, networkConfig, READ_ONLY_CREDENTIALS, requireCapabilities, roleUnsupported } from "./exchanges/venue.js";
|
|
27
27
|
import "./exchanges/venues/index.js";
|
|
28
|
+
import { venueMarketRow } from "./exchanges/venue-types.js";
|
|
29
|
+
import { lighterFundingStatsWarning } from "./exchanges/lighter-public.js";
|
|
28
30
|
import { newVenueClientOrderId, placeAndConfirm, validateLegacyTif, validateOrderRequest } from "./exchanges/trading-v2.js";
|
|
29
31
|
import { closePerpVerified, isOutcomeUnknown } from "./bot/perp-order.js";
|
|
30
32
|
import { quarantineSymbol } from "./bot/quarantine.js";
|
|
@@ -32,8 +34,11 @@ import { fillSizeString, sizeDecimalsOf } from "./exchanges/spot-result.js";
|
|
|
32
34
|
import { tradingV2Of } from "./exchanges/legacy-trading-v2.js";
|
|
33
35
|
import { PerpError, classifyError, extractErrorMessage, isDefiniteOrderFailure, orderOutcomeUnknown, parsePositiveDecimal } from "./errors.js";
|
|
34
36
|
import { invalidateCache } from "./cache.js";
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
+
import { probeVenueHealth } from "./venue-health.js";
|
|
38
|
+
import { getOptionsVenue, listOptionsVenueIds } from "./exchanges/options-venues.js";
|
|
39
|
+
import { fetchAllFundingRates, isRankedComparison } from "./funding-rates.js";
|
|
40
|
+
import { readPositionsByAddress } from "./positions-by-address.js";
|
|
41
|
+
import { readBalanceByAddress, readBalanceByAddressAll } from "./balance-by-address.js";
|
|
37
42
|
import { loadArbState } from "./arb/state.js";
|
|
38
43
|
import { validateTrade } from "./trade-validator.js";
|
|
39
44
|
import { orderResultJson, symbolMatch } from "./utils.js";
|
|
@@ -41,6 +46,7 @@ import { logMcpExecution, mcpPreTradeGate, mcpReduceOnlyGate, resolveMcpNetwork
|
|
|
41
46
|
import { setSharedApiNetwork } from "./api/public/urls.js";
|
|
42
47
|
import { logClientId } from "./client-id-tracker.js";
|
|
43
48
|
import { loadSettings } from "./settings.js";
|
|
49
|
+
import { registerPacificaEarnMcpTools } from "./mcp-pacifica-earn.js";
|
|
44
50
|
/**
|
|
45
51
|
* The one network this server serves: PERP_NETWORK, validated like `-n` (an invalid value
|
|
46
52
|
* makes the server refuse to start). Unset = mainnet, the documented default. Reported in
|
|
@@ -52,8 +58,8 @@ setQuarantineNetwork(MCP_NETWORK);
|
|
|
52
58
|
setSharedApiNetwork(MCP_NETWORK);
|
|
53
59
|
// ── Adapter cache & factory ──
|
|
54
60
|
//
|
|
55
|
-
// Same creation path and signer tiers as the CLI (agent → OWS master → PK
|
|
56
|
-
//
|
|
61
|
+
// Same creation path and signer tiers as the CLI (agent → OWS master → PK).
|
|
62
|
+
// The MCP server has no CLI flags, so credentials come
|
|
57
63
|
// from the registered agent, settings.owsActiveWallet, and (legacy venues) env.
|
|
58
64
|
const adapters = new Map();
|
|
59
65
|
/** The network each adapter was created on (TradingV2 wrappers must use the same one). */
|
|
@@ -71,6 +77,9 @@ async function getOrCreateAdapter(exchange) {
|
|
|
71
77
|
const cached = adapters.get(def.id);
|
|
72
78
|
if (cached)
|
|
73
79
|
return cached;
|
|
80
|
+
// Market-data-only venue (TxFlow: unofficial read-only API): every account / trading tool refuses here.
|
|
81
|
+
if (!def.roles.includes("account"))
|
|
82
|
+
throw roleUnsupported(def, "account");
|
|
74
83
|
const create = requireCreate(def);
|
|
75
84
|
if (!(await hasCredentials(def.id, NO_CLI_CREDENTIAL_OPTS))) {
|
|
76
85
|
throw new PerpError("NO_SIGNER_AVAILABLE", `No credentials configured for ${def.id}`, {
|
|
@@ -118,9 +127,58 @@ async function tryGetAdapter(exchange) {
|
|
|
118
127
|
throw e;
|
|
119
128
|
}
|
|
120
129
|
}
|
|
121
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* Fetch markets using the venue's public data path (no key needed). Unofficial-API rows carry
|
|
132
|
+
* venueApi; rows of a venue whose funding is unverified (QFEX, GRVT, TxFlow) carry fundingVerified false.
|
|
133
|
+
*/
|
|
122
134
|
async function fetchMarketsPublic(exchange) {
|
|
123
|
-
return getVenue(exchange).publicData.fetchMarkets(MCP_NETWORK);
|
|
135
|
+
return labelMarketRows(exchange, await getVenue(exchange).publicData.fetchMarkets(MCP_NETWORK));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* meta.warnings when a venue's rows carry fundingRate null for a known reason (Lighter: the
|
|
139
|
+
* settled-rate snapshot could not be read) — the reason reaches the agent, not only stderr.
|
|
140
|
+
*/
|
|
141
|
+
function fundingWarningMeta(exchange, rows) {
|
|
142
|
+
const id = getVenue(exchange).id;
|
|
143
|
+
if (id !== "lighter" && id !== "lighter-rh")
|
|
144
|
+
return {};
|
|
145
|
+
// Tied to the rows served: only when they actually carry a null rate (cached rows that have
|
|
146
|
+
// rates never get a stale warning; null rows always say why when the reason is known).
|
|
147
|
+
if (!rows.some(r => r.fundingRate === null))
|
|
148
|
+
return {};
|
|
149
|
+
const w = lighterFundingStatsWarning(id, MCP_NETWORK);
|
|
150
|
+
return w === null ? {} : { warnings: [w] };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Keyless get_markets rows: the public directory's VenueMarket rows in the CLI market-row shape
|
|
154
|
+
* (venueMarketRow — the mapper the VenueMarket CLI adapters use), the VenueMarket fields kept
|
|
155
|
+
* (venueSymbol, canonical, assetClass, fundingRateBasis, tickSize, …) and the MCP labels added.
|
|
156
|
+
*/
|
|
157
|
+
async function fetchMarketRowsPublic(exchange) {
|
|
158
|
+
const markets = await getVenue(exchange).publicData.fetchMarkets(MCP_NETWORK);
|
|
159
|
+
return labelMarketRows(exchange, markets.map(m => ({ ...m, ...venueMarketRow(m) })));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The one row label for market rows of any source (public data path or a credentialed adapter):
|
|
163
|
+
* every row carries fundingVerified (true or false — as `perp market list`), unofficial-API rows venueApi.
|
|
164
|
+
*/
|
|
165
|
+
function labelMarketRows(exchange, rows) {
|
|
166
|
+
const def = getVenue(exchange);
|
|
167
|
+
const labelled = rows.map(r => ({ ...r, fundingVerified: def.fundingVerified }));
|
|
168
|
+
return def.apiStatus.kind === "unofficial" ? labelled.map(r => ({ ...r, venueApi: "unofficial" })) : labelled;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* meta fields every market-data tool adds for its venue: apiStatus, the note for an unofficial API,
|
|
172
|
+
* and fundingVerified false (with a note) when the venue's funding rate is unverified.
|
|
173
|
+
*/
|
|
174
|
+
function apiMeta(exchange) {
|
|
175
|
+
const def = getVenue(exchange);
|
|
176
|
+
if (def.apiStatus.kind === "unofficial") {
|
|
177
|
+
return { apiStatus: "unofficial", apiNote: `${def.displayName}: UNOFFICIAL API — ${def.apiStatus.note}`, fundingVerified: def.fundingVerified };
|
|
178
|
+
}
|
|
179
|
+
return def.fundingVerified
|
|
180
|
+
? { apiStatus: "official" }
|
|
181
|
+
: { apiStatus: "official", fundingVerified: false, fundingNote: `${def.displayName}: funding rate UNVERIFIED (not confirmed against settled payments) — display only, never a basis for trading` };
|
|
124
182
|
}
|
|
125
183
|
/** One book level from a venue payload; a level without a string price / size is a payload error (never "0"). */
|
|
126
184
|
function bookLevel(venue, symbol, price, size) {
|
|
@@ -219,6 +277,8 @@ export const MCP_WITHDRAW_VENUES = ["pacifica", "hyperliquid", "lighter", "aster
|
|
|
219
277
|
const _require = createRequire(import.meta.url);
|
|
220
278
|
const _pkg = _require("../package.json");
|
|
221
279
|
const server = new McpServer({ name: "perp-cli", version: _pkg.version }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
280
|
+
// Pacifica Print + vaults: read-only tools (src/mcp-pacifica-earn.ts).
|
|
281
|
+
registerPacificaEarnMcpTools(server, MCP_NETWORK, ok, errOf);
|
|
222
282
|
// ============================================================
|
|
223
283
|
// Market Data tools (read-only, no private key needed)
|
|
224
284
|
// ============================================================
|
|
@@ -227,11 +287,14 @@ server.tool("get_markets", `Get all available perpetual futures markets on an ex
|
|
|
227
287
|
// Try adapter first (richer data), fall back to public API (no key needed)
|
|
228
288
|
const adapter = await tryGetAdapter(exchange);
|
|
229
289
|
if (adapter) {
|
|
230
|
-
const markets = await adapter.getMarkets();
|
|
231
|
-
return { content: [{ type: "text", text: ok(markets, { exchange, count: markets.length }) }] };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
290
|
+
const markets = labelMarketRows(exchange, await adapter.getMarkets());
|
|
291
|
+
return { content: [{ type: "text", text: ok(markets, { exchange, count: markets.length, ...apiMeta(exchange), ...fundingWarningMeta(exchange, markets) }) }] };
|
|
292
|
+
}
|
|
293
|
+
// No credentials: the venue's public directory — price-only on GRVT (no 184-request
|
|
294
|
+
// margin_rules fan-out per call) — in the CLI market-row shape (venueMarketRow), with the
|
|
295
|
+
// same market set as `perp market list` (the venue's shared eligibility / listing rule).
|
|
296
|
+
const markets = await fetchMarketRowsPublic(exchange);
|
|
297
|
+
return { content: [{ type: "text", text: ok(markets, { exchange, count: markets.length, source: "public", ...apiMeta(exchange), ...fundingWarningMeta(exchange, markets) }) }] };
|
|
235
298
|
}
|
|
236
299
|
catch (e) {
|
|
237
300
|
return { content: [{ type: "text", text: errOf(e, { exchange }) }], isError: true };
|
|
@@ -246,24 +309,25 @@ server.tool("get_orderbook", `Get the order book (bids and asks) for a symbol on
|
|
|
246
309
|
const adapter = await tryGetAdapter(exchange);
|
|
247
310
|
if (adapter) {
|
|
248
311
|
const book = await adapter.getOrderbook(symbol);
|
|
249
|
-
return { content: [{ type: "text", text: ok(book, { exchange, symbol }) }] };
|
|
312
|
+
return { content: [{ type: "text", text: ok(book, { exchange, symbol, ...apiMeta(exchange) }) }] };
|
|
250
313
|
}
|
|
251
314
|
const book = await fetchOrderbookPublic(exchange, symbol);
|
|
252
|
-
return { content: [{ type: "text", text: ok(book, { exchange, symbol, source: "public" }) }] };
|
|
315
|
+
return { content: [{ type: "text", text: ok(book, { exchange, symbol, source: "public", ...apiMeta(exchange) }) }] };
|
|
253
316
|
}
|
|
254
317
|
catch (e) {
|
|
255
318
|
return { content: [{ type: "text", text: errOf(e, { exchange, symbol }) }], isError: true };
|
|
256
319
|
}
|
|
257
320
|
});
|
|
258
|
-
server.tool("get_funding_rates", "Compare funding rates across every market-data venue. Returns rates per symbol with spread analysis", {
|
|
321
|
+
server.tool("get_funding_rates", "Compare funding rates across every market-data venue whose funding rate is verified. Returns rates per symbol with spread analysis. includeUnverified also returns venues whose rate is unverified (QFEX; TxFlow — unofficial API; Variational — a predicted rate not checked against settled payments; GRVT — indicative rate; Extended — not checked against settled payments), each row tagged fundingVerified / apiStatus — display only, never a basis for trading (mirrors the CLI `arb scan --include-unverified`).", {
|
|
259
322
|
symbols: z
|
|
260
323
|
.array(z.string())
|
|
261
324
|
.optional()
|
|
262
325
|
.describe("Filter to specific symbols (e.g. ['BTC','ETH']). Omit for all available"),
|
|
263
326
|
minSpread: z.number().optional().describe("Minimum annualized spread % to include (default: 0)"),
|
|
264
|
-
|
|
327
|
+
includeUnverified: z.boolean().optional().describe("Also include venues whose funding rate is unverified (default false); rows carry fundingVerified:false / apiStatus"),
|
|
328
|
+
}, async ({ symbols, minSpread, includeUnverified }) => {
|
|
265
329
|
try {
|
|
266
|
-
const snapshot = await fetchAllFundingRates({ symbols, minSpread });
|
|
330
|
+
const snapshot = await fetchAllFundingRates({ symbols, minSpread, includeUnverified: includeUnverified === true });
|
|
267
331
|
return {
|
|
268
332
|
content: [{
|
|
269
333
|
type: "text",
|
|
@@ -275,7 +339,7 @@ server.tool("get_funding_rates", "Compare funding rates across every market-data
|
|
|
275
339
|
return { content: [{ type: "text", text: errOf(e) }], isError: true };
|
|
276
340
|
}
|
|
277
341
|
});
|
|
278
|
-
server.tool("get_prices", "Get cross-exchange prices for symbols. Fetches mark prices from every market-data venue for comparison", {
|
|
342
|
+
server.tool("get_prices", "Get cross-exchange prices for symbols. Fetches mark prices from every market-data venue for comparison; a venue appears in a row only when it lists that symbol (e.g. QFEX lists no BTC). Each row's unofficialApiVenues names the venues in it read through an undocumented API (TxFlow) — like get_markets' venueApi tag.", {
|
|
279
343
|
symbols: z
|
|
280
344
|
.array(z.string())
|
|
281
345
|
.optional()
|
|
@@ -295,6 +359,10 @@ server.tool("get_prices", "Get cross-exchange prices for symbols. Fetches mark p
|
|
|
295
359
|
return;
|
|
296
360
|
}
|
|
297
361
|
for (const m of r.value) {
|
|
362
|
+
// An ambiguous ticker (SPX6900 vs the S&P 500) names different instruments per venue:
|
|
363
|
+
// never grouped by symbol across venues.
|
|
364
|
+
if (m.assetClass === "unclassified")
|
|
365
|
+
continue;
|
|
298
366
|
const sym = m.symbol.toUpperCase();
|
|
299
367
|
if (filter && !filter.has(sym))
|
|
300
368
|
continue;
|
|
@@ -303,8 +371,13 @@ server.tool("get_prices", "Get cross-exchange prices for symbols. Fetches mark p
|
|
|
303
371
|
priceMap.get(sym)[ex] = Number(m.markPrice);
|
|
304
372
|
}
|
|
305
373
|
});
|
|
306
|
-
const
|
|
307
|
-
|
|
374
|
+
const unofficial = listVenuesWithRole("marketData").filter(v => v.apiStatus.kind === "unofficial").map(v => v.id);
|
|
375
|
+
const data = Array.from(priceMap.entries()).map(([symbol, prices]) => ({
|
|
376
|
+
symbol,
|
|
377
|
+
prices,
|
|
378
|
+
unofficialApiVenues: Object.keys(prices).filter(ex => unofficial.includes(ex)),
|
|
379
|
+
}));
|
|
380
|
+
return { content: [{ type: "text", text: ok(data, { count: data.length, errors, unofficialApiVenues: unofficial }) }] };
|
|
308
381
|
}
|
|
309
382
|
catch (e) {
|
|
310
383
|
return { content: [{ type: "text", text: errOf(e) }], isError: true };
|
|
@@ -362,6 +435,44 @@ server.tool("get_outcome_book", "Get the orderbook for one outcome side. Public
|
|
|
362
435
|
}
|
|
363
436
|
});
|
|
364
437
|
// ============================================================
|
|
438
|
+
// Options market data (read-only, keyless; venues: src/exchanges/options-venues.ts)
|
|
439
|
+
// ============================================================
|
|
440
|
+
const OPTIONS_VENUE_ARG = z.string().describe(`Options venue: ${listOptionsVenueIds().join(", ")}`);
|
|
441
|
+
server.tool("get_option_expiries", "List the active option expiries of an underlying on an options venue (Derive): per expiry the UTC date (YYYY-MM-DD), expiry ms, strike and instrument counts, soonest first. Public read — no API key needed.", { venue: OPTIONS_VENUE_ARG, underlying: z.string().describe("Underlying, e.g. ETH, BTC") }, async ({ venue, underlying }) => {
|
|
442
|
+
try {
|
|
443
|
+
const data = await (await getOptionsVenue(venue, MCP_NETWORK)).listExpiries(underlying);
|
|
444
|
+
return { content: [{ type: "text", text: ok(data, { venue, count: data.expiries.length }) }] };
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
return { content: [{ type: "text", text: errOf(e, { venue, underlying }) }], isError: true };
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
server.tool("get_option_chain", "Option chain for one expiry (Derive): rows per strike with call and put quotes — bid / ask (price + size; null = empty side), mark, index, forward, IV (mark / bid / ask; null = not computable), greeks (delta, gamma, theta, vega, rho), open interest, 24h volume — plus the ATM strike. Decimals are strings. One venue request per expiry. Public read.", {
|
|
451
|
+
venue: OPTIONS_VENUE_ARG,
|
|
452
|
+
underlying: z.string().describe("Underlying, e.g. ETH, BTC"),
|
|
453
|
+
expiry: z.string().optional().describe("Expiry date YYYY-MM-DD (UTC); omit for the nearest active expiry (result expirySelection: \"nearest\")"),
|
|
454
|
+
strikes: z.number().int().min(1).optional().describe("Only the N strikes closest to the ATM reference (forward, else index); omit for all strikes"),
|
|
455
|
+
}, async ({ venue, underlying, expiry, strikes }) => {
|
|
456
|
+
try {
|
|
457
|
+
const chain = await (await getOptionsVenue(venue, MCP_NETWORK)).getChain({
|
|
458
|
+
underlying, expiry: expiry === undefined ? null : expiry, strikesAroundAtm: strikes === undefined ? null : strikes,
|
|
459
|
+
});
|
|
460
|
+
return { content: [{ type: "text", text: ok(chain, { venue, expiry: chain.expiry, rows: chain.rows.length }) }] };
|
|
461
|
+
}
|
|
462
|
+
catch (e) {
|
|
463
|
+
return { content: [{ type: "text", text: errOf(e, { venue, underlying }) }], isError: true };
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
server.tool("get_option_quote", "Quote one option instrument (Derive name, e.g. ETH-20261225-3500-P): bid / ask (price + size), mark, index, forward, IV, greeks, open interest, 24h stats. Unknown instrument → SYMBOL_NOT_FOUND. Public read.", { venue: OPTIONS_VENUE_ARG, instrument: z.string().describe("Venue instrument name, e.g. ETH-20261225-3500-P") }, async ({ venue, instrument }) => {
|
|
467
|
+
try {
|
|
468
|
+
const q = await (await getOptionsVenue(venue, MCP_NETWORK)).getQuote(instrument);
|
|
469
|
+
return { content: [{ type: "text", text: ok(q, { venue, instrument: q.instrument }) }] };
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
return { content: [{ type: "text", text: errOf(e, { venue, instrument }) }], isError: true };
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
// ============================================================
|
|
365
476
|
// Account tools (need private key)
|
|
366
477
|
// ============================================================
|
|
367
478
|
server.tool("get_outcome_positions", "Get the active account's open outcome positions (size, entry notional, mark price, unrealized PnL). Requires a configured Hyperliquid wallet or agent.", {}, async () => {
|
|
@@ -400,6 +511,49 @@ server.tool("get_positions", `Get all open positions on an exchange, including s
|
|
|
400
511
|
return { content: [{ type: "text", text: errOf(e, { exchange }) }], isError: true };
|
|
401
512
|
}
|
|
402
513
|
});
|
|
514
|
+
server.tool("get_positions_by_address", `Open positions of ANY account by its public address — keyless, read-only (public venue data; nothing is signed, no key is needed). Venues: ${listPublicPositionVenues().map(v => `${v.id} (${v.publicData.addressKind === "solana" ? "base58 Solana address" : "0x address"})`).join(", ")}. Hyperliquid reads the native dex and every HIP-3 dex (rows carry dex: null | "<dex>"; a dex that fails is listed in meta.errors / meta.warnings — its rows are missing, not absent); Lighter reads every account of the address (rows carry accountIndex; accountIndex narrows). Other venues: CAPABILITY_UNSUPPORTED; a wrong address format: INVALID_PARAMS. Same rows as \`perp -e <venue> account positions --address <addr>\`.`, {
|
|
515
|
+
exchange: z.string().describe(`Exchange: ${listPublicPositionVenues().map(v => v.id).join(", ")} (aliases accepted)`),
|
|
516
|
+
address: z.string().describe("Public account address (0x… for hyperliquid / lighter / lighter-rh; base58 Solana for pacifica)"),
|
|
517
|
+
symbol: z.string().optional().describe("Only this market (plain asset, e.g. BTC)"),
|
|
518
|
+
accountIndex: z.number().int().nonnegative().optional().describe("Lighter only: one account (sub-account) of the address"),
|
|
519
|
+
}, async ({ exchange, address, symbol, accountIndex }) => {
|
|
520
|
+
try {
|
|
521
|
+
const r = await readPositionsByAddress(exchange, MCP_NETWORK, address, { symbol, accountIndex });
|
|
522
|
+
return { content: [{ type: "text", text: ok(r.positions, {
|
|
523
|
+
exchange: r.venue, address, source: "public", count: r.positions.length,
|
|
524
|
+
...(r.warnings.length > 0 ? { warnings: r.warnings, errors: r.errors } : {}),
|
|
525
|
+
...(r.notes.length > 0 ? { notes: r.notes } : {}),
|
|
526
|
+
}) }] };
|
|
527
|
+
}
|
|
528
|
+
catch (e) {
|
|
529
|
+
return { content: [{ type: "text", text: errOf(e, { exchange }) }], isError: true };
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
server.tool("get_balance_by_address", `Balances of ANY account by its public address — keyless, read-only (public venue data; nothing is signed, no key is needed). Venues: ${listPublicBalanceVenues().map(v => `${v.id} (${v.publicData.addressKind === "solana" ? "base58 Solana address" : "0x address"})`).join(", ")}; omit exchange to read every one of them (a venue that fails, address format included, is listed in data.errors). data: { balance (equity / available / marginUsed / unrealizedPnl + venue extras; null on Lighter), spot (token balances; null where the venue does not publish them), accounts (Lighter: one row per account of the address; accountIndex narrows), dexes + accountMode (Hyperliquid: HIP-3 dexes with a non-zero account value; a dex that fails is in meta.errors / meta.warnings — unknown, not zero) }. Other venues: CAPABILITY_UNSUPPORTED; a wrong address format: INVALID_PARAMS. Same data as \`perp [-e <venue>] account balance --address <addr>\`.`, {
|
|
533
|
+
exchange: z.string().optional().describe(`Exchange: ${listPublicBalanceVenues().map(v => v.id).join(", ")} (aliases accepted); omit for every one`),
|
|
534
|
+
address: z.string().describe("Public account address (0x… for hyperliquid / lighter / lighter-rh / risex; base58 Solana for pacifica)"),
|
|
535
|
+
accountIndex: z.number().int().nonnegative().optional().describe("Lighter only: one account (sub-account) of the address"),
|
|
536
|
+
}, async ({ exchange, address, accountIndex }) => {
|
|
537
|
+
try {
|
|
538
|
+
if (exchange === undefined) {
|
|
539
|
+
const all = await readBalanceByAddressAll(MCP_NETWORK, address, { accountIndex });
|
|
540
|
+
return { content: [{ type: "text", text: ok({
|
|
541
|
+
venues: all.venues,
|
|
542
|
+
...(Object.keys(all.errors).length > 0 ? { errors: all.errors } : {}),
|
|
543
|
+
...(Object.keys(all.notes).length > 0 ? { notes: all.notes } : {}),
|
|
544
|
+
}, { address, source: "public", ...(all.warnings.length > 0 ? { warnings: all.warnings } : {}) }) }] };
|
|
545
|
+
}
|
|
546
|
+
const r = await readBalanceByAddress(exchange, MCP_NETWORK, address, { accountIndex });
|
|
547
|
+
return { content: [{ type: "text", text: ok({ exchange: r.venue, address, ...r.data }, {
|
|
548
|
+
exchange: r.venue, address, source: "public",
|
|
549
|
+
...(r.warnings.length > 0 ? { warnings: r.warnings, errors: r.errors } : {}),
|
|
550
|
+
...(r.notes.length > 0 ? { notes: r.notes } : {}),
|
|
551
|
+
}) }] };
|
|
552
|
+
}
|
|
553
|
+
catch (e) {
|
|
554
|
+
return { content: [{ type: "text", text: errOf(e, { exchange }) }], isError: true };
|
|
555
|
+
}
|
|
556
|
+
});
|
|
403
557
|
server.tool("get_open_orders", `Get all open/pending orders on an exchange. ${HL_SCOPE_NOTE}`, { exchange: z.string().describe(`Exchange name: ${listVenues().map(v => v.id).join(", ")} (aliases accepted)`) }, async ({ exchange }) => {
|
|
404
558
|
try {
|
|
405
559
|
const adapter = await getOrCreateAdapter(exchange);
|
|
@@ -433,6 +587,9 @@ server.tool("portfolio", "Cross-exchange portfolio summary: balances, positions,
|
|
|
433
587
|
};
|
|
434
588
|
}
|
|
435
589
|
}));
|
|
590
|
+
// Totals are null (unknown) when no account balance was read — no configured credentials, or
|
|
591
|
+
// every venue failed — never 0 (QA 0.15.0: totalEquity 0 without credentials).
|
|
592
|
+
const read = snapshots.filter(s => s.balance !== null);
|
|
436
593
|
let totalEquity = 0;
|
|
437
594
|
let totalAvailable = 0;
|
|
438
595
|
let totalMarginUsed = 0;
|
|
@@ -468,16 +625,21 @@ server.tool("portfolio", "Cross-exchange portfolio summary: balances, positions,
|
|
|
468
625
|
pct: totalEquity > 0 ? (Number(s.balance.equity) / totalEquity) * 100 : 0,
|
|
469
626
|
}))
|
|
470
627
|
.sort((a, b) => b.pct - a.pct);
|
|
628
|
+
const known = read.length > 0;
|
|
471
629
|
const summary = {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
630
|
+
/** "noAccount" = no venue has credentials; "unread" = credentials exist but no balance was read. */
|
|
631
|
+
accountState: EXCHANGES.length === 0 ? "noAccount" : known ? "ok" : "unread",
|
|
632
|
+
/** Venues whose balances the totals cover. */
|
|
633
|
+
totalsCover: read.map(s => s.exchange),
|
|
634
|
+
totalEquity: known ? totalEquity : null,
|
|
635
|
+
totalAvailable: known ? totalAvailable : null,
|
|
636
|
+
totalMarginUsed: known ? totalMarginUsed : null,
|
|
637
|
+
totalUnrealizedPnl: known ? totalUnrealizedPnl : null,
|
|
476
638
|
totalPositions,
|
|
477
639
|
totalOpenOrders,
|
|
478
640
|
exchanges: snapshots,
|
|
479
641
|
positions: allPositions,
|
|
480
|
-
riskMetrics: { marginUtilization, largestPosition, exchangeConcentration },
|
|
642
|
+
riskMetrics: { marginUtilization: known ? marginUtilization : null, largestPosition, exchangeConcentration },
|
|
481
643
|
};
|
|
482
644
|
return { content: [{ type: "text", text: ok(summary) }] };
|
|
483
645
|
});
|
|
@@ -1121,10 +1283,14 @@ server.tool("arb_scan", "Scan for funding rate arbitrage opportunities across ex
|
|
|
1121
1283
|
symbols,
|
|
1122
1284
|
minSpread: minSpread ?? 5,
|
|
1123
1285
|
});
|
|
1124
|
-
const opportunities = snapshot.symbols.map(s => ({
|
|
1286
|
+
const opportunities = snapshot.symbols.filter(isRankedComparison).map(s => ({
|
|
1125
1287
|
symbol: s.symbol,
|
|
1126
1288
|
maxSpreadAnnual: `${s.maxSpreadAnnual.toFixed(2)}%`,
|
|
1127
1289
|
strategy: `Long ${s.longExchange} / Short ${s.shortExchange}`,
|
|
1290
|
+
// false = a leg is on a read-only venue (RISEx in this release): data only, not tradable here.
|
|
1291
|
+
executable: s.executable,
|
|
1292
|
+
readOnlyLegs: s.readOnlyLegs,
|
|
1293
|
+
unverifiedLegs: s.unverifiedLegs,
|
|
1128
1294
|
estHourlyIncomePerK: `$${s.estHourlyIncomeUsd.toFixed(4)}`,
|
|
1129
1295
|
rates: s.rates.map(r => ({
|
|
1130
1296
|
exchange: r.exchange,
|
|
@@ -1149,44 +1315,17 @@ server.tool("arb_scan", "Scan for funding rate arbitrage opportunities across ex
|
|
|
1149
1315
|
});
|
|
1150
1316
|
server.tool("health_check", "Ping every registered venue on the server's network and return connectivity status and latency. Pacifica / Hyperliquid / Lighter / Aster / QFEX: REST ping (status = HTTP status, 0 = no response); every other venue: its public market-data read (status null). A failure carries its error message.", {}, async () => {
|
|
1151
1317
|
try {
|
|
1152
|
-
const
|
|
1153
|
-
const start = Date.now();
|
|
1154
|
-
try {
|
|
1155
|
-
const r = await fn();
|
|
1156
|
-
return { ok: r.ok, latencyMs: Date.now() - start, status: r.status, error: r.ok ? null : `HTTP ${r.status}`, probe };
|
|
1157
|
-
}
|
|
1158
|
-
catch (e) {
|
|
1159
|
-
return { ok: false, latencyMs: Date.now() - start, status: probe === "ping" ? 0 : null, error: extractErrorMessage(e), probe };
|
|
1160
|
-
}
|
|
1161
|
-
};
|
|
1162
|
-
const restPing = (url) => timed("ping", async () => {
|
|
1163
|
-
const res = await fetch(url);
|
|
1164
|
-
return { ok: res.ok, status: res.status };
|
|
1165
|
-
});
|
|
1166
|
-
// Shared-api helpers report a transport failure as status 0 (no message).
|
|
1167
|
-
const helperPing = (fn) => async () => {
|
|
1168
|
-
const r = await fn();
|
|
1169
|
-
return { ...r, error: r.ok ? null : r.status === 0 ? "no response" : `HTTP ${r.status}`, probe: "ping" };
|
|
1170
|
-
};
|
|
1171
|
-
const pings = {
|
|
1172
|
-
pacifica: helperPing(pingPacifica),
|
|
1173
|
-
hyperliquid: helperPing(pingHyperliquid),
|
|
1174
|
-
lighter: helperPing(pingLighter),
|
|
1175
|
-
aster: () => restPing(`${networkConfig(getVenue("aster"), MCP_NETWORK).restUrl}/fapi/v1/time`),
|
|
1176
|
-
// QFEX: public GET /refdata for one instrument on the server's network.
|
|
1177
|
-
qfex: () => restPing(`${networkConfig(getVenue("qfex"), MCP_NETWORK).restUrl}/refdata?ticker=AAPL-USD`),
|
|
1178
|
-
};
|
|
1179
|
-
const venues = listVenues();
|
|
1180
|
-
const probes = await Promise.all(venues.map(v => {
|
|
1181
|
-
const ping = pings[v.id];
|
|
1182
|
-
return ping !== undefined
|
|
1183
|
-
? ping()
|
|
1184
|
-
: timed("markets", async () => { await v.publicData.fetchMarkets(MCP_NETWORK); return { ok: true, status: null }; });
|
|
1185
|
-
}));
|
|
1318
|
+
const health = await probeVenueHealth(MCP_NETWORK);
|
|
1186
1319
|
const result = {};
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
result.
|
|
1320
|
+
for (const { venue, ...p } of health.probes)
|
|
1321
|
+
result[venue] = { ...p, statusText: p.ok ? "healthy" : "unreachable" };
|
|
1322
|
+
result.covered = health.probes.map(p => p.venue);
|
|
1323
|
+
// A venue without an environment on the server's network (TxFlow has no testnet) is not probed.
|
|
1324
|
+
result.skipped = health.skipped;
|
|
1325
|
+
// Same two verdicts as `perp health --json`: healthy = every OFFICIAL-API venue is up (an
|
|
1326
|
+
// unofficial API being down does not make perp-cli unhealthy); allHealthy includes them.
|
|
1327
|
+
result.healthy = health.probes.filter(p => p.apiStatus === "official").every(p => p.ok);
|
|
1328
|
+
result.allHealthy = health.allHealthy;
|
|
1190
1329
|
return { content: [{ type: "text", text: ok(result) }] };
|
|
1191
1330
|
}
|
|
1192
1331
|
catch (e) {
|
|
@@ -1446,7 +1585,7 @@ function mcpOrderRequest(adapter, p) {
|
|
|
1446
1585
|
};
|
|
1447
1586
|
}
|
|
1448
1587
|
server.tool("trade_preview", `Preview a trade WITHOUT executing. Runs trade_execute's pre-send checks read-only (size parse, order validation incl. TIF, quarantine, local risk limits — never forced) and reports any refusal as valid:false with its code + remediation; also returns the estimated fill price from the book, notional, margin impact, the builder fee the order would carry, and the existing position. Venue trading fees are NOT estimated. ALWAYS call this before trade_execute and show the result to the user for confirmation. ${HL_SCOPE_NOTE}`, {
|
|
1449
|
-
exchange: z.string().describe(`Exchange: ${listVenues().filter(v => v.
|
|
1588
|
+
exchange: z.string().describe(`Exchange: ${listVenues().filter(v => v.roles.includes("trading")).map(v => v.id).join(", ")} (aliases accepted)`),
|
|
1450
1589
|
symbol: z.string().describe("Trading symbol (e.g., BTC, ETH, SOL)"),
|
|
1451
1590
|
side: z.enum(["buy", "sell"]).describe("Order side"),
|
|
1452
1591
|
size: z.string().describe("Order size (base currency units, e.g., '0.1' for 0.1 BTC)"),
|
|
@@ -1456,6 +1595,8 @@ server.tool("trade_preview", `Preview a trade WITHOUT executing. Runs trade_exec
|
|
|
1456
1595
|
reduceOnly: z.boolean().default(false).describe("Reduce-only order (close position only) — checked like trade_execute (quarantine applies only to non-reduce-only orders)"),
|
|
1457
1596
|
}, async ({ exchange, symbol, side, size, orderType, price, tif, reduceOnly }) => {
|
|
1458
1597
|
try {
|
|
1598
|
+
// No trading role (TxFlow, release-gated RISEx) → CAPABILITY_UNSUPPORTED before any credential.
|
|
1599
|
+
requireCapabilities(getVenue(exchange));
|
|
1459
1600
|
const adapter = await getOrCreateAdapter(exchange);
|
|
1460
1601
|
const sym = symbol.toUpperCase();
|
|
1461
1602
|
const isLimit = orderType === "limit";
|
|
@@ -1570,9 +1711,9 @@ server.tool("trade_preview", `Preview a trade WITHOUT executing. Runs trade_exec
|
|
|
1570
1711
|
});
|
|
1571
1712
|
/**
|
|
1572
1713
|
* Place one order through TradingV2 (native on ondo/arcus, legacy wrapper on the four
|
|
1573
|
-
* pre-existing venues) and return OrderResult + execution
|
|
1714
|
+
* pre-existing venues) and return OrderResult + execution. Pre-send
|
|
1574
1715
|
* validation failures stay definite; anything thrown after the request may have reached
|
|
1575
|
-
* the venue becomes ORDER_PENDING ("do NOT resubmit"
|
|
1716
|
+
* the venue becomes ORDER_PENDING ("do NOT resubmit").
|
|
1576
1717
|
*/
|
|
1577
1718
|
async function placeViaV2(adapter, req, what, remediation) {
|
|
1578
1719
|
const def = getVenue(adapter.name);
|
|
@@ -1589,7 +1730,7 @@ async function placeViaV2(adapter, req, what, remediation) {
|
|
|
1589
1730
|
}
|
|
1590
1731
|
}
|
|
1591
1732
|
server.tool("trade_execute", `Execute a REAL trade on the server's network (meta.network; PERP_NETWORK, default mainnet). IMPORTANT: Always call trade_preview first and get explicit user confirmation before calling this tool. Refused with RISK_VIOLATION when the order breaches the local risk limits (perp risk set; there is no override over MCP) or when the symbol is quarantined (only reduce-only orders pass). Every order is written to the execution log. Supports market and limit orders. Returns the uniform OrderResult (status, finality, filledSize, execution: slippage / protection price / TIF sent) with the raw venue response under \`legacy\`, plus top-level filledSize / requestedSize / partial. Outcomes: an unknown outcome (transport failure after send, or a non-final venue ack) → ok:false, code ORDER_PENDING, executed:false, pending:true and a "do NOT resubmit" remediation — the symbol is quarantined for opening orders until a human clears it; a final rejection / cancel with nothing filled → ORDER_REJECTED, executed:false; a cancel after a partial fill → executed:true, partial:true with the real filledSize. ${HL_SCOPE_NOTE}`, {
|
|
1592
|
-
exchange: z.string().describe(`Exchange: ${listVenues().filter(v => v.
|
|
1733
|
+
exchange: z.string().describe(`Exchange: ${listVenues().filter(v => v.roles.includes("trading")).map(v => v.id).join(", ")} (aliases accepted)`),
|
|
1593
1734
|
symbol: z.string().describe("Trading symbol (e.g., BTC, ETH, SOL)"),
|
|
1594
1735
|
side: z.enum(["buy", "sell"]).describe("Order side"),
|
|
1595
1736
|
size: z.string().describe("Order size (base currency units)"),
|
|
@@ -1602,6 +1743,7 @@ server.tool("trade_execute", `Execute a REAL trade on the server's network (meta
|
|
|
1602
1743
|
try {
|
|
1603
1744
|
// One parsed size for the risk gate and the order ("1,000" / "abc" refused before any read or send).
|
|
1604
1745
|
const size = parsePositiveDecimal(rawSize, "size");
|
|
1746
|
+
requireCapabilities(getVenue(exchange)); // trading role first, before any credential
|
|
1605
1747
|
const adapter = await getOrCreateAdapter(exchange);
|
|
1606
1748
|
const def = getVenue(adapter.name);
|
|
1607
1749
|
const caps = requireCapabilities(def);
|
|
@@ -1722,10 +1864,11 @@ server.tool("trade_execute", `Execute a REAL trade on the server's network (meta
|
|
|
1722
1864
|
}
|
|
1723
1865
|
});
|
|
1724
1866
|
server.tool("trade_close", `Close an existing position. IMPORTANT: Call trade_preview first with the opposite side to show impact, then get user confirmation. Verified close: \`closedSize\` is the measured / venue-final fill (one residual market close is attempted); a residual still open → ORDER_PARTIAL error (symbol quarantined). Includes the uniform OrderResult of the reduce-only market order. ${HL_SCOPE_NOTE}`, {
|
|
1725
|
-
exchange: z.string().describe(`Exchange: ${listVenues().filter(v => v.
|
|
1867
|
+
exchange: z.string().describe(`Exchange: ${listVenues().filter(v => v.roles.includes("trading")).map(v => v.id).join(", ")} (aliases accepted)`),
|
|
1726
1868
|
symbol: z.string().describe("Symbol to close (e.g., BTC, ETH)"),
|
|
1727
1869
|
}, async ({ exchange, symbol }) => {
|
|
1728
1870
|
try {
|
|
1871
|
+
requireCapabilities(getVenue(exchange)); // trading role first, before any credential
|
|
1729
1872
|
const adapter = await getOrCreateAdapter(exchange);
|
|
1730
1873
|
const def = getVenue(adapter.name);
|
|
1731
1874
|
const caps = requireCapabilities(def);
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { type Network } from "./constants.js";
|
|
2
|
-
import
|
|
2
|
+
import { requireSolanaAddress } from "../exchanges/address.js";
|
|
3
|
+
import type { MarketInfo, PriceInfo, Orderbook, Trade, Kline, KlineInterval, AggLevel, AccountInfo, AccountSettings, Position, OrderInfo, MarketOrderParams, LimitOrderParams, StopOrderParams, EditOrderParams, CancelOrderParams, CancelAllOrdersParams, TWAPParams, CancelTWAPParams, TPSLParams, UpdateLeverageParams, WithdrawParams, TransferFundsParams, BatchAction, BatchActionType, CreateLakeParams, LakeDepositParams, LakeWithdrawParams } from "./types/index.js";
|
|
3
4
|
type SignMessageFn = (message: Uint8Array) => Promise<Uint8Array>;
|
|
4
5
|
/** Per-request timeout of a POST (fetch + body read). */
|
|
5
6
|
export declare const PACIFICA_POST_TIMEOUT_MS = 15000;
|
|
7
|
+
/**
|
|
8
|
+
* Credits ×10 reserved for one POST /orders/batch. The docs (api/rate-limits "Credit Costs") price
|
|
9
|
+
* a "Standard request/action" at 1 credit and an "Order cancellation" at 0.5 and say nothing
|
|
10
|
+
* batch-specific (batch-order page: no cost); every action is individually signed and processed,
|
|
11
|
+
* so the batch is reserved per ACTION — the conservative reading (over-reserving only throttles
|
|
12
|
+
* earlier; the venue's ratelimit headers on the response settle the real count).
|
|
13
|
+
*/
|
|
14
|
+
export declare function pacificaBatchCost(types: readonly BatchActionType[]): number;
|
|
6
15
|
export interface PacificaClientConfig {
|
|
7
16
|
network?: Network;
|
|
8
17
|
baseUrl?: string;
|
|
9
18
|
apiKey?: string;
|
|
10
19
|
}
|
|
20
|
+
export { requireSolanaAddress };
|
|
11
21
|
export declare class PacificaClient {
|
|
12
22
|
private baseUrl;
|
|
13
23
|
private apiKey?;
|
|
@@ -18,6 +28,10 @@ export declare class PacificaClient {
|
|
|
18
28
|
* against the agent key. Undefined for master/PK signers.
|
|
19
29
|
*/
|
|
20
30
|
private requestAgentWallet?;
|
|
31
|
+
/** Credit-quota state for this base URL + key tier (rate-limit.ts; process-local): unsigned GETs (IP quota) … */
|
|
32
|
+
private readonly rate;
|
|
33
|
+
/** … and signed POSTs (may report the account's quota) kept apart. */
|
|
34
|
+
private readonly signedRate;
|
|
21
35
|
/** Attach (or clear) the agent wallet used for subsequent signed requests. */
|
|
22
36
|
setRequestAgentWallet(agentWallet?: string): void;
|
|
23
37
|
/** Build a signed POST body, attaching agent_wallet when an agent is the active signer. */
|
|
@@ -31,8 +45,12 @@ export declare class PacificaClient {
|
|
|
31
45
|
* outcome unknown → ORDER_PENDING (sendFailure), never a retryable network error. No connection
|
|
32
46
|
* at all → not sent. A 4xx is the venue's refusal and keeps the plain `POST <path> failed
|
|
33
47
|
* (<status>): <body>` error the adapters map (422 business codes) — except on
|
|
34
|
-
* DEFINITE_4XX_PATHS, where it is a definite ACTION_REJECTED
|
|
35
|
-
*
|
|
48
|
+
* DEFINITE_4XX_PATHS, where it is a definite ACTION_REJECTED. HTTP 429 → RATE_LIMITED
|
|
49
|
+
* (details.sent === false, retryAfterMs) — never retried, a write is never resubmitted.
|
|
50
|
+
* "429 = not executed" is our reading of the docs ("When credits are exhausted, requests return
|
|
51
|
+
* HTTP 429"), not an explicit Pacifica statement. Before sending, the credit throttle may wait up
|
|
52
|
+
* to PACIFICA_SIGNED_MAX_WAIT_MS (the body is already signed) or fail fast with RATE_LIMITED
|
|
53
|
+
* (nothing sent). A write needs only its own cost, never the read reserve. A fetch timeout (PACIFICA_POST_TIMEOUT_MS) on a write is ORDER_PENDING too;
|
|
36
54
|
* on a listing read a timeout is TIMEOUT and any other transport failure EXCHANGE_UNREACHABLE.
|
|
37
55
|
*/
|
|
38
56
|
private post;
|
|
@@ -119,6 +137,27 @@ export declare class PacificaClient {
|
|
|
119
137
|
createLake(params: CreateLakeParams, account: string, signMessage: SignMessageFn): Promise<unknown>;
|
|
120
138
|
depositToLake(params: LakeDepositParams, account: string, signMessage: SignMessageFn): Promise<unknown>;
|
|
121
139
|
withdrawFromLake(params: LakeWithdrawParams, account: string, signMessage: SignMessageFn): Promise<unknown>;
|
|
140
|
+
/**
|
|
141
|
+
* Unauthenticated GET that keeps the whole {success, data, has_more, next_cursor} envelope.
|
|
142
|
+
* Same credit limiter as get() (unsigned IP quota, PACIFICA_READ_NEED: may wait for the window,
|
|
143
|
+
* else RATE_LIMITED sent:false); the response headers are recorded; HTTP 429 → RATE_LIMITED
|
|
144
|
+
* (structural, never retried). Timeout → TIMEOUT, transport failure → EXCHANGE_UNREACHABLE,
|
|
145
|
+
* non-JSON / non-object body → EXCHANGE_ERROR (a read: nothing can have changed).
|
|
146
|
+
*/
|
|
147
|
+
getEnvelope(path: string, params?: Record<string, string>): Promise<Record<string, unknown>>;
|
|
148
|
+
/**
|
|
149
|
+
* POST a body the caller already built and signed (Print / vault writes sign their own canonical
|
|
150
|
+
* message so `--dry-run` can show the exact body). Same unknown-outcome rules as every write.
|
|
151
|
+
*/
|
|
152
|
+
postPresigned(path: "/game/deposit" | "/game/end" | "/lake/deposit" | "/lake/withdraw", body: Record<string, unknown>): Promise<unknown>;
|
|
153
|
+
/**
|
|
154
|
+
* Pre-sign credit wait for a pre-signed write (same rule as signedBody / amend): call BEFORE
|
|
155
|
+
* building + signing, so the post-sign wait in post() (≤ PACIFICA_SIGNED_MAX_WAIT_MS, inside the
|
|
156
|
+
* 5 s expiry_window) finds credits. Cost: the docs' rate-limits table lists no Print / vault
|
|
157
|
+
* entry — "Standard request/action: 1 credit" applies (end_game is not an order cancellation).
|
|
158
|
+
* Fail-fast RATE_LIMITED here sent and signed nothing (details.sent === false).
|
|
159
|
+
*/
|
|
160
|
+
awaitPresignedCapacity(operationType: string): Promise<void>;
|
|
122
161
|
approveBuilderCode(params: {
|
|
123
162
|
builder_code: string;
|
|
124
163
|
max_fee_rate: string;
|
|
@@ -137,6 +176,31 @@ export declare class PacificaClient {
|
|
|
137
176
|
claimReferralCode(params: {
|
|
138
177
|
code: string;
|
|
139
178
|
}, account: string, signMessage: SignMessageFn): Promise<unknown>;
|
|
140
|
-
|
|
179
|
+
/**
|
|
180
|
+
* POST /orders/batch with the per-action cost (pacificaBatchCost) reserved. `maxWaitMs` caps the
|
|
181
|
+
* post-sign throttle wait (default PACIFICA_SIGNED_MAX_WAIT_MS, never more): a caller with a
|
|
182
|
+
* tighter signature deadline passes what is left of it, so a wait can never send an expired batch
|
|
183
|
+
* — a longer wait fails fast with RATE_LIMITED (sent: false, nothing sent).
|
|
184
|
+
*/
|
|
185
|
+
batchOrders(actions: BatchAction[], opts?: {
|
|
186
|
+
maxWaitMs?: number;
|
|
187
|
+
}): Promise<unknown>;
|
|
188
|
+
/**
|
|
189
|
+
* Pre-sign throttle for a batch (like signedBody does for single writes): wait — up to
|
|
190
|
+
* PACIFICA_MAX_THROTTLE_WAIT_MS, else RATE_LIMITED with nothing signed or sent — until the
|
|
191
|
+
* batch's whole cost fits, so the post-sign acquire in batchOrders normally finds the credits.
|
|
192
|
+
*/
|
|
193
|
+
awaitBatchCapacity(types: readonly BatchActionType[]): Promise<void>;
|
|
194
|
+
/**
|
|
195
|
+
* One signed batch action body (docs rest-api/orders/batch-order: "Batch orders are not signed
|
|
196
|
+
* as a whole, but rather by its individual actions components"; each action's `data` carries the
|
|
197
|
+
* fields of its standalone endpoint, signed with that endpoint's operation type). Nothing is sent.
|
|
198
|
+
* `header` fixes the signed timestamp / expiry window, so every action of one batch can share
|
|
199
|
+
* one timestamp. agent_wallet is attached when an agent is the active signer. A signing failure
|
|
200
|
+
* → ORDER_REJECTED (orderNotSent).
|
|
201
|
+
*/
|
|
202
|
+
signBatchAction(operationType: string, payload: Record<string, unknown>, account: string, signMessage: SignMessageFn, header: {
|
|
203
|
+
timestamp: number;
|
|
204
|
+
expiry_window: number;
|
|
205
|
+
}): Promise<Record<string, unknown>>;
|
|
141
206
|
}
|
|
142
|
-
export {};
|