@neuraiproject/neurai-assets 1.5.3 → 1.6.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 CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  Complete asset management library for Neurai blockchain. Supports creation, reissuance, and queries for all asset types in a non-custodial way.
4
4
 
5
+ > **1.6.0**: full DEPIN support — sub-DEPIN issuance (which needs the
6
+ > **immediate** parent's owner token), holder freeze/unfreeze through the same
7
+ > `freezeAddresses`/`unfreezeAddresses` calls, `selfRevokeDepin` and
8
+ > `listDepinAddresses`. Also a fix beyond DEPIN: the parent of `A/B/C` resolved
9
+ > to `A` instead of `A/B`, so every third-level issuance — sub-asset,
10
+ > sub-qualifier or sub-DEPIN — was rejected by the node.
11
+ >
12
+ > **1.5.3**: fee estimation stopped modelling the payload encoding and started
13
+ > measuring it. Twelve of eighteen operations were budgeting below the node's
14
+ > minimum, which forced a rebuild-and-re-sign on every one of them (see
15
+ > *Fee estimation*).
16
+ >
17
+ > **1.5.2**: same RPC calls, fewer round trips. The UTXO and mempool reads run
18
+ > together, and the fee rate and NIP-040 marker start alongside the first read
19
+ > instead of queuing behind it — 6 calls in 3 round trips instead of 6, which
20
+ > against a remote proxy halves the time to build anything.
21
+ >
22
+ > **1.5.1**: fix — `Buffer` reached the browser bundle through the fee sizing
23
+ > helper and broke any asset build in an extension.
24
+ >
25
+ > **1.5.0**: `createTransactionBuild` canonical contract; decimal strings for
26
+ > amounts; exact-satoshi funding loop; reissue built locally so assets with
27
+ > `units > 0` can be reissued at all.
28
+ >
5
29
  > **1.4.1**: fix — the constructor dropped `config.assetMarker`, so the
6
30
  > wallet-level override documented in 1.4.0 never reached the builders
7
31
  > (per-operation `params.assetMarker` was unaffected). Precedence is now
@@ -18,6 +42,7 @@ Complete asset management library for Neurai blockchain. Supports creation, reis
18
42
  - ✅ **Non-custodial**: Library builds unsigned transactions, your wallet signs them
19
43
  - ✅ **All asset types**: ROOT, SUB, UNIQUE (NFTs), QUALIFIER, RESTRICTED, DEPIN
20
44
  - ✅ **Complete operations**: Creation, reissuance, tagging, freezing
45
+ - ✅ **DEPIN management**: sub-assets, holder freeze/unfreeze, self-revocation, validity queries
21
46
  - ✅ **RPC queries**: Complete wrapper for all asset query methods
22
47
  - ✅ **Client-side validation**: Prevents errors before creating transactions
23
48
  - ✅ **Owner token protection**: Validation to prevent permanent loss
@@ -33,7 +58,8 @@ Complete asset management library for Neurai blockchain. Supports creation, reis
33
58
  | **QUALIFIER** | `#KYC` | 2000 XNA | Compliance tag |
34
59
  | **SUB_QUALIFIER** | `#PARENT/#SUB` | 200 XNA | Sub-qualifier |
35
60
  | **RESTRICTED** | `$SECURITY` | 3000 XNA | Security token with compliance |
36
- | **DEPIN** | `&DEVICE` or `&DEVICE/ROUTER001` | 10 XNA | Soulbound asset with holder validity controls |
61
+ | **DEPIN** | `&DEVICE` | 10 XNA | Soulbound asset with holder validity controls |
62
+ | **SUB DEPIN** | `&DEVICE/ROUTER001` | 10 XNA | Same burn as a root DEPIN, but requires the **immediate parent's** owner token |
37
63
 
38
64
  ## Quantities and asset units
39
65
 
@@ -194,6 +220,16 @@ const result = await assets.createDepinAsset({
194
220
  > **Note**: DEPIN assets always use `units = 0`. Recipient and change destinations
195
221
  > can be either legacy or AuthScript, as long as they belong to the same chain family.
196
222
 
223
+ A **sub-DEPIN** (`&DEVICE/ROUTER001`) costs the same as a root DEPIN, but the
224
+ node requires the transaction to spend and return the **immediate parent's**
225
+ owner token — `&DEVICE!` here, and `&DEVICE/ROUTER001!` for a third level. The
226
+ library finds it, adds it as an input and returns it automatically; without it
227
+ the node rejects the transaction with
228
+ `Trying to create outpoint for asset that you don't have`.
229
+
230
+ So `&A/B/C` needs `&A/B!`, **not** `&A!`. Before 1.6.0 the library resolved the
231
+ parent as the root and every third-level issuance was rejected.
232
+
197
233
  ### Transfer Asset
198
234
 
199
235
  ```javascript
@@ -299,13 +335,31 @@ const result = await assets.reissueRestrictedAsset({
299
335
  ### Freeze Addresses
300
336
 
301
337
  ```javascript
302
- // Requires the restricted asset's owner token ($SECURITY!)
338
+ // Requires the asset's owner token ($SECURITY! or &DEVICE!)
303
339
  const result = await assets.freezeAddresses({
304
340
  assetName: '$SECURITY',
305
341
  addresses: ['NAddress1...', 'NAddress2...']
306
342
  });
343
+
344
+ // Since 1.6.0, the same call freezes a DEPIN holder
345
+ await assets.freezeAddresses({
346
+ assetName: '&DEVICE',
347
+ addresses: ['NDevice1...']
348
+ });
307
349
  ```
308
350
 
351
+ Both asset kinds share this operation because the node builds them identically:
352
+ an owner-token escort plus one null-asset-data output carrying `(name, flag)`.
353
+ A frozen DEPIN holder keeps the asset but `checkDepinValidity` reports
354
+ `valid: 0`.
355
+
356
+ Two rules the library enforces before the node does:
357
+
358
+ - the address holding the owner token **cannot** be frozen or revoked — if it
359
+ were, nobody could undo it;
360
+ - **global** freeze does not apply to DEPIN assets; manage them holder by
361
+ holder.
362
+
309
363
  ### Unfreeze Addresses
310
364
 
311
365
  ```javascript
@@ -315,6 +369,8 @@ const result = await assets.unfreezeAddresses({
315
369
  });
316
370
  ```
317
371
 
372
+ This is also how an owner undoes a holder's self-revocation.
373
+
318
374
  ### Freeze Asset Globally
319
375
 
320
376
  ```javascript
@@ -323,6 +379,24 @@ const result = await assets.freezeAssetGlobally({
323
379
  });
324
380
  ```
325
381
 
382
+ > Restricted assets only. Calling it on a DEPIN asset throws: the node has no
383
+ > global restriction for them.
384
+
385
+ ### Self-Revoke a DEPIN Asset (1.6.0+)
386
+
387
+ ```javascript
388
+ // Run from the wallet that HOLDS the device asset — no owner token needed.
389
+ const result = await assets.selfRevokeDepin({ assetName: '&DEVICE' });
390
+ ```
391
+
392
+ A holder renouncing its own asset. The proof of ownership is spending its own
393
+ asset UTXO, which returns to the same address together with the revocation
394
+ mark, so the operation needs no owner token and no burn.
395
+
396
+ ⚠️ Only the asset owner can undo it, with `unfreezeAddresses`. If the revoking
397
+ address also held the owner token nobody could, so the library refuses that
398
+ case — move the owner token elsewhere first.
399
+
326
400
  ### Unfreeze Asset Globally
327
401
 
328
402
  ```javascript
@@ -466,6 +540,21 @@ console.log(validity);
466
540
  // }
467
541
  ```
468
542
 
543
+ ### List DEPIN Addresses With a Revealed Public Key (1.6.0+)
544
+
545
+ ```javascript
546
+ const addresses = await assets.listDepinAddresses('&DEVICE');
547
+ console.log(addresses);
548
+ // [ { address: 'tAhTaUnh...', pubkey: '03d0813ea333...' } ]
549
+ ```
550
+
551
+ The subset of holders that can take part in DEPIN messaging, which needs the
552
+ public key. It is **not** a replacement for `listDepinHolders`: it says nothing
553
+ about validity or amounts, and a holder that has never spent does not appear.
554
+
555
+ Requires the node running with `-pubkeyindex` (and a reindex). Without it the
556
+ error says so instead of hiding behind a generic failure.
557
+
469
558
  ### Detect Asset Type
470
559
 
471
560
  ```javascript
@@ -589,6 +678,16 @@ rule: sign, decimals, `units` divisibility and `MAX_MONEY` apply equally.
589
678
  consumer has to move on this release. New integrations should read
590
679
  `createTransactionBuild` only.
591
680
 
681
+ **1.6.0** adds DEPIN management and fixes two things that were silently broken.
682
+ No API changed shape:
683
+
684
+ | | Before 1.6.0 | 1.6.0 |
685
+ | --- | --- | --- |
686
+ | Parent of `A/B/C` | `A` — every third-level issuance was rejected | `A/B`, as the node resolves it |
687
+ | Create `&A/B` | rejected: the parent's owner token was never spent | works |
688
+ | `freezeAddresses` / `unfreezeAddresses` | restricted assets only | also DEPIN (`&NAME`) |
689
+ | Self-revoke, `listDepinAddresses` | — | new |
690
+
592
691
  ### Serializer version
593
692
 
594
693
  The canonical contract needs `@neuraiproject/neurai-create-transaction`
@@ -615,13 +714,18 @@ The value read from the chain is still used, to check that the requested
615
714
 
616
715
  ### Reissue is built locally
617
716
 
618
- `createrawtransaction`'s `reissue` object has no field for units, so the node
619
- fills in `0` and refuses any asset whose units are above zero
620
- (`unit must be larger than current unit selection`). Since 1.5.0 the two
621
- reissue operations therefore skip that RPC and build their `rawTx` with
622
- `createFromOperation`, which can say "keep the current units". They report
623
- `buildStrategy: 'local-builder'`; every other operation still reports
624
- `'rpc-node'`.
717
+ Some operations cannot be expressed through the node's `createrawtransaction`,
718
+ even though the node accepts the resulting transaction perfectly well. Those
719
+ build their `rawTx` with `createFromOperation` instead and report
720
+ `buildStrategy: 'local-builder'`; everything else still reports `'rpc-node'`.
721
+
722
+ | Operation | Why the RPC cannot express it | Since |
723
+ | --- | --- | --- |
724
+ | `reissueAsset`, `reissueRestrictedAsset` | the `reissue` object has no units field, so the node assumes `0` and refuses any asset with units above zero (`unit must be larger than current unit selection`) | 1.5.0 |
725
+ | `freezeAddresses` / `unfreezeAddresses` on a **DEPIN** asset | `freeze_addresses` demands a restricted name: `a valid restricted asset name must be provided` | 1.6.0 |
726
+ | `selfRevokeDepin` | no equivalent RPC object | 1.6.0 |
727
+
728
+ Restricted-asset freezing keeps the RPC path unchanged.
625
729
 
626
730
  This is why `@neuraiproject/neurai-create-transaction` is a runtime
627
731
  **dependency**, not just a dev one.
@@ -640,10 +744,13 @@ When you create an asset, an **owner token** is automatically generated (e.g., `
640
744
 
641
745
  ⚠️ **CRITICAL**: The owner token is required to:
642
746
  - Reissue (mint more supply)
643
- - Create SUB assets
747
+ - Create SUB assets — the **immediate** parent's token: `A/B/C` needs `A/B!`
748
+ - Create sub-DEPIN assets — likewise, `&A/B` needs `&A!`
644
749
  - Manage tags (if qualifier)
645
- - Freeze/unfreeze (if restricted)
646
- - Manage DEPIN reissuance and controls (if depin)
750
+ - Freeze/unfreeze holders (restricted **and** DEPIN)
751
+
752
+ A holder can always self-revoke its own DEPIN asset without any owner token
753
+ (`selfRevokeDepin`), but only the owner can undo that.
647
754
 
648
755
  ⚠️ **If you lose the owner token, you lose these capabilities PERMANENTLY**
649
756
 
@@ -664,13 +771,14 @@ The library automatically validates that the owner token is returned in each ope
664
771
  | Create QUALIFIER (root) | 2000 |
665
772
  | Create QUALIFIER (sub) | 200 |
666
773
  | Create RESTRICTED asset | 3000 |
667
- | Create DEPIN asset | 10 |
774
+ | Create DEPIN asset (root or sub) | 10 |
668
775
  | Reissue ROOT/SUB | 200 |
669
776
  | Reissue DEPIN | 200 |
670
777
  | Reissue RESTRICTED | 200 |
671
778
  | Tag/Untag address | 0 (network fee only; spends 1 unit of the qualifier per address) |
672
779
  | Freeze/Unfreeze address | 0 (network fee only) |
673
780
  | Freeze/Unfreeze global | 0 (network fee only) |
781
+ | Self-revoke DEPIN | 0 (network fee only) |
674
782
 
675
783
  **Note**: In addition to the burned cost, all operations pay a network fee (calculated automatically).
676
784
 
@@ -813,16 +921,20 @@ const builder = new builders.IssueRootBuilder(rpc, {
813
921
  const result = await builder.build();
814
922
  ```
815
923
 
816
- The builders module also includes:
924
+ The builders module exports:
817
925
 
926
+ - `DepinSelfRevokeBuilder`
927
+ - `FreezeAddressBuilder`
818
928
  - `IssueDepinBuilder`
929
+ - `IssueQualifierBuilder`
930
+ - `IssueRestrictedBuilder`
819
931
  - `IssueRootBuilder`
820
932
  - `IssueSubBuilder`
821
933
  - `IssueUniqueBuilder`
822
- - `IssueQualifierBuilder`
823
- - `IssueRestrictedBuilder`
824
934
  - `ReissueBuilder`
825
935
  - `ReissueRestrictedBuilder`
936
+ - `TagAddressBuilder`
937
+ - `TransferBuilder`
826
938
 
827
939
  ## Error Handling
828
940
 
@@ -917,6 +1029,8 @@ Estimates use the helpers in [`src/utils/feeSizing.js`](src/utils/feeSizing.js)
917
1029
 
918
1030
  Outputs that carry an asset payload are sized as such, not as bare P2PKH outputs. An asset output is `<destination> OP_XNA_ASSET <pushdata payload> OP_DROP`, which adds roughly 20-60 bytes; ignoring that under-counts a transaction by a few percent, and that is enough to fall below the floor whenever the node's fee rate sits close to its minimum relay fee.
919
1031
 
1032
+ Since `1.5.3` those sizes are **not modelled, they are measured**: `feeSizing` asks `@neuraiproject/neurai-create-transaction` to encode the very script the output will carry and takes its length. The hand-written formula it replaced had drifted in two ways — the owner token an operation *returns* is serialized as a transfer (it carries an amount), not with the `owner` payload that an issuance *creates*, and the null-asset-data outputs of tag/freeze were not counted at all, since their script **replaces** the destination instead of extending it. Twelve of eighteen operations were budgeting below what the node charges. The regtest e2e now asserts, for every operation, that the budgeted fee reaches the node's minimum for the signed vsize.
1033
+
920
1034
  You should not need to call these helpers directly; they are wired into every builder. They are documented here so you can audit the fee math or use the same constants if you compose transactions outside the standard builder flow.
921
1035
 
922
1036
  ```js
@@ -939,11 +1053,23 @@ estimateInputVbytes({ address: 'nq1…' }); // 977
939
1053
  estimateInputVbytes({ address: 'mgRYHdMq…' }); // 148
940
1054
  estimateOutputBytes('tnq1…'); // 43
941
1055
 
942
- // Asset outputs declare their payload: kind is 'transfer' (default),
943
- // 'owner', 'issue' or 'reissue'.
944
- estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX' }); // 55
945
- estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX!', kind: 'owner' }); // 48
1056
+ // Asset outputs declare their payload. Sizes come from the real encoder, so
1057
+ // they match the bytes the node will see.
1058
+ estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX' }); // 55 transfer (default)
946
1059
  estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX', kind: 'issue' }); // 58
1060
+ estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX', kind: 'reissue' }); // 57
1061
+
1062
+ // 'owner' describes the token an ISSUANCE CREATES — it carries no amount.
1063
+ estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX!', kind: 'owner' }); // 48
1064
+ // The token an operation SPENDS AND RETURNS is a transfer, 8 bytes wider.
1065
+ estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX!' }); // 56
1066
+
1067
+ // Null-asset-data kinds have no destination: their script replaces it, so
1068
+ // nothing is added on top.
1069
+ estimateOutputBytes({ address: 't7pv…', assetName: '#KYC', kind: 'tag' }); // 38
1070
+ estimateOutputBytes({ address: 't7pv…', assetName: '$SEC', kind: 'restriction' }); // 38
1071
+ estimateOutputBytes({ assetName: '$SEC', kind: 'globalRestriction' }); // 19
1072
+ estimateOutputBytes({ kind: 'verifier', verifierString: '#KYC' }); // 17
947
1073
 
948
1074
  const vbytes = estimateTransactionVbytes(
949
1075
  [{ script: '5120…' }, { address: 'mgRYHdMq…' }], // 1 PQ + 1 legacy input
@@ -953,6 +1079,8 @@ const vbytes = estimateTransactionVbytes(
953
1079
 
954
1080
  The constants mirror those exported from `@neuraiproject/neurai-sign-transaction` (`VBYTES`). They are inlined here on purpose: depending on the full signer would pull `bitcoinjs-lib` and `@noble/post-quantum` into the IIFE / browser bundles, far more weight than these constants need. The signer remains the source of truth — if it ever bumps a vbytes value, this file must follow.
955
1081
 
1082
+ `feeSizing` does depend on `neurai-create-transaction` for the payload sizes above, which is already a runtime dependency and is bundled anyway. If an encoder cannot express a descriptor — an address family it does not accept — the estimate falls back to the previous formula rather than throwing in the middle of a fee calculation.
1083
+
956
1084
  ### Limitations
957
1085
 
958
1086
  The estimator assumes the most common spend layout for every input: