@neuraiproject/neurai-assets 1.0.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.
Files changed (58) hide show
  1. package/README.md +522 -0
  2. package/examples/01-create-root-asset.js +71 -0
  3. package/examples/02-create-sub-asset.js +79 -0
  4. package/examples/03-create-nfts.js +140 -0
  5. package/examples/04-reissue-asset.js +164 -0
  6. package/examples/05-create-qualifier-and-tag.js +209 -0
  7. package/examples/06-create-restricted-asset.js +223 -0
  8. package/examples/07-freeze-and-unfreeze.js +292 -0
  9. package/examples/08-query-assets.js +332 -0
  10. package/examples/09-wallet-integration.js +320 -0
  11. package/examples/README.md +319 -0
  12. package/package.json +43 -0
  13. package/src/NeuraiAssets.js +468 -0
  14. package/src/builders/BaseAssetTransactionBuilder.js +303 -0
  15. package/src/builders/FreezeAddressBuilder.js +271 -0
  16. package/src/builders/IssueQualifierBuilder.js +251 -0
  17. package/src/builders/IssueRestrictedBuilder.js +187 -0
  18. package/src/builders/IssueRootBuilder.js +173 -0
  19. package/src/builders/IssueSubBuilder.js +237 -0
  20. package/src/builders/IssueUniqueBuilder.js +255 -0
  21. package/src/builders/ReissueBuilder.js +246 -0
  22. package/src/builders/ReissueRestrictedBuilder.js +264 -0
  23. package/src/builders/TagAddressBuilder.js +243 -0
  24. package/src/builders/index.js +38 -0
  25. package/src/constants/assetTypes.js +23 -0
  26. package/src/constants/burnAddresses.js +65 -0
  27. package/src/constants/fees.js +61 -0
  28. package/src/constants/index.js +44 -0
  29. package/src/constants/networks.js +112 -0
  30. package/src/errors/AssetErrors.js +135 -0
  31. package/src/errors/ValidationErrors.js +87 -0
  32. package/src/errors/index.js +56 -0
  33. package/src/index.js +68 -0
  34. package/src/managers/BurnManager.js +222 -0
  35. package/src/managers/OutputOrderer.js +289 -0
  36. package/src/managers/OwnerTokenManager.js +265 -0
  37. package/src/managers/UTXOSelector.js +309 -0
  38. package/src/managers/index.js +16 -0
  39. package/src/queries/AssetQueries.js +447 -0
  40. package/src/queries/index.js +10 -0
  41. package/src/utils/amountConverter.js +115 -0
  42. package/src/utils/assetNameParser.js +203 -0
  43. package/src/utils/index.js +16 -0
  44. package/src/utils/networkDetector.js +144 -0
  45. package/src/utils/outputFormatter.js +292 -0
  46. package/src/validators/amountValidator.js +149 -0
  47. package/src/validators/assetNameValidator.js +296 -0
  48. package/src/validators/index.js +16 -0
  49. package/src/validators/ipfsValidator.js +101 -0
  50. package/src/validators/verifierValidator.js +146 -0
  51. package/tests/README.md +126 -0
  52. package/tests/integration/assetLifecycle.test.js +244 -0
  53. package/tests/mocks/rpcMock.js +156 -0
  54. package/tests/unit/NeuraiAssets.test.js +217 -0
  55. package/tests/unit/utils/amountConverter.test.js +171 -0
  56. package/tests/unit/utils/assetNameParser.test.js +203 -0
  57. package/tests/unit/validators/amountValidator.test.js +143 -0
  58. package/tests/unit/validators/assetNameValidator.test.js +228 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * UTXO Selector
3
+ * Selects appropriate UTXOs for asset transactions
4
+ *
5
+ * Handles selection of:
6
+ * - Base currency (XNA) UTXOs for fees and burns
7
+ * - Asset UTXOs for transfers and operations
8
+ * - Mempool filtering to prevent double-spending
9
+ */
10
+
11
+ const { InsufficientFundsError } = require('../errors');
12
+
13
+ class UTXOSelector {
14
+ /**
15
+ * @param {Function} rpc - RPC function to call Neurai node
16
+ */
17
+ constructor(rpc) {
18
+ if (!rpc || typeof rpc !== 'function') {
19
+ throw new Error('RPC function is required');
20
+ }
21
+ this.rpc = rpc;
22
+ }
23
+
24
+ /**
25
+ * Get all UTXOs for addresses
26
+ * @param {string[]} addresses - Array of wallet addresses
27
+ * @param {string|null} assetName - Filter by asset name (null for XNA)
28
+ * @returns {Promise<Array>} Array of UTXOs
29
+ */
30
+ async getUTXOs(addresses, assetName = null) {
31
+ if (!Array.isArray(addresses) || addresses.length === 0) {
32
+ throw new Error('Addresses array is required');
33
+ }
34
+
35
+ try {
36
+ const params = assetName
37
+ ? [{ addresses, assetName }]
38
+ : [{ addresses }];
39
+
40
+ const utxos = await this.rpc('getaddressutxos', params);
41
+
42
+ // Filter for specific asset or XNA
43
+ if (assetName) {
44
+ return utxos.filter(utxo => utxo.assetName === assetName);
45
+ } else {
46
+ // XNA UTXOs don't have assetName or have assetName === 'XNA'
47
+ return utxos.filter(utxo => !utxo.assetName || utxo.assetName === 'XNA');
48
+ }
49
+ } catch (error) {
50
+ throw new Error(`Failed to get UTXOs: ${error.message}`);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Get mempool entries for addresses
56
+ * Used to filter out UTXOs that are already being spent
57
+ * @param {string[]} addresses - Array of wallet addresses
58
+ * @returns {Promise<Array>} Array of mempool entries
59
+ */
60
+ async getMempoolEntries(addresses) {
61
+ if (!Array.isArray(addresses) || addresses.length === 0) {
62
+ throw new Error('Addresses array is required');
63
+ }
64
+
65
+ try {
66
+ const mempool = await this.rpc('getaddressmempool', [{ addresses }]);
67
+ return mempool || [];
68
+ } catch (error) {
69
+ // If RPC method not available, return empty array
70
+ return [];
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Filter out UTXOs that are being spent in mempool
76
+ * @param {Array} utxos - Array of UTXOs
77
+ * @param {Array} mempoolEntries - Array of mempool entries
78
+ * @returns {Array} Filtered UTXOs
79
+ */
80
+ filterMempoolSpentUTXOs(utxos, mempoolEntries) {
81
+ if (mempoolEntries.length === 0) {
82
+ return utxos;
83
+ }
84
+
85
+ return utxos.filter(utxo => {
86
+ // Check if this UTXO is being spent in mempool
87
+ const isSpent = mempoolEntries.some(entry => {
88
+ return entry.prevtxid === utxo.txid && entry.prevout === utxo.outputIndex;
89
+ });
90
+
91
+ return !isSpent;
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Select UTXOs for base currency (XNA)
97
+ * Uses greedy algorithm: selects UTXOs until sum >= required amount
98
+ *
99
+ * @param {string[]} addresses - Wallet addresses
100
+ * @param {number} requiredAmount - Required amount in XNA
101
+ * @param {number} buffer - Safety buffer percentage (default: 0.1 = 10%)
102
+ * @returns {Promise<object>} { utxos, totalAmount }
103
+ * @throws {InsufficientFundsError} If not enough funds
104
+ */
105
+ async selectBaseCurrencyUTXOs(addresses, requiredAmount, buffer = 0.1) {
106
+ // Get all XNA UTXOs
107
+ const allUTXOs = await this.getUTXOs(addresses, null);
108
+
109
+ // Get mempool and filter
110
+ const mempool = await this.getMempoolEntries(addresses);
111
+ const availableUTXOs = this.filterMempoolSpentUTXOs(allUTXOs, mempool);
112
+
113
+ // Sort by value (largest first) for efficiency
114
+ const sortedUTXOs = availableUTXOs.sort((a, b) => b.satoshis - a.satoshis);
115
+
116
+ // Add buffer to required amount
117
+ const requiredWithBuffer = requiredAmount * (1 + buffer);
118
+
119
+ // Select UTXOs greedily
120
+ const selected = [];
121
+ let totalSatoshis = 0;
122
+ const requiredSatoshis = Math.ceil(requiredWithBuffer * 100000000); // Convert XNA to satoshis
123
+
124
+ for (const utxo of sortedUTXOs) {
125
+ selected.push(utxo);
126
+ totalSatoshis += utxo.satoshis;
127
+
128
+ if (totalSatoshis >= requiredSatoshis) {
129
+ break;
130
+ }
131
+ }
132
+
133
+ // Check if we have enough
134
+ if (totalSatoshis < requiredSatoshis) {
135
+ const available = totalSatoshis / 100000000;
136
+ throw new InsufficientFundsError(
137
+ `Insufficient XNA balance. Required: ${requiredAmount.toFixed(8)} XNA (+ ${(buffer * 100).toFixed(0)}% buffer), ` +
138
+ `Available: ${available.toFixed(8)} XNA`,
139
+ requiredAmount,
140
+ available
141
+ );
142
+ }
143
+
144
+ return {
145
+ utxos: selected,
146
+ totalAmount: totalSatoshis / 100000000
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Select UTXOs for asset transfer
152
+ * @param {string[]} addresses - Wallet addresses
153
+ * @param {string} assetName - Asset name
154
+ * @param {number} requiredAmount - Required amount (in asset units)
155
+ * @returns {Promise<object>} { utxos, totalAmount }
156
+ * @throws {InsufficientFundsError} If not enough asset balance
157
+ */
158
+ async selectAssetUTXOs(addresses, assetName, requiredAmount) {
159
+ if (!assetName) {
160
+ throw new Error('Asset name is required');
161
+ }
162
+
163
+ // Get all asset UTXOs
164
+ const allUTXOs = await this.getUTXOs(addresses, assetName);
165
+
166
+ // Get mempool and filter
167
+ const mempool = await this.getMempoolEntries(addresses);
168
+ const availableUTXOs = this.filterMempoolSpentUTXOs(allUTXOs, mempool);
169
+
170
+ // Sort by value (largest first)
171
+ const sortedUTXOs = availableUTXOs.sort((a, b) => b.satoshis - a.satoshis);
172
+
173
+ // Select UTXOs greedily
174
+ const selected = [];
175
+ let totalSatoshis = 0;
176
+ const requiredSatoshis = Math.ceil(requiredAmount * 100000000); // Assuming 8 decimals
177
+
178
+ for (const utxo of sortedUTXOs) {
179
+ selected.push(utxo);
180
+ totalSatoshis += utxo.satoshis;
181
+
182
+ if (totalSatoshis >= requiredSatoshis) {
183
+ break;
184
+ }
185
+ }
186
+
187
+ // Check if we have enough
188
+ if (totalSatoshis < requiredSatoshis) {
189
+ const available = totalSatoshis / 100000000;
190
+ throw new InsufficientFundsError(
191
+ `Insufficient ${assetName} balance. Required: ${requiredAmount}, Available: ${available}`,
192
+ requiredAmount,
193
+ available
194
+ );
195
+ }
196
+
197
+ return {
198
+ utxos: selected,
199
+ totalAmount: totalSatoshis / 100000000
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Select UTXOs for a transaction requiring both XNA and assets
205
+ * @param {string[]} addresses - Wallet addresses
206
+ * @param {number} xnaAmount - Required XNA amount
207
+ * @param {string|null} assetName - Asset name (null if not needed)
208
+ * @param {number} assetAmount - Required asset amount
209
+ * @returns {Promise<object>} { xnaUTXOs, assetUTXOs, totalXNA, totalAsset }
210
+ */
211
+ async selectMixedUTXOs(addresses, xnaAmount, assetName = null, assetAmount = 0) {
212
+ const result = {
213
+ xnaUTXOs: [],
214
+ assetUTXOs: [],
215
+ totalXNA: 0,
216
+ totalAsset: 0
217
+ };
218
+
219
+ // Select XNA UTXOs if needed
220
+ if (xnaAmount > 0) {
221
+ const xnaSelection = await this.selectBaseCurrencyUTXOs(addresses, xnaAmount);
222
+ result.xnaUTXOs = xnaSelection.utxos;
223
+ result.totalXNA = xnaSelection.totalAmount;
224
+ }
225
+
226
+ // Select asset UTXOs if needed
227
+ if (assetName && assetAmount > 0) {
228
+ const assetSelection = await this.selectAssetUTXOs(addresses, assetName, assetAmount);
229
+ result.assetUTXOs = assetSelection.utxos;
230
+ result.totalAsset = assetSelection.totalAmount;
231
+ }
232
+
233
+ return result;
234
+ }
235
+
236
+ /**
237
+ * Get total balance for an asset
238
+ * @param {string[]} addresses - Wallet addresses
239
+ * @param {string|null} assetName - Asset name (null for XNA)
240
+ * @returns {Promise<number>} Total balance
241
+ */
242
+ async getBalance(addresses, assetName = null) {
243
+ const utxos = await this.getUTXOs(addresses, assetName);
244
+ const mempool = await this.getMempoolEntries(addresses);
245
+ const availableUTXOs = this.filterMempoolSpentUTXOs(utxos, mempool);
246
+
247
+ const totalSatoshis = availableUTXOs.reduce((sum, utxo) => sum + utxo.satoshis, 0);
248
+ return totalSatoshis / 100000000;
249
+ }
250
+
251
+ /**
252
+ * Estimate transaction size in bytes
253
+ * Used for fee calculation
254
+ *
255
+ * @param {number} inputCount - Number of inputs
256
+ * @param {number} outputCount - Number of outputs
257
+ * @returns {number} Estimated size in bytes
258
+ */
259
+ estimateTransactionSize(inputCount, outputCount) {
260
+ // Rough estimation:
261
+ // - Each input: ~180 bytes
262
+ // - Each output: ~34 bytes
263
+ // - Transaction overhead: ~10 bytes
264
+ const inputSize = inputCount * 180;
265
+ const outputSize = outputCount * 34;
266
+ const overhead = 10;
267
+
268
+ return inputSize + outputSize + overhead;
269
+ }
270
+
271
+ /**
272
+ * Estimate fee for a transaction
273
+ * @param {number} inputCount - Number of inputs
274
+ * @param {number} outputCount - Number of outputs
275
+ * @param {number} feeRate - Fee rate in XNA per KB (default: 0.015)
276
+ * @returns {number} Estimated fee in XNA
277
+ */
278
+ estimateFee(inputCount, outputCount, feeRate = 0.015) {
279
+ const sizeBytes = this.estimateTransactionSize(inputCount, outputCount);
280
+ const sizeKB = sizeBytes / 1000;
281
+ const fee = sizeKB * feeRate;
282
+
283
+ // Round up to 8 decimals
284
+ return Math.ceil(fee * 100000000) / 100000000;
285
+ }
286
+
287
+ /**
288
+ * Get fee rate from network
289
+ * @param {number} confirmationTarget - Target confirmations (default: 20)
290
+ * @returns {Promise<number>} Fee rate in XNA per KB
291
+ */
292
+ async getFeeRate(confirmationTarget = 20) {
293
+ try {
294
+ const result = await this.rpc('estimatesmartfee', [confirmationTarget]);
295
+
296
+ if (result && result.feerate && result.feerate > 0) {
297
+ return result.feerate;
298
+ }
299
+
300
+ // Fallback to default
301
+ return 0.015;
302
+ } catch (error) {
303
+ // Fallback to default if estimation fails
304
+ return 0.015;
305
+ }
306
+ }
307
+ }
308
+
309
+ module.exports = UTXOSelector;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Managers Module
3
+ * Exports all manager classes
4
+ */
5
+
6
+ const BurnManager = require('./BurnManager');
7
+ const OwnerTokenManager = require('./OwnerTokenManager');
8
+ const UTXOSelector = require('./UTXOSelector');
9
+ const OutputOrderer = require('./OutputOrderer');
10
+
11
+ module.exports = {
12
+ BurnManager,
13
+ OwnerTokenManager,
14
+ UTXOSelector,
15
+ OutputOrderer
16
+ };