@profullstack/coinpay 0.4.0 → 0.4.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/bin/coinpay.js CHANGED
@@ -823,12 +823,16 @@ async function handleWallet(subcommand, args, flags) {
823
823
  print.info('Importing wallet...');
824
824
 
825
825
  try {
826
- const wallet = await WalletClient.fromSeed(mnemonic, {
827
- chains,
828
- baseUrl,
829
- });
830
-
831
- const walletId = wallet.getWalletId();
826
+ let walletId = null;
827
+ try {
828
+ const wallet = await WalletClient.fromSeed(mnemonic, {
829
+ chains,
830
+ baseUrl,
831
+ });
832
+ walletId = wallet.getWalletId();
833
+ } catch (serverErr) {
834
+ print.warn(`Server registration failed (wallet saved locally only): ${serverErr.message || serverErr}`);
835
+ }
832
836
 
833
837
  // Save encrypted locally (unless --no-save)
834
838
  if (!noSave) {
@@ -860,9 +864,9 @@ async function handleWallet(subcommand, args, flags) {
860
864
  config.walletFile = walletFile;
861
865
  saveConfig(config);
862
866
 
863
- print.success(`Wallet imported: ${walletId}`);
867
+ print.success(walletId ? `Wallet imported (ID: ${walletId})` : 'Wallet imported (local only)');
864
868
  } catch (error) {
865
- print.error(error.message);
869
+ print.error(error.message || String(error));
866
870
  }
867
871
  break;
868
872
  }
@@ -878,7 +882,7 @@ async function handleWallet(subcommand, args, flags) {
878
882
  const mnemonic = await getDecryptedMnemonic(flags);
879
883
 
880
884
  print.success('Wallet unlocked');
881
- print.info(`Wallet ID: ${config.walletId || 'unknown'}`);
885
+ print.info(`Wallet ID: ${config.walletId || '(local only)'}`);
882
886
  print.info(`Wallet file: ${walletFile}`);
883
887
 
884
888
  if (flags.show) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/coinpay",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "CoinPay SDK & CLI — Accept cryptocurrency payments (BTC, ETH, SOL, POL, BCH, USDC) with wallet and swap support",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/wallet.js CHANGED
@@ -10,8 +10,13 @@ import * as bip39 from '@scure/bip39';
10
10
  import { wordlist } from '@scure/bip39/wordlists/english';
11
11
  import { HDKey } from '@scure/bip32';
12
12
  import { secp256k1 } from '@noble/curves/secp256k1';
13
- import { sha256 } from '@noble/hashes/sha256';
14
- import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
13
+ import { sha256 } from '@noble/hashes/sha2.js';
14
+ import { ripemd160 } from '@noble/hashes/ripemd160.js';
15
+ import { keccak_256 } from '@noble/hashes/sha3.js';
16
+ import { ed25519 } from '@noble/curves/ed25519';
17
+ import { hmac } from '@noble/hashes/hmac.js';
18
+ import { sha512 } from '@noble/hashes/sha2.js';
19
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
15
20
 
16
21
  const DEFAULT_BASE_URL = 'https://coinpayportal.com/api';
17
22
 
@@ -61,6 +66,248 @@ const COIN_TYPES = {
61
66
  */
62
67
  const SECP256K1_CHAINS = ['BTC', 'BCH', 'ETH', 'POL', 'BNB', 'USDC_ETH', 'USDC_POL', 'USDT_ETH', 'USDT_POL'];
63
68
 
69
+ // ============================================================
70
+ // Address derivation helpers
71
+ // ============================================================
72
+
73
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
74
+
75
+ /**
76
+ * Base58 encode a byte array
77
+ */
78
+ function base58Encode(bytes) {
79
+ const digits = [0];
80
+ for (const byte of bytes) {
81
+ let carry = byte;
82
+ for (let j = 0; j < digits.length; j++) {
83
+ carry += digits[j] << 8;
84
+ digits[j] = carry % 58;
85
+ carry = (carry / 58) | 0;
86
+ }
87
+ while (carry > 0) {
88
+ digits.push(carry % 58);
89
+ carry = (carry / 58) | 0;
90
+ }
91
+ }
92
+ // Leading zeros
93
+ let result = '';
94
+ for (const byte of bytes) {
95
+ if (byte === 0) result += BASE58_ALPHABET[0];
96
+ else break;
97
+ }
98
+ for (let i = digits.length - 1; i >= 0; i--) {
99
+ result += BASE58_ALPHABET[digits[i]];
100
+ }
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * Base58Check encode (used for BTC addresses)
106
+ */
107
+ function base58CheckEncode(version, payload) {
108
+ const data = new Uint8Array(1 + payload.length);
109
+ data[0] = version;
110
+ data.set(payload, 1);
111
+ const checksum = sha256(sha256(data)).slice(0, 4);
112
+ const full = new Uint8Array(data.length + 4);
113
+ full.set(data);
114
+ full.set(checksum, data.length);
115
+ return base58Encode(full);
116
+ }
117
+
118
+ /**
119
+ * Hash160: SHA256 then RIPEMD160
120
+ */
121
+ function hash160(data) {
122
+ return ripemd160(sha256(data));
123
+ }
124
+
125
+ /**
126
+ * Derive BTC P2PKH address from compressed public key
127
+ */
128
+ function deriveBTCAddress(compressedPubKey) {
129
+ const h = hash160(compressedPubKey);
130
+ return base58CheckEncode(0x00, h);
131
+ }
132
+
133
+ /**
134
+ * Derive BCH CashAddr from compressed public key
135
+ * Uses simplified CashAddr encoding (lowercase bech32-like with bitcoincash: prefix)
136
+ */
137
+ function deriveBCHAddress(compressedPubKey) {
138
+ const h = hash160(compressedPubKey);
139
+ // CashAddr encoding
140
+ const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
141
+
142
+ // Convert hash160 to 5-bit groups with version byte
143
+ // Version byte: 0 = P2PKH, 160-bit hash → type=0, hash_size=0 → version_byte = 0
144
+ const payload8 = new Uint8Array(1 + h.length);
145
+ payload8[0] = 0; // version byte
146
+ payload8.set(h, 1);
147
+
148
+ // Convert 8-bit to 5-bit
149
+ const payload5 = convertBits(payload8, 8, 5, true);
150
+
151
+ // Polymod checksum
152
+ const prefixData = expandPrefix('bitcoincash');
153
+ const checksumInput = new Uint8Array(prefixData.length + payload5.length + 8);
154
+ checksumInput.set(prefixData);
155
+ checksumInput.set(payload5, prefixData.length);
156
+ // Last 8 bytes are zeros for checksum calculation
157
+ const polymod = cashAddrPolymod(checksumInput) ^ 1;
158
+
159
+ const checksum = new Uint8Array(8);
160
+ for (let i = 0; i < 8; i++) {
161
+ checksum[i] = (polymod >> (5 * (7 - i))) & 0x1f;
162
+ }
163
+
164
+ let encoded = 'bitcoincash:';
165
+ for (const val of payload5) encoded += CHARSET[val];
166
+ for (const val of checksum) encoded += CHARSET[val];
167
+
168
+ return encoded;
169
+ }
170
+
171
+ function convertBits(data, fromBits, toBits, pad) {
172
+ let acc = 0;
173
+ let bits = 0;
174
+ const result = [];
175
+ const maxv = (1 << toBits) - 1;
176
+ for (const value of data) {
177
+ acc = (acc << fromBits) | value;
178
+ bits += fromBits;
179
+ while (bits >= toBits) {
180
+ bits -= toBits;
181
+ result.push((acc >> bits) & maxv);
182
+ }
183
+ }
184
+ if (pad && bits > 0) {
185
+ result.push((acc << (toBits - bits)) & maxv);
186
+ }
187
+ return new Uint8Array(result);
188
+ }
189
+
190
+ function expandPrefix(prefix) {
191
+ const result = new Uint8Array(prefix.length + 1);
192
+ for (let i = 0; i < prefix.length; i++) {
193
+ result[i] = prefix.charCodeAt(i) & 0x1f;
194
+ }
195
+ result[prefix.length] = 0;
196
+ return result;
197
+ }
198
+
199
+ function cashAddrPolymod(values) {
200
+ const GENERATORS = [
201
+ 0x98f2bc8e61n, 0x79b76d99e2n, 0xf33e5fb3c4n, 0xae2eabe2a8n, 0x1e4f43e470n
202
+ ];
203
+ let c = 1n;
204
+ for (const d of values) {
205
+ const c0 = c >> 35n;
206
+ c = ((c & 0x07ffffffffn) << 5n) ^ BigInt(d);
207
+ for (let i = 0; i < 5; i++) {
208
+ if ((c0 >> BigInt(i)) & 1n) {
209
+ c ^= GENERATORS[i];
210
+ }
211
+ }
212
+ }
213
+ return Number(c);
214
+ }
215
+
216
+ /**
217
+ * Derive ETH/EVM address from compressed public key bytes
218
+ */
219
+ function deriveETHAddress(compressedPubKey) {
220
+ // Decompress: get uncompressed point (65 bytes: 04 || x || y)
221
+ const point = secp256k1.ProjectivePoint.fromHex(compressedPubKey);
222
+ const uncompressed = point.toRawBytes(false); // 65 bytes with 04 prefix
223
+ // Keccak256 of the 64 bytes (without 04 prefix)
224
+ const hash = keccak_256(uncompressed.slice(1));
225
+ // Last 20 bytes
226
+ const addr = bytesToHex(hash.slice(12));
227
+ return '0x' + addr;
228
+ }
229
+
230
+ /**
231
+ * SLIP-0010 ed25519 key derivation from seed
232
+ * Solana uses ed25519, derived via SLIP-0010 (not BIP32 secp256k1)
233
+ */
234
+ function deriveEd25519Key(seed, path) {
235
+ // SLIP-0010 master key derivation
236
+ const I = hmac(sha512, new TextEncoder().encode('ed25519 seed'), seed);
237
+ let key = I.slice(0, 32);
238
+ let chainCode = I.slice(32);
239
+
240
+ // Parse path
241
+ const segments = path.replace('m/', '').split('/');
242
+ for (const seg of segments) {
243
+ const hardened = seg.endsWith("'");
244
+ const index = parseInt(seg.replace("'", ''), 10);
245
+ if (!hardened) throw new Error('SLIP-0010 ed25519 only supports hardened derivation');
246
+
247
+ const indexBuf = new Uint8Array(4);
248
+ const val = (index | 0x80000000) >>> 0;
249
+ indexBuf[0] = (val >> 24) & 0xff;
250
+ indexBuf[1] = (val >> 16) & 0xff;
251
+ indexBuf[2] = (val >> 8) & 0xff;
252
+ indexBuf[3] = val & 0xff;
253
+
254
+ const data = new Uint8Array(1 + 32 + 4);
255
+ data[0] = 0x00;
256
+ data.set(key, 1);
257
+ data.set(indexBuf, 33);
258
+
259
+ const derived = hmac(sha512, chainCode, data);
260
+ key = derived.slice(0, 32);
261
+ chainCode = derived.slice(32);
262
+ }
263
+
264
+ return key;
265
+ }
266
+
267
+ /**
268
+ * Derive SOL address from seed using SLIP-0010 ed25519
269
+ */
270
+ function deriveSOLAddress(seed, index = 0) {
271
+ const path = `m/44'/501'/${index}'/0'`;
272
+ const privateKey = deriveEd25519Key(seed, path);
273
+ const publicKey = ed25519.getPublicKey(privateKey);
274
+ return { address: base58Encode(publicKey), publicKey, privateKey, path };
275
+ }
276
+
277
+ /**
278
+ * Derive the blockchain address for a given chain from seed/public key
279
+ * @param {Uint8Array} seed - Master seed
280
+ * @param {string} chain - Chain code
281
+ * @param {number} index - Derivation index
282
+ * @returns {string} The derived address
283
+ */
284
+ export function deriveAddress(seed, chain, index = 0) {
285
+ // Solana-based chains
286
+ if (chain === 'SOL' || chain === 'USDC_SOL' || chain === 'USDT_SOL') {
287
+ return deriveSOLAddress(seed, index).address;
288
+ }
289
+
290
+ // secp256k1-based chains
291
+ const { publicKey } = deriveKeyPair(seed, chain, index);
292
+
293
+ switch (chain) {
294
+ case 'BTC':
295
+ return deriveBTCAddress(publicKey);
296
+ case 'BCH':
297
+ return deriveBCHAddress(publicKey);
298
+ case 'ETH':
299
+ case 'POL':
300
+ case 'BNB':
301
+ case 'USDC_ETH':
302
+ case 'USDC_POL':
303
+ case 'USDT_ETH':
304
+ case 'USDT_POL':
305
+ return deriveETHAddress(publicKey);
306
+ default:
307
+ throw new Error(`Unsupported chain for address derivation: ${chain}`);
308
+ }
309
+ }
310
+
64
311
  /**
65
312
  * Generate a new mnemonic phrase
66
313
  * @param {number} [words=12] - Number of words (12 or 24)
@@ -173,11 +420,23 @@ function signMessage(message, privateKey) {
173
420
  * @private
174
421
  */
175
422
  function deriveAddressInfo(seed, chain, index = 0) {
176
- const { publicKey, path } = deriveKeyPair(seed, chain, index);
423
+ const address = deriveAddress(seed, chain, index);
424
+ const path = getDerivationPath(chain, index);
425
+
426
+ // For SOL chains, the public key is derived differently (ed25519)
427
+ let publicKeyHex;
428
+ if (chain === 'SOL' || chain === 'USDC_SOL' || chain === 'USDT_SOL') {
429
+ const solInfo = deriveSOLAddress(seed, index);
430
+ publicKeyHex = bytesToHex(solInfo.publicKey);
431
+ } else {
432
+ const { publicKey } = deriveKeyPair(seed, chain, index);
433
+ publicKeyHex = bytesToHex(publicKey);
434
+ }
177
435
 
178
436
  return {
179
437
  chain,
180
- publicKey: bytesToHex(publicKey),
438
+ address,
439
+ publicKey: publicKeyHex,
181
440
  derivation_path: path,
182
441
  derivation_index: index,
183
442
  };
@@ -246,7 +505,7 @@ export class WalletClient {
246
505
  // The actual address would be derived client-side with full implementation
247
506
  return {
248
507
  chain: info.chain,
249
- address: info.publicKey.slice(0, 42), // Placeholder - real impl derives actual address
508
+ address: info.address,
250
509
  derivation_path: info.derivation_path,
251
510
  };
252
511
  });
@@ -300,7 +559,7 @@ export class WalletClient {
300
559
  const info = deriveAddressInfo(seed, chain, 0);
301
560
  return {
302
561
  chain: info.chain,
303
- address: info.publicKey.slice(0, 42), // Placeholder
562
+ address: info.address,
304
563
  derivation_path: info.derivation_path,
305
564
  };
306
565
  });
@@ -469,7 +728,7 @@ export class WalletClient {
469
728
  method: 'POST',
470
729
  body: JSON.stringify({
471
730
  chain: info.chain,
472
- address: info.publicKey.slice(0, 42), // Placeholder
731
+ address: info.address,
473
732
  derivation_index: info.derivation_index,
474
733
  derivation_path: info.derivation_path,
475
734
  }),