perp-cli 0.12.17 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -27
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +286 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +70 -92
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +286 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +939 -245
- package/dist/commands/history.js +72 -45
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +316 -337
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +219 -6
- package/dist/exchanges/aster.js +1023 -153
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +166 -0
- package/dist/exchanges/ondo/adapter.js +835 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +122 -0
- package/dist/exchanges/ondo/funds.js +290 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +288 -4
- package/dist/exchanges/pacifica.js +1013 -76
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1383 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +34 -0
- package/dist/exchanges/venues/ondo.js +140 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +28 -0
- package/dist/exchanges/venues/qfex.js +132 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +156 -684
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +7 -2
- package/dist/landing.js +19 -3
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +820 -402
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +142 -55
- package/skills/perp-cli/references/agent-operations.md +110 -56
- package/skills/perp-cli/references/commands.md +266 -71
- package/skills/perp-cli/references/strategies.md +34 -31
- package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
- package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +31 -15
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variables that carry key material (SSOT Rule #3).
|
|
3
|
+
*
|
|
4
|
+
* Child processes (tmux background jobs, the OWS CLI) never receive these: they
|
|
5
|
+
* are stripped from the child's environment, and a background job resolves its
|
|
6
|
+
* credentials itself from the OWS vault / agent keystores / ~/.perp/.env.
|
|
7
|
+
*/
|
|
8
|
+
/** Every secret-bearing env var perp-cli (or the OWS CLI) reads. */
|
|
9
|
+
export declare const SECRET_ENV_NAMES: readonly string[];
|
|
10
|
+
/** True when an env var name is (or looks like) key material. */
|
|
11
|
+
export declare function isSecretEnvName(name: string): boolean;
|
|
12
|
+
/** Secret env var names to remove from a child: the fixed list plus any secret-looking name in `env`. */
|
|
13
|
+
export declare function secretEnvNamesIn(env: NodeJS.ProcessEnv): string[];
|
|
14
|
+
/** A copy of `env` without any secret-bearing variable. */
|
|
15
|
+
export declare function scrubSecretEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variables that carry key material (SSOT Rule #3).
|
|
3
|
+
*
|
|
4
|
+
* Child processes (tmux background jobs, the OWS CLI) never receive these: they
|
|
5
|
+
* are stripped from the child's environment, and a background job resolves its
|
|
6
|
+
* credentials itself from the OWS vault / agent keystores / ~/.perp/.env.
|
|
7
|
+
*/
|
|
8
|
+
/** Every secret-bearing env var perp-cli (or the OWS CLI) reads. */
|
|
9
|
+
export const SECRET_ENV_NAMES = [
|
|
10
|
+
"PRIVATE_KEY", "pk",
|
|
11
|
+
"PACIFICA_PRIVATE_KEY", "HL_PRIVATE_KEY", "HYPERLIQUID_PRIVATE_KEY",
|
|
12
|
+
"LIGHTER_PRIVATE_KEY", "LT_PRIVATE_KEY", "ASTER_PRIVATE_KEY", "LIGHTER_API_KEY",
|
|
13
|
+
"OWS_PASSPHRASE", "OWS_API_KEY", "OWS_PRIVATE_KEY", "OWS_MNEMONIC",
|
|
14
|
+
"LWS_PASSPHRASE", "LWS_PRIVATE_KEY", "LWS_MNEMONIC",
|
|
15
|
+
];
|
|
16
|
+
const SECRET_NAME_PATTERN = /PRIVATE_KEY|PASSPHRASE|SECRET|MNEMONIC|(^|_)API_KEY$/i;
|
|
17
|
+
/** True when an env var name is (or looks like) key material. */
|
|
18
|
+
export function isSecretEnvName(name) {
|
|
19
|
+
return SECRET_ENV_NAMES.includes(name) || SECRET_NAME_PATTERN.test(name);
|
|
20
|
+
}
|
|
21
|
+
/** Secret env var names to remove from a child: the fixed list plus any secret-looking name in `env`. */
|
|
22
|
+
export function secretEnvNamesIn(env) {
|
|
23
|
+
const names = new Set(SECRET_ENV_NAMES);
|
|
24
|
+
for (const k of Object.keys(env))
|
|
25
|
+
if (isSecretEnvName(k))
|
|
26
|
+
names.add(k);
|
|
27
|
+
return [...names];
|
|
28
|
+
}
|
|
29
|
+
/** A copy of `env` without any secret-bearing variable. */
|
|
30
|
+
export function scrubSecretEnv(env) {
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const [k, v] of Object.entries(env))
|
|
33
|
+
if (!isSecretEnvName(k))
|
|
34
|
+
out[k] = v;
|
|
35
|
+
return out;
|
|
36
|
+
}
|
package/dist/settings.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { VenueId } from "./exchanges/venue-types.js";
|
|
2
|
+
import { type BuilderSettings } from "./exchanges/builder-fee.js";
|
|
1
3
|
export interface ExchangeFees {
|
|
2
4
|
taker: number;
|
|
3
5
|
maker: number;
|
|
@@ -74,34 +76,122 @@ export interface AgentMeta {
|
|
|
74
76
|
owsPolicyId: string;
|
|
75
77
|
expiresAt: string;
|
|
76
78
|
approvedAt: string;
|
|
79
|
+
/**
|
|
80
|
+
* canWithdraw null = unknown: a key imported from the exchange website (its withdraw
|
|
81
|
+
* permission is set there and cannot be read back). Never treated as true (rotate does not
|
|
82
|
+
* inherit it; Aster withdraw requires === true).
|
|
83
|
+
*/
|
|
77
84
|
permissions: {
|
|
78
85
|
canPerpTrade: boolean;
|
|
79
86
|
canSpotTrade: boolean;
|
|
80
|
-
canWithdraw: boolean;
|
|
87
|
+
canWithdraw: boolean | null;
|
|
81
88
|
};
|
|
82
89
|
asterApprovalNonce: string;
|
|
83
90
|
status?: "active" | "partial";
|
|
91
|
+
/**
|
|
92
|
+
* Network the agent was registered on. Every approve/rotate writes it.
|
|
93
|
+
* Records written before network tagging lack it — see agentNetworkOf()
|
|
94
|
+
* in agent-wallet/store.ts for how those are classified.
|
|
95
|
+
*/
|
|
96
|
+
network?: "mainnet" | "testnet";
|
|
97
|
+
/**
|
|
98
|
+
* How the key got here: "approved" = this CLI signed the approval with a master wallet on this
|
|
99
|
+
* machine; "imported" = created on the exchange website and imported with
|
|
100
|
+
* `perp wallet agent import` (the master is known only by its public address and
|
|
101
|
+
* `masterWalletName` is ""). Records written before this field are "approved" — see
|
|
102
|
+
* agentSourceOf() in agent-wallet/store.ts.
|
|
103
|
+
*/
|
|
104
|
+
source?: AgentSource;
|
|
105
|
+
}
|
|
106
|
+
/** See AgentMeta.source. */
|
|
107
|
+
export type AgentSource = "approved" | "imported";
|
|
108
|
+
/** Ondo Perps API key (HMAC). The secret lives only in ~/.perp/ondo-agents/<keyId>.json (SSOT Rule #3). */
|
|
109
|
+
export interface OndoAgentMeta {
|
|
110
|
+
kind: "ondo";
|
|
111
|
+
agentName: string;
|
|
112
|
+
keyId: string;
|
|
113
|
+
keystorePath: string;
|
|
114
|
+
scopes: string[] | null;
|
|
115
|
+
/** Network the key was verified against at approve time; adapters refuse a mismatch. */
|
|
116
|
+
network: "mainnet" | "testnet";
|
|
117
|
+
approvedAt: string;
|
|
118
|
+
/** Ondo keys are always created in the Ondo Perps web app; absent on records written before this field. */
|
|
119
|
+
source?: AgentSource;
|
|
120
|
+
}
|
|
121
|
+
/** Arcus Ed25519 API key. The private key lives only in OWS wallet `agentWalletName` (SSOT Rule #3). */
|
|
122
|
+
export interface ArcusAgentMeta {
|
|
123
|
+
kind: "arcus";
|
|
124
|
+
agentName: string;
|
|
125
|
+
agentWalletName: string;
|
|
126
|
+
/** 64-hex Ed25519 public key registered via createApiKey. */
|
|
127
|
+
apiKeyHex: string;
|
|
128
|
+
/** Subaccount index 0–9, or 255 for all subaccounts. */
|
|
129
|
+
accountIndex: number;
|
|
130
|
+
ownerAddress: `0x${string}`;
|
|
131
|
+
/** ISO timestamp; Arcus keys expire after at most 180 days. */
|
|
132
|
+
validUntil: string;
|
|
133
|
+
/** Network the key was registered on at approve time; adapters refuse a mismatch. */
|
|
134
|
+
network: "mainnet" | "testnet";
|
|
135
|
+
approvedAt: string;
|
|
136
|
+
/** See AgentMeta.source; absent = "approved" (records written before this field). */
|
|
137
|
+
source?: AgentSource;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* QFEX API key (HMAC), created on the QFEX website (2FA) and imported. The secret lives only in
|
|
141
|
+
* ~/.perp/qfex-agents/<publicKey>.json (SSOT Rule #3).
|
|
142
|
+
*/
|
|
143
|
+
export interface QfexAgentMeta {
|
|
144
|
+
kind: "qfex";
|
|
145
|
+
agentName: string;
|
|
146
|
+
publicKey: string;
|
|
147
|
+
keystorePath: string;
|
|
148
|
+
/**
|
|
149
|
+
* The concrete account UUID this key trades (resolved and confirmed at import: the primary
|
|
150
|
+
* account's id, or the chosen subaccount). Never null — every request names it explicitly.
|
|
151
|
+
*/
|
|
152
|
+
accountId: string;
|
|
153
|
+
/** Whether accountId is the user's primary account (deposits / withdrawals are primary-only). */
|
|
154
|
+
isPrimaryAccount: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* What the import-time read checks showed. INFORMATIONAL ONLY: never used as a gate and never
|
|
157
|
+
* updated from a runtime denial (which may be KYC, terms, region or scope). null = not checked.
|
|
158
|
+
*/
|
|
159
|
+
permissions: {
|
|
160
|
+
viewPositions: boolean | null;
|
|
161
|
+
viewBalance: boolean | null;
|
|
162
|
+
viewOrders: boolean | null;
|
|
163
|
+
executeOrders: boolean | null;
|
|
164
|
+
depositWithdraw: boolean | null;
|
|
165
|
+
};
|
|
166
|
+
/** Network the key was verified against at import; the adapter refuses a mismatch. */
|
|
167
|
+
network: "mainnet" | "testnet";
|
|
168
|
+
approvedAt: string;
|
|
169
|
+
source: "imported";
|
|
84
170
|
}
|
|
171
|
+
export type AnyAgentMeta = AgentMeta | OndoAgentMeta | ArcusAgentMeta | QfexAgentMeta;
|
|
85
172
|
export interface AgentsByExchange {
|
|
86
173
|
aster?: Record<string, AgentMeta>;
|
|
87
174
|
hyperliquid?: Record<string, AgentMeta>;
|
|
88
175
|
pacifica?: Record<string, AgentMeta>;
|
|
89
176
|
lighter?: Record<string, AgentMeta>;
|
|
177
|
+
"lighter-rh"?: Record<string, AgentMeta>;
|
|
178
|
+
ondo?: Record<string, OndoAgentMeta>;
|
|
179
|
+
arcus?: Record<string, ArcusAgentMeta>;
|
|
180
|
+
qfex?: Record<string, QfexAgentMeta>;
|
|
90
181
|
}
|
|
91
182
|
export interface Settings {
|
|
92
183
|
/** Default exchange when -e flag is omitted */
|
|
93
184
|
defaultExchange: string;
|
|
94
185
|
/** Active OWS wallet name (used when no --ows or --wallet flag) */
|
|
95
186
|
owsActiveWallet: string;
|
|
96
|
-
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
187
|
+
/** HL / Lighter referral code: the default for the explicit, one-time
|
|
188
|
+
* `wallet manage referral set` (never applied implicitly on adapter creation).
|
|
189
|
+
* Referral = a one-time referrer binding only — per-order builder fees live in `builder`. */
|
|
99
190
|
referralCodes: {
|
|
100
|
-
pacifica: string;
|
|
101
191
|
hyperliquid: string;
|
|
102
192
|
lighter: string;
|
|
103
193
|
};
|
|
104
|
-
/**
|
|
194
|
+
/** Which exchanges `wallet manage referral set` has applied a referral code on */
|
|
105
195
|
referralApplied: {
|
|
106
196
|
hyperliquid: boolean;
|
|
107
197
|
lighter: boolean;
|
|
@@ -112,6 +202,79 @@ export interface Settings {
|
|
|
112
202
|
alerts: AlertSettings;
|
|
113
203
|
/** OWS-native agent wallets registered per exchange (Phase 2a: Aster only) */
|
|
114
204
|
agents?: AgentsByExchange;
|
|
205
|
+
/**
|
|
206
|
+
* Per-venue slippage bound override (basis points) for market-type orders
|
|
207
|
+
* (design §13.2). Absent key = use `defaultSlippageBps`.
|
|
208
|
+
* Resolved only by resolveSlippage() (src/exchanges/slippage.ts).
|
|
209
|
+
*/
|
|
210
|
+
slippageBps: {
|
|
211
|
+
[K in VenueId]?: number;
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Slippage bound (bps) for every market-type order (entry, close, trigger) when
|
|
215
|
+
* neither --slippage-bps nor slippageBps[venue] is set. Shipped default 50 —
|
|
216
|
+
* a documented, user-visible configuration value (`perp settings show` marks it
|
|
217
|
+
* "(shipped default)", `perp settings set defaultSlippageBps <n>` changes it),
|
|
218
|
+
* not a hidden fallback: see docs/SSOT_RULES.md Rule #2 (shipped defaults).
|
|
219
|
+
*/
|
|
220
|
+
defaultSlippageBps: number;
|
|
221
|
+
/**
|
|
222
|
+
* Per-venue builder fee (src/exchanges/builder-fee.ts). Absent venue = OFF; ships
|
|
223
|
+
* empty. A fee is attached only when the venue approval is also confirmed.
|
|
224
|
+
*/
|
|
225
|
+
builder: BuilderSettings;
|
|
226
|
+
/** Bridge integrations. */
|
|
227
|
+
bridge: BridgeSettings;
|
|
228
|
+
/**
|
|
229
|
+
* Withdraw gate: qfex (API-key withdrawals; absent = refused) and hyperliquid (withdrawals /
|
|
230
|
+
* transfers to a non-owner address; absent = only the owner's own address). Ships empty. See WithdrawGate.
|
|
231
|
+
*/
|
|
232
|
+
withdraw: WithdrawSettings;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Local limits a key-authorized withdrawal must pass before anything is sent. It guards against
|
|
236
|
+
* mistakes and misdirected automation; it is NOT a defence against key theft (a thief does not
|
|
237
|
+
* use this CLI) — that defence is a trading key WITHOUT the withdraw permission.
|
|
238
|
+
*/
|
|
239
|
+
export interface WithdrawGate {
|
|
240
|
+
/** Destination addresses (EVM, lowercase) withdrawals may go to. Changed only with an interactive confirmation. */
|
|
241
|
+
allowlist: string[];
|
|
242
|
+
/** Largest single withdrawal (USDC). */
|
|
243
|
+
maxPerTx: number;
|
|
244
|
+
/** Largest total withdrawn per UTC day (USDC), counted from this machine's withdraw log. */
|
|
245
|
+
maxPerDay: number;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Venues with a withdraw gate. qfex: API-key-authority withdrawals (every destination is gated).
|
|
249
|
+
* hyperliquid: master-signed withdraw3 / usdSend to any address other than the account owner
|
|
250
|
+
* (`funds withdraw hyperliquid --to`, `funds transfer`); the owner's own address needs no gate.
|
|
251
|
+
* Follow-up: ondo.
|
|
252
|
+
*/
|
|
253
|
+
export declare const WITHDRAW_GATE_VENUES: readonly ["qfex", "hyperliquid"];
|
|
254
|
+
export type WithdrawGateVenue = typeof WITHDRAW_GATE_VENUES[number];
|
|
255
|
+
export type WithdrawSettings = {
|
|
256
|
+
[K in WithdrawGateVenue]?: WithdrawGate;
|
|
257
|
+
};
|
|
258
|
+
/** Validate one settings.withdraw.<venue> entry (also used by `perp settings set`). Never clamps. */
|
|
259
|
+
export declare function validateWithdrawGate(venue: string, raw: unknown, invalid: (problem: string) => Error): WithdrawGate;
|
|
260
|
+
/** deBridge DLN affiliate parameters — appended to quotes / create-tx only when set (ships absent). */
|
|
261
|
+
export interface DebridgeAffiliate {
|
|
262
|
+
/** deBridge referral code (attribution only, no fee); null = not sent. */
|
|
263
|
+
referralCode: string | null;
|
|
264
|
+
/** Affiliate fee percent charged on the bridged amount (e.g. 0.1 = 0.1%); null = no fee. Requires feeRecipient. */
|
|
265
|
+
feePercent: number | null;
|
|
266
|
+
/** Address receiving the affiliate fee; null = no fee. Requires feePercent. */
|
|
267
|
+
feeRecipient: string | null;
|
|
268
|
+
}
|
|
269
|
+
export interface BridgeSettings {
|
|
270
|
+
/** null = no affiliate / referral parameters are sent to deBridge. */
|
|
271
|
+
debridgeAffiliate: DebridgeAffiliate | null;
|
|
115
272
|
}
|
|
273
|
+
/** Shipped default slippage bound (bps); see Settings.defaultSlippageBps. */
|
|
274
|
+
export declare const SHIPPED_DEFAULT_SLIPPAGE_BPS: number;
|
|
116
275
|
export declare function loadSettings(): Settings;
|
|
276
|
+
/** Validate bridge.debridgeAffiliate (also used by `perp settings set`). */
|
|
277
|
+
export declare function validateDebridgeAffiliate(raw: unknown, invalid: (problem: string) => Error): DebridgeAffiliate;
|
|
278
|
+
/** A slippage bound in bps: a finite number ≥ 1. Per-venue maxima are enforced by resolveSlippage(). */
|
|
279
|
+
export declare function isValidSlippageBps(v: unknown): v is number;
|
|
117
280
|
export declare function saveSettings(settings: Settings): void;
|
package/dist/settings.js
CHANGED
|
@@ -1,12 +1,59 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isVenueId } from "./exchanges/venue-types.js";
|
|
2
|
+
import { PerpError } from "./errors.js";
|
|
3
|
+
import { parseBuilderSettings } from "./exchanges/builder-fee.js";
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync } from "fs";
|
|
5
|
+
import { writeKeystoreAtomic } from "./agent-wallet/venue-keystore.js";
|
|
2
6
|
import { resolve } from "path";
|
|
3
7
|
const PERP_DIR = resolve(process.env.HOME || "~", ".perp");
|
|
4
8
|
const SETTINGS_FILE = resolve(PERP_DIR, "settings.json");
|
|
9
|
+
/**
|
|
10
|
+
* Venues with a withdraw gate. qfex: API-key-authority withdrawals (every destination is gated).
|
|
11
|
+
* hyperliquid: master-signed withdraw3 / usdSend to any address other than the account owner
|
|
12
|
+
* (`funds withdraw hyperliquid --to`, `funds transfer`); the owner's own address needs no gate.
|
|
13
|
+
* Follow-up: ondo.
|
|
14
|
+
*/
|
|
15
|
+
export const WITHDRAW_GATE_VENUES = ["qfex", "hyperliquid"];
|
|
16
|
+
const EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
17
|
+
/** Validate one settings.withdraw.<venue> entry (also used by `perp settings set`). Never clamps. */
|
|
18
|
+
export function validateWithdrawGate(venue, raw, invalid) {
|
|
19
|
+
if (!WITHDRAW_GATE_VENUES.includes(venue)) {
|
|
20
|
+
throw invalid(`withdraw: "${venue}" has no withdraw gate (venues: ${WITHDRAW_GATE_VENUES.join(", ")})`);
|
|
21
|
+
}
|
|
22
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
23
|
+
throw invalid(`withdraw.${venue} must be { allowlist: [<0x address>…], maxPerTx: <USDC>, maxPerDay: <USDC> }`);
|
|
24
|
+
}
|
|
25
|
+
const g = raw;
|
|
26
|
+
for (const k of Object.keys(g)) {
|
|
27
|
+
if (k !== "allowlist" && k !== "maxPerTx" && k !== "maxPerDay")
|
|
28
|
+
throw invalid(`withdraw.${venue}: unknown key "${k}" (expected allowlist, maxPerTx, maxPerDay)`);
|
|
29
|
+
}
|
|
30
|
+
if (!Array.isArray(g.allowlist) || g.allowlist.some(a => typeof a !== "string" || !EVM_ADDRESS_RE.test(a))) {
|
|
31
|
+
throw invalid(`withdraw.${venue}.allowlist must be a list of 0x-prefixed 20-byte EVM addresses (got ${JSON.stringify(g.allowlist)})`);
|
|
32
|
+
}
|
|
33
|
+
const positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
34
|
+
if (!positive(g.maxPerTx))
|
|
35
|
+
throw invalid(`withdraw.${venue}.maxPerTx must be a number of USDC > 0 (got ${JSON.stringify(g.maxPerTx)})`);
|
|
36
|
+
if (!positive(g.maxPerDay))
|
|
37
|
+
throw invalid(`withdraw.${venue}.maxPerDay must be a number of USDC > 0 (got ${JSON.stringify(g.maxPerDay)})`);
|
|
38
|
+
if (g.maxPerDay < g.maxPerTx)
|
|
39
|
+
throw invalid(`withdraw.${venue}.maxPerDay (${g.maxPerDay}) is below maxPerTx (${g.maxPerTx})`);
|
|
40
|
+
return { allowlist: [...new Set(g.allowlist.map(a => a.toLowerCase()))], maxPerTx: g.maxPerTx, maxPerDay: g.maxPerDay };
|
|
41
|
+
}
|
|
42
|
+
function parseWithdrawSettings(raw) {
|
|
43
|
+
const out = {};
|
|
44
|
+
if (raw === undefined || raw === null)
|
|
45
|
+
return out;
|
|
46
|
+
if (typeof raw !== "object" || Array.isArray(raw))
|
|
47
|
+
throw invalidSettings("withdraw must be an object of { venue: { allowlist, maxPerTx, maxPerDay } }");
|
|
48
|
+
for (const [venue, entry] of Object.entries(raw)) {
|
|
49
|
+
out[venue] = validateWithdrawGate(venue, entry, invalidSettings);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
5
53
|
const DEFAULTS = {
|
|
6
54
|
defaultExchange: "",
|
|
7
55
|
owsActiveWallet: "",
|
|
8
56
|
referralCodes: {
|
|
9
|
-
pacifica: "",
|
|
10
57
|
hyperliquid: "HYPERCASH",
|
|
11
58
|
lighter: "718585MY",
|
|
12
59
|
},
|
|
@@ -24,51 +71,185 @@ const DEFAULTS = {
|
|
|
24
71
|
intervalSec: 300,
|
|
25
72
|
cooldownMin: 60,
|
|
26
73
|
},
|
|
74
|
+
slippageBps: {},
|
|
75
|
+
defaultSlippageBps: 50,
|
|
76
|
+
builder: {},
|
|
77
|
+
bridge: { debridgeAffiliate: null },
|
|
78
|
+
withdraw: {},
|
|
27
79
|
};
|
|
80
|
+
/** Shipped default slippage bound (bps); see Settings.defaultSlippageBps. */
|
|
81
|
+
export const SHIPPED_DEFAULT_SLIPPAGE_BPS = DEFAULTS.defaultSlippageBps;
|
|
82
|
+
function freshDefaults() {
|
|
83
|
+
return {
|
|
84
|
+
...DEFAULTS,
|
|
85
|
+
referralCodes: { ...DEFAULTS.referralCodes },
|
|
86
|
+
referralApplied: { ...DEFAULTS.referralApplied },
|
|
87
|
+
fees: { ...DEFAULTS.fees },
|
|
88
|
+
alerts: { ...DEFAULTS.alerts, rules: [...DEFAULTS.alerts.rules] },
|
|
89
|
+
slippageBps: {},
|
|
90
|
+
builder: {},
|
|
91
|
+
bridge: { debridgeAffiliate: null },
|
|
92
|
+
withdraw: {},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function invalidSettings(problem) {
|
|
96
|
+
return new PerpError("INVALID_PARAMS", `Invalid ${SETTINGS_FILE}: ${problem}`, {
|
|
97
|
+
remediation: `Fix the offending value in ${SETTINGS_FILE} (or move the file aside to start from defaults — it also holds agent registrations), then retry.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* One-time migration (2026-10-02): `referralCodes.pacifica` used to be a builder code
|
|
102
|
+
* attached to every Pacifica order (with an env / "PERPCLI" fallback). Builder fees are
|
|
103
|
+
* now opt-in per venue (`builder.pacifica`), so the old key is dropped from the file and
|
|
104
|
+
* never attached — the user is told once how to opt in explicitly.
|
|
105
|
+
*/
|
|
106
|
+
function migrateLegacyPacificaBuilderCode(stored) {
|
|
107
|
+
const rc = stored.referralCodes;
|
|
108
|
+
if (rc === null || typeof rc !== "object" || !("pacifica" in rc))
|
|
109
|
+
return;
|
|
110
|
+
const old = rc.pacifica;
|
|
111
|
+
delete rc.pacifica;
|
|
112
|
+
writeSettingsFile(stored);
|
|
113
|
+
if (typeof old === "string" && old !== "") {
|
|
114
|
+
process.stderr.write(`[notice] settings referralCodes.pacifica ("${old}") was a Pacifica builder code attached to every order. ` +
|
|
115
|
+
`Builder fees are now opt-in and it is no longer attached. To keep attaching it, set it explicitly: ` +
|
|
116
|
+
`perp settings set builder.pacifica ${old},<maxFeeBps>, ` +
|
|
117
|
+
`then approve it: perp wallet builder approve pacifica\n`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
28
120
|
export function loadSettings() {
|
|
121
|
+
// A missing file is not a failure: the user has not configured anything yet.
|
|
29
122
|
if (!existsSync(SETTINGS_FILE))
|
|
30
|
-
return
|
|
123
|
+
return freshDefaults();
|
|
124
|
+
let stored; // parsed JSON; validated field by field below
|
|
31
125
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
126
|
+
stored = JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
throw invalidSettings(`not valid JSON (${e instanceof Error ? e.message : String(e)})`);
|
|
130
|
+
}
|
|
131
|
+
if (stored === null || typeof stored !== "object" || Array.isArray(stored)) {
|
|
132
|
+
throw invalidSettings("top level must be a JSON object");
|
|
133
|
+
}
|
|
134
|
+
migrateLegacyPacificaBuilderCode(stored);
|
|
135
|
+
// Merge stored fees with defaults (so new exchanges get defaults)
|
|
136
|
+
const fees = { ...DEFAULTS.fees };
|
|
137
|
+
if (stored.fees !== undefined && stored.fees !== null) {
|
|
138
|
+
if (typeof stored.fees !== "object" || Array.isArray(stored.fees))
|
|
139
|
+
throw invalidSettings("fees must be an object of { venue: { taker, maker } }");
|
|
140
|
+
for (const [ex, f] of Object.entries(stored.fees)) {
|
|
141
|
+
// Rule #2: a malformed entry is an error, never silently replaced by the built-in fee.
|
|
142
|
+
const fee = f;
|
|
143
|
+
const ok = (v) => typeof v === "number" && Number.isFinite(v) && v > -1 && v < 1;
|
|
144
|
+
if (fee === null || typeof fee !== "object" || Array.isArray(fee) || !ok(fee.taker) || !ok(fee.maker)) {
|
|
145
|
+
throw invalidSettings(`fees.${ex} must be { taker: <fraction>, maker: <fraction> } (got ${JSON.stringify(f)})`);
|
|
41
146
|
}
|
|
147
|
+
fees[ex] = { taker: fee.taker, maker: fee.maker };
|
|
42
148
|
}
|
|
43
|
-
return {
|
|
44
|
-
defaultExchange: stored.defaultExchange ?? DEFAULTS.defaultExchange,
|
|
45
|
-
owsActiveWallet: stored.owsActiveWallet ?? DEFAULTS.owsActiveWallet,
|
|
46
|
-
referralCodes: {
|
|
47
|
-
pacifica: stored.referralCodes?.pacifica ?? DEFAULTS.referralCodes.pacifica,
|
|
48
|
-
hyperliquid: stored.referralCodes?.hyperliquid ?? DEFAULTS.referralCodes.hyperliquid,
|
|
49
|
-
lighter: stored.referralCodes?.lighter ?? DEFAULTS.referralCodes.lighter,
|
|
50
|
-
},
|
|
51
|
-
referralApplied: {
|
|
52
|
-
hyperliquid: stored.referralApplied?.hyperliquid ?? DEFAULTS.referralApplied.hyperliquid,
|
|
53
|
-
lighter: stored.referralApplied?.lighter ?? DEFAULTS.referralApplied.lighter,
|
|
54
|
-
},
|
|
55
|
-
alerts: {
|
|
56
|
-
discord: stored.alerts?.discord ?? DEFAULTS.alerts.discord,
|
|
57
|
-
telegram: stored.alerts?.telegram ?? DEFAULTS.alerts.telegram,
|
|
58
|
-
rules: Array.isArray(stored.alerts?.rules) ? stored.alerts.rules : DEFAULTS.alerts.rules,
|
|
59
|
-
intervalSec: stored.alerts?.intervalSec ?? DEFAULTS.alerts.intervalSec,
|
|
60
|
-
cooldownMin: stored.alerts?.cooldownMin ?? DEFAULTS.alerts.cooldownMin,
|
|
61
|
-
},
|
|
62
|
-
fees,
|
|
63
|
-
agents: stored.agents && typeof stored.agents === "object" ? stored.agents : undefined,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return { ...DEFAULTS, referralCodes: { ...DEFAULTS.referralCodes }, fees: { ...DEFAULTS.fees }, alerts: { ...DEFAULTS.alerts } };
|
|
68
149
|
}
|
|
150
|
+
return {
|
|
151
|
+
defaultExchange: stored.defaultExchange ?? DEFAULTS.defaultExchange,
|
|
152
|
+
owsActiveWallet: stored.owsActiveWallet ?? DEFAULTS.owsActiveWallet,
|
|
153
|
+
referralCodes: {
|
|
154
|
+
hyperliquid: stored.referralCodes?.hyperliquid ?? DEFAULTS.referralCodes.hyperliquid,
|
|
155
|
+
lighter: stored.referralCodes?.lighter ?? DEFAULTS.referralCodes.lighter,
|
|
156
|
+
},
|
|
157
|
+
referralApplied: {
|
|
158
|
+
hyperliquid: stored.referralApplied?.hyperliquid ?? DEFAULTS.referralApplied.hyperliquid,
|
|
159
|
+
lighter: stored.referralApplied?.lighter ?? DEFAULTS.referralApplied.lighter,
|
|
160
|
+
},
|
|
161
|
+
alerts: {
|
|
162
|
+
discord: stored.alerts?.discord ?? DEFAULTS.alerts.discord,
|
|
163
|
+
telegram: stored.alerts?.telegram ?? DEFAULTS.alerts.telegram,
|
|
164
|
+
rules: Array.isArray(stored.alerts?.rules) ? stored.alerts.rules : DEFAULTS.alerts.rules,
|
|
165
|
+
intervalSec: stored.alerts?.intervalSec ?? DEFAULTS.alerts.intervalSec,
|
|
166
|
+
cooldownMin: stored.alerts?.cooldownMin ?? DEFAULTS.alerts.cooldownMin,
|
|
167
|
+
},
|
|
168
|
+
fees,
|
|
169
|
+
agents: stored.agents && typeof stored.agents === "object" ? stored.agents : undefined,
|
|
170
|
+
slippageBps: parseSlippageBps(stored.slippageBps),
|
|
171
|
+
defaultSlippageBps: parseDefaultSlippageBps(stored.defaultSlippageBps),
|
|
172
|
+
builder: parseBuilderSettings(stored.builder, invalidSettings),
|
|
173
|
+
bridge: { debridgeAffiliate: parseDebridgeAffiliate(stored.bridge) },
|
|
174
|
+
withdraw: parseWithdrawSettings(stored.withdraw),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function parseDebridgeAffiliate(rawBridge) {
|
|
178
|
+
if (rawBridge === undefined || rawBridge === null)
|
|
179
|
+
return null;
|
|
180
|
+
if (typeof rawBridge !== "object" || Array.isArray(rawBridge))
|
|
181
|
+
throw invalidSettings("bridge must be an object");
|
|
182
|
+
for (const k of Object.keys(rawBridge)) {
|
|
183
|
+
if (k !== "debridgeAffiliate")
|
|
184
|
+
throw invalidSettings(`bridge: unknown key "${k}" (expected debridgeAffiliate)`);
|
|
185
|
+
}
|
|
186
|
+
const raw = rawBridge.debridgeAffiliate;
|
|
187
|
+
if (raw === undefined || raw === null)
|
|
188
|
+
return null;
|
|
189
|
+
return validateDebridgeAffiliate(raw, invalidSettings);
|
|
190
|
+
}
|
|
191
|
+
/** Validate bridge.debridgeAffiliate (also used by `perp settings set`). */
|
|
192
|
+
export function validateDebridgeAffiliate(raw, invalid) {
|
|
193
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
194
|
+
throw invalid("bridge.debridgeAffiliate must be { referralCode, feePercent, feeRecipient } (each may be null)");
|
|
195
|
+
}
|
|
196
|
+
const a = raw;
|
|
197
|
+
for (const k of Object.keys(a)) {
|
|
198
|
+
if (k !== "referralCode" && k !== "feePercent" && k !== "feeRecipient")
|
|
199
|
+
throw invalid(`bridge.debridgeAffiliate: unknown key "${k}"`);
|
|
200
|
+
}
|
|
201
|
+
const referralCode = a.referralCode ?? null;
|
|
202
|
+
const feePercent = a.feePercent ?? null;
|
|
203
|
+
const feeRecipient = a.feeRecipient ?? null;
|
|
204
|
+
if (referralCode !== null && (typeof referralCode !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(referralCode))) {
|
|
205
|
+
throw invalid(`bridge.debridgeAffiliate.referralCode must be 1-64 chars of A-Z a-z 0-9 _ - (got ${JSON.stringify(referralCode)})`);
|
|
206
|
+
}
|
|
207
|
+
if (feePercent !== null && (typeof feePercent !== "number" || !Number.isFinite(feePercent) || feePercent <= 0 || feePercent > 1)) {
|
|
208
|
+
throw invalid(`bridge.debridgeAffiliate.feePercent must be a percent in (0, 1] (got ${JSON.stringify(feePercent)})`);
|
|
209
|
+
}
|
|
210
|
+
if (feeRecipient !== null && (typeof feeRecipient !== "string" || feeRecipient.trim() === "")) {
|
|
211
|
+
throw invalid(`bridge.debridgeAffiliate.feeRecipient must be an address string (got ${JSON.stringify(feeRecipient)})`);
|
|
212
|
+
}
|
|
213
|
+
if ((feePercent === null) !== (feeRecipient === null)) {
|
|
214
|
+
throw invalid("bridge.debridgeAffiliate: feePercent and feeRecipient must be set together");
|
|
215
|
+
}
|
|
216
|
+
return { referralCode: referralCode, feePercent: feePercent, feeRecipient: feeRecipient };
|
|
217
|
+
}
|
|
218
|
+
/** A slippage bound in bps: a finite number ≥ 1. Per-venue maxima are enforced by resolveSlippage(). */
|
|
219
|
+
export function isValidSlippageBps(v) {
|
|
220
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 1;
|
|
221
|
+
}
|
|
222
|
+
function parseDefaultSlippageBps(raw) {
|
|
223
|
+
if (raw === undefined)
|
|
224
|
+
return DEFAULTS.defaultSlippageBps;
|
|
225
|
+
if (!isValidSlippageBps(raw))
|
|
226
|
+
throw invalidSettings(`defaultSlippageBps must be a number of basis points ≥ 1 (got ${JSON.stringify(raw)})`);
|
|
227
|
+
return raw;
|
|
228
|
+
}
|
|
229
|
+
function parseSlippageBps(raw) {
|
|
230
|
+
const out = {};
|
|
231
|
+
if (raw === undefined || raw === null)
|
|
232
|
+
return out;
|
|
233
|
+
if (typeof raw !== "object" || Array.isArray(raw))
|
|
234
|
+
throw invalidSettings("slippageBps must be an object of { venue: bps }");
|
|
235
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
236
|
+
if (!isVenueId(k))
|
|
237
|
+
throw invalidSettings(`slippageBps: unknown venue "${k}"`);
|
|
238
|
+
if (!isValidSlippageBps(v))
|
|
239
|
+
throw invalidSettings(`slippageBps.${k} must be a number of basis points ≥ 1 (got ${JSON.stringify(v)})`);
|
|
240
|
+
out[k] = v;
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
69
243
|
}
|
|
70
244
|
export function saveSettings(settings) {
|
|
245
|
+
writeSettingsFile(settings);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The one writer of settings.json: tmp file (0600) + fsync + rename over the target, so a crash
|
|
249
|
+
* or full disk never leaves a truncated settings.json (which loadSettings would refuse).
|
|
250
|
+
*/
|
|
251
|
+
function writeSettingsFile(contents) {
|
|
71
252
|
if (!existsSync(PERP_DIR))
|
|
72
253
|
mkdirSync(PERP_DIR, { recursive: true, mode: 0o700 });
|
|
73
|
-
|
|
254
|
+
writeKeystoreAtomic(SETTINGS_FILE, JSON.stringify(contents, null, 2));
|
|
74
255
|
}
|
package/dist/signer/ows-evm.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EvmSigner } from "./interface.js";
|
|
2
|
+
import type { Transaction } from "ethers";
|
|
2
3
|
/**
|
|
3
4
|
* EVM signer backed by Open Wallet Standard.
|
|
4
5
|
* Keys never leave the OWS encrypted vault.
|
|
@@ -14,6 +15,13 @@ export declare class OwsEvmSigner implements EvmSigner {
|
|
|
14
15
|
name: string;
|
|
15
16
|
type: string;
|
|
16
17
|
}>>, value: Record<string, unknown>): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Sign an unsigned EVM transaction (on-chain txs sent by the master wallet — Lighter RH
|
|
20
|
+
* deposit, design §13). The digest is computed HERE from the transaction (`unsignedHash`),
|
|
21
|
+
* so callers can never feed an arbitrary 32-byte hash to the master key. Returns the
|
|
22
|
+
* 65-byte 0x signature; the caller attaches it and checks the recovered sender.
|
|
23
|
+
*/
|
|
24
|
+
signTransaction(tx: Transaction): Promise<string>;
|
|
17
25
|
signMessage(message: string | Uint8Array): Promise<string>;
|
|
18
26
|
}
|
|
19
27
|
/**
|
|
@@ -30,3 +38,8 @@ export declare function canonicalizeOwsSignature(result: {
|
|
|
30
38
|
signature: string;
|
|
31
39
|
recoveryId?: number;
|
|
32
40
|
}): string;
|
|
41
|
+
/**
|
|
42
|
+
* The signing digest of an unsigned, chain-bound EVM transaction. Refuses an already-signed
|
|
43
|
+
* tx or one without a chainId (replayable across chains).
|
|
44
|
+
*/
|
|
45
|
+
export declare function unsignedTxDigest(tx: Transaction): string;
|
package/dist/signer/ows-evm.js
CHANGED
|
@@ -42,6 +42,17 @@ export class OwsEvmSigner {
|
|
|
42
42
|
const result = ows.signTypedData(this._walletName, "ethereum", typedData, this._passphrase);
|
|
43
43
|
return canonicalizeOwsSignature(result);
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Sign an unsigned EVM transaction (on-chain txs sent by the master wallet — Lighter RH
|
|
47
|
+
* deposit, design §13). The digest is computed HERE from the transaction (`unsignedHash`),
|
|
48
|
+
* so callers can never feed an arbitrary 32-byte hash to the master key. Returns the
|
|
49
|
+
* 65-byte 0x signature; the caller attaches it and checks the recovered sender.
|
|
50
|
+
*/
|
|
51
|
+
async signTransaction(tx) {
|
|
52
|
+
const digest = unsignedTxDigest(tx);
|
|
53
|
+
const ows = loadOws();
|
|
54
|
+
return canonicalizeOwsSignature(ows.signHash(this._walletName, "ethereum", digest, this._passphrase));
|
|
55
|
+
}
|
|
45
56
|
async signMessage(message) {
|
|
46
57
|
const ows = loadOws();
|
|
47
58
|
const msgStr = typeof message === "string"
|
|
@@ -84,3 +95,14 @@ function inferEip712DomainType(key) {
|
|
|
84
95
|
default: return "string";
|
|
85
96
|
}
|
|
86
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The signing digest of an unsigned, chain-bound EVM transaction. Refuses an already-signed
|
|
100
|
+
* tx or one without a chainId (replayable across chains).
|
|
101
|
+
*/
|
|
102
|
+
export function unsignedTxDigest(tx) {
|
|
103
|
+
if (tx.signature !== null)
|
|
104
|
+
throw new Error("unsignedTxDigest: transaction is already signed");
|
|
105
|
+
if (tx.chainId === 0n)
|
|
106
|
+
throw new Error("unsignedTxDigest: transaction has no chainId (replay-unsafe)");
|
|
107
|
+
return tx.unsignedHash;
|
|
108
|
+
}
|
package/dist/smart-order.d.ts
CHANGED
|
@@ -16,12 +16,19 @@
|
|
|
16
16
|
* - Saves 5-50+ bps vs raw market orders on thin books
|
|
17
17
|
* - Predictable execution price
|
|
18
18
|
*
|
|
19
|
+
* Order-outcome typing (design §12.3): every failure BEFORE the IOC is sent (orderbook read,
|
|
20
|
+
* empty book, unusable price), an explicit venue rejection embedded in the response, and an
|
|
21
|
+
* explicit IOC no-match (nothing filled, nothing rests) throw a definite ORDER_REJECTED
|
|
22
|
+
* (orderNotSent), so placePerpVerified does not quarantine a leg that never traded. Anything
|
|
23
|
+
* else the IOC call throws is rethrown unchanged (placePerpVerified measures it).
|
|
24
|
+
*
|
|
19
25
|
* SSOT rule #2: silent fallback is forbidden. The opt-in `fallback: true` flag
|
|
20
26
|
* is preserved for callers that explicitly want best-effort market substitution
|
|
21
27
|
* after IOC reject, but the DEFAULT is `false` — failure must surface as a
|
|
22
28
|
* thrown error, never a silent market sweep.
|
|
23
29
|
*/
|
|
24
30
|
import type { ExchangeAdapter } from "./exchanges/index.js";
|
|
31
|
+
import { type ResolvedSlippage } from "./exchanges/slippage.js";
|
|
25
32
|
export interface SmartOrderOpts {
|
|
26
33
|
/** Extra ticks of tolerance beyond best price. Default: 1 */
|
|
27
34
|
tickTolerance?: number;
|
|
@@ -32,6 +39,8 @@ export interface SmartOrderOpts {
|
|
|
32
39
|
fallback?: boolean;
|
|
33
40
|
/** Reduce only flag for closing positions. Default: false */
|
|
34
41
|
reduceOnly?: boolean;
|
|
42
|
+
/** Bound for the market fallback; omitted → resolveSlippage(venue, null). */
|
|
43
|
+
slippage?: ResolvedSlippage;
|
|
35
44
|
}
|
|
36
45
|
export interface SmartOrderResult {
|
|
37
46
|
result: unknown;
|