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
|
-
|
|
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` |
|
|
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
|
|
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
|
|
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
|
|
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::
|
|
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(
|
|
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::
|
|
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(
|
|
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
|
-
|
|
18767
|
-
|
|
18768
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
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
|
|
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::
|
|
19109
|
-
arguments: [nav, t.object("0x6"), t.object(perpId), t.object(
|
|
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
|
-
.
|
|
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({
|
|
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
|
-
.
|
|
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({
|
|
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(
|
|
20923
|
+
function writeEnvFile(cred, network) {
|
|
21065
20924
|
fs__default["default"].mkdirSync(CONFIG_DIR, { recursive: true });
|
|
21066
|
-
const
|
|
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
|
|
21094
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
21104
|
-
|
|
21105
|
-
|
|
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
|
-
|
|
21108
|
-
const
|
|
21109
|
-
|
|
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(
|
|
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}`);
|