@profullstack/coinpay 0.4.6 → 0.4.8
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 +105 -11
- package/package.json +1 -1
- package/src/client.js +39 -0
- package/src/escrow.js +46 -2
- package/src/index.js +2 -0
- package/src/payments.js +5 -0
- package/src/wallet.js +5 -5
package/bin/coinpay.js
CHANGED
|
@@ -216,6 +216,7 @@ ${colors.cyan}Commands:${colors.reset}
|
|
|
216
216
|
refund <id> Refund funds to depositor
|
|
217
217
|
dispute <id> Open a dispute
|
|
218
218
|
events <id> Get escrow audit log
|
|
219
|
+
auth <id> Authenticate with escrow token
|
|
219
220
|
|
|
220
221
|
${colors.bright}webhook${colors.reset}
|
|
221
222
|
logs <business-id> Get webhook logs
|
|
@@ -226,11 +227,22 @@ ${colors.cyan}Wallet Options:${colors.reset}
|
|
|
226
227
|
--chains <BTC,ETH,...> Chains to derive (default: BTC,ETH,SOL,POL,BCH)
|
|
227
228
|
--chain <chain> Single chain for operations
|
|
228
229
|
--to <address> Recipient address
|
|
229
|
-
--amount <amount> Amount to send
|
|
230
|
+
--amount <amount> Amount to send (crypto)
|
|
231
|
+
--amount-fiat <amount> Amount to send (fiat, requires --fiat)
|
|
232
|
+
--fiat <currency> Fiat currency (USD, EUR, GBP, CAD, AUD, JPY, CHF, CNY, INR, BRL)
|
|
230
233
|
--password <pass> Wallet encryption password
|
|
231
234
|
--wallet-file <path> Custom wallet file (default: ~/.coinpay-wallet.gpg)
|
|
232
235
|
--no-save Don't save wallet locally after create/import
|
|
233
236
|
|
|
237
|
+
${colors.cyan}Escrow Options:${colors.reset}
|
|
238
|
+
--chain <chain> Blockchain (BTC, ETH, SOL, POL, BCH, etc.)
|
|
239
|
+
--amount <amount> Crypto amount to escrow
|
|
240
|
+
--amount-fiat <amount> Fiat amount to escrow (alternative to --amount)
|
|
241
|
+
--fiat <currency> Fiat currency (required with --amount-fiat)
|
|
242
|
+
--depositor <address> Depositor wallet address
|
|
243
|
+
--beneficiary <address> Beneficiary wallet address
|
|
244
|
+
--token <token> Release or beneficiary token (for auth/release/refund)
|
|
245
|
+
|
|
234
246
|
${colors.cyan}Swap Options:${colors.reset}
|
|
235
247
|
--from <coin> Source coin (e.g., BTC)
|
|
236
248
|
--to <coin> Destination coin (e.g., ETH)
|
|
@@ -249,6 +261,18 @@ ${colors.cyan}Examples:${colors.reset}
|
|
|
249
261
|
|
|
250
262
|
# Send transaction (auto-decrypts for signing)
|
|
251
263
|
coinpay wallet send --chain ETH --to 0x123... --amount 0.1
|
|
264
|
+
|
|
265
|
+
# Send transaction with fiat amount
|
|
266
|
+
coinpay wallet send --chain SOL --to abc123... --amount-fiat 10 --fiat USD
|
|
267
|
+
|
|
268
|
+
# Create escrow with crypto amount
|
|
269
|
+
coinpay escrow create --chain SOL --amount 0.5 --depositor abc... --beneficiary def...
|
|
270
|
+
|
|
271
|
+
# Create escrow with fiat amount
|
|
272
|
+
coinpay escrow create --chain SOL --amount-fiat 50 --fiat USD --depositor abc... --beneficiary def...
|
|
273
|
+
|
|
274
|
+
# Authenticate with escrow token
|
|
275
|
+
coinpay escrow auth escr_123 --token rel_abc456
|
|
252
276
|
|
|
253
277
|
# Swap BTC to ETH
|
|
254
278
|
coinpay swap quote --from BTC --to ETH --amount 0.1
|
|
@@ -1046,11 +1070,19 @@ async function handleWallet(subcommand, args, flags) {
|
|
|
1046
1070
|
const chain = (flags.chain || '').toUpperCase();
|
|
1047
1071
|
const to = flags.to;
|
|
1048
1072
|
const amount = flags.amount;
|
|
1073
|
+
const amountFiat = flags['amount-fiat'] ? parseFloat(flags['amount-fiat']) : undefined;
|
|
1074
|
+
const fiatCurrency = flags.fiat;
|
|
1049
1075
|
const priority = flags.priority || 'medium';
|
|
1050
1076
|
|
|
1051
|
-
if (!chain || !to || !amount) {
|
|
1052
|
-
print.error('Required: --chain, --to, --amount');
|
|
1077
|
+
if (!chain || !to || (!amount && !amountFiat)) {
|
|
1078
|
+
print.error('Required: --chain, --to, --amount (or --amount-fiat --fiat)');
|
|
1053
1079
|
print.info('Usage: coinpay wallet send --chain ETH --to 0x123... --amount 0.1');
|
|
1080
|
+
print.info(' or: coinpay wallet send --chain SOL --to abc123... --amount-fiat 10 --fiat USD');
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (amountFiat && !fiatCurrency) {
|
|
1085
|
+
print.error('--fiat is required when using --amount-fiat');
|
|
1054
1086
|
return;
|
|
1055
1087
|
}
|
|
1056
1088
|
|
|
@@ -1065,12 +1097,23 @@ async function handleWallet(subcommand, args, flags) {
|
|
|
1065
1097
|
}
|
|
1066
1098
|
|
|
1067
1099
|
try {
|
|
1100
|
+
let finalAmount = amount;
|
|
1101
|
+
|
|
1102
|
+
// Convert fiat to crypto if needed
|
|
1103
|
+
if (amountFiat && fiatCurrency) {
|
|
1104
|
+
print.info(`Converting ${fiatCurrency} ${amountFiat.toFixed(2)} to ${chain}...`);
|
|
1105
|
+
const apiClient = createClient();
|
|
1106
|
+
const conversion = await apiClient.convertFiatToCrypto(amountFiat, fiatCurrency, chain);
|
|
1107
|
+
finalAmount = conversion.cryptoAmount.toString();
|
|
1108
|
+
print.success(`Converting ${fiatCurrency} ${amountFiat.toFixed(2)} → ${conversion.cryptoAmount.toFixed(6)} ${chain} (rate: 1 ${chain} = ${fiatCurrency} ${conversion.rate.toFixed(2)})`);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1068
1111
|
const mnemonic = await getDecryptedMnemonic(flags);
|
|
1069
1112
|
const wallet = await WalletClient.fromSeed(mnemonic, { baseUrl });
|
|
1070
1113
|
|
|
1071
|
-
print.info(`Sending ${
|
|
1114
|
+
print.info(`Sending ${finalAmount} ${chain} to ${to}...`);
|
|
1072
1115
|
|
|
1073
|
-
const result = await wallet.send({ chain, to, amount, priority });
|
|
1116
|
+
const result = await wallet.send({ chain, to, amount: finalAmount, priority });
|
|
1074
1117
|
|
|
1075
1118
|
print.success('Transaction sent!');
|
|
1076
1119
|
if (result.tx_hash) {
|
|
@@ -1369,20 +1412,36 @@ async function handleEscrow(subcommand, args, flags) {
|
|
|
1369
1412
|
switch (subcommand) {
|
|
1370
1413
|
case 'create': {
|
|
1371
1414
|
const chain = flags.chain || flags.blockchain;
|
|
1372
|
-
const amount = parseFloat(flags.amount);
|
|
1415
|
+
const amount = flags.amount ? parseFloat(flags.amount) : undefined;
|
|
1416
|
+
const amountFiat = flags['amount-fiat'] ? parseFloat(flags['amount-fiat']) : undefined;
|
|
1417
|
+
const fiatCurrency = flags.fiat;
|
|
1373
1418
|
const depositor = flags.depositor || flags['depositor-address'];
|
|
1374
1419
|
const beneficiary = flags.beneficiary || flags['beneficiary-address'];
|
|
1375
1420
|
|
|
1376
|
-
if (!chain || !amount || !depositor || !beneficiary) {
|
|
1377
|
-
print.error('Required: --chain, --amount, --depositor, --beneficiary');
|
|
1421
|
+
if (!chain || (!amount && !amountFiat) || !depositor || !beneficiary) {
|
|
1422
|
+
print.error('Required: --chain, --amount (or --amount-fiat --fiat), --depositor, --beneficiary');
|
|
1378
1423
|
process.exit(1);
|
|
1379
1424
|
}
|
|
1380
1425
|
|
|
1381
|
-
|
|
1426
|
+
if (amountFiat && !fiatCurrency) {
|
|
1427
|
+
print.error('--fiat is required when using --amount-fiat');
|
|
1428
|
+
process.exit(1);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// Show conversion if using fiat
|
|
1432
|
+
let finalAmount = amount;
|
|
1433
|
+
if (amountFiat && fiatCurrency) {
|
|
1434
|
+
print.info(`Converting ${fiatCurrency} ${amountFiat.toFixed(2)} to ${chain}...`);
|
|
1435
|
+
const conversion = await client.convertFiatToCrypto(amountFiat, fiatCurrency, chain);
|
|
1436
|
+
finalAmount = conversion.cryptoAmount;
|
|
1437
|
+
print.success(`Converting ${fiatCurrency} ${amountFiat.toFixed(2)} → ${finalAmount.toFixed(6)} ${chain} (rate: 1 ${chain} = ${fiatCurrency} ${conversion.rate.toFixed(2)})`);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
print.info(`Creating escrow: ${finalAmount} ${chain}`);
|
|
1382
1441
|
|
|
1383
1442
|
const escrow = await client.createEscrow({
|
|
1384
1443
|
chain,
|
|
1385
|
-
amount,
|
|
1444
|
+
amount: finalAmount,
|
|
1386
1445
|
depositorAddress: depositor,
|
|
1387
1446
|
beneficiaryAddress: beneficiary,
|
|
1388
1447
|
metadata: flags.metadata ? JSON.parse(flags.metadata) : undefined,
|
|
@@ -1393,7 +1452,9 @@ async function handleEscrow(subcommand, args, flags) {
|
|
|
1393
1452
|
print.info(` Deposit to: ${escrow.escrowAddress}`);
|
|
1394
1453
|
print.info(` Status: ${escrow.status}`);
|
|
1395
1454
|
print.warn(` Release Token: ${escrow.releaseToken}`);
|
|
1455
|
+
print.warn(` Beneficiary Token: ${escrow.beneficiaryToken}`);
|
|
1396
1456
|
print.warn(' ⚠️ Save these tokens!');
|
|
1457
|
+
print.info(` Manage: coinpay escrow auth ${escrow.id} --token <token>`);
|
|
1397
1458
|
|
|
1398
1459
|
if (flags.json) print.json(escrow);
|
|
1399
1460
|
break;
|
|
@@ -1476,9 +1537,42 @@ async function handleEscrow(subcommand, args, flags) {
|
|
|
1476
1537
|
break;
|
|
1477
1538
|
}
|
|
1478
1539
|
|
|
1540
|
+
case 'auth': {
|
|
1541
|
+
const id = args[0];
|
|
1542
|
+
const token = flags.token;
|
|
1543
|
+
if (!id || !token) {
|
|
1544
|
+
print.error('Required: <id> --token <token>');
|
|
1545
|
+
process.exit(1);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
const auth = await client.authenticateEscrow(id, token);
|
|
1549
|
+
print.success(`Authenticated as: ${auth.role}`);
|
|
1550
|
+
print.info(`Escrow Details:`);
|
|
1551
|
+
print.info(` ID: ${auth.escrow.id}`);
|
|
1552
|
+
print.info(` Status: ${auth.escrow.status}`);
|
|
1553
|
+
print.info(` Chain: ${auth.escrow.chain}`);
|
|
1554
|
+
print.info(` Amount: ${auth.escrow.amount}`);
|
|
1555
|
+
print.info(` Depositor: ${auth.escrow.depositorAddress}`);
|
|
1556
|
+
print.info(` Beneficiary: ${auth.escrow.beneficiaryAddress}`);
|
|
1557
|
+
|
|
1558
|
+
// Show available actions based on role and status
|
|
1559
|
+
if (auth.escrow.status === 'funded') {
|
|
1560
|
+
if (auth.role === 'depositor') {
|
|
1561
|
+
print.info(`Available actions: release, refund, dispute`);
|
|
1562
|
+
} else if (auth.role === 'beneficiary') {
|
|
1563
|
+
print.info(`Available actions: dispute`);
|
|
1564
|
+
}
|
|
1565
|
+
} else if (auth.escrow.status === 'pending') {
|
|
1566
|
+
print.info(`Waiting for deposit to: ${auth.escrow.escrowAddress}`);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
if (flags.json) print.json(auth);
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1479
1573
|
default:
|
|
1480
1574
|
print.error(`Unknown escrow command: ${subcommand}`);
|
|
1481
|
-
print.info('Available: create, get, list, release, refund, dispute, events');
|
|
1575
|
+
print.info('Available: create, get, list, release, refund, dispute, events, auth');
|
|
1482
1576
|
process.exit(1);
|
|
1483
1577
|
}
|
|
1484
1578
|
}
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -306,6 +306,34 @@ export class CoinPayClient {
|
|
|
306
306
|
return this.request(`/rates?crypto=${cryptocurrency}&fiat=${fiatCurrency}`);
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Convert fiat amount to cryptocurrency amount
|
|
311
|
+
* @param {number} fiatAmount - Fiat amount to convert
|
|
312
|
+
* @param {string} fiatCurrency - Fiat currency code (USD, EUR, etc.)
|
|
313
|
+
* @param {string} cryptoCurrency - Cryptocurrency code (BTC, ETH, SOL, etc.)
|
|
314
|
+
* @returns {Promise<Object>} { cryptoAmount, rate, fiat, crypto }
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* const result = await client.convertFiatToCrypto(50, 'USD', 'SOL');
|
|
318
|
+
* console.log(`$50 USD = ${result.cryptoAmount} SOL (rate: 1 SOL = $${result.rate})`);
|
|
319
|
+
*/
|
|
320
|
+
async convertFiatToCrypto(fiatAmount, fiatCurrency, cryptoCurrency) {
|
|
321
|
+
const rateData = await this.request(`/rates?coin=${cryptoCurrency}&fiat=${fiatCurrency}`);
|
|
322
|
+
|
|
323
|
+
if (!rateData.success || !rateData.rate) {
|
|
324
|
+
throw new Error(`Failed to get exchange rate for ${cryptoCurrency}/${fiatCurrency}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const cryptoAmount = fiatAmount / rateData.rate;
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
cryptoAmount: cryptoAmount,
|
|
331
|
+
rate: rateData.rate,
|
|
332
|
+
fiat: fiatCurrency,
|
|
333
|
+
crypto: cryptoCurrency
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
309
337
|
/**
|
|
310
338
|
* Get multiple exchange rates
|
|
311
339
|
* @param {string[]} cryptocurrencies - Array of cryptocurrency codes
|
|
@@ -483,6 +511,17 @@ export class CoinPayClient {
|
|
|
483
511
|
const { waitForEscrow } = await import('./escrow.js');
|
|
484
512
|
return waitForEscrow(this, escrowId, options);
|
|
485
513
|
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Authenticate with escrow using token
|
|
517
|
+
* @param {string} escrowId - Escrow ID
|
|
518
|
+
* @param {string} token - Release token or beneficiary token
|
|
519
|
+
* @returns {Promise<Object>} { escrow, role }
|
|
520
|
+
*/
|
|
521
|
+
async authenticateEscrow(escrowId, token) {
|
|
522
|
+
const { authenticateEscrow } = await import('./escrow.js');
|
|
523
|
+
return authenticateEscrow(this, escrowId, token);
|
|
524
|
+
}
|
|
486
525
|
}
|
|
487
526
|
|
|
488
527
|
export default CoinPayClient;
|
package/src/escrow.js
CHANGED
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
* @param {CoinPayClient} client - API client instance
|
|
32
32
|
* @param {Object} params - Escrow parameters
|
|
33
33
|
* @param {string} params.chain - Blockchain (BTC, ETH, SOL, POL, etc.)
|
|
34
|
-
* @param {number} params.amount - Crypto amount to escrow
|
|
34
|
+
* @param {number} [params.amount] - Crypto amount to escrow
|
|
35
|
+
* @param {number} [params.amountFiat] - Fiat amount to escrow (alternative to amount)
|
|
36
|
+
* @param {string} [params.fiatCurrency] - Fiat currency (required if amountFiat is used)
|
|
35
37
|
* @param {string} params.depositorAddress - Wallet address for refunds
|
|
36
38
|
* @param {string} params.beneficiaryAddress - Wallet address for releases
|
|
37
39
|
* @param {string} [params.arbiterAddress] - Optional dispute resolver address
|
|
@@ -42,15 +44,33 @@
|
|
|
42
44
|
export async function createEscrow(client, {
|
|
43
45
|
chain,
|
|
44
46
|
amount,
|
|
47
|
+
amountFiat,
|
|
48
|
+
fiatCurrency,
|
|
45
49
|
depositorAddress,
|
|
46
50
|
beneficiaryAddress,
|
|
47
51
|
arbiterAddress,
|
|
48
52
|
metadata,
|
|
49
53
|
expiresInHours,
|
|
50
54
|
}) {
|
|
55
|
+
let finalAmount = amount;
|
|
56
|
+
|
|
57
|
+
// If fiat amount is provided, convert to crypto first
|
|
58
|
+
if (amountFiat && fiatCurrency) {
|
|
59
|
+
if (amount) {
|
|
60
|
+
throw new Error('Cannot specify both amount and amountFiat. Use one or the other.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const conversion = await client.convertFiatToCrypto(amountFiat, fiatCurrency, chain);
|
|
64
|
+
finalAmount = conversion.cryptoAmount;
|
|
65
|
+
} else if (amountFiat && !fiatCurrency) {
|
|
66
|
+
throw new Error('fiatCurrency is required when amountFiat is specified');
|
|
67
|
+
} else if (!amount && !amountFiat) {
|
|
68
|
+
throw new Error('Either amount or amountFiat must be specified');
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
const body = {
|
|
52
72
|
chain,
|
|
53
|
-
amount,
|
|
73
|
+
amount: finalAmount,
|
|
54
74
|
depositor_address: depositorAddress,
|
|
55
75
|
beneficiary_address: beneficiaryAddress,
|
|
56
76
|
};
|
|
@@ -165,6 +185,30 @@ export async function disputeEscrow(client, escrowId, token, reason) {
|
|
|
165
185
|
return normalizeEscrow(data);
|
|
166
186
|
}
|
|
167
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Authenticate with escrow using token
|
|
190
|
+
* @param {CoinPayClient} client
|
|
191
|
+
* @param {string} escrowId - Escrow ID
|
|
192
|
+
* @param {string} token - Release token or beneficiary token
|
|
193
|
+
* @returns {Promise<Object>} { escrow, role } where role is 'depositor' or 'beneficiary'
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* const auth = await client.authenticateEscrow('escrow_123', 'rel_abc123');
|
|
197
|
+
* console.log(`Authenticated as: ${auth.role}`);
|
|
198
|
+
* console.log(`Escrow status: ${auth.escrow.status}`);
|
|
199
|
+
*/
|
|
200
|
+
export async function authenticateEscrow(client, escrowId, token) {
|
|
201
|
+
const data = await client.request(`/escrow/${escrowId}/auth`, {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: JSON.stringify({ token }),
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
escrow: normalizeEscrow(data.escrow),
|
|
208
|
+
role: data.role,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
168
212
|
/**
|
|
169
213
|
* Get escrow event log
|
|
170
214
|
* @param {CoinPayClient} client
|
package/src/index.js
CHANGED
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
disputeEscrow,
|
|
64
64
|
getEscrowEvents,
|
|
65
65
|
waitForEscrow,
|
|
66
|
+
authenticateEscrow,
|
|
66
67
|
} from './escrow.js';
|
|
67
68
|
|
|
68
69
|
// Wallet exports
|
|
@@ -106,6 +107,7 @@ export {
|
|
|
106
107
|
disputeEscrow,
|
|
107
108
|
getEscrowEvents,
|
|
108
109
|
waitForEscrow,
|
|
110
|
+
authenticateEscrow,
|
|
109
111
|
|
|
110
112
|
// Wallet
|
|
111
113
|
WalletClient,
|
package/src/payments.js
CHANGED
package/src/wallet.js
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
import * as bip39 from '@scure/bip39';
|
|
10
10
|
import { wordlist } from '@scure/bip39/wordlists/english';
|
|
11
11
|
import { HDKey } from '@scure/bip32';
|
|
12
|
-
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
12
|
+
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
13
13
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
14
|
-
import { ripemd160 } from '@noble/hashes/
|
|
14
|
+
import { ripemd160 } from '@noble/hashes/legacy.js';
|
|
15
15
|
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
16
|
-
import { ed25519 } from '@noble/curves/ed25519';
|
|
16
|
+
import { ed25519 } from '@noble/curves/ed25519.js';
|
|
17
17
|
import { hmac } from '@noble/hashes/hmac.js';
|
|
18
18
|
import { sha512 } from '@noble/hashes/sha2.js';
|
|
19
19
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
@@ -218,8 +218,8 @@ function cashAddrPolymod(values) {
|
|
|
218
218
|
*/
|
|
219
219
|
function deriveETHAddress(compressedPubKey) {
|
|
220
220
|
// Decompress: get uncompressed point (65 bytes: 04 || x || y)
|
|
221
|
-
const point = secp256k1.
|
|
222
|
-
const uncompressed = point.
|
|
221
|
+
const point = secp256k1.Point.fromBytes(compressedPubKey);
|
|
222
|
+
const uncompressed = point.toBytes(false); // 65 bytes with 04 prefix
|
|
223
223
|
// Keccak256 of the 64 bytes (without 04 prefix)
|
|
224
224
|
const hash = keccak_256(uncompressed.slice(1));
|
|
225
225
|
// Last 20 bytes — EIP-55 checksummed
|