dipcoin-cli 1.3.4 → 1.3.6
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 +4 -0
- package/dist/cli.cjs +241 -56
- package/dist/cli.cjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -181,6 +181,10 @@ dipcoin-cli position tpsl <symbol> --side <buy|sell> --quantity <q> --leverage <
|
|
|
181
181
|
dipcoin-cli position tpsl <symbol> --side sell --quantity 0.01 --leverage 10 \
|
|
182
182
|
--tp-trigger 105000 --tp-type limit --tp-price 105000
|
|
183
183
|
|
|
184
|
+
# Set or edit TP/SL on a vault position. Existing plan IDs are discovered automatically.
|
|
185
|
+
dipcoin-cli position tpsl BTC --side sell --quantity 0.01 --leverage 10 \
|
|
186
|
+
--vault <vaultId> --tp-trigger 105000 --sl-trigger 90000
|
|
187
|
+
|
|
184
188
|
# Close positions — CLI figures out side & qty
|
|
185
189
|
dipcoin-cli position close BTC # close 100% of BTC position
|
|
186
190
|
dipcoin-cli position close BTC --percent 50 # close half
|
package/dist/cli.cjs
CHANGED
|
@@ -12921,8 +12921,8 @@ async function signMessage(keypair, messageBytes) {
|
|
|
12921
12921
|
*/
|
|
12922
12922
|
function buildSignature(signature, isKeyPair = false) {
|
|
12923
12923
|
const signatureData = cryptography.parseSerializedSignature(signature);
|
|
12924
|
-
|
|
12925
|
-
|
|
12924
|
+
const signatureHex = buffer.Buffer.from(signatureData.signature).toString("hex");
|
|
12925
|
+
const publicKey = buffer.Buffer.from(signatureData.publicKey).toString("base64");
|
|
12926
12926
|
let flag = SignerTypes.KP_SECP256;
|
|
12927
12927
|
if (signatureData.signatureScheme === "Secp256k1" ||
|
|
12928
12928
|
signatureData.signatureScheme === "Secp256r1") {
|
|
@@ -13006,13 +13006,11 @@ function getModuleDir() {
|
|
|
13006
13006
|
return path__default["default"].dirname(__filename);
|
|
13007
13007
|
}
|
|
13008
13008
|
}
|
|
13009
|
-
catch
|
|
13009
|
+
catch {
|
|
13010
13010
|
// Fall through to CommonJS
|
|
13011
13011
|
}
|
|
13012
13012
|
// CommonJS: use __dirname (will be available after compilation)
|
|
13013
|
-
// @ts-ignore - __dirname may not be defined in ESM context
|
|
13014
13013
|
if (typeof __dirname !== "undefined") {
|
|
13015
|
-
// @ts-ignore
|
|
13016
13014
|
return __dirname;
|
|
13017
13015
|
}
|
|
13018
13016
|
// Fallback to process.cwd()
|
|
@@ -16457,8 +16455,12 @@ class HttpClient {
|
|
|
16457
16455
|
const proc = child_process.spawn("curl", args);
|
|
16458
16456
|
let stdout = "";
|
|
16459
16457
|
let stderr = "";
|
|
16460
|
-
proc.stdout.on("data", (chunk) => {
|
|
16461
|
-
|
|
16458
|
+
proc.stdout.on("data", (chunk) => {
|
|
16459
|
+
stdout += chunk;
|
|
16460
|
+
});
|
|
16461
|
+
proc.stderr.on("data", (chunk) => {
|
|
16462
|
+
stderr += chunk;
|
|
16463
|
+
});
|
|
16462
16464
|
proc.on("close", (code) => {
|
|
16463
16465
|
if (!stdout) {
|
|
16464
16466
|
return reject(new Error(stderr || `curl exited with code ${code}`));
|
|
@@ -16806,17 +16808,87 @@ function buildSetOraclePriceTx(deployment, args, tx, gasBudget, sender) {
|
|
|
16806
16808
|
return t;
|
|
16807
16809
|
}
|
|
16808
16810
|
|
|
16811
|
+
// Copyright (c) 2025 Dipcoin LLC
|
|
16812
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
16813
|
+
const SUI_COIN_TYPE = "0x2::sui::SUI";
|
|
16814
|
+
const STALE_GAS_RETRY_DELAY_MS = 1000;
|
|
16815
|
+
function sleep(ms) {
|
|
16816
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16817
|
+
}
|
|
16818
|
+
function getErrorMessage(error) {
|
|
16819
|
+
if (error instanceof Error)
|
|
16820
|
+
return error.message;
|
|
16821
|
+
return String(error);
|
|
16822
|
+
}
|
|
16823
|
+
function isStaleObjectVersionError(error) {
|
|
16824
|
+
const message = getErrorMessage(error);
|
|
16825
|
+
return (message.includes("Transaction needs to be rebuilt") ||
|
|
16826
|
+
message.includes("unavailable for consumption") ||
|
|
16827
|
+
message.includes("current version:"));
|
|
16828
|
+
}
|
|
16829
|
+
async function getFreshGasObjectRef(suiClient, owner) {
|
|
16830
|
+
const coins = [];
|
|
16831
|
+
let cursor;
|
|
16832
|
+
do {
|
|
16833
|
+
const page = await suiClient.getCoins({ owner, coinType: SUI_COIN_TYPE, cursor });
|
|
16834
|
+
coins.push(...page.data);
|
|
16835
|
+
cursor = page.hasNextPage ? page.nextCursor : null;
|
|
16836
|
+
} while (cursor);
|
|
16837
|
+
if (!coins.length) {
|
|
16838
|
+
throw new Error(`No SUI gas coins found for ${owner}`);
|
|
16839
|
+
}
|
|
16840
|
+
const gasCoin = coins.slice().sort((a, b) => {
|
|
16841
|
+
const aBalance = BigInt(a.balance);
|
|
16842
|
+
const bBalance = BigInt(b.balance);
|
|
16843
|
+
if (aBalance === bBalance)
|
|
16844
|
+
return 0;
|
|
16845
|
+
return aBalance < bBalance ? 1 : -1;
|
|
16846
|
+
})[0];
|
|
16847
|
+
const fresh = await suiClient.getObject({ id: gasCoin.coinObjectId });
|
|
16848
|
+
const ref = fresh.data;
|
|
16849
|
+
if (!ref?.objectId || !ref.version || !ref.digest) {
|
|
16850
|
+
throw new Error(`Failed to refresh gas coin object ref for ${gasCoin.coinObjectId}`);
|
|
16851
|
+
}
|
|
16852
|
+
return {
|
|
16853
|
+
objectId: ref.objectId,
|
|
16854
|
+
version: ref.version,
|
|
16855
|
+
digest: ref.digest,
|
|
16856
|
+
};
|
|
16857
|
+
}
|
|
16858
|
+
async function setFreshGasPayment(suiClient, tx, owner) {
|
|
16859
|
+
tx.setGasPayment([await getFreshGasObjectRef(suiClient, owner)]);
|
|
16860
|
+
}
|
|
16861
|
+
async function signAndExecuteTransactionWithFreshGas(suiClient, tx, signer, options) {
|
|
16862
|
+
const sender = signer.getPublicKey().toSuiAddress();
|
|
16863
|
+
tx.setSender(sender);
|
|
16864
|
+
await setFreshGasPayment(suiClient, tx, sender);
|
|
16865
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
16866
|
+
try {
|
|
16867
|
+
return await suiClient.signAndExecuteTransaction({
|
|
16868
|
+
signer,
|
|
16869
|
+
transaction: tx,
|
|
16870
|
+
options,
|
|
16871
|
+
});
|
|
16872
|
+
}
|
|
16873
|
+
catch (error) {
|
|
16874
|
+
if (attempt === 0 && isStaleObjectVersionError(error)) {
|
|
16875
|
+
await sleep(STALE_GAS_RETRY_DELAY_MS);
|
|
16876
|
+
await setFreshGasPayment(suiClient, tx, sender);
|
|
16877
|
+
continue;
|
|
16878
|
+
}
|
|
16879
|
+
throw error;
|
|
16880
|
+
}
|
|
16881
|
+
}
|
|
16882
|
+
throw new Error("Transaction failed after refreshing gas payment");
|
|
16883
|
+
}
|
|
16884
|
+
|
|
16809
16885
|
// Copyright (c) 2025 Dipcoin LLC
|
|
16810
16886
|
async function executeTxBlock(suiClient, tx, signer) {
|
|
16811
|
-
return suiClient
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
showEffects: true,
|
|
16817
|
-
showEvents: true,
|
|
16818
|
-
showInput: true,
|
|
16819
|
-
},
|
|
16887
|
+
return signAndExecuteTransactionWithFreshGas(suiClient, tx, signer, {
|
|
16888
|
+
showObjectChanges: true,
|
|
16889
|
+
showEffects: true,
|
|
16890
|
+
showEvents: true,
|
|
16891
|
+
showInput: true,
|
|
16820
16892
|
});
|
|
16821
16893
|
}
|
|
16822
16894
|
function getDeploymentPerpetualID(deployment, market) {
|
|
@@ -16825,7 +16897,8 @@ function getDeploymentPerpetualID(deployment, market) {
|
|
|
16825
16897
|
async function getOraclePrice(suiClient, deployment, market) {
|
|
16826
16898
|
const objId = getPriceOracleObjectId(deployment, market);
|
|
16827
16899
|
const obj = await suiClient.getObject({ id: objId, options: { showContent: true } });
|
|
16828
|
-
const fields = obj.data?.content?.fields?.price_info?.fields?.price_feed?.fields?.price
|
|
16900
|
+
const fields = obj.data?.content?.fields?.price_info?.fields?.price_feed?.fields?.price
|
|
16901
|
+
?.fields;
|
|
16829
16902
|
if (!fields)
|
|
16830
16903
|
throw new Error(`Failed to read oracle price for ${market}`);
|
|
16831
16904
|
return Number(fields.price.fields.magnitude) / Math.pow(10, Number(fields.expo.fields.magnitude));
|
|
@@ -17627,7 +17700,8 @@ class DipCoinPerpSDK {
|
|
|
17627
17700
|
}
|
|
17628
17701
|
// Use sub-account keypair for signing if available
|
|
17629
17702
|
const signingKeypair = this.subKeypair || this.keypair;
|
|
17630
|
-
const { symbol, market, side, isLong, leverage, quantity, reduceOnly = true, postOnly = false, orderbookOnly = true, ioc = false, tp, sl, } = params;
|
|
17703
|
+
const { symbol, market, creator, side, isLong, leverage, quantity, reduceOnly = true, postOnly = false, orderbookOnly = true, ioc = false, tp, sl, } = params;
|
|
17704
|
+
const orderCreator = creator || this.walletAddress;
|
|
17631
17705
|
if (!this.isPositiveNumber(quantity)) {
|
|
17632
17706
|
return {
|
|
17633
17707
|
status: false,
|
|
@@ -17651,7 +17725,7 @@ class DipCoinPerpSDK {
|
|
|
17651
17725
|
symbol,
|
|
17652
17726
|
side,
|
|
17653
17727
|
leverage: leverageWei,
|
|
17654
|
-
creator:
|
|
17728
|
+
creator: orderCreator,
|
|
17655
17729
|
};
|
|
17656
17730
|
const sendPlanCloseRequest = async (payload) => {
|
|
17657
17731
|
let response = await this.httpClient.post(API_ENDPOINTS.PLAN_CLOSE_ORDER, payload);
|
|
@@ -17681,7 +17755,7 @@ class DipCoinPerpSDK {
|
|
|
17681
17755
|
: new BigNumber__default["default"](0);
|
|
17682
17756
|
const tpOrder = {
|
|
17683
17757
|
market,
|
|
17684
|
-
creator:
|
|
17758
|
+
creator: orderCreator,
|
|
17685
17759
|
isLong: !isLong,
|
|
17686
17760
|
reduceOnly,
|
|
17687
17761
|
postOnly,
|
|
@@ -17726,7 +17800,7 @@ class DipCoinPerpSDK {
|
|
|
17726
17800
|
: new BigNumber__default["default"](0);
|
|
17727
17801
|
const slOrder = {
|
|
17728
17802
|
market,
|
|
17729
|
-
creator:
|
|
17803
|
+
creator: orderCreator,
|
|
17730
17804
|
isLong: !isLong,
|
|
17731
17805
|
reduceOnly,
|
|
17732
17806
|
postOnly,
|
|
@@ -17784,9 +17858,7 @@ class DipCoinPerpSDK {
|
|
|
17784
17858
|
return {
|
|
17785
17859
|
status: false,
|
|
17786
17860
|
data: results,
|
|
17787
|
-
error: results.tpResult?.message ||
|
|
17788
|
-
results.slResult?.message ||
|
|
17789
|
-
"Failed to place TP/SL order",
|
|
17861
|
+
error: results.tpResult?.message || results.slResult?.message || "Failed to place TP/SL order",
|
|
17790
17862
|
};
|
|
17791
17863
|
}
|
|
17792
17864
|
catch (error) {
|
|
@@ -17800,7 +17872,7 @@ class DipCoinPerpSDK {
|
|
|
17800
17872
|
/**
|
|
17801
17873
|
* Get TP/SL orders for a position
|
|
17802
17874
|
*/
|
|
17803
|
-
async getPositionTpSl(positionId, tpslType = "normal") {
|
|
17875
|
+
async getPositionTpSl(positionId, tpslType = "normal", parentAddress) {
|
|
17804
17876
|
try {
|
|
17805
17877
|
const authResult = await this.authenticate();
|
|
17806
17878
|
if (!authResult.status) {
|
|
@@ -17812,6 +17884,7 @@ class DipCoinPerpSDK {
|
|
|
17812
17884
|
const params = {
|
|
17813
17885
|
positionId,
|
|
17814
17886
|
tpslType,
|
|
17887
|
+
...(parentAddress ? { parentAddress } : {}),
|
|
17815
17888
|
};
|
|
17816
17889
|
let response = await this.httpClient.get(API_ENDPOINTS.GET_POSITION_TPSL, { params });
|
|
17817
17890
|
if (response.code === 1000) {
|
|
@@ -18355,12 +18428,8 @@ class DipCoinPerpSDK {
|
|
|
18355
18428
|
tpOrderPrice: toNormal(raw.tpOrderPrice),
|
|
18356
18429
|
slTriggerPrice: toNormal(raw.slTriggerPrice),
|
|
18357
18430
|
slOrderPrice: toNormal(raw.slOrderPrice),
|
|
18358
|
-
tpPlanId: planOrderType === "takeProfit"
|
|
18359
|
-
|
|
18360
|
-
: raw.tpPlanId ?? null,
|
|
18361
|
-
slPlanId: planOrderType === "stopLoss"
|
|
18362
|
-
? raw.slPlanId ?? raw.id ?? null
|
|
18363
|
-
: raw.slPlanId ?? null,
|
|
18431
|
+
tpPlanId: planOrderType === "takeProfit" ? raw.tpPlanId ?? raw.id ?? null : raw.tpPlanId ?? null,
|
|
18432
|
+
slPlanId: planOrderType === "stopLoss" ? raw.slPlanId ?? raw.id ?? null : raw.slPlanId ?? null,
|
|
18364
18433
|
tpslType: raw.tpslType,
|
|
18365
18434
|
createdAt: raw.createdAt,
|
|
18366
18435
|
updatedAt: raw.updatedAt,
|
|
@@ -18713,7 +18782,7 @@ class DipCoinPerpSDK {
|
|
|
18713
18782
|
* Build ExchangeOnChain call args for margin adjustments
|
|
18714
18783
|
*/
|
|
18715
18784
|
buildMarginCallArgs(params, action) {
|
|
18716
|
-
const { amount, accountAddress, symbol, market, perpId, subAccountsMapId, gasBudget, txHash
|
|
18785
|
+
const { amount, accountAddress, symbol, market, perpId, subAccountsMapId, gasBudget, txHash } = params;
|
|
18717
18786
|
if (!this.isPositiveNumber(amount)) {
|
|
18718
18787
|
throw new Error(`Amount must be greater than zero to ${action} margin`);
|
|
18719
18788
|
}
|
|
@@ -18869,7 +18938,8 @@ class DipCoinPerpSDK {
|
|
|
18869
18938
|
}
|
|
18870
18939
|
async buildMarginTransaction(params, action) {
|
|
18871
18940
|
const payload = this.buildMarginCallArgs(params, action);
|
|
18872
|
-
const signedMarket = payload.perpID ||
|
|
18941
|
+
const signedMarket = payload.perpID ||
|
|
18942
|
+
(payload.market ? this.resolvePerpIdFromDeployment(payload.market) : undefined);
|
|
18873
18943
|
if (!signedMarket) {
|
|
18874
18944
|
throw new Error("PerpetualID is required for signed margin payload");
|
|
18875
18945
|
}
|
|
@@ -19106,7 +19176,10 @@ class DipCoinPerpSDK {
|
|
|
19106
19176
|
if (!metadata) {
|
|
19107
19177
|
return { status: false, error: "Coin metadata not found" };
|
|
19108
19178
|
}
|
|
19109
|
-
return {
|
|
19179
|
+
return {
|
|
19180
|
+
status: true,
|
|
19181
|
+
data: { decimals: metadata.decimals, symbol: metadata.symbol, name: metadata.name },
|
|
19182
|
+
};
|
|
19110
19183
|
}
|
|
19111
19184
|
catch (error) {
|
|
19112
19185
|
return { status: false, error: formatError(error) };
|
|
@@ -19220,7 +19293,8 @@ class DipCoinPerpSDK {
|
|
|
19220
19293
|
// to catch up before submitting the PTB. Public RPC backends can disagree by
|
|
19221
19294
|
// a few seconds, so target a timestamp that is already ~10s old.
|
|
19222
19295
|
const start = Date.now();
|
|
19223
|
-
|
|
19296
|
+
const deadline = start + 180_000;
|
|
19297
|
+
while (Date.now() <= deadline) {
|
|
19224
19298
|
const clock = await this.suiClient.getObject({
|
|
19225
19299
|
id: "0x6",
|
|
19226
19300
|
options: { showContent: true },
|
|
@@ -19231,7 +19305,7 @@ class DipCoinPerpSDK {
|
|
|
19231
19305
|
const waitMs = timestamp - clockMs + 10_000;
|
|
19232
19306
|
if (waitMs <= 0)
|
|
19233
19307
|
return;
|
|
19234
|
-
if (Date.now()
|
|
19308
|
+
if (Date.now() + waitMs > deadline) {
|
|
19235
19309
|
throw new Error(`Signed price feed is too far ahead of Sui Clock (${Math.ceil(waitMs / 1000)}s). Try again later.`);
|
|
19236
19310
|
}
|
|
19237
19311
|
await this.sleep(Math.min(waitMs, 15_000));
|
|
@@ -19244,7 +19318,8 @@ class DipCoinPerpSDK {
|
|
|
19244
19318
|
const maxTimestamp = Math.max(...timestamps);
|
|
19245
19319
|
const minTimestamp = Math.min(...timestamps);
|
|
19246
19320
|
const start = Date.now();
|
|
19247
|
-
|
|
19321
|
+
const deadline = start + 180_000;
|
|
19322
|
+
while (Date.now() <= deadline) {
|
|
19248
19323
|
const clock = await this.suiClient.getObject({
|
|
19249
19324
|
id: "0x6",
|
|
19250
19325
|
options: { showContent: true },
|
|
@@ -19259,7 +19334,7 @@ class DipCoinPerpSDK {
|
|
|
19259
19334
|
}
|
|
19260
19335
|
return;
|
|
19261
19336
|
}
|
|
19262
|
-
if (Date.now()
|
|
19337
|
+
if (Date.now() + waitMs > deadline) {
|
|
19263
19338
|
throw new Error(`Signed price feed is too far ahead of Sui Clock (${Math.ceil(waitMs / 1000)}s). Try again later.`);
|
|
19264
19339
|
}
|
|
19265
19340
|
await this.sleep(Math.min(waitMs, 15_000));
|
|
@@ -19338,10 +19413,7 @@ class DipCoinPerpSDK {
|
|
|
19338
19413
|
await this.sleep(2_000 * (attempt + 1));
|
|
19339
19414
|
continue;
|
|
19340
19415
|
}
|
|
19341
|
-
return Array.from(new Map(entries.map((feed) => [
|
|
19342
|
-
Buffer.from(feed.payload).toString("hex"),
|
|
19343
|
-
feed,
|
|
19344
|
-
])).values());
|
|
19416
|
+
return Array.from(new Map(entries.map((feed) => [Buffer.from(feed.payload).toString("hex"), feed])).values());
|
|
19345
19417
|
}
|
|
19346
19418
|
throw lastClockError || new Error("Failed to fetch usable signed price feed chunks");
|
|
19347
19419
|
}
|
|
@@ -19444,11 +19516,9 @@ class DipCoinPerpSDK {
|
|
|
19444
19516
|
return { nav, tx: t };
|
|
19445
19517
|
}
|
|
19446
19518
|
async signAndExecuteVaultTx(tx) {
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
transaction: tx,
|
|
19451
|
-
options: { showEffects: true, showObjectChanges: true },
|
|
19519
|
+
return signAndExecuteTransactionWithFreshGas(this.suiClient, tx, this.keypair, {
|
|
19520
|
+
showEffects: true,
|
|
19521
|
+
showObjectChanges: true,
|
|
19452
19522
|
});
|
|
19453
19523
|
}
|
|
19454
19524
|
/**
|
|
@@ -20146,9 +20216,13 @@ function tryAutoBindReferral(sdk) {
|
|
|
20146
20216
|
try {
|
|
20147
20217
|
fs__default["default"].writeFileSync(markerPath, "");
|
|
20148
20218
|
}
|
|
20149
|
-
catch {
|
|
20219
|
+
catch {
|
|
20220
|
+
// Marker creation is best-effort; retrying referral binding is harmless.
|
|
20221
|
+
}
|
|
20150
20222
|
})
|
|
20151
|
-
.catch(() => {
|
|
20223
|
+
.catch(() => {
|
|
20224
|
+
// Auto-binding should never block normal CLI startup.
|
|
20225
|
+
});
|
|
20152
20226
|
}
|
|
20153
20227
|
/**
|
|
20154
20228
|
* Get or create the SDK instance (singleton).
|
|
@@ -20668,6 +20742,9 @@ function registerPositionCommands(program) {
|
|
|
20668
20742
|
.option("--sl-trigger <price>", "SL trigger price")
|
|
20669
20743
|
.option("--sl-type <type>", "SL order type: market or limit", "market")
|
|
20670
20744
|
.option("--sl-price <price>", "SL order price (for limit)")
|
|
20745
|
+
.option("--vault <address>", "Target a vault position (vault object ID)")
|
|
20746
|
+
.option("--tp-plan-id <id>", "Existing TP plan ID to edit")
|
|
20747
|
+
.option("--sl-plan-id <id>", "Existing SL plan ID to edit")
|
|
20671
20748
|
.action(async (symbol, opts) => {
|
|
20672
20749
|
try {
|
|
20673
20750
|
const sdk = getSDK();
|
|
@@ -20675,11 +20752,94 @@ function registerPositionCommands(program) {
|
|
|
20675
20752
|
const perpId = await sdk.getPerpetualID(symbol);
|
|
20676
20753
|
if (!perpId)
|
|
20677
20754
|
return handleError(`PerpetualID not found for ${symbol}`);
|
|
20678
|
-
const
|
|
20755
|
+
const sideInput = String(opts.side).toUpperCase();
|
|
20756
|
+
if (sideInput !== OrderSide.BUY && sideInput !== OrderSide.SELL) {
|
|
20757
|
+
return handleError(`--side must be buy or sell, got ${opts.side}`);
|
|
20758
|
+
}
|
|
20759
|
+
const side = sideInput;
|
|
20679
20760
|
const isLong = side === OrderSide.SELL;
|
|
20761
|
+
if (!opts.tpTrigger && !opts.slTrigger) {
|
|
20762
|
+
return handleError("At least one of --tp-trigger or --sl-trigger is required.");
|
|
20763
|
+
}
|
|
20764
|
+
if (opts.tpPlanId && !opts.tpTrigger) {
|
|
20765
|
+
return handleError("--tp-plan-id requires --tp-trigger.");
|
|
20766
|
+
}
|
|
20767
|
+
if (opts.slPlanId && !opts.slTrigger) {
|
|
20768
|
+
return handleError("--sl-plan-id requires --sl-trigger.");
|
|
20769
|
+
}
|
|
20770
|
+
let tpPlanId = opts.tpPlanId;
|
|
20771
|
+
let slPlanId = opts.slPlanId;
|
|
20772
|
+
const shouldFindTpPlan = Boolean(opts.tpTrigger && !tpPlanId);
|
|
20773
|
+
const shouldFindSlPlan = Boolean(opts.slTrigger && !slPlanId);
|
|
20774
|
+
const parentAddress = opts.vault || sdk.address;
|
|
20775
|
+
const positionsResult = await sdk.getPositions({ parentAddress, symbol });
|
|
20776
|
+
if (!positionsResult.status) {
|
|
20777
|
+
return handleError(positionsResult.error || "Failed to fetch position");
|
|
20778
|
+
}
|
|
20779
|
+
const matchingPositions = (positionsResult.data || []).filter((position) => position.symbol === symbol);
|
|
20780
|
+
if (matchingPositions.length !== 1) {
|
|
20781
|
+
return handleError(matchingPositions.length === 0
|
|
20782
|
+
? `No open position for ${symbol}.`
|
|
20783
|
+
: `Multiple positions found for ${symbol}; cannot choose one safely.`);
|
|
20784
|
+
}
|
|
20785
|
+
const activePosition = matchingPositions[0];
|
|
20786
|
+
const positionSide = String(activePosition.side).toUpperCase();
|
|
20787
|
+
const expectedClosingSide = positionSide === "BUY" || positionSide === "LONG"
|
|
20788
|
+
? OrderSide.SELL
|
|
20789
|
+
: positionSide === "SELL" || positionSide === "SHORT"
|
|
20790
|
+
? OrderSide.BUY
|
|
20791
|
+
: undefined;
|
|
20792
|
+
if (!expectedClosingSide) {
|
|
20793
|
+
return handleError(`Unsupported position side: ${activePosition.side}`);
|
|
20794
|
+
}
|
|
20795
|
+
if (side !== expectedClosingSide) {
|
|
20796
|
+
return handleError(`--side must be ${expectedClosingSide.toLowerCase()} to close a ${positionSide} position.`);
|
|
20797
|
+
}
|
|
20798
|
+
const requestedQuantity = new BigNumber__default["default"](opts.quantity);
|
|
20799
|
+
const positionQuantity = new BigNumber__default["default"](activePosition.quantity).shiftedBy(-18);
|
|
20800
|
+
if (!requestedQuantity.isFinite() ||
|
|
20801
|
+
requestedQuantity.lte(0) ||
|
|
20802
|
+
requestedQuantity.gt(positionQuantity)) {
|
|
20803
|
+
return handleError(`--quantity must be greater than 0 and no more than the position quantity (${positionQuantity.toFixed()}).`);
|
|
20804
|
+
}
|
|
20805
|
+
const requestedLeverage = new BigNumber__default["default"](opts.leverage);
|
|
20806
|
+
const positionLeverage = new BigNumber__default["default"](activePosition.leverage).shiftedBy(-18);
|
|
20807
|
+
if (!requestedLeverage.isFinite() || !requestedLeverage.eq(positionLeverage)) {
|
|
20808
|
+
return handleError(`--leverage must match the position leverage (${positionLeverage.toFixed()}).`);
|
|
20809
|
+
}
|
|
20810
|
+
// Editing a plan requires its existing ID. Resolve it automatically for the
|
|
20811
|
+
// position-wide TP/SL pair so repeating this command updates instead of duplicates.
|
|
20812
|
+
if (shouldFindTpPlan || shouldFindSlPlan) {
|
|
20813
|
+
const positionId = activePosition.id ?? activePosition.positionId;
|
|
20814
|
+
if (positionId === undefined || positionId === null) {
|
|
20815
|
+
return handleError(`Position ID missing for ${symbol}.`);
|
|
20816
|
+
}
|
|
20817
|
+
const plansResult = await sdk.getPositionTpSl(positionId, "position", opts.vault);
|
|
20818
|
+
if (!plansResult.status) {
|
|
20819
|
+
return handleError(plansResult.error || "Failed to fetch existing TP/SL orders");
|
|
20820
|
+
}
|
|
20821
|
+
const findExistingPlanId = (planOrderType) => {
|
|
20822
|
+
const matches = (plansResult.data || []).filter((order) => String(order.planOrderType).toLowerCase() === planOrderType.toLowerCase());
|
|
20823
|
+
if (matches.length > 1) {
|
|
20824
|
+
throw new Error(`Multiple ${planOrderType} plans found; use --${planOrderType === "takeProfit" ? "tp" : "sl"}-plan-id to choose one.`);
|
|
20825
|
+
}
|
|
20826
|
+
if (!matches.length)
|
|
20827
|
+
return undefined;
|
|
20828
|
+
const planId = planOrderType === "takeProfit" ? matches[0].tpPlanId : matches[0].slPlanId;
|
|
20829
|
+
if (planId === undefined || planId === null) {
|
|
20830
|
+
throw new Error(`${planOrderType} plan is missing its editable plan ID.`);
|
|
20831
|
+
}
|
|
20832
|
+
return planId;
|
|
20833
|
+
};
|
|
20834
|
+
if (shouldFindTpPlan)
|
|
20835
|
+
tpPlanId = findExistingPlanId("takeProfit");
|
|
20836
|
+
if (shouldFindSlPlan)
|
|
20837
|
+
slPlanId = findExistingPlanId("stopLoss");
|
|
20838
|
+
}
|
|
20680
20839
|
const params = {
|
|
20681
20840
|
symbol,
|
|
20682
20841
|
market: perpId,
|
|
20842
|
+
...(opts.vault ? { creator: opts.vault } : {}),
|
|
20683
20843
|
side,
|
|
20684
20844
|
isLong,
|
|
20685
20845
|
quantity: opts.quantity,
|
|
@@ -20690,6 +20850,7 @@ function registerPositionCommands(program) {
|
|
|
20690
20850
|
triggerPrice: opts.tpTrigger,
|
|
20691
20851
|
orderType: opts.tpType?.toUpperCase() === "LIMIT" ? OrderType.LIMIT : OrderType.MARKET,
|
|
20692
20852
|
...(opts.tpPrice ? { orderPrice: opts.tpPrice } : {}),
|
|
20853
|
+
...(tpPlanId !== undefined ? { planId: tpPlanId } : {}),
|
|
20693
20854
|
tpslType: "position",
|
|
20694
20855
|
};
|
|
20695
20856
|
}
|
|
@@ -20698,6 +20859,7 @@ function registerPositionCommands(program) {
|
|
|
20698
20859
|
triggerPrice: opts.slTrigger,
|
|
20699
20860
|
orderType: opts.slType?.toUpperCase() === "LIMIT" ? OrderType.LIMIT : OrderType.MARKET,
|
|
20700
20861
|
...(opts.slPrice ? { orderPrice: opts.slPrice } : {}),
|
|
20862
|
+
...(slPlanId !== undefined ? { planId: slPlanId } : {}),
|
|
20701
20863
|
tpslType: "position",
|
|
20702
20864
|
};
|
|
20703
20865
|
}
|
|
@@ -20831,7 +20993,10 @@ function registerMarketCommands(program) {
|
|
|
20831
20993
|
const ob = result.data;
|
|
20832
20994
|
console.log(`\n Order Book: ${symbol}`);
|
|
20833
20995
|
console.log(" --- Asks (sell) ---");
|
|
20834
|
-
ob.asks
|
|
20996
|
+
ob.asks
|
|
20997
|
+
.slice(0, 10)
|
|
20998
|
+
.reverse()
|
|
20999
|
+
.forEach((a) => {
|
|
20835
21000
|
console.log(` ${formatWei(a.price).padStart(14)} | ${formatWei(a.quantity)}`);
|
|
20836
21001
|
});
|
|
20837
21002
|
console.log(" --- Bids (buy) ---");
|
|
@@ -21138,7 +21303,9 @@ function registerVaultCommands(program) {
|
|
|
21138
21303
|
return handleError(result.error);
|
|
21139
21304
|
let vaults = result.data || [];
|
|
21140
21305
|
if (!vaults.length)
|
|
21141
|
-
return isJson(program)
|
|
21306
|
+
return isJson(program)
|
|
21307
|
+
? printJson({ records: [], total: 0, pageNum: 1, pageSize: 10 })
|
|
21308
|
+
: console.log("No vaults found.");
|
|
21142
21309
|
// Client-side filter
|
|
21143
21310
|
const filter = opts.filter;
|
|
21144
21311
|
if (filter && filter !== "All") {
|
|
@@ -21166,7 +21333,16 @@ function registerVaultCommands(program) {
|
|
|
21166
21333
|
return printJson({ records: pageVaults, total, pageNum, pageSize, totalPages });
|
|
21167
21334
|
if (!pageVaults.length)
|
|
21168
21335
|
return console.log(`No vaults on page ${pageNum}. Total: ${total}, pages: ${totalPages}.`);
|
|
21169
|
-
printTable([
|
|
21336
|
+
printTable([
|
|
21337
|
+
"Name",
|
|
21338
|
+
"Vault ID",
|
|
21339
|
+
"TVL (USDC)",
|
|
21340
|
+
"APR (%)",
|
|
21341
|
+
"Depositors",
|
|
21342
|
+
"Age (d)",
|
|
21343
|
+
"Deposit",
|
|
21344
|
+
"Status",
|
|
21345
|
+
], pageVaults.map((v) => {
|
|
21170
21346
|
const tvl = new BigNumber__default["default"](v.tvl || "0").dividedBy(new BigNumber__default["default"](10).pow(18)).toFixed(2);
|
|
21171
21347
|
const apr = new BigNumber__default["default"](v.apr || "0").dividedBy(new BigNumber__default["default"](10).pow(14)).toFixed(2);
|
|
21172
21348
|
const depositors = new BigNumber__default["default"](v.currentDepositors || "0")
|
|
@@ -21208,7 +21384,10 @@ function registerVaultCommands(program) {
|
|
|
21208
21384
|
["Name", d.name || "-"],
|
|
21209
21385
|
["Creator", d.creator || "-"],
|
|
21210
21386
|
["Trader", d.trader || "-"],
|
|
21211
|
-
[
|
|
21387
|
+
[
|
|
21388
|
+
"Deposit Status",
|
|
21389
|
+
d.deposit_status === true || d.deposit_status === "true" ? "Open" : "Closed",
|
|
21390
|
+
],
|
|
21212
21391
|
["Total Shares", formatWeiValue(d.total_shares)],
|
|
21213
21392
|
["Max Cap", formatWeiValue(d.max_cap)],
|
|
21214
21393
|
["Min Deposit", formatWeiValue(d.min_deposit_amount)],
|
|
@@ -21480,7 +21659,10 @@ function registerVaultCommands(program) {
|
|
|
21480
21659
|
.action(async (vaultId, amount) => {
|
|
21481
21660
|
try {
|
|
21482
21661
|
const sdk = getSDK();
|
|
21483
|
-
const tx = await sdk.setVaultMinDepositAmount({
|
|
21662
|
+
const tx = await sdk.setVaultMinDepositAmount({
|
|
21663
|
+
vaultID: vaultId,
|
|
21664
|
+
minDepositAmount: parseAmount(amount),
|
|
21665
|
+
});
|
|
21484
21666
|
printTxResult(program, tx, `Min deposit set to ${amount} USDC.`);
|
|
21485
21667
|
}
|
|
21486
21668
|
catch (e) {
|
|
@@ -21497,7 +21679,10 @@ function registerVaultCommands(program) {
|
|
|
21497
21679
|
try {
|
|
21498
21680
|
const sdk = getSDK();
|
|
21499
21681
|
const enabled = !opts.disable;
|
|
21500
|
-
const tx = await sdk.setVaultAutoCloseOnWithdraw({
|
|
21682
|
+
const tx = await sdk.setVaultAutoCloseOnWithdraw({
|
|
21683
|
+
vaultID: vaultId,
|
|
21684
|
+
autoCloseOnWithdraw: enabled,
|
|
21685
|
+
});
|
|
21501
21686
|
printTxResult(program, tx, `Auto-close on withdraw ${enabled ? "enabled" : "disabled"}.`);
|
|
21502
21687
|
}
|
|
21503
21688
|
catch (e) {
|