@raintree-technology/perps 0.1.0 → 0.1.2
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/CHANGELOG.md +45 -1
- package/README.md +7 -2
- package/dist/adapters/decibel/order-manager.js +4 -4
- package/dist/adapters/decibel/rest-client.js +1 -1
- package/dist/adapters/decibel/ws-feed.js +14 -2
- package/dist/adapters/decibel.js +5 -5
- package/dist/commands/arb/basis-execute.js +2 -5
- package/dist/commands/arb/execute.js +2 -5
- package/dist/commands/asset/book-simple.js +10 -2
- package/dist/commands/markets/ls-simple.js +5 -2
- package/dist/commands/setup/index.js +292 -4
- package/dist/index.js +0 -0
- package/dist/lib/config.js +10 -5
- package/dist/lib/credential-vault.d.ts +8 -0
- package/dist/lib/credential-vault.js +61 -1
- package/dist/lib/onboarding.js +8 -4
- package/dist/lib/prompts.js +12 -1
- package/dist/lib/schema.d.ts +92 -92
- package/dist/lib/schema.js +17 -10
- package/dist/server/index.js +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.2] - 2026-02-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added `perps setup decibel-key` for guided Decibel API key onboarding with:
|
|
14
|
+
- Geomi key-capture guidance in interactive mode
|
|
15
|
+
- live token validation against Decibel REST
|
|
16
|
+
- optional account-overview validation
|
|
17
|
+
- encrypted local vault persistence by default
|
|
18
|
+
- optional env-file export for CI/shell workflows
|
|
19
|
+
- Added encrypted vault helpers for single-secret lifecycle management:
|
|
20
|
+
- `saveSecretToCredentialVault`
|
|
21
|
+
- `getLatestCredentialVaultSecret`
|
|
22
|
+
- Added release-ready Decibel onboarding docs covering mainnet startup, automation boundaries, and CI-safe non-interactive flows.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Updated Decibel default endpoints and package addresses for current Aptos mainnet/testnet deployment values.
|
|
26
|
+
- `loadConfig` now falls back to encrypted-vault Decibel bearer token when `DECIBEL_API_BEARER_TOKEN` is not set in environment.
|
|
27
|
+
- `setup wizard` now consumes vault-stored Decibel bearer token fallback during auth bootstrap.
|
|
28
|
+
- `asset book` now passes resolved exchange credentials and transport URLs into adapter connect flow for cleaner Decibel startup behavior.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- Updated Decibel on-chain function routing to `dex_accounts_entry::*` entrypoints for order placement/cancel/config calls.
|
|
32
|
+
- Hardened Decibel WebSocket shutdown path to avoid close-time crashes when sockets are still in `CONNECTING` state.
|
|
33
|
+
- Clarified Decibel auth/trading error messages to point users directly to Geomi API key source and required env/vault values.
|
|
34
|
+
|
|
35
|
+
### Tests
|
|
36
|
+
- Expanded Decibel coverage for:
|
|
37
|
+
- vault secret persistence/read-back
|
|
38
|
+
- `setup decibel-key` interactive and non-interactive flows
|
|
39
|
+
- config vault-fallback credential resolution
|
|
40
|
+
- Decibel websocket feed and simple orderbook command behavior.
|
|
41
|
+
|
|
42
|
+
## [0.1.1] - 2026-02-25
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- Restored Node 18 CI compatibility by deferring `@inquirer/prompts` loading to runtime and using shared prompt wrappers in arb commands
|
|
46
|
+
- Added a Node 18-safe `globalThis.crypto` fallback for onboarding key generation paths
|
|
47
|
+
- Updated README command-surface tests to strip inline shell comments before execution
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
- Exchange onboarding guides under `docs/exchanges/*` and README links to those guides
|
|
51
|
+
|
|
10
52
|
### Security
|
|
11
53
|
- Replaced `elliptic` and `keccak256` with `@noble/curves` and `@noble/hashes` in Orderly adapter (CVE-2025-14505, CVE-2026-2739)
|
|
12
54
|
- Removed `@types/elliptic` dev dependency
|
|
@@ -29,5 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
29
71
|
- Risk management: limits, drawdown, position sizing
|
|
30
72
|
- Execution journal and safety checks
|
|
31
73
|
|
|
32
|
-
[Unreleased]: https://github.com/raintree-technology/perps/compare/v0.1.
|
|
74
|
+
[Unreleased]: https://github.com/raintree-technology/perps/compare/v0.1.2...HEAD
|
|
75
|
+
[0.1.2]: https://github.com/raintree-technology/perps/releases/tag/v0.1.2
|
|
76
|
+
[0.1.1]: https://github.com/raintree-technology/perps/releases/tag/v0.1.1
|
|
33
77
|
[0.1.0]: https://github.com/raintree-technology/perps/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ perps data ccxt binanceusdm fetchTicker --args '["BTC/USDT:USDT"]' --json
|
|
|
57
57
|
Credentials are handled with care:
|
|
58
58
|
|
|
59
59
|
- **Testnet by default** — mainnet requires explicit `--mainnet` flag or `*_NETWORK=mainnet` env var
|
|
60
|
-
- **Encrypted at rest** — `perps setup wizard`
|
|
60
|
+
- **Encrypted at rest** — setup flows (including `perps setup wizard` and `perps setup decibel-key`) store secrets in an AES-256-GCM local vault (`~/.perp/`)
|
|
61
61
|
- **Owner-only permissions** — all credential files are `chmod 600`, directories `chmod 700`
|
|
62
62
|
- **Env vars supported** — standard `HYPERLIQUID_PRIVATE_KEY`, `AEVO_SIGNING_KEY`, etc. for CI/automation
|
|
63
63
|
- **No telemetry, no phoning home** — your keys and trades stay on your machine
|
|
@@ -87,6 +87,10 @@ All five adapters implement the same `PerpDEXAdapter` interface — swap `Hyperl
|
|
|
87
87
|
# Recommended: interactive wizard
|
|
88
88
|
perps setup wizard
|
|
89
89
|
|
|
90
|
+
# Decibel key helper: guide + validate + store in encrypted vault (env export optional)
|
|
91
|
+
perps setup decibel-key
|
|
92
|
+
perps setup decibel-key --env-file .env.local
|
|
93
|
+
|
|
90
94
|
# Or set env vars directly
|
|
91
95
|
export HYPERLIQUID_PRIVATE_KEY="0x..."
|
|
92
96
|
```
|
|
@@ -98,7 +102,7 @@ export HYPERLIQUID_PRIVATE_KEY="0x..."
|
|
|
98
102
|
|----------|-------|---------|
|
|
99
103
|
| Hyperliquid | `HYPERLIQUID_WALLET_ADDRESS` | `HYPERLIQUID_PRIVATE_KEY` |
|
|
100
104
|
| Aevo | `AEVO_API_KEY` + `AEVO_API_SECRET` | + `AEVO_SIGNING_KEY` |
|
|
101
|
-
| Decibel | `
|
|
105
|
+
| Decibel | Market data: `DECIBEL_API_BEARER_TOKEN`; account reads: + `DECIBEL_API_WALLET_ADDRESS` | + `DECIBEL_API_WALLET_PRIVATE_KEY` |
|
|
102
106
|
| Orderly | `ORDERLY_ACCOUNT_ID` + `ORDERLY_KEY` + `ORDERLY_SECRET` | + `ORDERLY_TRADING_SECRET` |
|
|
103
107
|
| Paradex | `PARADEX_ACCOUNT_ADDRESS` + `PARADEX_PRIVATE_KEY` | same |
|
|
104
108
|
|
|
@@ -125,6 +129,7 @@ perps signal --help # Trade signals
|
|
|
125
129
|
perps replay --help # Execution replay
|
|
126
130
|
perps data --help # Raw exchange data (ccxt, pmxt)
|
|
127
131
|
perps setup --help # Onboarding wizard
|
|
132
|
+
perps setup decibel-key --help # Decibel bearer key bootstrap + vault storage
|
|
128
133
|
perps config --help # Settings (show, get, set)
|
|
129
134
|
perps doctor # Health check
|
|
130
135
|
```
|
|
@@ -27,7 +27,7 @@ export class DecibelOrderManager {
|
|
|
27
27
|
const chainSize = formatSize(sizeUnits, market);
|
|
28
28
|
const isBuy = side === "buy";
|
|
29
29
|
const payload = {
|
|
30
|
-
function: `${this.packageAddress}::
|
|
30
|
+
function: `${this.packageAddress}::dex_accounts_entry::place_order_to_subaccount`,
|
|
31
31
|
typeArguments: [],
|
|
32
32
|
functionArguments: [
|
|
33
33
|
this.subaccountAddress,
|
|
@@ -68,7 +68,7 @@ export class DecibelOrderManager {
|
|
|
68
68
|
}
|
|
69
69
|
async cancelOrder(orderId, marketAddr) {
|
|
70
70
|
const payload = {
|
|
71
|
-
function: `${this.packageAddress}::
|
|
71
|
+
function: `${this.packageAddress}::dex_accounts_entry::cancel_order_to_subaccount`,
|
|
72
72
|
typeArguments: [],
|
|
73
73
|
functionArguments: [this.subaccountAddress, orderId, marketAddr],
|
|
74
74
|
};
|
|
@@ -85,7 +85,7 @@ export class DecibelOrderManager {
|
|
|
85
85
|
}
|
|
86
86
|
async cancelAllOrders(marketAddr) {
|
|
87
87
|
const payload = {
|
|
88
|
-
function: `${this.packageAddress}::
|
|
88
|
+
function: `${this.packageAddress}::dex_accounts_entry::cancel_bulk_order_to_subaccount`,
|
|
89
89
|
typeArguments: [],
|
|
90
90
|
functionArguments: [this.subaccountAddress, marketAddr],
|
|
91
91
|
};
|
|
@@ -103,7 +103,7 @@ export class DecibelOrderManager {
|
|
|
103
103
|
async configureUserSettingsForMarket(marketAddr, isCross, userLeverageBps) {
|
|
104
104
|
const leverageBps = Math.max(1, Math.round(userLeverageBps));
|
|
105
105
|
const payload = {
|
|
106
|
-
function: `${this.packageAddress}::
|
|
106
|
+
function: `${this.packageAddress}::dex_accounts_entry::configure_user_settings_for_market`,
|
|
107
107
|
typeArguments: [],
|
|
108
108
|
functionArguments: [this.subaccountAddress, marketAddr, isCross, leverageBps],
|
|
109
109
|
};
|
|
@@ -112,7 +112,7 @@ export class DecibelRestClient {
|
|
|
112
112
|
};
|
|
113
113
|
if (requiresAuth) {
|
|
114
114
|
if (!this.bearerToken) {
|
|
115
|
-
throw new Error(`Decibel endpoint '${path}' requires DECIBEL_API_BEARER_TOKEN
|
|
115
|
+
throw new Error(`Decibel endpoint '${path}' requires DECIBEL_API_BEARER_TOKEN (Client API key secret from app.decibel.trade -> Geomi -> API Keys).`);
|
|
116
116
|
}
|
|
117
117
|
headers.Authorization = `Bearer ${this.bearerToken}`;
|
|
118
118
|
}
|
|
@@ -41,9 +41,21 @@ export class DecibelWsFeed extends EventEmitter {
|
|
|
41
41
|
this.shouldReconnect = false;
|
|
42
42
|
this.clearReconnectTimer();
|
|
43
43
|
this.stopPing();
|
|
44
|
-
this.ws
|
|
45
|
-
this.ws?.close();
|
|
44
|
+
const ws = this.ws;
|
|
46
45
|
this.ws = null;
|
|
46
|
+
if (!ws) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Closing a CONNECTING socket can emit an error from ws; keep a no-op handler
|
|
50
|
+
// so shutdown does not crash the process.
|
|
51
|
+
if (ws.readyState === WebSocket.CONNECTING) {
|
|
52
|
+
ws.once("error", () => { });
|
|
53
|
+
ws.terminate();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
57
|
+
ws.close();
|
|
58
|
+
}
|
|
47
59
|
}
|
|
48
60
|
isConnected() {
|
|
49
61
|
return this.ws?.readyState === WebSocket.OPEN;
|
package/dist/adapters/decibel.js
CHANGED
|
@@ -14,13 +14,13 @@ const DECIBEL_DEFAULTS = {
|
|
|
14
14
|
fullnodeUrl: "https://api.testnet.aptoslabs.com/v1",
|
|
15
15
|
restUrl: "https://api.testnet.aptoslabs.com/decibel",
|
|
16
16
|
wsUrl: "wss://api.testnet.aptoslabs.com/decibel/ws",
|
|
17
|
-
packageAddress: "
|
|
17
|
+
packageAddress: "0x952535c3049e52f195f26798c2f1340d7dd5100edbe0f464e520a974d16fbe9f",
|
|
18
18
|
},
|
|
19
19
|
mainnet: {
|
|
20
20
|
fullnodeUrl: "https://api.mainnet.aptoslabs.com/v1",
|
|
21
21
|
restUrl: "https://api.mainnet.aptoslabs.com/decibel",
|
|
22
22
|
wsUrl: "wss://api.mainnet.aptoslabs.com/decibel/ws",
|
|
23
|
-
packageAddress: "
|
|
23
|
+
packageAddress: "0x50ead22afd6ffd9769e3b3d6e0e64a2a350d68e8b102c4e72e33d0b8cfdfdb06",
|
|
24
24
|
},
|
|
25
25
|
};
|
|
26
26
|
export class DecibelAdapter {
|
|
@@ -809,15 +809,15 @@ export class DecibelAdapter {
|
|
|
809
809
|
}
|
|
810
810
|
ensureAccountReadAuth() {
|
|
811
811
|
if (!this.accountAddress) {
|
|
812
|
-
throw new Error("Decibel account reads require account address (set DECIBEL_API_WALLET_ADDRESS or
|
|
812
|
+
throw new Error("Decibel account reads require account address (set DECIBEL_API_WALLET_ADDRESS or DECIBEL_SUBACCOUNT_ADDRESS).");
|
|
813
813
|
}
|
|
814
814
|
if (!this.config?.credentials?.apiBearerToken) {
|
|
815
|
-
throw new Error("Decibel account reads require DECIBEL_API_BEARER_TOKEN.");
|
|
815
|
+
throw new Error("Decibel account reads require DECIBEL_API_BEARER_TOKEN (Client API key secret from app.decibel.trade -> Geomi -> API Keys).");
|
|
816
816
|
}
|
|
817
817
|
}
|
|
818
818
|
ensureOrderManager() {
|
|
819
819
|
if (!this.orderManager) {
|
|
820
|
-
throw new Error("Decibel trading requires
|
|
820
|
+
throw new Error("Decibel trading requires DECIBEL_API_WALLET_PRIVATE_KEY, DECIBEL_API_WALLET_ADDRESS, DECIBEL_API_BEARER_TOKEN, and package/network settings.");
|
|
821
821
|
}
|
|
822
822
|
}
|
|
823
823
|
requireMarket(symbol) {
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Execute basis trades (cash-and-carry arbitrage) on a single exchange
|
|
4
4
|
*/
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { confirm } from "@inquirer/prompts";
|
|
7
6
|
import { getContext, getOutputOptions, getSelectedExchange } from "../../cli/program.js";
|
|
8
7
|
import { output, outputError } from "../../cli/output.js";
|
|
8
|
+
import { confirm } from "../../lib/prompts.js";
|
|
9
9
|
import { getExchangeAdapterById } from "../../lib/exchange.js";
|
|
10
10
|
import { DEFAULT_ARB_SIZE_USD } from "../../lib/constants.js";
|
|
11
11
|
import { validateAsset, validateSize } from "../../lib/validate.js";
|
|
@@ -178,10 +178,7 @@ export function registerArbBasisExecuteCommand(arb) {
|
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
180
|
if (!opts.yes) {
|
|
181
|
-
const confirmed = await confirm(
|
|
182
|
-
message: "Execute this basis trade?",
|
|
183
|
-
default: false,
|
|
184
|
-
});
|
|
181
|
+
const confirmed = await confirm("Execute this basis trade?", false);
|
|
185
182
|
if (!confirmed) {
|
|
186
183
|
if (isJson) {
|
|
187
184
|
emitJson({
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Arb Execute Command
|
|
3
3
|
* Execute delta-neutral funding arbitrage across exchanges
|
|
4
4
|
*/
|
|
5
|
-
import { confirm } from "@inquirer/prompts";
|
|
6
5
|
import { getContext, getOutputOptions } from "../../cli/program.js";
|
|
7
6
|
import { output, outputError } from "../../cli/output.js";
|
|
7
|
+
import { confirm } from "../../lib/prompts.js";
|
|
8
8
|
import { getExchangeAdapterById } from "../../lib/exchange.js";
|
|
9
9
|
import { getLatestFundingRates } from "../../lib/db/funding-history.js";
|
|
10
10
|
import { getExchangeIdByName, DEFAULT_ARB_SIZE_USD } from "../../lib/constants.js";
|
|
@@ -196,10 +196,7 @@ export function registerArbExecuteCommand(arb) {
|
|
|
196
196
|
return;
|
|
197
197
|
}
|
|
198
198
|
if (!opts.yes) {
|
|
199
|
-
const confirmed = await confirm(
|
|
200
|
-
message: "Execute this arbitrage trade?",
|
|
201
|
-
default: false,
|
|
202
|
-
});
|
|
199
|
+
const confirmed = await confirm("Execute this arbitrage trade?", false);
|
|
203
200
|
if (!confirmed) {
|
|
204
201
|
if (isJson) {
|
|
205
202
|
emitJson({
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Simple order book command using the adapter interface
|
|
3
3
|
*/
|
|
4
|
-
import { getContext, getOutputOptions } from "../../cli/program.js";
|
|
4
|
+
import { getContext, getOutputOptions, getSelectedExchange } from "../../cli/program.js";
|
|
5
5
|
import { output, outputError } from "../../cli/output.js";
|
|
6
6
|
import { getExchangeAdapter } from "../../lib/exchange.js";
|
|
7
|
+
import { getExchangeCredentials } from "../../lib/config.js";
|
|
7
8
|
export function registerBookSimpleCommand(asset) {
|
|
8
9
|
asset
|
|
9
10
|
.command("book <symbol>")
|
|
@@ -12,12 +13,19 @@ export function registerBookSimpleCommand(asset) {
|
|
|
12
13
|
.action(async function (symbol) {
|
|
13
14
|
const ctx = getContext(this);
|
|
14
15
|
const outputOpts = getOutputOptions(this);
|
|
16
|
+
const exchangeId = getSelectedExchange(this);
|
|
15
17
|
const opts = this.opts();
|
|
16
18
|
const depth = parseInt(opts.depth, 10);
|
|
17
19
|
const adapter = getExchangeAdapter();
|
|
18
20
|
let connected = false;
|
|
19
21
|
try {
|
|
20
|
-
|
|
22
|
+
const credentials = getExchangeCredentials(ctx.config, exchangeId);
|
|
23
|
+
await adapter.connect({
|
|
24
|
+
testnet: ctx.config.testnet,
|
|
25
|
+
rpcUrl: credentials.fullnodeUrl,
|
|
26
|
+
wsUrl: credentials.wsUrl,
|
|
27
|
+
credentials,
|
|
28
|
+
});
|
|
21
29
|
connected = true;
|
|
22
30
|
// Normalize symbol (accept BTC, BTC-PERP, btc, etc.)
|
|
23
31
|
let market = symbol.toUpperCase();
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { output, outputError } from "../../cli/output.js";
|
|
7
7
|
import { getContext, getOutputOptions, getVerbose } from "../../cli/program.js";
|
|
8
8
|
import { getAvailableExchanges, getExchangeAdapter, getExchangeAdapterById, } from "../../lib/exchange.js";
|
|
9
|
+
import { getExchangeCredentials } from "../../lib/config.js";
|
|
9
10
|
import { pLimit } from "../../lib/rate-limit.js";
|
|
10
11
|
export function registerMarketsLsSimpleCommand(markets) {
|
|
11
12
|
markets
|
|
@@ -38,7 +39,8 @@ async function handleSingleExchange(ctx, outputOpts, opts, limit = 0) {
|
|
|
38
39
|
const adapter = getExchangeAdapter();
|
|
39
40
|
let connected = false;
|
|
40
41
|
try {
|
|
41
|
-
|
|
42
|
+
const credentials = getExchangeCredentials(ctx.config, adapter.info.id);
|
|
43
|
+
await adapter.connect({ testnet: ctx.config.testnet, credentials });
|
|
42
44
|
connected = true;
|
|
43
45
|
const markets = await adapter.getMarkets();
|
|
44
46
|
let filtered = markets;
|
|
@@ -88,7 +90,8 @@ async function fetchAllExchangeMarkets(ctx) {
|
|
|
88
90
|
const connectedAdapters = [];
|
|
89
91
|
const tasks = exchangeIds.map((id) => async () => {
|
|
90
92
|
const adapter = getExchangeAdapterById(id);
|
|
91
|
-
|
|
93
|
+
const credentials = getExchangeCredentials(ctx.config, id);
|
|
94
|
+
await adapter.connect({ testnet: ctx.config.testnet, credentials });
|
|
92
95
|
connectedAdapters.push(adapter);
|
|
93
96
|
const markets = await adapter.getMarkets();
|
|
94
97
|
return { id, name: adapter.info.name, markets };
|
|
@@ -1,14 +1,81 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { getOutputOptions } from "../../cli/program.js";
|
|
4
5
|
import { output, outputError, outputSuccess } from "../../cli/output.js";
|
|
5
6
|
import { withJsonContract } from "../../lib/contracts.js";
|
|
6
7
|
import { createAccount, getAccountByAliasForExchange, } from "../../lib/db/index.js";
|
|
7
8
|
import { privateKeyToAccount } from "viem/accounts";
|
|
8
|
-
import { getCredentialVaultSummary, saveOnboardingProfileToVault, } from "../../lib/credential-vault.js";
|
|
9
|
+
import { getCredentialVaultSummary, getLatestCredentialVaultSecret, saveSecretToCredentialVault, saveOnboardingProfileToVault, } from "../../lib/credential-vault.js";
|
|
9
10
|
import { ALL_SETUP_CHAINS, ALL_SETUP_EXCHANGES, buildOnboardingEnvFile, parseSetupChains, parseSetupExchanges, parseSetupMode, resolveExchangesFromChains, runOnboardingStateMachine, } from "../../lib/onboarding.js";
|
|
10
|
-
import { confirm, multiSelect, select } from "../../lib/prompts.js";
|
|
11
|
+
import { confirm, multiSelect, pressEnterOrEsc, prompt, select } from "../../lib/prompts.js";
|
|
11
12
|
import { hardenPrivateFile, PRIVATE_FILE_MODE } from "../../lib/fs-security.js";
|
|
13
|
+
import { FetchError, fetchWithTimeout } from "../../lib/fetch.js";
|
|
14
|
+
const DECIBEL_REST_DEFAULTS = {
|
|
15
|
+
mainnet: "https://api.mainnet.aptoslabs.com/decibel",
|
|
16
|
+
testnet: "https://api.testnet.aptoslabs.com/decibel",
|
|
17
|
+
};
|
|
18
|
+
function normalizeText(value) {
|
|
19
|
+
if (!value)
|
|
20
|
+
return undefined;
|
|
21
|
+
const trimmed = value.trim();
|
|
22
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
23
|
+
}
|
|
24
|
+
function normalizeNetwork(value) {
|
|
25
|
+
const normalized = normalizeText(value)?.toLowerCase();
|
|
26
|
+
if (normalized === "testnet")
|
|
27
|
+
return "testnet";
|
|
28
|
+
return "mainnet";
|
|
29
|
+
}
|
|
30
|
+
function resolveDecibelNetwork(opts, command) {
|
|
31
|
+
if (opts.network) {
|
|
32
|
+
return normalizeNetwork(opts.network);
|
|
33
|
+
}
|
|
34
|
+
const globalOpts = command.optsWithGlobals();
|
|
35
|
+
if (globalOpts.mainnet)
|
|
36
|
+
return "mainnet";
|
|
37
|
+
if (globalOpts.testnet)
|
|
38
|
+
return "testnet";
|
|
39
|
+
return normalizeNetwork(process.env.DECIBEL_NETWORK);
|
|
40
|
+
}
|
|
41
|
+
function resolveDecibelRestUrl(opts, command) {
|
|
42
|
+
const explicit = normalizeText(opts.restUrl) ?? normalizeText(process.env.DECIBEL_REST_URL);
|
|
43
|
+
if (explicit) {
|
|
44
|
+
return explicit.replace(/\/+$/, "");
|
|
45
|
+
}
|
|
46
|
+
return DECIBEL_REST_DEFAULTS[resolveDecibelNetwork(opts, command)];
|
|
47
|
+
}
|
|
48
|
+
function openExternalUrl(url) {
|
|
49
|
+
const platform = process.platform;
|
|
50
|
+
if (platform === "darwin") {
|
|
51
|
+
execFileSync("open", [url], { stdio: "ignore" });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (platform === "win32") {
|
|
55
|
+
execFileSync("cmd", ["/c", "start", "", url], { stdio: "ignore" });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
execFileSync("xdg-open", [url], { stdio: "ignore" });
|
|
59
|
+
}
|
|
60
|
+
function upsertEnvVar(content, key, value) {
|
|
61
|
+
const lines = content.split(/\r?\n/);
|
|
62
|
+
const matcher = new RegExp(`^\\s*#?\\s*${key}=`);
|
|
63
|
+
let replaced = false;
|
|
64
|
+
const next = lines.map((line) => {
|
|
65
|
+
if (matcher.test(line)) {
|
|
66
|
+
replaced = true;
|
|
67
|
+
return `${key}=${value}`;
|
|
68
|
+
}
|
|
69
|
+
return line;
|
|
70
|
+
});
|
|
71
|
+
if (!replaced) {
|
|
72
|
+
if (next.length > 0 && next[next.length - 1].trim() !== "") {
|
|
73
|
+
next.push("");
|
|
74
|
+
}
|
|
75
|
+
next.push(`${key}=${value}`);
|
|
76
|
+
}
|
|
77
|
+
return `${next.join("\n").replace(/\n+$/, "")}\n`;
|
|
78
|
+
}
|
|
12
79
|
const MODE_DESCRIPTIONS = {
|
|
13
80
|
"mainnet-data": "No credentials. Use real mainnet data immediately.",
|
|
14
81
|
"testnet-execution": "Force selected exchanges to testnet for all commands.",
|
|
@@ -37,8 +104,11 @@ function normalizeAuthOptions(opts) {
|
|
|
37
104
|
const orderlyChainId = opts.orderlyChainId ??
|
|
38
105
|
process.env.ORDERLY_CHAIN_ID ??
|
|
39
106
|
process.env.ORDERLY_TESTNET_FAUCET_CHAIN_ID;
|
|
107
|
+
const vaultDecibelBearer = getLatestCredentialVaultSecret("DECIBEL_API_BEARER_TOKEN");
|
|
40
108
|
return {
|
|
41
|
-
decibelBearerToken: opts.decibelBearerToken ??
|
|
109
|
+
decibelBearerToken: opts.decibelBearerToken ??
|
|
110
|
+
process.env.DECIBEL_API_BEARER_TOKEN ??
|
|
111
|
+
vaultDecibelBearer,
|
|
42
112
|
aevoApiKey: opts.aevoApiKey ?? process.env.AEVO_API_KEY,
|
|
43
113
|
aevoApiSecret: opts.aevoApiSecret ?? process.env.AEVO_API_SECRET,
|
|
44
114
|
aevoAccountPrivateKey: (opts.aevoAccountPrivateKey ??
|
|
@@ -302,6 +372,224 @@ function printWizardSummary(args) {
|
|
|
302
372
|
}
|
|
303
373
|
export function registerSetupCommands(program) {
|
|
304
374
|
const setup = program.command("setup").description("Onboarding and environment setup");
|
|
375
|
+
setup
|
|
376
|
+
.command("decibel-key")
|
|
377
|
+
.description("Bootstrap a Decibel Client API key flow, validate it, and store it in the encrypted vault")
|
|
378
|
+
.option("--token <token>", "Decibel Client API key secret (overrides DECIBEL_API_BEARER_TOKEN)")
|
|
379
|
+
.option("--wallet-address <address>", "Account address to check with account_overview")
|
|
380
|
+
.option("--subaccount-address <address>", "Subaccount address to check with account_overview")
|
|
381
|
+
.option("--network <network>", "Decibel network (mainnet or testnet)")
|
|
382
|
+
.option("--rest-url <url>", "Decibel REST URL override")
|
|
383
|
+
.option("--env-file <path>", "Write/update DECIBEL_API_BEARER_TOKEN in this env file")
|
|
384
|
+
.option("--no-vault", "Skip encrypted vault persistence for DECIBEL_API_BEARER_TOKEN")
|
|
385
|
+
.option("--no-open-browser", "Do not open Geomi/API keys pages automatically")
|
|
386
|
+
.option("--require-account", "Fail if account_overview check fails")
|
|
387
|
+
.option("--yes", "Skip interactive confirmations")
|
|
388
|
+
.action(async function () {
|
|
389
|
+
const outputOpts = getOutputOptions(this);
|
|
390
|
+
const opts = this.opts();
|
|
391
|
+
const hasInteractiveTty = process.stdin.isTTY && process.stdout.isTTY;
|
|
392
|
+
const assumeDefaults = Boolean(opts.yes) || !hasInteractiveTty;
|
|
393
|
+
const network = resolveDecibelNetwork(opts, this);
|
|
394
|
+
const restUrl = resolveDecibelRestUrl(opts, this);
|
|
395
|
+
const persistToVault = opts.vault ?? true;
|
|
396
|
+
try {
|
|
397
|
+
const vaultToken = normalizeText(getLatestCredentialVaultSecret("DECIBEL_API_BEARER_TOKEN"));
|
|
398
|
+
let tokenSource = "interactive";
|
|
399
|
+
let token = normalizeText(opts.token);
|
|
400
|
+
if (token) {
|
|
401
|
+
tokenSource = "cli";
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
token = normalizeText(process.env.DECIBEL_API_BEARER_TOKEN);
|
|
405
|
+
if (token) {
|
|
406
|
+
tokenSource = "env";
|
|
407
|
+
}
|
|
408
|
+
else if (vaultToken) {
|
|
409
|
+
token = vaultToken;
|
|
410
|
+
tokenSource = "vault";
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const usedInteractiveCapture = !token;
|
|
414
|
+
if (!token) {
|
|
415
|
+
if (!hasInteractiveTty) {
|
|
416
|
+
throw new Error("No Decibel API key found. Pass --token, set DECIBEL_API_BEARER_TOKEN, or run once interactively to store it in the encrypted vault.");
|
|
417
|
+
}
|
|
418
|
+
console.log("Decibel key generation is currently handled in Geomi (web UI).");
|
|
419
|
+
if (opts.openBrowser ?? true) {
|
|
420
|
+
let shouldOpen = true;
|
|
421
|
+
if (!assumeDefaults) {
|
|
422
|
+
shouldOpen = await pressEnterOrEsc("Press Enter to open Decibel + Geomi API keys pages (or Esc to skip)");
|
|
423
|
+
}
|
|
424
|
+
if (shouldOpen) {
|
|
425
|
+
try {
|
|
426
|
+
openExternalUrl("https://app.decibel.trade");
|
|
427
|
+
openExternalUrl("https://app.decibel.trade/geomi");
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
// Fall through with manual links.
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
console.log("");
|
|
435
|
+
console.log("Create/copy your Key Secret from Geomi -> API Keys, then paste it below.");
|
|
436
|
+
token = normalizeText(await prompt("Paste DECIBEL_API_BEARER_TOKEN (Key Secret):"));
|
|
437
|
+
if (!token) {
|
|
438
|
+
throw new Error("No Decibel API key provided.");
|
|
439
|
+
}
|
|
440
|
+
tokenSource = "interactive";
|
|
441
|
+
}
|
|
442
|
+
const headers = {
|
|
443
|
+
Authorization: `Bearer ${token}`,
|
|
444
|
+
Origin: "https://app.decibel.trade",
|
|
445
|
+
};
|
|
446
|
+
const marketsUrl = new URL("api/v1/markets", `${restUrl.replace(/\/+$/, "")}/`).toString();
|
|
447
|
+
const markets = await fetchWithTimeout(marketsUrl, {
|
|
448
|
+
headers,
|
|
449
|
+
retries: 0,
|
|
450
|
+
});
|
|
451
|
+
const marketCount = Array.isArray(markets) ? markets.length : 0;
|
|
452
|
+
if (marketCount === 0) {
|
|
453
|
+
throw new Error("Decibel token validated but returned no markets.");
|
|
454
|
+
}
|
|
455
|
+
const accountAddress = normalizeText(opts.subaccountAddress) ??
|
|
456
|
+
normalizeText(opts.walletAddress) ??
|
|
457
|
+
normalizeText(process.env.DECIBEL_SUBACCOUNT_ADDRESS) ??
|
|
458
|
+
normalizeText(process.env.DECIBEL_API_WALLET_ADDRESS);
|
|
459
|
+
let accountCheck;
|
|
460
|
+
if (accountAddress) {
|
|
461
|
+
const accountUrl = new URL("api/v1/account_overview", `${restUrl.replace(/\/+$/, "")}/`);
|
|
462
|
+
accountUrl.searchParams.set("account", accountAddress);
|
|
463
|
+
try {
|
|
464
|
+
await fetchWithTimeout(accountUrl.toString(), {
|
|
465
|
+
headers,
|
|
466
|
+
retries: 0,
|
|
467
|
+
});
|
|
468
|
+
accountCheck = {
|
|
469
|
+
status: "ok",
|
|
470
|
+
detail: `account_overview succeeded for ${accountAddress}`,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
catch (err) {
|
|
474
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
475
|
+
accountCheck = {
|
|
476
|
+
status: "failed",
|
|
477
|
+
detail: `account_overview failed for ${accountAddress}: ${detail}`,
|
|
478
|
+
};
|
|
479
|
+
if (opts.requireAccount) {
|
|
480
|
+
throw new Error(accountCheck.detail);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
accountCheck = {
|
|
486
|
+
status: "skipped",
|
|
487
|
+
detail: "No DECIBEL_API_WALLET_ADDRESS/DECIBEL_SUBACCOUNT_ADDRESS provided.",
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
let vaultRef;
|
|
491
|
+
let vaultStatus = "disabled";
|
|
492
|
+
let vaultPath;
|
|
493
|
+
if (persistToVault) {
|
|
494
|
+
vaultPath = getCredentialVaultSummary().path;
|
|
495
|
+
if (vaultToken === token) {
|
|
496
|
+
vaultStatus = "unchanged";
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
vaultRef = saveSecretToCredentialVault("DECIBEL_API_BEARER_TOKEN", token, {
|
|
500
|
+
mode: "setup.decibel-key",
|
|
501
|
+
exchange: "decibel",
|
|
502
|
+
});
|
|
503
|
+
vaultStatus = "stored";
|
|
504
|
+
vaultPath = vaultRef.path;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
let envFilePath;
|
|
508
|
+
if (opts.envFile) {
|
|
509
|
+
envFilePath = resolve(opts.envFile.trim());
|
|
510
|
+
const existing = existsSync(envFilePath) ? readFileSync(envFilePath, "utf8") : "";
|
|
511
|
+
const updated = upsertEnvVar(existing, "DECIBEL_API_BEARER_TOKEN", token);
|
|
512
|
+
writeFileSync(envFilePath, updated, { mode: PRIVATE_FILE_MODE });
|
|
513
|
+
hardenPrivateFile(envFilePath);
|
|
514
|
+
}
|
|
515
|
+
const nextSteps = envFilePath
|
|
516
|
+
? [
|
|
517
|
+
`source ${envFilePath}`,
|
|
518
|
+
"perps markets ls -e decibel",
|
|
519
|
+
"perps asset book BTC-PERP -e decibel",
|
|
520
|
+
]
|
|
521
|
+
: persistToVault
|
|
522
|
+
? [
|
|
523
|
+
"perps markets ls -e decibel",
|
|
524
|
+
"perps asset book BTC-PERP -e decibel",
|
|
525
|
+
]
|
|
526
|
+
: [
|
|
527
|
+
"Export DECIBEL_API_BEARER_TOKEN in your shell (or re-run without --no-vault).",
|
|
528
|
+
"perps markets ls -e decibel",
|
|
529
|
+
"perps asset book BTC-PERP -e decibel",
|
|
530
|
+
];
|
|
531
|
+
const payload = withJsonContract("setup.decibel_key.result", {
|
|
532
|
+
network,
|
|
533
|
+
restUrl,
|
|
534
|
+
tokenSource,
|
|
535
|
+
tokenCapturedInteractively: usedInteractiveCapture,
|
|
536
|
+
tokenValidated: true,
|
|
537
|
+
marketCount,
|
|
538
|
+
accountCheck,
|
|
539
|
+
vault: {
|
|
540
|
+
enabled: persistToVault,
|
|
541
|
+
status: vaultStatus,
|
|
542
|
+
id: vaultRef?.id,
|
|
543
|
+
path: vaultPath,
|
|
544
|
+
},
|
|
545
|
+
envFile: envFilePath,
|
|
546
|
+
envVar: "DECIBEL_API_BEARER_TOKEN",
|
|
547
|
+
nextSteps,
|
|
548
|
+
});
|
|
549
|
+
if (outputOpts.json) {
|
|
550
|
+
output(payload, outputOpts);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
outputSuccess("Decibel API key validated successfully.");
|
|
554
|
+
console.log("");
|
|
555
|
+
console.log(`Network: ${network}`);
|
|
556
|
+
console.log(`REST URL: ${restUrl}`);
|
|
557
|
+
console.log(`Markets checked: ${marketCount}`);
|
|
558
|
+
console.log(`Token source: ${tokenSource}`);
|
|
559
|
+
if (accountCheck) {
|
|
560
|
+
console.log(`Account check: [${accountCheck.status}] ${accountCheck.detail}`);
|
|
561
|
+
}
|
|
562
|
+
if (persistToVault && vaultPath) {
|
|
563
|
+
if (vaultStatus === "stored" && vaultRef) {
|
|
564
|
+
console.log(`Vault: stored ${vaultRef.id} (${vaultPath})`);
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
console.log(`Vault: unchanged (${vaultPath})`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
console.log("Vault: skipped (--no-vault)");
|
|
572
|
+
}
|
|
573
|
+
if (envFilePath) {
|
|
574
|
+
console.log(`Env file: ${envFilePath}`);
|
|
575
|
+
}
|
|
576
|
+
console.log("");
|
|
577
|
+
console.log("Next steps:");
|
|
578
|
+
for (const [index, step] of nextSteps.entries()) {
|
|
579
|
+
console.log(`${index + 1}) ${step}`);
|
|
580
|
+
}
|
|
581
|
+
console.log("");
|
|
582
|
+
}
|
|
583
|
+
catch (err) {
|
|
584
|
+
const message = err instanceof FetchError
|
|
585
|
+
? `Decibel key bootstrap failed: ${err.message}`
|
|
586
|
+
: err instanceof Error
|
|
587
|
+
? err.message
|
|
588
|
+
: String(err);
|
|
589
|
+
outputError(message);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
305
593
|
setup
|
|
306
594
|
.command("wizard")
|
|
307
595
|
.description("Interactive setup for mainnet data defaults and testnet onboarding")
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/lib/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { privateKeyToAccount } from "viem/accounts";
|
|
2
2
|
import { getDefaultAccountForExchange } from "./db/index.js";
|
|
3
|
+
import { getLatestCredentialVaultSecret } from "./credential-vault.js";
|
|
3
4
|
import { parseEnv } from "./schema.js";
|
|
4
5
|
import { recordTelemetryMetric } from "./telemetry.js";
|
|
5
6
|
const DECIBEL_NETWORK_DEFAULTS = {
|
|
@@ -7,13 +8,13 @@ const DECIBEL_NETWORK_DEFAULTS = {
|
|
|
7
8
|
fullnodeUrl: "https://api.testnet.aptoslabs.com/v1",
|
|
8
9
|
restUrl: "https://api.testnet.aptoslabs.com/decibel",
|
|
9
10
|
wsUrl: "wss://api.testnet.aptoslabs.com/decibel/ws",
|
|
10
|
-
packageAddress: "
|
|
11
|
+
packageAddress: "0x952535c3049e52f195f26798c2f1340d7dd5100edbe0f464e520a974d16fbe9f",
|
|
11
12
|
},
|
|
12
13
|
mainnet: {
|
|
13
14
|
fullnodeUrl: "https://api.mainnet.aptoslabs.com/v1",
|
|
14
15
|
restUrl: "https://api.mainnet.aptoslabs.com/decibel",
|
|
15
16
|
wsUrl: "wss://api.mainnet.aptoslabs.com/decibel/ws",
|
|
16
|
-
packageAddress: "
|
|
17
|
+
packageAddress: "0x50ead22afd6ffd9769e3b3d6e0e64a2a350d68e8b102c4e72e33d0b8cfdfdb06",
|
|
17
18
|
},
|
|
18
19
|
};
|
|
19
20
|
const ORDERLY_DEFAULTS = {
|
|
@@ -84,10 +85,14 @@ function resolveHyperliquidSettings(testnet, env) {
|
|
|
84
85
|
function resolveDecibelSettings(testnet, env) {
|
|
85
86
|
const network = resolveExchangeNetwork(testnet, env.DECIBEL_NETWORK);
|
|
86
87
|
const defaults = DECIBEL_NETWORK_DEFAULTS[network];
|
|
88
|
+
const envBearerToken = env.DECIBEL_API_BEARER_TOKEN;
|
|
89
|
+
const vaultBearerToken = envBearerToken
|
|
90
|
+
? undefined
|
|
91
|
+
: getLatestCredentialVaultSecret("DECIBEL_API_BEARER_TOKEN");
|
|
87
92
|
const credentials = {
|
|
88
93
|
apiWalletPrivateKey: env.DECIBEL_API_WALLET_PRIVATE_KEY,
|
|
89
94
|
apiWalletAddress: env.DECIBEL_API_WALLET_ADDRESS,
|
|
90
|
-
apiBearerToken:
|
|
95
|
+
apiBearerToken: envBearerToken ?? vaultBearerToken,
|
|
91
96
|
subaccountAddress: env.DECIBEL_SUBACCOUNT_ADDRESS,
|
|
92
97
|
};
|
|
93
98
|
const hasAnyCredentials = !!credentials.apiWalletPrivateKey ||
|
|
@@ -291,7 +296,7 @@ export function getExchangeCredentials(config, exchangeId, requirement = {}) {
|
|
|
291
296
|
if (requirement.requireReadAccess &&
|
|
292
297
|
(!credentials.apiBearerToken || !credentials.accountAddress)) {
|
|
293
298
|
recordAuth("read", "failed");
|
|
294
|
-
throw new Error("Decibel authenticated reads require DECIBEL_API_WALLET_ADDRESS + DECIBEL_API_BEARER_TOKEN.");
|
|
299
|
+
throw new Error("Decibel authenticated reads require DECIBEL_API_WALLET_ADDRESS + DECIBEL_API_BEARER_TOKEN (Client API key secret from app.decibel.trade -> Geomi -> API Keys).");
|
|
295
300
|
}
|
|
296
301
|
if (requirement.requireReadAccess) {
|
|
297
302
|
recordAuth("read", "success");
|
|
@@ -302,7 +307,7 @@ export function getExchangeCredentials(config, exchangeId, requirement = {}) {
|
|
|
302
307
|
!credentials.apiBearerToken ||
|
|
303
308
|
!credentials.packageAddress)) {
|
|
304
309
|
recordAuth("trade", "failed");
|
|
305
|
-
throw new Error("Decibel trading requires DECIBEL_API_WALLET_PRIVATE_KEY, DECIBEL_API_WALLET_ADDRESS, DECIBEL_API_BEARER_TOKEN,
|
|
310
|
+
throw new Error("Decibel trading requires DECIBEL_API_WALLET_PRIVATE_KEY, DECIBEL_API_WALLET_ADDRESS, and DECIBEL_API_BEARER_TOKEN (Client API key secret from app.decibel.trade -> Geomi -> API Keys), plus package/network settings.");
|
|
306
311
|
}
|
|
307
312
|
if (requirement.requireTrading) {
|
|
308
313
|
recordAuth("trade", "success");
|
|
@@ -20,3 +20,11 @@ export declare function getCredentialVaultSummary(): {
|
|
|
20
20
|
entries: number;
|
|
21
21
|
updatedAt?: string;
|
|
22
22
|
};
|
|
23
|
+
export declare function getLatestCredentialVaultSecret(secretKey: string): string | undefined;
|
|
24
|
+
export declare function saveSecretToCredentialVault(secretKey: string, secretValue: string, options?: {
|
|
25
|
+
mode?: string;
|
|
26
|
+
exchange?: string;
|
|
27
|
+
}): {
|
|
28
|
+
id: string;
|
|
29
|
+
path: string;
|
|
30
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { CREDENTIAL_VAULT_PATH } from "./paths.js";
|
|
4
|
-
import { encryptSecret } from "./secrets.js";
|
|
4
|
+
import { decryptSecret, encryptSecret } from "./secrets.js";
|
|
5
5
|
import { ensurePrivateDir, hardenPrivateFile, PRIVATE_FILE_MODE } from "./fs-security.js";
|
|
6
6
|
const VAULT_SCHEMA_VERSION = 1;
|
|
7
7
|
function createVault() {
|
|
@@ -78,6 +78,25 @@ function buildId(profile) {
|
|
|
78
78
|
const suffix = Math.random().toString(36).slice(2, 8);
|
|
79
79
|
return `onb-${day}-${suffix}`;
|
|
80
80
|
}
|
|
81
|
+
function buildSecretId(now) {
|
|
82
|
+
const day = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
83
|
+
const suffix = Math.random().toString(36).slice(2, 8);
|
|
84
|
+
return `sec-${day}-${suffix}`;
|
|
85
|
+
}
|
|
86
|
+
function normalizeSecretKey(secretKey) {
|
|
87
|
+
const normalized = secretKey.trim().toUpperCase();
|
|
88
|
+
if (!/^[A-Z0-9_]+$/.test(normalized)) {
|
|
89
|
+
throw new Error(`Invalid vault secret key '${secretKey}'`);
|
|
90
|
+
}
|
|
91
|
+
return normalized;
|
|
92
|
+
}
|
|
93
|
+
function normalizeSecretValue(secretValue) {
|
|
94
|
+
const normalized = secretValue.trim();
|
|
95
|
+
if (!normalized) {
|
|
96
|
+
throw new Error("Vault secret value cannot be empty");
|
|
97
|
+
}
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
81
100
|
export function saveOnboardingProfileToVault(profile) {
|
|
82
101
|
const vault = readVault();
|
|
83
102
|
const id = buildId(profile);
|
|
@@ -107,3 +126,44 @@ export function getCredentialVaultSummary() {
|
|
|
107
126
|
updatedAt: vault.updatedAt,
|
|
108
127
|
};
|
|
109
128
|
}
|
|
129
|
+
export function getLatestCredentialVaultSecret(secretKey) {
|
|
130
|
+
const normalizedKey = normalizeSecretKey(secretKey);
|
|
131
|
+
const vault = readVault();
|
|
132
|
+
for (const entry of vault.entries) {
|
|
133
|
+
const encryptedValue = entry?.encrypted?.[normalizedKey];
|
|
134
|
+
if (typeof encryptedValue !== "string" || encryptedValue.trim().length === 0) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const plaintext = decryptSecret(encryptedValue).trim();
|
|
139
|
+
if (plaintext.length > 0) {
|
|
140
|
+
return plaintext;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Skip malformed entries and continue searching older entries.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
export function saveSecretToCredentialVault(secretKey, secretValue, options = {}) {
|
|
150
|
+
const key = normalizeSecretKey(secretKey);
|
|
151
|
+
const value = normalizeSecretValue(secretValue);
|
|
152
|
+
const now = new Date();
|
|
153
|
+
const vault = readVault();
|
|
154
|
+
const id = buildSecretId(now);
|
|
155
|
+
const entry = {
|
|
156
|
+
id,
|
|
157
|
+
createdAt: now.toISOString(),
|
|
158
|
+
mode: options.mode ?? "manual_secret",
|
|
159
|
+
exchanges: options.exchange ? [options.exchange] : [],
|
|
160
|
+
wallets: {},
|
|
161
|
+
encrypted: {
|
|
162
|
+
[key]: encryptSecret(value),
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
vault.entries.unshift(entry);
|
|
166
|
+
vault.updatedAt = now.toISOString();
|
|
167
|
+
writeVault(vault);
|
|
168
|
+
return { id, path: CREDENTIAL_VAULT_PATH };
|
|
169
|
+
}
|
package/dist/lib/onboarding.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
1
|
+
import { randomBytes, webcrypto } from "node:crypto";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { Ed25519Account, Ed25519PrivateKey } from "@aptos-labs/ts-sdk";
|
|
@@ -40,6 +40,10 @@ const AEVO_TESTNET_CHAIN_ID = 11155111;
|
|
|
40
40
|
const PARADEX_TESTNET_API = "https://api.testnet.paradex.trade/v1";
|
|
41
41
|
const SECONDS_PER_DAY = 86_400;
|
|
42
42
|
const MILLISECONDS_PER_DAY = 86_400_000;
|
|
43
|
+
// Node 18 may not expose globalThis.crypto in every runtime context.
|
|
44
|
+
if (!globalThis.crypto) {
|
|
45
|
+
globalThis.crypto = webcrypto;
|
|
46
|
+
}
|
|
43
47
|
function dedupe(values, order) {
|
|
44
48
|
const set = new Set(values);
|
|
45
49
|
return order.filter((item) => set.has(item));
|
|
@@ -608,7 +612,7 @@ export async function bootstrapOnboardingAuth(profile, options = {}) {
|
|
|
608
612
|
exchange: "decibel",
|
|
609
613
|
service: "decibel auth",
|
|
610
614
|
status: "ok",
|
|
611
|
-
detail: "using DECIBEL_API_BEARER_TOKEN",
|
|
615
|
+
detail: "using DECIBEL_API_BEARER_TOKEN (Client API key secret)",
|
|
612
616
|
});
|
|
613
617
|
}
|
|
614
618
|
else {
|
|
@@ -616,7 +620,7 @@ export async function bootstrapOnboardingAuth(profile, options = {}) {
|
|
|
616
620
|
exchange: "decibel",
|
|
617
621
|
service: "decibel auth",
|
|
618
622
|
status: "manual",
|
|
619
|
-
detail: "still requires DECIBEL_API_BEARER_TOKEN",
|
|
623
|
+
detail: "still requires DECIBEL_API_BEARER_TOKEN (Client API key secret from app.decibel.trade -> Geomi -> API Keys)",
|
|
620
624
|
});
|
|
621
625
|
}
|
|
622
626
|
}
|
|
@@ -833,7 +837,7 @@ const ONBOARDING_PROVIDERS = {
|
|
|
833
837
|
manualAuthFollowUp: {
|
|
834
838
|
exchange: "decibel",
|
|
835
839
|
env: ["DECIBEL_API_BEARER_TOKEN"],
|
|
836
|
-
detail: "
|
|
840
|
+
detail: "Create a key in app.decibel.trade -> Geomi -> API Keys, copy the Key Secret, and set DECIBEL_API_BEARER_TOKEN.",
|
|
837
841
|
},
|
|
838
842
|
fundingTask: ({ profile, options }) => requestAptosFaucet({
|
|
839
843
|
aptosAddress: profile.wallets.aptosAddress,
|
package/dist/lib/prompts.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { input, select as inquirerSelect, confirm as inquirerConfirm, checkbox, } from "@inquirer/prompts";
|
|
2
1
|
import { inquirerTheme, highlighter } from "./ui-tokens.js";
|
|
2
|
+
let promptsModule = null;
|
|
3
|
+
async function loadPrompts() {
|
|
4
|
+
if (promptsModule)
|
|
5
|
+
return promptsModule;
|
|
6
|
+
promptsModule = await import("@inquirer/prompts");
|
|
7
|
+
return promptsModule;
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* Prompt for text input
|
|
5
11
|
*/
|
|
6
12
|
export async function prompt(question) {
|
|
13
|
+
const { input } = await loadPrompts();
|
|
7
14
|
const answer = await input({
|
|
8
15
|
message: question,
|
|
9
16
|
theme: inquirerTheme,
|
|
@@ -14,6 +21,7 @@ export async function prompt(question) {
|
|
|
14
21
|
* Prompt for selection from a list of options with arrow key navigation
|
|
15
22
|
*/
|
|
16
23
|
export async function select(question, options) {
|
|
24
|
+
const { select: inquirerSelect } = await loadPrompts();
|
|
17
25
|
const result = await inquirerSelect({
|
|
18
26
|
message: question,
|
|
19
27
|
choices: options.map((opt) => ({
|
|
@@ -29,6 +37,7 @@ export async function select(question, options) {
|
|
|
29
37
|
* Prompt for multiple selections with checkboxes
|
|
30
38
|
*/
|
|
31
39
|
export async function multiSelect(question, options) {
|
|
40
|
+
const { checkbox } = await loadPrompts();
|
|
32
41
|
const results = await checkbox({
|
|
33
42
|
message: question,
|
|
34
43
|
choices: options.map((opt) => ({
|
|
@@ -44,6 +53,7 @@ export async function multiSelect(question, options) {
|
|
|
44
53
|
* Prompt for yes/no confirmation
|
|
45
54
|
*/
|
|
46
55
|
export async function confirm(question, defaultValue = false) {
|
|
56
|
+
const { confirm: inquirerConfirm } = await loadPrompts();
|
|
47
57
|
return inquirerConfirm({
|
|
48
58
|
message: question,
|
|
49
59
|
default: defaultValue,
|
|
@@ -54,6 +64,7 @@ export async function confirm(question, defaultValue = false) {
|
|
|
54
64
|
* Wait for user to press Enter
|
|
55
65
|
*/
|
|
56
66
|
export async function waitForEnter(message = "Press Enter to continue...") {
|
|
67
|
+
const { input } = await loadPrompts();
|
|
57
68
|
await input({
|
|
58
69
|
message,
|
|
59
70
|
theme: {
|
package/dist/lib/schema.d.ts
CHANGED
|
@@ -60,80 +60,80 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
|
|
|
60
60
|
EXECUTION_TAKE_PROFIT_PCT: number;
|
|
61
61
|
EXECUTION_SPREAD_OFFSET_PCT: number;
|
|
62
62
|
OPERATOR_HEARTBEAT_INTERVAL_MS: number;
|
|
63
|
-
DECIBEL_REST_URL?: string | undefined;
|
|
64
|
-
DECIBEL_WS_URL?: string | undefined;
|
|
65
|
-
DECIBEL_FULLNODE_URL?: string | undefined;
|
|
66
|
-
AEVO_REST_URL?: string | undefined;
|
|
67
63
|
ORDERLY_REST_URL?: string | undefined;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
HYPERLIQUID_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
72
|
-
DECIBEL_API_WALLET_PRIVATE_KEY?: string | undefined;
|
|
73
|
-
DECIBEL_API_WALLET_ADDRESS?: string | undefined;
|
|
64
|
+
AEVO_REST_URL?: string | undefined;
|
|
65
|
+
PARADEX_ACCOUNT_ADDRESS?: string | undefined;
|
|
66
|
+
PARADEX_ETHEREUM_ACCOUNT?: string | undefined;
|
|
74
67
|
DECIBEL_API_BEARER_TOKEN?: string | undefined;
|
|
75
|
-
DECIBEL_SUBACCOUNT_ADDRESS?: string | undefined;
|
|
76
|
-
DECIBEL_PACKAGE_ADDRESS?: string | undefined;
|
|
77
|
-
DECIBEL_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
78
68
|
AEVO_API_KEY?: string | undefined;
|
|
79
69
|
AEVO_API_SECRET?: string | undefined;
|
|
80
|
-
AEVO_SIGNING_KEY?: string | undefined;
|
|
81
70
|
AEVO_ACCOUNT_PRIVATE_KEY?: string | undefined;
|
|
82
|
-
AEVO_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
83
|
-
ORDERLY_BROKER_ID?: string | undefined;
|
|
84
|
-
ORDERLY_CHAIN_ID?: string | undefined;
|
|
85
71
|
ORDERLY_ACCOUNT_ID?: string | undefined;
|
|
86
72
|
ORDERLY_KEY?: string | undefined;
|
|
87
73
|
ORDERLY_SECRET?: string | undefined;
|
|
88
|
-
|
|
89
|
-
ORDERLY_TRADING_SECRET?: string | undefined;
|
|
90
|
-
ORDERLY_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
74
|
+
ORDERLY_BROKER_ID?: string | undefined;
|
|
91
75
|
PARADEX_API_BEARER_TOKEN?: string | undefined;
|
|
92
|
-
|
|
76
|
+
HYPERLIQUID_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
77
|
+
DECIBEL_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
78
|
+
AEVO_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
79
|
+
ORDERLY_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
80
|
+
PARADEX_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
81
|
+
PARADEX_REST_URL?: string | undefined;
|
|
82
|
+
HYPERLIQUID_PRIVATE_KEY?: string | undefined;
|
|
83
|
+
DECIBEL_API_WALLET_PRIVATE_KEY?: string | undefined;
|
|
84
|
+
AEVO_SIGNING_KEY?: string | undefined;
|
|
85
|
+
ORDERLY_TRADING_SECRET?: string | undefined;
|
|
93
86
|
PARADEX_PRIVATE_KEY?: string | undefined;
|
|
94
|
-
|
|
87
|
+
DECIBEL_REST_URL?: string | undefined;
|
|
88
|
+
DECIBEL_WS_URL?: string | undefined;
|
|
89
|
+
DECIBEL_FULLNODE_URL?: string | undefined;
|
|
90
|
+
HYPERLIQUID_WALLET_ADDRESS?: string | undefined;
|
|
91
|
+
DECIBEL_API_WALLET_ADDRESS?: string | undefined;
|
|
92
|
+
DECIBEL_SUBACCOUNT_ADDRESS?: string | undefined;
|
|
93
|
+
DECIBEL_PACKAGE_ADDRESS?: string | undefined;
|
|
94
|
+
ORDERLY_CHAIN_ID?: string | undefined;
|
|
95
|
+
ORDERLY_TRADING_KEY?: string | undefined;
|
|
95
96
|
PARADEX_CHAIN_ID?: string | undefined;
|
|
96
|
-
PARADEX_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
97
97
|
PERPS_ALLOW_UNTRUSTED_ENDPOINTS?: "0" | "1" | "true" | "false" | undefined;
|
|
98
98
|
PERPS_BLOCKED_EXCHANGES?: string | undefined;
|
|
99
99
|
PERPS_BLOCKED_MARKETS?: string | undefined;
|
|
100
100
|
PERPS_BLOCKED_MARKET_PATTERNS?: string | undefined;
|
|
101
101
|
PERPS_REPUTATION_BLOCKLIST_FILE?: string | undefined;
|
|
102
102
|
}, {
|
|
103
|
-
DECIBEL_REST_URL?: string | undefined;
|
|
104
|
-
DECIBEL_WS_URL?: string | undefined;
|
|
105
|
-
DECIBEL_FULLNODE_URL?: string | undefined;
|
|
106
|
-
AEVO_REST_URL?: string | undefined;
|
|
107
103
|
ORDERLY_REST_URL?: string | undefined;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
HYPERLIQUID_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
112
|
-
DECIBEL_API_WALLET_PRIVATE_KEY?: string | undefined;
|
|
113
|
-
DECIBEL_API_WALLET_ADDRESS?: string | undefined;
|
|
104
|
+
AEVO_REST_URL?: string | undefined;
|
|
105
|
+
PARADEX_ACCOUNT_ADDRESS?: string | undefined;
|
|
106
|
+
PARADEX_ETHEREUM_ACCOUNT?: string | undefined;
|
|
114
107
|
DECIBEL_API_BEARER_TOKEN?: string | undefined;
|
|
115
|
-
DECIBEL_SUBACCOUNT_ADDRESS?: string | undefined;
|
|
116
|
-
DECIBEL_PACKAGE_ADDRESS?: string | undefined;
|
|
117
|
-
DECIBEL_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
118
108
|
AEVO_API_KEY?: string | undefined;
|
|
119
109
|
AEVO_API_SECRET?: string | undefined;
|
|
120
|
-
AEVO_SIGNING_KEY?: string | undefined;
|
|
121
110
|
AEVO_ACCOUNT_PRIVATE_KEY?: string | undefined;
|
|
122
|
-
AEVO_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
123
|
-
ORDERLY_BROKER_ID?: string | undefined;
|
|
124
|
-
ORDERLY_CHAIN_ID?: string | undefined;
|
|
125
111
|
ORDERLY_ACCOUNT_ID?: string | undefined;
|
|
126
112
|
ORDERLY_KEY?: string | undefined;
|
|
127
113
|
ORDERLY_SECRET?: string | undefined;
|
|
128
|
-
|
|
129
|
-
ORDERLY_TRADING_SECRET?: string | undefined;
|
|
130
|
-
ORDERLY_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
114
|
+
ORDERLY_BROKER_ID?: string | undefined;
|
|
131
115
|
PARADEX_API_BEARER_TOKEN?: string | undefined;
|
|
132
|
-
|
|
116
|
+
HYPERLIQUID_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
117
|
+
DECIBEL_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
118
|
+
AEVO_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
119
|
+
ORDERLY_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
120
|
+
PARADEX_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
121
|
+
PARADEX_REST_URL?: string | undefined;
|
|
122
|
+
HYPERLIQUID_PRIVATE_KEY?: string | undefined;
|
|
123
|
+
DECIBEL_API_WALLET_PRIVATE_KEY?: string | undefined;
|
|
124
|
+
AEVO_SIGNING_KEY?: string | undefined;
|
|
125
|
+
ORDERLY_TRADING_SECRET?: string | undefined;
|
|
133
126
|
PARADEX_PRIVATE_KEY?: string | undefined;
|
|
134
|
-
|
|
127
|
+
DECIBEL_REST_URL?: string | undefined;
|
|
128
|
+
DECIBEL_WS_URL?: string | undefined;
|
|
129
|
+
DECIBEL_FULLNODE_URL?: string | undefined;
|
|
130
|
+
HYPERLIQUID_WALLET_ADDRESS?: string | undefined;
|
|
131
|
+
DECIBEL_API_WALLET_ADDRESS?: string | undefined;
|
|
132
|
+
DECIBEL_SUBACCOUNT_ADDRESS?: string | undefined;
|
|
133
|
+
DECIBEL_PACKAGE_ADDRESS?: string | undefined;
|
|
134
|
+
ORDERLY_CHAIN_ID?: string | undefined;
|
|
135
|
+
ORDERLY_TRADING_KEY?: string | undefined;
|
|
135
136
|
PARADEX_CHAIN_ID?: string | undefined;
|
|
136
|
-
PARADEX_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
137
137
|
RISK_MAX_POSITION_SIZE_USD?: number | undefined;
|
|
138
138
|
RISK_MAX_TOTAL_EXPOSURE_USD?: number | undefined;
|
|
139
139
|
RISK_MAX_LEVERAGE?: number | undefined;
|
|
@@ -160,80 +160,80 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
|
|
|
160
160
|
EXECUTION_TAKE_PROFIT_PCT: number;
|
|
161
161
|
EXECUTION_SPREAD_OFFSET_PCT: number;
|
|
162
162
|
OPERATOR_HEARTBEAT_INTERVAL_MS: number;
|
|
163
|
-
DECIBEL_REST_URL?: string | undefined;
|
|
164
|
-
DECIBEL_WS_URL?: string | undefined;
|
|
165
|
-
DECIBEL_FULLNODE_URL?: string | undefined;
|
|
166
|
-
AEVO_REST_URL?: string | undefined;
|
|
167
163
|
ORDERLY_REST_URL?: string | undefined;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
HYPERLIQUID_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
172
|
-
DECIBEL_API_WALLET_PRIVATE_KEY?: string | undefined;
|
|
173
|
-
DECIBEL_API_WALLET_ADDRESS?: string | undefined;
|
|
164
|
+
AEVO_REST_URL?: string | undefined;
|
|
165
|
+
PARADEX_ACCOUNT_ADDRESS?: string | undefined;
|
|
166
|
+
PARADEX_ETHEREUM_ACCOUNT?: string | undefined;
|
|
174
167
|
DECIBEL_API_BEARER_TOKEN?: string | undefined;
|
|
175
|
-
DECIBEL_SUBACCOUNT_ADDRESS?: string | undefined;
|
|
176
|
-
DECIBEL_PACKAGE_ADDRESS?: string | undefined;
|
|
177
|
-
DECIBEL_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
178
168
|
AEVO_API_KEY?: string | undefined;
|
|
179
169
|
AEVO_API_SECRET?: string | undefined;
|
|
180
|
-
AEVO_SIGNING_KEY?: string | undefined;
|
|
181
170
|
AEVO_ACCOUNT_PRIVATE_KEY?: string | undefined;
|
|
182
|
-
AEVO_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
183
|
-
ORDERLY_BROKER_ID?: string | undefined;
|
|
184
|
-
ORDERLY_CHAIN_ID?: string | undefined;
|
|
185
171
|
ORDERLY_ACCOUNT_ID?: string | undefined;
|
|
186
172
|
ORDERLY_KEY?: string | undefined;
|
|
187
173
|
ORDERLY_SECRET?: string | undefined;
|
|
188
|
-
|
|
189
|
-
ORDERLY_TRADING_SECRET?: string | undefined;
|
|
190
|
-
ORDERLY_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
174
|
+
ORDERLY_BROKER_ID?: string | undefined;
|
|
191
175
|
PARADEX_API_BEARER_TOKEN?: string | undefined;
|
|
192
|
-
|
|
176
|
+
HYPERLIQUID_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
177
|
+
DECIBEL_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
178
|
+
AEVO_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
179
|
+
ORDERLY_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
180
|
+
PARADEX_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
181
|
+
PARADEX_REST_URL?: string | undefined;
|
|
182
|
+
HYPERLIQUID_PRIVATE_KEY?: string | undefined;
|
|
183
|
+
DECIBEL_API_WALLET_PRIVATE_KEY?: string | undefined;
|
|
184
|
+
AEVO_SIGNING_KEY?: string | undefined;
|
|
185
|
+
ORDERLY_TRADING_SECRET?: string | undefined;
|
|
193
186
|
PARADEX_PRIVATE_KEY?: string | undefined;
|
|
194
|
-
|
|
187
|
+
DECIBEL_REST_URL?: string | undefined;
|
|
188
|
+
DECIBEL_WS_URL?: string | undefined;
|
|
189
|
+
DECIBEL_FULLNODE_URL?: string | undefined;
|
|
190
|
+
HYPERLIQUID_WALLET_ADDRESS?: string | undefined;
|
|
191
|
+
DECIBEL_API_WALLET_ADDRESS?: string | undefined;
|
|
192
|
+
DECIBEL_SUBACCOUNT_ADDRESS?: string | undefined;
|
|
193
|
+
DECIBEL_PACKAGE_ADDRESS?: string | undefined;
|
|
194
|
+
ORDERLY_CHAIN_ID?: string | undefined;
|
|
195
|
+
ORDERLY_TRADING_KEY?: string | undefined;
|
|
195
196
|
PARADEX_CHAIN_ID?: string | undefined;
|
|
196
|
-
PARADEX_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
197
197
|
PERPS_ALLOW_UNTRUSTED_ENDPOINTS?: "0" | "1" | "true" | "false" | undefined;
|
|
198
198
|
PERPS_BLOCKED_EXCHANGES?: string | undefined;
|
|
199
199
|
PERPS_BLOCKED_MARKETS?: string | undefined;
|
|
200
200
|
PERPS_BLOCKED_MARKET_PATTERNS?: string | undefined;
|
|
201
201
|
PERPS_REPUTATION_BLOCKLIST_FILE?: string | undefined;
|
|
202
202
|
}, {
|
|
203
|
-
DECIBEL_REST_URL?: string | undefined;
|
|
204
|
-
DECIBEL_WS_URL?: string | undefined;
|
|
205
|
-
DECIBEL_FULLNODE_URL?: string | undefined;
|
|
206
|
-
AEVO_REST_URL?: string | undefined;
|
|
207
203
|
ORDERLY_REST_URL?: string | undefined;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
HYPERLIQUID_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
212
|
-
DECIBEL_API_WALLET_PRIVATE_KEY?: string | undefined;
|
|
213
|
-
DECIBEL_API_WALLET_ADDRESS?: string | undefined;
|
|
204
|
+
AEVO_REST_URL?: string | undefined;
|
|
205
|
+
PARADEX_ACCOUNT_ADDRESS?: string | undefined;
|
|
206
|
+
PARADEX_ETHEREUM_ACCOUNT?: string | undefined;
|
|
214
207
|
DECIBEL_API_BEARER_TOKEN?: string | undefined;
|
|
215
|
-
DECIBEL_SUBACCOUNT_ADDRESS?: string | undefined;
|
|
216
|
-
DECIBEL_PACKAGE_ADDRESS?: string | undefined;
|
|
217
|
-
DECIBEL_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
218
208
|
AEVO_API_KEY?: string | undefined;
|
|
219
209
|
AEVO_API_SECRET?: string | undefined;
|
|
220
|
-
AEVO_SIGNING_KEY?: string | undefined;
|
|
221
210
|
AEVO_ACCOUNT_PRIVATE_KEY?: string | undefined;
|
|
222
|
-
AEVO_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
223
|
-
ORDERLY_BROKER_ID?: string | undefined;
|
|
224
|
-
ORDERLY_CHAIN_ID?: string | undefined;
|
|
225
211
|
ORDERLY_ACCOUNT_ID?: string | undefined;
|
|
226
212
|
ORDERLY_KEY?: string | undefined;
|
|
227
213
|
ORDERLY_SECRET?: string | undefined;
|
|
228
|
-
|
|
229
|
-
ORDERLY_TRADING_SECRET?: string | undefined;
|
|
230
|
-
ORDERLY_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
214
|
+
ORDERLY_BROKER_ID?: string | undefined;
|
|
231
215
|
PARADEX_API_BEARER_TOKEN?: string | undefined;
|
|
232
|
-
|
|
216
|
+
HYPERLIQUID_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
217
|
+
DECIBEL_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
218
|
+
AEVO_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
219
|
+
ORDERLY_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
220
|
+
PARADEX_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
221
|
+
PARADEX_REST_URL?: string | undefined;
|
|
222
|
+
HYPERLIQUID_PRIVATE_KEY?: string | undefined;
|
|
223
|
+
DECIBEL_API_WALLET_PRIVATE_KEY?: string | undefined;
|
|
224
|
+
AEVO_SIGNING_KEY?: string | undefined;
|
|
225
|
+
ORDERLY_TRADING_SECRET?: string | undefined;
|
|
233
226
|
PARADEX_PRIVATE_KEY?: string | undefined;
|
|
234
|
-
|
|
227
|
+
DECIBEL_REST_URL?: string | undefined;
|
|
228
|
+
DECIBEL_WS_URL?: string | undefined;
|
|
229
|
+
DECIBEL_FULLNODE_URL?: string | undefined;
|
|
230
|
+
HYPERLIQUID_WALLET_ADDRESS?: string | undefined;
|
|
231
|
+
DECIBEL_API_WALLET_ADDRESS?: string | undefined;
|
|
232
|
+
DECIBEL_SUBACCOUNT_ADDRESS?: string | undefined;
|
|
233
|
+
DECIBEL_PACKAGE_ADDRESS?: string | undefined;
|
|
234
|
+
ORDERLY_CHAIN_ID?: string | undefined;
|
|
235
|
+
ORDERLY_TRADING_KEY?: string | undefined;
|
|
235
236
|
PARADEX_CHAIN_ID?: string | undefined;
|
|
236
|
-
PARADEX_NETWORK?: "testnet" | "mainnet" | undefined;
|
|
237
237
|
RISK_MAX_POSITION_SIZE_USD?: number | undefined;
|
|
238
238
|
RISK_MAX_TOTAL_EXPOSURE_USD?: number | undefined;
|
|
239
239
|
RISK_MAX_LEVERAGE?: number | undefined;
|
package/dist/lib/schema.js
CHANGED
|
@@ -34,9 +34,23 @@ const secureWsUrl = z
|
|
|
34
34
|
message: "must use wss:// (ws:// allowed only for localhost)",
|
|
35
35
|
});
|
|
36
36
|
const EXCHANGE_URL_HOST_ALLOWLIST = {
|
|
37
|
-
DECIBEL_REST_URL: [
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
DECIBEL_REST_URL: [
|
|
38
|
+
"api.decibel.trade",
|
|
39
|
+
"api.testnet.aptoslabs.com",
|
|
40
|
+
"api.netna.aptoslabs.com",
|
|
41
|
+
"api.mainnet.aptoslabs.com",
|
|
42
|
+
],
|
|
43
|
+
DECIBEL_WS_URL: [
|
|
44
|
+
"api.decibel.trade",
|
|
45
|
+
"api.testnet.aptoslabs.com",
|
|
46
|
+
"api.netna.aptoslabs.com",
|
|
47
|
+
"api.mainnet.aptoslabs.com",
|
|
48
|
+
],
|
|
49
|
+
DECIBEL_FULLNODE_URL: [
|
|
50
|
+
"api.testnet.aptoslabs.com",
|
|
51
|
+
"api.netna.aptoslabs.com",
|
|
52
|
+
"api.mainnet.aptoslabs.com",
|
|
53
|
+
],
|
|
40
54
|
AEVO_REST_URL: ["api-testnet.aevo.xyz", "api.aevo.xyz"],
|
|
41
55
|
ORDERLY_REST_URL: ["testnet-api.orderly.org", "api-evm.orderly.org"],
|
|
42
56
|
PARADEX_REST_URL: ["api.testnet.paradex.trade", "api.prod.paradex.trade"],
|
|
@@ -118,13 +132,6 @@ export const envSchema = z
|
|
|
118
132
|
message: "required when DECIBEL_API_WALLET_PRIVATE_KEY is set",
|
|
119
133
|
});
|
|
120
134
|
}
|
|
121
|
-
if (env.DECIBEL_API_BEARER_TOKEN && !env.DECIBEL_API_WALLET_ADDRESS) {
|
|
122
|
-
ctx.addIssue({
|
|
123
|
-
code: z.ZodIssueCode.custom,
|
|
124
|
-
path: ["DECIBEL_API_WALLET_ADDRESS"],
|
|
125
|
-
message: "required when DECIBEL_API_BEARER_TOKEN is set",
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
135
|
const aevoCredentialValues = [env.AEVO_API_KEY, env.AEVO_API_SECRET];
|
|
129
136
|
const aevoCredentialCount = aevoCredentialValues.filter(Boolean).length;
|
|
130
137
|
if (aevoCredentialCount === 1) {
|
package/dist/server/index.js
CHANGED
|
File without changes
|