@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,203 @@
1
+ /**
2
+ * Asset Name Parser
3
+ * Parses and analyzes asset names
4
+ */
5
+
6
+ const { AssetType } = require('../constants');
7
+
8
+ class AssetNameParser {
9
+ /**
10
+ * Parse asset name and extract information
11
+ * @param {string} name - Asset name
12
+ * @returns {object} Parsed information
13
+ */
14
+ static parse(name) {
15
+ const isOwner = name.endsWith('!');
16
+ const isRestricted = name.startsWith('$');
17
+ const isQualifier = name.startsWith('#');
18
+ const cleanName = isOwner ? name.slice(0, -1) : name;
19
+
20
+ let type;
21
+ let parent = null;
22
+ let subName = null;
23
+ let tag = null;
24
+ let prefix = null;
25
+
26
+ if (isQualifier) {
27
+ // QUALIFIER or SUB_QUALIFIER: #NAME or #ROOT/SUB
28
+ if (cleanName.includes('/')) {
29
+ type = AssetType.SUB_QUALIFIER;
30
+ const withoutHash = cleanName.substring(1);
31
+ const parts = withoutHash.split('/');
32
+ parent = '#' + parts[0];
33
+ subName = parts[1];
34
+ prefix = '#';
35
+ } else {
36
+ type = AssetType.QUALIFIER;
37
+ prefix = '#';
38
+ }
39
+ } else if (isRestricted) {
40
+ // RESTRICTED: $NAME
41
+ type = AssetType.RESTRICTED;
42
+ prefix = '$';
43
+ } else if (cleanName.includes('#')) {
44
+ // UNIQUE: ROOT#TAG
45
+ type = AssetType.UNIQUE;
46
+ const parts = cleanName.split('#');
47
+ parent = parts[0];
48
+ tag = parts[1];
49
+ } else if (cleanName.includes('/')) {
50
+ // SUB: ROOT/SUB
51
+ type = AssetType.SUB;
52
+ const parts = cleanName.split('/');
53
+ parent = parts[0];
54
+ subName = parts[1];
55
+ } else {
56
+ // ROOT
57
+ type = AssetType.ROOT;
58
+ }
59
+
60
+ // Override type if owner token
61
+ if (isOwner) {
62
+ const baseType = type;
63
+ type = AssetType.OWNER;
64
+ return {
65
+ type,
66
+ baseType,
67
+ parent,
68
+ name: cleanName,
69
+ subName,
70
+ tag,
71
+ prefix,
72
+ isOwner: true,
73
+ isRestricted: cleanName.startsWith('$'),
74
+ isQualifier: false,
75
+ fullName: name,
76
+ baseName: cleanName
77
+ };
78
+ }
79
+
80
+ return {
81
+ type,
82
+ parent,
83
+ name: cleanName,
84
+ subName,
85
+ tag,
86
+ prefix,
87
+ isOwner,
88
+ isRestricted,
89
+ isQualifier,
90
+ fullName: name,
91
+ baseName: cleanName
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Get asset type from name
97
+ * @param {string} name - Asset name
98
+ * @returns {number} AssetType enum value
99
+ */
100
+ static getType(name) {
101
+ return this.parse(name).type;
102
+ }
103
+
104
+ /**
105
+ * Get parent asset name
106
+ * @param {string} name - Asset name
107
+ * @returns {string|null} Parent asset name or null
108
+ */
109
+ static getParent(name) {
110
+ return this.parse(name).parent;
111
+ }
112
+
113
+ /**
114
+ * Check if asset is an owner token
115
+ * @param {string} name - Asset name
116
+ * @returns {boolean} True if owner token
117
+ */
118
+ static isOwnerToken(name) {
119
+ return name.endsWith('!');
120
+ }
121
+
122
+ /**
123
+ * Get owner token name for an asset
124
+ * @param {string} assetName - Asset name
125
+ * @returns {string} Owner token name (assetName + '!')
126
+ */
127
+ static getOwnerTokenName(assetName) {
128
+ if (this.isOwnerToken(assetName)) {
129
+ return assetName;
130
+ }
131
+ return assetName + '!';
132
+ }
133
+
134
+ /**
135
+ * Get base asset name from owner token
136
+ * @param {string} ownerTokenName - Owner token name (with !)
137
+ * @returns {string} Base asset name (without !)
138
+ */
139
+ static getBaseAssetName(ownerTokenName) {
140
+ if (this.isOwnerToken(ownerTokenName)) {
141
+ return ownerTokenName.slice(0, -1);
142
+ }
143
+ return ownerTokenName;
144
+ }
145
+
146
+ /**
147
+ * Check if asset is restricted
148
+ * @param {string} name - Asset name
149
+ * @returns {boolean} True if restricted
150
+ */
151
+ static isRestricted(name) {
152
+ return name.startsWith('$');
153
+ }
154
+
155
+ /**
156
+ * Check if asset is a qualifier
157
+ * @param {string} name - Asset name
158
+ * @returns {boolean} True if qualifier
159
+ */
160
+ static isQualifier(name) {
161
+ return name.startsWith('#');
162
+ }
163
+
164
+ /**
165
+ * Check if asset is unique (NFT)
166
+ * @param {string} name - Asset name
167
+ * @returns {boolean} True if unique
168
+ */
169
+ static isUnique(name) {
170
+ return name.includes('#') && !name.startsWith('#');
171
+ }
172
+
173
+ /**
174
+ * Check if asset is a sub-asset
175
+ * @param {string} name - Asset name
176
+ * @returns {boolean} True if sub-asset
177
+ */
178
+ static isSub(name) {
179
+ return name.includes('/') && !name.startsWith('#');
180
+ }
181
+
182
+ /**
183
+ * Build unique asset name
184
+ * @param {string} rootName - Root asset name
185
+ * @param {string} tag - Unique tag
186
+ * @returns {string} Full unique asset name
187
+ */
188
+ static buildUniqueName(rootName, tag) {
189
+ return `${rootName}#${tag}`;
190
+ }
191
+
192
+ /**
193
+ * Build sub-asset name
194
+ * @param {string} rootName - Root asset name
195
+ * @param {string} subName - Sub-asset name
196
+ * @returns {string} Full sub-asset name
197
+ */
198
+ static buildSubName(rootName, subName) {
199
+ return `${rootName}/${subName}`;
200
+ }
201
+ }
202
+
203
+ module.exports = AssetNameParser;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Utils Module
3
+ * Exports all utility classes
4
+ */
5
+
6
+ const AssetNameParser = require('./assetNameParser');
7
+ const AmountConverter = require('./amountConverter');
8
+ const NetworkDetector = require('./networkDetector');
9
+ const OutputFormatter = require('./outputFormatter');
10
+
11
+ module.exports = {
12
+ AssetNameParser,
13
+ AmountConverter,
14
+ NetworkDetector,
15
+ OutputFormatter
16
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Network Detector
3
+ * Detects network type from various sources
4
+ */
5
+
6
+ const { NETWORKS } = require('../constants');
7
+
8
+ class NetworkDetector {
9
+ /**
10
+ * Detect network from RPC client
11
+ * Calls getblockchaininfo to determine network
12
+ * @param {Function} rpc - RPC function
13
+ * @returns {Promise<string>} Network name ('xna' or 'xna-test')
14
+ */
15
+ static async detectFromRPC(rpc) {
16
+ try {
17
+ const blockchainInfo = await rpc('getblockchaininfo', []);
18
+
19
+ // Check chain name
20
+ if (blockchainInfo.chain === 'main') {
21
+ return 'xna';
22
+ } else if (blockchainInfo.chain === 'test') {
23
+ return 'xna-test';
24
+ } else if (blockchainInfo.chain === 'regtest') {
25
+ return 'xna-test'; // Treat regtest as testnet
26
+ }
27
+
28
+ // Fallback: check if testnet field exists
29
+ if (blockchainInfo.testnet === true) {
30
+ return 'xna-test';
31
+ }
32
+
33
+ // Default to mainnet
34
+ return 'xna';
35
+ } catch (error) {
36
+ throw new Error(`Failed to detect network from RPC: ${error.message}`);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Detect network from address
42
+ * @param {string} address - Neurai address
43
+ * @returns {string} Network name ('xna' or 'xna-test')
44
+ */
45
+ static detectFromAddress(address) {
46
+ if (!address || typeof address !== 'string') {
47
+ throw new Error('Address must be a non-empty string');
48
+ }
49
+
50
+ // Mainnet addresses start with 'N'
51
+ if (address.startsWith(NETWORKS.MAINNET.addressPrefix)) {
52
+ return 'xna';
53
+ }
54
+
55
+ // Testnet addresses start with 'm' or 'n'
56
+ if (address.startsWith(NETWORKS.TESTNET.addressPrefix) || address.startsWith('n')) {
57
+ return 'xna-test';
58
+ }
59
+
60
+ throw new Error(`Cannot detect network from address: ${address}`);
61
+ }
62
+
63
+ /**
64
+ * Detect network from multiple addresses
65
+ * @param {string[]} addresses - Array of addresses
66
+ * @returns {string} Network name ('xna' or 'xna-test')
67
+ */
68
+ static detectFromAddresses(addresses) {
69
+ if (!Array.isArray(addresses) || addresses.length === 0) {
70
+ throw new Error('Addresses must be a non-empty array');
71
+ }
72
+
73
+ // Detect from first address
74
+ const network = this.detectFromAddress(addresses[0]);
75
+
76
+ // Verify all addresses are from the same network
77
+ for (let i = 1; i < addresses.length; i++) {
78
+ const otherNetwork = this.detectFromAddress(addresses[i]);
79
+ if (otherNetwork !== network) {
80
+ throw new Error(`Mixed network addresses detected: ${network} and ${otherNetwork}`);
81
+ }
82
+ }
83
+
84
+ return network;
85
+ }
86
+
87
+ /**
88
+ * Validate that addresses match expected network
89
+ * @param {string[]} addresses - Array of addresses
90
+ * @param {string} expectedNetwork - Expected network ('xna' or 'xna-test')
91
+ * @returns {boolean} True if all addresses match network
92
+ */
93
+ static validateAddressesNetwork(addresses, expectedNetwork) {
94
+ if (!Array.isArray(addresses) || addresses.length === 0) {
95
+ throw new Error('Addresses must be a non-empty array');
96
+ }
97
+
98
+ for (const address of addresses) {
99
+ const network = this.detectFromAddress(address);
100
+ if (network !== expectedNetwork) {
101
+ throw new Error(
102
+ `Address ${address} is from ${network} but expected ${expectedNetwork}`
103
+ );
104
+ }
105
+ }
106
+
107
+ return true;
108
+ }
109
+
110
+ /**
111
+ * Get network config from network name
112
+ * @param {string} network - Network name
113
+ * @returns {object} Network configuration
114
+ */
115
+ static getNetworkConfig(network) {
116
+ if (network === 'xna' || network === 'mainnet') {
117
+ return NETWORKS.MAINNET;
118
+ } else if (network === 'xna-test' || network === 'testnet') {
119
+ return NETWORKS.TESTNET;
120
+ } else {
121
+ throw new Error(`Unknown network: ${network}`);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Check if network is mainnet
127
+ * @param {string} network - Network name
128
+ * @returns {boolean} True if mainnet
129
+ */
130
+ static isMainnet(network) {
131
+ return network === 'xna' || network === 'mainnet';
132
+ }
133
+
134
+ /**
135
+ * Check if network is testnet
136
+ * @param {string} network - Network name
137
+ * @returns {boolean} True if testnet
138
+ */
139
+ static isTestnet(network) {
140
+ return network === 'xna-test' || network === 'testnet' || network === 'regtest';
141
+ }
142
+ }
143
+
144
+ module.exports = NetworkDetector;
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Output Formatter
3
+ * Formats outputs for createrawtransaction RPC calls
4
+ */
5
+
6
+ class OutputFormatter {
7
+ /**
8
+ * Format issue operation output
9
+ * @param {object} params - Issue parameters
10
+ * @returns {object} Formatted output for createrawtransaction
11
+ */
12
+ static formatIssueOutput(params) {
13
+ const {
14
+ asset_name,
15
+ asset_quantity,
16
+ units,
17
+ reissuable,
18
+ has_ipfs,
19
+ ipfs_hash
20
+ } = params;
21
+
22
+ return {
23
+ issue: {
24
+ asset_name,
25
+ asset_quantity,
26
+ units,
27
+ reissuable: reissuable ? 1 : 0,
28
+ has_ipfs: has_ipfs ? 1 : 0,
29
+ ipfs_hash: ipfs_hash || ''
30
+ }
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Format issue unique operation output
36
+ * @param {object} params - Issue unique parameters
37
+ * @returns {object} Formatted output for createrawtransaction
38
+ */
39
+ static formatIssueUniqueOutput(params) {
40
+ const { root_name, asset_tags, ipfs_hashes } = params;
41
+
42
+ return {
43
+ issue_unique: {
44
+ root_name,
45
+ asset_tags,
46
+ ipfs_hashes: ipfs_hashes || []
47
+ }
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Format issue restricted operation output
53
+ * @param {object} params - Issue restricted parameters
54
+ * @returns {object} Formatted output for createrawtransaction
55
+ */
56
+ static formatIssueRestrictedOutput(params) {
57
+ const {
58
+ asset_name,
59
+ asset_quantity,
60
+ verifier_string,
61
+ units,
62
+ reissuable,
63
+ has_ipfs,
64
+ ipfs_hash
65
+ } = params;
66
+
67
+ return {
68
+ issue_restricted: {
69
+ asset_name,
70
+ asset_quantity,
71
+ verifier_string,
72
+ units,
73
+ reissuable: reissuable ? 1 : 0,
74
+ has_ipfs: has_ipfs ? 1 : 0,
75
+ ipfs_hash: ipfs_hash || ''
76
+ }
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Format issue qualifier operation output
82
+ * @param {object} params - Issue qualifier parameters
83
+ * @returns {object} Formatted output for createrawtransaction
84
+ */
85
+ static formatIssueQualifierOutput(params) {
86
+ const {
87
+ asset_name,
88
+ asset_quantity,
89
+ has_ipfs,
90
+ ipfs_hash
91
+ } = params;
92
+
93
+ return {
94
+ issue_qualifier: {
95
+ asset_name,
96
+ asset_quantity,
97
+ has_ipfs: has_ipfs ? 1 : 0,
98
+ ipfs_hash: ipfs_hash || ''
99
+ }
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Format reissue operation output
105
+ * @param {object} params - Reissue parameters
106
+ * @returns {object} Formatted output for createrawtransaction
107
+ */
108
+ static formatReissueOutput(params) {
109
+ const {
110
+ asset_name,
111
+ asset_quantity,
112
+ reissuable,
113
+ new_ipfs
114
+ } = params;
115
+
116
+ const output = {
117
+ reissue: {
118
+ asset_name,
119
+ asset_quantity
120
+ }
121
+ };
122
+
123
+ // Optional parameters
124
+ if (reissuable !== undefined) {
125
+ output.reissue.reissuable = reissuable ? 1 : 0;
126
+ }
127
+
128
+ if (new_ipfs) {
129
+ output.reissue.ipfs_hash = new_ipfs;
130
+ }
131
+
132
+ return output;
133
+ }
134
+
135
+ /**
136
+ * Format reissue restricted operation output
137
+ * @param {object} params - Reissue restricted parameters
138
+ * @returns {object} Formatted output for createrawtransaction
139
+ */
140
+ static formatReissueRestrictedOutput(params) {
141
+ const {
142
+ asset_name,
143
+ asset_quantity,
144
+ change_verifier,
145
+ new_verifier,
146
+ reissuable,
147
+ new_ipfs
148
+ } = params;
149
+
150
+ const output = {
151
+ reissue_restricted: {
152
+ asset_name,
153
+ asset_quantity
154
+ }
155
+ };
156
+
157
+ // Optional parameters
158
+ if (change_verifier && new_verifier) {
159
+ output.reissue_restricted.verifier_string = new_verifier;
160
+ }
161
+
162
+ if (reissuable !== undefined) {
163
+ output.reissue_restricted.reissuable = reissuable ? 1 : 0;
164
+ }
165
+
166
+ if (new_ipfs) {
167
+ output.reissue_restricted.ipfs_hash = new_ipfs;
168
+ }
169
+
170
+ return output;
171
+ }
172
+
173
+ /**
174
+ * Format asset transfer output
175
+ * @param {string} assetName - Asset name
176
+ * @param {number} amount - Amount to transfer
177
+ * @returns {object} Formatted transfer output
178
+ */
179
+ static formatTransferOutput(assetName, amount) {
180
+ return {
181
+ transfer: {
182
+ [assetName]: amount
183
+ }
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Format tag addresses operation output
189
+ * @param {object} params - Tag addresses parameters
190
+ * @returns {object} Formatted output for createrawtransaction
191
+ */
192
+ static formatTagAddressesOutput(params) {
193
+ const {
194
+ tag_name,
195
+ addresses,
196
+ asset_data
197
+ } = params;
198
+
199
+ return {
200
+ tag_addresses: {
201
+ tag_name,
202
+ addresses,
203
+ asset_data: asset_data || ''
204
+ }
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Format untag addresses operation output
210
+ * @param {object} params - Untag addresses parameters
211
+ * @returns {object} Formatted output for createrawtransaction
212
+ */
213
+ static formatUntagAddressesOutput(params) {
214
+ const {
215
+ tag_name,
216
+ addresses
217
+ } = params;
218
+
219
+ return {
220
+ untag_addresses: {
221
+ tag_name,
222
+ addresses
223
+ }
224
+ };
225
+ }
226
+
227
+ /**
228
+ * Format freeze addresses operation output
229
+ * @param {object} params - Freeze addresses parameters
230
+ * @returns {object} Formatted output for createrawtransaction
231
+ */
232
+ static formatFreezeAddressesOutput(params) {
233
+ const {
234
+ asset_name,
235
+ addresses
236
+ } = params;
237
+
238
+ return {
239
+ freeze_addresses: {
240
+ asset_name,
241
+ addresses
242
+ }
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Format unfreeze addresses operation output
248
+ * @param {object} params - Unfreeze addresses parameters
249
+ * @returns {object} Formatted output for createrawtransaction
250
+ */
251
+ static formatUnfreezeAddressesOutput(params) {
252
+ const {
253
+ asset_name,
254
+ addresses
255
+ } = params;
256
+
257
+ return {
258
+ unfreeze_addresses: {
259
+ asset_name,
260
+ addresses
261
+ }
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Format freeze asset operation output
267
+ * @param {string} assetName - Restricted asset name
268
+ * @returns {object} Formatted output for createrawtransaction
269
+ */
270
+ static formatFreezeAssetOutput(assetName) {
271
+ return {
272
+ freeze_asset: {
273
+ asset_name: assetName
274
+ }
275
+ };
276
+ }
277
+
278
+ /**
279
+ * Format unfreeze asset operation output
280
+ * @param {string} assetName - Restricted asset name
281
+ * @returns {object} Formatted output for createrawtransaction
282
+ */
283
+ static formatUnfreezeAssetOutput(assetName) {
284
+ return {
285
+ unfreeze_asset: {
286
+ asset_name: assetName
287
+ }
288
+ };
289
+ }
290
+ }
291
+
292
+ module.exports = OutputFormatter;