openbroker 1.0.33
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 +94 -0
- package/README.md +160 -0
- package/SKILL.md +296 -0
- package/bin/cli.ts +170 -0
- package/bin/openbroker.js +24 -0
- package/config/example.env +48 -0
- package/package.json +79 -0
- package/scripts/core/client.ts +844 -0
- package/scripts/core/config.ts +92 -0
- package/scripts/core/types.ts +192 -0
- package/scripts/core/utils.ts +156 -0
- package/scripts/info/account.ts +117 -0
- package/scripts/info/all-markets.ts +223 -0
- package/scripts/info/funding.ts +133 -0
- package/scripts/info/markets.ts +151 -0
- package/scripts/info/positions.ts +88 -0
- package/scripts/info/search-markets.ts +230 -0
- package/scripts/info/spot.ts +192 -0
- package/scripts/operations/bracket.ts +285 -0
- package/scripts/operations/cancel.ts +124 -0
- package/scripts/operations/chase.ts +236 -0
- package/scripts/operations/limit-order.ts +160 -0
- package/scripts/operations/market-order.ts +167 -0
- package/scripts/operations/scale.ts +263 -0
- package/scripts/operations/set-tpsl.ts +302 -0
- package/scripts/operations/trigger-order.ts +201 -0
- package/scripts/operations/twap.ts +222 -0
- package/scripts/setup/approve-builder.ts +178 -0
- package/scripts/setup/onboard.ts +242 -0
- package/scripts/strategies/dca.ts +292 -0
- package/scripts/strategies/funding-arb.ts +319 -0
- package/scripts/strategies/grid.ts +397 -0
- package/scripts/strategies/mm-maker.ts +411 -0
- package/scripts/strategies/mm-spread.ts +402 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Configuration loader for Open Broker
|
|
2
|
+
|
|
3
|
+
import { config as loadDotenv } from 'dotenv';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
7
|
+
import type { OpenBrokerConfig } from './types.js';
|
|
8
|
+
|
|
9
|
+
// Get project root relative to this file (scripts/core/config.ts -> project root)
|
|
10
|
+
export const PROJECT_ROOT = resolve(import.meta.dirname, '../..');
|
|
11
|
+
export const ENV_PATH = resolve(PROJECT_ROOT, '.env');
|
|
12
|
+
|
|
13
|
+
// Load .env from project root (silently skip if doesn't exist)
|
|
14
|
+
if (existsSync(ENV_PATH)) {
|
|
15
|
+
// Set DOTENV_CONFIG_QUIET to suppress dotenv's default logging
|
|
16
|
+
process.env.DOTENV_CONFIG_QUIET = 'true';
|
|
17
|
+
const result = loadDotenv({ path: ENV_PATH });
|
|
18
|
+
|
|
19
|
+
if (process.env.VERBOSE === '1' || process.env.VERBOSE === 'true') {
|
|
20
|
+
console.log(`[DEBUG] Loading .env from: ${ENV_PATH}`);
|
|
21
|
+
console.log(`[DEBUG] .env loaded: ${result.parsed ? 'yes' : 'no'}`);
|
|
22
|
+
}
|
|
23
|
+
} else if (process.env.VERBOSE === '1' || process.env.VERBOSE === 'true') {
|
|
24
|
+
console.log(`[DEBUG] No .env file found at: ${ENV_PATH}`);
|
|
25
|
+
console.log(`[DEBUG] Run 'npx tsx scripts/setup/onboard.ts' to create one`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MAINNET_URL = 'https://api.hyperliquid.xyz';
|
|
29
|
+
const TESTNET_URL = 'https://api.hyperliquid-testnet.xyz';
|
|
30
|
+
|
|
31
|
+
// Open Broker builder address - receives builder fees on all trades
|
|
32
|
+
// This funds continued development of the open-broker project
|
|
33
|
+
export const OPEN_BROKER_BUILDER_ADDRESS = '0xbb67021fA3e62ab4DA985bb5a55c5c1884381068';
|
|
34
|
+
|
|
35
|
+
export function loadConfig(): OpenBrokerConfig {
|
|
36
|
+
const privateKey = process.env.HYPERLIQUID_PRIVATE_KEY;
|
|
37
|
+
if (!privateKey) {
|
|
38
|
+
if (!existsSync(ENV_PATH)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
'No .env file found. Run onboarding first:\n\n' +
|
|
41
|
+
' npx tsx scripts/setup/onboard.ts\n'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
throw new Error(
|
|
45
|
+
'HYPERLIQUID_PRIVATE_KEY not found in .env file.\n' +
|
|
46
|
+
'Add it to your .env or run: npx tsx scripts/setup/onboard.ts'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!privateKey.startsWith('0x') || privateKey.length !== 66) {
|
|
51
|
+
throw new Error('HYPERLIQUID_PRIVATE_KEY must be a 64-character hex string with 0x prefix');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const network = process.env.HYPERLIQUID_NETWORK || 'mainnet';
|
|
55
|
+
const baseUrl = network === 'testnet' ? TESTNET_URL : MAINNET_URL;
|
|
56
|
+
|
|
57
|
+
// Use open-broker address by default, but allow override for custom builders
|
|
58
|
+
const builderAddress = (process.env.BUILDER_ADDRESS || OPEN_BROKER_BUILDER_ADDRESS).toLowerCase();
|
|
59
|
+
const builderFee = parseInt(process.env.BUILDER_FEE || '10', 10); // default 1 bps
|
|
60
|
+
const slippageBps = parseInt(process.env.SLIPPAGE_BPS || '50', 10); // default 0.5%
|
|
61
|
+
|
|
62
|
+
// Derive the wallet address from private key
|
|
63
|
+
const wallet = privateKeyToAccount(privateKey as `0x${string}`);
|
|
64
|
+
const walletAddress = wallet.address.toLowerCase();
|
|
65
|
+
|
|
66
|
+
// Account address can be different if using an API wallet
|
|
67
|
+
// If not specified, use the wallet address itself
|
|
68
|
+
const accountAddress = process.env.HYPERLIQUID_ACCOUNT_ADDRESS?.toLowerCase();
|
|
69
|
+
|
|
70
|
+
// Determine if this is an API wallet setup
|
|
71
|
+
const isApiWallet = accountAddress !== undefined && accountAddress !== walletAddress;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
baseUrl,
|
|
75
|
+
privateKey: privateKey as `0x${string}`,
|
|
76
|
+
walletAddress,
|
|
77
|
+
accountAddress: accountAddress || walletAddress,
|
|
78
|
+
isApiWallet,
|
|
79
|
+
builderAddress,
|
|
80
|
+
builderFee,
|
|
81
|
+
slippageBps,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getNetwork(): 'mainnet' | 'testnet' {
|
|
86
|
+
const network = process.env.HYPERLIQUID_NETWORK || 'mainnet';
|
|
87
|
+
return network === 'testnet' ? 'testnet' : 'mainnet';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function isMainnet(): boolean {
|
|
91
|
+
return getNetwork() === 'mainnet';
|
|
92
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Hyperliquid API Types for Open Broker
|
|
2
|
+
|
|
3
|
+
// ============ Configuration ============
|
|
4
|
+
|
|
5
|
+
export interface OpenBrokerConfig {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
privateKey: `0x${string}`;
|
|
8
|
+
walletAddress: string; // Address derived from private key (the signer)
|
|
9
|
+
accountAddress: string; // Address to trade on behalf of (may differ if using API wallet)
|
|
10
|
+
isApiWallet: boolean; // True if walletAddress != accountAddress
|
|
11
|
+
builderAddress: string;
|
|
12
|
+
builderFee: number; // tenths of bps (10 = 1 bps)
|
|
13
|
+
slippageBps: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ============ Builder ============
|
|
17
|
+
|
|
18
|
+
export interface BuilderInfo {
|
|
19
|
+
b: string; // builder address (lowercase)
|
|
20
|
+
f: number; // fee in tenths of bps
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ============ Order Types ============
|
|
24
|
+
|
|
25
|
+
export type OrderType =
|
|
26
|
+
| { limit: { tif: 'Gtc' | 'Ioc' | 'Alo' } }
|
|
27
|
+
| { trigger: { triggerPx: string; isMarket: boolean; tpsl: 'tp' | 'sl' } };
|
|
28
|
+
|
|
29
|
+
export interface OrderRequest {
|
|
30
|
+
coin: string;
|
|
31
|
+
is_buy: boolean;
|
|
32
|
+
sz: number;
|
|
33
|
+
limit_px: number;
|
|
34
|
+
order_type: OrderType;
|
|
35
|
+
reduce_only?: boolean;
|
|
36
|
+
cloid?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface OrderWire {
|
|
40
|
+
a: number; // asset index
|
|
41
|
+
b: boolean; // is_buy
|
|
42
|
+
p: string; // price
|
|
43
|
+
s: string; // size
|
|
44
|
+
r: boolean; // reduce_only
|
|
45
|
+
t: OrderType;
|
|
46
|
+
c?: string; // cloid
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface OrderResponse {
|
|
50
|
+
status: 'ok' | 'err';
|
|
51
|
+
response?: {
|
|
52
|
+
type: 'order';
|
|
53
|
+
data: {
|
|
54
|
+
statuses: Array<{
|
|
55
|
+
resting?: { oid: number };
|
|
56
|
+
filled?: { totalSz: string; avgPx: string; oid: number };
|
|
57
|
+
error?: string;
|
|
58
|
+
[key: string]: unknown; // Catch any other fields
|
|
59
|
+
}>;
|
|
60
|
+
};
|
|
61
|
+
} | string; // API can return string error message
|
|
62
|
+
error?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============ Cancel Types ============
|
|
66
|
+
|
|
67
|
+
export interface CancelRequest {
|
|
68
|
+
coin: string;
|
|
69
|
+
oid: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface CancelResponse {
|
|
73
|
+
status: 'ok' | 'err';
|
|
74
|
+
response?: {
|
|
75
|
+
type: 'cancel';
|
|
76
|
+
data: { statuses: string[] };
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============ Market Data Types ============
|
|
81
|
+
|
|
82
|
+
export interface AssetMeta {
|
|
83
|
+
name: string;
|
|
84
|
+
szDecimals: number;
|
|
85
|
+
maxLeverage: number;
|
|
86
|
+
onlyIsolated: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface AssetCtx {
|
|
90
|
+
funding: string;
|
|
91
|
+
openInterest: string;
|
|
92
|
+
prevDayPx: string;
|
|
93
|
+
dayNtlVlm: string;
|
|
94
|
+
premium: string;
|
|
95
|
+
oraclePx: string;
|
|
96
|
+
markPx: string;
|
|
97
|
+
midPx?: string;
|
|
98
|
+
impactPxs?: [string, string];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface Meta {
|
|
102
|
+
universe: AssetMeta[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface MetaAndAssetCtxs {
|
|
106
|
+
meta: Meta;
|
|
107
|
+
assetCtxs: AssetCtx[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============ Account Types ============
|
|
111
|
+
|
|
112
|
+
export interface Position {
|
|
113
|
+
coin: string;
|
|
114
|
+
szi: string; // signed size (negative = short)
|
|
115
|
+
entryPx: string;
|
|
116
|
+
positionValue: string;
|
|
117
|
+
unrealizedPnl: string;
|
|
118
|
+
returnOnEquity: string;
|
|
119
|
+
liquidationPx: string | null;
|
|
120
|
+
leverage: {
|
|
121
|
+
type: 'cross' | 'isolated';
|
|
122
|
+
value: number;
|
|
123
|
+
rawUsd?: string;
|
|
124
|
+
};
|
|
125
|
+
marginUsed: string;
|
|
126
|
+
maxLeverage: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface AssetPosition {
|
|
130
|
+
position: Position;
|
|
131
|
+
type: 'oneWay';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface MarginSummary {
|
|
135
|
+
accountValue: string;
|
|
136
|
+
totalNtlPos: string;
|
|
137
|
+
totalRawUsd: string;
|
|
138
|
+
totalMarginUsed: string;
|
|
139
|
+
withdrawable: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface ClearinghouseState {
|
|
143
|
+
assetPositions: AssetPosition[];
|
|
144
|
+
crossMarginSummary: MarginSummary;
|
|
145
|
+
marginSummary: MarginSummary;
|
|
146
|
+
crossMaintenanceMarginUsed: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============ Funding Types ============
|
|
150
|
+
|
|
151
|
+
export interface FundingInfo {
|
|
152
|
+
coin: string;
|
|
153
|
+
fundingRate: string; // hourly rate
|
|
154
|
+
annualizedRate: number; // calculated
|
|
155
|
+
premium: string;
|
|
156
|
+
openInterest: string;
|
|
157
|
+
markPx: string;
|
|
158
|
+
oraclePx: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============ Open Orders ============
|
|
162
|
+
|
|
163
|
+
export interface OpenOrder {
|
|
164
|
+
coin: string;
|
|
165
|
+
oid: number;
|
|
166
|
+
cloid?: string;
|
|
167
|
+
side: 'B' | 'A'; // Bid or Ask
|
|
168
|
+
sz: string;
|
|
169
|
+
limitPx: string;
|
|
170
|
+
orderType: string;
|
|
171
|
+
origSz: string;
|
|
172
|
+
timestamp: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============ API Request/Response ============
|
|
176
|
+
|
|
177
|
+
export interface InfoRequest {
|
|
178
|
+
type: string;
|
|
179
|
+
user?: string;
|
|
180
|
+
[key: string]: unknown;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ExchangeRequest {
|
|
184
|
+
action: Record<string, unknown>;
|
|
185
|
+
nonce: number;
|
|
186
|
+
signature: {
|
|
187
|
+
r: string;
|
|
188
|
+
s: string;
|
|
189
|
+
v: number;
|
|
190
|
+
};
|
|
191
|
+
vaultAddress?: string | null;
|
|
192
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Utility functions for Open Broker
|
|
2
|
+
|
|
3
|
+
import type { OrderType, OrderWire, OrderRequest } from './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Round price to 5 significant figures and appropriate decimals
|
|
7
|
+
* Perps: max 6 decimals, Spot: max 8 decimals
|
|
8
|
+
*/
|
|
9
|
+
export function roundPrice(price: number, szDecimals: number, isSpot: boolean = false): string {
|
|
10
|
+
// Round to 5 significant figures first
|
|
11
|
+
const sigFigs = parseFloat(price.toPrecision(5));
|
|
12
|
+
// Then round to max decimals (6 for perps - szDecimals adjustment, 8 for spot)
|
|
13
|
+
const maxDecimals = isSpot ? 8 : Math.max(0, 6 - szDecimals);
|
|
14
|
+
return sigFigs.toFixed(maxDecimals);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Round size to asset's szDecimals
|
|
19
|
+
*/
|
|
20
|
+
export function roundSize(size: number, szDecimals: number): string {
|
|
21
|
+
return size.toFixed(szDecimals);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Calculate slippage-adjusted price for market orders
|
|
26
|
+
*/
|
|
27
|
+
export function getSlippagePrice(
|
|
28
|
+
midPrice: number,
|
|
29
|
+
isBuy: boolean,
|
|
30
|
+
slippageBps: number
|
|
31
|
+
): number {
|
|
32
|
+
const slippageMultiplier = slippageBps / 10000;
|
|
33
|
+
return isBuy
|
|
34
|
+
? midPrice * (1 + slippageMultiplier)
|
|
35
|
+
: midPrice * (1 - slippageMultiplier);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert order request to wire format
|
|
40
|
+
*/
|
|
41
|
+
export function orderToWire(
|
|
42
|
+
order: OrderRequest,
|
|
43
|
+
assetIndex: number,
|
|
44
|
+
szDecimals: number
|
|
45
|
+
): OrderWire {
|
|
46
|
+
const wire: OrderWire = {
|
|
47
|
+
a: assetIndex,
|
|
48
|
+
b: order.is_buy,
|
|
49
|
+
p: roundPrice(order.limit_px, szDecimals),
|
|
50
|
+
s: roundSize(order.sz, szDecimals),
|
|
51
|
+
r: order.reduce_only ?? false,
|
|
52
|
+
t: order.order_type,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (order.cloid) {
|
|
56
|
+
wire.c = order.cloid;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return wire;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get current timestamp in milliseconds
|
|
64
|
+
*/
|
|
65
|
+
export function getTimestampMs(): number {
|
|
66
|
+
return Date.now();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Format USD amount for display
|
|
71
|
+
*/
|
|
72
|
+
export function formatUsd(amount: number | string): string {
|
|
73
|
+
const num = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
74
|
+
return new Intl.NumberFormat('en-US', {
|
|
75
|
+
style: 'currency',
|
|
76
|
+
currency: 'USD',
|
|
77
|
+
minimumFractionDigits: 2,
|
|
78
|
+
maximumFractionDigits: 2,
|
|
79
|
+
}).format(num);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Format percentage for display
|
|
84
|
+
*/
|
|
85
|
+
export function formatPercent(value: number | string, decimals: number = 2): string {
|
|
86
|
+
const num = typeof value === 'string' ? parseFloat(value) : value;
|
|
87
|
+
return `${(num * 100).toFixed(decimals)}%`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Format funding rate (hourly to annualized)
|
|
92
|
+
*/
|
|
93
|
+
export function annualizeFundingRate(hourlyRate: number | string): number {
|
|
94
|
+
const rate = typeof hourlyRate === 'string' ? parseFloat(hourlyRate) : hourlyRate;
|
|
95
|
+
return rate * 8760; // 24 * 365 hours
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse CLI arguments into key-value pairs
|
|
100
|
+
*/
|
|
101
|
+
export function parseArgs(args: string[]): Record<string, string | boolean> {
|
|
102
|
+
const result: Record<string, string | boolean> = {};
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < args.length; i++) {
|
|
105
|
+
const arg = args[i];
|
|
106
|
+
if (arg.startsWith('--')) {
|
|
107
|
+
const key = arg.slice(2);
|
|
108
|
+
const nextArg = args[i + 1];
|
|
109
|
+
|
|
110
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
111
|
+
result[key] = nextArg;
|
|
112
|
+
i++;
|
|
113
|
+
} else {
|
|
114
|
+
result[key] = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Sleep for specified milliseconds
|
|
124
|
+
*/
|
|
125
|
+
export function sleep(ms: number): Promise<void> {
|
|
126
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate a random client order ID
|
|
131
|
+
*/
|
|
132
|
+
export function generateCloid(): string {
|
|
133
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
134
|
+
let result = '';
|
|
135
|
+
for (let i = 0; i < 16; i++) {
|
|
136
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if builder fee is approved and print warning if not
|
|
143
|
+
* Returns true if approved, false if not
|
|
144
|
+
*/
|
|
145
|
+
export async function checkBuilderFeeApproval(
|
|
146
|
+
client: { getMaxBuilderFee: () => Promise<string | null>; builderAddress: string }
|
|
147
|
+
): Promise<boolean> {
|
|
148
|
+
const approval = await client.getMaxBuilderFee();
|
|
149
|
+
if (!approval) {
|
|
150
|
+
console.log('⚠️ Builder fee not approved!');
|
|
151
|
+
console.log(` Run: npx tsx scripts/setup/approve-builder.ts`);
|
|
152
|
+
console.log(` Builder: ${client.builderAddress}\n`);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Get account info from Hyperliquid
|
|
3
|
+
|
|
4
|
+
import { getClient } from '../core/client.js';
|
|
5
|
+
import { formatUsd, formatPercent, parseArgs } from '../core/utils.js';
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const args = parseArgs(process.argv.slice(2));
|
|
9
|
+
const client = getClient();
|
|
10
|
+
|
|
11
|
+
console.log('Open Broker - Account Info');
|
|
12
|
+
console.log('==========================\n');
|
|
13
|
+
|
|
14
|
+
console.log('Wallet Configuration');
|
|
15
|
+
console.log('--------------------');
|
|
16
|
+
console.log(`Trading Account: ${client.address}`);
|
|
17
|
+
console.log(`Signing Wallet: ${client.walletAddress}`);
|
|
18
|
+
console.log(`Wallet Type: ${client.isApiWallet ? 'API Wallet' : 'Main Wallet'}`);
|
|
19
|
+
|
|
20
|
+
// Check builder fee approval
|
|
21
|
+
const builderApproval = await client.getMaxBuilderFee();
|
|
22
|
+
console.log(`Builder Address: ${client.builderAddress}`);
|
|
23
|
+
console.log(`Builder Fee: ${client.builderFeeBps} bps`);
|
|
24
|
+
if (builderApproval) {
|
|
25
|
+
console.log(`Builder Approved: ✅ Yes (max: ${builderApproval})`);
|
|
26
|
+
} else {
|
|
27
|
+
console.log(`Builder Approved: ❌ No`);
|
|
28
|
+
console.log(`\n⚠️ Run: npx tsx scripts/setup/approve-builder.ts`);
|
|
29
|
+
}
|
|
30
|
+
console.log('');
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const state = await client.getUserState();
|
|
34
|
+
|
|
35
|
+
const margin = state.crossMarginSummary;
|
|
36
|
+
const accountValue = parseFloat(margin.accountValue);
|
|
37
|
+
const totalMarginUsed = parseFloat(margin.totalMarginUsed);
|
|
38
|
+
const withdrawable = parseFloat(margin.withdrawable);
|
|
39
|
+
const totalNotional = parseFloat(margin.totalNtlPos);
|
|
40
|
+
|
|
41
|
+
console.log('Margin Summary');
|
|
42
|
+
console.log('--------------');
|
|
43
|
+
console.log(`Account Value: ${formatUsd(accountValue)}`);
|
|
44
|
+
console.log(`Total Notional: ${formatUsd(totalNotional)}`);
|
|
45
|
+
console.log(`Margin Used: ${formatUsd(totalMarginUsed)}`);
|
|
46
|
+
console.log(`Withdrawable: ${formatUsd(withdrawable)}`);
|
|
47
|
+
|
|
48
|
+
if (totalMarginUsed > 0) {
|
|
49
|
+
const marginRatio = totalMarginUsed / accountValue;
|
|
50
|
+
console.log(`Margin Ratio: ${formatPercent(marginRatio)}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log('\nPositions Summary');
|
|
54
|
+
console.log('-----------------');
|
|
55
|
+
|
|
56
|
+
if (state.assetPositions.length === 0) {
|
|
57
|
+
console.log('No open positions');
|
|
58
|
+
} else {
|
|
59
|
+
let totalPnl = 0;
|
|
60
|
+
console.log('Coin | Size | Entry | Mark | PnL | Leverage');
|
|
61
|
+
console.log('---------|------------|------------|------------|------------|----------');
|
|
62
|
+
|
|
63
|
+
for (const ap of state.assetPositions) {
|
|
64
|
+
const pos = ap.position;
|
|
65
|
+
const size = parseFloat(pos.szi);
|
|
66
|
+
if (Math.abs(size) < 0.0001) continue;
|
|
67
|
+
|
|
68
|
+
const entryPx = parseFloat(pos.entryPx);
|
|
69
|
+
const pnl = parseFloat(pos.unrealizedPnl);
|
|
70
|
+
totalPnl += pnl;
|
|
71
|
+
|
|
72
|
+
// Get mark price from leverage calculation
|
|
73
|
+
const notional = parseFloat(pos.positionValue);
|
|
74
|
+
const markPx = Math.abs(notional / size);
|
|
75
|
+
|
|
76
|
+
const side = size > 0 ? 'L' : 'S';
|
|
77
|
+
const leverageStr = `${pos.leverage.value}x ${pos.leverage.type}`;
|
|
78
|
+
|
|
79
|
+
console.log(
|
|
80
|
+
`${pos.coin.padEnd(8)} | ${side} ${Math.abs(size).toFixed(4).padStart(8)} | ` +
|
|
81
|
+
`${formatUsd(entryPx).padStart(10)} | ${formatUsd(markPx).padStart(10)} | ` +
|
|
82
|
+
`${formatUsd(pnl).padStart(10)} | ${leverageStr}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log('---------|------------|------------|------------|------------|----------');
|
|
87
|
+
console.log(`Total Unrealized PnL: ${formatUsd(totalPnl)}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Show open orders if requested
|
|
91
|
+
if (args.orders) {
|
|
92
|
+
console.log('\nOpen Orders');
|
|
93
|
+
console.log('-----------');
|
|
94
|
+
|
|
95
|
+
const orders = await client.getOpenOrders();
|
|
96
|
+
if (orders.length === 0) {
|
|
97
|
+
console.log('No open orders');
|
|
98
|
+
} else {
|
|
99
|
+
console.log('Coin | Side | Size | Price | Type');
|
|
100
|
+
console.log('---------|------|------------|------------|------');
|
|
101
|
+
for (const order of orders) {
|
|
102
|
+
const side = order.side === 'B' ? 'BUY ' : 'SELL';
|
|
103
|
+
console.log(
|
|
104
|
+
`${order.coin.padEnd(8)} | ${side} | ${parseFloat(order.sz).toFixed(4).padStart(10)} | ` +
|
|
105
|
+
`${formatUsd(parseFloat(order.limitPx)).padStart(10)} | ${order.orderType}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Error fetching account info:', error);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main();
|