perp-cli 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -13
- 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/pacifica.js +83 -13
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- 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/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +9 -9
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -2
- package/dist/bot/strategies/funding-arb-utils.js +43 -24
- 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.js +23 -12
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +1 -1
- 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 +125 -5
- 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 +4 -4
- package/dist/commands/arb-auto.js +21 -17
- package/dist/commands/arb.d.ts +12 -0
- package/dist/commands/arb.js +61 -143
- 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.js +168 -36
- package/dist/commands/options.d.ts +9 -0
- package/dist/commands/options.js +134 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +468 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.js +32 -47
- 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 +63 -58
- 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/dashboard/server.js +1 -1
- package/dist/dashboard/ui.js +1 -1
- package/dist/errors.d.ts +24 -1
- package/dist/errors.js +25 -3
- package/dist/exchanges/arcus/adapter.d.ts +7 -7
- package/dist/exchanges/arcus/adapter.js +12 -12
- 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/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 +9 -4
- package/dist/exchanges/aster.js +125 -55
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +1 -2
- package/dist/exchanges/canonical-symbols.js +26 -2
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +68 -0
- package/dist/exchanges/derive/options.js +360 -0
- package/dist/exchanges/extended/adapter.d.ts +82 -0
- package/dist/exchanges/extended/adapter.js +179 -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 +130 -0
- package/dist/exchanges/extended/public.js +429 -0
- package/dist/exchanges/grvt/adapter.d.ts +86 -0
- package/dist/exchanges/grvt/adapter.js +206 -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-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +5 -5
- package/dist/exchanges/hyperliquid.d.ts +22 -16
- package/dist/exchanges/hyperliquid.js +80 -55
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +97 -8
- package/dist/exchanges/interface.js +19 -2
- 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 +2 -2
- package/dist/exchanges/lighter-public.js +2 -2
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +3 -4
- package/dist/exchanges/lighter-spot.js +6 -7
- package/dist/exchanges/lighter.d.ts +11 -11
- package/dist/exchanges/lighter.js +28 -14
- package/dist/exchanges/ondo/adapter.d.ts +5 -6
- package/dist/exchanges/ondo/adapter.js +16 -14
- 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 +8 -1
- package/dist/exchanges/ondo/public.js +13 -2
- 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/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 +2 -2
- package/dist/exchanges/pacifica-spot.js +3 -3
- 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 +121 -0
- package/dist/exchanges/pacifica-vaults.js +264 -0
- package/dist/exchanges/pacifica.d.ts +32 -5
- package/dist/exchanges/pacifica.js +114 -48
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/qfex/adapter.d.ts +18 -5
- package/dist/exchanges/qfex/adapter.js +32 -12
- 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 +3 -3
- package/dist/exchanges/qfex/public.js +3 -3
- 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 +2 -2
- 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 +148 -0
- package/dist/exchanges/risex/adapter.js +796 -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 +7 -7
- 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 +54 -0
- package/dist/exchanges/txflow/adapter.js +118 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +153 -0
- package/dist/exchanges/txflow/public.d.ts +76 -0
- package/dist/exchanges/txflow/public.js +284 -0
- package/dist/exchanges/unknown-symbol.d.ts +29 -0
- package/dist/exchanges/unknown-symbol.js +51 -0
- package/dist/exchanges/variational/adapter.d.ts +71 -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 +6 -7
- package/dist/exchanges/venue-types.js +2 -2
- package/dist/exchanges/venue.d.ts +51 -8
- package/dist/exchanges/venue.js +54 -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 +10 -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 +7 -5
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +5 -3
- 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 +6 -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 +116 -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 +1 -1
- package/dist/funding/legacy-public.js +9 -4
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +44 -4
- package/dist/funding/rates.js +56 -12
- 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/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +58 -0
- package/dist/mcp-server.js +113 -59
- package/dist/pacifica/client.d.ts +67 -4
- package/dist/pacifica/client.js +170 -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/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 +1 -1
- package/dist/trade-validator.d.ts +1 -1
- package/dist/trade-validator.js +9 -4
- package/dist/utils.d.ts +12 -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/index.d.ts +959 -0
- package/dist/ws/index.js +4295 -0
- package/dist/ws/node.d.ts +975 -0
- package/dist/ws/node.js +4366 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +58 -10
- package/skills/perp-cli/SKILL.md +62 -9
- package/skills/perp-cli/references/agent-operations.md +7 -0
- package/skills/perp-cli/references/commands.md +76 -5
- package/skills/perp-cli/references/ws-library.md +492 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variational (Omni) venue definition — MARKET DATA ONLY, through the OFFICIAL read-only API.
|
|
3
|
+
*
|
|
4
|
+
* Variational Omni is an RFQ venue: the Omni Liquidity Provider (OLP) is the only maker and quotes
|
|
5
|
+
* every trade — there is no order book (docs.variational.io/variational-protocol/key-concepts/trading-via-rfq).
|
|
6
|
+
* Its documented public API (docs.variational.io/technical-documentation/api) is one endpoint,
|
|
7
|
+
* `GET /metadata/v2/stats`, serving every market at once. The trading API "is still in
|
|
8
|
+
* development, and is not yet available to any users", so:
|
|
9
|
+
* - roles are ["marketData"]: every account / trading / funds / agent / builder / bot path refuses
|
|
10
|
+
* with CAPABILITY_UNSUPPORTED before any request (venue.ts roleUnsupported());
|
|
11
|
+
* - `market book` shows the OLP's indicative RFQ quotes at fixed notionals, labelled as such
|
|
12
|
+
* (never as order-book levels).
|
|
13
|
+
* No WebSocket is documented, so there is no ws codec.
|
|
14
|
+
* Not registered here; the registry module owns registerVenue().
|
|
15
|
+
*/
|
|
16
|
+
import type { VenueDefinition } from "../venue.js";
|
|
17
|
+
export declare const VARIATIONAL_REST_URL = "https://omni-client-api.prod.ap-northeast-1.variational.io";
|
|
18
|
+
export declare const VARIATIONAL_API_DOCS = "https://docs.variational.io/technical-documentation/api";
|
|
19
|
+
/** Every role refusal's remediation (VenueDefinition.readOnlyRemediation, read by roleUnsupported). */
|
|
20
|
+
export declare const VARIATIONAL_READ_ONLY_REMEDIATION = "Variational is read-only (market data) in perp-cli; Variational has no public trading API yet. Trade on omni.variational.io; perp-cli shows Variational market data (perp -e variational market list|prices|info|funding|book, perp arb scan --rates --include-unverified)";
|
|
21
|
+
export declare const variationalVenue: VenueDefinition;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variational (Omni) venue definition — MARKET DATA ONLY, through the OFFICIAL read-only API.
|
|
3
|
+
*
|
|
4
|
+
* Variational Omni is an RFQ venue: the Omni Liquidity Provider (OLP) is the only maker and quotes
|
|
5
|
+
* every trade — there is no order book (docs.variational.io/variational-protocol/key-concepts/trading-via-rfq).
|
|
6
|
+
* Its documented public API (docs.variational.io/technical-documentation/api) is one endpoint,
|
|
7
|
+
* `GET /metadata/v2/stats`, serving every market at once. The trading API "is still in
|
|
8
|
+
* development, and is not yet available to any users", so:
|
|
9
|
+
* - roles are ["marketData"]: every account / trading / funds / agent / builder / bot path refuses
|
|
10
|
+
* with CAPABILITY_UNSUPPORTED before any request (venue.ts roleUnsupported());
|
|
11
|
+
* - `market book` shows the OLP's indicative RFQ quotes at fixed notionals, labelled as such
|
|
12
|
+
* (never as order-book levels).
|
|
13
|
+
* No WebSocket is documented, so there is no ws codec.
|
|
14
|
+
* Not registered here; the registry module owns registerVenue().
|
|
15
|
+
*/
|
|
16
|
+
export const VARIATIONAL_REST_URL = "https://omni-client-api.prod.ap-northeast-1.variational.io";
|
|
17
|
+
export const VARIATIONAL_API_DOCS = "https://docs.variational.io/technical-documentation/api";
|
|
18
|
+
/** Every role refusal's remediation (VenueDefinition.readOnlyRemediation, read by roleUnsupported). */
|
|
19
|
+
export const VARIATIONAL_READ_ONLY_REMEDIATION = "Variational is read-only (market data) in perp-cli; Variational has no public trading API yet. Trade on omni.variational.io; perp-cli shows Variational market data (perp -e variational market list|prices|info|funding|book, perp arb scan --rates --include-unverified)";
|
|
20
|
+
/** Lazy: public.ts is only loaded when market data is actually requested. */
|
|
21
|
+
const publicData = {
|
|
22
|
+
async fetchFundingRates(net) {
|
|
23
|
+
return (await import("../variational/public.js")).variationalPublicData.fetchFundingRates(net);
|
|
24
|
+
},
|
|
25
|
+
async fetchMarkets(net) {
|
|
26
|
+
return (await import("../variational/public.js")).variationalPublicData.fetchMarkets(net);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export const variationalVenue = {
|
|
30
|
+
id: "variational",
|
|
31
|
+
aliases: ["var", "omni"],
|
|
32
|
+
displayName: "Variational",
|
|
33
|
+
family: "variational",
|
|
34
|
+
masterKeyKind: "none",
|
|
35
|
+
agentKeyKind: "none",
|
|
36
|
+
secretStorage: { kind: "none", dir: null },
|
|
37
|
+
networks: {
|
|
38
|
+
mainnet: {
|
|
39
|
+
restUrl: VARIATIONAL_REST_URL,
|
|
40
|
+
// No WebSocket is documented.
|
|
41
|
+
wsUrl: null,
|
|
42
|
+
explorerUrl: null,
|
|
43
|
+
signingChainId: null,
|
|
44
|
+
// Settlement pools live on Arbitrum; perp-cli never touches them (read-only).
|
|
45
|
+
settlementChain: "offchain",
|
|
46
|
+
collateral: ["USDC"],
|
|
47
|
+
collateralAssetIndex: null,
|
|
48
|
+
rpcUrl: null,
|
|
49
|
+
depositPins: null,
|
|
50
|
+
},
|
|
51
|
+
// No public testnet API.
|
|
52
|
+
testnet: null,
|
|
53
|
+
},
|
|
54
|
+
// funding.interval_seconds is per market (1h / 4h / 8h perps; 24h swaps).
|
|
55
|
+
fundingRateBasis: "perMarket",
|
|
56
|
+
settlementIntervalHours: "perMarket",
|
|
57
|
+
// UNVERIFIED (design §14.12 #6: verified = checked against settled payments). The unit is
|
|
58
|
+
// documented (funding.last.interval_rate = fraction per interval_seconds, positive = longs pay)
|
|
59
|
+
// and interval_rate = annualized_rate × interval / 365 d holds on every live perp (2026-10-19),
|
|
60
|
+
// but that only shows the published fields agree: the rate is a moving prediction, there is no
|
|
61
|
+
// public account API to check settled payments, and the accrual hours of equity / ETF perps are
|
|
62
|
+
// not documented. Shown only on request (arb scan --rates --include-unverified).
|
|
63
|
+
fundingVerified: false,
|
|
64
|
+
roles: ["marketData"],
|
|
65
|
+
gatedRoles: null,
|
|
66
|
+
capabilities: null,
|
|
67
|
+
// Documented: 10 requests / 10 s per IP (shared with the legacy v1 endpoint). One call serves
|
|
68
|
+
// every market and is cached briefly (variational/public.ts), so commands rarely hit this.
|
|
69
|
+
rateLimit: { capacity: 10, refillPerSec: 1, weights: null },
|
|
70
|
+
// docs.variational.io/legal/restricted-persons (last updated 2026-04-20). Warn-only (never enforced).
|
|
71
|
+
restrictedRegions: ["US", "CA", "CU", "IR", "KP", "SD", "SY", "TW", "PR", "GU", "VI", "AS", "MP", "UM", "FM", "MH", "PW"],
|
|
72
|
+
symbolIdentity: "baseTicker",
|
|
73
|
+
publicData,
|
|
74
|
+
spot: null,
|
|
75
|
+
// Unused (no trading role); the field is required by the definition shape.
|
|
76
|
+
maxSlippageBps: 1,
|
|
77
|
+
builderFee: null,
|
|
78
|
+
apiStatus: { kind: "official" },
|
|
79
|
+
readOnlyRemediation: VARIATIONAL_READ_ONLY_REMEDIATION,
|
|
80
|
+
// A public-only adapter: market data works, every account / trading method refuses.
|
|
81
|
+
async create(ctx) {
|
|
82
|
+
const { VariationalPublicAdapter } = await import("../variational/adapter.js");
|
|
83
|
+
return new VariationalPublicAdapter(variationalVenue, ctx.network);
|
|
84
|
+
},
|
|
85
|
+
};
|
package/dist/execution-log.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface ExecutionRecord {
|
|
2
2
|
id: string;
|
|
3
3
|
timestamp: string;
|
|
4
|
-
type: "market_order" | "limit_order" | "cancel" | "stop_order" | "edit_order" | "rebalance" | "arb_entry" | "arb_close" | "bridge" | "multi_leg" | "multi_leg_rollback" | "split_order";
|
|
4
|
+
type: "market_order" | "limit_order" | "cancel" | "stop_order" | "edit_order" | "rebalance" | "arb_entry" | "arb_close" | "bridge" | "multi_leg" | "multi_leg_rollback" | "split_order" | "print_deposit" | "print_end" | "vault_deposit" | "vault_withdraw";
|
|
5
5
|
exchange: string;
|
|
6
6
|
symbol: string;
|
|
7
7
|
side: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public market data for the legacy venues without a native `getVenueMarkets`
|
|
3
|
-
* (pacifica, hyperliquid core, aster)
|
|
3
|
+
* (pacifica, hyperliquid core, aster). Lighter uses
|
|
4
4
|
* exchanges/lighter-public.ts.
|
|
5
5
|
*
|
|
6
6
|
* Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public market data for the legacy venues without a native `getVenueMarkets`
|
|
3
|
-
* (pacifica, hyperliquid core, aster)
|
|
3
|
+
* (pacifica, hyperliquid core, aster). Lighter uses
|
|
4
4
|
* exchanges/lighter-public.ts.
|
|
5
5
|
*
|
|
6
6
|
* Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* size; Aster leverage brackets need a signed call; Aster OI is per-symbol only).
|
|
28
28
|
*/
|
|
29
29
|
import { HL_MIN_ORDER_VALUE_USD } from "../constants.js";
|
|
30
|
+
import { isPacificaPerpetual } from "../ws/pacifica-instrument.js";
|
|
30
31
|
import { PerpError } from "../errors.js";
|
|
31
32
|
import { venueRateString } from "../utils/numeric.js";
|
|
32
33
|
import { assertOk } from "../api/public/_http.js";
|
|
@@ -127,11 +128,11 @@ async function pacificaRows(net) {
|
|
|
127
128
|
const unclassified = [];
|
|
128
129
|
const unpriced = [];
|
|
129
130
|
for (const m of info) {
|
|
130
|
-
// /info lists spot markets too (e.g. SOL-USDC). A row without the field cannot be classified.
|
|
131
|
+
// /info lists spot markets too (e.g. SOL-USDC): perps only. A row without the field cannot be classified.
|
|
131
132
|
if (typeof m.instrument_type !== "string") {
|
|
132
133
|
throw new PerpError("EXCHANGE_ERROR", `Pacifica /info row ${String(m.symbol)} has no instrument_type`, { exchange: "pacifica" });
|
|
133
134
|
}
|
|
134
|
-
if (m
|
|
135
|
+
if (!isPacificaPerpetual(m))
|
|
135
136
|
continue;
|
|
136
137
|
const symbol = String(m.symbol ?? "");
|
|
137
138
|
const assetClass = classifyPacifica(symbol);
|
|
@@ -200,6 +201,10 @@ async function hyperliquidRows(net) {
|
|
|
200
201
|
const unclassified = [];
|
|
201
202
|
const unpriced = [];
|
|
202
203
|
universe.forEach((a, i) => {
|
|
204
|
+
// Delisted assets stay in meta (isDelisted: true) with zeroed funding / volume / OI
|
|
205
|
+
// (live 2026-10-14: 56 of 234) — not a market: excluded from listings, prices and scans.
|
|
206
|
+
if (a.isDelisted === true)
|
|
207
|
+
return;
|
|
203
208
|
const ctx = ctxs[i];
|
|
204
209
|
const symbol = String(a.name ?? "");
|
|
205
210
|
const assetClass = classifyHyperliquid(symbol);
|
|
@@ -237,7 +242,7 @@ async function hyperliquidRows(net) {
|
|
|
237
242
|
maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
|
|
238
243
|
marginTiers: null,
|
|
239
244
|
session: null,
|
|
240
|
-
tradable:
|
|
245
|
+
tradable: true, // delisted assets are skipped above
|
|
241
246
|
},
|
|
242
247
|
nextFundingTime: null, // not published in metaAndAssetCtxs
|
|
243
248
|
});
|
package/dist/funding/rates.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Real-time cross-venue funding rate comparison.
|
|
3
3
|
*
|
|
4
|
-
* Fans out over every venue with the "marketData" role
|
|
4
|
+
* Fans out over every venue with the "marketData" role using
|
|
5
5
|
* each venue's `publicData.fetchFundingRates(net)`, normalizes every row to a
|
|
6
6
|
* per-hour rate with the row's own basis (Lighter
|
|
7
7
|
* family 8h, Aster per-symbol), and matches markets across venues by
|
|
8
|
-
* `canonical` (null → excluded
|
|
8
|
+
* `canonical` (null → excluded).
|
|
9
9
|
*
|
|
10
10
|
* A venue that fails is reported in `exchangeStatus` ("error") and
|
|
11
11
|
* `exchangeErrors` (message) — its rows are absent, never silently empty.
|
|
@@ -28,6 +28,8 @@ export interface ExchangeFundingRate {
|
|
|
28
28
|
historicalAvg: HistoricalAverages | null;
|
|
29
29
|
/** false = the venue's funding rate is unverified (VenueDefinition.fundingVerified) — shown only on explicit request. */
|
|
30
30
|
fundingVerified: boolean;
|
|
31
|
+
/** How the row was read: "unofficial" = an undocumented venue API (VenueDefinition.apiStatus) — may change without notice. */
|
|
32
|
+
apiStatus: "official" | "unofficial";
|
|
31
33
|
}
|
|
32
34
|
export interface SymbolFundingComparison {
|
|
33
35
|
/** Canonical symbol shared by every row. */
|
|
@@ -38,12 +40,19 @@ export interface SymbolFundingComparison {
|
|
|
38
40
|
shortExchange: string;
|
|
39
41
|
bestMarkPrice: number;
|
|
40
42
|
estHourlyIncomeUsd: number;
|
|
43
|
+
/**
|
|
44
|
+
* false = a leg of the long/short pair sits on a venue without the trading role (RISEx while its
|
|
45
|
+
* trading is gated in this release): the spread is data only — `arb exec` refuses it.
|
|
46
|
+
*/
|
|
47
|
+
executable: boolean;
|
|
48
|
+
/** Venues of the long/short pair that perp-cli cannot trade (read-only); [] when executable. */
|
|
49
|
+
readOnlyLegs: string[];
|
|
41
50
|
}
|
|
42
51
|
export interface FundingRateSnapshot {
|
|
43
52
|
timestamp: string;
|
|
44
53
|
symbols: SymbolFundingComparison[];
|
|
45
54
|
exchangeStatus: Record<string, "ok" | "error">;
|
|
46
|
-
/** Error message per venue whose status is "error"
|
|
55
|
+
/** Error message per venue whose status is "error". */
|
|
47
56
|
exchangeErrors: Record<string, string>;
|
|
48
57
|
}
|
|
49
58
|
export declare const TOP_SYMBOLS: string[];
|
|
@@ -59,6 +68,37 @@ export declare function fetchVenuesRates(venues: VenueDefinition[], net: Network
|
|
|
59
68
|
exchangeStatus: Record<string, "ok" | "error">;
|
|
60
69
|
exchangeErrors: Record<string, string>;
|
|
61
70
|
}>;
|
|
71
|
+
export interface VenuePrice {
|
|
72
|
+
exchange: VenueId;
|
|
73
|
+
/** Venue display symbol. */
|
|
74
|
+
symbol: string;
|
|
75
|
+
canonical: string;
|
|
76
|
+
markPrice: number;
|
|
77
|
+
apiStatus: "official" | "unofficial";
|
|
78
|
+
}
|
|
79
|
+
export interface PriceGroupSnapshot {
|
|
80
|
+
/** canonical → venue prices, after the median price-outlier drop (same guard as the funding scan). */
|
|
81
|
+
groups: Map<string, VenuePrice[]>;
|
|
82
|
+
exchangeStatus: Record<string, "ok" | "error">;
|
|
83
|
+
exchangeErrors: Record<string, string>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Mark prices of `venues`, grouped by canonical symbol — the grouping and price-outlier guard of
|
|
87
|
+
* the funding comparison, WITHOUT any funding dependency: read from each venue's market directory
|
|
88
|
+
* (publicData.fetchMarkets), so a market with a null funding rate or a venue whose funding is
|
|
89
|
+
* unverified is still priced. Rows without a canonical symbol, not tradable, or without a
|
|
90
|
+
* positive mark are excluded. A venue that fails is reported in exchangeStatus / exchangeErrors.
|
|
91
|
+
*/
|
|
92
|
+
export declare function fetchCanonicalPriceGroups(venues: VenueDefinition[], opts?: {
|
|
93
|
+
symbols?: string[];
|
|
94
|
+
network?: Network;
|
|
95
|
+
tag?: string;
|
|
96
|
+
}): Promise<PriceGroupSnapshot>;
|
|
97
|
+
/** Whether perp-cli can trade both legs of a long/short pair (the trading role), and which it cannot. */
|
|
98
|
+
export declare function pairExecutability(longExchange: string, shortExchange: string): {
|
|
99
|
+
executable: boolean;
|
|
100
|
+
readOnlyLegs: string[];
|
|
101
|
+
};
|
|
62
102
|
/**
|
|
63
103
|
* Fetch funding rates from every market-data venue in parallel, normalize, and compare.
|
|
64
104
|
* Returns rates sorted by max spread (descending).
|
|
@@ -97,7 +137,7 @@ export interface SpotPerpSpread {
|
|
|
97
137
|
/**
|
|
98
138
|
* Fetch spot+perp funding rate spreads.
|
|
99
139
|
* Since spot has 0 funding cost, the spread is simply |perp funding rate|.
|
|
100
|
-
* Only includes symbols available on at least one bot spot venue (def.spot
|
|
140
|
+
* Only includes symbols available on at least one bot spot venue (def.spot).
|
|
101
141
|
*
|
|
102
142
|
* Key safety checks:
|
|
103
143
|
* - U-token mapping: UBTC→BTC, UETH→ETH, USOL→SOL, UFART→FARTCOIN
|
package/dist/funding/rates.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Real-time cross-venue funding rate comparison.
|
|
3
3
|
*
|
|
4
|
-
* Fans out over every venue with the "marketData" role
|
|
4
|
+
* Fans out over every venue with the "marketData" role using
|
|
5
5
|
* each venue's `publicData.fetchFundingRates(net)`, normalizes every row to a
|
|
6
6
|
* per-hour rate with the row's own basis (Lighter
|
|
7
7
|
* family 8h, Aster per-symbol), and matches markets across venues by
|
|
8
|
-
* `canonical` (null → excluded
|
|
8
|
+
* `canonical` (null → excluded).
|
|
9
9
|
*
|
|
10
10
|
* A venue that fails is reported in `exchangeStatus` ("error") and
|
|
11
11
|
* `exchangeErrors` (message) — its rows are absent, never silently empty.
|
|
@@ -26,7 +26,11 @@ export const TOP_SYMBOLS = [
|
|
|
26
26
|
];
|
|
27
27
|
/** Mark-price source preference for `bestMarkPrice` (most liquid first). */
|
|
28
28
|
const MARK_PRICE_PREFERENCE = [
|
|
29
|
-
"hyperliquid", "pacifica", "lighter", "aster", "lighter-rh", "ondo", "arcus", "qfex",
|
|
29
|
+
"hyperliquid", "pacifica", "lighter", "aster", "lighter-rh", "ondo", "arcus", "risex", "qfex", "grvt", "extended",
|
|
30
|
+
// RFQ venue: the mark is the single maker's (OLP) price.
|
|
31
|
+
"variational",
|
|
32
|
+
// Unofficial API: last resort as a mark-price source.
|
|
33
|
+
"txflow",
|
|
30
34
|
];
|
|
31
35
|
// ── Per-venue fetch ──
|
|
32
36
|
function errorMessage(e) {
|
|
@@ -57,14 +61,14 @@ async function fetchVenueRates(def, net) {
|
|
|
57
61
|
unpriced.push(r.symbol);
|
|
58
62
|
continue;
|
|
59
63
|
}
|
|
60
|
-
out.push(toExchangeRate(r, r.canonical, markPrice, def.fundingVerified));
|
|
64
|
+
out.push(toExchangeRate(r, r.canonical, markPrice, def.fundingVerified, def.apiStatus.kind));
|
|
61
65
|
}
|
|
62
66
|
if (unpriced.length > 0) {
|
|
63
67
|
console.warn(`[${def.id}] excluded ${unpriced.length} funding row(s) without a mark price: ${unpriced.join(", ")}`);
|
|
64
68
|
}
|
|
65
69
|
return out;
|
|
66
70
|
}
|
|
67
|
-
function toExchangeRate(r, canonical, markPrice, fundingVerified) {
|
|
71
|
+
function toExchangeRate(r, canonical, markPrice, fundingVerified, apiStatus) {
|
|
68
72
|
const fundingRate = Number(r.rate);
|
|
69
73
|
if (!Number.isFinite(fundingRate)) {
|
|
70
74
|
throw new PerpError("EXCHANGE_ERROR", `${r.venue} ${r.symbol}: non-numeric funding rate "${r.rate}"`);
|
|
@@ -82,6 +86,7 @@ function toExchangeRate(r, canonical, markPrice, fundingVerified) {
|
|
|
82
86
|
nextFundingTime: r.nextFundingTime,
|
|
83
87
|
historicalAvg: null,
|
|
84
88
|
fundingVerified,
|
|
89
|
+
apiStatus,
|
|
85
90
|
};
|
|
86
91
|
}
|
|
87
92
|
/**
|
|
@@ -117,7 +122,7 @@ const _warnedPriceMismatch = new Set();
|
|
|
117
122
|
* venue whose mark price deviates more than MAX_PRICE_DEVIATION_PCT from the
|
|
118
123
|
* group median, with a one-time stderr warning per (symbol, venue).
|
|
119
124
|
*/
|
|
120
|
-
function dropPriceOutliers(canonical, list) {
|
|
125
|
+
function dropPriceOutliers(canonical, list, tag = "funding") {
|
|
121
126
|
if (list.length < 2)
|
|
122
127
|
return list;
|
|
123
128
|
const sorted = list.map(r => r.markPrice).sort((x, y) => x - y);
|
|
@@ -133,12 +138,12 @@ function dropPriceOutliers(canonical, list) {
|
|
|
133
138
|
const key = `${canonical}:${r.exchange}`;
|
|
134
139
|
if (!_warnedPriceMismatch.has(key)) {
|
|
135
140
|
_warnedPriceMismatch.add(key);
|
|
136
|
-
process.stderr.write(`[
|
|
141
|
+
process.stderr.write(`[${tag}] excluded ${r.exchange} ${canonical}: mark ${r.markPrice} deviates ${devPct.toFixed(1)}% from cross-venue median ${mid} (likely a different asset with the same ticker)\n`);
|
|
137
142
|
}
|
|
138
143
|
return false;
|
|
139
144
|
});
|
|
140
145
|
}
|
|
141
|
-
function groupByCanonical(rates, symbols) {
|
|
146
|
+
function groupByCanonical(rates, symbols, tag = "funding") {
|
|
142
147
|
const out = new Map();
|
|
143
148
|
for (const r of rates) {
|
|
144
149
|
if (symbols && !symbols.includes(r.canonical))
|
|
@@ -150,7 +155,7 @@ function groupByCanonical(rates, symbols) {
|
|
|
150
155
|
out.set(r.canonical, [r]);
|
|
151
156
|
}
|
|
152
157
|
for (const [canonical, list] of [...out]) {
|
|
153
|
-
const kept = dropPriceOutliers(canonical, list);
|
|
158
|
+
const kept = dropPriceOutliers(canonical, list, tag);
|
|
154
159
|
if (kept.length === 0)
|
|
155
160
|
out.delete(canonical); // every venue disagreed — not comparable
|
|
156
161
|
else
|
|
@@ -158,6 +163,44 @@ function groupByCanonical(rates, symbols) {
|
|
|
158
163
|
}
|
|
159
164
|
return out;
|
|
160
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Mark prices of `venues`, grouped by canonical symbol — the grouping and price-outlier guard of
|
|
168
|
+
* the funding comparison, WITHOUT any funding dependency: read from each venue's market directory
|
|
169
|
+
* (publicData.fetchMarkets), so a market with a null funding rate or a venue whose funding is
|
|
170
|
+
* unverified is still priced. Rows without a canonical symbol, not tradable, or without a
|
|
171
|
+
* positive mark are excluded. A venue that fails is reported in exchangeStatus / exchangeErrors.
|
|
172
|
+
*/
|
|
173
|
+
export async function fetchCanonicalPriceGroups(venues, opts) {
|
|
174
|
+
ensureVenuesRegistered();
|
|
175
|
+
const net = opts?.network ?? sharedApiNetwork();
|
|
176
|
+
const settled = await Promise.allSettled(venues.map(v => v.publicData.fetchMarkets(net)));
|
|
177
|
+
const exchangeStatus = {};
|
|
178
|
+
const exchangeErrors = {};
|
|
179
|
+
const prices = [];
|
|
180
|
+
settled.forEach((res, i) => {
|
|
181
|
+
const def = venues[i];
|
|
182
|
+
if (res.status === "rejected") {
|
|
183
|
+
exchangeStatus[def.id] = "error";
|
|
184
|
+
exchangeErrors[def.id] = errorMessage(res.reason);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
exchangeStatus[def.id] = "ok";
|
|
188
|
+
for (const m of res.value) {
|
|
189
|
+
if (m.canonical === null || !m.tradable)
|
|
190
|
+
continue;
|
|
191
|
+
const markPrice = Number(m.markPrice);
|
|
192
|
+
if (!Number.isFinite(markPrice) || markPrice <= 0)
|
|
193
|
+
continue;
|
|
194
|
+
prices.push({ exchange: def.id, symbol: m.symbol, canonical: m.canonical, markPrice, apiStatus: def.apiStatus.kind });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
return { groups: groupByCanonical(prices, opts?.symbols, opts?.tag ?? "prices"), exchangeStatus, exchangeErrors };
|
|
198
|
+
}
|
|
199
|
+
/** Whether perp-cli can trade both legs of a long/short pair (the trading role), and which it cannot. */
|
|
200
|
+
export function pairExecutability(longExchange, shortExchange) {
|
|
201
|
+
const readOnlyLegs = [...new Set([longExchange, shortExchange])].filter(v => !getVenue(v).roles.includes("trading"));
|
|
202
|
+
return { executable: readOnlyLegs.length === 0, readOnlyLegs };
|
|
203
|
+
}
|
|
161
204
|
function bestMarkPrice(rates) {
|
|
162
205
|
const rank = (id) => MARK_PRICE_PREFERENCE.indexOf(id);
|
|
163
206
|
return [...rates].sort((a, b) => rank(a.exchange) - rank(b.exchange))[0].markPrice;
|
|
@@ -218,6 +261,7 @@ export async function fetchAllFundingRates(opts) {
|
|
|
218
261
|
shortExchange: highest.exchange, // short where funding is highest
|
|
219
262
|
bestMarkPrice: bestMarkPrice(rates),
|
|
220
263
|
estHourlyIncomeUsd,
|
|
264
|
+
...pairExecutability(lowest.exchange, highest.exchange),
|
|
221
265
|
});
|
|
222
266
|
}
|
|
223
267
|
// Sort by spread descending
|
|
@@ -239,7 +283,7 @@ export async function fetchSymbolFundingRates(symbol, includeUnverified = false)
|
|
|
239
283
|
/**
|
|
240
284
|
* Fetch spot+perp funding rate spreads.
|
|
241
285
|
* Since spot has 0 funding cost, the spread is simply |perp funding rate|.
|
|
242
|
-
* Only includes symbols available on at least one bot spot venue (def.spot
|
|
286
|
+
* Only includes symbols available on at least one bot spot venue (def.spot).
|
|
243
287
|
*
|
|
244
288
|
* Key safety checks:
|
|
245
289
|
* - U-token mapping: UBTC→BTC, UETH→ETH, USOL→SOL, UFART→FARTCOIN
|
|
@@ -247,8 +291,8 @@ export async function fetchSymbolFundingRates(symbol, includeUnverified = false)
|
|
|
247
291
|
* (filters out same-ticker-different-token issues like HIP-1 TRUMP ≠ perp TRUMP)
|
|
248
292
|
*/
|
|
249
293
|
export async function fetchSpotPerpSpreads(opts) {
|
|
250
|
-
// Spot legs come from the venue definitions (def.spot
|
|
251
|
-
// (separate/runtime account + transfer
|
|
294
|
+
// Spot legs come from the venue definitions (def.spot): only bot/arb spot venues
|
|
295
|
+
// (separate/runtime account + transfer). Perp legs: those venues + pacifica.
|
|
252
296
|
ensureVenuesRegistered();
|
|
253
297
|
const net = opts?.network ?? sharedApiNetwork();
|
|
254
298
|
const { listBotSpotVenues, requireSpotAdapter, defaultSpotQuote } = await import("../exchanges/spot-factory.js");
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { createRequire } from "node:module";
|
|
|
17
17
|
config({ path: resolve(process.env.HOME || "~", ".perp", ".env") });
|
|
18
18
|
import chalk from "chalk";
|
|
19
19
|
import { listVenues } from "./exchanges/venue.js";
|
|
20
|
+
import { optionsVenueWords } from "./exchanges/options-venues.js";
|
|
20
21
|
import { PerpError } from "./errors.js";
|
|
21
22
|
import { loadSettings } from "./settings.js";
|
|
22
23
|
import { jsonError, jsonOk, printCliError, printJson } from "./utils.js";
|
|
@@ -49,7 +50,7 @@ const _defaultExchange = _settings.defaultExchange || "pacifica";
|
|
|
49
50
|
const { program, getAdapterForExchange, isJson } = buildProgram({ defaultExchange: _defaultExchange, version: _pkg.version });
|
|
50
51
|
// Smart landing page: `perp` with no subcommand
|
|
51
52
|
const rawArgs = process.argv.slice(2);
|
|
52
|
-
const _venueWords = listVenues().flatMap(v => [v.id, ...v.aliases]);
|
|
53
|
+
const _venueWords = [...listVenues().flatMap(v => [v.id, ...v.aliases]), ...optionsVenueWords()];
|
|
53
54
|
const hasSubcommand = rawArgs.some((a) => !a.startsWith("-") && ![..._venueWords, "mainnet", "testnet"].includes(a));
|
|
54
55
|
// `history list -n 5` (old record-count flag) → INVALID_PARAMS with the --limit migration, before
|
|
55
56
|
// Commander turns it into a bare "invalid network" usage error.
|
|
@@ -118,18 +119,18 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
|
|
|
118
119
|
const landingVenues = [];
|
|
119
120
|
for (const ex of LANDING_EXCHANGES) {
|
|
120
121
|
const def = getVenue(ex);
|
|
121
|
-
if (def.
|
|
122
|
+
if (!def.roles.includes("account") || await hasCredentials(def.id, NO_CLI_CREDENTIAL_OPTS))
|
|
122
123
|
landingVenues.push(ex);
|
|
123
124
|
}
|
|
124
125
|
const statusResults = await Promise.allSettled(landingVenues.map(async (ex) => {
|
|
125
|
-
if (getVenue(ex).
|
|
126
|
-
return { exchange: ex, marketDataOnly: true, ok: false, equity: 0, positions: 0 };
|
|
126
|
+
if (!getVenue(ex).roles.includes("account")) {
|
|
127
|
+
return { exchange: ex, marketDataOnly: true, unofficialApi: getVenue(ex).apiStatus.kind === "unofficial", ok: false, equity: 0, positions: 0 };
|
|
127
128
|
}
|
|
128
129
|
try {
|
|
129
130
|
const adapter = await withTimeout(getAdapterForExchange(ex), 5000);
|
|
130
131
|
const [balance, positions] = await withTimeout(Promise.all([adapter.getBalance(), adapter.getPositions()]), 5000);
|
|
131
132
|
const posCount = positions.filter(p => Number(p.size) > 0).length;
|
|
132
|
-
// Spot value on top of perp equity for venues with def.spot
|
|
133
|
+
// Spot value on top of perp equity for venues with def.spot. Shared balances
|
|
133
134
|
// (already in perp equity) are skipped; unpriced tokens are excluded + warned.
|
|
134
135
|
let spotValue = 0;
|
|
135
136
|
const spotDef = getVenue(ex);
|
|
@@ -196,7 +197,7 @@ else {
|
|
|
196
197
|
}).catch(async (err) => {
|
|
197
198
|
// Central printer (utils.printCliError): typed PerpError code / remediation / details
|
|
198
199
|
// are preserved (Rule #2: stay loud, keep semantic envelope); generic Errors go
|
|
199
|
-
// through classifyError; CAPABILITY_UNSUPPORTED prints the uniform
|
|
200
|
+
// through classifyError; CAPABILITY_UNSUPPORTED prints the uniform line.
|
|
200
201
|
const { printCliError } = await import("./utils.js");
|
|
201
202
|
printCliError(err, isJson());
|
|
202
203
|
_origExit(1);
|
package/dist/jobs.d.ts
CHANGED
package/dist/jobs.js
CHANGED
|
@@ -4,9 +4,11 @@ import { join } from "node:path";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { parse as parseDotenv } from "dotenv";
|
|
7
|
+
import { getVenue, roleUnsupported, tryResolveVenueId } from "./exchanges/venue.js";
|
|
8
|
+
import "./exchanges/venues/index.js";
|
|
7
9
|
import { PerpError } from "./errors.js";
|
|
8
10
|
import { QFEX_AUTOMATION_VERIFIED, QFEX_REDUCE_ONLY_WIRE, qfexAutomationGate, qfexReduceOnlyGate } from "./exchanges/qfex/signing.js";
|
|
9
|
-
import { startRefusal } from "./exchanges/trading-v2.js";
|
|
11
|
+
import { startRefusal, unsupported } from "./exchanges/trading-v2.js";
|
|
10
12
|
import { scrubSecretEnv, secretEnvNamesIn } from "./secret-env.js";
|
|
11
13
|
const JOBS_DIR = join(homedir(), ".perp", "jobs");
|
|
12
14
|
const LOGS_DIR = join(homedir(), ".perp", "logs");
|
|
@@ -165,6 +167,28 @@ export function startJob(opts) {
|
|
|
165
167
|
if (automation !== null)
|
|
166
168
|
throw startRefusal(what, "it would trade unattended", automation);
|
|
167
169
|
}
|
|
170
|
+
for (const v of new Set([opts.venue, opts.exchange])) {
|
|
171
|
+
const id = v?.toLowerCase();
|
|
172
|
+
const what = `Background job "${opts.strategy}"`;
|
|
173
|
+
// RISEx: unattended trading stays off until its trading path is verified on mainnet (no TP/SL yet).
|
|
174
|
+
if (id === "risex" || id === "rise") {
|
|
175
|
+
// Read-only release (trading role gated): the uniform phase refusal.
|
|
176
|
+
const risex = getVenue("risex");
|
|
177
|
+
if (!risex.roles.includes("trading"))
|
|
178
|
+
throw startRefusal(what, "it would trade unattended", roleUnsupported(risex, "automation"));
|
|
179
|
+
throw startRefusal(what, "it would trade unattended", unsupported("RISEx", "automation", "unattended trading on RISEx is not enabled until its trading path is verified on mainnet (and it has no TP/SL yet)", "Trade RISEx manually with perp trade …"));
|
|
180
|
+
}
|
|
181
|
+
// TxFlow: market data only (unofficial read-only API).
|
|
182
|
+
if (id === "txflow" || id === "txf") {
|
|
183
|
+
throw startRefusal(what, "TxFlow has no trading in perp-cli", unsupported("txflow", "trading", "TxFlow is integrated through an unofficial read-only API (market data only)", "Trade on the TxFlow website"));
|
|
184
|
+
}
|
|
185
|
+
// Any other registered venue without the trading role (Variational, GRVT, Extended: read-only in this version).
|
|
186
|
+
const venueId = id === undefined ? null : tryResolveVenueId(id);
|
|
187
|
+
if (venueId !== null && !getVenue(venueId).roles.includes("trading")) {
|
|
188
|
+
const def = getVenue(venueId);
|
|
189
|
+
throw startRefusal(what, `${def.displayName} has no trading in perp-cli`, roleUnsupported(def, "trading"));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
168
192
|
}
|
|
169
193
|
if (!hasTmux()) {
|
|
170
194
|
throw new Error("tmux is required for background jobs. Install with: brew install tmux");
|
package/dist/landing.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type LandingExchangeStatus = {
|
|
|
5
5
|
exchange: string;
|
|
6
6
|
/** Venue has no account API — rendered as market data only. */
|
|
7
7
|
marketDataOnly?: boolean;
|
|
8
|
+
/** Venue is read through an undocumented API (VenueDefinition.apiStatus) — labelled "unofficial API". */
|
|
9
|
+
unofficialApi?: boolean;
|
|
8
10
|
ok: boolean;
|
|
9
11
|
equity: number;
|
|
10
12
|
positions: number;
|
package/dist/landing.js
CHANGED
|
@@ -25,7 +25,7 @@ export function renderLandingExchangeLine(status, asterAgentMissing) {
|
|
|
25
25
|
return ` ${chalk.yellow("⚙")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.yellow("agent required")} ${chalk.gray("→ perp wallet agent approve aster")}`;
|
|
26
26
|
}
|
|
27
27
|
if (status.marketDataOnly) {
|
|
28
|
-
return ` ${chalk.gray("◇")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.gray("market data only")}`;
|
|
28
|
+
return ` ${chalk.gray("◇")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.gray("market data only")}${status.unofficialApi ? chalk.yellow(" (unofficial API)") : ""}`;
|
|
29
29
|
}
|
|
30
30
|
const icon = status.ok ? chalk.green("●") : chalk.red("○");
|
|
31
31
|
const equity = status.ok
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only MCP tools for Pacifica Print and vaults (unauthenticated GETs; nothing is signed).
|
|
3
|
+
* Print deposits / ends and vault deposits / withdrawals are NOT exposed over MCP: the server has
|
|
4
|
+
* no money-moving deposit / withdraw tools (funds moves are CLI-only, `perp funds …`), and these
|
|
5
|
+
* follow the same rule — use `perp print deposit` / `perp vault deposit|withdraw` (with --dry-run).
|
|
6
|
+
*/
|
|
7
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import type { Network } from "./exchanges/venue-types.js";
|
|
9
|
+
export declare function registerPacificaEarnMcpTools(server: McpServer, network: Network, ok: (data: unknown, meta?: Record<string, unknown>) => string, errOf: (e: unknown, meta?: Record<string, unknown>) => string): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PacificaClient } from "./pacifica/client.js";
|
|
3
|
+
import { fetchPrintGames, fetchPrintHistory, fetchPrintMarket, fetchPrintPositions, findPrintGame, quotePrintOrder } from "./exchanges/pacifica-print.js";
|
|
4
|
+
import { DEPOSIT_WHITELIST_API_STATUS, fetchDepositWhitelist, fetchVaults, findVault, heldShares, withdrawWindowAt } from "./exchanges/pacifica-vaults.js";
|
|
5
|
+
export function registerPacificaEarnMcpTools(server, network, ok, errOf) {
|
|
6
|
+
const client = () => new PacificaClient({ network });
|
|
7
|
+
const run = async (meta, f) => {
|
|
8
|
+
try {
|
|
9
|
+
return { content: [{ type: "text", text: ok(await f(), meta) }] };
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
return { content: [{ type: "text", text: errOf(e, meta) }], isError: true };
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const address = z.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/, "base58 Solana address");
|
|
16
|
+
server.tool("pacifica_print_products", "Pacifica Print products (read-only): game id (e.g. BTC_24H, BTC_7D, BTC_1M), cycle length in days, max leverage, deposit caps, allowed target distance from mark. Print = a strike order that earns a premium each cycle; while resting it fills / is liquidated only at a cycle checkpoint, after a fill it is an ordinary perp position (continuous liquidation).", {}, async () => run({ exchange: "pacifica" }, async () => fetchPrintGames(client())));
|
|
17
|
+
server.tool("pacifica_print_quote", "Validate a Pacifica Print order and preview it (read-only, nothing signed): notional, size on fill, docs-formula liquidation price (an outer bound — a liquidation was observed earlier in one live case; venue trigger unpublished), max loss (deposit + premiums), lock period. The premium is not quoted (the venue's preview is a signed request). Placing the order is CLI-only: perp -e pacifica print deposit … (supports --dry-run).", {
|
|
18
|
+
game: z.string().describe("Print product, e.g. BTC_24H"),
|
|
19
|
+
direction: z.enum(["long", "short"]).describe("long = target below mark, short = target above mark"),
|
|
20
|
+
deposit: z.string().describe("USDC deposit, min 10"),
|
|
21
|
+
strike: z.string().describe("Target price (on the market tick)"),
|
|
22
|
+
leverage: z.string().describe("Whole-number leverage 1 … product max"),
|
|
23
|
+
cycles: z.number().int().optional().describe("Cycles 1 … 1000; omit = roll until cancelled"),
|
|
24
|
+
}, async ({ game, direction, deposit, strike, leverage, cycles }) => run({ exchange: "pacifica", game }, async () => {
|
|
25
|
+
const c = client();
|
|
26
|
+
const g = findPrintGame(await fetchPrintGames(c), game);
|
|
27
|
+
return quotePrintOrder(g, { game: g.game, direction, deposit, strikePrice: strike, leverage, cycles: cycles ?? null }, await fetchPrintMarket(c, g.asset));
|
|
28
|
+
}));
|
|
29
|
+
server.tool("pacifica_print_positions", "Pacifica Print orders of an account (read-only): current orders, or with history=true every order (paginated: limit / cursor). Status printing | cancelling | filled | liquidated | cancelled | completed | ended.", {
|
|
30
|
+
account: address.describe("Pacifica account (Solana address)"),
|
|
31
|
+
history: z.boolean().optional().describe("true = every order (history_summary), default false = current orders"),
|
|
32
|
+
limit: z.number().int().positive().optional(),
|
|
33
|
+
cursor: z.string().optional(),
|
|
34
|
+
}, async ({ account, history, limit, cursor }) => run({ exchange: "pacifica", account }, async () => {
|
|
35
|
+
if (history !== true)
|
|
36
|
+
return { account, positions: await fetchPrintPositions(client(), account) };
|
|
37
|
+
const page = await fetchPrintHistory(client(), account, { limit: limit ?? null, cursor: cursor ?? null });
|
|
38
|
+
return { account, positions: page.items, hasMore: page.hasMore, nextCursor: page.nextCursor };
|
|
39
|
+
}));
|
|
40
|
+
server.tool("pacifica_vaults", "Pacifica vaults (API: lakes), read-only. Without vault: every vault (TVL, NAV per share, fee, lock-up, withdrawal window, config); with account: plus that account's shares. With vault (address, or a nickname matching exactly one vault — an ambiguous nickname is INVALID_PARAMS listing the candidates): one vault plus live account equity, positions, deposit whitelist (unofficial endpoint, advisory) and whether withdrawals are open now. APY is not published (null). Deposits / withdrawals are CLI-only: perp -e pacifica vault deposit|withdraw (supports --dry-run).", {
|
|
41
|
+
vault: z.string().optional().describe("Vault address or exact nickname"),
|
|
42
|
+
account: address.optional().describe("Include this account's shares"),
|
|
43
|
+
}, async ({ vault, account }) => run({ exchange: "pacifica" }, async () => {
|
|
44
|
+
const c = client();
|
|
45
|
+
const vaults = await fetchVaults(c, account ?? null);
|
|
46
|
+
if (vault === undefined)
|
|
47
|
+
return vaults;
|
|
48
|
+
const v = findVault(vaults, vault);
|
|
49
|
+
const [acct, positions, whitelist] = await Promise.all([c.getAccount(v.address), c.getPositions(v.address), fetchDepositWhitelist(c, v.address).catch(() => null)]);
|
|
50
|
+
return {
|
|
51
|
+
...v,
|
|
52
|
+
...(account !== undefined ? { holding: heldShares(v, account) } : {}),
|
|
53
|
+
live: { accountEquity: acct.account_equity ?? null, availableToWithdraw: acct.available_to_withdraw ?? null, positions: positions.map((p) => ({ symbol: p.symbol, side: p.side, amount: p.amount, entryPrice: p.entry_price })) },
|
|
54
|
+
depositWhitelist: { apiStatus: DEPOSIT_WHITELIST_API_STATUS, addresses: whitelist },
|
|
55
|
+
withdrawWindow: withdrawWindowAt(v.config, Date.now()),
|
|
56
|
+
};
|
|
57
|
+
}));
|
|
58
|
+
}
|