@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.
- package/README.md +522 -0
- package/examples/01-create-root-asset.js +71 -0
- package/examples/02-create-sub-asset.js +79 -0
- package/examples/03-create-nfts.js +140 -0
- package/examples/04-reissue-asset.js +164 -0
- package/examples/05-create-qualifier-and-tag.js +209 -0
- package/examples/06-create-restricted-asset.js +223 -0
- package/examples/07-freeze-and-unfreeze.js +292 -0
- package/examples/08-query-assets.js +332 -0
- package/examples/09-wallet-integration.js +320 -0
- package/examples/README.md +319 -0
- package/package.json +43 -0
- package/src/NeuraiAssets.js +468 -0
- package/src/builders/BaseAssetTransactionBuilder.js +303 -0
- package/src/builders/FreezeAddressBuilder.js +271 -0
- package/src/builders/IssueQualifierBuilder.js +251 -0
- package/src/builders/IssueRestrictedBuilder.js +187 -0
- package/src/builders/IssueRootBuilder.js +173 -0
- package/src/builders/IssueSubBuilder.js +237 -0
- package/src/builders/IssueUniqueBuilder.js +255 -0
- package/src/builders/ReissueBuilder.js +246 -0
- package/src/builders/ReissueRestrictedBuilder.js +264 -0
- package/src/builders/TagAddressBuilder.js +243 -0
- package/src/builders/index.js +38 -0
- package/src/constants/assetTypes.js +23 -0
- package/src/constants/burnAddresses.js +65 -0
- package/src/constants/fees.js +61 -0
- package/src/constants/index.js +44 -0
- package/src/constants/networks.js +112 -0
- package/src/errors/AssetErrors.js +135 -0
- package/src/errors/ValidationErrors.js +87 -0
- package/src/errors/index.js +56 -0
- package/src/index.js +68 -0
- package/src/managers/BurnManager.js +222 -0
- package/src/managers/OutputOrderer.js +289 -0
- package/src/managers/OwnerTokenManager.js +265 -0
- package/src/managers/UTXOSelector.js +309 -0
- package/src/managers/index.js +16 -0
- package/src/queries/AssetQueries.js +447 -0
- package/src/queries/index.js +10 -0
- package/src/utils/amountConverter.js +115 -0
- package/src/utils/assetNameParser.js +203 -0
- package/src/utils/index.js +16 -0
- package/src/utils/networkDetector.js +144 -0
- package/src/utils/outputFormatter.js +292 -0
- package/src/validators/amountValidator.js +149 -0
- package/src/validators/assetNameValidator.js +296 -0
- package/src/validators/index.js +16 -0
- package/src/validators/ipfsValidator.js +101 -0
- package/src/validators/verifierValidator.js +146 -0
- package/tests/README.md +126 -0
- package/tests/integration/assetLifecycle.test.js +244 -0
- package/tests/mocks/rpcMock.js +156 -0
- package/tests/unit/NeuraiAssets.test.js +217 -0
- package/tests/unit/utils/amountConverter.test.js +171 -0
- package/tests/unit/utils/assetNameParser.test.js +203 -0
- package/tests/unit/validators/amountValidator.test.js +143 -0
- 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
|
+
};
|