@neuraiproject/neurai-assets 1.0.2 → 1.1.1
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 +107 -12
- package/examples/10-depin-post-quantum.js +83 -0
- package/examples/README.md +20 -2
- package/package.json +7 -4
- package/src/NeuraiAssets.js +36 -1
- package/src/builders/BaseAssetTransactionBuilder.js +3 -0
- package/src/builders/IssueDepinBuilder.js +144 -0
- package/src/builders/ReissueBuilder.js +2 -1
- package/src/builders/index.js +2 -0
- package/src/constants/burnAddresses.js +22 -4
- package/src/constants/index.js +2 -0
- package/src/constants/networks.js +44 -5
- package/src/index.js +1 -0
- package/src/managers/BurnManager.js +9 -0
- package/src/queries/AssetQueries.js +50 -1
- package/src/utils/assetNameParser.js +22 -1
- package/src/utils/networkDetector.js +21 -5
- package/src/validators/assetNameValidator.js +78 -2
- package/tests/integration/assetLifecycle.test.js +27 -0
- package/tests/mocks/rpcMock.js +3 -1
- package/tests/unit/NeuraiAssets.test.js +26 -0
- package/tests/unit/utils/assetNameParser.test.js +37 -0
- package/tests/unit/utils/networkDetector.test.js +89 -0
- package/tests/unit/validators/assetNameValidator.test.js +29 -0
package/README.md
CHANGED
|
@@ -5,11 +5,12 @@ Complete asset management library for Neurai blockchain. Supports creation, reis
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- ✅ **Non-custodial**: Library builds unsigned transactions, your wallet signs them
|
|
8
|
-
- ✅ **All asset types**: ROOT, SUB, UNIQUE (NFTs), QUALIFIER, RESTRICTED
|
|
8
|
+
- ✅ **All asset types**: ROOT, SUB, UNIQUE (NFTs), QUALIFIER, RESTRICTED, DEPIN
|
|
9
9
|
- ✅ **Complete operations**: Creation, reissuance, tagging, freezing
|
|
10
10
|
- ✅ **RPC queries**: Complete wrapper for all asset query methods
|
|
11
11
|
- ✅ **Client-side validation**: Prevents errors before creating transactions
|
|
12
12
|
- ✅ **Owner token protection**: Validation to prevent permanent loss
|
|
13
|
+
- ✅ **PQ-ready networks**: Supports `xna-pq` and `xna-pq-test` with `nq1...` / `tnq1...` addresses
|
|
13
14
|
|
|
14
15
|
## Supported Asset Types
|
|
15
16
|
|
|
@@ -21,6 +22,7 @@ Complete asset management library for Neurai blockchain. Supports creation, reis
|
|
|
21
22
|
| **QUALIFIER** | `#KYC` | 2000 XNA | Compliance tag |
|
|
22
23
|
| **SUB_QUALIFIER** | `#PARENT/#SUB` | 200 XNA | Sub-qualifier |
|
|
23
24
|
| **RESTRICTED** | `$SECURITY` | 3000 XNA | Security token with compliance |
|
|
25
|
+
| **DEPIN** | `&DEVICE` or `&DEVICE/ROUTER001` | 10 XNA | Soulbound asset with holder validity controls |
|
|
24
26
|
|
|
25
27
|
## Installation
|
|
26
28
|
|
|
@@ -54,6 +56,17 @@ const signedTx = await wallet.signTransaction(result.rawTx);
|
|
|
54
56
|
const txid = await wallet.broadcastTransaction(signedTx);
|
|
55
57
|
```
|
|
56
58
|
|
|
59
|
+
You can also initialize the library with PQ networks and addresses:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const assetsPQ = new NeuraiAssets(rpc, {
|
|
63
|
+
network: 'xna-pq', // or 'xna-pq-test'
|
|
64
|
+
addresses: ['nq1yourpqaddress...'],
|
|
65
|
+
changeAddress: 'nq1yourpqchange...',
|
|
66
|
+
toAddress: 'nq1recipientpqaddress...'
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
57
70
|
## Operation Examples
|
|
58
71
|
|
|
59
72
|
### Create ROOT Asset
|
|
@@ -93,6 +106,20 @@ const result = await assets.reissueAsset({
|
|
|
93
106
|
});
|
|
94
107
|
```
|
|
95
108
|
|
|
109
|
+
### Create DEPIN Asset
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const result = await assets.createDepinAsset({
|
|
113
|
+
assetName: '&DEVICE/ROUTER001',
|
|
114
|
+
quantity: 1,
|
|
115
|
+
reissuable: false,
|
|
116
|
+
hasIpfs: false
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
> **Note**: DEPIN assets always use `units = 0`. The library accepts both legacy
|
|
121
|
+
> and PQ addresses as recipients depending on the configured network.
|
|
122
|
+
|
|
96
123
|
### Create UNIQUE Assets (NFTs)
|
|
97
124
|
|
|
98
125
|
```javascript
|
|
@@ -319,6 +346,30 @@ const exists = await assets.assetExists('MYTOKEN');
|
|
|
319
346
|
console.log(exists); // true/false
|
|
320
347
|
```
|
|
321
348
|
|
|
349
|
+
### View DEPIN Holders
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const holders = await assets.listDepinHolders('&DEVICE/ROUTER001');
|
|
353
|
+
console.log(holders);
|
|
354
|
+
// [
|
|
355
|
+
// { address: 'nq1holder...', amount: 1, valid: 1 },
|
|
356
|
+
// { address: 'nq1holder2...', amount: 1, valid: 0 }
|
|
357
|
+
// ]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Check DEPIN Validity for an Address
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
const validity = await assets.checkDepinValidity('&DEVICE/ROUTER001', 'nq1holder...');
|
|
364
|
+
console.log(validity);
|
|
365
|
+
// {
|
|
366
|
+
// has_asset: true,
|
|
367
|
+
// amount: 1,
|
|
368
|
+
// valid: 1,
|
|
369
|
+
// blocked: false
|
|
370
|
+
// }
|
|
371
|
+
```
|
|
372
|
+
|
|
322
373
|
### Detect Asset Type
|
|
323
374
|
|
|
324
375
|
```javascript
|
|
@@ -327,7 +378,8 @@ const type2 = assets.getAssetType('PARENT/SUB'); // 'SUB'
|
|
|
327
378
|
const type3 = assets.getAssetType('TOKEN#NFT'); // 'UNIQUE'
|
|
328
379
|
const type4 = assets.getAssetType('#KYC'); // 'QUALIFIER'
|
|
329
380
|
const type5 = assets.getAssetType('$SECURITY'); // 'RESTRICTED'
|
|
330
|
-
const type6 = assets.getAssetType('
|
|
381
|
+
const type6 = assets.getAssetType('&DEVICE/ONE'); // 'DEPIN'
|
|
382
|
+
const type7 = assets.getAssetType('MYTOKEN!'); // 'OWNER'
|
|
331
383
|
```
|
|
332
384
|
|
|
333
385
|
## Transaction Result Structure
|
|
@@ -337,15 +389,14 @@ All creation/reissuance operations return an object with this structure:
|
|
|
337
389
|
```javascript
|
|
338
390
|
{
|
|
339
391
|
rawTx: 'hex string', // Unsigned transaction (to sign with wallet)
|
|
392
|
+
utxos: [...], // UTXOs selected for the operation
|
|
340
393
|
inputs: [...], // Transaction inputs
|
|
341
|
-
outputs:
|
|
394
|
+
outputs: [...], // Ordered outputs
|
|
342
395
|
fee: 0.001, // Fee in XNA
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
operationType: 'ISSUE_ROOT'
|
|
348
|
-
}
|
|
396
|
+
burnAmount: 1000, // Burned amount in XNA
|
|
397
|
+
assetName: 'MYTOKEN', // Operation-specific fields vary by builder
|
|
398
|
+
ownerTokenName: 'MYTOKEN!',
|
|
399
|
+
operationType: 'ISSUE_ROOT'
|
|
349
400
|
}
|
|
350
401
|
```
|
|
351
402
|
|
|
@@ -358,6 +409,7 @@ When you create an asset, an **owner token** is automatically generated (e.g., `
|
|
|
358
409
|
- Create SUB assets
|
|
359
410
|
- Manage tags (if qualifier)
|
|
360
411
|
- Freeze/unfreeze (if restricted)
|
|
412
|
+
- Manage DEPIN reissuance and controls (if depin)
|
|
361
413
|
|
|
362
414
|
⚠️ **If you lose the owner token, you lose these capabilities PERMANENTLY**
|
|
363
415
|
|
|
@@ -378,7 +430,9 @@ The library automatically validates that the owner token is returned in each ope
|
|
|
378
430
|
| Create QUALIFIER (root) | 2000 |
|
|
379
431
|
| Create QUALIFIER (sub) | 200 |
|
|
380
432
|
| Create RESTRICTED asset | 3000 |
|
|
433
|
+
| Create DEPIN asset | 10 |
|
|
381
434
|
| Reissue ROOT/SUB | 200 |
|
|
435
|
+
| Reissue DEPIN | 200 |
|
|
382
436
|
| Reissue RESTRICTED | 200 |
|
|
383
437
|
| Tag/Untag address | 0.1 (per address) |
|
|
384
438
|
| Freeze/Unfreeze address | 0 (network fee only) |
|
|
@@ -415,11 +469,37 @@ const assets = new NeuraiAssets(rpc, {
|
|
|
415
469
|
const assets = new NeuraiAssets(rpc, {
|
|
416
470
|
network: 'xna-test',
|
|
417
471
|
addresses: [...],
|
|
418
|
-
changeAddress: '
|
|
419
|
-
toAddress: '
|
|
472
|
+
changeAddress: 't...',
|
|
473
|
+
toAddress: 't...'
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// PQ Mainnet
|
|
477
|
+
const assetsPQ = new NeuraiAssets(rpc, {
|
|
478
|
+
network: 'xna-pq',
|
|
479
|
+
addresses: ['nq1...'],
|
|
480
|
+
changeAddress: 'nq1...',
|
|
481
|
+
toAddress: 'nq1...'
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// PQ Testnet
|
|
485
|
+
const assetsPQTest = new NeuraiAssets(rpc, {
|
|
486
|
+
network: 'xna-pq-test',
|
|
487
|
+
addresses: ['tnq1...'],
|
|
488
|
+
changeAddress: 'tnq1...',
|
|
489
|
+
toAddress: 'tnq1...'
|
|
420
490
|
});
|
|
421
491
|
```
|
|
422
492
|
|
|
493
|
+
The library accepts these network names:
|
|
494
|
+
|
|
495
|
+
- `xna`: legacy/mainnet address flow (`N...`)
|
|
496
|
+
- `xna-test`: legacy/testnet address flow (`t...`)
|
|
497
|
+
- `xna-pq`: PQ mainnet address flow (`nq1...`)
|
|
498
|
+
- `xna-pq-test`: PQ testnet address flow (`tnq1...`)
|
|
499
|
+
|
|
500
|
+
If you need to derive PQ addresses, use `neurai-key` and pass the resulting `nq1...`
|
|
501
|
+
or `tnq1...` addresses into this library.
|
|
502
|
+
|
|
423
503
|
## Update Configuration
|
|
424
504
|
|
|
425
505
|
```javascript
|
|
@@ -449,6 +529,17 @@ const builder = new builders.IssueRootBuilder(rpc, {
|
|
|
449
529
|
const result = await builder.build();
|
|
450
530
|
```
|
|
451
531
|
|
|
532
|
+
The builders module also includes:
|
|
533
|
+
|
|
534
|
+
- `IssueDepinBuilder`
|
|
535
|
+
- `IssueRootBuilder`
|
|
536
|
+
- `IssueSubBuilder`
|
|
537
|
+
- `IssueUniqueBuilder`
|
|
538
|
+
- `IssueQualifierBuilder`
|
|
539
|
+
- `IssueRestrictedBuilder`
|
|
540
|
+
- `ReissueBuilder`
|
|
541
|
+
- `ReissueRestrictedBuilder`
|
|
542
|
+
|
|
452
543
|
## Error Handling
|
|
453
544
|
|
|
454
545
|
The library throws specific errors:
|
|
@@ -524,4 +615,8 @@ const signedTx = await wallet.signTransaction(result.rawTx);
|
|
|
524
615
|
// Broadcast
|
|
525
616
|
const txid = await wallet.broadcastTransaction(signedTx);
|
|
526
617
|
console.log('Transaction ID:', txid);
|
|
527
|
-
```
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
For PQ wallets, derive addresses externally with `neurai-key` using `xna-pq` or
|
|
621
|
+
`xna-pq-test`, then initialize `NeuraiAssets` with those addresses and the matching
|
|
622
|
+
network name.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: DEPIN Assets with Post-Quantum Addresses
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates how to use DEPIN assets with PQ addresses
|
|
5
|
+
* generated by neurai-key using the xna-pq / xna-pq-test networks.
|
|
6
|
+
*
|
|
7
|
+
* PQ address examples:
|
|
8
|
+
* - Mainnet: nq1...
|
|
9
|
+
* - Testnet: tnq1...
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const NeuraiAssets = require('@neuraiproject/neurai-assets');
|
|
13
|
+
|
|
14
|
+
async function createDepinForPQAddress() {
|
|
15
|
+
const rpc = async (method, params) => {
|
|
16
|
+
console.log(`RPC Call: ${method}`, params);
|
|
17
|
+
// Replace with your actual RPC client
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const assets = new NeuraiAssets(rpc, {
|
|
21
|
+
network: 'xna-pq', // Or 'xna-pq-test'
|
|
22
|
+
addresses: ['nq1yourpqwalletaddress...'], // PQ addresses from neurai-key
|
|
23
|
+
changeAddress: 'nq1yourpqchangeaddress...',
|
|
24
|
+
toAddress: 'nq1recipientpqaddress...'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await assets.createDepinAsset({
|
|
29
|
+
assetName: '&DEVICE/ROUTER001',
|
|
30
|
+
quantity: 1,
|
|
31
|
+
reissuable: false
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log('DEPIN transaction created');
|
|
35
|
+
console.log('Raw transaction:', result.rawTx);
|
|
36
|
+
console.log('Burn:', result.burnAmount, 'XNA');
|
|
37
|
+
console.log('Owner token:', result.ownerTokenName);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error creating DEPIN asset for PQ address:', error.message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function queryDepinValidityForPQAddress() {
|
|
44
|
+
const rpc = async (method, params) => {
|
|
45
|
+
console.log(`RPC Call: ${method}`, params);
|
|
46
|
+
|
|
47
|
+
if (method === 'checkdepinvalidity') {
|
|
48
|
+
return {
|
|
49
|
+
has_asset: true,
|
|
50
|
+
amount: 1,
|
|
51
|
+
valid: 1,
|
|
52
|
+
blocked: false
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (method === 'listdepinholders') {
|
|
57
|
+
return [
|
|
58
|
+
{ address: 'nq1firstholder...', amount: 1, valid: 1 },
|
|
59
|
+
{ address: 'nq1secondholder...', amount: 1, valid: 0 }
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const assets = new NeuraiAssets(rpc, {
|
|
65
|
+
network: 'xna-pq',
|
|
66
|
+
addresses: ['nq1yourpqwalletaddress...'],
|
|
67
|
+
changeAddress: 'nq1yourpqchangeaddress...',
|
|
68
|
+
toAddress: 'nq1recipientpqaddress...'
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const validity = await assets.checkDepinValidity('&DEVICE/ROUTER001', 'nq1recipientpqaddress...');
|
|
73
|
+
console.log('DEPIN validity for PQ address:', validity);
|
|
74
|
+
|
|
75
|
+
const holders = await assets.listDepinHolders('&DEVICE/ROUTER001');
|
|
76
|
+
console.log('DEPIN holders:', holders);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Error querying DEPIN PQ data:', error.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
createDepinForPQAddress();
|
|
83
|
+
queryDepinValidityForPQAddress();
|
package/examples/README.md
CHANGED
|
@@ -163,6 +163,23 @@ Learn complete integration with `@neuraiproject/neurai-jswallet`.
|
|
|
163
163
|
|
|
164
164
|
---
|
|
165
165
|
|
|
166
|
+
### 10. DEPIN with Post-Quantum Addresses
|
|
167
|
+
**File:** [10-depin-post-quantum.js](10-depin-post-quantum.js)
|
|
168
|
+
|
|
169
|
+
Learn how to use DEPIN assets with PQ addresses generated by `neurai-key`.
|
|
170
|
+
|
|
171
|
+
- PQ mainnet network: `xna-pq`
|
|
172
|
+
- PQ testnet network: `xna-pq-test`
|
|
173
|
+
- PQ address formats: `nq1...` and `tnq1...`
|
|
174
|
+
|
|
175
|
+
**Topics covered:**
|
|
176
|
+
- Initializing `NeuraiAssets` with PQ networks
|
|
177
|
+
- Creating DEPIN assets sent to PQ addresses
|
|
178
|
+
- Querying DEPIN validity for PQ holders
|
|
179
|
+
- Listing DEPIN holders with PQ addresses
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
166
183
|
## Running the Examples
|
|
167
184
|
|
|
168
185
|
### Prerequisites
|
|
@@ -184,9 +201,9 @@ node 01-create-root-asset.js
|
|
|
184
201
|
|
|
185
202
|
1. **Mock RPC**: All examples use mock RPC functions. Replace with your actual RPC client in production.
|
|
186
203
|
|
|
187
|
-
2. **Network**: Examples use mainnet (`'xna'`). Change to `'xna-test'` for testnet.
|
|
204
|
+
2. **Network**: Examples use mainnet (`'xna'`). Change to `'xna-test'` for testnet. PQ networks are `xna-pq` and `xna-pq-test`.
|
|
188
205
|
|
|
189
|
-
3. **Addresses**: Replace placeholder addresses with your actual wallet addresses.
|
|
206
|
+
3. **Addresses**: Replace placeholder addresses with your actual wallet addresses. PQ examples use `nq1...` / `tnq1...` addresses.
|
|
190
207
|
|
|
191
208
|
4. **Testing**: Test on testnet first before using mainnet.
|
|
192
209
|
|
|
@@ -232,6 +249,7 @@ const txid = await wallet.broadcastTransaction(signedTx);
|
|
|
232
249
|
| QUALIFIER | `#NAME` | 2000 XNA | `#KYC_VERIFIED` |
|
|
233
250
|
| SUB_QUALIFIER | `#ROOT/#SUB` | 200 XNA | `#KYC/#LEVEL2` |
|
|
234
251
|
| RESTRICTED | `$NAME` | 3000 XNA | `$SECURITY` |
|
|
252
|
+
| DEPIN | `&NAME` or `&ROOT/SUB` | 10 XNA | `&DEVICE/ROUTER001` |
|
|
235
253
|
| OWNER | `NAME!` | N/A | `MYTOKEN!` |
|
|
236
254
|
|
|
237
255
|
## Common Patterns
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neuraiproject/neurai-assets",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Non-custodial Neurai asset management library for JavaScript",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -29,13 +29,16 @@
|
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
|
-
"dependencies": {},
|
|
33
32
|
"devDependencies": {
|
|
34
33
|
"chai": "^4.3.10",
|
|
35
|
-
"mocha": "^
|
|
34
|
+
"mocha": "^11.7.5"
|
|
35
|
+
},
|
|
36
|
+
"overrides": {
|
|
37
|
+
"diff": "8.0.4",
|
|
38
|
+
"serialize-javascript": "7.0.5"
|
|
36
39
|
},
|
|
37
40
|
"peerDependencies": {
|
|
38
|
-
"@neuraiproject/neurai-rpc": "^0.4.
|
|
41
|
+
"@neuraiproject/neurai-rpc": "^0.4.7"
|
|
39
42
|
},
|
|
40
43
|
"engines": {
|
|
41
44
|
"node": ">=14.0.0"
|
package/src/NeuraiAssets.js
CHANGED
|
@@ -25,6 +25,7 @@ const { AssetQueries } = require('./queries');
|
|
|
25
25
|
const {
|
|
26
26
|
IssueRootBuilder,
|
|
27
27
|
IssueSubBuilder,
|
|
28
|
+
IssueDepinBuilder,
|
|
28
29
|
IssueUniqueBuilder,
|
|
29
30
|
IssueQualifierBuilder,
|
|
30
31
|
IssueRestrictedBuilder,
|
|
@@ -119,6 +120,21 @@ class NeuraiAssets {
|
|
|
119
120
|
return await builder.build();
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Create a DEPIN asset
|
|
125
|
+
* @param {object} params - DEPIN creation parameters
|
|
126
|
+
* @param {string} params.assetName - Asset name (&NAME or &NAME/SUB)
|
|
127
|
+
* @param {number} params.quantity - Total supply
|
|
128
|
+
* @param {boolean} [params.reissuable=true] - Can mint more later
|
|
129
|
+
* @param {boolean} [params.hasIpfs=false] - Has IPFS metadata
|
|
130
|
+
* @param {string} [params.ipfsHash] - IPFS hash
|
|
131
|
+
* @returns {Promise<object>} Transaction data
|
|
132
|
+
*/
|
|
133
|
+
async createDepinAsset(params) {
|
|
134
|
+
const builder = new IssueDepinBuilder(this.rpc, this._buildParams(params));
|
|
135
|
+
return await builder.build();
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
/**
|
|
123
139
|
* Reissue (mint more) of a ROOT or SUB asset
|
|
124
140
|
* @param {object} params - Reissue parameters
|
|
@@ -439,6 +455,25 @@ class NeuraiAssets {
|
|
|
439
455
|
return await this.queries.cancelSnapshotRequest(assetName, blockHeight);
|
|
440
456
|
}
|
|
441
457
|
|
|
458
|
+
/**
|
|
459
|
+
* List DEPIN holders with validity status
|
|
460
|
+
* @param {string} assetName - DEPIN asset name
|
|
461
|
+
* @returns {Promise<Array>} Holder entries
|
|
462
|
+
*/
|
|
463
|
+
async listDepinHolders(assetName) {
|
|
464
|
+
return await this.queries.listDepinHolders(assetName);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Check DEPIN validity for an address
|
|
469
|
+
* @param {string} assetName - DEPIN asset name
|
|
470
|
+
* @param {string} address - Address to query
|
|
471
|
+
* @returns {Promise<object>} Validity details
|
|
472
|
+
*/
|
|
473
|
+
async checkDepinValidity(assetName, address) {
|
|
474
|
+
return await this.queries.checkDepinValidity(assetName, address);
|
|
475
|
+
}
|
|
476
|
+
|
|
442
477
|
/**
|
|
443
478
|
* Check if asset exists
|
|
444
479
|
* @param {string} assetName - Asset name
|
|
@@ -451,7 +486,7 @@ class NeuraiAssets {
|
|
|
451
486
|
/**
|
|
452
487
|
* Get asset type from name
|
|
453
488
|
* @param {string} assetName - Asset name
|
|
454
|
-
* @returns {string} Asset type ('ROOT', 'SUB', 'UNIQUE', 'QUALIFIER', 'RESTRICTED', 'OWNER')
|
|
489
|
+
* @returns {string} Asset type ('ROOT', 'SUB', 'UNIQUE', 'QUALIFIER', 'RESTRICTED', 'DEPIN', 'OWNER')
|
|
455
490
|
*/
|
|
456
491
|
getAssetType(assetName) {
|
|
457
492
|
return this.queries.getAssetType(assetName);
|
|
@@ -225,6 +225,9 @@ class BaseAssetTransactionBuilder {
|
|
|
225
225
|
case 'RESTRICTED':
|
|
226
226
|
AssetNameValidator.validateRestricted(assetName);
|
|
227
227
|
break;
|
|
228
|
+
case 'DEPIN':
|
|
229
|
+
AssetNameValidator.validateDepin(assetName);
|
|
230
|
+
break;
|
|
228
231
|
default:
|
|
229
232
|
throw new Error(`Unknown asset type: ${type}`);
|
|
230
233
|
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue DePIN Builder
|
|
3
|
+
* Builds transactions for creating DEPIN assets.
|
|
4
|
+
*
|
|
5
|
+
* DEPIN assets:
|
|
6
|
+
* - Soulbound assets
|
|
7
|
+
* - Format: &NAME or &NAME/SUB
|
|
8
|
+
* - Cost: 10 XNA (same burn as UNIQUE assets)
|
|
9
|
+
* - Units: Always 0
|
|
10
|
+
* - Owner token is auto-created by the node
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const BaseAssetTransactionBuilder = require('./BaseAssetTransactionBuilder');
|
|
14
|
+
const { OutputFormatter } = require('../utils');
|
|
15
|
+
const { AssetExistsError } = require('../errors');
|
|
16
|
+
const { IpfsValidator } = require('../validators');
|
|
17
|
+
|
|
18
|
+
class IssueDepinBuilder extends BaseAssetTransactionBuilder {
|
|
19
|
+
/**
|
|
20
|
+
* Validate issue DEPIN parameters
|
|
21
|
+
* @param {object} params - Issue parameters
|
|
22
|
+
* @throws {Error} If validation fails
|
|
23
|
+
*/
|
|
24
|
+
validateParams(params) {
|
|
25
|
+
if (!params.assetName) {
|
|
26
|
+
throw new Error('assetName is required');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (params.quantity === undefined || params.quantity === null) {
|
|
30
|
+
throw new Error('quantity is required');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.validateAssetName(params.assetName, 'DEPIN');
|
|
34
|
+
this.validateAmount(params.quantity, 0);
|
|
35
|
+
|
|
36
|
+
if (params.units !== undefined && params.units !== 0) {
|
|
37
|
+
throw new Error('DEPIN assets must use units=0');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (params.hasIpfs && params.ipfsHash) {
|
|
41
|
+
IpfsValidator.validate(params.ipfsHash);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (params.reissuable !== undefined && typeof params.reissuable !== 'boolean') {
|
|
45
|
+
throw new Error('reissuable must be a boolean');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build DEPIN asset issuance transaction
|
|
53
|
+
* @returns {Promise<object>} Transaction result
|
|
54
|
+
*/
|
|
55
|
+
async build() {
|
|
56
|
+
await this.validateParams(this.params);
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
assetName,
|
|
60
|
+
quantity,
|
|
61
|
+
reissuable = true,
|
|
62
|
+
hasIpfs = false,
|
|
63
|
+
ipfsHash = ''
|
|
64
|
+
} = this.params;
|
|
65
|
+
|
|
66
|
+
const exists = await this.assetExists(assetName);
|
|
67
|
+
if (exists) {
|
|
68
|
+
throw new AssetExistsError(
|
|
69
|
+
`Asset ${assetName} already exists on the blockchain`,
|
|
70
|
+
assetName
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const burnInfo = this.burnManager.getIssueDepinBurn();
|
|
75
|
+
const toAddress = await this.getToAddress();
|
|
76
|
+
const changeAddress = await this.getChangeAddress();
|
|
77
|
+
|
|
78
|
+
const estimatedFee = await this.estimateFee(1, 3);
|
|
79
|
+
const totalXNANeeded = burnInfo.amount + estimatedFee;
|
|
80
|
+
|
|
81
|
+
const utxoSelection = await this.selectUTXOs(totalXNANeeded, null, 0);
|
|
82
|
+
const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
|
|
83
|
+
const totalXNAInput = utxoSelection.totalXNA;
|
|
84
|
+
|
|
85
|
+
const actualFee = await this.estimateFee(baseCurrencyUTXOs.length, 3);
|
|
86
|
+
const totalRequired = burnInfo.amount + actualFee;
|
|
87
|
+
|
|
88
|
+
if (totalXNAInput < totalRequired) {
|
|
89
|
+
const additionalNeeded = totalRequired - totalXNAInput + 0.001;
|
|
90
|
+
const additionalSelection = await this.selectUTXOs(additionalNeeded, null, 0);
|
|
91
|
+
baseCurrencyUTXOs.push(...additionalSelection.xnaUTXOs);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const finalTotalInput = baseCurrencyUTXOs.reduce(
|
|
95
|
+
(sum, utxo) => sum + utxo.satoshis / 100000000,
|
|
96
|
+
0
|
|
97
|
+
);
|
|
98
|
+
const xnaChange = finalTotalInput - burnInfo.amount - actualFee;
|
|
99
|
+
|
|
100
|
+
const inputs = baseCurrencyUTXOs.map(utxo => ({
|
|
101
|
+
txid: utxo.txid,
|
|
102
|
+
vout: utxo.outputIndex,
|
|
103
|
+
address: utxo.address,
|
|
104
|
+
satoshis: utxo.satoshis
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const outputs = [];
|
|
108
|
+
outputs.push({ [burnInfo.address]: burnInfo.amount });
|
|
109
|
+
|
|
110
|
+
if (xnaChange > 0.00000001) {
|
|
111
|
+
outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const issueOutput = OutputFormatter.formatIssueOutput({
|
|
115
|
+
asset_name: assetName,
|
|
116
|
+
asset_quantity: this.toSatoshis(quantity, 0),
|
|
117
|
+
units: 0,
|
|
118
|
+
reissuable,
|
|
119
|
+
has_ipfs: hasIpfs,
|
|
120
|
+
ipfs_hash: ipfsHash
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
outputs.push({ [toAddress]: issueOutput });
|
|
124
|
+
|
|
125
|
+
const orderedOutputs = this.outputOrderer.order(outputs);
|
|
126
|
+
const rawTx = await this.buildRawTransaction(inputs, orderedOutputs);
|
|
127
|
+
|
|
128
|
+
return this.formatResult(
|
|
129
|
+
rawTx,
|
|
130
|
+
baseCurrencyUTXOs,
|
|
131
|
+
inputs,
|
|
132
|
+
orderedOutputs,
|
|
133
|
+
actualFee,
|
|
134
|
+
burnInfo.amount,
|
|
135
|
+
{
|
|
136
|
+
assetName,
|
|
137
|
+
ownerTokenName: `${assetName}!`,
|
|
138
|
+
operationType: 'ISSUE_DEPIN'
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = IssueDepinBuilder;
|
|
@@ -103,6 +103,7 @@ class ReissueBuilder extends BaseAssetTransactionBuilder {
|
|
|
103
103
|
const addresses = await this._getAddresses();
|
|
104
104
|
const toAddress = await this.getToAddress();
|
|
105
105
|
const changeAddress = await this.getChangeAddress();
|
|
106
|
+
const isDepinAsset = AssetNameParser.isDepin(assetName);
|
|
106
107
|
|
|
107
108
|
// 6. Find owner token (CRITICAL: must have this)
|
|
108
109
|
const ownerTokenName = AssetNameParser.getOwnerTokenName(assetName);
|
|
@@ -201,7 +202,7 @@ class ReissueBuilder extends BaseAssetTransactionBuilder {
|
|
|
201
202
|
asset_quantity: this.toSatoshis(quantity, units),
|
|
202
203
|
reissuable: reissuable !== undefined ? reissuable : undefined,
|
|
203
204
|
new_ipfs: newIpfs || undefined,
|
|
204
|
-
owner_change_address: changeAddress
|
|
205
|
+
owner_change_address: isDepinAsset ? toAddress : changeAddress
|
|
205
206
|
});
|
|
206
207
|
|
|
207
208
|
outputs.push({ [toAddress]: reissueOutput });
|
package/src/builders/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const BaseAssetTransactionBuilder = require('./BaseAssetTransactionBuilder');
|
|
|
9
9
|
// Basic Builders
|
|
10
10
|
const IssueRootBuilder = require('./IssueRootBuilder');
|
|
11
11
|
const IssueSubBuilder = require('./IssueSubBuilder');
|
|
12
|
+
const IssueDepinBuilder = require('./IssueDepinBuilder');
|
|
12
13
|
const ReissueBuilder = require('./ReissueBuilder');
|
|
13
14
|
|
|
14
15
|
// Advanced Builders
|
|
@@ -26,6 +27,7 @@ module.exports = {
|
|
|
26
27
|
// Basic Builders
|
|
27
28
|
IssueRootBuilder,
|
|
28
29
|
IssueSubBuilder,
|
|
30
|
+
IssueDepinBuilder,
|
|
29
31
|
ReissueBuilder,
|
|
30
32
|
|
|
31
33
|
// Advanced Builders
|
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
* Different addresses for mainnet and testnet
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const MAINNET_NETWORKS = ['xna', 'mainnet', 'xna-pq', 'mainnet-pq'];
|
|
7
|
+
const TESTNET_NETWORKS = ['xna-test', 'testnet', 'regtest', 'xna-pq-test', 'testnet-pq'];
|
|
8
|
+
|
|
9
|
+
function resolveNetworkFamily(network) {
|
|
10
|
+
if (MAINNET_NETWORKS.includes(network)) {
|
|
11
|
+
return 'mainnet';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (TESTNET_NETWORKS.includes(network)) {
|
|
15
|
+
return 'testnet';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
throw new Error(`Unknown network: ${network}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
const MAINNET_BURN_ADDRESSES = {
|
|
7
22
|
ISSUE_ROOT: 'NbURNXXXXXXXXXXXXXXXXXXXXXXXT65Gdr',
|
|
8
23
|
ISSUE_SUB: 'NXissueSubAssetXXXXXXXXXXXXXX6B2JF',
|
|
@@ -32,11 +47,12 @@ const TESTNET_BURN_ADDRESSES = {
|
|
|
32
47
|
/**
|
|
33
48
|
* Get burn address for an operation and network
|
|
34
49
|
* @param {string} operationType - Operation type (e.g., 'ISSUE_ROOT')
|
|
35
|
-
* @param {string} network - Network type ('xna' or 'xna-test')
|
|
50
|
+
* @param {string} network - Network type ('xna', 'xna-test', 'xna-pq', or 'xna-pq-test')
|
|
36
51
|
* @returns {string} Burn address
|
|
37
52
|
*/
|
|
38
53
|
function getBurnAddress(operationType, network) {
|
|
39
|
-
const
|
|
54
|
+
const family = resolveNetworkFamily(network);
|
|
55
|
+
const addresses = family === 'mainnet' ? MAINNET_BURN_ADDRESSES : TESTNET_BURN_ADDRESSES;
|
|
40
56
|
|
|
41
57
|
const address = addresses[operationType];
|
|
42
58
|
if (!address) {
|
|
@@ -49,17 +65,19 @@ function getBurnAddress(operationType, network) {
|
|
|
49
65
|
/**
|
|
50
66
|
* Check if an address is a burn address
|
|
51
67
|
* @param {string} address - Address to check
|
|
52
|
-
* @param {string} network - Network type ('xna' or 'xna-test')
|
|
68
|
+
* @param {string} network - Network type ('xna', 'xna-test', 'xna-pq', or 'xna-pq-test')
|
|
53
69
|
* @returns {boolean} True if it's a burn address
|
|
54
70
|
*/
|
|
55
71
|
function isBurnAddress(address, network) {
|
|
56
|
-
const
|
|
72
|
+
const family = resolveNetworkFamily(network);
|
|
73
|
+
const addresses = family === 'mainnet' ? MAINNET_BURN_ADDRESSES : TESTNET_BURN_ADDRESSES;
|
|
57
74
|
return Object.values(addresses).includes(address);
|
|
58
75
|
}
|
|
59
76
|
|
|
60
77
|
module.exports = {
|
|
61
78
|
MAINNET_BURN_ADDRESSES,
|
|
62
79
|
TESTNET_BURN_ADDRESSES,
|
|
80
|
+
resolveNetworkFamily,
|
|
63
81
|
getBurnAddress,
|
|
64
82
|
isBurnAddress
|
|
65
83
|
};
|
package/src/constants/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const { ASSET_COSTS, getAssetCost, getUniqueAssetCost, getTaggingCost } = requir
|
|
|
8
8
|
const {
|
|
9
9
|
MAINNET_BURN_ADDRESSES,
|
|
10
10
|
TESTNET_BURN_ADDRESSES,
|
|
11
|
+
resolveNetworkFamily,
|
|
11
12
|
getBurnAddress,
|
|
12
13
|
isBurnAddress
|
|
13
14
|
} = require('./burnAddresses');
|
|
@@ -32,6 +33,7 @@ module.exports = {
|
|
|
32
33
|
// Burn Addresses
|
|
33
34
|
MAINNET_BURN_ADDRESSES,
|
|
34
35
|
TESTNET_BURN_ADDRESSES,
|
|
36
|
+
resolveNetworkFamily,
|
|
35
37
|
getBurnAddress,
|
|
36
38
|
isBurnAddress,
|
|
37
39
|
|