xyqon 0.2.0 → 0.4.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 +2 -0
- package/package.json +1 -1
- package/src/cli.js +7 -3
- package/src/index.js +72 -7
package/README.md
CHANGED
|
@@ -67,6 +67,7 @@ xyqon send \
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
The transaction is broadcast to reachable public nodes. A miner must include it in a block before the receiver sees the confirmed balance.
|
|
70
|
+
The client checks the best reachable public chain before broadcasting and refuses sends that exceed the wallet's confirmed XYQON balance.
|
|
70
71
|
|
|
71
72
|
## Create And Send Coins
|
|
72
73
|
|
|
@@ -83,6 +84,7 @@ xyqon coin send \
|
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
Coin creation and coin sends are signed 0 XYQON transactions. A miner must include them before the new coin or transfer appears on-chain.
|
|
87
|
+
Before broadcasting a coin transfer, the client checks the best reachable public chain and refuses sends that exceed the wallet's confirmed coin balance.
|
|
86
88
|
|
|
87
89
|
## Mint And Send NFTs
|
|
88
90
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -42,7 +42,7 @@ ${color('bold', 'Usage')}
|
|
|
42
42
|
xyqon wallet new --name <NAME> [--out <FILE>]
|
|
43
43
|
xyqon wallet show [--wallet <FILE>] [--private]
|
|
44
44
|
xyqon balance [--wallet <FILE>] [--address <PUBLIC_KEY>]
|
|
45
|
-
xyqon send --to <PUBLIC_KEY> --amount <AMOUNT> [--wallet <FILE>]
|
|
45
|
+
xyqon send --to <PUBLIC_KEY> --amount <AMOUNT> [--fee <AMOUNT>] [--wallet <FILE>]
|
|
46
46
|
xyqon coin create --symbol <SYMBOL> --name <NAME> --supply <AMOUNT> [--wallet <FILE>]
|
|
47
47
|
xyqon coin send --symbol <SYMBOL> --to <PUBLIC_KEY> --amount <AMOUNT> [--wallet <FILE>]
|
|
48
48
|
xyqon nft mint --collection <SYMBOL> --token-id <ID> --name <NAME> [--image-url <URL>] [--wallet <FILE>]
|
|
@@ -152,14 +152,17 @@ async function run() {
|
|
|
152
152
|
wallet,
|
|
153
153
|
recipient: options.to,
|
|
154
154
|
amount: options.amount,
|
|
155
|
+
fee: options.fee,
|
|
155
156
|
peers: peersFromOptions(options)
|
|
156
157
|
});
|
|
157
158
|
|
|
158
159
|
printHero('Transaction Sent');
|
|
159
160
|
console.log(`${color('bold', 'Amount:')} ${result.transaction.amount} XYQON`);
|
|
161
|
+
console.log(`${color('bold', 'Fee:')} ${result.transaction.fee} XYQON`);
|
|
160
162
|
console.log(`${color('bold', 'To:')} ${result.transaction.recipient}`);
|
|
161
163
|
console.log(`${color('bold', 'Transaction ID:')} ${result.transactionId}`);
|
|
162
|
-
console.log(`${color('
|
|
164
|
+
console.log(`${color('dim', 'Checked balance:')} ${result.preflight.balance} XYQON at block ${result.preflight.blockHeight}`);
|
|
165
|
+
console.log(`${color('green', 'Delivered:')} ${result.acceptedBy}/${result.peers.length} reachable nodes`);
|
|
163
166
|
console.log(color('yellow', 'The receiver balance updates after a miner includes this transaction in a block.'));
|
|
164
167
|
return;
|
|
165
168
|
}
|
|
@@ -217,7 +220,8 @@ async function run() {
|
|
|
217
220
|
console.log(`${color('bold', 'Amount:')} ${result.transaction.asset_operation.TransferCoin.amount} ${result.transaction.asset_operation.TransferCoin.symbol}`);
|
|
218
221
|
console.log(`${color('bold', 'To:')} ${result.transaction.recipient}`);
|
|
219
222
|
console.log(`${color('bold', 'Transaction ID:')} ${result.transactionId}`);
|
|
220
|
-
console.log(`${color('
|
|
223
|
+
console.log(`${color('dim', 'Checked balance:')} ${result.preflight.balance} ${result.preflight.symbol} at block ${result.preflight.blockHeight}`);
|
|
224
|
+
console.log(`${color('green', 'Delivered:')} ${result.acceptedBy}/${result.peers.length} reachable nodes`);
|
|
221
225
|
return;
|
|
222
226
|
}
|
|
223
227
|
|
package/src/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export const DEFAULT_PEERS = [
|
|
|
8
8
|
'143.244.149.8:7101',
|
|
9
9
|
'147.182.138.183:7101'
|
|
10
10
|
];
|
|
11
|
+
const XYQON_EPSILON = 0.00000001;
|
|
12
|
+
export const DEFAULT_XYQON_TRANSACTION_FEE = 0.001;
|
|
11
13
|
|
|
12
14
|
export const DEFAULT_WALLET_PATH = 'xyqon.wallet.json';
|
|
13
15
|
|
|
@@ -102,8 +104,11 @@ function stringifyAssetOperationForSigning(assetOperation) {
|
|
|
102
104
|
throw new Error('unknown asset operation');
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
export function transactionPayload(sender, recipient, amount, senderPublicKey, assetOperation = null) {
|
|
106
|
-
|
|
107
|
+
export function transactionPayload(sender, recipient, amount, senderPublicKey, assetOperation = null, fee = 0) {
|
|
108
|
+
let base = `${sender}|${recipient}|${Number(amount).toFixed(8)}|${senderPublicKey}`;
|
|
109
|
+
if (Math.abs(Number(fee)) > XYQON_EPSILON) {
|
|
110
|
+
base = `${base}|fee:${Number(fee).toFixed(8)}`;
|
|
111
|
+
}
|
|
107
112
|
const serializedAsset = stringifyAssetOperationForSigning(assetOperation);
|
|
108
113
|
return serializedAsset ? `${base}|${serializedAsset}` : base;
|
|
109
114
|
}
|
|
@@ -173,22 +178,27 @@ export function normalizeTokenAmount(amount, label = 'amount') {
|
|
|
173
178
|
return numeric;
|
|
174
179
|
}
|
|
175
180
|
|
|
176
|
-
export function createSignedTransaction(wallet, recipient, amount) {
|
|
181
|
+
export function createSignedTransaction(wallet, recipient, amount, fee = DEFAULT_XYQON_TRANSACTION_FEE) {
|
|
177
182
|
const numericAmount = Number(amount);
|
|
178
183
|
if (!Number.isFinite(numericAmount) || numericAmount <= 0) {
|
|
179
184
|
throw new Error('amount must be a positive number');
|
|
180
185
|
}
|
|
186
|
+
const numericFee = Number(fee);
|
|
187
|
+
if (!Number.isFinite(numericFee) || numericFee < 0) {
|
|
188
|
+
throw new Error('fee must be a non-negative number');
|
|
189
|
+
}
|
|
181
190
|
|
|
182
191
|
const privateKey = hexToBytes(wallet.private_key);
|
|
183
192
|
const senderPublicKey = assertWalletMatchesPrivateKey(wallet, privateKey);
|
|
184
193
|
|
|
185
|
-
const payload = transactionPayload(wallet.name, recipient, numericAmount, senderPublicKey);
|
|
194
|
+
const payload = transactionPayload(wallet.name, recipient, numericAmount, senderPublicKey, null, numericFee);
|
|
186
195
|
const signature = ed25519.sign(new TextEncoder().encode(payload), privateKey);
|
|
187
196
|
|
|
188
197
|
return {
|
|
189
198
|
sender: wallet.name,
|
|
190
199
|
recipient,
|
|
191
200
|
amount: numericAmount,
|
|
201
|
+
fee: numericFee,
|
|
192
202
|
sender_public_key: senderPublicKey,
|
|
193
203
|
signature: bytesToHex(signature)
|
|
194
204
|
};
|
|
@@ -401,7 +411,7 @@ export function calculateBalances(chain) {
|
|
|
401
411
|
if (transaction.asset_operation) {
|
|
402
412
|
continue;
|
|
403
413
|
}
|
|
404
|
-
debit(transaction.sender_public_key, transaction.amount);
|
|
414
|
+
debit(transaction.sender_public_key, transaction.amount + (transaction.fee ?? 0));
|
|
405
415
|
credit(transaction.recipient, transaction.amount);
|
|
406
416
|
}
|
|
407
417
|
}
|
|
@@ -524,6 +534,52 @@ export async function getBalance(addressOrWallet, seedPeers = DEFAULT_PEERS) {
|
|
|
524
534
|
};
|
|
525
535
|
}
|
|
526
536
|
|
|
537
|
+
export async function assertSpendableXyqonBalance(wallet, amount, seedPeers = DEFAULT_PEERS, fee = 0) {
|
|
538
|
+
const numericAmount = Number(amount);
|
|
539
|
+
const numericFee = Number(fee);
|
|
540
|
+
const { chain, sourcePeer, peers } = await getBestChain(seedPeers);
|
|
541
|
+
const balances = calculateBalances(chain);
|
|
542
|
+
const balance = balances.get(wallet.public_key) ?? 0;
|
|
543
|
+
const totalDebit = numericAmount + numericFee;
|
|
544
|
+
|
|
545
|
+
if (balance + XYQON_EPSILON < totalDebit) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`insufficient confirmed XYQON balance; balance is ${balance}, attempted to spend ${numericAmount} plus fee ${numericFee}`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
balance,
|
|
553
|
+
fee: numericFee,
|
|
554
|
+
totalDebit,
|
|
555
|
+
blockHeight: chain.chain.at(-1)?.index ?? 0,
|
|
556
|
+
sourcePeer,
|
|
557
|
+
peers
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export async function assertSpendableCoinBalance(wallet, symbol, amount, seedPeers = DEFAULT_PEERS) {
|
|
562
|
+
const normalizedSymbol = normalizeCoinSymbol(symbol);
|
|
563
|
+
const numericAmount = Number(amount);
|
|
564
|
+
const { chain, sourcePeer, peers } = await getBestChain(seedPeers);
|
|
565
|
+
const holding = listCoinHoldings(chain, wallet).find((coin) => coin.symbol === normalizedSymbol);
|
|
566
|
+
const balance = holding?.balance ?? 0;
|
|
567
|
+
|
|
568
|
+
if (balance + XYQON_EPSILON < numericAmount) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`insufficient confirmed ${normalizedSymbol} balance; balance is ${balance}, attempted to spend ${numericAmount}`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
symbol: normalizedSymbol,
|
|
576
|
+
balance,
|
|
577
|
+
blockHeight: chain.chain.at(-1)?.index ?? 0,
|
|
578
|
+
sourcePeer,
|
|
579
|
+
peers
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
527
583
|
export async function getCreatedCoins(addressOrWallet, seedPeers = DEFAULT_PEERS) {
|
|
528
584
|
const { chain, sourcePeer, peers } = await getBestChain(seedPeers);
|
|
529
585
|
return {
|
|
@@ -581,10 +637,12 @@ export async function broadcastTransaction(transaction, seedPeers = DEFAULT_PEER
|
|
|
581
637
|
};
|
|
582
638
|
}
|
|
583
639
|
|
|
584
|
-
export async function sendTransaction({ wallet, recipient, amount, peers = DEFAULT_PEERS }) {
|
|
585
|
-
const transaction = createSignedTransaction(wallet, recipient, amount);
|
|
640
|
+
export async function sendTransaction({ wallet, recipient, amount, fee = DEFAULT_XYQON_TRANSACTION_FEE, peers = DEFAULT_PEERS }) {
|
|
641
|
+
const transaction = createSignedTransaction(wallet, recipient, amount, fee);
|
|
642
|
+
const preflight = await assertSpendableXyqonBalance(wallet, transaction.amount, peers, transaction.fee);
|
|
586
643
|
return {
|
|
587
644
|
transaction,
|
|
645
|
+
preflight,
|
|
588
646
|
...(await broadcastTransaction(transaction, peers))
|
|
589
647
|
};
|
|
590
648
|
}
|
|
@@ -599,8 +657,15 @@ export async function createCoin({ wallet, symbol, name, supply, peers = DEFAULT
|
|
|
599
657
|
|
|
600
658
|
export async function sendCoin({ wallet, recipient, symbol, amount, peers = DEFAULT_PEERS }) {
|
|
601
659
|
const transaction = createCoinTransferTransaction(wallet, { recipient, symbol, amount });
|
|
660
|
+
const preflight = await assertSpendableCoinBalance(
|
|
661
|
+
wallet,
|
|
662
|
+
transaction.asset_operation.TransferCoin.symbol,
|
|
663
|
+
transaction.asset_operation.TransferCoin.amount,
|
|
664
|
+
peers
|
|
665
|
+
);
|
|
602
666
|
return {
|
|
603
667
|
transaction,
|
|
668
|
+
preflight,
|
|
604
669
|
...(await broadcastTransaction(transaction, peers))
|
|
605
670
|
};
|
|
606
671
|
}
|