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...>
|
|
201
|
-
dipcoin-cli vault close <vaultId>
|
|
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::
|
|
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(
|
|
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::
|
|
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(
|
|
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
|
-
|
|
18628
|
-
|
|
18629
|
-
|
|
18630
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18921
|
-
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
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
|
|
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::
|
|
18970
|
-
arguments: [nav, t.object("0x6"), t.object(perpId), t.object(
|
|
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
|
-
.
|
|
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({
|
|
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
|
-
.
|
|
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({
|
|
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
|
-
.
|
|
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
|
|
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();
|