dipcoin-cli 1.2.0 → 1.2.2

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
@@ -20,23 +20,37 @@ npm install -g dipcoin-cli
20
20
 
21
21
  ### Configuration
22
22
 
23
- Create a config file at `~/.config/dipcoin/env` (recommended) or `.env` in your working directory:
23
+ Credentials live in `~/.config/dipcoin/env`. Set them up with one of the two built-in commands — both write the file with `chmod 600` and never echo your private key to the terminal.
24
+
25
+ **Import an existing Sui wallet:**
24
26
 
25
27
  ```bash
26
- mkdir -p ~/.config/dipcoin
27
- cat > ~/.config/dipcoin/env << 'EOF'
28
- DIPCOIN_PRIVATE_KEY=suiprivkey1...
29
- DIPCOIN_NETWORK=mainnet
30
- EOF
28
+ dipcoin-cli setup import # mainnet (default)
29
+ dipcoin-cli setup import --network testnet
31
30
  ```
32
31
 
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
+
34
+ **Or generate a new wallet:**
35
+
36
+ ```bash
37
+ dipcoin-cli setup generate # mainnet (default)
38
+ dipcoin-cli setup generate --network testnet
39
+ ```
40
+
41
+ A new Ed25519 keypair is created and saved; the command prints the public Sui address. Transfer USDC on Sui to that address, then run `dipcoin-cli account deposit <amount>` to fund the DEX.
42
+
43
+ #### Config file format
44
+
45
+ The generated `~/.config/dipcoin/env` contains:
46
+
33
47
  | Variable | Required | Description |
34
48
  |----------|----------|-------------|
35
49
  | `DIPCOIN_PRIVATE_KEY` | One of these | Sui private key (`suiprivkey1...`), supports ED25519/Secp256k1/Secp256r1 |
36
- | `DIPCOIN_MNEMONIC` | is required | 12-word Sui mnemonic phrase (derives keypair at `m/44'/784'/0'/0'/0'`) |
50
+ | `DIPCOIN_MNEMONIC` | is required | 12/15/18/21/24-word BIP39 mnemonic |
37
51
  | `DIPCOIN_NETWORK` | No | `mainnet` or `testnet` (default: `mainnet`) |
38
52
 
39
- If both `DIPCOIN_PRIVATE_KEY` and `DIPCOIN_MNEMONIC` are set, the private key takes precedence.
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.
40
54
 
41
55
  ### Quick Start
42
56
 
@@ -72,6 +86,14 @@ dipcoin-cli history orders --symbol BTC-PERP
72
86
  | `--json` | Output in JSON format (machine-readable) |
73
87
  | `-V, --version` | Show version |
74
88
 
89
+ #### setup
90
+
91
+ ```bash
92
+ dipcoin-cli setup import # Import existing key (hidden prompt)
93
+ dipcoin-cli setup generate # Generate new keypair
94
+ dipcoin-cli setup import --network testnet # Same, on testnet
95
+ ```
96
+
75
97
  #### market
76
98
 
77
99
  ```bash
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;
@@ -21061,9 +20922,12 @@ function readHiddenInput(prompt) {
21061
20922
  stdin.on("data", onData);
21062
20923
  });
21063
20924
  }
21064
- function writeEnvFile(privateKey, network) {
20925
+ function writeEnvFile(cred, network) {
21065
20926
  fs__default["default"].mkdirSync(CONFIG_DIR, { recursive: true });
21066
- const content = `DIPCOIN_PRIVATE_KEY=${privateKey}\nDIPCOIN_NETWORK=${network}\n`;
20927
+ const line = cred.kind === "privateKey"
20928
+ ? `DIPCOIN_PRIVATE_KEY=${cred.value}`
20929
+ : `DIPCOIN_MNEMONIC=${cred.value}`;
20930
+ const content = `${line}\nDIPCOIN_NETWORK=${network}\n`;
21067
20931
  fs__default["default"].writeFileSync(ENV_PATH, content, { mode: 0o600 });
21068
20932
  try {
21069
20933
  fs__default["default"].chmodSync(ENV_PATH, 0o600);
@@ -21072,6 +20936,11 @@ function writeEnvFile(privateKey, network) {
21072
20936
  // best-effort on platforms without POSIX perms
21073
20937
  }
21074
20938
  }
20939
+ const VALID_MNEMONIC_WORD_COUNTS = new Set([12, 15, 18, 21, 24]);
20940
+ function looksLikeMnemonic(input) {
20941
+ const words = input.split(/\s+/).filter(Boolean);
20942
+ return VALID_MNEMONIC_WORD_COUNTS.has(words.length);
20943
+ }
21075
20944
  function resolveNetwork(input) {
21076
20945
  return input === "testnet" ? "testnet" : "mainnet";
21077
20946
  }
@@ -21081,7 +20950,7 @@ function registerSetupCommands(program) {
21081
20950
  .description("Configure credentials (~/.config/dipcoin/env)");
21082
20951
  setup
21083
20952
  .command("import")
21084
- .description("Import an existing Sui private key (interactive, hidden input)")
20953
+ .description("Import an existing Sui private key or mnemonic (interactive, hidden input)")
21085
20954
  .option("--network <network>", "mainnet | testnet", "mainnet")
21086
20955
  .action(async (opts) => {
21087
20956
  try {
@@ -21090,27 +20959,48 @@ function registerSetupCommands(program) {
21090
20959
  console.error("Aborted.");
21091
20960
  process.exit(1);
21092
20961
  }
21093
- const key = (await readHiddenInput("Paste your Sui private key (suiprivkey1...): ")).trim();
21094
- if (!key) {
20962
+ const raw = (await readHiddenInput("Paste your Sui private key (suiprivkey1...) or 12-word mnemonic: ")).trim();
20963
+ if (!raw) {
21095
20964
  console.error("No input provided.");
21096
20965
  process.exit(1);
21097
20966
  }
21098
- if (!key.startsWith("suiprivkey1")) {
21099
- console.error("Invalid format. Sui private keys must start with 'suiprivkey1'.");
21100
- process.exit(1);
21101
- }
20967
+ let cred;
21102
20968
  let address;
21103
- try {
21104
- decodeSuiPrivateKey(key);
21105
- address = fromExportedKeypair(key).toSuiAddress();
20969
+ if (raw.startsWith("suiprivkey1")) {
20970
+ try {
20971
+ cryptography.decodeSuiPrivateKey(raw);
20972
+ address = fromExportedKeypair(raw).toSuiAddress();
20973
+ }
20974
+ catch (e) {
20975
+ const msg = e instanceof Error ? e.message : String(e);
20976
+ console.error(`Failed to decode private key: ${msg}`);
20977
+ process.exit(1);
20978
+ }
20979
+ cred = { kind: "privateKey", value: raw };
21106
20980
  }
21107
- catch (e) {
21108
- const msg = e instanceof Error ? e.message : String(e);
21109
- console.error(`Failed to decode private key: ${msg}`);
20981
+ else if (looksLikeMnemonic(raw)) {
20982
+ const normalized = raw.split(/\s+/).filter(Boolean).join(" ").toLowerCase();
20983
+ if (!bip39.validateMnemonic(normalized, english_js.wordlist)) {
20984
+ console.error("Invalid mnemonic: words must be from the BIP39 English wordlist and the checksum must match.");
20985
+ process.exit(1);
20986
+ }
20987
+ try {
20988
+ address = ed25519.Ed25519Keypair.deriveKeypair(normalized, `m/44'/784'/0'/0'/0'`).toSuiAddress();
20989
+ }
20990
+ catch (e) {
20991
+ const msg = e instanceof Error ? e.message : String(e);
20992
+ console.error(`Failed to derive keypair from mnemonic: ${msg}`);
20993
+ process.exit(1);
20994
+ }
20995
+ cred = { kind: "mnemonic", value: normalized };
20996
+ }
20997
+ else {
20998
+ console.error("Unrecognized format. Expected a Sui private key (starts with 'suiprivkey1') or a 12/15/18/21/24-word mnemonic.");
21110
20999
  process.exit(1);
21111
21000
  }
21112
- writeEnvFile(key, network);
21001
+ writeEnvFile(cred, network);
21113
21002
  console.log(`Saved to ${ENV_PATH} (chmod 600).`);
21003
+ console.log(`Type: ${cred.kind === "privateKey" ? "private key" : "mnemonic"}`);
21114
21004
  console.log(`Network: ${network}`);
21115
21005
  console.log(`Address: ${address}`);
21116
21006
  }
@@ -21134,7 +21024,7 @@ function registerSetupCommands(program) {
21134
21024
  const kp = new ed25519.Ed25519Keypair();
21135
21025
  const privateKey = kp.getSecretKey();
21136
21026
  const address = kp.toSuiAddress();
21137
- writeEnvFile(privateKey, network);
21027
+ writeEnvFile({ kind: "privateKey", value: privateKey }, network);
21138
21028
  console.log(`Generated new keypair and saved to ${ENV_PATH} (chmod 600).`);
21139
21029
  console.log(`Network: ${network}`);
21140
21030
  console.log(`Address: ${address}`);