@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,243 @@
1
+ /**
2
+ * Tag Address Builder
3
+ * Builds transactions for tagging/untagging addresses with qualifiers
4
+ *
5
+ * Tag operations:
6
+ * - Assign qualifier tags to addresses (for restricted asset compliance)
7
+ * - Remove qualifier tags from addresses
8
+ * - Cost: 0.1 XNA per address (burned)
9
+ * - Requires qualifier's owner token (#QUALIFIER!)
10
+ * - Used to mark addresses as KYC'd, accredited, etc.
11
+ * - Owner token must be returned
12
+ */
13
+
14
+ const BaseAssetTransactionBuilder = require('./BaseAssetTransactionBuilder');
15
+ const { OutputFormatter, AssetNameParser } = require('../utils');
16
+ const { AssetNotFoundError, OwnerTokenNotFoundError, InvalidAddressError } = require('../errors');
17
+
18
+ class TagAddressBuilder extends BaseAssetTransactionBuilder {
19
+ /**
20
+ * Validate tag/untag parameters
21
+ * @param {object} params - Tag parameters
22
+ * @param {boolean} isUntag - True if untag operation
23
+ * @throws {Error} If validation fails
24
+ */
25
+ validateParams(params, isUntag = false) {
26
+ // Validate required parameters
27
+ if (!params.qualifierName) {
28
+ throw new Error('qualifierName is required');
29
+ }
30
+
31
+ if (!params.addresses || !Array.isArray(params.addresses) || params.addresses.length === 0) {
32
+ throw new Error('addresses is required and must be a non-empty array');
33
+ }
34
+
35
+ // Validate qualifier name
36
+ this.validateAssetName(params.qualifierName, 'QUALIFIER');
37
+
38
+ // Validate addresses
39
+ params.addresses.forEach((address, index) => {
40
+ if (!address || typeof address !== 'string') {
41
+ throw new InvalidAddressError(
42
+ `addresses[${index}] must be a non-empty string`,
43
+ address
44
+ );
45
+ }
46
+
47
+ // Basic address validation (starts with N or m/n depending on network)
48
+ const validPrefixes = this.network === 'xna' ? ['N'] : ['m', 'n'];
49
+ if (!validPrefixes.some(prefix => address.startsWith(prefix))) {
50
+ throw new InvalidAddressError(
51
+ `addresses[${index}] has invalid prefix for network ${this.network}`,
52
+ address
53
+ );
54
+ }
55
+ });
56
+
57
+ return true;
58
+ }
59
+
60
+ /**
61
+ * Build tag addresses transaction
62
+ * @param {boolean} isUntag - True for untag operation, false for tag
63
+ * @returns {Promise<object>} Transaction result
64
+ */
65
+ async buildTagOperation(isUntag = false) {
66
+ // 1. Validate parameters
67
+ await this.validateParams(this.params, isUntag);
68
+
69
+ const {
70
+ qualifierName,
71
+ addresses: targetAddresses,
72
+ assetData = ''
73
+ } = this.params;
74
+
75
+ // 2. Check if qualifier exists
76
+ const qualifierExists = await this.assetExists(qualifierName);
77
+ if (!qualifierExists) {
78
+ throw new AssetNotFoundError(
79
+ `Qualifier ${qualifierName} does not exist. You must create the qualifier first.`,
80
+ qualifierName
81
+ );
82
+ }
83
+
84
+ // 3. Get wallet addresses
85
+ const addresses = await this._getAddresses();
86
+ const changeAddress = await this.getChangeAddress();
87
+
88
+ // 4. Find qualifier's owner token (CRITICAL: must have this)
89
+ const ownerTokenName = AssetNameParser.getOwnerTokenName(qualifierName);
90
+ let ownerTokenUTXO;
91
+ try {
92
+ ownerTokenUTXO = await this.ownerTokenManager.findOwnerTokenUTXO(
93
+ ownerTokenName,
94
+ addresses
95
+ );
96
+ } catch (error) {
97
+ if (error instanceof OwnerTokenNotFoundError) {
98
+ throw new OwnerTokenNotFoundError(
99
+ `You must own the qualifier's owner token (${ownerTokenName}) to tag/untag addresses.`,
100
+ ownerTokenName
101
+ );
102
+ }
103
+ throw error;
104
+ }
105
+
106
+ // 5. Get burn information (0.1 XNA per address)
107
+ const addressCount = targetAddresses.length;
108
+ const burnInfo = isUntag
109
+ ? this.burnManager.getUntagAddressBurn(addressCount)
110
+ : this.burnManager.getTagAddressBurn(addressCount);
111
+
112
+ // 6. Estimate fee
113
+ const estimatedFee = await this.estimateFee(2, 3);
114
+
115
+ // 7. Calculate total XNA needed
116
+ const totalXNANeeded = burnInfo.amount + estimatedFee;
117
+
118
+ // 8. Select XNA UTXOs
119
+ const utxoSelection = await this.selectUTXOs(totalXNANeeded, null, 0);
120
+ const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
121
+ const totalXNAInput = utxoSelection.totalXNA;
122
+
123
+ // 9. Recalculate fee with actual input count
124
+ const actualInputCount = baseCurrencyUTXOs.length + 1; // +1 for owner token
125
+ const actualFee = await this.estimateFee(actualInputCount, 3);
126
+
127
+ // 10. Verify we have enough XNA
128
+ const totalRequired = burnInfo.amount + actualFee;
129
+ if (totalXNAInput < totalRequired) {
130
+ const additionalNeeded = totalRequired - totalXNAInput + 0.001;
131
+ const additionalSelection = await this.selectUTXOs(additionalNeeded, null, 0);
132
+ baseCurrencyUTXOs.push(...additionalSelection.xnaUTXOs);
133
+ }
134
+
135
+ // 11. Calculate XNA change
136
+ const finalTotalInput = baseCurrencyUTXOs.reduce(
137
+ (sum, utxo) => sum + utxo.satoshis / 100000000,
138
+ 0
139
+ );
140
+ const xnaChange = finalTotalInput - burnInfo.amount - actualFee;
141
+
142
+ // 12. Build inputs (XNA + owner token)
143
+ const inputs = [];
144
+
145
+ // Add XNA inputs
146
+ baseCurrencyUTXOs.forEach(utxo => {
147
+ inputs.push({
148
+ txid: utxo.txid,
149
+ vout: utxo.outputIndex,
150
+ address: utxo.address,
151
+ satoshis: utxo.satoshis
152
+ });
153
+ });
154
+
155
+ // Add owner token input
156
+ inputs.push({
157
+ txid: ownerTokenUTXO.txid,
158
+ vout: ownerTokenUTXO.outputIndex,
159
+ address: ownerTokenUTXO.address,
160
+ assetName: ownerTokenUTXO.assetName,
161
+ satoshis: ownerTokenUTXO.satoshis
162
+ });
163
+
164
+ // 13. Build outputs (ORDER CRITICAL!)
165
+ const outputs = {};
166
+
167
+ // First: Burn output
168
+ outputs[burnInfo.address] = burnInfo.amount;
169
+
170
+ // Second: XNA change (if any)
171
+ if (xnaChange > 0.00000001) {
172
+ outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
173
+ }
174
+
175
+ // Third: Owner token return (CRITICAL - must return or lost forever!)
176
+ const ownerTokenReturn = this.ownerTokenManager.createOwnerTokenReturnOutput(
177
+ ownerTokenName,
178
+ changeAddress
179
+ );
180
+ Object.assign(outputs, ownerTokenReturn);
181
+
182
+ // Last: Tag/Untag operation
183
+ // Note: Using first address as transaction output address (protocol requirement)
184
+ const operationOutput = isUntag
185
+ ? OutputFormatter.formatUntagAddressesOutput({
186
+ tag_name: qualifierName,
187
+ addresses: targetAddresses
188
+ })
189
+ : OutputFormatter.formatTagAddressesOutput({
190
+ tag_name: qualifierName,
191
+ addresses: targetAddresses,
192
+ asset_data: assetData
193
+ });
194
+
195
+ outputs[targetAddresses[0]] = operationOutput;
196
+
197
+ // 14. Order outputs (protocol requirement)
198
+ const orderedOutputs = this.outputOrderer.order(outputs);
199
+
200
+ // 15. Validate owner token is returned (safety check)
201
+ this.ownerTokenManager.validateOwnerTokenReturn(inputs, orderedOutputs);
202
+
203
+ // 16. Create raw transaction
204
+ const rawTx = await this.buildRawTransaction(inputs, orderedOutputs);
205
+
206
+ // 17. Format and return result
207
+ const allUTXOs = [...baseCurrencyUTXOs, ownerTokenUTXO];
208
+
209
+ return this.formatResult(
210
+ rawTx,
211
+ allUTXOs,
212
+ inputs,
213
+ orderedOutputs,
214
+ actualFee,
215
+ burnInfo.amount,
216
+ {
217
+ qualifierName,
218
+ ownerTokenUsed: ownerTokenName,
219
+ targetAddresses,
220
+ addressCount,
221
+ operationType: isUntag ? 'UNTAG_ADDRESSES' : 'TAG_ADDRESSES'
222
+ }
223
+ );
224
+ }
225
+
226
+ /**
227
+ * Build tag addresses transaction
228
+ * @returns {Promise<object>} Transaction result
229
+ */
230
+ async build() {
231
+ return this.buildTagOperation(false);
232
+ }
233
+
234
+ /**
235
+ * Build untag addresses transaction
236
+ * @returns {Promise<object>} Transaction result
237
+ */
238
+ async buildUntag() {
239
+ return this.buildTagOperation(true);
240
+ }
241
+ }
242
+
243
+ module.exports = TagAddressBuilder;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Builders Module
3
+ * Exports all transaction builder classes
4
+ */
5
+
6
+ // Base
7
+ const BaseAssetTransactionBuilder = require('./BaseAssetTransactionBuilder');
8
+
9
+ // Basic Builders
10
+ const IssueRootBuilder = require('./IssueRootBuilder');
11
+ const IssueSubBuilder = require('./IssueSubBuilder');
12
+ const ReissueBuilder = require('./ReissueBuilder');
13
+
14
+ // Advanced Builders
15
+ const IssueUniqueBuilder = require('./IssueUniqueBuilder');
16
+ const IssueQualifierBuilder = require('./IssueQualifierBuilder');
17
+ const IssueRestrictedBuilder = require('./IssueRestrictedBuilder');
18
+ const ReissueRestrictedBuilder = require('./ReissueRestrictedBuilder');
19
+ const TagAddressBuilder = require('./TagAddressBuilder');
20
+ const FreezeAddressBuilder = require('./FreezeAddressBuilder');
21
+
22
+ module.exports = {
23
+ // Base
24
+ BaseAssetTransactionBuilder,
25
+
26
+ // Basic Builders
27
+ IssueRootBuilder,
28
+ IssueSubBuilder,
29
+ ReissueBuilder,
30
+
31
+ // Advanced Builders
32
+ IssueUniqueBuilder,
33
+ IssueQualifierBuilder,
34
+ IssueRestrictedBuilder,
35
+ ReissueRestrictedBuilder,
36
+ TagAddressBuilder,
37
+ FreezeAddressBuilder
38
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Neurai Asset Types
3
+ * Based on: src/assets/assettypes.h
4
+ */
5
+
6
+ const AssetType = {
7
+ ROOT: 0, // Top-level asset (Cost: 1000 XNA)
8
+ SUB: 1, // Sub-asset (Cost: 200 XNA, requires PARENT!)
9
+ UNIQUE: 2, // NFT/Unique asset (Cost: 10 XNA per token)
10
+ MSGCHANNEL: 3, // Message channel (Cost: 200 XNA, legacy)
11
+ QUALIFIER: 4, // KYC qualifier tag (Cost: 2000 XNA)
12
+ SUB_QUALIFIER: 5, // Sub-qualifier (Cost: 200 XNA)
13
+ RESTRICTED: 6, // Restricted/security token (Cost: 3000 XNA)
14
+ VOTE: 7, // Voting asset (Reserved for future use)
15
+ REISSUE: 8, // Reissuance operation (Cost: 200 XNA)
16
+ OWNER: 9, // Owner token (Cost: 0 XNA, auto-generated)
17
+ NULL_ADD_QUALIFIER: 10, // Qualifier assignment (Cost: 0.1 XNA)
18
+ DEPIN: 12 // Soulbound DePIN asset (Testnet only)
19
+ };
20
+
21
+ module.exports = {
22
+ AssetType
23
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Burn Addresses for Asset Operations
3
+ * Different addresses for mainnet and testnet
4
+ */
5
+
6
+ const MAINNET_BURN_ADDRESSES = {
7
+ ISSUE_ROOT: 'NbURNXXXXXXXXXXXXXXXXXXXXXXXT65Gdr',
8
+ ISSUE_SUB: 'NXissueSubAssetXXXXXXXXXXXXXX6B2JF',
9
+ ISSUE_UNIQUE: 'NXissueUniqueAssetXXXXXXXXXXUBzP4Z',
10
+ ISSUE_MSGCHANNEL: 'NXissueMsgChanneLAssetXXXXXXVeF2mU',
11
+ REISSUE: 'NXReissueAssetXXXXXXXXXXXXXXVEFAWu',
12
+ ISSUE_RESTRICTED: 'NXissueRestrictedXXXXXXXXXXXZaQtSE',
13
+ ISSUE_QUALIFIER: 'NXissueQualifierAssetXXXXXXXXWPHpDX',
14
+ ISSUE_SUB_QUALIFIER: 'NXissueSubQualifierAssetXXXXXYWcFp8',
15
+ TAG_ADDRESS: 'NXaddTagBurnXXXXXXXXXXXXXXXXUBL1a5',
16
+ UNTAG_ADDRESS: 'NXaddTagBurnXXXXXXXXXXXXXXXXUBL1a5'
17
+ };
18
+
19
+ const TESTNET_BURN_ADDRESSES = {
20
+ ISSUE_ROOT: 'mbURNXXXXXXXXXXXXXXXXXXXXXXUV9F72',
21
+ ISSUE_SUB: 'mXissueSubAssetXXXXXXXXXXXXXUDJP6N',
22
+ ISSUE_UNIQUE: 'mXissueUniqueAssetXXXXXXXXXXQV8W2V',
23
+ ISSUE_MSGCHANNEL: 'mXissueMsgChanneLAssetXXXXXXX9fhPh',
24
+ REISSUE: 'mXReissueAssetXXXXXXXXXXXXXXTZzJ3c',
25
+ ISSUE_RESTRICTED: 'mXissueRestrictedXXXXXXXXXXXXYHqJY',
26
+ ISSUE_QUALIFIER: 'mXissueQualifierAssetXXXXXXXXZhqiGs',
27
+ ISSUE_SUB_QUALIFIER: 'mXissueSubQualifierAssetXXXXXab7bRN',
28
+ TAG_ADDRESS: 'mXaddTagBurnXXXXXXXXXXXXXXXXWEKFqa',
29
+ UNTAG_ADDRESS: 'mXaddTagBurnXXXXXXXXXXXXXXXXWEKFqa'
30
+ };
31
+
32
+ /**
33
+ * Get burn address for an operation and network
34
+ * @param {string} operationType - Operation type (e.g., 'ISSUE_ROOT')
35
+ * @param {string} network - Network type ('xna' or 'xna-test')
36
+ * @returns {string} Burn address
37
+ */
38
+ function getBurnAddress(operationType, network) {
39
+ const addresses = network === 'xna' ? MAINNET_BURN_ADDRESSES : TESTNET_BURN_ADDRESSES;
40
+
41
+ const address = addresses[operationType];
42
+ if (!address) {
43
+ throw new Error(`Unknown operation type: ${operationType} for network: ${network}`);
44
+ }
45
+
46
+ return address;
47
+ }
48
+
49
+ /**
50
+ * Check if an address is a burn address
51
+ * @param {string} address - Address to check
52
+ * @param {string} network - Network type ('xna' or 'xna-test')
53
+ * @returns {boolean} True if it's a burn address
54
+ */
55
+ function isBurnAddress(address, network) {
56
+ const addresses = network === 'xna' ? MAINNET_BURN_ADDRESSES : TESTNET_BURN_ADDRESSES;
57
+ return Object.values(addresses).includes(address);
58
+ }
59
+
60
+ module.exports = {
61
+ MAINNET_BURN_ADDRESSES,
62
+ TESTNET_BURN_ADDRESSES,
63
+ getBurnAddress,
64
+ isBurnAddress
65
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Asset Operation Costs (in XNA)
3
+ * These are burn amounts required for each operation
4
+ */
5
+
6
+ const ASSET_COSTS = {
7
+ ISSUE_ROOT: 1000,
8
+ ISSUE_SUB: 200,
9
+ ISSUE_UNIQUE: 10, // Per NFT
10
+ ISSUE_MSGCHANNEL: 200,
11
+ ISSUE_QUALIFIER: 2000,
12
+ ISSUE_SUB_QUALIFIER: 200,
13
+ ISSUE_RESTRICTED: 3000,
14
+ REISSUE: 200,
15
+ REISSUE_RESTRICTED: 200,
16
+ TAG_ADDRESS: 0.1, // Per address
17
+ UNTAG_ADDRESS: 0.1, // Per address
18
+ FREEZE_ADDRESS: 0, // No cost (requires owner token)
19
+ UNFREEZE_ADDRESS: 0, // No cost (requires owner token)
20
+ FREEZE_ASSET: 0, // No cost (requires owner token)
21
+ UNFREEZE_ASSET: 0, // No cost (requires owner token)
22
+ OWNER_TOKEN: 0 // Auto-generated, no cost
23
+ };
24
+
25
+ /**
26
+ * Get cost for a specific operation type
27
+ * @param {string} operationType - Operation type (e.g., 'ISSUE_ROOT')
28
+ * @returns {number} Cost in XNA
29
+ */
30
+ function getAssetCost(operationType) {
31
+ const cost = ASSET_COSTS[operationType];
32
+ if (cost === undefined) {
33
+ throw new Error(`Unknown operation type: ${operationType}`);
34
+ }
35
+ return cost;
36
+ }
37
+
38
+ /**
39
+ * Calculate total cost for unique asset issuance
40
+ * @param {number} count - Number of unique assets to create
41
+ * @returns {number} Total cost in XNA
42
+ */
43
+ function getUniqueAssetCost(count) {
44
+ return ASSET_COSTS.ISSUE_UNIQUE * count;
45
+ }
46
+
47
+ /**
48
+ * Calculate total cost for tagging multiple addresses
49
+ * @param {number} addressCount - Number of addresses to tag
50
+ * @returns {number} Total cost in XNA
51
+ */
52
+ function getTaggingCost(addressCount) {
53
+ return ASSET_COSTS.TAG_ADDRESS * addressCount;
54
+ }
55
+
56
+ module.exports = {
57
+ ASSET_COSTS,
58
+ getAssetCost,
59
+ getUniqueAssetCost,
60
+ getTaggingCost
61
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Constants Module
3
+ * Exports all constant values and configurations
4
+ */
5
+
6
+ const { AssetType } = require('./assetTypes');
7
+ const { ASSET_COSTS, getAssetCost, getUniqueAssetCost, getTaggingCost } = require('./fees');
8
+ const {
9
+ MAINNET_BURN_ADDRESSES,
10
+ TESTNET_BURN_ADDRESSES,
11
+ getBurnAddress,
12
+ isBurnAddress
13
+ } = require('./burnAddresses');
14
+ const {
15
+ NETWORKS,
16
+ ASSET_NAME_RULES,
17
+ ASSET_LIMITS,
18
+ getNetworkConfig,
19
+ detectNetworkFromAddress
20
+ } = require('./networks');
21
+
22
+ module.exports = {
23
+ // Asset Types
24
+ AssetType,
25
+
26
+ // Fees/Costs
27
+ ASSET_COSTS,
28
+ getAssetCost,
29
+ getUniqueAssetCost,
30
+ getTaggingCost,
31
+
32
+ // Burn Addresses
33
+ MAINNET_BURN_ADDRESSES,
34
+ TESTNET_BURN_ADDRESSES,
35
+ getBurnAddress,
36
+ isBurnAddress,
37
+
38
+ // Networks
39
+ NETWORKS,
40
+ ASSET_NAME_RULES,
41
+ ASSET_LIMITS,
42
+ getNetworkConfig,
43
+ detectNetworkFromAddress
44
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Network Configuration for Neurai
3
+ */
4
+
5
+ const NETWORKS = {
6
+ MAINNET: {
7
+ name: 'xna',
8
+ displayName: 'Neurai Mainnet',
9
+ addressPrefix: 'N',
10
+ assetNameMaxLength: 31,
11
+ defaultRPCPort: 8766,
12
+ coin: 'XNA'
13
+ },
14
+ TESTNET: {
15
+ name: 'xna-test',
16
+ displayName: 'Neurai Testnet',
17
+ addressPrefix: 'm',
18
+ assetNameMaxLength: 31, // Same as mainnet
19
+ defaultRPCPort: 18766,
20
+ coin: 'TXNA'
21
+ }
22
+ };
23
+
24
+ /**
25
+ * Asset naming rules (same for all networks)
26
+ */
27
+ const ASSET_NAME_RULES = {
28
+ ROOT: {
29
+ minLength: 3,
30
+ maxLength: 30,
31
+ pattern: /^[A-Z0-9_.]+$/,
32
+ cannotStartWith: ['.', 'A', 'Z'],
33
+ reserved: ['XNA', 'NEURAI']
34
+ },
35
+ SUB: {
36
+ minLength: 1,
37
+ maxLength: 30,
38
+ pattern: /^[A-Z0-9_.]+$/,
39
+ separator: '/',
40
+ maxDepth: 1 // Only one level of sub-assets
41
+ },
42
+ UNIQUE: {
43
+ minLength: 1,
44
+ maxLength: 30,
45
+ pattern: /^[A-Z0-9_.]+$/,
46
+ separator: '#'
47
+ },
48
+ QUALIFIER: {
49
+ minLength: 3,
50
+ maxLength: 30,
51
+ pattern: /^[A-Z0-9_]+$/,
52
+ prefix: '#',
53
+ separator: '/'
54
+ },
55
+ RESTRICTED: {
56
+ minLength: 3,
57
+ maxLength: 30,
58
+ pattern: /^[A-Z0-9_.]+$/,
59
+ prefix: '$'
60
+ }
61
+ };
62
+
63
+ /**
64
+ * Asset quantity limits
65
+ */
66
+ const ASSET_LIMITS = {
67
+ MIN_QUANTITY: 1,
68
+ MAX_QUANTITY: 21000000000, // 21 billion (same as Bitcoin's 21M with 3 extra decimals)
69
+ MIN_UNITS: 0,
70
+ MAX_UNITS: 8,
71
+ OWNER_TOKEN_QUANTITY: 1, // Owner tokens are always exactly 1
72
+ QUALIFIER_MIN_QUANTITY: 1,
73
+ QUALIFIER_MAX_QUANTITY: 10 // Qualifiers are limited to 1-10 units
74
+ };
75
+
76
+ /**
77
+ * Get network configuration
78
+ * @param {string} networkName - Network name ('xna' or 'xna-test')
79
+ * @returns {object} Network configuration
80
+ */
81
+ function getNetworkConfig(networkName) {
82
+ if (networkName === 'xna' || networkName === 'mainnet') {
83
+ return NETWORKS.MAINNET;
84
+ } else if (networkName === 'xna-test' || networkName === 'testnet') {
85
+ return NETWORKS.TESTNET;
86
+ } else {
87
+ throw new Error(`Unknown network: ${networkName}`);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Detect network from address prefix
93
+ * @param {string} address - Neurai address
94
+ * @returns {string} Network name ('xna' or 'xna-test')
95
+ */
96
+ function detectNetworkFromAddress(address) {
97
+ if (address.startsWith('N')) {
98
+ return 'xna';
99
+ } else if (address.startsWith('m') || address.startsWith('n')) {
100
+ return 'xna-test';
101
+ } else {
102
+ throw new Error(`Cannot detect network from address: ${address}`);
103
+ }
104
+ }
105
+
106
+ module.exports = {
107
+ NETWORKS,
108
+ ASSET_NAME_RULES,
109
+ ASSET_LIMITS,
110
+ getNetworkConfig,
111
+ detectNetworkFromAddress
112
+ };