dipcoin-cli 1.3.0 → 1.3.3
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/README.md +30 -5
- package/dist/cli.cjs +352 -10
- package/dist/cli.cjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,9 +101,12 @@ dipcoin-cli market pairs # List all trading pairs
|
|
|
101
101
|
dipcoin-cli market ticker <symbol> # Ticker (price, volume, funding)
|
|
102
102
|
dipcoin-cli market orderbook <symbol> # Order book
|
|
103
103
|
dipcoin-cli market oracle <symbol> # Oracle price
|
|
104
|
+
dipcoin-cli market kline <symbol> # Candlestick history (default 1h, last 24h)
|
|
105
|
+
dipcoin-cli market kline BTC --interval 5m --count 20
|
|
106
|
+
dipcoin-cli market kline BTC --interval 1d --from <unix-sec> --to <unix-sec>
|
|
104
107
|
```
|
|
105
108
|
|
|
106
|
-
Symbols auto-normalize: `BTC` becomes `BTC-PERP`.
|
|
109
|
+
Symbols auto-normalize: `BTC` becomes `BTC-PERP`. `kline` intervals: `1m / 5m / 15m / 30m / 1h / 4h / 1d / 1w`.
|
|
107
110
|
|
|
108
111
|
#### account
|
|
109
112
|
|
|
@@ -145,8 +148,14 @@ dipcoin-cli trade orders --symbol BTC-PERP
|
|
|
145
148
|
|
|
146
149
|
# Cancel orders
|
|
147
150
|
dipcoin-cli trade cancel <symbol> <hash1> [hash2...]
|
|
151
|
+
|
|
152
|
+
# Adjust default (ISOLATED) leverage for a market (REST update, no on-chain tx)
|
|
153
|
+
dipcoin-cli trade leverage BTC 10
|
|
154
|
+
dipcoin-cli trade leverage ETH 5
|
|
148
155
|
```
|
|
149
156
|
|
|
157
|
+
DipCoin perp currently only supports ISOLATED margin; CROSS is not exposed by the backend.
|
|
158
|
+
|
|
150
159
|
**Trade Options:**
|
|
151
160
|
|
|
152
161
|
| Option | Description |
|
|
@@ -172,13 +181,29 @@ dipcoin-cli position tpsl <symbol> --side <buy|sell> --quantity <q> --leverage <
|
|
|
172
181
|
dipcoin-cli position tpsl <symbol> --side sell --quantity 0.01 --leverage 10 \
|
|
173
182
|
--tp-trigger 105000 --tp-type limit --tp-price 105000
|
|
174
183
|
|
|
175
|
-
#
|
|
184
|
+
# Close positions — CLI figures out side & qty
|
|
185
|
+
dipcoin-cli position close BTC # close 100% of BTC position
|
|
186
|
+
dipcoin-cli position close BTC --percent 50 # close half
|
|
187
|
+
dipcoin-cli position close-all # close every open position
|
|
188
|
+
dipcoin-cli position close-all --percent 50 # halve every open position
|
|
189
|
+
|
|
190
|
+
# Manual form is still supported (specify exact qty yourself):
|
|
191
|
+
dipcoin-cli trade sell BTC 0 10x --qty 0.01 --reduce-only # close 0.01 of a long
|
|
192
|
+
dipcoin-cli trade buy ETH 0 5x --qty 1 --reduce-only # close 1 of a short
|
|
193
|
+
|
|
194
|
+
# Margin operations (on-chain) — auto-prepends a fresh signed price update
|
|
176
195
|
dipcoin-cli position margin add <symbol> <amount>
|
|
177
196
|
dipcoin-cli position margin remove <symbol> <amount>
|
|
197
|
+
|
|
198
|
+
# Same, but target a vault's position
|
|
199
|
+
dipcoin-cli position margin add <symbol> <amount> --vault <vaultId>
|
|
200
|
+
dipcoin-cli position margin remove <symbol> <amount> --vault <vaultId>
|
|
178
201
|
```
|
|
179
202
|
|
|
180
203
|
The `--side` is the **closing side**: use `sell` for long positions, `buy` for short.
|
|
181
204
|
|
|
205
|
+
> All on-chain margin / vault writes (`position margin add/remove`, `vault deposit/close/withdraw/claim/fill-withdrawals`) fetch a fresh signed price from `/api/perp-market-api/price/latest` and bundle a `price_feed::update_price_feed` PTB step before the actual call — required by the v6 oracle freshness check.
|
|
206
|
+
|
|
182
207
|
#### vault
|
|
183
208
|
|
|
184
209
|
On-chain vault operations (DipCoin vault contracts):
|
|
@@ -197,9 +222,9 @@ dipcoin-cli vault position <vaultId> --address <addr> # Query another address
|
|
|
197
222
|
dipcoin-cli vault deposit <vaultId> <amount>
|
|
198
223
|
dipcoin-cli vault withdraw <vaultId> <shares>
|
|
199
224
|
dipcoin-cli vault withdraw <vaultId> --all # Withdraw all shares
|
|
200
|
-
dipcoin-cli vault fill <vaultId> <requestIDs...>
|
|
201
|
-
dipcoin-cli vault close <vaultId>
|
|
202
|
-
dipcoin-cli vault remove <vaultId>
|
|
225
|
+
dipcoin-cli vault fill-withdrawals <vaultId> <requestIDs...> # creator/operator only
|
|
226
|
+
dipcoin-cli vault close <vaultId>
|
|
227
|
+
dipcoin-cli vault remove <vaultId> # operator-only (normal creators get "unauthorized")
|
|
203
228
|
dipcoin-cli vault claim <vaultId>
|
|
204
229
|
dipcoin-cli vault set-trader <vaultId> <address>
|
|
205
230
|
dipcoin-cli vault set-sub-trader <vaultId> <address> [--disable]
|
package/dist/cli.cjs
CHANGED
|
@@ -12973,7 +12973,11 @@ function formatNormalToWei(value, decimals = 18) {
|
|
|
12973
12973
|
* @returns BigNumber instance
|
|
12974
12974
|
*/
|
|
12975
12975
|
function formatNormalToWeiBN(value, decimals = 18) {
|
|
12976
|
+
if (!value && value !== 0)
|
|
12977
|
+
return new BigNumber__default["default"](0);
|
|
12976
12978
|
const bn = new BigNumber__default["default"](value);
|
|
12979
|
+
if (bn.isNaN())
|
|
12980
|
+
return new BigNumber__default["default"](0);
|
|
12977
12981
|
return bn.multipliedBy(new BigNumber__default["default"](10).pow(decimals));
|
|
12978
12982
|
}
|
|
12979
12983
|
/**
|
|
@@ -16369,6 +16373,8 @@ const API_ENDPOINTS = {
|
|
|
16369
16373
|
FUNDING_SETTLEMENTS: "/api/perp-trade-api/history/funding-settlements",
|
|
16370
16374
|
BALANCE_CHANGES: "/api/perp-trade-api/history/balance-changes",
|
|
16371
16375
|
ORACLE: "/api/perp-market-api/oracle",
|
|
16376
|
+
SIGNED_PRICE_LATEST: "/api/perp-market-api/price/latest",
|
|
16377
|
+
KLINE_HISTORY: "/api/perp-market-api/kline/history",
|
|
16372
16378
|
VAULTS_BY_CREATOR: "/api/perp-vault-api/public/vaults/by-creator",
|
|
16373
16379
|
VAULTS_PUBLIC: "/api/perp-vault-api/public/vaults",
|
|
16374
16380
|
// Points & Referral
|
|
@@ -18558,6 +18564,77 @@ class DipCoinPerpSDK {
|
|
|
18558
18564
|
return { status: false, error: formatError(error) };
|
|
18559
18565
|
}
|
|
18560
18566
|
}
|
|
18567
|
+
/**
|
|
18568
|
+
* Fetch kline (candlestick) history for a market.
|
|
18569
|
+
*
|
|
18570
|
+
* @param params.symbol Trading pair (e.g. "BTC-PERP")
|
|
18571
|
+
* @param params.interval Bar interval (e.g. "1m" / "5m" / "1h" / "1d")
|
|
18572
|
+
* @param params.from Inclusive start timestamp (Unix seconds)
|
|
18573
|
+
* @param params.to Exclusive end timestamp (Unix seconds)
|
|
18574
|
+
* @param params.countback Optional cap on returned bars (most recent first)
|
|
18575
|
+
*/
|
|
18576
|
+
async getKlineHistory(params) {
|
|
18577
|
+
try {
|
|
18578
|
+
if (!params?.symbol)
|
|
18579
|
+
return { status: false, error: "symbol is required" };
|
|
18580
|
+
if (!params?.interval)
|
|
18581
|
+
return { status: false, error: "interval is required" };
|
|
18582
|
+
// Server requires both startTime and endTime in unix seconds.
|
|
18583
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
18584
|
+
const startTime = params.from ?? nowSec - 86400;
|
|
18585
|
+
const endTime = params.to ?? nowSec;
|
|
18586
|
+
const response = await this.httpClient.get(API_ENDPOINTS.KLINE_HISTORY, {
|
|
18587
|
+
params: {
|
|
18588
|
+
symbol: params.symbol,
|
|
18589
|
+
interval: params.interval,
|
|
18590
|
+
startTime,
|
|
18591
|
+
endTime,
|
|
18592
|
+
...(params.countback !== undefined ? { countback: params.countback } : {}),
|
|
18593
|
+
},
|
|
18594
|
+
});
|
|
18595
|
+
if (response.code !== 200) {
|
|
18596
|
+
return { status: false, error: response.message || `code ${response.code}` };
|
|
18597
|
+
}
|
|
18598
|
+
const raw = Array.isArray(response.data)
|
|
18599
|
+
? response.data
|
|
18600
|
+
: Array.isArray(response.data?.data)
|
|
18601
|
+
? response.data.data
|
|
18602
|
+
: [];
|
|
18603
|
+
const weiToNormal = (v) => {
|
|
18604
|
+
if (v === undefined || v === null || v === "")
|
|
18605
|
+
return "0";
|
|
18606
|
+
const bn = new BigNumber__default["default"](String(v));
|
|
18607
|
+
if (!bn.isFinite() || bn.isZero())
|
|
18608
|
+
return "0";
|
|
18609
|
+
return bn.dividedBy(new BigNumber__default["default"](10).pow(18)).toString();
|
|
18610
|
+
};
|
|
18611
|
+
const bars = raw.map((row) => {
|
|
18612
|
+
if (Array.isArray(row)) {
|
|
18613
|
+
const [time, open, high, low, close, volume] = row;
|
|
18614
|
+
return {
|
|
18615
|
+
time: Number(time ?? 0),
|
|
18616
|
+
open: weiToNormal(open),
|
|
18617
|
+
high: weiToNormal(high),
|
|
18618
|
+
low: weiToNormal(low),
|
|
18619
|
+
close: weiToNormal(close),
|
|
18620
|
+
volume: weiToNormal(volume),
|
|
18621
|
+
};
|
|
18622
|
+
}
|
|
18623
|
+
return {
|
|
18624
|
+
time: Number(row.time ?? row.timestamp ?? 0),
|
|
18625
|
+
open: weiToNormal(row.open),
|
|
18626
|
+
high: weiToNormal(row.high),
|
|
18627
|
+
low: weiToNormal(row.low),
|
|
18628
|
+
close: weiToNormal(row.close),
|
|
18629
|
+
volume: weiToNormal(row.volume ?? row.vol),
|
|
18630
|
+
};
|
|
18631
|
+
});
|
|
18632
|
+
return { status: true, data: bars };
|
|
18633
|
+
}
|
|
18634
|
+
catch (error) {
|
|
18635
|
+
return { status: false, error: formatError(error) };
|
|
18636
|
+
}
|
|
18637
|
+
}
|
|
18561
18638
|
/**
|
|
18562
18639
|
* Add isolated margin to an existing position (on-chain)
|
|
18563
18640
|
* @param params Margin adjustment parameters
|
|
@@ -18620,9 +18697,14 @@ class DipCoinPerpSDK {
|
|
|
18620
18697
|
}
|
|
18621
18698
|
async buildMarginTransaction(params, action) {
|
|
18622
18699
|
const payload = this.buildMarginCallArgs(params, action);
|
|
18623
|
-
// v6+
|
|
18624
|
-
//
|
|
18700
|
+
// v6+ requires PriceFeed to be fresh (~60s) before the margin call. Operator updates
|
|
18701
|
+
// are only hourly so we always fetch + prepend our own update_price_feed step.
|
|
18702
|
+
const sym = payload.market || params.symbol;
|
|
18703
|
+
if (!sym)
|
|
18704
|
+
throw new Error("symbol/market required to refresh PriceFeed before margin op");
|
|
18705
|
+
const feeds = await this.fetchSignedPriceFeeds([sym]);
|
|
18625
18706
|
const baseTx = new transactions.Transaction();
|
|
18707
|
+
this.appendPriceFeedUpdates(baseTx, Array.from(feeds.values()));
|
|
18626
18708
|
if (action === "add") {
|
|
18627
18709
|
return buildAddMarginTx(this.deploymentConfig, payload, baseTx, params.gasBudget);
|
|
18628
18710
|
}
|
|
@@ -18915,6 +18997,63 @@ class DipCoinPerpSDK {
|
|
|
18915
18997
|
}
|
|
18916
18998
|
return id;
|
|
18917
18999
|
}
|
|
19000
|
+
/**
|
|
19001
|
+
* Fetch signed price update bytes for the given markets from the DipCoin pricing service.
|
|
19002
|
+
* Each entry's payload/signature/publicKey is consumed by
|
|
19003
|
+
* `<pkg>::price_feed::update_price_feed(PriceFeed, Clock, payload, signature, publicKey)`.
|
|
19004
|
+
*
|
|
19005
|
+
* The on-chain v6 contract requires the PriceFeed entry for a perp to be fresh (~60s)
|
|
19006
|
+
* for any margin/vault NAV write. The CLI prepends one update_price_feed call per
|
|
19007
|
+
* perp involved in the tx before the actual margin/vault call.
|
|
19008
|
+
*/
|
|
19009
|
+
async fetchSignedPriceFeeds(symbols) {
|
|
19010
|
+
const out = new Map();
|
|
19011
|
+
if (symbols.length === 0)
|
|
19012
|
+
return out;
|
|
19013
|
+
const qs = symbols.map((s) => `symbols=${encodeURIComponent(s)}`).join("&");
|
|
19014
|
+
const resp = await this.httpClient.get(`${API_ENDPOINTS.SIGNED_PRICE_LATEST}?${qs}`);
|
|
19015
|
+
if (resp.code !== 200 || !resp.data) {
|
|
19016
|
+
throw new Error(`Failed to fetch signed price feed for [${symbols.join(",")}]: ${resp.message || `code ${resp.code}`}`);
|
|
19017
|
+
}
|
|
19018
|
+
const d = resp.data;
|
|
19019
|
+
const b64 = (s) => Uint8Array.from(Buffer.from(s, "base64"));
|
|
19020
|
+
if (!d.payload || !d.signature || !d.publicKey) {
|
|
19021
|
+
throw new Error("Signed price feed response missing payload/signature/publicKey");
|
|
19022
|
+
}
|
|
19023
|
+
const entry = { payload: b64(d.payload), signature: b64(d.signature), publicKey: b64(d.publicKey) };
|
|
19024
|
+
// API returns a single bundle covering all requested symbols' prices in priceList,
|
|
19025
|
+
// but only one (payload, signature, publicKey) tuple. Map every requested symbol to it.
|
|
19026
|
+
for (const sym of symbols)
|
|
19027
|
+
out.set(sym, entry);
|
|
19028
|
+
return out;
|
|
19029
|
+
}
|
|
19030
|
+
/**
|
|
19031
|
+
* Build a `price_feed::update_price_feed` PTB command for each provided signed feed
|
|
19032
|
+
* entry and append to the given transaction.
|
|
19033
|
+
*/
|
|
19034
|
+
appendPriceFeedUpdates(tx, feeds) {
|
|
19035
|
+
const pkg = this.getPackageId();
|
|
19036
|
+
const pfId = this.getPriceFeedId();
|
|
19037
|
+
for (const f of feeds) {
|
|
19038
|
+
tx.moveCall({
|
|
19039
|
+
target: `${pkg}::price_feed::update_price_feed`,
|
|
19040
|
+
arguments: [
|
|
19041
|
+
tx.object(pfId),
|
|
19042
|
+
tx.object("0x6"),
|
|
19043
|
+
tx.pure.vector("u8", Array.from(f.payload)),
|
|
19044
|
+
tx.pure.vector("u8", Array.from(f.signature)),
|
|
19045
|
+
tx.pure.vector("u8", Array.from(f.publicKey)),
|
|
19046
|
+
],
|
|
19047
|
+
typeArguments: [],
|
|
19048
|
+
});
|
|
19049
|
+
}
|
|
19050
|
+
}
|
|
19051
|
+
getPackageId() {
|
|
19052
|
+
const pkgs = this.deploymentConfig?.packages;
|
|
19053
|
+
if (!pkgs || !pkgs.length)
|
|
19054
|
+
throw new Error("Deployment config missing packages array");
|
|
19055
|
+
return pkgs[pkgs.length - 1];
|
|
19056
|
+
}
|
|
18918
19057
|
async getOnChainPerpetualIds() {
|
|
18919
19058
|
const pcId = this.getDeploymentProtocolConfigId();
|
|
18920
19059
|
const df = await this.suiClient.getDynamicFieldObject({
|
|
@@ -18935,12 +19074,16 @@ class DipCoinPerpSDK {
|
|
|
18935
19074
|
const t = tx || new transactions.Transaction();
|
|
18936
19075
|
const vaultPkg = this.getVaultPackageId();
|
|
18937
19076
|
const currencyType = this.getCurrencyType();
|
|
18938
|
-
// v6+: PriceFeed
|
|
18939
|
-
//
|
|
18940
|
-
//
|
|
18941
|
-
//
|
|
18942
|
-
// list (which can drift behind chain additions).
|
|
19077
|
+
// v6+: PriceFeed must be fresh for every perp whose value compute_perpetual_position_value_v2
|
|
19078
|
+
// will touch. Refresh ALL markets first by fetching one combined signed bundle and prepending
|
|
19079
|
+
// a single update_price_feed step. Then iterate the on-chain perpetual_registry (which is
|
|
19080
|
+
// what new_vault_nav populates, and is_nav_filled requires every entry to be computed).
|
|
18943
19081
|
const priceFeedId = this.getPriceFeedId();
|
|
19082
|
+
const allSymbols = this.getMarketSymbols();
|
|
19083
|
+
const feeds = await this.fetchSignedPriceFeeds(allSymbols);
|
|
19084
|
+
// Dedup the bundle entries (the API returns one tuple covering all requested symbols).
|
|
19085
|
+
const uniqFeeds = Array.from(new Map(Array.from(feeds.values()).map((f) => [Buffer.from(f.payload).toString("hex"), f])).values());
|
|
19086
|
+
this.appendPriceFeedUpdates(t, uniqFeeds);
|
|
18944
19087
|
const perpetualIds = await this.getOnChainPerpetualIds();
|
|
18945
19088
|
const [nav] = t.moveCall({
|
|
18946
19089
|
target: `${vaultPkg}::vault::new_vault_nav`,
|
|
@@ -19821,6 +19964,34 @@ function registerTradeCommands(program) {
|
|
|
19821
19964
|
.argument("<symbol>", "Trading pair (e.g. BTC or BTC-PERP)")
|
|
19822
19965
|
.argument("<amount>", "USDC margin amount (e.g. 100 or 100USDC)")
|
|
19823
19966
|
.argument("<leverage>", "Leverage multiplier (e.g. 10 or 10x)")).action((symbol, amount, leverage, opts) => placeOrder(program, OrderSide.SELL, symbol, amount, leverage, opts));
|
|
19967
|
+
trade
|
|
19968
|
+
.command("leverage")
|
|
19969
|
+
.description("Adjust default leverage for a market (e.g. trade leverage BTC 10)")
|
|
19970
|
+
.argument("<symbol>", "Trading pair (e.g. BTC or BTC-PERP)")
|
|
19971
|
+
.argument("<leverage>", "Leverage multiplier (e.g. 10 or 10x)")
|
|
19972
|
+
.action(async (symbol, leverage) => {
|
|
19973
|
+
try {
|
|
19974
|
+
symbol = normalizeSymbol(symbol);
|
|
19975
|
+
const lev = Number(String(leverage).replace(/x$/i, ""));
|
|
19976
|
+
if (!Number.isFinite(lev) || lev <= 0)
|
|
19977
|
+
return handleError(`Invalid leverage: ${leverage}`);
|
|
19978
|
+
const sdk = getSDK();
|
|
19979
|
+
// Only ISOLATED margin is supported by the backend today.
|
|
19980
|
+
const result = await sdk.adjustLeverage({
|
|
19981
|
+
symbol,
|
|
19982
|
+
leverage: lev,
|
|
19983
|
+
marginType: "ISOLATED",
|
|
19984
|
+
});
|
|
19985
|
+
if (!result.status)
|
|
19986
|
+
return handleError(result.error);
|
|
19987
|
+
if (isJson(program))
|
|
19988
|
+
return printJson(result.data);
|
|
19989
|
+
console.log(`Leverage for ${symbol} set to ${lev}x (ISOLATED).`);
|
|
19990
|
+
}
|
|
19991
|
+
catch (e) {
|
|
19992
|
+
handleError(e);
|
|
19993
|
+
}
|
|
19994
|
+
});
|
|
19824
19995
|
trade
|
|
19825
19996
|
.command("cancel")
|
|
19826
19997
|
.description("Cancel orders by hash")
|
|
@@ -19921,6 +20092,134 @@ function registerPositionCommands(program) {
|
|
|
19921
20092
|
handleError(e);
|
|
19922
20093
|
}
|
|
19923
20094
|
});
|
|
20095
|
+
// Helper: align a human-readable quantity to the market's step size.
|
|
20096
|
+
async function alignToStepSize(sdk, symbol, qty) {
|
|
20097
|
+
let stepSize = 0.001;
|
|
20098
|
+
try {
|
|
20099
|
+
const pairResult = await sdk.getTradingPairs();
|
|
20100
|
+
if (pairResult.status && pairResult.data) {
|
|
20101
|
+
const pair = pairResult.data.find((p) => p.symbol === symbol);
|
|
20102
|
+
const raw = pair?.stepSize ?? pair?.minTradeQty;
|
|
20103
|
+
if (raw) {
|
|
20104
|
+
const parsed = parseFloat(raw);
|
|
20105
|
+
if (parsed > 0)
|
|
20106
|
+
stepSize = parsed > 1e10 ? parsed / 1e18 : parsed;
|
|
20107
|
+
}
|
|
20108
|
+
}
|
|
20109
|
+
}
|
|
20110
|
+
catch {
|
|
20111
|
+
// fall through with default stepSize
|
|
20112
|
+
}
|
|
20113
|
+
const stepped = Math.floor(qty / stepSize) * stepSize;
|
|
20114
|
+
const decimals = stepSize < 1 ? Math.ceil(-Math.log10(stepSize)) : 0;
|
|
20115
|
+
return parseFloat(stepped.toFixed(decimals));
|
|
20116
|
+
}
|
|
20117
|
+
// Helper: close one position with a reduce-only market order. Returns the API response.
|
|
20118
|
+
async function closeOnePosition(sdk, pos, percent, vault) {
|
|
20119
|
+
const symbol = pos.symbol;
|
|
20120
|
+
const sideStr = String(pos.side).toUpperCase();
|
|
20121
|
+
const closingSide = sideStr === "BUY" ? OrderSide.SELL : OrderSide.BUY;
|
|
20122
|
+
const totalQtyHuman = Number(pos.quantity) / 1e18;
|
|
20123
|
+
const wanted = totalQtyHuman * (percent / 100);
|
|
20124
|
+
const aligned = await alignToStepSize(sdk, symbol, wanted);
|
|
20125
|
+
if (aligned <= 0) {
|
|
20126
|
+
return {
|
|
20127
|
+
ok: false,
|
|
20128
|
+
message: `Closing ${percent}% of ${totalQtyHuman} ${symbol} would round to 0 at market step size — try a larger percentage.`,
|
|
20129
|
+
};
|
|
20130
|
+
}
|
|
20131
|
+
const perpId = await sdk.getPerpetualID(symbol);
|
|
20132
|
+
if (!perpId)
|
|
20133
|
+
return { ok: false, message: `PerpetualID not found for ${symbol}` };
|
|
20134
|
+
const leverageHuman = String(Number(pos.leverage) / 1e18);
|
|
20135
|
+
const params = {
|
|
20136
|
+
symbol,
|
|
20137
|
+
market: perpId,
|
|
20138
|
+
side: closingSide,
|
|
20139
|
+
orderType: OrderType.MARKET,
|
|
20140
|
+
quantity: String(aligned),
|
|
20141
|
+
leverage: leverageHuman,
|
|
20142
|
+
reduceOnly: true,
|
|
20143
|
+
};
|
|
20144
|
+
if (vault)
|
|
20145
|
+
params.creator = vault;
|
|
20146
|
+
const result = await sdk.placeOrder(params);
|
|
20147
|
+
if (!result.status)
|
|
20148
|
+
return { ok: false, message: result.error || "placeOrder failed" };
|
|
20149
|
+
return {
|
|
20150
|
+
ok: true,
|
|
20151
|
+
message: `Closed ${aligned} ${symbol} (${closingSide} reduce-only).`,
|
|
20152
|
+
data: result.data,
|
|
20153
|
+
};
|
|
20154
|
+
}
|
|
20155
|
+
position
|
|
20156
|
+
.command("close")
|
|
20157
|
+
.description("Close a position with a reduce-only market order (default: 100%)")
|
|
20158
|
+
.argument("<symbol>", "Trading pair (e.g. BTC or BTC-PERP)")
|
|
20159
|
+
.option("--percent <n>", "Percent of position to close (1-100, default: 100)", "100")
|
|
20160
|
+
.option("--vault <address>", "Target a vault's position instead of personal")
|
|
20161
|
+
.action(async (symbol, opts) => {
|
|
20162
|
+
try {
|
|
20163
|
+
symbol = normalizeSymbol(symbol);
|
|
20164
|
+
const percent = Number(opts.percent);
|
|
20165
|
+
if (!Number.isFinite(percent) || percent <= 0 || percent > 100) {
|
|
20166
|
+
return handleError(`--percent must be in (0, 100], got ${opts.percent}`);
|
|
20167
|
+
}
|
|
20168
|
+
const sdk = getSDK();
|
|
20169
|
+
const parentAddress = opts.vault || sdk.address;
|
|
20170
|
+
const result = await sdk.getPositions({ parentAddress, symbol });
|
|
20171
|
+
if (!result.status)
|
|
20172
|
+
return handleError(result.error);
|
|
20173
|
+
const pos = (result.data || []).find((p) => p.symbol === symbol);
|
|
20174
|
+
if (!pos)
|
|
20175
|
+
return handleError(`No open position for ${symbol}.`);
|
|
20176
|
+
const r = await closeOnePosition(sdk, pos, percent, opts.vault);
|
|
20177
|
+
if (!r.ok)
|
|
20178
|
+
return handleError(r.message);
|
|
20179
|
+
if (isJson(program))
|
|
20180
|
+
return printJson(r.data);
|
|
20181
|
+
console.log(r.message);
|
|
20182
|
+
}
|
|
20183
|
+
catch (e) {
|
|
20184
|
+
handleError(e);
|
|
20185
|
+
}
|
|
20186
|
+
});
|
|
20187
|
+
position
|
|
20188
|
+
.command("close-all")
|
|
20189
|
+
.description("Close every open position with reduce-only market orders")
|
|
20190
|
+
.option("--percent <n>", "Percent of each position to close (1-100, default: 100)", "100")
|
|
20191
|
+
.option("--vault <address>", "Target vault positions instead of personal")
|
|
20192
|
+
.action(async (opts) => {
|
|
20193
|
+
try {
|
|
20194
|
+
const percent = Number(opts.percent);
|
|
20195
|
+
if (!Number.isFinite(percent) || percent <= 0 || percent > 100) {
|
|
20196
|
+
return handleError(`--percent must be in (0, 100], got ${opts.percent}`);
|
|
20197
|
+
}
|
|
20198
|
+
const sdk = getSDK();
|
|
20199
|
+
const parentAddress = opts.vault || sdk.address;
|
|
20200
|
+
const result = await sdk.getPositions({ parentAddress });
|
|
20201
|
+
if (!result.status)
|
|
20202
|
+
return handleError(result.error);
|
|
20203
|
+
const positions = result.data || [];
|
|
20204
|
+
if (!positions.length) {
|
|
20205
|
+
if (isJson(program))
|
|
20206
|
+
return printJson({ closed: 0, results: [] });
|
|
20207
|
+
return console.log("No open positions to close.");
|
|
20208
|
+
}
|
|
20209
|
+
const results = [];
|
|
20210
|
+
for (const pos of positions) {
|
|
20211
|
+
const r = await closeOnePosition(sdk, pos, percent, opts.vault);
|
|
20212
|
+
results.push({ symbol: pos.symbol, ok: r.ok, message: r.message });
|
|
20213
|
+
if (!isJson(program))
|
|
20214
|
+
console.log(` ${pos.symbol}: ${r.ok ? "OK" : "FAILED"} — ${r.message}`);
|
|
20215
|
+
}
|
|
20216
|
+
if (isJson(program))
|
|
20217
|
+
printJson({ closed: results.filter((r) => r.ok).length, results });
|
|
20218
|
+
}
|
|
20219
|
+
catch (e) {
|
|
20220
|
+
handleError(e);
|
|
20221
|
+
}
|
|
20222
|
+
});
|
|
19924
20223
|
position
|
|
19925
20224
|
.command("tpsl")
|
|
19926
20225
|
.description("Place TP/SL orders on a position")
|
|
@@ -20128,6 +20427,48 @@ function registerMarketCommands(program) {
|
|
|
20128
20427
|
handleError(e);
|
|
20129
20428
|
}
|
|
20130
20429
|
});
|
|
20430
|
+
market
|
|
20431
|
+
.command("kline")
|
|
20432
|
+
.description("Get kline (candlestick) history for a symbol")
|
|
20433
|
+
.argument("<symbol>", "Trading pair")
|
|
20434
|
+
.option("--interval <i>", "Bar interval: 1m / 5m / 15m / 30m / 1h / 4h / 1d / 1w (default: 1h)", "1h")
|
|
20435
|
+
.option("--from <ts>", "Start unix timestamp in seconds (default: 24h ago)")
|
|
20436
|
+
.option("--to <ts>", "End unix timestamp in seconds (default: now)")
|
|
20437
|
+
.option("--count <n>", "Cap on number of bars returned (most recent first)")
|
|
20438
|
+
.action(async (symbol, opts) => {
|
|
20439
|
+
try {
|
|
20440
|
+
symbol = normalizeSymbol(symbol);
|
|
20441
|
+
const now = Math.floor(Date.now() / 1000);
|
|
20442
|
+
const from = opts.from !== undefined ? Number(opts.from) : now - 86400;
|
|
20443
|
+
const to = opts.to !== undefined ? Number(opts.to) : now;
|
|
20444
|
+
const countback = opts.count !== undefined ? Number(opts.count) : undefined;
|
|
20445
|
+
const sdk = getSDK();
|
|
20446
|
+
const result = await sdk.getKlineHistory({
|
|
20447
|
+
symbol,
|
|
20448
|
+
interval: String(opts.interval),
|
|
20449
|
+
from,
|
|
20450
|
+
to,
|
|
20451
|
+
countback,
|
|
20452
|
+
});
|
|
20453
|
+
if (!result.status || !result.data)
|
|
20454
|
+
return handleError(result.error);
|
|
20455
|
+
if (isJson(program))
|
|
20456
|
+
return printJson(result.data);
|
|
20457
|
+
if (!result.data.length)
|
|
20458
|
+
return console.log("No bars.");
|
|
20459
|
+
printTable(["Time (UTC)", "Open", "High", "Low", "Close", "Volume"], result.data.map((b) => [
|
|
20460
|
+
new Date(b.time * 1000).toISOString().replace("T", " ").slice(0, 16),
|
|
20461
|
+
b.open,
|
|
20462
|
+
b.high,
|
|
20463
|
+
b.low,
|
|
20464
|
+
b.close,
|
|
20465
|
+
b.volume,
|
|
20466
|
+
]));
|
|
20467
|
+
}
|
|
20468
|
+
catch (e) {
|
|
20469
|
+
handleError(e);
|
|
20470
|
+
}
|
|
20471
|
+
});
|
|
20131
20472
|
}
|
|
20132
20473
|
|
|
20133
20474
|
function registerHistoryCommands(program) {
|
|
@@ -20556,11 +20897,12 @@ function registerVaultCommands(program) {
|
|
|
20556
20897
|
});
|
|
20557
20898
|
// === vault fill ===
|
|
20558
20899
|
vault
|
|
20559
|
-
.command("fill")
|
|
20560
|
-
.
|
|
20900
|
+
.command("fill-withdrawals")
|
|
20901
|
+
.alias("fill")
|
|
20902
|
+
.description("Fill pending withdrawal requests (creator/operator only) — e.g. vault fill-withdrawals <vaultId> <requestIDs...>")
|
|
20561
20903
|
.argument("<vaultId>", "Vault object ID")
|
|
20562
20904
|
.argument("<requestIDs...>", "Withdrawal request object IDs")
|
|
20563
|
-
.option("--markets <ids>", "Comma-separated market PerpetualIDs
|
|
20905
|
+
.option("--markets <ids>", "Comma-separated market PerpetualIDs (deprecated; NAV now uses on-chain registry)")
|
|
20564
20906
|
.action(async (vaultId, requestIDs, opts) => {
|
|
20565
20907
|
try {
|
|
20566
20908
|
const sdk = getSDK();
|