xyqon 0.1.0 → 0.2.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
@@ -8,6 +8,8 @@ It can:
8
8
  - Show your public key
9
9
  - Check your balance from live public nodes
10
10
  - Send a signed transaction to the network
11
+ - Create and send coins on the XYQON network
12
+ - Mint and transfer NFTs
11
13
  - Show currently reachable nodes
12
14
 
13
15
  You do not need to mine and you do not need to run a full node.
@@ -15,13 +17,13 @@ You do not need to mine and you do not need to run a full node.
15
17
  ## Install
16
18
 
17
19
  ```bash
18
- npm install -g xyqon-client
20
+ npm install -g xyqon
19
21
  ```
20
22
 
21
23
  Or run without installing:
22
24
 
23
25
  ```bash
24
- npx xyqon-client help
26
+ npx xyqon help
25
27
  ```
26
28
 
27
29
  ## Create A Wallet
@@ -66,6 +68,37 @@ xyqon send \
66
68
 
67
69
  The transaction is broadcast to reachable public nodes. A miner must include it in a block before the receiver sees the confirmed balance.
68
70
 
71
+ ## Create And Send Coins
72
+
73
+ ```bash
74
+ xyqon coin create \
75
+ --symbol GAME \
76
+ --name "Game Coin" \
77
+ --supply 1000000
78
+
79
+ xyqon coin send \
80
+ --symbol GAME \
81
+ --to RECIPIENT_PUBLIC_KEY \
82
+ --amount 25
83
+ ```
84
+
85
+ Coin creation and coin sends are signed 0 XYQON transactions. A miner must include them before the new coin or transfer appears on-chain.
86
+
87
+ ## Mint And Send NFTs
88
+
89
+ ```bash
90
+ xyqon nft mint \
91
+ --collection ITEMS \
92
+ --token-id sword-001 \
93
+ --name "Iron Sword" \
94
+ --image-url https://example.com/iron-sword.png
95
+
96
+ xyqon nft send \
97
+ --collection ITEMS \
98
+ --token-id sword-001 \
99
+ --to RECIPIENT_PUBLIC_KEY
100
+ ```
101
+
69
102
  ## Show Nodes
70
103
 
71
104
  ```bash
@@ -95,8 +128,16 @@ import {
95
128
  createWallet,
96
129
  loadWallet,
97
130
  getBalance,
98
- sendTransaction
99
- } from 'xyqon-client';
131
+ sendTransaction,
132
+ createCoin,
133
+ sendCoin,
134
+ mintNft,
135
+ transferNft,
136
+ getCreatedCoins,
137
+ getCreatedNfts,
138
+ getOwnedNfts,
139
+ getCoinHoldings
140
+ } from 'xyqon';
100
141
 
101
142
  const wallet = createWallet('Cathy');
102
143
  console.log(wallet.public_key);
@@ -109,4 +150,38 @@ await sendTransaction({
109
150
  recipient: 'RECIPIENT_PUBLIC_KEY',
110
151
  amount: 1
111
152
  });
153
+
154
+ await createCoin({
155
+ wallet,
156
+ symbol: 'GAME',
157
+ name: 'Game Coin',
158
+ supply: 1000000
159
+ });
160
+
161
+ await sendCoin({
162
+ wallet,
163
+ recipient: 'RECIPIENT_PUBLIC_KEY',
164
+ symbol: 'GAME',
165
+ amount: 25
166
+ });
167
+
168
+ await mintNft({
169
+ wallet,
170
+ collection: 'ITEMS',
171
+ tokenId: 'sword-001',
172
+ name: 'Iron Sword',
173
+ imageUrl: 'https://example.com/iron-sword.png'
174
+ });
175
+
176
+ await transferNft({
177
+ wallet,
178
+ recipient: 'RECIPIENT_PUBLIC_KEY',
179
+ collection: 'ITEMS',
180
+ tokenId: 'sword-001'
181
+ });
182
+
183
+ const createdCoins = await getCreatedCoins(wallet);
184
+ const createdNfts = await getCreatedNfts(wallet);
185
+ const ownedNfts = await getOwnedNfts(wallet);
186
+ const coinHoldings = await getCoinHoldings(wallet);
112
187
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xyqon",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Friendly wallet CLI and JavaScript client for the XYQON network.",
5
5
  "keywords": [
6
6
  "xyqon",
@@ -23,7 +23,7 @@
23
23
  "exports": {
24
24
  ".": "./src/index.js"
25
25
  },
26
- "main": "index.js",
26
+ "main": "./src/index.js",
27
27
  "bin": {
28
28
  "xyqon": "src/cli.js",
29
29
  "xyqon-client": "src/cli.js"
package/src/cli.js CHANGED
@@ -3,11 +3,15 @@ import {
3
3
  DEFAULT_PEERS,
4
4
  DEFAULT_WALLET_PATH,
5
5
  createWallet,
6
+ createCoin,
6
7
  discoverPeers,
7
8
  getBalance,
8
9
  getBestChain,
9
10
  loadWallet,
11
+ mintNft,
10
12
  saveWallet,
13
+ sendCoin,
14
+ transferNft,
11
15
  sendTransaction
12
16
  } from './index.js';
13
17
 
@@ -39,6 +43,10 @@ ${color('bold', 'Usage')}
39
43
  xyqon wallet show [--wallet <FILE>] [--private]
40
44
  xyqon balance [--wallet <FILE>] [--address <PUBLIC_KEY>]
41
45
  xyqon send --to <PUBLIC_KEY> --amount <AMOUNT> [--wallet <FILE>]
46
+ xyqon coin create --symbol <SYMBOL> --name <NAME> --supply <AMOUNT> [--wallet <FILE>]
47
+ xyqon coin send --symbol <SYMBOL> --to <PUBLIC_KEY> --amount <AMOUNT> [--wallet <FILE>]
48
+ xyqon nft mint --collection <SYMBOL> --token-id <ID> --name <NAME> [--image-url <URL>] [--wallet <FILE>]
49
+ xyqon nft send --collection <SYMBOL> --token-id <ID> --to <PUBLIC_KEY> [--wallet <FILE>]
42
50
  xyqon nodes
43
51
 
44
52
  ${color('bold', 'Network options')}
@@ -88,8 +96,8 @@ function peersFromOptions(options) {
88
96
  async function run() {
89
97
  const argv = process.argv.slice(2);
90
98
  const command = argv[0];
91
- const subcommand = command === 'wallet' ? argv[1] : undefined;
92
- const rest = command === 'wallet' ? argv.slice(2) : argv.slice(1);
99
+ const subcommand = command === 'wallet' || command === 'coin' || command === 'nft' ? argv[1] : undefined;
100
+ const rest = command === 'wallet' || command === 'coin' || command === 'nft' ? argv.slice(2) : argv.slice(1);
93
101
  const options = parseArgs(rest);
94
102
 
95
103
  if (!command || command === 'help' || command === '--help' || command === '-h') {
@@ -156,6 +164,120 @@ async function run() {
156
164
  return;
157
165
  }
158
166
 
167
+ if (command === 'coin' && subcommand === 'create') {
168
+ if (!options.symbol) {
169
+ throw new Error('coin create requires --symbol <SYMBOL>');
170
+ }
171
+ if (!options.name) {
172
+ throw new Error('coin create requires --name <NAME>');
173
+ }
174
+ if (!options.supply) {
175
+ throw new Error('coin create requires --supply <AMOUNT>');
176
+ }
177
+
178
+ const wallet = await loadWallet(options.wallet ?? DEFAULT_WALLET_PATH);
179
+ const result = await createCoin({
180
+ wallet,
181
+ symbol: options.symbol,
182
+ name: options.name,
183
+ supply: options.supply,
184
+ peers: peersFromOptions(options)
185
+ });
186
+
187
+ printHero('Coin Created');
188
+ console.log(`${color('bold', 'Symbol:')} ${result.transaction.asset_operation.CreateCoin.symbol}`);
189
+ console.log(`${color('bold', 'Supply:')} ${result.transaction.asset_operation.CreateCoin.supply}`);
190
+ console.log(`${color('bold', 'Transaction ID:')} ${result.transactionId}`);
191
+ console.log(`${color('green', 'Broadcast:')} ${result.acceptedBy}/${result.peers.length} reachable nodes`);
192
+ console.log(color('yellow', 'The coin exists after a miner includes this transaction in a block.'));
193
+ return;
194
+ }
195
+
196
+ if (command === 'coin' && subcommand === 'send') {
197
+ if (!options.symbol) {
198
+ throw new Error('coin send requires --symbol <SYMBOL>');
199
+ }
200
+ if (!options.to) {
201
+ throw new Error('coin send requires --to <PUBLIC_KEY>');
202
+ }
203
+ if (!options.amount) {
204
+ throw new Error('coin send requires --amount <AMOUNT>');
205
+ }
206
+
207
+ const wallet = await loadWallet(options.wallet ?? DEFAULT_WALLET_PATH);
208
+ const result = await sendCoin({
209
+ wallet,
210
+ recipient: options.to,
211
+ symbol: options.symbol,
212
+ amount: options.amount,
213
+ peers: peersFromOptions(options)
214
+ });
215
+
216
+ printHero('Coin Sent');
217
+ console.log(`${color('bold', 'Amount:')} ${result.transaction.asset_operation.TransferCoin.amount} ${result.transaction.asset_operation.TransferCoin.symbol}`);
218
+ console.log(`${color('bold', 'To:')} ${result.transaction.recipient}`);
219
+ console.log(`${color('bold', 'Transaction ID:')} ${result.transactionId}`);
220
+ console.log(`${color('green', 'Broadcast:')} ${result.acceptedBy}/${result.peers.length} reachable nodes`);
221
+ return;
222
+ }
223
+
224
+ if (command === 'nft' && subcommand === 'mint') {
225
+ if (!options.collection) {
226
+ throw new Error('nft mint requires --collection <SYMBOL>');
227
+ }
228
+ if (!options['token-id']) {
229
+ throw new Error('nft mint requires --token-id <ID>');
230
+ }
231
+ if (!options.name) {
232
+ throw new Error('nft mint requires --name <NAME>');
233
+ }
234
+
235
+ const wallet = await loadWallet(options.wallet ?? DEFAULT_WALLET_PATH);
236
+ const result = await mintNft({
237
+ wallet,
238
+ collection: options.collection,
239
+ tokenId: options['token-id'],
240
+ name: options.name,
241
+ imageUrl: options['image-url'],
242
+ peers: peersFromOptions(options)
243
+ });
244
+
245
+ printHero('NFT Minted');
246
+ console.log(`${color('bold', 'NFT:')} ${result.transaction.asset_operation.MintNft.collection}:${result.transaction.asset_operation.MintNft.token_id}`);
247
+ console.log(`${color('bold', 'Transaction ID:')} ${result.transactionId}`);
248
+ console.log(`${color('green', 'Broadcast:')} ${result.acceptedBy}/${result.peers.length} reachable nodes`);
249
+ console.log(color('yellow', 'The NFT exists after a miner includes this transaction in a block.'));
250
+ return;
251
+ }
252
+
253
+ if (command === 'nft' && subcommand === 'send') {
254
+ if (!options.collection) {
255
+ throw new Error('nft send requires --collection <SYMBOL>');
256
+ }
257
+ if (!options['token-id']) {
258
+ throw new Error('nft send requires --token-id <ID>');
259
+ }
260
+ if (!options.to) {
261
+ throw new Error('nft send requires --to <PUBLIC_KEY>');
262
+ }
263
+
264
+ const wallet = await loadWallet(options.wallet ?? DEFAULT_WALLET_PATH);
265
+ const result = await transferNft({
266
+ wallet,
267
+ recipient: options.to,
268
+ collection: options.collection,
269
+ tokenId: options['token-id'],
270
+ peers: peersFromOptions(options)
271
+ });
272
+
273
+ printHero('NFT Sent');
274
+ console.log(`${color('bold', 'NFT:')} ${result.transaction.asset_operation.TransferNft.collection}:${result.transaction.asset_operation.TransferNft.token_id}`);
275
+ console.log(`${color('bold', 'To:')} ${result.transaction.recipient}`);
276
+ console.log(`${color('bold', 'Transaction ID:')} ${result.transactionId}`);
277
+ console.log(`${color('green', 'Broadcast:')} ${result.acceptedBy}/${result.peers.length} reachable nodes`);
278
+ return;
279
+ }
280
+
159
281
  if (command === 'nodes') {
160
282
  const peers = await discoverPeers(peersFromOptions(options));
161
283
  const { nodes, chain, sourcePeer } = await getBestChain(peers);
package/src/index.js CHANGED
@@ -60,8 +60,117 @@ export async function loadWallet(path = DEFAULT_WALLET_PATH) {
60
60
  return wallet;
61
61
  }
62
62
 
63
- export function transactionPayload(sender, recipient, amount, senderPublicKey) {
64
- return `${sender}|${recipient}|${Number(amount).toFixed(8)}|${senderPublicKey}`;
63
+ function rustJsonNumber(value) {
64
+ const numeric = Number(value);
65
+ if (!Number.isFinite(numeric)) {
66
+ throw new Error('amount must be a finite number');
67
+ }
68
+
69
+ return Number.isInteger(numeric) ? `${numeric}.0` : `${numeric}`;
70
+ }
71
+
72
+ function jsonString(value) {
73
+ return JSON.stringify(`${value}`);
74
+ }
75
+
76
+ function stringifyAssetOperationForSigning(assetOperation) {
77
+ if (!assetOperation) {
78
+ return null;
79
+ }
80
+
81
+ if (assetOperation.CreateCoin) {
82
+ const coin = assetOperation.CreateCoin;
83
+ return `{"CreateCoin":{"symbol":${jsonString(coin.symbol)},"name":${jsonString(coin.name)},"supply":${rustJsonNumber(coin.supply)}}}`;
84
+ }
85
+
86
+ if (assetOperation.TransferCoin) {
87
+ const transfer = assetOperation.TransferCoin;
88
+ return `{"TransferCoin":{"symbol":${jsonString(transfer.symbol)},"amount":${rustJsonNumber(transfer.amount)}}}`;
89
+ }
90
+
91
+ if (assetOperation.MintNft) {
92
+ const nft = assetOperation.MintNft;
93
+ const imageUrl = nft.image_url ? `,"image_url":${jsonString(nft.image_url)}` : '';
94
+ return `{"MintNft":{"collection":${jsonString(nft.collection)},"token_id":${jsonString(nft.token_id)},"name":${jsonString(nft.name)}${imageUrl}}}`;
95
+ }
96
+
97
+ if (assetOperation.TransferNft) {
98
+ const transfer = assetOperation.TransferNft;
99
+ return `{"TransferNft":{"collection":${jsonString(transfer.collection)},"token_id":${jsonString(transfer.token_id)}}}`;
100
+ }
101
+
102
+ throw new Error('unknown asset operation');
103
+ }
104
+
105
+ export function transactionPayload(sender, recipient, amount, senderPublicKey, assetOperation = null) {
106
+ const base = `${sender}|${recipient}|${Number(amount).toFixed(8)}|${senderPublicKey}`;
107
+ const serializedAsset = stringifyAssetOperationForSigning(assetOperation);
108
+ return serializedAsset ? `${base}|${serializedAsset}` : base;
109
+ }
110
+
111
+ function assertWalletMatchesPrivateKey(wallet, privateKey) {
112
+ const senderPublicKey = bytesToHex(ed25519.getPublicKey(privateKey));
113
+ if (senderPublicKey !== wallet.public_key) {
114
+ throw new Error('wallet public key does not match its private key');
115
+ }
116
+
117
+ return senderPublicKey;
118
+ }
119
+
120
+ function createSignedAssetTransaction(wallet, recipient, assetOperation) {
121
+ const privateKey = hexToBytes(wallet.private_key);
122
+ const senderPublicKey = assertWalletMatchesPrivateKey(wallet, privateKey);
123
+ const payload = transactionPayload(wallet.name, recipient, 0, senderPublicKey, assetOperation);
124
+ const signature = ed25519.sign(new TextEncoder().encode(payload), privateKey);
125
+
126
+ return {
127
+ sender: wallet.name,
128
+ recipient,
129
+ amount: 0,
130
+ sender_public_key: senderPublicKey,
131
+ signature: bytesToHex(signature),
132
+ asset_operation: assetOperation
133
+ };
134
+ }
135
+
136
+ export function normalizeCoinSymbol(symbol) {
137
+ const value = `${symbol}`.trim().toUpperCase();
138
+ if (!/^[A-Z0-9]{2,12}$/.test(value)) {
139
+ throw new Error('coin symbol must be 2 to 12 letters or numbers');
140
+ }
141
+
142
+ return value;
143
+ }
144
+
145
+ export function normalizeAssetName(name) {
146
+ const value = `${name}`.trim();
147
+ if (!value || value.length > 64) {
148
+ throw new Error('asset name must be 1 to 64 characters');
149
+ }
150
+
151
+ return value;
152
+ }
153
+
154
+ export function normalizeTokenId(tokenId) {
155
+ const value = `${tokenId}`.trim().toLowerCase();
156
+ if (!/^[a-z0-9_-]{1,64}$/.test(value)) {
157
+ throw new Error('NFT token id must be 1 to 64 letters, numbers, hyphens, or underscores');
158
+ }
159
+
160
+ return value;
161
+ }
162
+
163
+ export function normalizeTokenAmount(amount, label = 'amount') {
164
+ const numeric = Number(amount);
165
+ if (!Number.isFinite(numeric) || numeric <= 0) {
166
+ throw new Error(`${label} must be a positive finite number`);
167
+ }
168
+
169
+ if (Math.round(numeric * 100_000_000) / 100_000_000 !== numeric) {
170
+ throw new Error(`${label} can have at most 8 decimal places`);
171
+ }
172
+
173
+ return numeric;
65
174
  }
66
175
 
67
176
  export function createSignedTransaction(wallet, recipient, amount) {
@@ -71,10 +180,7 @@ export function createSignedTransaction(wallet, recipient, amount) {
71
180
  }
72
181
 
73
182
  const privateKey = hexToBytes(wallet.private_key);
74
- const senderPublicKey = bytesToHex(ed25519.getPublicKey(privateKey));
75
- if (senderPublicKey !== wallet.public_key) {
76
- throw new Error('wallet public key does not match its private key');
77
- }
183
+ const senderPublicKey = assertWalletMatchesPrivateKey(wallet, privateKey);
78
184
 
79
185
  const payload = transactionPayload(wallet.name, recipient, numericAmount, senderPublicKey);
80
186
  const signature = ed25519.sign(new TextEncoder().encode(payload), privateKey);
@@ -88,6 +194,68 @@ export function createSignedTransaction(wallet, recipient, amount) {
88
194
  };
89
195
  }
90
196
 
197
+ export function createCoinTransaction(wallet, { symbol, name, supply }) {
198
+ const senderPublicKey = wallet.public_key;
199
+ const assetOperation = {
200
+ CreateCoin: {
201
+ symbol: normalizeCoinSymbol(symbol),
202
+ name: normalizeAssetName(name),
203
+ supply: normalizeTokenAmount(supply, 'coin supply')
204
+ }
205
+ };
206
+
207
+ return createSignedAssetTransaction(wallet, senderPublicKey, assetOperation);
208
+ }
209
+
210
+ export function createCoinTransferTransaction(wallet, { recipient, symbol, amount }) {
211
+ if (!recipient) {
212
+ throw new Error('coin transfer requires a recipient public key');
213
+ }
214
+
215
+ const assetOperation = {
216
+ TransferCoin: {
217
+ symbol: normalizeCoinSymbol(symbol),
218
+ amount: normalizeTokenAmount(amount, 'coin transfer amount')
219
+ }
220
+ };
221
+
222
+ return createSignedAssetTransaction(wallet, recipient, assetOperation);
223
+ }
224
+
225
+ export function createNftMintTransaction(wallet, { collection, tokenId, name, imageUrl = null }) {
226
+ const senderPublicKey = wallet.public_key;
227
+ const mint = {
228
+ collection: normalizeCoinSymbol(collection),
229
+ token_id: normalizeTokenId(tokenId),
230
+ name: normalizeAssetName(name)
231
+ };
232
+
233
+ if (imageUrl) {
234
+ const value = `${imageUrl}`.trim();
235
+ if (value.length > 512 || !/^https?:\/\//i.test(value)) {
236
+ throw new Error('NFT image URL must start with http:// or https:// and be at most 512 characters');
237
+ }
238
+ mint.image_url = value;
239
+ }
240
+
241
+ return createSignedAssetTransaction(wallet, senderPublicKey, { MintNft: mint });
242
+ }
243
+
244
+ export function createNftTransferTransaction(wallet, { recipient, collection, tokenId }) {
245
+ if (!recipient) {
246
+ throw new Error('NFT transfer requires a recipient public key');
247
+ }
248
+
249
+ const assetOperation = {
250
+ TransferNft: {
251
+ collection: normalizeCoinSymbol(collection),
252
+ token_id: normalizeTokenId(tokenId)
253
+ }
254
+ };
255
+
256
+ return createSignedAssetTransaction(wallet, recipient, assetOperation);
257
+ }
258
+
91
259
  export function transactionId(transaction) {
92
260
  return createHash('sha256').update(JSON.stringify(transaction)).digest('hex');
93
261
  }
@@ -230,6 +398,9 @@ export function calculateBalances(chain) {
230
398
  }
231
399
 
232
400
  for (const transaction of transactions) {
401
+ if (transaction.asset_operation) {
402
+ continue;
403
+ }
233
404
  debit(transaction.sender_public_key, transaction.amount);
234
405
  credit(transaction.recipient, transaction.amount);
235
406
  }
@@ -238,6 +409,106 @@ export function calculateBalances(chain) {
238
409
  return balances;
239
410
  }
240
411
 
412
+ export function calculateAssets(chain) {
413
+ const coins = new Map();
414
+ const nfts = new Map();
415
+
416
+ const ensureCoin = (symbol, name = symbol, supply = 0) => {
417
+ if (!coins.has(symbol)) {
418
+ coins.set(symbol, { symbol, name, supply, holders: new Map() });
419
+ }
420
+ return coins.get(symbol);
421
+ };
422
+
423
+ const addBalance = (symbol, address, amount) => {
424
+ const coin = ensureCoin(symbol);
425
+ coin.holders.set(address, (coin.holders.get(address) ?? 0) + amount);
426
+ };
427
+
428
+ for (const block of chain.chain.slice(1)) {
429
+ for (const transaction of block.transactions.slice(1)) {
430
+ const operation = transaction.asset_operation;
431
+ if (!operation) {
432
+ continue;
433
+ }
434
+
435
+ if (operation.CreateCoin) {
436
+ const coin = operation.CreateCoin;
437
+ const symbol = normalizeCoinSymbol(coin.symbol);
438
+ const record = ensureCoin(symbol, coin.name, coin.supply);
439
+ record.creator = transaction.sender_public_key;
440
+ addBalance(symbol, transaction.sender_public_key, coin.supply);
441
+ } else if (operation.TransferCoin) {
442
+ const transfer = operation.TransferCoin;
443
+ const symbol = normalizeCoinSymbol(transfer.symbol);
444
+ addBalance(symbol, transaction.sender_public_key, -transfer.amount);
445
+ addBalance(symbol, transaction.recipient, transfer.amount);
446
+ } else if (operation.MintNft) {
447
+ const nft = operation.MintNft;
448
+ const collection = normalizeCoinSymbol(nft.collection);
449
+ const tokenId = normalizeTokenId(nft.token_id);
450
+ nfts.set(`${collection}:${tokenId}`, {
451
+ collection,
452
+ tokenId,
453
+ name: nft.name,
454
+ imageUrl: nft.image_url ?? null,
455
+ creator: transaction.sender_public_key,
456
+ owner: transaction.sender_public_key
457
+ });
458
+ } else if (operation.TransferNft) {
459
+ const transfer = operation.TransferNft;
460
+ const key = `${normalizeCoinSymbol(transfer.collection)}:${normalizeTokenId(transfer.token_id)}`;
461
+ const nft = nfts.get(key);
462
+ if (nft) {
463
+ nft.owner = transaction.recipient;
464
+ }
465
+ }
466
+ }
467
+ }
468
+
469
+ return {
470
+ coins: [...coins.values()].map((coin) => ({
471
+ ...coin,
472
+ creator: coin.creator ?? null,
473
+ holders: [...coin.holders.entries()]
474
+ .map(([address, balance]) => ({ address, balance }))
475
+ .filter((holder) => Math.abs(holder.balance) > 0.00000001)
476
+ .sort((left, right) => right.balance - left.balance)
477
+ })),
478
+ nfts: [...nfts.values()].sort((left, right) =>
479
+ `${left.collection}:${left.tokenId}`.localeCompare(`${right.collection}:${right.tokenId}`)
480
+ )
481
+ };
482
+ }
483
+
484
+ export function listCoinsCreatedBy(chain, addressOrWallet) {
485
+ const address = typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key;
486
+ return calculateAssets(chain).coins.filter((coin) => coin.creator === address);
487
+ }
488
+
489
+ export function listNftsCreatedBy(chain, addressOrWallet) {
490
+ const address = typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key;
491
+ return calculateAssets(chain).nfts.filter((nft) => nft.creator === address);
492
+ }
493
+
494
+ export function listNftsOwnedBy(chain, addressOrWallet) {
495
+ const address = typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key;
496
+ return calculateAssets(chain).nfts.filter((nft) => nft.owner === address);
497
+ }
498
+
499
+ export function listCoinHoldings(chain, addressOrWallet) {
500
+ const address = typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key;
501
+ return calculateAssets(chain).coins
502
+ .map((coin) => ({
503
+ symbol: coin.symbol,
504
+ name: coin.name,
505
+ supply: coin.supply,
506
+ creator: coin.creator,
507
+ balance: coin.holders.find((holder) => holder.address === address)?.balance ?? 0
508
+ }))
509
+ .filter((coin) => Math.abs(coin.balance) > 0.00000001);
510
+ }
511
+
241
512
  export async function getBalance(addressOrWallet, seedPeers = DEFAULT_PEERS) {
242
513
  const address = typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key;
243
514
  const { chain, sourcePeer, peers } = await getBestChain(seedPeers);
@@ -253,6 +524,50 @@ export async function getBalance(addressOrWallet, seedPeers = DEFAULT_PEERS) {
253
524
  };
254
525
  }
255
526
 
527
+ export async function getCreatedCoins(addressOrWallet, seedPeers = DEFAULT_PEERS) {
528
+ const { chain, sourcePeer, peers } = await getBestChain(seedPeers);
529
+ return {
530
+ address: typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key,
531
+ coins: listCoinsCreatedBy(chain, addressOrWallet),
532
+ blockHeight: chain.chain.at(-1)?.index ?? 0,
533
+ sourcePeer,
534
+ peers
535
+ };
536
+ }
537
+
538
+ export async function getCreatedNfts(addressOrWallet, seedPeers = DEFAULT_PEERS) {
539
+ const { chain, sourcePeer, peers } = await getBestChain(seedPeers);
540
+ return {
541
+ address: typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key,
542
+ nfts: listNftsCreatedBy(chain, addressOrWallet),
543
+ blockHeight: chain.chain.at(-1)?.index ?? 0,
544
+ sourcePeer,
545
+ peers
546
+ };
547
+ }
548
+
549
+ export async function getOwnedNfts(addressOrWallet, seedPeers = DEFAULT_PEERS) {
550
+ const { chain, sourcePeer, peers } = await getBestChain(seedPeers);
551
+ return {
552
+ address: typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key,
553
+ nfts: listNftsOwnedBy(chain, addressOrWallet),
554
+ blockHeight: chain.chain.at(-1)?.index ?? 0,
555
+ sourcePeer,
556
+ peers
557
+ };
558
+ }
559
+
560
+ export async function getCoinHoldings(addressOrWallet, seedPeers = DEFAULT_PEERS) {
561
+ const { chain, sourcePeer, peers } = await getBestChain(seedPeers);
562
+ return {
563
+ address: typeof addressOrWallet === 'string' ? addressOrWallet : addressOrWallet.public_key,
564
+ coins: listCoinHoldings(chain, addressOrWallet),
565
+ blockHeight: chain.chain.at(-1)?.index ?? 0,
566
+ sourcePeer,
567
+ peers
568
+ };
569
+ }
570
+
256
571
  export async function broadcastTransaction(transaction, seedPeers = DEFAULT_PEERS) {
257
572
  const peers = await discoverPeers(seedPeers);
258
573
  const message = { NewTransaction: transaction };
@@ -273,3 +588,35 @@ export async function sendTransaction({ wallet, recipient, amount, peers = DEFAU
273
588
  ...(await broadcastTransaction(transaction, peers))
274
589
  };
275
590
  }
591
+
592
+ export async function createCoin({ wallet, symbol, name, supply, peers = DEFAULT_PEERS }) {
593
+ const transaction = createCoinTransaction(wallet, { symbol, name, supply });
594
+ return {
595
+ transaction,
596
+ ...(await broadcastTransaction(transaction, peers))
597
+ };
598
+ }
599
+
600
+ export async function sendCoin({ wallet, recipient, symbol, amount, peers = DEFAULT_PEERS }) {
601
+ const transaction = createCoinTransferTransaction(wallet, { recipient, symbol, amount });
602
+ return {
603
+ transaction,
604
+ ...(await broadcastTransaction(transaction, peers))
605
+ };
606
+ }
607
+
608
+ export async function mintNft({ wallet, collection, tokenId, name, imageUrl = null, peers = DEFAULT_PEERS }) {
609
+ const transaction = createNftMintTransaction(wallet, { collection, tokenId, name, imageUrl });
610
+ return {
611
+ transaction,
612
+ ...(await broadcastTransaction(transaction, peers))
613
+ };
614
+ }
615
+
616
+ export async function transferNft({ wallet, recipient, collection, tokenId, peers = DEFAULT_PEERS }) {
617
+ const transaction = createNftTransferTransaction(wallet, { recipient, collection, tokenId });
618
+ return {
619
+ transaction,
620
+ ...(await broadcastTransaction(transaction, peers))
621
+ };
622
+ }