@neuraiproject/neurai-assets 1.4.1 → 1.5.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 +170 -11
- package/dist/NeuraiAssets.global.js +4337 -471
- package/dist/NeuraiAssets.global.js.map +1 -1
- package/dist/browser.js +4337 -471
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +4337 -471
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4337 -471
- package/dist/index.js.map +1 -1
- package/index.d.ts +222 -3
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -484,18 +484,142 @@ All creation/reissuance operations return an object with this structure:
|
|
|
484
484
|
|
|
485
485
|
```javascript
|
|
486
486
|
{
|
|
487
|
-
rawTx: 'hex string', // Unsigned transaction (to sign with wallet)
|
|
487
|
+
rawTx: 'hex string', // Unsigned transaction built by the node (to sign with wallet)
|
|
488
488
|
utxos: [...], // UTXOs selected for the operation
|
|
489
489
|
inputs: [...], // Transaction inputs
|
|
490
|
-
outputs: [...], // Ordered outputs
|
|
490
|
+
outputs: [...], // Ordered outputs (DISPLAY amounts, for createrawtransaction)
|
|
491
491
|
fee: 0.001, // Fee in XNA
|
|
492
492
|
burnAmount: 1000, // Burned amount in XNA
|
|
493
493
|
assetName: 'MYTOKEN', // Operation-specific fields vary by builder
|
|
494
494
|
ownerTokenName: 'MYTOKEN!',
|
|
495
|
-
operationType: 'ISSUE_ROOT'
|
|
495
|
+
operationType: 'ISSUE_ROOT',
|
|
496
|
+
createTransactionBuild: { ... } // Build it yourself, offline — see below
|
|
496
497
|
}
|
|
497
498
|
```
|
|
498
499
|
|
|
500
|
+
## Building offline with `createTransactionBuild` (1.5.0+)
|
|
501
|
+
|
|
502
|
+
`rawTx` above is built by the node through `createrawtransaction`. To build the
|
|
503
|
+
same transaction **locally**, pass `result.createTransactionBuild` straight to
|
|
504
|
+
`@neuraiproject/neurai-create-transaction`:
|
|
505
|
+
|
|
506
|
+
```javascript
|
|
507
|
+
import { createFromOperation } from '@neuraiproject/neurai-create-transaction';
|
|
508
|
+
|
|
509
|
+
const result = await assets.transferAsset({
|
|
510
|
+
assetName: 'MYTOKEN',
|
|
511
|
+
recipients: [{ address: 'tRecipient...', amount: 4.35 }]
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const built = createFromOperation(result.createTransactionBuild);
|
|
515
|
+
// built.rawTx — sign it with @neuraiproject/neurai-sign-transaction
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Nothing has to be renamed, rescaled or reinterpreted in between. In particular
|
|
519
|
+
you do **not** need to:
|
|
520
|
+
|
|
521
|
+
- map the operation type — a transfer already arrives as `STANDARD_TRANSFER` or
|
|
522
|
+
`TRANSFER_DEPIN`, the discriminants the serializer accepts;
|
|
523
|
+
- merge recipients, asset change and the DePIN owner return into one list (the
|
|
524
|
+
serializer emits the `&NAME!` escort itself; listing it again would produce
|
|
525
|
+
two owner outputs);
|
|
526
|
+
- convert display amounts to protocol integers;
|
|
527
|
+
- add or correct `assetMarker`.
|
|
528
|
+
|
|
529
|
+
### Display amounts vs raw amounts
|
|
530
|
+
|
|
531
|
+
The library speaks two representations, deliberately kept apart:
|
|
532
|
+
|
|
533
|
+
| Where | Representation | Why |
|
|
534
|
+
| --- | --- | --- |
|
|
535
|
+
| Your call (`quantity`, `recipients[].amount`) | Display (`4.35`) | What a user types |
|
|
536
|
+
| `result.outputs` (RPC envelope) | Display (`4.35`) | `createrawtransaction` scales it itself |
|
|
537
|
+
| `result.createTransactionBuild` (`*Raw`, `*Sats`) | `bigint` (`435000000n`) | What the chain encodes |
|
|
538
|
+
|
|
539
|
+
The asset payload scale is always `10^8`, **independently of the asset's
|
|
540
|
+
`units`**: `units` limits divisibility and presentation, it is never a
|
|
541
|
+
multiplier. A quantity of `1.25` reaches the chain as `125000000n` whether the
|
|
542
|
+
asset has `units=2` or `units=8`.
|
|
543
|
+
|
|
544
|
+
Conversion goes through text rather than `Math.round(value * 1e8)`. That
|
|
545
|
+
multiplication is fine for ordinary magnitudes — `4.35 * 1e8` is
|
|
546
|
+
`434999999.99999994`, but `Math.round` recovers `435000000` — and it fails
|
|
547
|
+
silently in exactly two places:
|
|
548
|
+
|
|
549
|
+
- **more than eight decimals** are rounded away instead of rejected, so an
|
|
550
|
+
amount can vanish (`1e-9` → `0`) or shift (`1.123456789` → `112345679`);
|
|
551
|
+
- **past `Number.MAX_SAFE_INTEGER`** the scaled product drops bits:
|
|
552
|
+
`184467440.73709551` → `18446744073709552`, one unit off.
|
|
553
|
+
|
|
554
|
+
Both are reachable with values a wallet can hold. Here, an amount is rejected
|
|
555
|
+
rather than silently truncated when it has more than eight decimals, when the
|
|
556
|
+
asset's `units` cannot represent it, or when it exceeds the consensus ceiling
|
|
557
|
+
`MAX_MONEY` (`21000000000` units — the node's `MoneyRange`).
|
|
558
|
+
|
|
559
|
+
### Large amounts: pass a string
|
|
560
|
+
|
|
561
|
+
Above `MAX_SAFE_INTEGER / 1e8` (~`90071992.55`) a JavaScript `number` can no
|
|
562
|
+
longer name every 8-decimal value, so a **fractional** one is refused there and
|
|
563
|
+
the error names the string to use instead:
|
|
564
|
+
|
|
565
|
+
```javascript
|
|
566
|
+
await assets.createRootAsset({ assetName: 'BIG', quantity: 100000000.5, units: 1 });
|
|
567
|
+
// InvalidAmountError: ... pass it as a decimal string ("100000000.5") instead.
|
|
568
|
+
|
|
569
|
+
await assets.createRootAsset({ assetName: 'BIG', quantity: '100000000.5', units: 1 });
|
|
570
|
+
// works
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
A **safe integer** is still accepted however large it scales, so the documented
|
|
574
|
+
maximum supply `21000000000` keeps working as a number. Strings are exempt from
|
|
575
|
+
this precision guard — they carried their own digits — but not from any other
|
|
576
|
+
rule: sign, decimals, `units` divisibility and `MAX_MONEY` apply equally.
|
|
577
|
+
|
|
578
|
+
### Migration 1.4.x → 1.5.x → 2.0
|
|
579
|
+
|
|
580
|
+
| | 1.4.x | 1.5.x | 2.0 |
|
|
581
|
+
| --- | --- | --- | --- |
|
|
582
|
+
| `localRawBuild` | only option | present, **deprecated** | removed |
|
|
583
|
+
| `createTransactionBuild` | — | present, canonical | only option |
|
|
584
|
+
| Transfers via `createFromOperation` | rejected (`TRANSFER` is not a discriminant) | work | work |
|
|
585
|
+
| `*Raw` fields | display values | protocol integers | protocol integers |
|
|
586
|
+
| `toSatoshis(amount, units)` | returns the display amount | unchanged, deprecated | removed |
|
|
587
|
+
|
|
588
|
+
`localRawBuild` keeps its exact 1.4.x shape through the whole 1.x line, so no
|
|
589
|
+
consumer has to move on this release. New integrations should read
|
|
590
|
+
`createTransactionBuild` only.
|
|
591
|
+
|
|
592
|
+
### Serializer version
|
|
593
|
+
|
|
594
|
+
The canonical contract needs `@neuraiproject/neurai-create-transaction`
|
|
595
|
+
**>= 0.8.0**. Two of its fixes are load-bearing here:
|
|
596
|
+
|
|
597
|
+
- global `FREEZE_ASSET` / `UNFREEZE_ASSET` encode the restriction flag as
|
|
598
|
+
`1`/`0` (0.7.0 emitted `3`/`2`, which the node rejected with
|
|
599
|
+
`bad-txns-null-data-flag-must-be-0-or-1`, so those two discriminants could
|
|
600
|
+
not reach a mempool);
|
|
601
|
+
- a reissue that omits `units` encodes "keep the current units" (`0xff`), which
|
|
602
|
+
is what this library relies on — see below.
|
|
603
|
+
|
|
604
|
+
### Reissue never changes an asset's units
|
|
605
|
+
|
|
606
|
+
There is no API here to change the precision of an existing asset, so a reissue
|
|
607
|
+
build deliberately **omits** `units`, which the serializer encodes as `0xff`
|
|
608
|
+
("keep"). Echoing the value read from `getassetdata` would instead say "set
|
|
609
|
+
units to N", and a stale read — the asset reissued to a higher precision
|
|
610
|
+
between the read and the broadcast — would ask the node to lower them, which it
|
|
611
|
+
rejects with `unit must be larger than current unit selection`.
|
|
612
|
+
|
|
613
|
+
The value read from the chain is still used, to check that the requested
|
|
614
|
+
`quantity` fits the asset's precision.
|
|
615
|
+
|
|
616
|
+
One consequence worth knowing: the **node-built** `rawTx` cannot reissue an
|
|
617
|
+
asset whose `units` are above zero at all. `createrawtransaction`'s `reissue`
|
|
618
|
+
object has no field for units and the node fills in `0`, so it refuses. That is
|
|
619
|
+
a limitation of the RPC interface, not of the operation — build those offline
|
|
620
|
+
with `createFromOperation(result.createTransactionBuild)`. The error message
|
|
621
|
+
says so when you hit it.
|
|
622
|
+
|
|
499
623
|
## Owner Tokens - IMPORTANT
|
|
500
624
|
|
|
501
625
|
When you create an asset, an **owner token** is automatically generated (e.g., `MYTOKEN!`).
|
|
@@ -544,18 +668,43 @@ Ravencoin-inherited `rvn` to `xna` at an activation height per network
|
|
|
544
668
|
|
|
545
669
|
- Transactions built **through the node** (`createrawtransaction`) need
|
|
546
670
|
nothing: the node stamps the marker itself.
|
|
547
|
-
-
|
|
548
|
-
|
|
549
|
-
|
|
671
|
+
- Locally built transactions carry `params.assetMarker` in both
|
|
672
|
+
`createTransactionBuild` and the deprecated `localRawBuild`. Builders
|
|
673
|
+
resolve it **once per build**:
|
|
550
674
|
1. `params.assetMarker` / `config.assetMarker` if you set it (`'rvn'` |
|
|
551
675
|
`'xna'` — offline builds or tests);
|
|
552
676
|
2. otherwise the node's `getblockchaininfo.asset_marker` (node commit
|
|
553
677
|
`347362b` or later);
|
|
554
|
-
3. `'rvn'` when the node predates that field
|
|
555
|
-
|
|
678
|
+
3. `'rvn'` when the node predates that field — which matches what such a
|
|
679
|
+
node enforces.
|
|
556
680
|
|
|
557
681
|
No height tables and no network inference: the node (or you) decides.
|
|
558
682
|
|
|
683
|
+
### Failure policy (`assetMarkerPolicy`, 1.5.0+)
|
|
684
|
+
|
|
685
|
+
Step 3 above covers a node that *answers* without the field. A node that does
|
|
686
|
+
not answer at all is a different situation, and `assetMarkerPolicy` decides it:
|
|
687
|
+
|
|
688
|
+
```javascript
|
|
689
|
+
const assets = new NeuraiAssets(rpc, {
|
|
690
|
+
network: 'xna-test',
|
|
691
|
+
addresses: [...],
|
|
692
|
+
assetMarkerPolicy: 'strict' // default: 'legacy-fallback'
|
|
693
|
+
});
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
| Policy | `getblockchaininfo` fails | Field absent/null | Unknown value |
|
|
697
|
+
| --- | --- | --- | --- |
|
|
698
|
+
| `legacy-fallback` (default in 1.x) | resolves `'rvn'` | resolves `'rvn'` | throws |
|
|
699
|
+
| `strict` | **throws** | resolves `'rvn'` | throws |
|
|
700
|
+
|
|
701
|
+
Use `strict` in a connected wallet on a post-NIP-040 chain: guessing `'rvn'`
|
|
702
|
+
there builds a transaction the node rejects with
|
|
703
|
+
`bad-txns-legacy-asset-marker-after-nip040`, so "the node did not answer" must
|
|
704
|
+
not silently become "the node said rvn". The rejection propagates out of the
|
|
705
|
+
build — you never receive a partial result — and the node is queried only once,
|
|
706
|
+
whether the query succeeds or fails.
|
|
707
|
+
|
|
559
708
|
## Validations
|
|
560
709
|
|
|
561
710
|
The library validates client-side:
|
|
@@ -744,11 +893,15 @@ are `xna` and `xna-test`; `xna-pq` and `xna-pq-test` remain available as compati
|
|
|
744
893
|
|
|
745
894
|
## Fee estimation (PQ-aware)
|
|
746
895
|
|
|
747
|
-
Asset transactions are usually built with one or two XNA inputs plus, depending on the operation, an owner-token or qualifier UTXO.
|
|
896
|
+
Asset transactions are usually built with one or two XNA inputs plus, depending on the operation, an owner-token or qualifier UTXO. Since `1.5.0` the XNA side is funded by a loop that selects inputs, recomputes the fee from the *real* (PQ-aware) descriptors of the full input set, and repeats until the funds cover burn + fee. Every round excludes the outpoints it already holds, so a transaction never spends the same outpoint twice and the fee always accounts for every input it pays for.
|
|
748
897
|
|
|
749
|
-
|
|
898
|
+
Running out of funds raises `InsufficientFundsError` rather than returning an underfunded build. That includes a case worth knowing about: each PQ input costs about `0.0147 XNA` in fee, so a UTXO worth less than that makes the shortfall *worse*, and a wallet fragmented into such pieces cannot fund a PQ transaction at all.
|
|
750
899
|
|
|
751
|
-
|
|
900
|
+
All estimates share a single `estimatesmartfee` lookup. The fee rate is stable for the lifetime of one build, so it is fetched on the first estimate and cached on the builder instance.
|
|
901
|
+
|
|
902
|
+
Estimates use the helpers in [`src/utils/feeSizing.js`](src/utils/feeSizing.js) and distinguish PQ AuthScript inputs/outputs from legacy P2PKH ones. PQ inputs spend ~977 vbytes vs ~148 for legacy — without this distinction, transactions built from PQ addresses fall under the node's `min relay fee` and are rejected with `code -26: min relay fee not met`.
|
|
903
|
+
|
|
904
|
+
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.
|
|
752
905
|
|
|
753
906
|
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.
|
|
754
907
|
|
|
@@ -772,6 +925,12 @@ estimateInputVbytes({ address: 'nq1…' }); // 977
|
|
|
772
925
|
estimateInputVbytes({ address: 'mgRYHdMq…' }); // 148
|
|
773
926
|
estimateOutputBytes('tnq1…'); // 43
|
|
774
927
|
|
|
928
|
+
// Asset outputs declare their payload: kind is 'transfer' (default),
|
|
929
|
+
// 'owner', 'issue' or 'reissue'.
|
|
930
|
+
estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX' }); // 55
|
|
931
|
+
estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX!', kind: 'owner' }); // 48
|
|
932
|
+
estimateOutputBytes({ address: 't7pv…', assetName: 'ROOTX', kind: 'issue' }); // 58
|
|
933
|
+
|
|
775
934
|
const vbytes = estimateTransactionVbytes(
|
|
776
935
|
[{ script: '5120…' }, { address: 'mgRYHdMq…' }], // 1 PQ + 1 legacy input
|
|
777
936
|
['nq1qchange…', 'mgRYHdMqburn…'], // 1 PQ + 1 legacy output
|