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.
Files changed (169) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/auto/audit.d.ts +57 -0
  3. package/dist/auto/audit.d.ts.map +1 -0
  4. package/dist/auto/audit.js +407 -0
  5. package/dist/auto/cli.d.ts +2 -0
  6. package/dist/auto/cli.d.ts.map +1 -0
  7. package/dist/auto/cli.js +423 -0
  8. package/dist/auto/events.d.ts +11 -0
  9. package/dist/auto/events.d.ts.map +1 -0
  10. package/dist/auto/events.js +36 -0
  11. package/dist/auto/examples/dca.d.ts +4 -0
  12. package/dist/auto/examples/dca.d.ts.map +1 -0
  13. package/dist/auto/examples/dca.js +60 -0
  14. package/dist/auto/examples/funding-arb.d.ts +4 -0
  15. package/dist/auto/examples/funding-arb.d.ts.map +1 -0
  16. package/dist/auto/examples/funding-arb.js +81 -0
  17. package/dist/auto/examples/grid.d.ts +4 -0
  18. package/dist/auto/examples/grid.d.ts.map +1 -0
  19. package/dist/auto/examples/grid.js +114 -0
  20. package/dist/auto/examples/mm-maker.d.ts +4 -0
  21. package/dist/auto/examples/mm-maker.d.ts.map +1 -0
  22. package/dist/auto/examples/mm-maker.js +131 -0
  23. package/dist/auto/examples/mm-spread.d.ts +4 -0
  24. package/dist/auto/examples/mm-spread.d.ts.map +1 -0
  25. package/dist/auto/examples/mm-spread.js +119 -0
  26. package/dist/auto/examples/price-alert.d.ts +4 -0
  27. package/dist/auto/examples/price-alert.d.ts.map +1 -0
  28. package/dist/auto/examples/price-alert.js +85 -0
  29. package/dist/auto/keep-awake.d.ts +11 -0
  30. package/dist/auto/keep-awake.d.ts.map +1 -0
  31. package/dist/auto/keep-awake.js +70 -0
  32. package/dist/auto/loader.d.ts +22 -0
  33. package/dist/auto/loader.d.ts.map +1 -0
  34. package/dist/auto/loader.js +127 -0
  35. package/dist/auto/prune.d.ts +40 -0
  36. package/dist/auto/prune.d.ts.map +1 -0
  37. package/dist/auto/prune.js +204 -0
  38. package/dist/auto/registry.d.ts +24 -0
  39. package/dist/auto/registry.d.ts.map +1 -0
  40. package/dist/auto/registry.js +93 -0
  41. package/dist/auto/report.d.ts +3 -0
  42. package/dist/auto/report.d.ts.map +1 -0
  43. package/dist/auto/report.js +385 -0
  44. package/dist/auto/runtime.d.ts +33 -0
  45. package/dist/auto/runtime.d.ts.map +1 -0
  46. package/dist/auto/runtime.js +844 -0
  47. package/dist/auto/types.d.ts +236 -0
  48. package/dist/auto/types.d.ts.map +1 -0
  49. package/dist/auto/types.js +3 -0
  50. package/dist/core/client.d.ts +691 -0
  51. package/dist/core/client.d.ts.map +1 -0
  52. package/dist/core/client.js +2061 -0
  53. package/dist/core/config.d.ts +22 -0
  54. package/dist/core/config.d.ts.map +1 -0
  55. package/dist/core/config.js +143 -0
  56. package/dist/core/types.d.ts +228 -0
  57. package/dist/core/types.d.ts.map +1 -0
  58. package/dist/core/types.js +2 -0
  59. package/dist/core/utils.d.ts +61 -0
  60. package/dist/core/utils.d.ts.map +1 -0
  61. package/dist/core/utils.js +142 -0
  62. package/dist/core/ws.d.ts +121 -0
  63. package/dist/core/ws.d.ts.map +1 -0
  64. package/dist/core/ws.js +222 -0
  65. package/dist/info/account.d.ts +3 -0
  66. package/dist/info/account.d.ts.map +1 -0
  67. package/dist/info/account.js +198 -0
  68. package/dist/info/all-markets.d.ts +3 -0
  69. package/dist/info/all-markets.d.ts.map +1 -0
  70. package/dist/info/all-markets.js +272 -0
  71. package/dist/info/candles.d.ts +3 -0
  72. package/dist/info/candles.d.ts.map +1 -0
  73. package/dist/info/candles.js +120 -0
  74. package/dist/info/fees.d.ts +3 -0
  75. package/dist/info/fees.d.ts.map +1 -0
  76. package/dist/info/fees.js +87 -0
  77. package/dist/info/fills.d.ts +3 -0
  78. package/dist/info/fills.d.ts.map +1 -0
  79. package/dist/info/fills.js +105 -0
  80. package/dist/info/funding-history.d.ts +3 -0
  81. package/dist/info/funding-history.d.ts.map +1 -0
  82. package/dist/info/funding-history.js +98 -0
  83. package/dist/info/funding-scan.d.ts +3 -0
  84. package/dist/info/funding-scan.d.ts.map +1 -0
  85. package/dist/info/funding-scan.js +178 -0
  86. package/dist/info/funding.d.ts +3 -0
  87. package/dist/info/funding.d.ts.map +1 -0
  88. package/dist/info/funding.js +158 -0
  89. package/dist/info/markets.d.ts +3 -0
  90. package/dist/info/markets.d.ts.map +1 -0
  91. package/dist/info/markets.js +178 -0
  92. package/dist/info/order-status.d.ts +3 -0
  93. package/dist/info/order-status.d.ts.map +1 -0
  94. package/dist/info/order-status.js +85 -0
  95. package/dist/info/orders.d.ts +3 -0
  96. package/dist/info/orders.d.ts.map +1 -0
  97. package/dist/info/orders.js +162 -0
  98. package/dist/info/outcomes.d.ts +3 -0
  99. package/dist/info/outcomes.d.ts.map +1 -0
  100. package/dist/info/outcomes.js +175 -0
  101. package/dist/info/positions.d.ts +3 -0
  102. package/dist/info/positions.d.ts.map +1 -0
  103. package/dist/info/positions.js +127 -0
  104. package/dist/info/rate-limit.d.ts +3 -0
  105. package/dist/info/rate-limit.d.ts.map +1 -0
  106. package/dist/info/rate-limit.js +58 -0
  107. package/dist/info/search-markets.d.ts +3 -0
  108. package/dist/info/search-markets.d.ts.map +1 -0
  109. package/dist/info/search-markets.js +296 -0
  110. package/dist/info/spot.d.ts +3 -0
  111. package/dist/info/spot.d.ts.map +1 -0
  112. package/dist/info/spot.js +192 -0
  113. package/dist/info/trades.d.ts +3 -0
  114. package/dist/info/trades.d.ts.map +1 -0
  115. package/dist/info/trades.js +97 -0
  116. package/dist/lib.d.ts +14 -0
  117. package/dist/lib.d.ts.map +1 -0
  118. package/dist/lib.js +17 -0
  119. package/dist/operations/bracket.d.ts +28 -0
  120. package/dist/operations/bracket.d.ts.map +1 -0
  121. package/dist/operations/bracket.js +266 -0
  122. package/dist/operations/cancel.d.ts +3 -0
  123. package/dist/operations/cancel.d.ts.map +1 -0
  124. package/dist/operations/cancel.js +107 -0
  125. package/dist/operations/chase.d.ts +25 -0
  126. package/dist/operations/chase.d.ts.map +1 -0
  127. package/dist/operations/chase.js +215 -0
  128. package/dist/operations/limit-order.d.ts +3 -0
  129. package/dist/operations/limit-order.d.ts.map +1 -0
  130. package/dist/operations/limit-order.js +144 -0
  131. package/dist/operations/market-order.d.ts +3 -0
  132. package/dist/operations/market-order.d.ts.map +1 -0
  133. package/dist/operations/market-order.js +153 -0
  134. package/dist/operations/outcome-order.d.ts +3 -0
  135. package/dist/operations/outcome-order.d.ts.map +1 -0
  136. package/dist/operations/outcome-order.js +171 -0
  137. package/dist/operations/scale.d.ts +3 -0
  138. package/dist/operations/scale.d.ts.map +1 -0
  139. package/dist/operations/scale.js +212 -0
  140. package/dist/operations/set-tpsl.d.ts +3 -0
  141. package/dist/operations/set-tpsl.d.ts.map +1 -0
  142. package/dist/operations/set-tpsl.js +277 -0
  143. package/dist/operations/spot-order.d.ts +3 -0
  144. package/dist/operations/spot-order.d.ts.map +1 -0
  145. package/dist/operations/spot-order.js +173 -0
  146. package/dist/operations/trigger-order.d.ts +3 -0
  147. package/dist/operations/trigger-order.d.ts.map +1 -0
  148. package/dist/operations/trigger-order.js +177 -0
  149. package/dist/operations/twap-cancel.d.ts +3 -0
  150. package/dist/operations/twap-cancel.d.ts.map +1 -0
  151. package/dist/operations/twap-cancel.js +57 -0
  152. package/dist/operations/twap-status.d.ts +3 -0
  153. package/dist/operations/twap-status.d.ts.map +1 -0
  154. package/dist/operations/twap-status.js +81 -0
  155. package/dist/operations/twap.d.ts +3 -0
  156. package/dist/operations/twap.d.ts.map +1 -0
  157. package/dist/operations/twap.js +124 -0
  158. package/dist/setup/approve-builder.d.ts +3 -0
  159. package/dist/setup/approve-builder.d.ts.map +1 -0
  160. package/dist/setup/approve-builder.js +155 -0
  161. package/dist/setup/env.d.ts +4 -0
  162. package/dist/setup/env.d.ts.map +1 -0
  163. package/dist/setup/env.js +8 -0
  164. package/dist/setup/onboard.d.ts +10 -0
  165. package/dist/setup/onboard.d.ts.map +1 -0
  166. package/dist/setup/onboard.js +462 -0
  167. package/package.json +10 -4
  168. package/scripts/core/client.ts +19 -1
  169. 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.2",
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": "./scripts/lib.ts",
9
+ "main": "./dist/lib.js",
10
+ "types": "./dist/lib.d.ts",
10
11
  "exports": {
11
- ".": "./scripts/lib.ts",
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
- "prepublishOnly": "npm run test:cli",
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": {
@@ -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
 
@@ -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;