dipcoin-cli 1.2.1 → 1.3.0

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
@@ -29,7 +29,7 @@ dipcoin-cli setup import # mainnet (default)
29
29
  dipcoin-cli setup import --network testnet
30
30
  ```
31
31
 
32
- You'll be prompted for your `suiprivkey1...` private key with hidden input.
32
+ Paste either your `suiprivkey1...` private key **or** a 12-word (or 15/18/21/24) BIP39 mnemonic at the hidden prompt. The input is auto-detected; mnemonics are validated against the BIP39 wordlist and checksum.
33
33
 
34
34
  **Or generate a new wallet:**
35
35
 
@@ -46,10 +46,11 @@ The generated `~/.config/dipcoin/env` contains:
46
46
 
47
47
  | Variable | Required | Description |
48
48
  |----------|----------|-------------|
49
- | `DIPCOIN_PRIVATE_KEY` | Yes | Sui private key (`suiprivkey1...`), supports ED25519/Secp256k1/Secp256r1 |
49
+ | `DIPCOIN_PRIVATE_KEY` | One of these | Sui private key (`suiprivkey1...`), supports ED25519/Secp256k1/Secp256r1 |
50
+ | `DIPCOIN_MNEMONIC` | is required | 12/15/18/21/24-word BIP39 mnemonic |
50
51
  | `DIPCOIN_NETWORK` | No | `mainnet` or `testnet` (default: `mainnet`) |
51
52
 
52
- Manual editing is supported but discouraged — prefer the `setup` commands so format and permissions stay correct.
53
+ If both are set, `DIPCOIN_PRIVATE_KEY` takes precedence. Manual editing is supported but discouraged — prefer the `setup` commands so format and permissions stay correct.
53
54
 
54
55
  ### Quick Start
55
56
 
package/dist/cli.cjs CHANGED
@@ -7,6 +7,8 @@ var path = require('path');
7
7
  var os = require('os');
8
8
  var fs = require('fs');
9
9
  var ed25519 = require('@mysten/sui/keypairs/ed25519');
10
+ var bip39 = require('@scure/bip39');
11
+ var cryptography = require('@mysten/sui/cryptography');
10
12
  var utils = require('@mysten/sui/utils');
11
13
  var BigNumber = require('bignumber.js');
12
14
  var buffer = require('buffer');
@@ -15,6 +17,7 @@ var transactions = require('@mysten/sui/transactions');
15
17
  var pythSuiJs = require('@pythnetwork/pyth-sui-js');
16
18
  var child_process = require('child_process');
17
19
  var Table = require('cli-table3');
20
+ var english_js = require('@scure/bip39/wordlists/english.js');
18
21
  var readline = require('readline');
19
22
 
20
23
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -195,16 +198,6 @@ function toBytes(data) {
195
198
  abytes$1(data);
196
199
  return data;
197
200
  }
198
- /**
199
- * Helper for KDFs: consumes uint8array or string.
200
- * When string is passed, does utf8 decoding, using TextDecoder.
201
- */
202
- function kdfInputToBytes(data) {
203
- if (typeof data === 'string')
204
- data = utf8ToBytes(data);
205
- abytes$1(data);
206
- return data;
207
- }
208
201
  /** Copies several Uint8Arrays into one. */
209
202
  function concatBytes(...arrays) {
210
203
  let sum = 0;
@@ -221,12 +214,6 @@ function concatBytes(...arrays) {
221
214
  }
222
215
  return res;
223
216
  }
224
- function checkOpts(defaults, opts) {
225
- if (opts !== undefined && {}.toString.call(opts) !== '[object Object]')
226
- throw new Error('options should be object or undefined');
227
- const merged = Object.assign(defaults, opts);
228
- return merged;
229
- }
230
217
  /** For runtime check if class implements interface */
231
218
  class Hash {
232
219
  }
@@ -3785,7 +3772,7 @@ function padding(bits, chr = '=') {
3785
3772
  /**
3786
3773
  * @__NO_SIDE_EFFECTS__
3787
3774
  */
3788
- function normalize$1(fn) {
3775
+ function normalize(fn) {
3789
3776
  afn(fn);
3790
3777
  return { encode: (from) => from, decode: (to) => fn(to) };
3791
3778
  }
@@ -4040,7 +4027,7 @@ chain(radix2(5), alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'), join$1(''));
4040
4027
  * // => Uint8Array.from([0x12, 0xab])
4041
4028
  * ```
4042
4029
  */
4043
- chain(radix2(5), alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'), join$1(''), normalize$1((s) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1')));
4030
+ chain(radix2(5), alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'), join$1(''), normalize((s) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1')));
4044
4031
  // Built-in base64 conversion https://caniuse.com/mdn-javascript_builtins_uint8array_frombase64
4045
4032
  // prettier-ignore
4046
4033
  const hasBase64Builtin = /* @__PURE__ */ (() => typeof Uint8Array.from([]).toBase64 === 'function' &&
@@ -4265,7 +4252,7 @@ const hexBuiltin = {
4265
4252
  */
4266
4253
  hasHexBuiltin
4267
4254
  ? hexBuiltin
4268
- : chain(radix2(4), alphabet('0123456789abcdef'), join$1(''), normalize$1((s) => {
4255
+ : chain(radix2(4), alphabet('0123456789abcdef'), join$1(''), normalize((s) => {
4269
4256
  if (typeof s !== 'string' || s.length % 2 !== 0)
4270
4257
  throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`);
4271
4258
  return s.toLowerCase();
@@ -12413,7 +12400,6 @@ function parseSerializedSignature(serializedSignature) {
12413
12400
  }
12414
12401
 
12415
12402
  const PRIVATE_KEY_SIZE = 32;
12416
- const LEGACY_PRIVATE_KEY_SIZE = 64;
12417
12403
  const SUI_PRIVATE_KEY_PREFIX = "suiprivkey";
12418
12404
  class Signer {
12419
12405
  /**
@@ -12495,131 +12481,6 @@ function encodeSuiPrivateKey(bytes, scheme) {
12495
12481
  return bech32.encode(SUI_PRIVATE_KEY_PREFIX, bech32.toWords(privKeyBytes));
12496
12482
  }
12497
12483
 
12498
- /**
12499
- * PBKDF (RFC 2898). Can be used to create a key from password and salt.
12500
- * @module
12501
- */
12502
- // Common prologue and epilogue for sync/async functions
12503
- function pbkdf2Init(hash, _password, _salt, _opts) {
12504
- ahash(hash);
12505
- const opts = checkOpts({ dkLen: 32, asyncTick: 10 }, _opts);
12506
- const { c, dkLen, asyncTick } = opts;
12507
- anumber$1(c);
12508
- anumber$1(dkLen);
12509
- anumber$1(asyncTick);
12510
- if (c < 1)
12511
- throw new Error('iterations (c) should be >= 1');
12512
- const password = kdfInputToBytes(_password);
12513
- const salt = kdfInputToBytes(_salt);
12514
- // DK = PBKDF2(PRF, Password, Salt, c, dkLen);
12515
- const DK = new Uint8Array(dkLen);
12516
- // U1 = PRF(Password, Salt + INT_32_BE(i))
12517
- const PRF = hmac.create(hash, password);
12518
- const PRFSalt = PRF._cloneInto().update(salt);
12519
- return { c, dkLen, asyncTick, DK, PRF, PRFSalt };
12520
- }
12521
- function pbkdf2Output(PRF, PRFSalt, DK, prfW, u) {
12522
- PRF.destroy();
12523
- PRFSalt.destroy();
12524
- if (prfW)
12525
- prfW.destroy();
12526
- clean(u);
12527
- return DK;
12528
- }
12529
- /**
12530
- * PBKDF2-HMAC: RFC 2898 key derivation function
12531
- * @param hash - hash function that would be used e.g. sha256
12532
- * @param password - password from which a derived key is generated
12533
- * @param salt - cryptographic salt
12534
- * @param opts - {c, dkLen} where c is work factor and dkLen is output message size
12535
- * @example
12536
- * const key = pbkdf2(sha256, 'password', 'salt', { dkLen: 32, c: Math.pow(2, 18) });
12537
- */
12538
- function pbkdf2(hash, password, salt, opts) {
12539
- const { c, dkLen, DK, PRF, PRFSalt } = pbkdf2Init(hash, password, salt, opts);
12540
- let prfW; // Working copy
12541
- const arr = new Uint8Array(4);
12542
- const view = createView(arr);
12543
- const u = new Uint8Array(PRF.outputLen);
12544
- // DK = T1 + T2 + ⋯ + Tdklen/hlen
12545
- for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += PRF.outputLen) {
12546
- // Ti = F(Password, Salt, c, i)
12547
- const Ti = DK.subarray(pos, pos + PRF.outputLen);
12548
- view.setInt32(0, ti, false);
12549
- // F(Password, Salt, c, i) = U1 ^ U2 ^ ⋯ ^ Uc
12550
- // U1 = PRF(Password, Salt + INT_32_BE(i))
12551
- (prfW = PRFSalt._cloneInto(prfW)).update(arr).digestInto(u);
12552
- Ti.set(u.subarray(0, Ti.length));
12553
- for (let ui = 1; ui < c; ui++) {
12554
- // Uc = PRF(Password, Uc−1)
12555
- PRF._cloneInto(prfW).update(u).digestInto(u);
12556
- for (let i = 0; i < Ti.length; i++)
12557
- Ti[i] ^= u[i];
12558
- }
12559
- }
12560
- return pbkdf2Output(PRF, PRFSalt, DK, prfW, u);
12561
- }
12562
-
12563
- /**
12564
- * Audited & minimal JS implementation of
12565
- * [BIP39 mnemonic phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).
12566
- * @module
12567
- * @example
12568
- ```js
12569
- import * as bip39 from '@scure/bip39';
12570
- import { wordlist } from '@scure/bip39/wordlists/english';
12571
- const mn = bip39.generateMnemonic(wordlist);
12572
- console.log(mn);
12573
- const ent = bip39.mnemonicToEntropy(mn, wordlist)
12574
- bip39.entropyToMnemonic(ent, wordlist);
12575
- bip39.validateMnemonic(mn, wordlist);
12576
- await bip39.mnemonicToSeed(mn, 'password');
12577
- bip39.mnemonicToSeedSync(mn, 'password');
12578
-
12579
- // Wordlists
12580
- import { wordlist as czech } from '@scure/bip39/wordlists/czech';
12581
- import { wordlist as english } from '@scure/bip39/wordlists/english';
12582
- import { wordlist as french } from '@scure/bip39/wordlists/french';
12583
- import { wordlist as italian } from '@scure/bip39/wordlists/italian';
12584
- import { wordlist as japanese } from '@scure/bip39/wordlists/japanese';
12585
- import { wordlist as korean } from '@scure/bip39/wordlists/korean';
12586
- import { wordlist as portuguese } from '@scure/bip39/wordlists/portuguese';
12587
- import { wordlist as simplifiedChinese } from '@scure/bip39/wordlists/simplified-chinese';
12588
- import { wordlist as spanish } from '@scure/bip39/wordlists/spanish';
12589
- import { wordlist as traditionalChinese } from '@scure/bip39/wordlists/traditional-chinese';
12590
- ```
12591
- */
12592
- // Normalization replaces equivalent sequences of characters
12593
- // so that any two texts that are equivalent will be reduced
12594
- // to the same sequence of code points, called the normal form of the original text.
12595
- // https://tonsky.me/blog/unicode/#why-is-a----
12596
- function nfkd(str) {
12597
- if (typeof str !== 'string')
12598
- throw new TypeError('invalid mnemonic type: ' + typeof str);
12599
- return str.normalize('NFKD');
12600
- }
12601
- function normalize(str) {
12602
- const norm = nfkd(str);
12603
- const words = norm.split(' ');
12604
- if (![12, 15, 18, 21, 24].includes(words.length))
12605
- throw new Error('Invalid mnemonic');
12606
- return { nfkd: norm, words };
12607
- }
12608
- const psalt = (passphrase) => nfkd('mnemonic' + passphrase);
12609
- /**
12610
- * Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.
12611
- * @param mnemonic 12-24 words
12612
- * @param passphrase string that will additionally protect the key
12613
- * @returns 64 bytes of key data
12614
- * @example
12615
- * const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
12616
- * mnemonicToSeedSync(mnem, 'password');
12617
- * // new Uint8Array([...64 bytes])
12618
- */
12619
- function mnemonicToSeedSync(mnemonic, passphrase = '') {
12620
- return pbkdf2(sha512, normalize(mnemonic).nfkd, psalt(passphrase), { c: 2048, dkLen: 64 });
12621
- }
12622
-
12623
12484
  function isValidBIP32Path(path) {
12624
12485
  if (!new RegExp("^m\\/(54|74)'\\/784'\\/[0-9]+'\\/[0-9]+\\/[0-9]+$").test(path)) {
12625
12486
  return false;
@@ -12627,7 +12488,7 @@ function isValidBIP32Path(path) {
12627
12488
  return true;
12628
12489
  }
12629
12490
  function mnemonicToSeed(mnemonics) {
12630
- return mnemonicToSeedSync(mnemonics, "");
12491
+ return bip39.mnemonicToSeedSync(mnemonics, "");
12631
12492
  }
12632
12493
 
12633
12494
  const SECP256K1_PUBLIC_KEY_SIZE = 33;
@@ -13018,16 +12879,16 @@ function fromExportedKeypair(secret, legacySupport = false) {
13018
12879
  schema = secret.schema;
13019
12880
  }
13020
12881
  else {
13021
- const decoded = decodeSuiPrivateKey(secret);
12882
+ const decoded = cryptography.decodeSuiPrivateKey(secret);
13022
12883
  schema = decoded.schema;
13023
12884
  secretKey = decoded.secretKey;
13024
12885
  }
13025
12886
  switch (schema) {
13026
12887
  case "ED25519": {
13027
12888
  let pureSecretKey = secretKey;
13028
- if (secretKey.length === LEGACY_PRIVATE_KEY_SIZE) {
12889
+ if (secretKey.length === cryptography.LEGACY_PRIVATE_KEY_SIZE) {
13029
12890
  // Legacy secret key, strip public key bytes
13030
- pureSecretKey = secretKey.slice(0, PRIVATE_KEY_SIZE);
12891
+ pureSecretKey = secretKey.slice(0, cryptography.PRIVATE_KEY_SIZE);
13031
12892
  }
13032
12893
  return ed25519.Ed25519Keypair.fromSecretKey(pureSecretKey);
13033
12894
  }
@@ -13059,7 +12920,7 @@ async function signMessage(keypair, messageBytes) {
13059
12920
  * @returns Formatted signature string
13060
12921
  */
13061
12922
  function buildSignature(signature, isKeyPair = false) {
13062
- const signatureData = parseSerializedSignature(signature);
12923
+ const signatureData = cryptography.parseSerializedSignature(signature);
13063
12924
  let signatureHex = buffer.Buffer.from(signatureData.signature).toString("hex");
13064
12925
  let publicKey = buffer.Buffer.from(signatureData.publicKey).toString("base64");
13065
12926
  let flag = SignerTypes.KP_SECP256;
@@ -16735,6 +16596,13 @@ function getPerpetualId(deployment, market) {
16735
16596
  throw new Error(`Deployment config missing Perpetual id for ${m}`);
16736
16597
  return id;
16737
16598
  }
16599
+ function getPriceFeedId(deployment) {
16600
+ const id = deployment?.objects?.PriceFeed?.id;
16601
+ if (!id) {
16602
+ 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.");
16603
+ }
16604
+ return id;
16605
+ }
16738
16606
  function getPriceOracleObjectId(deployment, market) {
16739
16607
  const m = market?.toUpperCase() || "ETH-PERP";
16740
16608
  const id = deployment?.markets?.[m]?.Objects?.PriceInfoObject?.id;
@@ -16780,7 +16648,7 @@ function buildAddMarginTx(deployment, args, tx, gasBudget, sender) {
16780
16648
  t.setSender(sender);
16781
16649
  const packageId = getPackageId(deployment);
16782
16650
  t.moveCall({
16783
- target: `${packageId}::exchange::add_margin_v2`,
16651
+ target: `${packageId}::exchange::add_margin_v3`,
16784
16652
  arguments: [
16785
16653
  t.object(getProtocolConfigId(deployment)),
16786
16654
  t.object(CLOCK_OBJECT_ID),
@@ -16788,7 +16656,7 @@ function buildAddMarginTx(deployment, args, tx, gasBudget, sender) {
16788
16656
  t.object(getBankId(deployment)),
16789
16657
  t.object(args.subAccountsMapID || getSubAccountsId(deployment)),
16790
16658
  t.object(getTxIndexerId(deployment)),
16791
- t.object(getPriceOracleObjectId(deployment, args.market)),
16659
+ t.object(getPriceFeedId(deployment)),
16792
16660
  t.pure.address(args.account || sender || ""),
16793
16661
  t.pure.u128(args.amount.toString()),
16794
16662
  ],
@@ -16804,7 +16672,7 @@ function buildRemoveMarginTx(deployment, args, tx, gasBudget, sender) {
16804
16672
  t.setSender(sender);
16805
16673
  const packageId = getPackageId(deployment);
16806
16674
  t.moveCall({
16807
- target: `${packageId}::exchange::remove_margin_v2`,
16675
+ target: `${packageId}::exchange::remove_margin_v3`,
16808
16676
  arguments: [
16809
16677
  t.object(getProtocolConfigId(deployment)),
16810
16678
  t.object(CLOCK_OBJECT_ID),
@@ -16812,7 +16680,7 @@ function buildRemoveMarginTx(deployment, args, tx, gasBudget, sender) {
16812
16680
  t.object(getBankId(deployment)),
16813
16681
  t.object(args.subAccountsMapID || getSubAccountsId(deployment)),
16814
16682
  t.object(getTxIndexerId(deployment)),
16815
- t.object(getPriceOracleObjectId(deployment, args.market)),
16683
+ t.object(getPriceFeedId(deployment)),
16816
16684
  t.pure.address(args.account || sender || ""),
16817
16685
  t.pure.u128(args.amount.toString()),
16818
16686
  ],
@@ -16883,17 +16751,6 @@ function buildSetOraclePriceTx(deployment, args, tx, gasBudget, sender) {
16883
16751
  });
16884
16752
  return t;
16885
16753
  }
16886
- function buildBatchSetOraclePriceTx(deployment, args, tx, gasBudget, sender) {
16887
- let t = tx || new transactions.Transaction();
16888
- for (const priceArgs of args.prices) {
16889
- t = buildSetOraclePriceTx(deployment, priceArgs, t, undefined, undefined);
16890
- }
16891
- if (gasBudget)
16892
- t.setGasBudget(gasBudget);
16893
- if (sender)
16894
- t.setSender(sender);
16895
- return t;
16896
- }
16897
16754
 
16898
16755
  // Copyright (c) 2025 Dipcoin LLC
16899
16756
  async function executeTxBlock(suiClient, tx, signer) {
@@ -18763,10 +18620,9 @@ class DipCoinPerpSDK {
18763
18620
  }
18764
18621
  async buildMarginTransaction(params, action) {
18765
18622
  const payload = this.buildMarginCallArgs(params, action);
18766
- const updatePriceTx = payload.market
18767
- ? await this.buildUpdatePriceTransaction(payload.market)
18768
- : undefined;
18769
- const baseTx = updatePriceTx || new transactions.Transaction();
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.
18625
+ const baseTx = new transactions.Transaction();
18770
18626
  if (action === "add") {
18771
18627
  return buildAddMarginTx(this.deploymentConfig, payload, baseTx, params.gasBudget);
18772
18628
  }
@@ -19052,45 +18908,40 @@ class DipCoinPerpSDK {
19052
18908
  throw new Error(`Deployment config missing PriceInfoObject id for ${symbol}`);
19053
18909
  return id;
19054
18910
  }
19055
- async buildVaultNavTransaction(vaultID, markets, tx) {
18911
+ getPriceFeedId() {
18912
+ const id = this.deploymentConfig?.objects?.PriceFeed?.id;
18913
+ if (!id) {
18914
+ throw new Error("Deployment config missing PriceFeed id. Add objects.PriceFeed to the network's main_contract.json — required for v6+ vault/margin writes.");
18915
+ }
18916
+ return id;
18917
+ }
18918
+ async getOnChainPerpetualIds() {
18919
+ const pcId = this.getDeploymentProtocolConfigId();
18920
+ const df = await this.suiClient.getDynamicFieldObject({
18921
+ parentId: pcId,
18922
+ name: {
18923
+ type: "vector<u8>",
18924
+ value: Array.from(Buffer.from("perpetual_registry", "utf8")),
18925
+ },
18926
+ });
18927
+ const content = df.data?.content;
18928
+ const contents = content?.fields?.value?.fields?.perpetuals?.fields?.contents;
18929
+ if (!Array.isArray(contents)) {
18930
+ throw new Error("Could not read perpetual_registry from on-chain ProtocolConfig. Vault NAV requires the full list of perpetuals registered in the protocol.");
18931
+ }
18932
+ return contents;
18933
+ }
18934
+ async buildVaultNavTransaction(vaultID, _markets, tx) {
19056
18935
  const t = tx || new transactions.Transaction();
19057
18936
  const vaultPkg = this.getVaultPackageId();
19058
18937
  const currencyType = this.getCurrencyType();
19059
- const symbols = markets && markets.length > 0 ? markets : this.getMarketSymbols();
19060
- if (this.options.network === "mainnet") {
19061
- // On mainnet, use Pyth to fetch fresh price VAAs and update on-chain oracles
19062
- this.ensurePythClients();
19063
- if (this.priceServiceConnection && this.pythClient) {
19064
- const priceIds = [];
19065
- for (const sym of symbols) {
19066
- const feedId = await this.resolvePriceFeedId(sym);
19067
- if (feedId)
19068
- priceIds.push(feedId);
19069
- }
19070
- if (priceIds.length > 0) {
19071
- const priceUpdateData = await this.priceServiceConnection.getPriceFeedsUpdateData(priceIds);
19072
- await this.pythClient.updatePriceFeeds(t, priceUpdateData, priceIds);
19073
- }
19074
- }
19075
- }
19076
- else {
19077
- // On testnet, prepend price oracle updates so PriceInfoObjects are fresh
19078
- const prices = [];
19079
- for (const sym of symbols) {
19080
- try {
19081
- const oraclePrice = await getOraclePrice(this.suiClient, this.deploymentConfig, sym);
19082
- if (oraclePrice !== undefined && oraclePrice !== null) {
19083
- prices.push({ price: Number(oraclePrice), market: sym });
19084
- }
19085
- }
19086
- catch {
19087
- // skip symbols where oracle price is unavailable
19088
- }
19089
- }
19090
- if (prices.length > 0) {
19091
- buildBatchSetOraclePriceTx(this.deploymentConfig, { prices }, t);
19092
- }
19093
- }
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).
18943
+ const priceFeedId = this.getPriceFeedId();
18944
+ const perpetualIds = await this.getOnChainPerpetualIds();
19094
18945
  const [nav] = t.moveCall({
19095
18946
  target: `${vaultPkg}::vault::new_vault_nav`,
19096
18947
  arguments: [
@@ -19101,12 +18952,10 @@ class DipCoinPerpSDK {
19101
18952
  ],
19102
18953
  typeArguments: [currencyType],
19103
18954
  });
19104
- for (const sym of symbols) {
19105
- const perpId = this.getVaultPerpetualId(sym);
19106
- const priceInfoId = this.getVaultPriceInfoObjectId(sym);
18955
+ for (const perpId of perpetualIds) {
19107
18956
  t.moveCall({
19108
- target: `${vaultPkg}::vault::compute_perpetual_position_value`,
19109
- arguments: [nav, t.object("0x6"), t.object(perpId), t.object(priceInfoId)],
18957
+ target: `${vaultPkg}::vault::compute_perpetual_position_value_v2`,
18958
+ arguments: [nav, t.object("0x6"), t.object(perpId), t.object(priceFeedId)],
19110
18959
  typeArguments: [],
19111
18960
  });
19112
18961
  }
@@ -20135,11 +19984,16 @@ function registerPositionCommands(program) {
20135
19984
  .description("Add margin to position")
20136
19985
  .argument("<symbol>", "Trading pair")
20137
19986
  .argument("<amount>", "Amount in USDC")
20138
- .action(async (symbol, amount) => {
19987
+ .option("--vault <address>", "Target a vault's position (vault id) instead of personal account")
19988
+ .action(async (symbol, amount, opts) => {
20139
19989
  try {
20140
19990
  const sdk = getSDK();
20141
19991
  symbol = normalizeSymbol(symbol);
20142
- const tx = await sdk.addMargin({ symbol, amount: parseAmount(amount) });
19992
+ const tx = await sdk.addMargin({
19993
+ symbol,
19994
+ amount: parseAmount(amount),
19995
+ accountAddress: opts.vault,
19996
+ });
20143
19997
  if (isJson(program))
20144
19998
  return printJson({ digest: tx?.digest, status: "ok" });
20145
19999
  console.log(`Added ${amount} margin to ${symbol}. Tx: ${tx?.digest || JSON.stringify(tx)}`);
@@ -20153,11 +20007,16 @@ function registerPositionCommands(program) {
20153
20007
  .description("Remove margin from position")
20154
20008
  .argument("<symbol>", "Trading pair")
20155
20009
  .argument("<amount>", "Amount in USDC")
20156
- .action(async (symbol, amount) => {
20010
+ .option("--vault <address>", "Target a vault's position (vault id) instead of personal account")
20011
+ .action(async (symbol, amount, opts) => {
20157
20012
  try {
20158
20013
  const sdk = getSDK();
20159
20014
  symbol = normalizeSymbol(symbol);
20160
- const tx = await sdk.removeMargin({ symbol, amount: parseAmount(amount) });
20015
+ const tx = await sdk.removeMargin({
20016
+ symbol,
20017
+ amount: parseAmount(amount),
20018
+ accountAddress: opts.vault,
20019
+ });
20161
20020
  if (isJson(program))
20162
20021
  return printJson({ digest: tx?.digest, status: "ok" });
20163
20022
  console.log(`Removed ${amount} margin from ${symbol}. Tx: ${tx?.digest || JSON.stringify(tx)}`);
@@ -21061,9 +20920,12 @@ function readHiddenInput(prompt) {
21061
20920
  stdin.on("data", onData);
21062
20921
  });
21063
20922
  }
21064
- function writeEnvFile(privateKey, network) {
20923
+ function writeEnvFile(cred, network) {
21065
20924
  fs__default["default"].mkdirSync(CONFIG_DIR, { recursive: true });
21066
- const content = `DIPCOIN_PRIVATE_KEY=${privateKey}\nDIPCOIN_NETWORK=${network}\n`;
20925
+ const line = cred.kind === "privateKey"
20926
+ ? `DIPCOIN_PRIVATE_KEY=${cred.value}`
20927
+ : `DIPCOIN_MNEMONIC=${cred.value}`;
20928
+ const content = `${line}\nDIPCOIN_NETWORK=${network}\n`;
21067
20929
  fs__default["default"].writeFileSync(ENV_PATH, content, { mode: 0o600 });
21068
20930
  try {
21069
20931
  fs__default["default"].chmodSync(ENV_PATH, 0o600);
@@ -21072,6 +20934,11 @@ function writeEnvFile(privateKey, network) {
21072
20934
  // best-effort on platforms without POSIX perms
21073
20935
  }
21074
20936
  }
20937
+ const VALID_MNEMONIC_WORD_COUNTS = new Set([12, 15, 18, 21, 24]);
20938
+ function looksLikeMnemonic(input) {
20939
+ const words = input.split(/\s+/).filter(Boolean);
20940
+ return VALID_MNEMONIC_WORD_COUNTS.has(words.length);
20941
+ }
21075
20942
  function resolveNetwork(input) {
21076
20943
  return input === "testnet" ? "testnet" : "mainnet";
21077
20944
  }
@@ -21081,7 +20948,7 @@ function registerSetupCommands(program) {
21081
20948
  .description("Configure credentials (~/.config/dipcoin/env)");
21082
20949
  setup
21083
20950
  .command("import")
21084
- .description("Import an existing Sui private key (interactive, hidden input)")
20951
+ .description("Import an existing Sui private key or mnemonic (interactive, hidden input)")
21085
20952
  .option("--network <network>", "mainnet | testnet", "mainnet")
21086
20953
  .action(async (opts) => {
21087
20954
  try {
@@ -21090,27 +20957,48 @@ function registerSetupCommands(program) {
21090
20957
  console.error("Aborted.");
21091
20958
  process.exit(1);
21092
20959
  }
21093
- const key = (await readHiddenInput("Paste your Sui private key (suiprivkey1...): ")).trim();
21094
- if (!key) {
20960
+ const raw = (await readHiddenInput("Paste your Sui private key (suiprivkey1...) or 12-word mnemonic: ")).trim();
20961
+ if (!raw) {
21095
20962
  console.error("No input provided.");
21096
20963
  process.exit(1);
21097
20964
  }
21098
- if (!key.startsWith("suiprivkey1")) {
21099
- console.error("Invalid format. Sui private keys must start with 'suiprivkey1'.");
21100
- process.exit(1);
21101
- }
20965
+ let cred;
21102
20966
  let address;
21103
- try {
21104
- decodeSuiPrivateKey(key);
21105
- address = fromExportedKeypair(key).toSuiAddress();
20967
+ if (raw.startsWith("suiprivkey1")) {
20968
+ try {
20969
+ cryptography.decodeSuiPrivateKey(raw);
20970
+ address = fromExportedKeypair(raw).toSuiAddress();
20971
+ }
20972
+ catch (e) {
20973
+ const msg = e instanceof Error ? e.message : String(e);
20974
+ console.error(`Failed to decode private key: ${msg}`);
20975
+ process.exit(1);
20976
+ }
20977
+ cred = { kind: "privateKey", value: raw };
21106
20978
  }
21107
- catch (e) {
21108
- const msg = e instanceof Error ? e.message : String(e);
21109
- console.error(`Failed to decode private key: ${msg}`);
20979
+ else if (looksLikeMnemonic(raw)) {
20980
+ const normalized = raw.split(/\s+/).filter(Boolean).join(" ").toLowerCase();
20981
+ if (!bip39.validateMnemonic(normalized, english_js.wordlist)) {
20982
+ console.error("Invalid mnemonic: words must be from the BIP39 English wordlist and the checksum must match.");
20983
+ process.exit(1);
20984
+ }
20985
+ try {
20986
+ address = ed25519.Ed25519Keypair.deriveKeypair(normalized, `m/44'/784'/0'/0'/0'`).toSuiAddress();
20987
+ }
20988
+ catch (e) {
20989
+ const msg = e instanceof Error ? e.message : String(e);
20990
+ console.error(`Failed to derive keypair from mnemonic: ${msg}`);
20991
+ process.exit(1);
20992
+ }
20993
+ cred = { kind: "mnemonic", value: normalized };
20994
+ }
20995
+ else {
20996
+ console.error("Unrecognized format. Expected a Sui private key (starts with 'suiprivkey1') or a 12/15/18/21/24-word mnemonic.");
21110
20997
  process.exit(1);
21111
20998
  }
21112
- writeEnvFile(key, network);
20999
+ writeEnvFile(cred, network);
21113
21000
  console.log(`Saved to ${ENV_PATH} (chmod 600).`);
21001
+ console.log(`Type: ${cred.kind === "privateKey" ? "private key" : "mnemonic"}`);
21114
21002
  console.log(`Network: ${network}`);
21115
21003
  console.log(`Address: ${address}`);
21116
21004
  }
@@ -21134,7 +21022,7 @@ function registerSetupCommands(program) {
21134
21022
  const kp = new ed25519.Ed25519Keypair();
21135
21023
  const privateKey = kp.getSecretKey();
21136
21024
  const address = kp.toSuiAddress();
21137
- writeEnvFile(privateKey, network);
21025
+ writeEnvFile({ kind: "privateKey", value: privateKey }, network);
21138
21026
  console.log(`Generated new keypair and saved to ${ENV_PATH} (chmod 600).`);
21139
21027
  console.log(`Network: ${network}`);
21140
21028
  console.log(`Address: ${address}`);