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 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
- # Margin operations (on-chain)
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...> [--markets <ids>]
201
- dipcoin-cli vault close <vaultId> [--markets <ids>]
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+ uses add_margin_v3/remove_margin_v3 which read &PriceFeed (a shared object
18624
- // maintained off-band by an operator). No Pyth update is needed on the user-side tx.
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 is a single shared object maintained by an operator off-band.
18939
- // new_vault_nav populates `perpetuals` from ProtocolConfig.perpetual_registry, and
18940
- // is_nav_filled requires EVERY registered perpetual to have a position value entry.
18941
- // So we must compute against the on-chain registry, not the local deployment.markets
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
- .description("Fill pending withdrawal requests (e.g. vault fill <vaultId> <requestIDs...>)")
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 for share price update")
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();