dipcoin-cli 1.2.2 → 1.3.1

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,19 @@ 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
+ # Margin operations (on-chain) — auto-prepends a fresh signed price update
176
185
  dipcoin-cli position margin add <symbol> <amount>
177
186
  dipcoin-cli position margin remove <symbol> <amount>
187
+
188
+ # Same, but target a vault's position
189
+ dipcoin-cli position margin add <symbol> <amount> --vault <vaultId>
190
+ dipcoin-cli position margin remove <symbol> <amount> --vault <vaultId>
178
191
  ```
179
192
 
180
193
  The `--side` is the **closing side**: use `sell` for long positions, `buy` for short.
181
194
 
195
+ > 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.
196
+
182
197
  #### vault
183
198
 
184
199
  On-chain vault operations (DipCoin vault contracts):
@@ -197,9 +212,9 @@ dipcoin-cli vault position <vaultId> --address <addr> # Query another address
197
212
  dipcoin-cli vault deposit <vaultId> <amount>
198
213
  dipcoin-cli vault withdraw <vaultId> <shares>
199
214
  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>
215
+ dipcoin-cli vault fill-withdrawals <vaultId> <requestIDs...> # creator/operator only
216
+ dipcoin-cli vault close <vaultId>
217
+ dipcoin-cli vault remove <vaultId> # operator-only (normal creators get "unauthorized")
203
218
  dipcoin-cli vault claim <vaultId>
204
219
  dipcoin-cli vault set-trader <vaultId> <address>
205
220
  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
@@ -16596,6 +16602,13 @@ function getPerpetualId(deployment, market) {
16596
16602
  throw new Error(`Deployment config missing Perpetual id for ${m}`);
16597
16603
  return id;
16598
16604
  }
16605
+ function getPriceFeedId(deployment) {
16606
+ const id = deployment?.objects?.PriceFeed?.id;
16607
+ if (!id) {
16608
+ throw new Error("Deployment config missing PriceFeed id. Add objects.PriceFeed to the network's main_contract.json — required for v6+ on-chain margin/vault writes.");
16609
+ }
16610
+ return id;
16611
+ }
16599
16612
  function getPriceOracleObjectId(deployment, market) {
16600
16613
  const m = market?.toUpperCase() || "ETH-PERP";
16601
16614
  const id = deployment?.markets?.[m]?.Objects?.PriceInfoObject?.id;
@@ -16641,7 +16654,7 @@ function buildAddMarginTx(deployment, args, tx, gasBudget, sender) {
16641
16654
  t.setSender(sender);
16642
16655
  const packageId = getPackageId(deployment);
16643
16656
  t.moveCall({
16644
- target: `${packageId}::exchange::add_margin_v2`,
16657
+ target: `${packageId}::exchange::add_margin_v3`,
16645
16658
  arguments: [
16646
16659
  t.object(getProtocolConfigId(deployment)),
16647
16660
  t.object(CLOCK_OBJECT_ID),
@@ -16649,7 +16662,7 @@ function buildAddMarginTx(deployment, args, tx, gasBudget, sender) {
16649
16662
  t.object(getBankId(deployment)),
16650
16663
  t.object(args.subAccountsMapID || getSubAccountsId(deployment)),
16651
16664
  t.object(getTxIndexerId(deployment)),
16652
- t.object(getPriceOracleObjectId(deployment, args.market)),
16665
+ t.object(getPriceFeedId(deployment)),
16653
16666
  t.pure.address(args.account || sender || ""),
16654
16667
  t.pure.u128(args.amount.toString()),
16655
16668
  ],
@@ -16665,7 +16678,7 @@ function buildRemoveMarginTx(deployment, args, tx, gasBudget, sender) {
16665
16678
  t.setSender(sender);
16666
16679
  const packageId = getPackageId(deployment);
16667
16680
  t.moveCall({
16668
- target: `${packageId}::exchange::remove_margin_v2`,
16681
+ target: `${packageId}::exchange::remove_margin_v3`,
16669
16682
  arguments: [
16670
16683
  t.object(getProtocolConfigId(deployment)),
16671
16684
  t.object(CLOCK_OBJECT_ID),
@@ -16673,7 +16686,7 @@ function buildRemoveMarginTx(deployment, args, tx, gasBudget, sender) {
16673
16686
  t.object(getBankId(deployment)),
16674
16687
  t.object(args.subAccountsMapID || getSubAccountsId(deployment)),
16675
16688
  t.object(getTxIndexerId(deployment)),
16676
- t.object(getPriceOracleObjectId(deployment, args.market)),
16689
+ t.object(getPriceFeedId(deployment)),
16677
16690
  t.pure.address(args.account || sender || ""),
16678
16691
  t.pure.u128(args.amount.toString()),
16679
16692
  ],
@@ -16744,17 +16757,6 @@ function buildSetOraclePriceTx(deployment, args, tx, gasBudget, sender) {
16744
16757
  });
16745
16758
  return t;
16746
16759
  }
16747
- function buildBatchSetOraclePriceTx(deployment, args, tx, gasBudget, sender) {
16748
- let t = tx || new transactions.Transaction();
16749
- for (const priceArgs of args.prices) {
16750
- t = buildSetOraclePriceTx(deployment, priceArgs, t, undefined, undefined);
16751
- }
16752
- if (gasBudget)
16753
- t.setGasBudget(gasBudget);
16754
- if (sender)
16755
- t.setSender(sender);
16756
- return t;
16757
- }
16758
16760
 
16759
16761
  // Copyright (c) 2025 Dipcoin LLC
16760
16762
  async function executeTxBlock(suiClient, tx, signer) {
@@ -18562,6 +18564,77 @@ class DipCoinPerpSDK {
18562
18564
  return { status: false, error: formatError(error) };
18563
18565
  }
18564
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
+ }
18565
18638
  /**
18566
18639
  * Add isolated margin to an existing position (on-chain)
18567
18640
  * @param params Margin adjustment parameters
@@ -18624,10 +18697,14 @@ class DipCoinPerpSDK {
18624
18697
  }
18625
18698
  async buildMarginTransaction(params, action) {
18626
18699
  const payload = this.buildMarginCallArgs(params, action);
18627
- const updatePriceTx = payload.market
18628
- ? await this.buildUpdatePriceTransaction(payload.market)
18629
- : undefined;
18630
- const baseTx = updatePriceTx || new transactions.Transaction();
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]);
18706
+ const baseTx = new transactions.Transaction();
18707
+ this.appendPriceFeedUpdates(baseTx, Array.from(feeds.values()));
18631
18708
  if (action === "add") {
18632
18709
  return buildAddMarginTx(this.deploymentConfig, payload, baseTx, params.gasBudget);
18633
18710
  }
@@ -18913,45 +18990,101 @@ class DipCoinPerpSDK {
18913
18990
  throw new Error(`Deployment config missing PriceInfoObject id for ${symbol}`);
18914
18991
  return id;
18915
18992
  }
18916
- async buildVaultNavTransaction(vaultID, markets, tx) {
18993
+ getPriceFeedId() {
18994
+ const id = this.deploymentConfig?.objects?.PriceFeed?.id;
18995
+ if (!id) {
18996
+ throw new Error("Deployment config missing PriceFeed id. Add objects.PriceFeed to the network's main_contract.json — required for v6+ vault/margin writes.");
18997
+ }
18998
+ return id;
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
+ }
19057
+ async getOnChainPerpetualIds() {
19058
+ const pcId = this.getDeploymentProtocolConfigId();
19059
+ const df = await this.suiClient.getDynamicFieldObject({
19060
+ parentId: pcId,
19061
+ name: {
19062
+ type: "vector<u8>",
19063
+ value: Array.from(Buffer.from("perpetual_registry", "utf8")),
19064
+ },
19065
+ });
19066
+ const content = df.data?.content;
19067
+ const contents = content?.fields?.value?.fields?.perpetuals?.fields?.contents;
19068
+ if (!Array.isArray(contents)) {
19069
+ throw new Error("Could not read perpetual_registry from on-chain ProtocolConfig. Vault NAV requires the full list of perpetuals registered in the protocol.");
19070
+ }
19071
+ return contents;
19072
+ }
19073
+ async buildVaultNavTransaction(vaultID, _markets, tx) {
18917
19074
  const t = tx || new transactions.Transaction();
18918
19075
  const vaultPkg = this.getVaultPackageId();
18919
19076
  const currencyType = this.getCurrencyType();
18920
- const symbols = markets && markets.length > 0 ? markets : this.getMarketSymbols();
18921
- if (this.options.network === "mainnet") {
18922
- // On mainnet, use Pyth to fetch fresh price VAAs and update on-chain oracles
18923
- this.ensurePythClients();
18924
- if (this.priceServiceConnection && this.pythClient) {
18925
- const priceIds = [];
18926
- for (const sym of symbols) {
18927
- const feedId = await this.resolvePriceFeedId(sym);
18928
- if (feedId)
18929
- priceIds.push(feedId);
18930
- }
18931
- if (priceIds.length > 0) {
18932
- const priceUpdateData = await this.priceServiceConnection.getPriceFeedsUpdateData(priceIds);
18933
- await this.pythClient.updatePriceFeeds(t, priceUpdateData, priceIds);
18934
- }
18935
- }
18936
- }
18937
- else {
18938
- // On testnet, prepend price oracle updates so PriceInfoObjects are fresh
18939
- const prices = [];
18940
- for (const sym of symbols) {
18941
- try {
18942
- const oraclePrice = await getOraclePrice(this.suiClient, this.deploymentConfig, sym);
18943
- if (oraclePrice !== undefined && oraclePrice !== null) {
18944
- prices.push({ price: Number(oraclePrice), market: sym });
18945
- }
18946
- }
18947
- catch {
18948
- // skip symbols where oracle price is unavailable
18949
- }
18950
- }
18951
- if (prices.length > 0) {
18952
- buildBatchSetOraclePriceTx(this.deploymentConfig, { prices }, t);
18953
- }
18954
- }
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).
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);
19087
+ const perpetualIds = await this.getOnChainPerpetualIds();
18955
19088
  const [nav] = t.moveCall({
18956
19089
  target: `${vaultPkg}::vault::new_vault_nav`,
18957
19090
  arguments: [
@@ -18962,12 +19095,10 @@ class DipCoinPerpSDK {
18962
19095
  ],
18963
19096
  typeArguments: [currencyType],
18964
19097
  });
18965
- for (const sym of symbols) {
18966
- const perpId = this.getVaultPerpetualId(sym);
18967
- const priceInfoId = this.getVaultPriceInfoObjectId(sym);
19098
+ for (const perpId of perpetualIds) {
18968
19099
  t.moveCall({
18969
- target: `${vaultPkg}::vault::compute_perpetual_position_value`,
18970
- arguments: [nav, t.object("0x6"), t.object(perpId), t.object(priceInfoId)],
19100
+ target: `${vaultPkg}::vault::compute_perpetual_position_value_v2`,
19101
+ arguments: [nav, t.object("0x6"), t.object(perpId), t.object(priceFeedId)],
18971
19102
  typeArguments: [],
18972
19103
  });
18973
19104
  }
@@ -19833,6 +19964,34 @@ function registerTradeCommands(program) {
19833
19964
  .argument("<symbol>", "Trading pair (e.g. BTC or BTC-PERP)")
19834
19965
  .argument("<amount>", "USDC margin amount (e.g. 100 or 100USDC)")
19835
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
+ });
19836
19995
  trade
19837
19996
  .command("cancel")
19838
19997
  .description("Cancel orders by hash")
@@ -19996,11 +20155,16 @@ function registerPositionCommands(program) {
19996
20155
  .description("Add margin to position")
19997
20156
  .argument("<symbol>", "Trading pair")
19998
20157
  .argument("<amount>", "Amount in USDC")
19999
- .action(async (symbol, amount) => {
20158
+ .option("--vault <address>", "Target a vault's position (vault id) instead of personal account")
20159
+ .action(async (symbol, amount, opts) => {
20000
20160
  try {
20001
20161
  const sdk = getSDK();
20002
20162
  symbol = normalizeSymbol(symbol);
20003
- const tx = await sdk.addMargin({ symbol, amount: parseAmount(amount) });
20163
+ const tx = await sdk.addMargin({
20164
+ symbol,
20165
+ amount: parseAmount(amount),
20166
+ accountAddress: opts.vault,
20167
+ });
20004
20168
  if (isJson(program))
20005
20169
  return printJson({ digest: tx?.digest, status: "ok" });
20006
20170
  console.log(`Added ${amount} margin to ${symbol}. Tx: ${tx?.digest || JSON.stringify(tx)}`);
@@ -20014,11 +20178,16 @@ function registerPositionCommands(program) {
20014
20178
  .description("Remove margin from position")
20015
20179
  .argument("<symbol>", "Trading pair")
20016
20180
  .argument("<amount>", "Amount in USDC")
20017
- .action(async (symbol, amount) => {
20181
+ .option("--vault <address>", "Target a vault's position (vault id) instead of personal account")
20182
+ .action(async (symbol, amount, opts) => {
20018
20183
  try {
20019
20184
  const sdk = getSDK();
20020
20185
  symbol = normalizeSymbol(symbol);
20021
- const tx = await sdk.removeMargin({ symbol, amount: parseAmount(amount) });
20186
+ const tx = await sdk.removeMargin({
20187
+ symbol,
20188
+ amount: parseAmount(amount),
20189
+ accountAddress: opts.vault,
20190
+ });
20022
20191
  if (isJson(program))
20023
20192
  return printJson({ digest: tx?.digest, status: "ok" });
20024
20193
  console.log(`Removed ${amount} margin from ${symbol}. Tx: ${tx?.digest || JSON.stringify(tx)}`);
@@ -20130,6 +20299,48 @@ function registerMarketCommands(program) {
20130
20299
  handleError(e);
20131
20300
  }
20132
20301
  });
20302
+ market
20303
+ .command("kline")
20304
+ .description("Get kline (candlestick) history for a symbol")
20305
+ .argument("<symbol>", "Trading pair")
20306
+ .option("--interval <i>", "Bar interval: 1m / 5m / 15m / 30m / 1h / 4h / 1d / 1w (default: 1h)", "1h")
20307
+ .option("--from <ts>", "Start unix timestamp in seconds (default: 24h ago)")
20308
+ .option("--to <ts>", "End unix timestamp in seconds (default: now)")
20309
+ .option("--count <n>", "Cap on number of bars returned (most recent first)")
20310
+ .action(async (symbol, opts) => {
20311
+ try {
20312
+ symbol = normalizeSymbol(symbol);
20313
+ const now = Math.floor(Date.now() / 1000);
20314
+ const from = opts.from !== undefined ? Number(opts.from) : now - 86400;
20315
+ const to = opts.to !== undefined ? Number(opts.to) : now;
20316
+ const countback = opts.count !== undefined ? Number(opts.count) : undefined;
20317
+ const sdk = getSDK();
20318
+ const result = await sdk.getKlineHistory({
20319
+ symbol,
20320
+ interval: String(opts.interval),
20321
+ from,
20322
+ to,
20323
+ countback,
20324
+ });
20325
+ if (!result.status || !result.data)
20326
+ return handleError(result.error);
20327
+ if (isJson(program))
20328
+ return printJson(result.data);
20329
+ if (!result.data.length)
20330
+ return console.log("No bars.");
20331
+ printTable(["Time (UTC)", "Open", "High", "Low", "Close", "Volume"], result.data.map((b) => [
20332
+ new Date(b.time * 1000).toISOString().replace("T", " ").slice(0, 16),
20333
+ b.open,
20334
+ b.high,
20335
+ b.low,
20336
+ b.close,
20337
+ b.volume,
20338
+ ]));
20339
+ }
20340
+ catch (e) {
20341
+ handleError(e);
20342
+ }
20343
+ });
20133
20344
  }
20134
20345
 
20135
20346
  function registerHistoryCommands(program) {
@@ -20558,11 +20769,12 @@ function registerVaultCommands(program) {
20558
20769
  });
20559
20770
  // === vault fill ===
20560
20771
  vault
20561
- .command("fill")
20562
- .description("Fill pending withdrawal requests (e.g. vault fill <vaultId> <requestIDs...>)")
20772
+ .command("fill-withdrawals")
20773
+ .alias("fill")
20774
+ .description("Fill pending withdrawal requests (creator/operator only) — e.g. vault fill-withdrawals <vaultId> <requestIDs...>")
20563
20775
  .argument("<vaultId>", "Vault object ID")
20564
20776
  .argument("<requestIDs...>", "Withdrawal request object IDs")
20565
- .option("--markets <ids>", "Comma-separated market PerpetualIDs for share price update")
20777
+ .option("--markets <ids>", "Comma-separated market PerpetualIDs (deprecated; NAV now uses on-chain registry)")
20566
20778
  .action(async (vaultId, requestIDs, opts) => {
20567
20779
  try {
20568
20780
  const sdk = getSDK();