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 +1 -1
- package/scripts/setup/onboard.ts +207 -11
package/package.json
CHANGED
package/scripts/setup/onboard.ts
CHANGED
|
@@ -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
|
|
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('
|
|
93
|
-
console.log(' 1)
|
|
94
|
-
console.log(' 2)
|
|
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
|
|
99
|
-
if (choice !== '1' && choice !== '2') {
|
|
100
|
-
console.log('Please enter 1 or
|
|
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(
|
|
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`);
|