openbroker 1.0.39 → 1.0.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbroker",
3
- "version": "1.0.39",
3
+ "version": "1.0.40",
4
4
  "description": "Hyperliquid trading CLI - execute orders, manage positions, and run trading strategies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@ import * as readline from 'readline';
9
9
  import { homedir } from 'os';
10
10
 
11
11
  const OPEN_BROKER_BUILDER_ADDRESS = '0xbb67021fA3e62ab4DA985bb5a55c5c1884381068';
12
+ const OPENBROKER_URL = process.env.OPENBROKER_URL || 'https://openbroker.dev';
12
13
 
13
14
  // Global config directory: ~/.openbroker/
14
15
  const CONFIG_DIR = path.join(homedir(), '.openbroker');
@@ -46,6 +47,190 @@ function ensureConfigDir(): void {
46
47
  }
47
48
  }
48
49
 
50
+ // ── Polling & verification helpers ──
51
+
52
+ const POLL_INTERVAL_MS = 3000;
53
+ const POLL_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
54
+
55
+ async function pollForApproval(agentAddress: string): Promise<string | null> {
56
+ const startTime = Date.now();
57
+ const statusUrl = `${OPENBROKER_URL}/api/approve-status?agent=${agentAddress}`;
58
+
59
+ let dotCount = 0;
60
+
61
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
62
+ try {
63
+ const response = await fetch(statusUrl);
64
+ const data = await response.json() as { status: string; master?: string };
65
+
66
+ if (data.status === 'approved' && data.master) {
67
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
68
+ return data.master;
69
+ }
70
+
71
+ if (data.status === 'expired') {
72
+ return null;
73
+ }
74
+ } catch {
75
+ // Network error — keep polling
76
+ }
77
+
78
+ dotCount = (dotCount + 1) % 4;
79
+ process.stdout.write(`\r Waiting for browser approval${'.'.repeat(dotCount)}${' '.repeat(3 - dotCount)}`);
80
+
81
+ await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
82
+ }
83
+
84
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
85
+ return null;
86
+ }
87
+
88
+ async function verifyBuilderFee(masterAddress: string): Promise<boolean> {
89
+ try {
90
+ const response = await fetch('https://api.hyperliquid.xyz/info', {
91
+ method: 'POST',
92
+ headers: { 'Content-Type': 'application/json' },
93
+ body: JSON.stringify({
94
+ type: 'maxBuilderFee',
95
+ user: masterAddress.toLowerCase(),
96
+ builder: OPEN_BROKER_BUILDER_ADDRESS.toLowerCase(),
97
+ }),
98
+ });
99
+ const data = await response.json();
100
+ return data !== null && data !== 0 && data !== '0';
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ function buildApiWalletEnvContent(privateKey: string, masterAddress: string): string {
107
+ return `# OpenBroker Configuration (API Wallet)
108
+ # Location: ~/.openbroker/.env
109
+ # WARNING: Keep this file secret! Never share it!
110
+
111
+ # API wallet private key (can trade, cannot withdraw)
112
+ HYPERLIQUID_PRIVATE_KEY=${privateKey}
113
+
114
+ # Master account address (the wallet that owns the funds)
115
+ HYPERLIQUID_ACCOUNT_ADDRESS=${masterAddress}
116
+
117
+ # Network: mainnet or testnet
118
+ HYPERLIQUID_NETWORK=mainnet
119
+
120
+ # Builder fee (supports openbroker development)
121
+ # Default: 1 bps (0.01%) on trades
122
+ BUILDER_ADDRESS=${OPEN_BROKER_BUILDER_ADDRESS}
123
+ BUILDER_FEE=10
124
+ `;
125
+ }
126
+
127
+ // ── API wallet setup flow ──
128
+
129
+ async function setupApiWallet(): Promise<OnboardResult> {
130
+ console.log('\nGenerating API wallet keypair...');
131
+ const privateKey = generatePrivateKey();
132
+ const apiAccount = privateKeyToAccount(privateKey);
133
+ console.log(`✅ API Wallet Address: ${apiAccount.address}\n`);
134
+
135
+ // Save partial config immediately (so the key isn't lost)
136
+ console.log('Step 2/3: Creating config...');
137
+ ensureConfigDir();
138
+
139
+ // Build the approval URL
140
+ const approveUrl = `${OPENBROKER_URL}/approve?agent=${apiAccount.address}`;
141
+
142
+ console.log(`✅ Config directory ready: ${CONFIG_DIR}\n`);
143
+
144
+ console.log('Step 3/3: Master wallet approval');
145
+ console.log('================================\n');
146
+ console.log('Your API wallet needs to be authorized by a master wallet.');
147
+ console.log('Open this URL in your browser and connect your master wallet:\n');
148
+ console.log(` ${approveUrl}\n`);
149
+ console.log('The master wallet will sign two transactions:');
150
+ console.log(' 1. ApproveAgent — authorizes this API wallet to trade');
151
+ console.log(' 2. ApproveBuilderFee — approves the 1 bps builder fee\n');
152
+
153
+ // Poll for approval
154
+ const masterAddress = await pollForApproval(apiAccount.address);
155
+
156
+ if (!masterAddress) {
157
+ console.log('\n⚠️ Approval timed out or was not completed.');
158
+ console.log(` You can retry by visiting: ${approveUrl}`);
159
+ console.log(' After approval, re-run: openbroker setup\n');
160
+
161
+ // Save config without master address so user can manually add it later
162
+ const partialEnv = `# OpenBroker Configuration (API Wallet — INCOMPLETE)
163
+ # Location: ~/.openbroker/.env
164
+ # WARNING: Keep this file secret! Never share it!
165
+ # NOTE: Approval not completed. Re-run "openbroker setup" after approving.
166
+
167
+ # API wallet private key
168
+ HYPERLIQUID_PRIVATE_KEY=${privateKey}
169
+
170
+ # TODO: Set this after approving at ${approveUrl}
171
+ # HYPERLIQUID_ACCOUNT_ADDRESS=0x...
172
+
173
+ HYPERLIQUID_NETWORK=mainnet
174
+ BUILDER_ADDRESS=${OPEN_BROKER_BUILDER_ADDRESS}
175
+ BUILDER_FEE=10
176
+ `;
177
+ fs.writeFileSync(CONFIG_PATH, partialEnv, { mode: 0o600 });
178
+ console.log(` Partial config saved to: ${CONFIG_PATH}`);
179
+
180
+ return { success: false, error: 'Approval not completed' };
181
+ }
182
+
183
+ console.log(`\n✅ Master wallet detected: ${masterAddress}`);
184
+
185
+ // Verify on-chain
186
+ console.log(' Verifying builder fee approval...');
187
+ const feeApproved = await verifyBuilderFee(masterAddress);
188
+
189
+ if (feeApproved) {
190
+ console.log(' ✅ Builder fee: approved on-chain');
191
+ } else {
192
+ console.log(' ⚠️ Builder fee not yet confirmed on-chain (may take a moment)');
193
+ }
194
+
195
+ // Save complete config
196
+ const envContent = buildApiWalletEnvContent(privateKey, masterAddress);
197
+ fs.writeFileSync(CONFIG_PATH, envContent, { mode: 0o600 });
198
+ console.log(`\n✅ Config saved to: ${CONFIG_PATH}`);
199
+
200
+ // Final summary
201
+ console.log('\n========================================');
202
+ console.log(' SETUP COMPLETE! ');
203
+ console.log('========================================\n');
204
+
205
+ console.log('API Wallet Setup');
206
+ console.log('-----------------');
207
+ console.log(`API Wallet: ${apiAccount.address}`);
208
+ console.log(`Master Account: ${masterAddress}`);
209
+ console.log(`Network: Hyperliquid (Mainnet)`);
210
+ console.log(`Config: ${CONFIG_PATH}`);
211
+
212
+ console.log('\n📋 Next Steps');
213
+ console.log('--------------');
214
+ console.log('1. Ensure your master wallet is funded on Hyperliquid');
215
+ console.log('2. Start trading:');
216
+ console.log(' openbroker account');
217
+ console.log(' openbroker buy --coin ETH --size 0.01 --dry');
218
+
219
+ console.log('\n⚠️ Security');
220
+ console.log('------------');
221
+ console.log('This API wallet can trade but CANNOT withdraw funds.');
222
+ console.log('You can revoke access at any time from app.hyperliquid.xyz');
223
+ console.log(`Config stored at: ${CONFIG_PATH}`);
224
+
225
+ return {
226
+ success: true,
227
+ walletAddress: apiAccount.address,
228
+ privateKey: privateKey,
229
+ };
230
+ }
231
+
232
+ // ── Main ──
233
+
49
234
  async function main(): Promise<OnboardResult> {
50
235
  console.log('OpenBroker - One-Command Setup');
51
236
  console.log('==============================\n');
@@ -84,32 +269,44 @@ async function main(): Promise<OnboardResult> {
84
269
  };
85
270
  }
86
271
 
87
- // Ask user if they have an existing private key
272
+ // Ask user which setup mode
88
273
  const rl = createReadline();
89
274
 
90
275
  console.log('Step 1/3: Wallet Setup');
91
276
  console.log('----------------------');
92
- console.log('Do you have an existing Hyperliquid private key?\n');
93
- console.log(' 1) Yes, I have a private key ready');
94
- console.log(' 2) No, generate a new wallet for me\n');
277
+ console.log('How would you like to set up your wallet?\n');
278
+ console.log(' 1) Import existing private key (master wallet)');
279
+ console.log(' 2) Generate a new wallet (master wallet)');
280
+ console.log(' 3) Generate API wallet (recommended for agents)');
281
+ console.log(' Safer: can trade but cannot withdraw funds.');
282
+ console.log(' Requires browser approval from your master wallet.\n');
95
283
 
96
284
  let choice = '';
97
- while (choice !== '1' && choice !== '2') {
98
- choice = await prompt(rl, 'Enter choice (1 or 2): ');
99
- if (choice !== '1' && choice !== '2') {
100
- console.log('Please enter 1 or 2');
285
+ while (choice !== '1' && choice !== '2' && choice !== '3') {
286
+ choice = await prompt(rl, 'Enter choice (1, 2, or 3): ');
287
+ if (choice !== '1' && choice !== '2' && choice !== '3') {
288
+ console.log('Please enter 1, 2, or 3');
101
289
  }
102
290
  }
103
291
 
292
+ rl.close();
293
+
294
+ // Option 3: API wallet flow
295
+ if (choice === '3') {
296
+ return setupApiWallet();
297
+ }
298
+
299
+ // Options 1 & 2: Master wallet flow
104
300
  let privateKey: `0x${string}`;
105
301
 
106
302
  if (choice === '1') {
107
303
  // User has existing key
304
+ const rl2 = createReadline();
108
305
  console.log('\nEnter your private key (0x... format):\n');
109
306
 
110
307
  let validKey = false;
111
308
  while (!validKey) {
112
- const inputKey = await prompt(rl, 'Private key: ');
309
+ const inputKey = await prompt(rl2, 'Private key: ');
113
310
 
114
311
  if (isValidPrivateKey(inputKey)) {
115
312
  privateKey = inputKey as `0x${string}`;
@@ -119,6 +316,7 @@ async function main(): Promise<OnboardResult> {
119
316
  console.log('Example: 0x1234...abcd (66 characters total)\n');
120
317
  }
121
318
  }
319
+ rl2.close();
122
320
 
123
321
  console.log('\n✅ Private key accepted');
124
322
  } else {
@@ -128,8 +326,6 @@ async function main(): Promise<OnboardResult> {
128
326
  console.log('✅ New wallet created');
129
327
  }
130
328
 
131
- rl.close();
132
-
133
329
  // Derive account from private key
134
330
  const account = privateKeyToAccount(privateKey);
135
331
  console.log(`\nWallet Address: ${account.address}\n`);