openbroker 1.3.2 → 1.5.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/CHANGELOG.md +23 -0
- package/dist/auto/audit.d.ts +57 -0
- package/dist/auto/audit.d.ts.map +1 -0
- package/dist/auto/audit.js +407 -0
- package/dist/auto/cli.d.ts +2 -0
- package/dist/auto/cli.d.ts.map +1 -0
- package/dist/auto/cli.js +423 -0
- package/dist/auto/events.d.ts +11 -0
- package/dist/auto/events.d.ts.map +1 -0
- package/dist/auto/events.js +36 -0
- package/dist/auto/examples/dca.d.ts +4 -0
- package/dist/auto/examples/dca.d.ts.map +1 -0
- package/dist/auto/examples/dca.js +60 -0
- package/dist/auto/examples/funding-arb.d.ts +4 -0
- package/dist/auto/examples/funding-arb.d.ts.map +1 -0
- package/dist/auto/examples/funding-arb.js +81 -0
- package/dist/auto/examples/grid.d.ts +4 -0
- package/dist/auto/examples/grid.d.ts.map +1 -0
- package/dist/auto/examples/grid.js +114 -0
- package/dist/auto/examples/mm-maker.d.ts +4 -0
- package/dist/auto/examples/mm-maker.d.ts.map +1 -0
- package/dist/auto/examples/mm-maker.js +131 -0
- package/dist/auto/examples/mm-spread.d.ts +4 -0
- package/dist/auto/examples/mm-spread.d.ts.map +1 -0
- package/dist/auto/examples/mm-spread.js +119 -0
- package/dist/auto/examples/price-alert.d.ts +4 -0
- package/dist/auto/examples/price-alert.d.ts.map +1 -0
- package/dist/auto/examples/price-alert.js +85 -0
- package/dist/auto/keep-awake.d.ts +11 -0
- package/dist/auto/keep-awake.d.ts.map +1 -0
- package/dist/auto/keep-awake.js +70 -0
- package/dist/auto/loader.d.ts +22 -0
- package/dist/auto/loader.d.ts.map +1 -0
- package/dist/auto/loader.js +127 -0
- package/dist/auto/prune.d.ts +40 -0
- package/dist/auto/prune.d.ts.map +1 -0
- package/dist/auto/prune.js +204 -0
- package/dist/auto/registry.d.ts +24 -0
- package/dist/auto/registry.d.ts.map +1 -0
- package/dist/auto/registry.js +93 -0
- package/dist/auto/report.d.ts +3 -0
- package/dist/auto/report.d.ts.map +1 -0
- package/dist/auto/report.js +385 -0
- package/dist/auto/runtime.d.ts +33 -0
- package/dist/auto/runtime.d.ts.map +1 -0
- package/dist/auto/runtime.js +844 -0
- package/dist/auto/types.d.ts +236 -0
- package/dist/auto/types.d.ts.map +1 -0
- package/dist/auto/types.js +3 -0
- package/dist/core/client.d.ts +691 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +2061 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +143 -0
- package/dist/core/types.d.ts +228 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/utils.d.ts +61 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +142 -0
- package/dist/core/ws.d.ts +121 -0
- package/dist/core/ws.d.ts.map +1 -0
- package/dist/core/ws.js +222 -0
- package/dist/info/account.d.ts +3 -0
- package/dist/info/account.d.ts.map +1 -0
- package/dist/info/account.js +198 -0
- package/dist/info/all-markets.d.ts +3 -0
- package/dist/info/all-markets.d.ts.map +1 -0
- package/dist/info/all-markets.js +272 -0
- package/dist/info/candles.d.ts +3 -0
- package/dist/info/candles.d.ts.map +1 -0
- package/dist/info/candles.js +120 -0
- package/dist/info/fees.d.ts +3 -0
- package/dist/info/fees.d.ts.map +1 -0
- package/dist/info/fees.js +87 -0
- package/dist/info/fills.d.ts +3 -0
- package/dist/info/fills.d.ts.map +1 -0
- package/dist/info/fills.js +105 -0
- package/dist/info/funding-history.d.ts +3 -0
- package/dist/info/funding-history.d.ts.map +1 -0
- package/dist/info/funding-history.js +98 -0
- package/dist/info/funding-scan.d.ts +3 -0
- package/dist/info/funding-scan.d.ts.map +1 -0
- package/dist/info/funding-scan.js +178 -0
- package/dist/info/funding.d.ts +3 -0
- package/dist/info/funding.d.ts.map +1 -0
- package/dist/info/funding.js +158 -0
- package/dist/info/markets.d.ts +3 -0
- package/dist/info/markets.d.ts.map +1 -0
- package/dist/info/markets.js +178 -0
- package/dist/info/order-status.d.ts +3 -0
- package/dist/info/order-status.d.ts.map +1 -0
- package/dist/info/order-status.js +85 -0
- package/dist/info/orders.d.ts +3 -0
- package/dist/info/orders.d.ts.map +1 -0
- package/dist/info/orders.js +162 -0
- package/dist/info/outcomes.d.ts +3 -0
- package/dist/info/outcomes.d.ts.map +1 -0
- package/dist/info/outcomes.js +175 -0
- package/dist/info/positions.d.ts +3 -0
- package/dist/info/positions.d.ts.map +1 -0
- package/dist/info/positions.js +127 -0
- package/dist/info/rate-limit.d.ts +3 -0
- package/dist/info/rate-limit.d.ts.map +1 -0
- package/dist/info/rate-limit.js +58 -0
- package/dist/info/search-markets.d.ts +3 -0
- package/dist/info/search-markets.d.ts.map +1 -0
- package/dist/info/search-markets.js +296 -0
- package/dist/info/spot.d.ts +3 -0
- package/dist/info/spot.d.ts.map +1 -0
- package/dist/info/spot.js +192 -0
- package/dist/info/trades.d.ts +3 -0
- package/dist/info/trades.d.ts.map +1 -0
- package/dist/info/trades.js +97 -0
- package/dist/lib.d.ts +14 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +17 -0
- package/dist/operations/bracket.d.ts +28 -0
- package/dist/operations/bracket.d.ts.map +1 -0
- package/dist/operations/bracket.js +266 -0
- package/dist/operations/cancel.d.ts +3 -0
- package/dist/operations/cancel.d.ts.map +1 -0
- package/dist/operations/cancel.js +107 -0
- package/dist/operations/chase.d.ts +25 -0
- package/dist/operations/chase.d.ts.map +1 -0
- package/dist/operations/chase.js +215 -0
- package/dist/operations/limit-order.d.ts +3 -0
- package/dist/operations/limit-order.d.ts.map +1 -0
- package/dist/operations/limit-order.js +144 -0
- package/dist/operations/market-order.d.ts +3 -0
- package/dist/operations/market-order.d.ts.map +1 -0
- package/dist/operations/market-order.js +153 -0
- package/dist/operations/outcome-order.d.ts +3 -0
- package/dist/operations/outcome-order.d.ts.map +1 -0
- package/dist/operations/outcome-order.js +171 -0
- package/dist/operations/scale.d.ts +3 -0
- package/dist/operations/scale.d.ts.map +1 -0
- package/dist/operations/scale.js +212 -0
- package/dist/operations/set-tpsl.d.ts +3 -0
- package/dist/operations/set-tpsl.d.ts.map +1 -0
- package/dist/operations/set-tpsl.js +277 -0
- package/dist/operations/spot-order.d.ts +3 -0
- package/dist/operations/spot-order.d.ts.map +1 -0
- package/dist/operations/spot-order.js +173 -0
- package/dist/operations/trigger-order.d.ts +3 -0
- package/dist/operations/trigger-order.d.ts.map +1 -0
- package/dist/operations/trigger-order.js +177 -0
- package/dist/operations/twap-cancel.d.ts +3 -0
- package/dist/operations/twap-cancel.d.ts.map +1 -0
- package/dist/operations/twap-cancel.js +57 -0
- package/dist/operations/twap-status.d.ts +3 -0
- package/dist/operations/twap-status.d.ts.map +1 -0
- package/dist/operations/twap-status.js +81 -0
- package/dist/operations/twap.d.ts +3 -0
- package/dist/operations/twap.d.ts.map +1 -0
- package/dist/operations/twap.js +124 -0
- package/dist/setup/approve-builder.d.ts +3 -0
- package/dist/setup/approve-builder.d.ts.map +1 -0
- package/dist/setup/approve-builder.js +155 -0
- package/dist/setup/env.d.ts +4 -0
- package/dist/setup/env.d.ts.map +1 -0
- package/dist/setup/env.js +8 -0
- package/dist/setup/onboard.d.ts +10 -0
- package/dist/setup/onboard.d.ts.map +1 -0
- package/dist/setup/onboard.js +462 -0
- package/package.json +10 -4
- package/scripts/core/client.ts +19 -1
- package/scripts/core/types.ts +7 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Open Broker - Automated Onboarding
|
|
3
|
+
// Creates wallet, configures environment, and approves builder fee
|
|
4
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as readline from 'readline';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { OPENBROKER_URL, ENV_TESTNET, ENV_CONFIG_PATH } from './env.js';
|
|
10
|
+
const OPEN_BROKER_BUILDER_ADDRESS = '0xbb67021fA3e62ab4DA985bb5a55c5c1884381068';
|
|
11
|
+
// Global config directory: ~/.openbroker/
|
|
12
|
+
const GLOBAL_CONFIG_DIR = path.join(homedir(), '.openbroker');
|
|
13
|
+
const GLOBAL_CONFIG_PATH = path.join(GLOBAL_CONFIG_DIR, '.env');
|
|
14
|
+
// Parse CLI flags
|
|
15
|
+
const cliArgs = process.argv.slice(2);
|
|
16
|
+
const useTestnet = cliArgs.includes('--testnet') || ENV_TESTNET;
|
|
17
|
+
const accountAddressIdx = cliArgs.indexOf('--account-address');
|
|
18
|
+
const cliAccountAddress = accountAddressIdx !== -1 ? cliArgs[accountAddressIdx + 1] : undefined;
|
|
19
|
+
const configPathIdx = cliArgs.indexOf('-c') !== -1 ? cliArgs.indexOf('-c') : cliArgs.indexOf('--config');
|
|
20
|
+
const cliConfigPath = configPathIdx !== -1 ? cliArgs[configPathIdx + 1] : ENV_CONFIG_PATH;
|
|
21
|
+
const CONFIG_PATH = cliConfigPath ? path.resolve(cliConfigPath) : GLOBAL_CONFIG_PATH;
|
|
22
|
+
const CONFIG_DIR = path.dirname(CONFIG_PATH);
|
|
23
|
+
function createReadline() {
|
|
24
|
+
return readline.createInterface({
|
|
25
|
+
input: process.stdin,
|
|
26
|
+
output: process.stdout,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function prompt(rl, question) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
rl.question(question, (answer) => {
|
|
32
|
+
resolve(answer.trim());
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function isValidPrivateKey(key) {
|
|
37
|
+
return /^0x[a-fA-F0-9]{64}$/.test(key);
|
|
38
|
+
}
|
|
39
|
+
function ensureConfigDir() {
|
|
40
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
41
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ── Polling & verification helpers ──
|
|
45
|
+
const POLL_INTERVAL_MS = 3000;
|
|
46
|
+
const POLL_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
47
|
+
async function pollForApproval(agentAddress) {
|
|
48
|
+
const startTime = Date.now();
|
|
49
|
+
const statusUrl = `${OPENBROKER_URL}/api/approve-status?agent=${agentAddress}${useTestnet ? '&network=testnet' : ''}`;
|
|
50
|
+
let dotCount = 0;
|
|
51
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(statusUrl);
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
if (data.status === 'approved' && data.master) {
|
|
56
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
57
|
+
return data.master;
|
|
58
|
+
}
|
|
59
|
+
if (data.status === 'expired') {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Network error — keep polling
|
|
65
|
+
}
|
|
66
|
+
dotCount = (dotCount + 1) % 4;
|
|
67
|
+
process.stdout.write(`\r Waiting for browser approval${'.'.repeat(dotCount)}${' '.repeat(3 - dotCount)}`);
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
69
|
+
}
|
|
70
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
async function verifyBuilderFee(masterAddress) {
|
|
74
|
+
const apiUrl = useTestnet ? 'https://api.hyperliquid-testnet.xyz/info' : 'https://api.hyperliquid.xyz/info';
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(apiUrl, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
type: 'maxBuilderFee',
|
|
81
|
+
user: masterAddress.toLowerCase(),
|
|
82
|
+
builder: OPEN_BROKER_BUILDER_ADDRESS.toLowerCase(),
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
const data = await response.json();
|
|
86
|
+
return data !== null && data !== 0 && data !== '0';
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function buildApiWalletEnvContent(privateKey, masterAddress) {
|
|
93
|
+
const network = useTestnet ? 'testnet' : 'mainnet';
|
|
94
|
+
return `# OpenBroker Configuration (API Wallet)
|
|
95
|
+
# Location: ${CONFIG_PATH}
|
|
96
|
+
# WARNING: Keep this file secret! Never share it!
|
|
97
|
+
|
|
98
|
+
# API wallet private key (can trade, cannot withdraw)
|
|
99
|
+
HYPERLIQUID_PRIVATE_KEY=${privateKey}
|
|
100
|
+
|
|
101
|
+
# Master account address (the wallet that owns the funds)
|
|
102
|
+
HYPERLIQUID_ACCOUNT_ADDRESS=${masterAddress}
|
|
103
|
+
|
|
104
|
+
# Network: mainnet or testnet
|
|
105
|
+
HYPERLIQUID_NETWORK=${network}
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
// ── API wallet setup flow ──
|
|
109
|
+
async function setupApiWallet() {
|
|
110
|
+
console.log('\nGenerating API wallet keypair...');
|
|
111
|
+
const privateKey = generatePrivateKey();
|
|
112
|
+
const apiAccount = privateKeyToAccount(privateKey);
|
|
113
|
+
console.log(`✅ API Wallet Address: ${apiAccount.address}\n`);
|
|
114
|
+
// Save partial config immediately (so the key isn't lost)
|
|
115
|
+
console.log('Step 2/3: Creating config...');
|
|
116
|
+
ensureConfigDir();
|
|
117
|
+
// Build the approval URL
|
|
118
|
+
const approveUrl = `${OPENBROKER_URL}/approve?agent=${apiAccount.address}${useTestnet ? '&network=testnet' : ''}`;
|
|
119
|
+
console.log(`✅ Config directory ready: ${CONFIG_DIR}\n`);
|
|
120
|
+
console.log('Step 3/3: Master wallet approval');
|
|
121
|
+
console.log('================================\n');
|
|
122
|
+
console.log('Your API wallet needs to be authorized by a master wallet.');
|
|
123
|
+
console.log('Open this URL in your browser and connect your master wallet:\n');
|
|
124
|
+
console.log(` ${approveUrl}\n`);
|
|
125
|
+
if (useTestnet) {
|
|
126
|
+
console.log('The master wallet will sign one transaction:');
|
|
127
|
+
console.log(' 1. ApproveAgent — authorizes this API wallet to trade');
|
|
128
|
+
console.log(' (Builder fee approval is skipped on testnet)\n');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log('The master wallet will sign two transactions:');
|
|
132
|
+
console.log(' 1. ApproveAgent — authorizes this API wallet to trade');
|
|
133
|
+
console.log(' 2. ApproveBuilderFee — approves the 1 bps builder fee\n');
|
|
134
|
+
}
|
|
135
|
+
// Poll for approval
|
|
136
|
+
const masterAddress = await pollForApproval(apiAccount.address);
|
|
137
|
+
if (!masterAddress) {
|
|
138
|
+
console.log('\n⚠️ Approval timed out or was not completed.');
|
|
139
|
+
console.log(` You can retry by visiting: ${approveUrl}`);
|
|
140
|
+
console.log(' After approval, re-run: openbroker setup\n');
|
|
141
|
+
// Save config without master address so user can manually add it later
|
|
142
|
+
const partialEnv = `# OpenBroker Configuration (API Wallet — INCOMPLETE)
|
|
143
|
+
# Location: ~/.openbroker/.env
|
|
144
|
+
# WARNING: Keep this file secret! Never share it!
|
|
145
|
+
# NOTE: Approval not completed. Re-run "openbroker setup" after approving.
|
|
146
|
+
|
|
147
|
+
# API wallet private key
|
|
148
|
+
HYPERLIQUID_PRIVATE_KEY=${privateKey}
|
|
149
|
+
|
|
150
|
+
# TODO: Set this after approving at ${approveUrl}
|
|
151
|
+
# HYPERLIQUID_ACCOUNT_ADDRESS=0x...
|
|
152
|
+
|
|
153
|
+
HYPERLIQUID_NETWORK=mainnet
|
|
154
|
+
`;
|
|
155
|
+
fs.writeFileSync(CONFIG_PATH, partialEnv, { mode: 0o600 });
|
|
156
|
+
console.log(` Partial config saved to: ${CONFIG_PATH}`);
|
|
157
|
+
return { success: false, error: 'Approval not completed' };
|
|
158
|
+
}
|
|
159
|
+
console.log(`\n✅ Master wallet detected: ${masterAddress}`);
|
|
160
|
+
// Verify builder fee on-chain (skip on testnet)
|
|
161
|
+
if (!useTestnet) {
|
|
162
|
+
console.log(' Verifying builder fee approval...');
|
|
163
|
+
const feeApproved = await verifyBuilderFee(masterAddress);
|
|
164
|
+
if (feeApproved) {
|
|
165
|
+
console.log(' ✅ Builder fee: approved on-chain');
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(' ⚠️ Builder fee not yet confirmed on-chain (may take a moment)');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log(' (Builder fee verification skipped on testnet)');
|
|
173
|
+
}
|
|
174
|
+
// Save complete config
|
|
175
|
+
const envContent = buildApiWalletEnvContent(privateKey, masterAddress);
|
|
176
|
+
fs.writeFileSync(CONFIG_PATH, envContent, { mode: 0o600 });
|
|
177
|
+
console.log(`\n✅ Config saved to: ${CONFIG_PATH}`);
|
|
178
|
+
// Final summary
|
|
179
|
+
console.log('\n========================================');
|
|
180
|
+
console.log(' SETUP COMPLETE! ');
|
|
181
|
+
console.log('========================================\n');
|
|
182
|
+
console.log('API Wallet Setup');
|
|
183
|
+
console.log('-----------------');
|
|
184
|
+
console.log(`API Wallet: ${apiAccount.address}`);
|
|
185
|
+
console.log(`Master Account: ${masterAddress}`);
|
|
186
|
+
console.log(`Network: Hyperliquid (Mainnet)`);
|
|
187
|
+
console.log(`Config: ${CONFIG_PATH}`);
|
|
188
|
+
console.log('\n📋 Next Steps');
|
|
189
|
+
console.log('--------------');
|
|
190
|
+
console.log('1. Ensure your master wallet is funded on Hyperliquid');
|
|
191
|
+
console.log('2. Start trading:');
|
|
192
|
+
console.log(' openbroker account');
|
|
193
|
+
console.log(' openbroker buy --coin ETH --size 0.01 --dry');
|
|
194
|
+
console.log('\n⚠️ Security');
|
|
195
|
+
console.log('------------');
|
|
196
|
+
console.log('This API wallet can trade but CANNOT withdraw funds.');
|
|
197
|
+
console.log('You can revoke access at any time from app.hyperliquid.xyz');
|
|
198
|
+
console.log(`Config stored at: ${CONFIG_PATH}`);
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
walletAddress: apiAccount.address,
|
|
202
|
+
privateKey: privateKey,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// ── Main ──
|
|
206
|
+
async function main() {
|
|
207
|
+
if (cliArgs.includes('--help') || cliArgs.includes('-h')) {
|
|
208
|
+
console.log(`
|
|
209
|
+
OpenBroker Setup
|
|
210
|
+
|
|
211
|
+
Usage: openbroker setup [options]
|
|
212
|
+
|
|
213
|
+
Options:
|
|
214
|
+
-c, --config <path> Save config to a custom path (default: ~/.openbroker/.env)
|
|
215
|
+
--testnet Configure for testnet
|
|
216
|
+
--account-address <addr> Set HYPERLIQUID_ACCOUNT_ADDRESS (for API wallet / vault trading)
|
|
217
|
+
--help Show this help
|
|
218
|
+
|
|
219
|
+
Examples:
|
|
220
|
+
openbroker setup # Interactive → ~/.openbroker/.env
|
|
221
|
+
openbroker setup -c .env --testnet # Write to ./.env for testnet
|
|
222
|
+
openbroker setup -c ./testnet.env --testnet --account-address 0x... # API wallet config
|
|
223
|
+
`);
|
|
224
|
+
process.exit(0);
|
|
225
|
+
}
|
|
226
|
+
console.log('OpenBroker - One-Command Setup');
|
|
227
|
+
console.log('==============================\n');
|
|
228
|
+
if (cliConfigPath)
|
|
229
|
+
console.log(`Config will be saved to: ${CONFIG_PATH}\n`);
|
|
230
|
+
if (useTestnet)
|
|
231
|
+
console.log('Network: testnet\n');
|
|
232
|
+
console.log('This will: 1) Create wallet 2) Save config 3) Approve builder fee\n');
|
|
233
|
+
// Check if config already exists
|
|
234
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
235
|
+
const envContent = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
236
|
+
const keyMatch = envContent.match(/HYPERLIQUID_PRIVATE_KEY=0x([a-fA-F0-9]{64})/);
|
|
237
|
+
if (!keyMatch) {
|
|
238
|
+
return {
|
|
239
|
+
success: false,
|
|
240
|
+
error: 'Invalid config file - missing or malformed private key',
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const existingKey = `0x${keyMatch[1]}`;
|
|
244
|
+
const account = privateKeyToAccount(existingKey);
|
|
245
|
+
// Check if this is an incomplete API wallet setup (HYPERLIQUID_ACCOUNT_ADDRESS missing or commented out)
|
|
246
|
+
const hasAccountAddress = /^HYPERLIQUID_ACCOUNT_ADDRESS=0x[a-fA-F0-9]{40}/m.test(envContent);
|
|
247
|
+
const isIncompleteApiWallet = envContent.includes('INCOMPLETE') || envContent.includes('# HYPERLIQUID_ACCOUNT_ADDRESS');
|
|
248
|
+
if (!hasAccountAddress && isIncompleteApiWallet) {
|
|
249
|
+
console.log('⚠️ Incomplete API wallet setup detected!');
|
|
250
|
+
console.log(` API Wallet: ${account.address}`);
|
|
251
|
+
console.log(` Master account address is missing — re-polling for approval...\n`);
|
|
252
|
+
const approveUrl = `${OPENBROKER_URL}/approve?agent=${account.address}${useTestnet ? '&network=testnet' : ''}`;
|
|
253
|
+
console.log(` If not yet approved, visit: ${approveUrl}\n`);
|
|
254
|
+
const masterAddress = await pollForApproval(account.address);
|
|
255
|
+
if (masterAddress) {
|
|
256
|
+
console.log(`\n✅ Master wallet detected: ${masterAddress}`);
|
|
257
|
+
// Verify builder fee on-chain
|
|
258
|
+
console.log(' Verifying builder fee approval...');
|
|
259
|
+
const feeApproved = await verifyBuilderFee(masterAddress);
|
|
260
|
+
if (feeApproved) {
|
|
261
|
+
console.log(' ✅ Builder fee: approved on-chain');
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
console.log(' ⚠️ Builder fee not yet confirmed on-chain (may take a moment)');
|
|
265
|
+
}
|
|
266
|
+
// Save complete config
|
|
267
|
+
const completeEnv = buildApiWalletEnvContent(existingKey, masterAddress);
|
|
268
|
+
fs.writeFileSync(CONFIG_PATH, completeEnv, { mode: 0o600 });
|
|
269
|
+
console.log(`\n✅ Config updated: ${CONFIG_PATH}`);
|
|
270
|
+
console.log(` API Wallet: ${account.address}`);
|
|
271
|
+
console.log(` Master Account: ${masterAddress}`);
|
|
272
|
+
console.log('\n Start trading: openbroker account');
|
|
273
|
+
return { success: true, walletAddress: account.address };
|
|
274
|
+
}
|
|
275
|
+
console.log('\n⚠️ Approval still not completed.');
|
|
276
|
+
console.log(` Visit: ${approveUrl}`);
|
|
277
|
+
console.log(' Then re-run: openbroker setup\n');
|
|
278
|
+
return { success: false, error: 'Approval not completed' };
|
|
279
|
+
}
|
|
280
|
+
// Config exists and is complete
|
|
281
|
+
console.log('⚠️ Config already exists!');
|
|
282
|
+
console.log(` Location: ${CONFIG_PATH}\n`);
|
|
283
|
+
console.log('Current Configuration');
|
|
284
|
+
console.log('---------------------');
|
|
285
|
+
console.log(`Wallet Address: ${account.address}`);
|
|
286
|
+
if (hasAccountAddress) {
|
|
287
|
+
const addrMatch = envContent.match(/HYPERLIQUID_ACCOUNT_ADDRESS=(0x[a-fA-F0-9]+)/);
|
|
288
|
+
if (addrMatch) {
|
|
289
|
+
console.log(`Master Account: ${addrMatch[1]}`);
|
|
290
|
+
console.log(`Wallet Type: API Wallet`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
console.log(`Config File: ${CONFIG_PATH}`);
|
|
294
|
+
console.log(`\nTo reconfigure, delete the config file first:`);
|
|
295
|
+
console.log(` rm ${CONFIG_PATH}`);
|
|
296
|
+
console.log(`\nTo fund this wallet, send USDC on Arbitrum, then deposit at:`);
|
|
297
|
+
console.log(` https://app.hyperliquid.xyz/`);
|
|
298
|
+
return {
|
|
299
|
+
success: true,
|
|
300
|
+
walletAddress: account.address,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// Ask user which setup mode
|
|
304
|
+
const rl = createReadline();
|
|
305
|
+
console.log('Step 1/3: Wallet Setup');
|
|
306
|
+
console.log('----------------------');
|
|
307
|
+
console.log('How would you like to set up your wallet?\n');
|
|
308
|
+
console.log(' 1) Generate a fresh wallet (recommended for agents)');
|
|
309
|
+
console.log(' Creates a dedicated trading wallet. Builder fee is auto-approved.');
|
|
310
|
+
console.log(' Just fund it with USDC and start trading — no browser steps needed.');
|
|
311
|
+
console.log('');
|
|
312
|
+
console.log(' 2) Import existing private key');
|
|
313
|
+
console.log(' 3) Generate API wallet (restricted, requires browser approval)');
|
|
314
|
+
console.log(' Can trade but cannot withdraw. Requires master wallet approval in browser.\n');
|
|
315
|
+
let choice = '';
|
|
316
|
+
while (choice !== '1' && choice !== '2' && choice !== '3') {
|
|
317
|
+
choice = await prompt(rl, 'Enter choice (1, 2, or 3): ');
|
|
318
|
+
if (choice !== '1' && choice !== '2' && choice !== '3') {
|
|
319
|
+
console.log('Please enter 1, 2, or 3');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
rl.close();
|
|
323
|
+
// Option 3: API wallet flow
|
|
324
|
+
if (choice === '3') {
|
|
325
|
+
return setupApiWallet();
|
|
326
|
+
}
|
|
327
|
+
// Options 1 & 2: Master wallet flow. Initialised in the branches below —
|
|
328
|
+
// the compiler can't prove the while-loop in option 2 assigns, so we use
|
|
329
|
+
// `| undefined` and narrow at use sites (`assertDefined`).
|
|
330
|
+
let privateKey;
|
|
331
|
+
if (choice === '2') {
|
|
332
|
+
// User has existing key
|
|
333
|
+
const rl2 = createReadline();
|
|
334
|
+
console.log('\nEnter your private key (0x... format):\n');
|
|
335
|
+
let validKey = false;
|
|
336
|
+
while (!validKey) {
|
|
337
|
+
const inputKey = await prompt(rl2, 'Private key: ');
|
|
338
|
+
if (isValidPrivateKey(inputKey)) {
|
|
339
|
+
privateKey = inputKey;
|
|
340
|
+
validKey = true;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
console.log('Invalid private key format. Must be 0x followed by 64 hex characters.');
|
|
344
|
+
console.log('Example: 0x1234...abcd (66 characters total)\n');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
rl2.close();
|
|
348
|
+
console.log('\n✅ Private key accepted');
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
// Generate new wallet (option 1)
|
|
352
|
+
console.log('\nGenerating new wallet...');
|
|
353
|
+
privateKey = generatePrivateKey();
|
|
354
|
+
console.log('✅ New wallet created');
|
|
355
|
+
}
|
|
356
|
+
if (!privateKey) {
|
|
357
|
+
throw new Error('Internal error: privateKey was not set by onboarding flow.');
|
|
358
|
+
}
|
|
359
|
+
// Derive account from private key
|
|
360
|
+
const account = privateKeyToAccount(privateKey);
|
|
361
|
+
console.log(`\nWallet Address: ${account.address}\n`);
|
|
362
|
+
// Create config directory and file
|
|
363
|
+
console.log('Step 2/3: Creating config...');
|
|
364
|
+
ensureConfigDir();
|
|
365
|
+
const network = useTestnet ? 'testnet' : 'mainnet';
|
|
366
|
+
const accountLine = cliAccountAddress ? `\n# Master/vault account address\nHYPERLIQUID_ACCOUNT_ADDRESS=${cliAccountAddress}\n` : '';
|
|
367
|
+
const envContent = `# OpenBroker Configuration
|
|
368
|
+
# Location: ${CONFIG_PATH}
|
|
369
|
+
# WARNING: Keep this file secret! Never share it!
|
|
370
|
+
|
|
371
|
+
# Your wallet private key
|
|
372
|
+
HYPERLIQUID_PRIVATE_KEY=${privateKey}
|
|
373
|
+
${accountLine}
|
|
374
|
+
# Network: mainnet or testnet
|
|
375
|
+
HYPERLIQUID_NETWORK=${network}
|
|
376
|
+
`;
|
|
377
|
+
fs.writeFileSync(CONFIG_PATH, envContent, { mode: 0o600 });
|
|
378
|
+
console.log(`✅ Config saved to: ${CONFIG_PATH}\n`);
|
|
379
|
+
// Approve builder fee (automatic - no user action needed; skip on testnet)
|
|
380
|
+
if (useTestnet) {
|
|
381
|
+
console.log('Step 3/3: Skipping builder fee approval (testnet)\n');
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
console.log('Step 3/3: Approving builder fee...');
|
|
385
|
+
console.log('(This is automatic, and required for trading)\n');
|
|
386
|
+
try {
|
|
387
|
+
// Import and run approve-builder inline
|
|
388
|
+
const { getClient } = await import('../core/client.js');
|
|
389
|
+
const client = getClient();
|
|
390
|
+
console.log(` Account: ${client.address}`);
|
|
391
|
+
console.log(` Builder: ${OPEN_BROKER_BUILDER_ADDRESS}`);
|
|
392
|
+
// Check if already approved
|
|
393
|
+
const currentApproval = await client.getMaxBuilderFee(client.address, OPEN_BROKER_BUILDER_ADDRESS);
|
|
394
|
+
if (currentApproval) {
|
|
395
|
+
console.log(`\n✅ Builder fee already approved (${currentApproval})`);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
console.log('\n Sending approval transaction...');
|
|
399
|
+
const result = await client.approveBuilderFee('0.1%', OPEN_BROKER_BUILDER_ADDRESS);
|
|
400
|
+
if (result.status === 'ok') {
|
|
401
|
+
console.log('✅ Builder fee approved successfully!');
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
console.log(`⚠️ Approval may have failed: ${result.response}`);
|
|
405
|
+
console.log(' You can retry later: openbroker approve-builder');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
console.log(`⚠️ Could not approve builder fee: ${error}`);
|
|
411
|
+
console.log(' You can retry later: openbroker approve-builder');
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// Final summary
|
|
415
|
+
console.log('\n========================================');
|
|
416
|
+
console.log(' SETUP COMPLETE! ');
|
|
417
|
+
console.log('========================================\n');
|
|
418
|
+
console.log('Your Trading Wallet');
|
|
419
|
+
console.log('-------------------');
|
|
420
|
+
console.log(`Address: ${account.address}`);
|
|
421
|
+
console.log(`Network: Hyperliquid (Mainnet)`);
|
|
422
|
+
console.log(`Config: ${CONFIG_PATH}`);
|
|
423
|
+
if (choice === '1' || choice === '2') {
|
|
424
|
+
console.log('\n⚠️ IMPORTANT: Save your private key!');
|
|
425
|
+
console.log('-----------------------------------');
|
|
426
|
+
console.log(`Private Key: ${privateKey}`);
|
|
427
|
+
console.log('\nThis key is stored in ~/.openbroker/.env');
|
|
428
|
+
console.log('Back it up securely - if lost, funds cannot be recovered!');
|
|
429
|
+
}
|
|
430
|
+
console.log('\n📋 Next Steps');
|
|
431
|
+
console.log('--------------');
|
|
432
|
+
console.log('1. Fund your wallet with USDC on Arbitrum:');
|
|
433
|
+
console.log(` ${account.address}`);
|
|
434
|
+
console.log('');
|
|
435
|
+
console.log('2. Deposit USDC to Hyperliquid:');
|
|
436
|
+
console.log(' https://app.hyperliquid.xyz/');
|
|
437
|
+
console.log('');
|
|
438
|
+
console.log('3. Start trading!');
|
|
439
|
+
console.log(' openbroker account');
|
|
440
|
+
console.log(' openbroker buy --coin ETH --size 0.01 --dry');
|
|
441
|
+
console.log('\n⚠️ Security');
|
|
442
|
+
console.log('------------');
|
|
443
|
+
console.log(`Config stored at: ${CONFIG_PATH}`);
|
|
444
|
+
console.log('Never share this file or your private key!');
|
|
445
|
+
return {
|
|
446
|
+
success: true,
|
|
447
|
+
walletAddress: account.address,
|
|
448
|
+
privateKey: privateKey,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
// Export for programmatic use
|
|
452
|
+
export { main as onboard };
|
|
453
|
+
// Run if executed directly
|
|
454
|
+
main().then(result => {
|
|
455
|
+
if (!result.success) {
|
|
456
|
+
console.error(`\nSetup failed: ${result.error}`);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
}).catch(error => {
|
|
460
|
+
console.error('Setup error:', error);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
});
|
package/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbroker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"openbroker": "./bin/openbroker.js"
|
|
8
8
|
},
|
|
9
|
-
"main": "./
|
|
9
|
+
"main": "./dist/lib.js",
|
|
10
|
+
"types": "./dist/lib.d.ts",
|
|
10
11
|
"exports": {
|
|
11
|
-
".":
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/lib.d.ts",
|
|
14
|
+
"default": "./dist/lib.js"
|
|
15
|
+
},
|
|
12
16
|
"./package.json": "./package.json"
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
15
19
|
"bin/",
|
|
20
|
+
"dist/",
|
|
16
21
|
"scripts/",
|
|
17
22
|
"config/example.env",
|
|
18
23
|
"SKILL.md",
|
|
@@ -44,7 +49,8 @@
|
|
|
44
49
|
"chase": "tsx scripts/operations/chase.ts",
|
|
45
50
|
"funding-scan": "tsx scripts/info/funding-scan.ts",
|
|
46
51
|
"outcomes": "tsx scripts/info/outcomes.ts",
|
|
47
|
-
"
|
|
52
|
+
"build": "tsc",
|
|
53
|
+
"prepublishOnly": "npm run build && npm run test:cli",
|
|
48
54
|
"test:cli": "node --import tsx bin/cli.ts --help"
|
|
49
55
|
},
|
|
50
56
|
"dependencies": {
|
package/scripts/core/client.ts
CHANGED
|
@@ -1978,11 +1978,27 @@ export class HyperliquidClient {
|
|
|
1978
1978
|
* For unified accounts: equity comes from spotClearinghouseState (single USDC balance).
|
|
1979
1979
|
* For standard accounts: aggregates margin summaries from each dex.
|
|
1980
1980
|
*/
|
|
1981
|
+
/**
|
|
1982
|
+
* Stamp a position with its canonical asset index and normalize its coin to the
|
|
1983
|
+
* prefixed `{dex}:{coin}` form. The per-dex clearinghouseState may report HIP-3
|
|
1984
|
+
* coins bare; we know the dex at fetch time, so we canonicalize here and look up
|
|
1985
|
+
* the global index in assetMap (which is keyed by the prefixed name).
|
|
1986
|
+
*/
|
|
1987
|
+
private stampAssetId(position: { coin: string; assetId?: number }, dexName?: string | null): void {
|
|
1988
|
+
let coin = position.coin;
|
|
1989
|
+
if (dexName && !coin.includes(':')) coin = `${dexName}:${coin}`;
|
|
1990
|
+
position.coin = coin;
|
|
1991
|
+
const idx = this.assetMap.get(coin);
|
|
1992
|
+
if (idx !== undefined) position.assetId = idx;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1981
1995
|
async getUserStateAll(user?: string): Promise<ClearinghouseState> {
|
|
1982
1996
|
await this.getMetaAndAssetCtxs(); // Ensure HIP-3 dex list is loaded
|
|
1983
1997
|
|
|
1984
1998
|
const unified = await this.isUnifiedAccount(user);
|
|
1985
1999
|
const mainState = await this.getUserState(user);
|
|
2000
|
+
// Stamp native positions (dexIdx 0; coins are already bare/canonical).
|
|
2001
|
+
for (const ap of mainState.assetPositions ?? []) this.stampAssetId(ap.position);
|
|
1986
2002
|
|
|
1987
2003
|
// Collect positions from all HIP-3 dexes (in parallel; testnet: only loaded dexes)
|
|
1988
2004
|
const validDexs = await this.getIterableHip3Dexs();
|
|
@@ -2006,8 +2022,10 @@ export class HyperliquidClient {
|
|
|
2006
2022
|
this.log(`Failed to fetch state for HIP-3 dex:`, result.reason instanceof Error ? result.reason.message : String(result.reason));
|
|
2007
2023
|
continue;
|
|
2008
2024
|
}
|
|
2009
|
-
const { dexState } = result.value;
|
|
2025
|
+
const { dex, dexState } = result.value;
|
|
2010
2026
|
if (dexState.assetPositions?.length > 0) {
|
|
2027
|
+
// Canonicalize + stamp asset index using the dex we fetched under.
|
|
2028
|
+
for (const ap of dexState.assetPositions) this.stampAssetId(ap.position, dex.name);
|
|
2011
2029
|
mainState.assetPositions.push(...dexState.assetPositions);
|
|
2012
2030
|
}
|
|
2013
2031
|
|
package/scripts/core/types.ts
CHANGED
|
@@ -173,6 +173,13 @@ export interface OutcomeMarket {
|
|
|
173
173
|
|
|
174
174
|
export interface Position {
|
|
175
175
|
coin: string;
|
|
176
|
+
/**
|
|
177
|
+
* Canonical Hyperliquid asset index (native: meta index; HIP-3:
|
|
178
|
+
* 100000 + perp_dex_index*10000 + index_in_meta). Stamped by `getUserStateAll`
|
|
179
|
+
* so consumers can key positions by a unique integer rather than the coin string
|
|
180
|
+
* (whose HIP-3 prefix may differ between endpoints). Undefined if unresolvable.
|
|
181
|
+
*/
|
|
182
|
+
assetId?: number;
|
|
176
183
|
szi: string; // signed size (negative = short)
|
|
177
184
|
entryPx: string;
|
|
178
185
|
positionValue: string;
|