@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,126 @@
1
+ # Tests para @neuraiproject/neurai-assets
2
+
3
+ Esta carpeta contiene la suite completa de tests para la librería de gestión de activos de Neurai.
4
+
5
+ ## Estructura de Tests
6
+
7
+ ```
8
+ tests/
9
+ ├── mocks/ # Mocks para RPC y dependencias
10
+ │ └── rpcMock.js # Mock del RPC de Neurai
11
+ ├── unit/ # Tests unitarios
12
+ │ ├── validators/ # Tests para validadores
13
+ │ ├── utils/ # Tests para utilidades
14
+ │ └── NeuraiAssets.test.js # Tests de la clase principal
15
+ └── integration/ # Tests de integración
16
+ └── assetLifecycle.test.js # Tests del ciclo de vida completo
17
+ ```
18
+
19
+ ## Ejecutar Tests
20
+
21
+ ### Todos los tests
22
+ ```bash
23
+ npm test
24
+ ```
25
+
26
+ ### Solo tests unitarios
27
+ ```bash
28
+ npm run test:unit
29
+ ```
30
+
31
+ ### Solo tests de integración
32
+ ```bash
33
+ npm run test:integration
34
+ ```
35
+
36
+ ### Tests en modo watch (re-ejecuta al guardar cambios)
37
+ ```bash
38
+ npm run test:watch
39
+ ```
40
+
41
+ ## Cobertura de Tests
42
+
43
+ ### Validators (Validadores)
44
+ - **assetNameValidator.test.js**: Valida nombres de activos ROOT, SUB, UNIQUE, QUALIFIER, RESTRICTED y OWNER
45
+ - **amountValidator.test.js**: Valida cantidades, unidades y rangos de activos
46
+
47
+ ### Utils (Utilidades)
48
+ - **assetNameParser.test.js**: Prueba el parsing y detección de tipos de activos
49
+ - **amountConverter.test.js**: Prueba conversiones entre cantidades y satoshis
50
+
51
+ ### Clase Principal
52
+ - **NeuraiAssets.test.js**: Prueba la clase principal y sus métodos
53
+
54
+ ### Integración
55
+ - **assetLifecycle.test.js**: Prueba workflows completos de creación y gestión de activos
56
+
57
+ ## Tests Actuales
58
+
59
+ Total: **124 tests pasando**
60
+
61
+ ### Desglose por módulo:
62
+ - AmountConverter: 24 tests
63
+ - AssetNameParser: 29 tests
64
+ - AmountValidator: 21 tests
65
+ - AssetNameValidator: 33 tests
66
+ - NeuraiAssets: 10 tests
67
+ - Integration Tests: 7 tests
68
+
69
+ ## Mocks Disponibles
70
+
71
+ ### RPCMock
72
+ Mock flexible del RPC de Neurai que permite:
73
+ - Configurar respuestas personalizadas para métodos específicos
74
+ - Rastrear llamadas realizadas
75
+ - Simular errores y casos edge
76
+
77
+ ```javascript
78
+ const { createMockRPC, createMockUTXO, createMockAssetData } = require('./mocks/rpcMock');
79
+
80
+ // Crear mock con respuestas personalizadas
81
+ const mockRPC = createMockRPC({
82
+ 'getassetdata': { name: 'MYTOKEN', amount: 1000000 }
83
+ });
84
+
85
+ // Usar en tests
86
+ const assets = new NeuraiAssets(mockRPC);
87
+ ```
88
+
89
+ ## Agregar Nuevos Tests
90
+
91
+ Para agregar nuevos tests:
92
+
93
+ 1. **Tests unitarios**: Coloca el archivo en `tests/unit/[modulo]/`
94
+ 2. **Tests de integración**: Coloca el archivo en `tests/integration/`
95
+ 3. **Nombra el archivo**: `*.test.js` para que Mocha lo detecte automáticamente
96
+ 4. **Usa la estructura estándar**:
97
+
98
+ ```javascript
99
+ const { expect } = require('chai');
100
+
101
+ describe('NombreDelModulo', () => {
102
+ describe('nombreDelMetodo', () => {
103
+ it('debería hacer algo específico', () => {
104
+ // Arrange
105
+ const input = 'valor';
106
+
107
+ // Act
108
+ const result = someFunction(input);
109
+
110
+ // Assert
111
+ expect(result).to.equal('esperado');
112
+ });
113
+ });
114
+ });
115
+ ```
116
+
117
+ ## Frameworks Utilizados
118
+
119
+ - **Mocha**: Framework de testing
120
+ - **Chai**: Librería de assertions (expect)
121
+
122
+ ## Notas
123
+
124
+ - Los tests no requieren conexión a un nodo Neurai real
125
+ - Todos los tests usan mocks para simular llamadas RPC
126
+ - Los tests verifican tanto casos exitosos como errores esperados
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Integration Tests - Asset Lifecycle
3
+ * Tests the complete workflow of asset operations
4
+ */
5
+
6
+ const { expect } = require('chai');
7
+ const NeuraiAssets = require('../../src/NeuraiAssets');
8
+ const AssetNameValidator = require('../../src/validators/assetNameValidator');
9
+ const AssetNameParser = require('../../src/utils/assetNameParser');
10
+ const AmountConverter = require('../../src/utils/amountConverter');
11
+ const { createMockRPC, createMockUTXO, createMockAssetData } = require('../mocks/rpcMock');
12
+
13
+ describe('Integration: Asset Lifecycle', () => {
14
+ describe('Validator and Parser Integration', () => {
15
+ it('should validate and parse ROOT assets correctly', () => {
16
+ const assetName = 'MYTOKEN';
17
+
18
+ expect(AssetNameValidator.validateRoot(assetName)).to.be.true;
19
+
20
+ const parsed = AssetNameParser.parse(assetName);
21
+ expect(parsed.type).to.equal(0);
22
+ expect(parsed.name).to.equal(assetName);
23
+ expect(parsed.isOwner).to.be.false;
24
+ });
25
+
26
+ it('should validate and parse UNIQUE assets correctly', () => {
27
+ const assetName = 'MYTOKEN#NFT1';
28
+
29
+ expect(AssetNameValidator.validateUnique(assetName)).to.be.true;
30
+
31
+ const parsed = AssetNameParser.parse(assetName);
32
+ expect(parsed.type).to.equal(2);
33
+ expect(parsed.parent).to.equal('MYTOKEN');
34
+ expect(parsed.tag).to.equal('NFT1');
35
+ });
36
+
37
+ it('should validate and parse QUALIFIER assets correctly', () => {
38
+ const assetName = '#KYC_VERIFIED';
39
+
40
+ expect(AssetNameValidator.validateQualifier(assetName)).to.be.true;
41
+
42
+ const parsed = AssetNameParser.parse(assetName);
43
+ expect(parsed.type).to.equal(4);
44
+ expect(parsed.isQualifier).to.be.true;
45
+ expect(parsed.prefix).to.equal('#');
46
+ });
47
+
48
+ it('should validate and parse RESTRICTED assets correctly', () => {
49
+ const assetName = '$SECURITY_TOKEN';
50
+
51
+ expect(AssetNameValidator.validateRestricted(assetName)).to.be.true;
52
+
53
+ const parsed = AssetNameParser.parse(assetName);
54
+ expect(parsed.type).to.equal(6);
55
+ expect(parsed.isRestricted).to.be.true;
56
+ expect(parsed.prefix).to.equal('$');
57
+ });
58
+ });
59
+
60
+ describe('Amount Conversion Integration', () => {
61
+ it('should convert amounts correctly for different units', () => {
62
+ const testCases = [
63
+ { amount: 100, units: 0, expected: 100 },
64
+ { amount: 1.5, units: 1, expected: 15 },
65
+ { amount: 1.5, units: 2, expected: 150 },
66
+ { amount: 1.00000001, units: 8, expected: 100000001 }
67
+ ];
68
+
69
+ testCases.forEach(({ amount, units, expected }) => {
70
+ const satoshis = AmountConverter.toSatoshis(amount, units);
71
+ expect(satoshis).to.equal(expected);
72
+
73
+ const back = AmountConverter.fromSatoshis(satoshis, units);
74
+ expect(back).to.equal(amount);
75
+ });
76
+ });
77
+
78
+ it('should format and parse amounts correctly', () => {
79
+ const amount = 1.5;
80
+ const units = 2;
81
+
82
+ const formatted = AmountConverter.format(amount, units);
83
+ expect(formatted).to.equal('1.50');
84
+
85
+ const parsed = AmountConverter.parse(formatted);
86
+ expect(parsed).to.equal(amount);
87
+ });
88
+ });
89
+
90
+ describe('NeuraiAssets Integration', () => {
91
+ let assets;
92
+ let mockRPC;
93
+
94
+ beforeEach(() => {
95
+ mockRPC = createMockRPC({
96
+ 'listunspent': [
97
+ createMockUTXO('N123...', 100),
98
+ createMockUTXO('N123...', 50, 'MYTOKEN')
99
+ ],
100
+ 'getassetdata': createMockAssetData('MYTOKEN', {
101
+ amount: 1000000,
102
+ units: 2,
103
+ reissuable: true
104
+ })
105
+ });
106
+
107
+ assets = new NeuraiAssets(mockRPC, {
108
+ network: 'xna',
109
+ addresses: ['N123...'],
110
+ changeAddress: 'N123...',
111
+ toAddress: 'N456...'
112
+ });
113
+ });
114
+
115
+ it('should validate asset name before operations', async () => {
116
+ expect(() => assets.getAssetType('invalid-name'))
117
+ .to.not.throw();
118
+
119
+ const type = assets.getAssetType('MYTOKEN');
120
+ expect(type).to.equal('ROOT');
121
+ });
122
+
123
+ it('should query asset data successfully', async () => {
124
+ const data = await assets.getAssetData('MYTOKEN');
125
+ expect(data.name).to.equal('MYTOKEN');
126
+ expect(data.amount).to.equal(1000000);
127
+ expect(data.units).to.equal(2);
128
+ });
129
+
130
+ it('should detect different asset types', () => {
131
+ const testCases = [
132
+ { name: 'MYTOKEN', expected: 'ROOT' },
133
+ { name: 'MYTOKEN/SUB', expected: 'SUB' },
134
+ { name: 'MYTOKEN#NFT', expected: 'UNIQUE' },
135
+ { name: '#KYC', expected: 'QUALIFIER' },
136
+ { name: '$SECURITY', expected: 'RESTRICTED' },
137
+ { name: 'MYTOKEN!', expected: 'OWNER' }
138
+ ];
139
+
140
+ testCases.forEach(({ name, expected }) => {
141
+ const type = assets.getAssetType(name);
142
+ expect(type).to.equal(expected);
143
+ });
144
+ });
145
+ });
146
+
147
+ describe('Complex Workflows', () => {
148
+ it('should handle owner token derivation', () => {
149
+ const assetName = 'MYTOKEN';
150
+ const ownerToken = AssetNameParser.getOwnerTokenName(assetName);
151
+
152
+ expect(ownerToken).to.equal('MYTOKEN!');
153
+ expect(AssetNameParser.isOwnerToken(ownerToken)).to.be.true;
154
+
155
+ const baseAsset = AssetNameParser.getBaseAssetName(ownerToken);
156
+ expect(baseAsset).to.equal(assetName);
157
+ });
158
+
159
+ it('should handle sub-asset creation workflow', () => {
160
+ const rootName = 'MYTOKEN';
161
+ const subName = 'ALPHA';
162
+
163
+ expect(AssetNameValidator.validateRoot(rootName)).to.be.true;
164
+
165
+ const fullSubName = AssetNameParser.buildSubName(rootName, subName);
166
+ expect(fullSubName).to.equal('MYTOKEN/ALPHA');
167
+
168
+ expect(AssetNameValidator.validateSub(fullSubName)).to.be.true;
169
+
170
+ const parsed = AssetNameParser.parse(fullSubName);
171
+ expect(parsed.parent).to.equal(rootName);
172
+ expect(parsed.subName).to.equal(subName);
173
+ });
174
+
175
+ it('should handle unique asset creation workflow', () => {
176
+ const rootName = 'MYTOKEN';
177
+ const tag = 'NFT001';
178
+
179
+ expect(AssetNameValidator.validateRoot(rootName)).to.be.true;
180
+
181
+ const uniqueName = AssetNameParser.buildUniqueName(rootName, tag);
182
+ expect(uniqueName).to.equal('MYTOKEN#NFT001');
183
+
184
+ expect(AssetNameValidator.validateUnique(uniqueName)).to.be.true;
185
+
186
+ const parsed = AssetNameParser.parse(uniqueName);
187
+ expect(parsed.parent).to.equal(rootName);
188
+ expect(parsed.tag).to.equal(tag);
189
+ expect(parsed.type).to.equal(2);
190
+ });
191
+
192
+ it('should handle restricted asset workflow', () => {
193
+ const assetName = '$SECURITY';
194
+
195
+ expect(AssetNameValidator.validateRestricted(assetName)).to.be.true;
196
+ expect(AssetNameParser.isRestricted(assetName)).to.be.true;
197
+
198
+ const parsed = AssetNameParser.parse(assetName);
199
+ expect(parsed.type).to.equal(6);
200
+ expect(parsed.prefix).to.equal('$');
201
+ });
202
+
203
+ it('should handle qualifier workflow', () => {
204
+ const qualifierName = '#KYC_VERIFIED';
205
+
206
+ expect(AssetNameValidator.validateQualifier(qualifierName)).to.be.true;
207
+ expect(AssetNameParser.isQualifier(qualifierName)).to.be.true;
208
+
209
+ const parsed = AssetNameParser.parse(qualifierName);
210
+ expect(parsed.type).to.equal(4);
211
+ expect(parsed.isQualifier).to.be.true;
212
+ });
213
+ });
214
+
215
+ describe('Error Handling Integration', () => {
216
+ it('should propagate validation errors correctly', () => {
217
+ expect(() => AssetNameValidator.validateRoot('ab'))
218
+ .to.throw();
219
+
220
+ expect(() => AssetNameValidator.validateSub('INVALID'))
221
+ .to.throw();
222
+
223
+ expect(() => AssetNameValidator.validateUnique('INVALID'))
224
+ .to.throw();
225
+
226
+ expect(() => AssetNameValidator.validateQualifier('INVALID'))
227
+ .to.throw();
228
+
229
+ expect(() => AssetNameValidator.validateRestricted('INVALID'))
230
+ .to.throw();
231
+ });
232
+
233
+ it('should handle amount conversion errors', () => {
234
+ expect(() => AmountConverter.toSatoshis('invalid', 0))
235
+ .to.throw();
236
+
237
+ expect(() => AmountConverter.toSatoshis(100, -1))
238
+ .to.throw();
239
+
240
+ expect(() => AmountConverter.fromSatoshis('invalid', 0))
241
+ .to.throw();
242
+ });
243
+ });
244
+ });
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Mock RPC Function for Testing
3
+ * Simulates Neurai RPC calls without requiring a live node
4
+ */
5
+
6
+ class RPCMock {
7
+ constructor() {
8
+ this.mockResponses = new Map();
9
+ this.callHistory = [];
10
+ }
11
+
12
+ /**
13
+ * Set mock response for a specific RPC method
14
+ * @param {string} method - RPC method name
15
+ * @param {*} response - Response to return
16
+ */
17
+ setMockResponse(method, response) {
18
+ this.mockResponses.set(method, response);
19
+ }
20
+
21
+ /**
22
+ * Mock RPC function
23
+ * @param {string} method - RPC method name
24
+ * @param {Array} params - RPC parameters
25
+ * @returns {Promise<*>} Mock response
26
+ */
27
+ async call(method, ...params) {
28
+ this.callHistory.push({ method, params });
29
+
30
+ if (this.mockResponses.has(method)) {
31
+ const response = this.mockResponses.get(method);
32
+
33
+ // If response is a function, call it with params
34
+ if (typeof response === 'function') {
35
+ return response(...params);
36
+ }
37
+
38
+ return response;
39
+ }
40
+
41
+ // Default responses for common methods
42
+ return this.getDefaultResponse(method, params);
43
+ }
44
+
45
+ /**
46
+ * Get default response for common RPC methods
47
+ */
48
+ getDefaultResponse(method, params) {
49
+ const defaults = {
50
+ 'listunspent': [],
51
+ 'getassetdata': { name: params[0], amount: 1000000 },
52
+ 'listassets': [],
53
+ 'listmyassets': {},
54
+ 'listaddressesbyasset': [],
55
+ 'listassetbalancesbyaddress': [],
56
+ 'checkaddresstag': false,
57
+ 'checkaddressrestriction': true,
58
+ 'isaddressfrozen': false,
59
+ 'checkglobalrestriction': false,
60
+ 'getverifierstring': '',
61
+ 'isvalidverifierstring': true
62
+ };
63
+
64
+ return defaults[method] || null;
65
+ }
66
+
67
+ /**
68
+ * Clear call history
69
+ */
70
+ clearHistory() {
71
+ this.callHistory = [];
72
+ }
73
+
74
+ /**
75
+ * Get number of times a method was called
76
+ */
77
+ getCallCount(method) {
78
+ return this.callHistory.filter(call => call.method === method).length;
79
+ }
80
+
81
+ /**
82
+ * Get last call for a method
83
+ */
84
+ getLastCall(method) {
85
+ const calls = this.callHistory.filter(call => call.method === method);
86
+ return calls[calls.length - 1];
87
+ }
88
+
89
+ /**
90
+ * Reset all mocks
91
+ */
92
+ reset() {
93
+ this.mockResponses.clear();
94
+ this.callHistory = [];
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Create a simple mock RPC function
100
+ */
101
+ function createMockRPC(responses = {}) {
102
+ const mock = new RPCMock();
103
+
104
+ Object.entries(responses).forEach(([method, response]) => {
105
+ mock.setMockResponse(method, response);
106
+ });
107
+
108
+ return mock.call.bind(mock);
109
+ }
110
+
111
+ /**
112
+ * Create mock UTXO data
113
+ */
114
+ function createMockUTXO(address, amount = 100, assetName = null) {
115
+ const utxo = {
116
+ txid: '0000000000000000000000000000000000000000000000000000000000000001',
117
+ vout: 0,
118
+ address: address,
119
+ scriptPubKey: '76a914...',
120
+ amount: assetName ? undefined : amount,
121
+ confirmations: 10,
122
+ spendable: true,
123
+ solvable: true,
124
+ safe: true
125
+ };
126
+
127
+ if (assetName) {
128
+ utxo.assetName = assetName;
129
+ utxo.assetAmount = amount;
130
+ }
131
+
132
+ return utxo;
133
+ }
134
+
135
+ /**
136
+ * Create mock asset data
137
+ */
138
+ function createMockAssetData(assetName, options = {}) {
139
+ return {
140
+ name: assetName,
141
+ amount: options.amount || 1000000,
142
+ units: options.units || 0,
143
+ reissuable: options.reissuable !== undefined ? options.reissuable : true,
144
+ has_ipfs: options.hasIpfs || false,
145
+ ipfs_hash: options.ipfsHash || '',
146
+ block_height: 1000,
147
+ blockhash: '0000000000000000000000000000000000000000000000000000000000000001'
148
+ };
149
+ }
150
+
151
+ module.exports = {
152
+ RPCMock,
153
+ createMockRPC,
154
+ createMockUTXO,
155
+ createMockAssetData
156
+ };