openbroker 1.0.80 → 1.0.85
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/SKILL.md +79 -7
- package/bin/cli.ts +22 -1
- package/bin/openbroker.js +4 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/auto/cli.ts +10 -1
- package/scripts/auto/dashboard-forwarder.ts +77 -0
- package/scripts/auto/loader.ts +41 -5
- package/scripts/auto/runtime.ts +18 -6
- package/scripts/core/client.ts +603 -214
- package/scripts/core/config.ts +20 -1
- package/scripts/core/types.ts +1 -0
- package/scripts/core/utils.ts +4 -1
- package/scripts/info/account.ts +12 -8
- package/scripts/info/all-markets.ts +53 -23
- package/scripts/info/candles.ts +16 -2
- package/scripts/info/fees.ts +16 -5
- package/scripts/info/funding-history.ts +16 -2
- package/scripts/info/funding-scan.ts +10 -0
- package/scripts/info/funding.ts +13 -0
- package/scripts/info/markets.ts +12 -0
- package/scripts/info/order-status.ts +11 -2
- package/scripts/info/orders.ts +5 -0
- package/scripts/info/rate-limit.ts +11 -2
- package/scripts/info/search-markets.ts +37 -9
- package/scripts/info/spot.ts +25 -5
- package/scripts/info/trades.ts +16 -2
- package/scripts/setup/onboard.ts +95 -44
|
@@ -7,6 +7,7 @@ interface Args {
|
|
|
7
7
|
query: string;
|
|
8
8
|
type?: 'perp' | 'spot' | 'hip3' | 'all';
|
|
9
9
|
verbose?: boolean;
|
|
10
|
+
json?: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function parseArgs(): Args {
|
|
@@ -23,6 +24,8 @@ function parseArgs(): Args {
|
|
|
23
24
|
}
|
|
24
25
|
} else if (arg === '--verbose') {
|
|
25
26
|
args.verbose = true;
|
|
27
|
+
} else if (arg === '--json') {
|
|
28
|
+
args.json = true;
|
|
26
29
|
} else if (arg === '--help' || arg === '-h') {
|
|
27
30
|
console.log(`
|
|
28
31
|
Search Markets - Find assets across all Hyperliquid markets
|
|
@@ -32,6 +35,7 @@ Usage: npx tsx scripts/info/search-markets.ts --query <search> [options]
|
|
|
32
35
|
Options:
|
|
33
36
|
--query <search> Search term (required) - matches coin name
|
|
34
37
|
--type <type> Filter by market type: perp, spot, hip3, or all (default: all)
|
|
38
|
+
--json Output as JSON (machine-readable)
|
|
35
39
|
--verbose Show detailed output
|
|
36
40
|
--help Show this help
|
|
37
41
|
|
|
@@ -40,6 +44,7 @@ Examples:
|
|
|
40
44
|
npx tsx scripts/info/search-markets.ts --query BTC # Find all BTC markets
|
|
41
45
|
npx tsx scripts/info/search-markets.ts --query ETH --type perp # ETH perps only
|
|
42
46
|
npx tsx scripts/info/search-markets.ts --query PURR --type spot # PURR spot only
|
|
47
|
+
npx tsx scripts/info/search-markets.ts --query HYPE --json # JSON output
|
|
43
48
|
`);
|
|
44
49
|
process.exit(0);
|
|
45
50
|
} else if (!args.query && !arg.startsWith('-')) {
|
|
@@ -85,18 +90,23 @@ async function main() {
|
|
|
85
90
|
client.verbose = args.verbose ?? false;
|
|
86
91
|
|
|
87
92
|
const query = args.query.toUpperCase();
|
|
88
|
-
|
|
93
|
+
if (!args.json) {
|
|
94
|
+
console.log(`Searching for "${query}" across all markets...\n`);
|
|
95
|
+
}
|
|
89
96
|
|
|
90
|
-
|
|
97
|
+
interface Result {
|
|
91
98
|
type: 'perp' | 'spot' | 'hip3';
|
|
92
99
|
provider: string;
|
|
93
100
|
coin: string;
|
|
101
|
+
assetId: number;
|
|
94
102
|
price: string;
|
|
95
103
|
volume24h: number;
|
|
96
104
|
funding?: string;
|
|
97
105
|
maxLeverage?: number;
|
|
98
106
|
openInterest?: string;
|
|
99
|
-
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const results: Result[] = [];
|
|
100
110
|
|
|
101
111
|
// Search main perps
|
|
102
112
|
if (args.type === 'all' || args.type === 'perp') {
|
|
@@ -110,6 +120,7 @@ async function main() {
|
|
|
110
120
|
type: 'perp',
|
|
111
121
|
provider: 'Hyperliquid',
|
|
112
122
|
coin: asset.name,
|
|
123
|
+
assetId: i,
|
|
113
124
|
price: ctx.markPx,
|
|
114
125
|
volume24h: parseFloat(ctx.dayNtlVlm),
|
|
115
126
|
funding: ctx.funding,
|
|
@@ -122,6 +133,14 @@ async function main() {
|
|
|
122
133
|
|
|
123
134
|
// Search HIP-3 perps
|
|
124
135
|
if (args.type === 'all' || args.type === 'hip3') {
|
|
136
|
+
if (client.isTestnet) {
|
|
137
|
+
// On testnet, load specific dex if query is "dex:COIN" format
|
|
138
|
+
if (args.query.includes(':')) {
|
|
139
|
+
await client.loadSingleHip3Dex(args.query.split(':')[0]);
|
|
140
|
+
} else if (!args.json) {
|
|
141
|
+
console.log(' (Testnet: HIP-3 dexes not auto-loaded. Use "dexName:COIN" to search a specific dex.)\n');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
125
144
|
try {
|
|
126
145
|
const allPerpMetas = await client.getAllPerpMetas();
|
|
127
146
|
// Skip index 0 (main dex), process HIP-3 dexs
|
|
@@ -135,10 +154,13 @@ async function main() {
|
|
|
135
154
|
if (!asset || !ctx) continue;
|
|
136
155
|
|
|
137
156
|
if (asset.name.toUpperCase().includes(query)) {
|
|
157
|
+
let assetId = -1;
|
|
158
|
+
try { assetId = client.getAssetIndex(asset.name); } catch { /* not registered */ }
|
|
138
159
|
results.push({
|
|
139
160
|
type: 'hip3',
|
|
140
161
|
provider: dexData.dexName || `HIP-3 DEX ${dexIdx}`,
|
|
141
162
|
coin: asset.name,
|
|
163
|
+
assetId,
|
|
142
164
|
price: ctx.markPx,
|
|
143
165
|
volume24h: parseFloat(ctx.dayNtlVlm || '0'),
|
|
144
166
|
funding: ctx.funding,
|
|
@@ -191,6 +213,7 @@ async function main() {
|
|
|
191
213
|
type: 'spot',
|
|
192
214
|
provider: 'Spot',
|
|
193
215
|
coin: displayName,
|
|
216
|
+
assetId: 10000 + pair.index,
|
|
194
217
|
price: ctx.markPx,
|
|
195
218
|
volume24h: parseFloat(ctx.dayNtlVlm || '0'),
|
|
196
219
|
});
|
|
@@ -204,20 +227,25 @@ async function main() {
|
|
|
204
227
|
// Sort by volume
|
|
205
228
|
results.sort((a, b) => b.volume24h - a.volume24h);
|
|
206
229
|
|
|
230
|
+
if (args.json) {
|
|
231
|
+
console.log(JSON.stringify(results, null, 2));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
207
235
|
if (results.length === 0) {
|
|
208
236
|
console.log(`No markets found matching "${query}"`);
|
|
209
237
|
return;
|
|
210
238
|
}
|
|
211
239
|
|
|
212
240
|
console.log(`Found ${results.length} market(s) matching "${query}":\n`);
|
|
213
|
-
console.log('Type Provider Coin Price 24h Volume Funding (Ann.) OI');
|
|
214
|
-
console.log('-'.repeat(
|
|
241
|
+
console.log('Type Provider Coin AssetID Price 24h Volume Funding (Ann.) OI');
|
|
242
|
+
console.log('-'.repeat(112));
|
|
215
243
|
|
|
216
244
|
for (const m of results) {
|
|
217
245
|
const typeStr = m.type === 'hip3' ? 'HIP-3' : m.type.charAt(0).toUpperCase() + m.type.slice(1);
|
|
218
246
|
const oi = m.openInterest ? formatVolume(parseFloat(m.openInterest)) : '-';
|
|
219
247
|
console.log(
|
|
220
|
-
`${typeStr.padEnd(8)} ${m.provider.padEnd(16)} ${m.coin.padEnd(14)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${(m.funding ? formatFunding(m.funding) : '-').padStart(14)} ${oi.padStart(10)}`
|
|
248
|
+
`${typeStr.padEnd(8)} ${m.provider.padEnd(16)} ${m.coin.padEnd(14)} ${String(m.assetId).padStart(7)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${(m.funding ? formatFunding(m.funding) : '-').padStart(14)} ${oi.padStart(10)}`
|
|
221
249
|
);
|
|
222
250
|
}
|
|
223
251
|
|
|
@@ -237,11 +265,11 @@ async function main() {
|
|
|
237
265
|
const perpsWithFunding = markets.filter((m) => m.funding && m.type !== 'spot');
|
|
238
266
|
if (perpsWithFunding.length > 1) {
|
|
239
267
|
console.log(`\n=== ${coin} Funding Comparison ===\n`);
|
|
240
|
-
console.log('Provider Coin Funding (Ann.) Price');
|
|
241
|
-
console.log('-'.repeat(
|
|
268
|
+
console.log('Provider Coin AssetID Funding (Ann.) Price');
|
|
269
|
+
console.log('-'.repeat(78));
|
|
242
270
|
for (const m of perpsWithFunding.sort((a, b) => parseFloat(b.funding!) - parseFloat(a.funding!))) {
|
|
243
271
|
console.log(
|
|
244
|
-
`${m.provider.padEnd(16)} ${m.coin.padEnd(14)} ${formatFunding(m.funding!).padStart(14)} ${formatPrice(m.price)}`
|
|
272
|
+
`${m.provider.padEnd(16)} ${m.coin.padEnd(14)} ${String(m.assetId).padStart(7)} ${formatFunding(m.funding!).padStart(14)} ${formatPrice(m.price)}`
|
|
245
273
|
);
|
|
246
274
|
}
|
|
247
275
|
}
|
package/scripts/info/spot.ts
CHANGED
|
@@ -9,6 +9,7 @@ interface Args {
|
|
|
9
9
|
top?: number;
|
|
10
10
|
verbose?: boolean;
|
|
11
11
|
address?: string;
|
|
12
|
+
json?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function parseArgs(): Args {
|
|
@@ -26,6 +27,8 @@ function parseArgs(): Args {
|
|
|
26
27
|
args.address = process.argv[++i].toLowerCase();
|
|
27
28
|
} else if (arg === '--verbose') {
|
|
28
29
|
args.verbose = true;
|
|
30
|
+
} else if (arg === '--json') {
|
|
31
|
+
args.json = true;
|
|
29
32
|
} else if (arg === '--help' || arg === '-h') {
|
|
30
33
|
console.log(`
|
|
31
34
|
Spot Markets - View Hyperliquid spot markets and balances
|
|
@@ -37,6 +40,7 @@ Options:
|
|
|
37
40
|
--balances Show your spot token balances
|
|
38
41
|
--address <0x...> Look up another account's spot balances (with --balances)
|
|
39
42
|
--top <n> Show only top N markets by volume
|
|
43
|
+
--json Output as JSON (machine-readable)
|
|
40
44
|
--verbose Show detailed output
|
|
41
45
|
--help Show this help
|
|
42
46
|
|
|
@@ -85,10 +89,15 @@ async function main() {
|
|
|
85
89
|
// Show balances
|
|
86
90
|
if (args.balances) {
|
|
87
91
|
const lookupAddress = args.address ?? client.address;
|
|
88
|
-
console.log(`Fetching spot balances for ${lookupAddress}...\n`);
|
|
92
|
+
if (!args.json) console.log(`Fetching spot balances for ${lookupAddress}...\n`);
|
|
89
93
|
|
|
90
94
|
const balances = await client.getSpotBalances(args.address);
|
|
91
95
|
|
|
96
|
+
if (args.json) {
|
|
97
|
+
console.log(JSON.stringify(balances.balances ?? [], null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
if (!balances.balances || balances.balances.length === 0) {
|
|
93
102
|
console.log('No spot token balances found.');
|
|
94
103
|
return;
|
|
@@ -112,17 +121,20 @@ async function main() {
|
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
// Show markets
|
|
115
|
-
console.log('Fetching spot market data...\n');
|
|
124
|
+
if (!args.json) console.log('Fetching spot market data...\n');
|
|
116
125
|
|
|
117
126
|
const spotData = await client.getSpotMetaAndAssetCtxs();
|
|
118
127
|
|
|
119
128
|
interface SpotMarket {
|
|
120
129
|
name: string;
|
|
121
130
|
index: number;
|
|
131
|
+
assetId: number;
|
|
122
132
|
price: string;
|
|
123
133
|
volume24h: number;
|
|
124
134
|
change24h: string;
|
|
125
135
|
tokens: [number, number];
|
|
136
|
+
base?: string;
|
|
137
|
+
quote?: string;
|
|
126
138
|
}
|
|
127
139
|
|
|
128
140
|
const markets: SpotMarket[] = [];
|
|
@@ -155,10 +167,13 @@ async function main() {
|
|
|
155
167
|
markets.push({
|
|
156
168
|
name: pair.name,
|
|
157
169
|
index: pair.index,
|
|
170
|
+
assetId: 10000 + pair.index,
|
|
158
171
|
price: ctx.markPx,
|
|
159
172
|
volume24h: parseFloat(ctx.dayNtlVlm || '0'),
|
|
160
173
|
change24h: formatChange(ctx.markPx, ctx.prevDayPx),
|
|
161
174
|
tokens: pair.tokens,
|
|
175
|
+
base: tokenNameMap.get(pair.tokens[0]),
|
|
176
|
+
quote: tokenNameMap.get(pair.tokens[1]),
|
|
162
177
|
});
|
|
163
178
|
}
|
|
164
179
|
|
|
@@ -168,6 +183,11 @@ async function main() {
|
|
|
168
183
|
// Apply top filter
|
|
169
184
|
const displayMarkets = args.top ? markets.slice(0, args.top) : markets;
|
|
170
185
|
|
|
186
|
+
if (args.json) {
|
|
187
|
+
console.log(JSON.stringify(displayMarkets, null, 2));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
171
191
|
if (displayMarkets.length === 0) {
|
|
172
192
|
console.log(args.coin ? `No spot markets found for "${args.coin}"` : 'No spot markets found');
|
|
173
193
|
return;
|
|
@@ -180,8 +200,8 @@ async function main() {
|
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
console.log(`=== Spot Markets (${displayMarkets.length} total) ===\n`);
|
|
183
|
-
console.log('Pair Price 24h Volume 24h Change Base/Quote');
|
|
184
|
-
console.log('-'.repeat(
|
|
203
|
+
console.log('Pair AssetID Price 24h Volume 24h Change Base/Quote');
|
|
204
|
+
console.log('-'.repeat(92));
|
|
185
205
|
|
|
186
206
|
for (const m of displayMarkets) {
|
|
187
207
|
const baseToken = tokenMap.get(m.tokens[0]);
|
|
@@ -189,7 +209,7 @@ async function main() {
|
|
|
189
209
|
const pairStr = `${baseToken?.name || '?'}/${quoteToken?.name || '?'}`;
|
|
190
210
|
|
|
191
211
|
console.log(
|
|
192
|
-
`${m.name.padEnd(14)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${m.change24h.padStart(11)} ${pairStr}`
|
|
212
|
+
`${m.name.padEnd(14)} ${String(m.assetId).padStart(7)} ${formatPrice(m.price).padStart(16)} ${formatVolume(m.volume24h).padStart(13)} ${m.change24h.padStart(11)} ${pairStr}`
|
|
193
213
|
);
|
|
194
214
|
}
|
|
195
215
|
|
package/scripts/info/trades.ts
CHANGED
|
@@ -11,6 +11,7 @@ Usage: openbroker trades --coin <symbol> [options]
|
|
|
11
11
|
Options:
|
|
12
12
|
--coin <symbol> Asset symbol (required, e.g. ETH, BTC)
|
|
13
13
|
--top <n> Show last N trades (default: 30)
|
|
14
|
+
--json Output as JSON (machine-readable)
|
|
14
15
|
--help, -h Show this help
|
|
15
16
|
|
|
16
17
|
Examples:
|
|
@@ -35,14 +36,20 @@ async function main() {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const top = parseInt(args.top as string) || 30;
|
|
39
|
+
const jsonOutput = args.json as boolean;
|
|
38
40
|
const client = getClient();
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
if (!jsonOutput) {
|
|
43
|
+
console.log(`Open Broker - ${normalizeCoin(coin)} Recent Trades`);
|
|
44
|
+
console.log('='.repeat(40) + '\n');
|
|
45
|
+
}
|
|
42
46
|
|
|
43
47
|
try {
|
|
44
48
|
// Load metadata (needed for HIP-3 coin resolution)
|
|
45
49
|
await client.getMetaAndAssetCtxs();
|
|
50
|
+
if (client.isTestnet && coin.includes(':')) {
|
|
51
|
+
await client.loadSingleHip3Dex(coin.split(':')[0]);
|
|
52
|
+
}
|
|
46
53
|
|
|
47
54
|
let trades = await client.getRecentTrades(normalizeCoin(coin));
|
|
48
55
|
|
|
@@ -50,6 +57,13 @@ async function main() {
|
|
|
50
57
|
trades.sort((a, b) => b.time - a.time);
|
|
51
58
|
trades = trades.slice(0, top);
|
|
52
59
|
|
|
60
|
+
if (jsonOutput) {
|
|
61
|
+
let assetId = -1;
|
|
62
|
+
try { assetId = client.getAssetIndex(normalizeCoin(coin)); } catch { /* noop */ }
|
|
63
|
+
console.log(JSON.stringify({ coin: normalizeCoin(coin), assetId, trades }, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
if (trades.length === 0) {
|
|
54
68
|
console.log('No recent trades found');
|
|
55
69
|
return;
|
package/scripts/setup/onboard.ts
CHANGED
|
@@ -12,8 +12,19 @@ const OPEN_BROKER_BUILDER_ADDRESS = '0xbb67021fA3e62ab4DA985bb5a55c5c1884381068'
|
|
|
12
12
|
const OPENBROKER_URL = process.env.OPENBROKER_URL || 'https://openbroker.dev';
|
|
13
13
|
|
|
14
14
|
// Global config directory: ~/.openbroker/
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const GLOBAL_CONFIG_DIR = path.join(homedir(), '.openbroker');
|
|
16
|
+
const GLOBAL_CONFIG_PATH = path.join(GLOBAL_CONFIG_DIR, '.env');
|
|
17
|
+
|
|
18
|
+
// Parse CLI flags
|
|
19
|
+
const cliArgs = process.argv.slice(2);
|
|
20
|
+
const useTestnet = cliArgs.includes('--testnet') || process.env.HYPERLIQUID_NETWORK === 'testnet';
|
|
21
|
+
const accountAddressIdx = cliArgs.indexOf('--account-address');
|
|
22
|
+
const cliAccountAddress = accountAddressIdx !== -1 ? cliArgs[accountAddressIdx + 1] : undefined;
|
|
23
|
+
const configPathIdx = cliArgs.indexOf('-c') !== -1 ? cliArgs.indexOf('-c') : cliArgs.indexOf('--config');
|
|
24
|
+
const cliConfigPath = configPathIdx !== -1 ? cliArgs[configPathIdx + 1] : process.env.OPENBROKER_CONFIG;
|
|
25
|
+
|
|
26
|
+
const CONFIG_PATH = cliConfigPath ? path.resolve(cliConfigPath) : GLOBAL_CONFIG_PATH;
|
|
27
|
+
const CONFIG_DIR = path.dirname(CONFIG_PATH);
|
|
17
28
|
|
|
18
29
|
interface OnboardResult {
|
|
19
30
|
success: boolean;
|
|
@@ -54,7 +65,7 @@ const POLL_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
|
54
65
|
|
|
55
66
|
async function pollForApproval(agentAddress: string): Promise<string | null> {
|
|
56
67
|
const startTime = Date.now();
|
|
57
|
-
const statusUrl = `${OPENBROKER_URL}/api/approve-status?agent=${agentAddress}`;
|
|
68
|
+
const statusUrl = `${OPENBROKER_URL}/api/approve-status?agent=${agentAddress}${useTestnet ? '&network=testnet' : ''}`;
|
|
58
69
|
|
|
59
70
|
let dotCount = 0;
|
|
60
71
|
|
|
@@ -86,8 +97,9 @@ async function pollForApproval(agentAddress: string): Promise<string | null> {
|
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
async function verifyBuilderFee(masterAddress: string): Promise<boolean> {
|
|
100
|
+
const apiUrl = useTestnet ? 'https://api.hyperliquid-testnet.xyz/info' : 'https://api.hyperliquid.xyz/info';
|
|
89
101
|
try {
|
|
90
|
-
const response = await fetch(
|
|
102
|
+
const response = await fetch(apiUrl, {
|
|
91
103
|
method: 'POST',
|
|
92
104
|
headers: { 'Content-Type': 'application/json' },
|
|
93
105
|
body: JSON.stringify({
|
|
@@ -104,8 +116,9 @@ async function verifyBuilderFee(masterAddress: string): Promise<boolean> {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
function buildApiWalletEnvContent(privateKey: string, masterAddress: string): string {
|
|
119
|
+
const network = useTestnet ? 'testnet' : 'mainnet';
|
|
107
120
|
return `# OpenBroker Configuration (API Wallet)
|
|
108
|
-
# Location:
|
|
121
|
+
# Location: ${CONFIG_PATH}
|
|
109
122
|
# WARNING: Keep this file secret! Never share it!
|
|
110
123
|
|
|
111
124
|
# API wallet private key (can trade, cannot withdraw)
|
|
@@ -115,7 +128,7 @@ HYPERLIQUID_PRIVATE_KEY=${privateKey}
|
|
|
115
128
|
HYPERLIQUID_ACCOUNT_ADDRESS=${masterAddress}
|
|
116
129
|
|
|
117
130
|
# Network: mainnet or testnet
|
|
118
|
-
HYPERLIQUID_NETWORK
|
|
131
|
+
HYPERLIQUID_NETWORK=${network}
|
|
119
132
|
`;
|
|
120
133
|
}
|
|
121
134
|
|
|
@@ -132,7 +145,7 @@ async function setupApiWallet(): Promise<OnboardResult> {
|
|
|
132
145
|
ensureConfigDir();
|
|
133
146
|
|
|
134
147
|
// Build the approval URL
|
|
135
|
-
const approveUrl = `${OPENBROKER_URL}/approve?agent=${apiAccount.address}`;
|
|
148
|
+
const approveUrl = `${OPENBROKER_URL}/approve?agent=${apiAccount.address}${useTestnet ? '&network=testnet' : ''}`;
|
|
136
149
|
|
|
137
150
|
console.log(`✅ Config directory ready: ${CONFIG_DIR}\n`);
|
|
138
151
|
|
|
@@ -141,9 +154,15 @@ async function setupApiWallet(): Promise<OnboardResult> {
|
|
|
141
154
|
console.log('Your API wallet needs to be authorized by a master wallet.');
|
|
142
155
|
console.log('Open this URL in your browser and connect your master wallet:\n');
|
|
143
156
|
console.log(` ${approveUrl}\n`);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
if (useTestnet) {
|
|
158
|
+
console.log('The master wallet will sign one transaction:');
|
|
159
|
+
console.log(' 1. ApproveAgent — authorizes this API wallet to trade');
|
|
160
|
+
console.log(' (Builder fee approval is skipped on testnet)\n');
|
|
161
|
+
} else {
|
|
162
|
+
console.log('The master wallet will sign two transactions:');
|
|
163
|
+
console.log(' 1. ApproveAgent — authorizes this API wallet to trade');
|
|
164
|
+
console.log(' 2. ApproveBuilderFee — approves the 1 bps builder fee\n');
|
|
165
|
+
}
|
|
147
166
|
|
|
148
167
|
// Poll for approval
|
|
149
168
|
const masterAddress = await pollForApproval(apiAccount.address);
|
|
@@ -175,14 +194,18 @@ HYPERLIQUID_NETWORK=mainnet
|
|
|
175
194
|
|
|
176
195
|
console.log(`\n✅ Master wallet detected: ${masterAddress}`);
|
|
177
196
|
|
|
178
|
-
// Verify on-chain
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
// Verify builder fee on-chain (skip on testnet)
|
|
198
|
+
if (!useTestnet) {
|
|
199
|
+
console.log(' Verifying builder fee approval...');
|
|
200
|
+
const feeApproved = await verifyBuilderFee(masterAddress);
|
|
181
201
|
|
|
182
|
-
|
|
183
|
-
|
|
202
|
+
if (feeApproved) {
|
|
203
|
+
console.log(' ✅ Builder fee: approved on-chain');
|
|
204
|
+
} else {
|
|
205
|
+
console.log(' ⚠️ Builder fee not yet confirmed on-chain (may take a moment)');
|
|
206
|
+
}
|
|
184
207
|
} else {
|
|
185
|
-
console.log('
|
|
208
|
+
console.log(' (Builder fee verification skipped on testnet)');
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
// Save complete config
|
|
@@ -225,8 +248,30 @@ HYPERLIQUID_NETWORK=mainnet
|
|
|
225
248
|
// ── Main ──
|
|
226
249
|
|
|
227
250
|
async function main(): Promise<OnboardResult> {
|
|
251
|
+
if (cliArgs.includes('--help') || cliArgs.includes('-h')) {
|
|
252
|
+
console.log(`
|
|
253
|
+
OpenBroker Setup
|
|
254
|
+
|
|
255
|
+
Usage: openbroker setup [options]
|
|
256
|
+
|
|
257
|
+
Options:
|
|
258
|
+
-c, --config <path> Save config to a custom path (default: ~/.openbroker/.env)
|
|
259
|
+
--testnet Configure for testnet
|
|
260
|
+
--account-address <addr> Set HYPERLIQUID_ACCOUNT_ADDRESS (for API wallet / vault trading)
|
|
261
|
+
--help Show this help
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
openbroker setup # Interactive → ~/.openbroker/.env
|
|
265
|
+
openbroker setup -c .env --testnet # Write to ./.env for testnet
|
|
266
|
+
openbroker setup -c ./testnet.env --testnet --account-address 0x... # API wallet config
|
|
267
|
+
`);
|
|
268
|
+
process.exit(0);
|
|
269
|
+
}
|
|
270
|
+
|
|
228
271
|
console.log('OpenBroker - One-Command Setup');
|
|
229
272
|
console.log('==============================\n');
|
|
273
|
+
if (cliConfigPath) console.log(`Config will be saved to: ${CONFIG_PATH}\n`);
|
|
274
|
+
if (useTestnet) console.log('Network: testnet\n');
|
|
230
275
|
console.log('This will: 1) Create wallet 2) Save config 3) Approve builder fee\n');
|
|
231
276
|
|
|
232
277
|
// Check if config already exists
|
|
@@ -253,7 +298,7 @@ async function main(): Promise<OnboardResult> {
|
|
|
253
298
|
console.log(` API Wallet: ${account.address}`);
|
|
254
299
|
console.log(` Master account address is missing — re-polling for approval...\n`);
|
|
255
300
|
|
|
256
|
-
const approveUrl = `${OPENBROKER_URL}/approve?agent=${account.address}`;
|
|
301
|
+
const approveUrl = `${OPENBROKER_URL}/approve?agent=${account.address}${useTestnet ? '&network=testnet' : ''}`;
|
|
257
302
|
console.log(` If not yet approved, visit: ${approveUrl}\n`);
|
|
258
303
|
|
|
259
304
|
const masterAddress = await pollForApproval(account.address);
|
|
@@ -380,51 +425,57 @@ async function main(): Promise<OnboardResult> {
|
|
|
380
425
|
console.log('Step 2/3: Creating config...');
|
|
381
426
|
ensureConfigDir();
|
|
382
427
|
|
|
428
|
+
const network = useTestnet ? 'testnet' : 'mainnet';
|
|
429
|
+
const accountLine = cliAccountAddress ? `\n# Master/vault account address\nHYPERLIQUID_ACCOUNT_ADDRESS=${cliAccountAddress}\n` : '';
|
|
383
430
|
const envContent = `# OpenBroker Configuration
|
|
384
|
-
# Location:
|
|
431
|
+
# Location: ${CONFIG_PATH}
|
|
385
432
|
# WARNING: Keep this file secret! Never share it!
|
|
386
433
|
|
|
387
434
|
# Your wallet private key
|
|
388
435
|
HYPERLIQUID_PRIVATE_KEY=${privateKey}
|
|
389
|
-
|
|
436
|
+
${accountLine}
|
|
390
437
|
# Network: mainnet or testnet
|
|
391
|
-
HYPERLIQUID_NETWORK
|
|
438
|
+
HYPERLIQUID_NETWORK=${network}
|
|
392
439
|
`;
|
|
393
440
|
|
|
394
441
|
fs.writeFileSync(CONFIG_PATH, envContent, { mode: 0o600 });
|
|
395
442
|
console.log(`✅ Config saved to: ${CONFIG_PATH}\n`);
|
|
396
443
|
|
|
397
|
-
// Approve builder fee (automatic - no user action needed)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const { getClient } = await import('../core/client.js');
|
|
404
|
-
const client = getClient();
|
|
444
|
+
// Approve builder fee (automatic - no user action needed; skip on testnet)
|
|
445
|
+
if (useTestnet) {
|
|
446
|
+
console.log('Step 3/3: Skipping builder fee approval (testnet)\n');
|
|
447
|
+
} else {
|
|
448
|
+
console.log('Step 3/3: Approving builder fee...');
|
|
449
|
+
console.log('(This is automatic, and required for trading)\n');
|
|
405
450
|
|
|
406
|
-
|
|
407
|
-
|
|
451
|
+
try {
|
|
452
|
+
// Import and run approve-builder inline
|
|
453
|
+
const { getClient } = await import('../core/client.js');
|
|
454
|
+
const client = getClient();
|
|
408
455
|
|
|
409
|
-
|
|
410
|
-
|
|
456
|
+
console.log(` Account: ${client.address}`);
|
|
457
|
+
console.log(` Builder: ${OPEN_BROKER_BUILDER_ADDRESS}`);
|
|
411
458
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
} else {
|
|
415
|
-
console.log('\n Sending approval transaction...');
|
|
416
|
-
const result = await client.approveBuilderFee('0.1%', OPEN_BROKER_BUILDER_ADDRESS);
|
|
459
|
+
// Check if already approved
|
|
460
|
+
const currentApproval = await client.getMaxBuilderFee(client.address, OPEN_BROKER_BUILDER_ADDRESS);
|
|
417
461
|
|
|
418
|
-
if (
|
|
419
|
-
console.log(
|
|
462
|
+
if (currentApproval) {
|
|
463
|
+
console.log(`\n✅ Builder fee already approved (${currentApproval})`);
|
|
420
464
|
} else {
|
|
421
|
-
console.log(
|
|
422
|
-
|
|
465
|
+
console.log('\n Sending approval transaction...');
|
|
466
|
+
const result = await client.approveBuilderFee('0.1%', OPEN_BROKER_BUILDER_ADDRESS);
|
|
467
|
+
|
|
468
|
+
if (result.status === 'ok') {
|
|
469
|
+
console.log('✅ Builder fee approved successfully!');
|
|
470
|
+
} else {
|
|
471
|
+
console.log(`⚠️ Approval may have failed: ${result.response}`);
|
|
472
|
+
console.log(' You can retry later: openbroker approve-builder');
|
|
473
|
+
}
|
|
423
474
|
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
console.log(`⚠️ Could not approve builder fee: ${error}`);
|
|
477
|
+
console.log(' You can retry later: openbroker approve-builder');
|
|
424
478
|
}
|
|
425
|
-
} catch (error) {
|
|
426
|
-
console.log(`⚠️ Could not approve builder fee: ${error}`);
|
|
427
|
-
console.log(' You can retry later: openbroker approve-builder');
|
|
428
479
|
}
|
|
429
480
|
|
|
430
481
|
// Final summary
|