@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,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for NeuraiAssets main class
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { expect } = require('chai');
|
|
6
|
+
const NeuraiAssets = require('../../src/NeuraiAssets');
|
|
7
|
+
const { createMockRPC } = require('../mocks/rpcMock');
|
|
8
|
+
|
|
9
|
+
describe('NeuraiAssets', () => {
|
|
10
|
+
let assets;
|
|
11
|
+
let mockRPC;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockRPC = createMockRPC();
|
|
15
|
+
assets = new NeuraiAssets(mockRPC, {
|
|
16
|
+
network: 'xna',
|
|
17
|
+
addresses: ['N123...'],
|
|
18
|
+
changeAddress: 'N123...',
|
|
19
|
+
toAddress: 'N456...'
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('constructor', () => {
|
|
24
|
+
it('should initialize with RPC and config', () => {
|
|
25
|
+
expect(assets.rpc).to.be.a('function');
|
|
26
|
+
expect(assets.config.network).to.equal('xna');
|
|
27
|
+
expect(assets.config.addresses).to.deep.equal(['N123...']);
|
|
28
|
+
expect(assets.config.changeAddress).to.equal('N123...');
|
|
29
|
+
expect(assets.config.toAddress).to.equal('N456...');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should use default config values when not provided', () => {
|
|
33
|
+
const defaultAssets = new NeuraiAssets(mockRPC);
|
|
34
|
+
expect(defaultAssets.config.network).to.equal('xna');
|
|
35
|
+
expect(defaultAssets.config.addresses).to.deep.equal([]);
|
|
36
|
+
expect(defaultAssets.config.changeAddress).to.be.null;
|
|
37
|
+
expect(defaultAssets.config.toAddress).to.be.null;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should throw error if RPC is not provided', () => {
|
|
41
|
+
expect(() => new NeuraiAssets())
|
|
42
|
+
.to.throw('RPC function is required');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw error if RPC is not a function', () => {
|
|
46
|
+
expect(() => new NeuraiAssets('not a function'))
|
|
47
|
+
.to.throw('RPC function is required');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should initialize queries instance', () => {
|
|
51
|
+
expect(assets.queries).to.exist;
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('updateConfig', () => {
|
|
56
|
+
it('should update configuration', () => {
|
|
57
|
+
assets.updateConfig({
|
|
58
|
+
network: 'xna-test',
|
|
59
|
+
changeAddress: 'N789...'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(assets.config.network).to.equal('xna-test');
|
|
63
|
+
expect(assets.config.changeAddress).to.equal('N789...');
|
|
64
|
+
expect(assets.config.addresses).to.deep.equal(['N123...']);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('_buildParams', () => {
|
|
69
|
+
it('should merge params with config', () => {
|
|
70
|
+
const params = assets._buildParams({
|
|
71
|
+
assetName: 'MYTOKEN',
|
|
72
|
+
quantity: 1000
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(params.assetName).to.equal('MYTOKEN');
|
|
76
|
+
expect(params.quantity).to.equal(1000);
|
|
77
|
+
expect(params.network).to.equal('xna');
|
|
78
|
+
expect(params.addresses).to.deep.equal(['N123...']);
|
|
79
|
+
expect(params.changeAddress).to.equal('N123...');
|
|
80
|
+
expect(params.toAddress).to.equal('N456...');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should allow params to override config values', () => {
|
|
84
|
+
const params = assets._buildParams({
|
|
85
|
+
assetName: 'MYTOKEN',
|
|
86
|
+
changeAddress: 'N999...'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(params.changeAddress).to.equal('N999...');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('Asset Type Detection', () => {
|
|
94
|
+
it('should detect ROOT asset type', () => {
|
|
95
|
+
const type = assets.getAssetType('MYTOKEN');
|
|
96
|
+
expect(type).to.equal('ROOT');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should detect SUB asset type', () => {
|
|
100
|
+
const type = assets.getAssetType('MYTOKEN/SUB');
|
|
101
|
+
expect(type).to.equal('SUB');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should detect UNIQUE asset type', () => {
|
|
105
|
+
const type = assets.getAssetType('MYTOKEN#NFT1');
|
|
106
|
+
expect(type).to.equal('UNIQUE');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should detect QUALIFIER asset type', () => {
|
|
110
|
+
const type = assets.getAssetType('#KYC');
|
|
111
|
+
expect(type).to.equal('QUALIFIER');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should detect RESTRICTED asset type', () => {
|
|
115
|
+
const type = assets.getAssetType('$SECURITY');
|
|
116
|
+
expect(type).to.equal('RESTRICTED');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should detect OWNER token type', () => {
|
|
120
|
+
const type = assets.getAssetType('MYTOKEN!');
|
|
121
|
+
expect(type).to.equal('OWNER');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('Query Methods', () => {
|
|
126
|
+
it('should call getAssetData', async () => {
|
|
127
|
+
const mockData = { name: 'MYTOKEN', amount: 1000000 };
|
|
128
|
+
const rpcMock = createMockRPC({ 'getassetdata': mockData });
|
|
129
|
+
const testAssets = new NeuraiAssets(rpcMock);
|
|
130
|
+
|
|
131
|
+
const result = await testAssets.getAssetData('MYTOKEN');
|
|
132
|
+
expect(result).to.deep.equal(mockData);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should call listAssets', async () => {
|
|
136
|
+
const mockList = ['TOKEN1', 'TOKEN2', 'TOKEN3'];
|
|
137
|
+
const rpcMock = createMockRPC({ 'listassets': mockList });
|
|
138
|
+
const testAssets = new NeuraiAssets(rpcMock);
|
|
139
|
+
|
|
140
|
+
const result = await testAssets.listAssets('*', false, 100, 0);
|
|
141
|
+
expect(result).to.deep.equal(mockList);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should call listMyAssets', async () => {
|
|
145
|
+
const mockAssets = { 'MYTOKEN': 1000, 'MYTOKEN2': 500 };
|
|
146
|
+
const rpcMock = createMockRPC({ 'listmyassets': mockAssets });
|
|
147
|
+
const testAssets = new NeuraiAssets(rpcMock);
|
|
148
|
+
|
|
149
|
+
const result = await testAssets.listMyAssets();
|
|
150
|
+
expect(result).to.deep.equal(mockAssets);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should call checkAddressTag', async () => {
|
|
154
|
+
const rpcMock = createMockRPC({ 'checkaddresstag': true });
|
|
155
|
+
const testAssets = new NeuraiAssets(rpcMock);
|
|
156
|
+
|
|
157
|
+
const result = await testAssets.checkAddressTag('N123...', '#KYC');
|
|
158
|
+
expect(result).to.be.true;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should call assetExists', async () => {
|
|
162
|
+
const rpcMock = createMockRPC({ 'getassetdata': { name: 'MYTOKEN' } });
|
|
163
|
+
const testAssets = new NeuraiAssets(rpcMock);
|
|
164
|
+
|
|
165
|
+
const result = await testAssets.assetExists('MYTOKEN');
|
|
166
|
+
expect(result).to.exist;
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('Method Availability', () => {
|
|
171
|
+
it('should have all ROOT asset methods', () => {
|
|
172
|
+
expect(assets.createRootAsset).to.be.a('function');
|
|
173
|
+
expect(assets.createSubAsset).to.be.a('function');
|
|
174
|
+
expect(assets.reissueAsset).to.be.a('function');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should have all UNIQUE asset methods', () => {
|
|
178
|
+
expect(assets.createUniqueAssets).to.be.a('function');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should have all QUALIFIER methods', () => {
|
|
182
|
+
expect(assets.createQualifier).to.be.a('function');
|
|
183
|
+
expect(assets.tagAddresses).to.be.a('function');
|
|
184
|
+
expect(assets.untagAddresses).to.be.a('function');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should have all RESTRICTED asset methods', () => {
|
|
188
|
+
expect(assets.createRestrictedAsset).to.be.a('function');
|
|
189
|
+
expect(assets.reissueRestrictedAsset).to.be.a('function');
|
|
190
|
+
expect(assets.freezeAddresses).to.be.a('function');
|
|
191
|
+
expect(assets.unfreezeAddresses).to.be.a('function');
|
|
192
|
+
expect(assets.freezeAssetGlobally).to.be.a('function');
|
|
193
|
+
expect(assets.unfreezeAssetGlobally).to.be.a('function');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should have all query methods', () => {
|
|
197
|
+
expect(assets.getAssetData).to.be.a('function');
|
|
198
|
+
expect(assets.listAssets).to.be.a('function');
|
|
199
|
+
expect(assets.listMyAssets).to.be.a('function');
|
|
200
|
+
expect(assets.listAddressesByAsset).to.be.a('function');
|
|
201
|
+
expect(assets.listAssetBalancesByAddress).to.be.a('function');
|
|
202
|
+
expect(assets.checkAddressTag).to.be.a('function');
|
|
203
|
+
expect(assets.listTagsForAddress).to.be.a('function');
|
|
204
|
+
expect(assets.listAddressesForTag).to.be.a('function');
|
|
205
|
+
expect(assets.checkAddressRestriction).to.be.a('function');
|
|
206
|
+
expect(assets.isAddressFrozen).to.be.a('function');
|
|
207
|
+
expect(assets.checkGlobalRestriction).to.be.a('function');
|
|
208
|
+
expect(assets.getVerifierString).to.be.a('function');
|
|
209
|
+
expect(assets.isValidVerifierString).to.be.a('function');
|
|
210
|
+
expect(assets.getSnapshotRequest).to.be.a('function');
|
|
211
|
+
expect(assets.cancelSnapshotRequest).to.be.a('function');
|
|
212
|
+
expect(assets.assetExists).to.be.a('function');
|
|
213
|
+
expect(assets.getAssetType).to.be.a('function');
|
|
214
|
+
expect(assets.getAssetCount).to.be.a('function');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AmountConverter
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { expect } = require('chai');
|
|
6
|
+
const AmountConverter = require('../../../src/utils/amountConverter');
|
|
7
|
+
|
|
8
|
+
describe('AmountConverter', () => {
|
|
9
|
+
describe('toSatoshis', () => {
|
|
10
|
+
it('should convert whole numbers with 0 units', () => {
|
|
11
|
+
expect(AmountConverter.toSatoshis(100, 0)).to.equal(100);
|
|
12
|
+
expect(AmountConverter.toSatoshis(1000, 0)).to.equal(1000);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should convert decimal amounts with various units', () => {
|
|
16
|
+
expect(AmountConverter.toSatoshis(1.5, 1)).to.equal(15);
|
|
17
|
+
expect(AmountConverter.toSatoshis(1.5, 2)).to.equal(150);
|
|
18
|
+
expect(AmountConverter.toSatoshis(1.00000001, 8)).to.equal(100000001);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should handle very small amounts', () => {
|
|
22
|
+
expect(AmountConverter.toSatoshis(0.00000001, 8)).to.equal(1);
|
|
23
|
+
expect(AmountConverter.toSatoshis(0.0001, 4)).to.equal(1);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle large amounts', () => {
|
|
27
|
+
expect(AmountConverter.toSatoshis(1000000, 0)).to.equal(1000000);
|
|
28
|
+
expect(AmountConverter.toSatoshis(1000000, 8)).to.equal(100000000000000);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should round to avoid floating point issues', () => {
|
|
32
|
+
expect(AmountConverter.toSatoshis(0.1 + 0.2, 1)).to.equal(3);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should throw error for invalid amounts', () => {
|
|
36
|
+
expect(() => AmountConverter.toSatoshis('100', 0))
|
|
37
|
+
.to.throw('Amount must be a valid number');
|
|
38
|
+
|
|
39
|
+
expect(() => AmountConverter.toSatoshis(NaN, 0))
|
|
40
|
+
.to.throw('Amount must be a valid number');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should throw error for invalid units', () => {
|
|
44
|
+
expect(() => AmountConverter.toSatoshis(100, -1))
|
|
45
|
+
.to.throw('Units must be a number between 0 and 8');
|
|
46
|
+
|
|
47
|
+
expect(() => AmountConverter.toSatoshis(100, 9))
|
|
48
|
+
.to.throw('Units must be a number between 0 and 8');
|
|
49
|
+
|
|
50
|
+
expect(() => AmountConverter.toSatoshis(100, 'two'))
|
|
51
|
+
.to.throw('Units must be a number between 0 and 8');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('fromSatoshis', () => {
|
|
56
|
+
it('should convert satoshis to whole numbers with 0 units', () => {
|
|
57
|
+
expect(AmountConverter.fromSatoshis(100, 0)).to.equal(100);
|
|
58
|
+
expect(AmountConverter.fromSatoshis(1000, 0)).to.equal(1000);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should convert satoshis to decimal amounts with various units', () => {
|
|
62
|
+
expect(AmountConverter.fromSatoshis(15, 1)).to.equal(1.5);
|
|
63
|
+
expect(AmountConverter.fromSatoshis(150, 2)).to.equal(1.5);
|
|
64
|
+
expect(AmountConverter.fromSatoshis(100000001, 8)).to.equal(1.00000001);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle very small satoshi amounts', () => {
|
|
68
|
+
expect(AmountConverter.fromSatoshis(1, 8)).to.equal(0.00000001);
|
|
69
|
+
expect(AmountConverter.fromSatoshis(1, 4)).to.equal(0.0001);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should throw error for invalid satoshis', () => {
|
|
73
|
+
expect(() => AmountConverter.fromSatoshis('100', 0))
|
|
74
|
+
.to.throw('Satoshis must be a valid number');
|
|
75
|
+
|
|
76
|
+
expect(() => AmountConverter.fromSatoshis(NaN, 0))
|
|
77
|
+
.to.throw('Satoshis must be a valid number');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should throw error for invalid units', () => {
|
|
81
|
+
expect(() => AmountConverter.fromSatoshis(100, -1))
|
|
82
|
+
.to.throw('Units must be a number between 0 and 8');
|
|
83
|
+
|
|
84
|
+
expect(() => AmountConverter.fromSatoshis(100, 9))
|
|
85
|
+
.to.throw('Units must be a number between 0 and 8');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('format', () => {
|
|
90
|
+
it('should format amounts with 0 units as integers', () => {
|
|
91
|
+
expect(AmountConverter.format(100, 0)).to.equal('100');
|
|
92
|
+
expect(AmountConverter.format(1000, 0)).to.equal('1000');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should format amounts with decimal places', () => {
|
|
96
|
+
expect(AmountConverter.format(1.5, 1)).to.equal('1.5');
|
|
97
|
+
expect(AmountConverter.format(1.5, 2)).to.equal('1.50');
|
|
98
|
+
expect(AmountConverter.format(1.00000001, 8)).to.equal('1.00000001');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should pad with zeros to match units', () => {
|
|
102
|
+
expect(AmountConverter.format(1, 2)).to.equal('1.00');
|
|
103
|
+
expect(AmountConverter.format(1, 8)).to.equal('1.00000000');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('parse', () => {
|
|
108
|
+
it('should parse formatted amounts', () => {
|
|
109
|
+
expect(AmountConverter.parse('100')).to.equal(100);
|
|
110
|
+
expect(AmountConverter.parse('1.5')).to.equal(1.5);
|
|
111
|
+
expect(AmountConverter.parse('0.00000001')).to.equal(0.00000001);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should throw error for invalid formats', () => {
|
|
115
|
+
expect(() => AmountConverter.parse('abc'))
|
|
116
|
+
.to.throw('Invalid number format');
|
|
117
|
+
|
|
118
|
+
expect(() => AmountConverter.parse(''))
|
|
119
|
+
.to.throw('Invalid number format');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('getDecimalPlaces', () => {
|
|
124
|
+
it('should count decimal places correctly', () => {
|
|
125
|
+
expect(AmountConverter.getDecimalPlaces(100)).to.equal(0);
|
|
126
|
+
expect(AmountConverter.getDecimalPlaces(1.5)).to.equal(1);
|
|
127
|
+
expect(AmountConverter.getDecimalPlaces(1.25)).to.equal(2);
|
|
128
|
+
expect(AmountConverter.getDecimalPlaces(0.00000001)).to.equal(8);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should handle scientific notation', () => {
|
|
132
|
+
expect(AmountConverter.getDecimalPlaces(1e-8)).to.equal(8);
|
|
133
|
+
expect(AmountConverter.getDecimalPlaces(1.5e2)).to.equal(0);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('adjustToUnits', () => {
|
|
138
|
+
it('should not modify amounts with fewer decimals than units', () => {
|
|
139
|
+
expect(AmountConverter.adjustToUnits(1.5, 2)).to.equal(1.5);
|
|
140
|
+
expect(AmountConverter.adjustToUnits(1, 8)).to.equal(1);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should round amounts with more decimals than units', () => {
|
|
144
|
+
expect(AmountConverter.adjustToUnits(1.123, 2)).to.equal(1.12);
|
|
145
|
+
expect(AmountConverter.adjustToUnits(1.567, 2)).to.equal(1.57);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should handle edge cases', () => {
|
|
149
|
+
expect(AmountConverter.adjustToUnits(1.999, 2)).to.equal(2);
|
|
150
|
+
expect(AmountConverter.adjustToUnits(0.001, 2)).to.equal(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('round-trip conversion', () => {
|
|
155
|
+
it('should convert back and forth without loss', () => {
|
|
156
|
+
const amount = 1.5;
|
|
157
|
+
const units = 2;
|
|
158
|
+
const satoshis = AmountConverter.toSatoshis(amount, units);
|
|
159
|
+
const back = AmountConverter.fromSatoshis(satoshis, units);
|
|
160
|
+
expect(back).to.equal(amount);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should handle maximum precision', () => {
|
|
164
|
+
const amount = 1.00000001;
|
|
165
|
+
const units = 8;
|
|
166
|
+
const satoshis = AmountConverter.toSatoshis(amount, units);
|
|
167
|
+
const back = AmountConverter.fromSatoshis(satoshis, units);
|
|
168
|
+
expect(back).to.equal(amount);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AssetNameParser
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { expect } = require('chai');
|
|
6
|
+
const AssetNameParser = require('../../../src/utils/assetNameParser');
|
|
7
|
+
const { AssetType } = require('../../../src/constants');
|
|
8
|
+
|
|
9
|
+
describe('AssetNameParser', () => {
|
|
10
|
+
describe('parse', () => {
|
|
11
|
+
it('should parse ROOT asset names', () => {
|
|
12
|
+
const result = AssetNameParser.parse('MYTOKEN');
|
|
13
|
+
expect(result.type).to.equal(AssetType.ROOT);
|
|
14
|
+
expect(result.name).to.equal('MYTOKEN');
|
|
15
|
+
expect(result.parent).to.be.null;
|
|
16
|
+
expect(result.isOwner).to.be.false;
|
|
17
|
+
expect(result.isRestricted).to.be.false;
|
|
18
|
+
expect(result.isQualifier).to.be.false;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should parse SUB asset names', () => {
|
|
22
|
+
const result = AssetNameParser.parse('MYTOKEN/SUB');
|
|
23
|
+
expect(result.type).to.equal(AssetType.SUB);
|
|
24
|
+
expect(result.parent).to.equal('MYTOKEN');
|
|
25
|
+
expect(result.subName).to.equal('SUB');
|
|
26
|
+
expect(result.name).to.equal('MYTOKEN/SUB');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should parse UNIQUE asset names', () => {
|
|
30
|
+
const result = AssetNameParser.parse('MYTOKEN#NFT1');
|
|
31
|
+
expect(result.type).to.equal(AssetType.UNIQUE);
|
|
32
|
+
expect(result.parent).to.equal('MYTOKEN');
|
|
33
|
+
expect(result.tag).to.equal('NFT1');
|
|
34
|
+
expect(result.name).to.equal('MYTOKEN#NFT1');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should parse QUALIFIER asset names', () => {
|
|
38
|
+
const result = AssetNameParser.parse('#KYC');
|
|
39
|
+
expect(result.type).to.equal(AssetType.QUALIFIER);
|
|
40
|
+
expect(result.prefix).to.equal('#');
|
|
41
|
+
expect(result.isQualifier).to.be.true;
|
|
42
|
+
expect(result.name).to.equal('#KYC');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should parse SUB_QUALIFIER asset names', () => {
|
|
46
|
+
const result = AssetNameParser.parse('#KYC/TIER1');
|
|
47
|
+
expect(result.type).to.equal(AssetType.SUB_QUALIFIER);
|
|
48
|
+
expect(result.parent).to.equal('#KYC');
|
|
49
|
+
expect(result.subName).to.equal('TIER1');
|
|
50
|
+
expect(result.prefix).to.equal('#');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should parse RESTRICTED asset names', () => {
|
|
54
|
+
const result = AssetNameParser.parse('$SECURITY');
|
|
55
|
+
expect(result.type).to.equal(AssetType.RESTRICTED);
|
|
56
|
+
expect(result.prefix).to.equal('$');
|
|
57
|
+
expect(result.isRestricted).to.be.true;
|
|
58
|
+
expect(result.name).to.equal('$SECURITY');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should parse OWNER token names', () => {
|
|
62
|
+
const result = AssetNameParser.parse('MYTOKEN!');
|
|
63
|
+
expect(result.type).to.equal(AssetType.OWNER);
|
|
64
|
+
expect(result.isOwner).to.be.true;
|
|
65
|
+
expect(result.baseName).to.equal('MYTOKEN');
|
|
66
|
+
expect(result.fullName).to.equal('MYTOKEN!');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should parse RESTRICTED OWNER token names', () => {
|
|
70
|
+
const result = AssetNameParser.parse('$SECURITY!');
|
|
71
|
+
expect(result.type).to.equal(AssetType.OWNER);
|
|
72
|
+
expect(result.isOwner).to.be.true;
|
|
73
|
+
expect(result.isRestricted).to.be.true;
|
|
74
|
+
expect(result.baseName).to.equal('$SECURITY');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('getType', () => {
|
|
79
|
+
it('should return correct type for various asset names', () => {
|
|
80
|
+
expect(AssetNameParser.getType('MYTOKEN')).to.equal(AssetType.ROOT);
|
|
81
|
+
expect(AssetNameParser.getType('MYTOKEN/SUB')).to.equal(AssetType.SUB);
|
|
82
|
+
expect(AssetNameParser.getType('MYTOKEN#NFT')).to.equal(AssetType.UNIQUE);
|
|
83
|
+
expect(AssetNameParser.getType('#KYC')).to.equal(AssetType.QUALIFIER);
|
|
84
|
+
expect(AssetNameParser.getType('#KYC/TIER1')).to.equal(AssetType.SUB_QUALIFIER);
|
|
85
|
+
expect(AssetNameParser.getType('$SECURITY')).to.equal(AssetType.RESTRICTED);
|
|
86
|
+
expect(AssetNameParser.getType('MYTOKEN!')).to.equal(AssetType.OWNER);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('getParent', () => {
|
|
91
|
+
it('should return parent for SUB assets', () => {
|
|
92
|
+
expect(AssetNameParser.getParent('MYTOKEN/SUB')).to.equal('MYTOKEN');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should return parent for UNIQUE assets', () => {
|
|
96
|
+
expect(AssetNameParser.getParent('MYTOKEN#NFT')).to.equal('MYTOKEN');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should return null for ROOT assets', () => {
|
|
100
|
+
expect(AssetNameParser.getParent('MYTOKEN')).to.be.null;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should return parent for SUB_QUALIFIER', () => {
|
|
104
|
+
expect(AssetNameParser.getParent('#KYC/TIER1')).to.equal('#KYC');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('isOwnerToken', () => {
|
|
109
|
+
it('should identify owner tokens', () => {
|
|
110
|
+
expect(AssetNameParser.isOwnerToken('MYTOKEN!')).to.be.true;
|
|
111
|
+
expect(AssetNameParser.isOwnerToken('$SECURITY!')).to.be.true;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should return false for non-owner tokens', () => {
|
|
115
|
+
expect(AssetNameParser.isOwnerToken('MYTOKEN')).to.be.false;
|
|
116
|
+
expect(AssetNameParser.isOwnerToken('MYTOKEN/SUB')).to.be.false;
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('getOwnerTokenName', () => {
|
|
121
|
+
it('should add ! to get owner token name', () => {
|
|
122
|
+
expect(AssetNameParser.getOwnerTokenName('MYTOKEN')).to.equal('MYTOKEN!');
|
|
123
|
+
expect(AssetNameParser.getOwnerTokenName('$SECURITY')).to.equal('$SECURITY!');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should return as-is if already an owner token', () => {
|
|
127
|
+
expect(AssetNameParser.getOwnerTokenName('MYTOKEN!')).to.equal('MYTOKEN!');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('getBaseAssetName', () => {
|
|
132
|
+
it('should remove ! to get base asset name', () => {
|
|
133
|
+
expect(AssetNameParser.getBaseAssetName('MYTOKEN!')).to.equal('MYTOKEN');
|
|
134
|
+
expect(AssetNameParser.getBaseAssetName('$SECURITY!')).to.equal('$SECURITY');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should return as-is if not an owner token', () => {
|
|
138
|
+
expect(AssetNameParser.getBaseAssetName('MYTOKEN')).to.equal('MYTOKEN');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('isRestricted', () => {
|
|
143
|
+
it('should identify restricted assets', () => {
|
|
144
|
+
expect(AssetNameParser.isRestricted('$SECURITY')).to.be.true;
|
|
145
|
+
expect(AssetNameParser.isRestricted('$TOKEN')).to.be.true;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should return false for non-restricted assets', () => {
|
|
149
|
+
expect(AssetNameParser.isRestricted('MYTOKEN')).to.be.false;
|
|
150
|
+
expect(AssetNameParser.isRestricted('#KYC')).to.be.false;
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('isQualifier', () => {
|
|
155
|
+
it('should identify qualifiers', () => {
|
|
156
|
+
expect(AssetNameParser.isQualifier('#KYC')).to.be.true;
|
|
157
|
+
expect(AssetNameParser.isQualifier('#KYC/TIER1')).to.be.true;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should return false for non-qualifiers', () => {
|
|
161
|
+
expect(AssetNameParser.isQualifier('MYTOKEN')).to.be.false;
|
|
162
|
+
expect(AssetNameParser.isQualifier('MYTOKEN#NFT')).to.be.false;
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('isUnique', () => {
|
|
167
|
+
it('should identify unique assets', () => {
|
|
168
|
+
expect(AssetNameParser.isUnique('MYTOKEN#NFT')).to.be.true;
|
|
169
|
+
expect(AssetNameParser.isUnique('TOKEN#123')).to.be.true;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should return false for non-unique assets', () => {
|
|
173
|
+
expect(AssetNameParser.isUnique('MYTOKEN')).to.be.false;
|
|
174
|
+
expect(AssetNameParser.isUnique('#KYC')).to.be.false;
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('isSub', () => {
|
|
179
|
+
it('should identify sub-assets', () => {
|
|
180
|
+
expect(AssetNameParser.isSub('MYTOKEN/SUB')).to.be.true;
|
|
181
|
+
expect(AssetNameParser.isSub('TOKEN/ALPHA')).to.be.true;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should return false for non-sub assets', () => {
|
|
185
|
+
expect(AssetNameParser.isSub('MYTOKEN')).to.be.false;
|
|
186
|
+
expect(AssetNameParser.isSub('#KYC/TIER1')).to.be.false;
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('buildUniqueName', () => {
|
|
191
|
+
it('should build unique asset names', () => {
|
|
192
|
+
expect(AssetNameParser.buildUniqueName('MYTOKEN', 'NFT1')).to.equal('MYTOKEN#NFT1');
|
|
193
|
+
expect(AssetNameParser.buildUniqueName('TOKEN', '123')).to.equal('TOKEN#123');
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('buildSubName', () => {
|
|
198
|
+
it('should build sub-asset names', () => {
|
|
199
|
+
expect(AssetNameParser.buildSubName('MYTOKEN', 'SUB')).to.equal('MYTOKEN/SUB');
|
|
200
|
+
expect(AssetNameParser.buildSubName('TOKEN', 'ALPHA')).to.equal('TOKEN/ALPHA');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|