openbroker 1.3.1 → 1.4.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 +12 -0
- package/SKILL.md +7 -4
- 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 +684 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +2040 -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 +221 -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 +13 -3
- package/scripts/info/all-markets.ts +18 -2
- package/scripts/info/search-markets.ts +18 -2
|
@@ -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.4.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
|
@@ -668,9 +668,19 @@ export class HyperliquidClient {
|
|
|
668
668
|
private parseOutcomeDescription(description: string): Record<string, string> {
|
|
669
669
|
const parsed: Record<string, string> = {};
|
|
670
670
|
for (const part of description.split('|')) {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
671
|
+
const rawSegment = part.trim();
|
|
672
|
+
const metadataIdx = rawSegment.indexOf('metadata=');
|
|
673
|
+
const segments = metadataIdx >= 0
|
|
674
|
+
? [rawSegment, rawSegment.slice(metadataIdx + 'metadata='.length)]
|
|
675
|
+
: [rawSegment];
|
|
676
|
+
|
|
677
|
+
for (const segment of segments) {
|
|
678
|
+
const idx = segment.indexOf(':');
|
|
679
|
+
if (idx <= 0) continue;
|
|
680
|
+
const key = segment.slice(0, idx).trim();
|
|
681
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key)) continue;
|
|
682
|
+
parsed[key] = segment.slice(idx + 1).trim();
|
|
683
|
+
}
|
|
674
684
|
}
|
|
675
685
|
return parsed;
|
|
676
686
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
// All Markets - View all available markets across perps, spot, and HIP-
|
|
2
|
+
// All Markets - View all available markets across perps, HIP-3, spot, and HIP-4 outcomes
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
5
|
|
|
@@ -88,9 +88,22 @@ interface MarketRow {
|
|
|
88
88
|
maxLeverage?: number;
|
|
89
89
|
outcome?: number;
|
|
90
90
|
outcomeSide?: string;
|
|
91
|
+
outcomeName?: string;
|
|
92
|
+
tokenName?: string;
|
|
93
|
+
parsedDescription?: Record<string, string>;
|
|
91
94
|
description?: string;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
function handleMarketFetchError(kind: NonNullable<Args['type']>, error: unknown, requestedType: Args['type'], verbose?: boolean): void {
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
if (requestedType === kind) {
|
|
100
|
+
throw new Error(`Failed to fetch ${kind} markets: ${message}`);
|
|
101
|
+
}
|
|
102
|
+
if (verbose) {
|
|
103
|
+
console.error(`Failed to fetch ${kind} markets:`, error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
async function main() {
|
|
95
108
|
const args = parseArgs();
|
|
96
109
|
const client = getClient();
|
|
@@ -202,12 +215,15 @@ async function main() {
|
|
|
202
215
|
volume24h: parseFloat(side.dayNtlVlm || '0'),
|
|
203
216
|
outcome: market.outcome,
|
|
204
217
|
outcomeSide: side.name,
|
|
218
|
+
outcomeName: market.name,
|
|
219
|
+
tokenName: side.tokenName,
|
|
220
|
+
parsedDescription: market.parsedDescription,
|
|
205
221
|
description: market.description,
|
|
206
222
|
});
|
|
207
223
|
}
|
|
208
224
|
}
|
|
209
225
|
} catch (e) {
|
|
210
|
-
|
|
226
|
+
handleMarketFetchError('outcome', e, args.type, args.verbose);
|
|
211
227
|
}
|
|
212
228
|
}
|
|
213
229
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
-
// Search Markets - Find specific assets across all providers (perps, HIP-3, spot)
|
|
2
|
+
// Search Markets - Find specific assets across all providers (perps, HIP-3, spot, HIP-4)
|
|
3
3
|
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
5
|
|
|
@@ -85,6 +85,16 @@ function formatFunding(rate: string): string {
|
|
|
85
85
|
return `${sign}${annualized.toFixed(2)}%`;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function handleMarketFetchError(kind: NonNullable<Args['type']>, error: unknown, requestedType: Args['type'], verbose?: boolean): void {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
if (requestedType === kind) {
|
|
91
|
+
throw new Error(`Failed to fetch ${kind} markets: ${message}`);
|
|
92
|
+
}
|
|
93
|
+
if (verbose) {
|
|
94
|
+
console.error(`Failed to fetch ${kind} markets:`, error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
async function main() {
|
|
89
99
|
const args = parseArgs();
|
|
90
100
|
const client = getClient();
|
|
@@ -107,6 +117,9 @@ async function main() {
|
|
|
107
117
|
openInterest?: string;
|
|
108
118
|
outcome?: number;
|
|
109
119
|
outcomeSide?: string;
|
|
120
|
+
outcomeName?: string;
|
|
121
|
+
tokenName?: string;
|
|
122
|
+
parsedDescription?: Record<string, string>;
|
|
110
123
|
description?: string;
|
|
111
124
|
}
|
|
112
125
|
|
|
@@ -247,12 +260,15 @@ async function main() {
|
|
|
247
260
|
volume24h: parseFloat(side.dayNtlVlm || '0'),
|
|
248
261
|
outcome: market.outcome,
|
|
249
262
|
outcomeSide: side.name,
|
|
263
|
+
outcomeName: market.name,
|
|
264
|
+
tokenName: side.tokenName,
|
|
265
|
+
parsedDescription: market.parsedDescription,
|
|
250
266
|
description: market.description,
|
|
251
267
|
});
|
|
252
268
|
}
|
|
253
269
|
}
|
|
254
270
|
} catch (e) {
|
|
255
|
-
|
|
271
|
+
handleMarketFetchError('outcome', e, args.type, args.verbose);
|
|
256
272
|
}
|
|
257
273
|
}
|
|
258
274
|
|