@neuraiproject/neurai-assets 1.3.1 → 1.3.3
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 +65 -6
- package/dist/NeuraiAssets.global.js +331 -11
- package/dist/NeuraiAssets.global.js.map +1 -1
- package/dist/browser.js +331 -11
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +331 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +331 -11
- package/dist/index.js.map +1 -1
- package/index.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,31 @@ Complete asset management library for Neurai blockchain. Supports creation, reis
|
|
|
24
24
|
| **RESTRICTED** | `$SECURITY` | 3000 XNA | Security token with compliance |
|
|
25
25
|
| **DEPIN** | `&DEVICE` or `&DEVICE/ROUTER001` | 10 XNA | Soulbound asset with holder validity controls |
|
|
26
26
|
|
|
27
|
+
## Quantities and asset units
|
|
28
|
+
|
|
29
|
+
Every `quantity` / `asset_quantity` parameter accepted by this library is a
|
|
30
|
+
**user-facing display amount** — the same number a human would write to mean
|
|
31
|
+
"this many tokens". For an asset with `units = 2`, `quantity: 10.50` means
|
|
32
|
+
ten and a half tokens; for an asset with `units = 0`, `quantity: 1` means
|
|
33
|
+
one whole token.
|
|
34
|
+
|
|
35
|
+
Internally the daemon parses the JSON `asset_quantity` field with
|
|
36
|
+
`AmountFromValue` ([Bitcoin-style decimal → 10⁸ sats][amount-from-value])
|
|
37
|
+
and validates that the resulting CAmount is a multiple of `10^(8 − units)`
|
|
38
|
+
via `CheckAmountWithUnits`. Because the chain already does the ×10⁸ scaling
|
|
39
|
+
itself, **the library must NOT pre-multiply** the value. Sending the raw
|
|
40
|
+
display number is the only correct behavior; any extra factor on the wire
|
|
41
|
+
either silently inflates the minted supply (`× 10⁸ → 100,000,000` tokens
|
|
42
|
+
where the user asked for 1) or trips the daemon's
|
|
43
|
+
`ParseFixedPoint` cap with `Invalid amount (3): …`.
|
|
44
|
+
|
|
45
|
+
This was regressed in `1.2.2`/`1.3.x` (a hardcoded `× 10⁸` was added to
|
|
46
|
+
`BaseAssetTransactionBuilder.toSatoshis`) and fixed in the version after
|
|
47
|
+
`1.3.1`. If you write a custom builder, follow the same convention: pass
|
|
48
|
+
the user amount through unchanged, let the daemon scale.
|
|
49
|
+
|
|
50
|
+
[amount-from-value]: https://github.com/NeuraiProject/Neurai-DePIN/blob/main/src/rpc/server.cpp
|
|
51
|
+
|
|
27
52
|
## Installation
|
|
28
53
|
|
|
29
54
|
```bash
|
|
@@ -105,8 +130,9 @@ const assetsPQ = new NeuraiAssets(rpc, {
|
|
|
105
130
|
```javascript
|
|
106
131
|
const result = await assets.createRootAsset({
|
|
107
132
|
assetName: 'MYTOKEN',
|
|
108
|
-
quantity: 1000000, // Total supply
|
|
109
|
-
units: 2, //
|
|
133
|
+
quantity: 1000000, // Total supply, in display units (1,000,000 tokens)
|
|
134
|
+
units: 2, // Decimal precision (0–8). With units=2, fractional
|
|
135
|
+
// values down to 0.01 are allowed.
|
|
110
136
|
reissuable: true, // Allow reissuance
|
|
111
137
|
hasIpfs: true,
|
|
112
138
|
ipfsHash: 'QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG'
|
|
@@ -131,12 +157,18 @@ const result = await assets.createSubAsset({
|
|
|
131
157
|
// Requires the asset's owner token (MYTOKEN!)
|
|
132
158
|
const result = await assets.reissueAsset({
|
|
133
159
|
assetName: 'MYTOKEN',
|
|
134
|
-
quantity: 500000, // Additional amount to mint
|
|
160
|
+
quantity: 500000, // Additional amount to mint, in display units.
|
|
161
|
+
// For an asset with units=0, `quantity: 1`
|
|
162
|
+
// mints exactly 1 token (NOT 100,000,000).
|
|
135
163
|
reissuable: true, // false = lock supply permanently
|
|
136
|
-
newIpfs: 'Qm...'
|
|
164
|
+
newIpfs: 'Qm...' // Update IPFS (optional)
|
|
137
165
|
});
|
|
138
166
|
```
|
|
139
167
|
|
|
168
|
+
> **Note**: `units` cannot be passed to `reissueAsset` — the chain inherits
|
|
169
|
+
> the asset's existing precision (use `new_units` in the raw output if you
|
|
170
|
+
> ever need to change it, but this library doesn't expose that today).
|
|
171
|
+
|
|
140
172
|
### Create DEPIN Asset
|
|
141
173
|
|
|
142
174
|
```javascript
|
|
@@ -151,6 +183,28 @@ const result = await assets.createDepinAsset({
|
|
|
151
183
|
> **Note**: DEPIN assets always use `units = 0`. Recipient and change destinations
|
|
152
184
|
> can be either legacy or AuthScript, as long as they belong to the same chain family.
|
|
153
185
|
|
|
186
|
+
### Transfer Asset
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
// Works for any asset type (regular, sub, restricted, DePIN).
|
|
190
|
+
const result = await assets.transferAsset({
|
|
191
|
+
assetName: 'MYTOKEN',
|
|
192
|
+
recipients: [
|
|
193
|
+
{ address: 'nM...', amount: 5 }, // amount in display units
|
|
194
|
+
{ address: 'nQ...', amount: 2.5 }
|
|
195
|
+
]
|
|
196
|
+
// changeAddress is optional; defaults to the configured change address.
|
|
197
|
+
// Asset change and the network fee are handled automatically.
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
> **DePIN (`&`) note**: DePIN assets are soulbound — the transfer is only valid
|
|
202
|
+
> if it is authorized by the owner. `transferAsset` handles this automatically:
|
|
203
|
+
> it spends the asset's owner token (`&NAME!`) and returns it to the change
|
|
204
|
+
> address, so authority stays with the sender. You must hold the owner token, or
|
|
205
|
+
> the call throws `OwnerTokenNotFoundError`. Transferring ownership itself (handing
|
|
206
|
+
> the owner token to the recipient) is not done here.
|
|
207
|
+
|
|
154
208
|
### Create UNIQUE Assets (NFTs)
|
|
155
209
|
|
|
156
210
|
```javascript
|
|
@@ -465,7 +519,7 @@ The library automatically validates that the owner token is returned in each ope
|
|
|
465
519
|
| Reissue ROOT/SUB | 200 |
|
|
466
520
|
| Reissue DEPIN | 200 |
|
|
467
521
|
| Reissue RESTRICTED | 200 |
|
|
468
|
-
| Tag/Untag address | 0
|
|
522
|
+
| Tag/Untag address | 0 (network fee only; spends 1 unit of the qualifier per address) |
|
|
469
523
|
| Freeze/Unfreeze address | 0 (network fee only) |
|
|
470
524
|
| Freeze/Unfreeze global | 0 (network fee only) |
|
|
471
525
|
|
|
@@ -476,7 +530,7 @@ The library automatically validates that the owner token is returned in each ope
|
|
|
476
530
|
The library validates client-side:
|
|
477
531
|
|
|
478
532
|
✅ Asset names (format, length, allowed characters)
|
|
479
|
-
✅ Amounts (not exceeding max supply of 21 billion)
|
|
533
|
+
✅ Amounts (not exceeding max supply of 21 billion display tokens)
|
|
480
534
|
✅ Decimals (0-8)
|
|
481
535
|
✅ IPFS hashes (valid format)
|
|
482
536
|
✅ Verifier strings (boolean logic syntax)
|
|
@@ -485,6 +539,11 @@ The library validates client-side:
|
|
|
485
539
|
✅ Owner tokens returned (prevents loss)
|
|
486
540
|
✅ Address prefixes by network
|
|
487
541
|
|
|
542
|
+
The daemon also enforces server-side that quantities respect the asset's
|
|
543
|
+
precision (`CheckAmountWithUnits` — see [Quantities and asset units](#quantities-and-asset-units)),
|
|
544
|
+
so e.g. trying to issue `0.1` of a `units=0` asset is rejected with
|
|
545
|
+
`min-qty-not-multiple-of-units` regardless of what the client sent.
|
|
546
|
+
|
|
488
547
|
## Network Configuration
|
|
489
548
|
|
|
490
549
|
```javascript
|
|
@@ -4606,24 +4606,47 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
4606
4606
|
}
|
|
4607
4607
|
|
|
4608
4608
|
/**
|
|
4609
|
-
*
|
|
4610
|
-
*
|
|
4611
|
-
*
|
|
4609
|
+
* Build the JSON `asset_quantity` value for a `createrawtransaction`
|
|
4610
|
+
* output (issue / reissue / tag change_quantity / etc.).
|
|
4611
|
+
*
|
|
4612
|
+
* The chain parses this field with `AmountFromValue()` (Bitcoin-style
|
|
4613
|
+
* decimal-XNA → 10^8 sats), then validates that the resulting CAmount
|
|
4614
|
+
* is a multiple of `10^(8 - units)` via `CheckAmountWithUnits`
|
|
4615
|
+
* (assets.cpp). So:
|
|
4616
|
+
*
|
|
4617
|
+
* - The JSON value MUST be the user-facing display amount
|
|
4618
|
+
* (e.g. "1" for one token, "1.5" for one and a half tokens).
|
|
4619
|
+
* - The lib must NOT pre-multiply by 10^8 or 10^units; the daemon
|
|
4620
|
+
* does the 10^8 scaling itself, and any extra factor here lands
|
|
4621
|
+
* duplicated and inflates the minted supply (or trips the
|
|
4622
|
+
* ParseFixedPoint `exponent >= 18` cap → "Invalid amount (3)").
|
|
4623
|
+
*
|
|
4624
|
+
* History: pre-1.2.2 multiplied by 10^units (correct only for units=0
|
|
4625
|
+
* assets, inflated everything else by 10^units). v1.2.2 changed to
|
|
4626
|
+
* always 10^8 (correct only for units=8, inflated everything else by
|
|
4627
|
+
* 10^8 — e.g. reissuing 1 token of a units=0 asset minted 100,000,000).
|
|
4628
|
+
* The right answer is to send the value untouched.
|
|
4629
|
+
*
|
|
4630
|
+
* The `units` parameter is kept for API compatibility but is unused.
|
|
4612
4631
|
*
|
|
4613
4632
|
* @param {number} amount - User-facing asset amount
|
|
4614
|
-
* @param {number} units - Asset decimal places (kept for API
|
|
4615
|
-
* @returns {number}
|
|
4633
|
+
* @param {number} units - Asset decimal places (unused; kept for API)
|
|
4634
|
+
* @returns {number} The user-facing amount, ready for the JSON output
|
|
4616
4635
|
*/
|
|
4617
4636
|
toSatoshis(amount, units) {
|
|
4618
|
-
return
|
|
4637
|
+
return amount;
|
|
4619
4638
|
}
|
|
4620
4639
|
|
|
4621
4640
|
/**
|
|
4622
|
-
* Convert
|
|
4641
|
+
* Convert a chain-side asset balance / UTXO satoshis value back to a
|
|
4642
|
+
* user-facing amount. The chain consistently encodes asset balances
|
|
4643
|
+
* in 10^8 sats (because everything goes through AmountFromValue on
|
|
4644
|
+
* the way in), so the divisor is always 10^8 — independent of the
|
|
4645
|
+
* asset's `units`.
|
|
4623
4646
|
*
|
|
4624
|
-
* @param {number} satoshis -
|
|
4625
|
-
* @param {number} units - Asset decimal places (kept for API
|
|
4626
|
-
* @returns {number}
|
|
4647
|
+
* @param {number} satoshis - Chain value in 10^8 sats
|
|
4648
|
+
* @param {number} units - Asset decimal places (unused; kept for API)
|
|
4649
|
+
* @returns {number} User-facing asset amount
|
|
4627
4650
|
*/
|
|
4628
4651
|
fromSatoshis(satoshis, units) {
|
|
4629
4652
|
return satoshis / 100000000;
|
|
@@ -5746,6 +5769,274 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
5746
5769
|
return ReissueBuilder_1;
|
|
5747
5770
|
}
|
|
5748
5771
|
|
|
5772
|
+
/**
|
|
5773
|
+
* Transfer Builder
|
|
5774
|
+
* Builds transactions that transfer an existing asset to one or more recipients.
|
|
5775
|
+
*
|
|
5776
|
+
* Works for any asset type (regular, sub, restricted, DePIN). The only
|
|
5777
|
+
* type-specific rule lives in Neurai consensus for DePIN (`&`) assets, which are
|
|
5778
|
+
* soulbound: a DePIN transfer is only valid if the same transaction
|
|
5779
|
+
* 1. SPENDS the asset's owner token `&NAME!` as an input, and
|
|
5780
|
+
* 2. re-creates (transfers) that owner token in an output.
|
|
5781
|
+
* See Neurai-DePIN/src/consensus/tx_verify.cpp (bad-txns-depin-transfer-not-by-owner).
|
|
5782
|
+
* For non-DePIN assets no owner token is required for a plain transfer.
|
|
5783
|
+
*
|
|
5784
|
+
* Owner-token destination: the owner token is returned to the sender's change
|
|
5785
|
+
* address — the asset moves to the recipient but authority stays with the owner
|
|
5786
|
+
* (soulbound semantics). Transferring ownership itself is out of scope here.
|
|
5787
|
+
*
|
|
5788
|
+
* This builder mirrors ReissueBuilder (which also spends + returns an owner
|
|
5789
|
+
* token) but, since a transfer has no reissue entry, it adds the owner-token
|
|
5790
|
+
* return output explicitly via OwnerTokenManager.
|
|
5791
|
+
*/
|
|
5792
|
+
|
|
5793
|
+
var TransferBuilder_1;
|
|
5794
|
+
var hasRequiredTransferBuilder;
|
|
5795
|
+
|
|
5796
|
+
function requireTransferBuilder () {
|
|
5797
|
+
if (hasRequiredTransferBuilder) return TransferBuilder_1;
|
|
5798
|
+
hasRequiredTransferBuilder = 1;
|
|
5799
|
+
const BaseAssetTransactionBuilder = requireBaseAssetTransactionBuilder();
|
|
5800
|
+
const { OutputFormatter, AssetNameParser } = requireUtils();
|
|
5801
|
+
const { OwnerTokenNotFoundError } = requireErrors();
|
|
5802
|
+
|
|
5803
|
+
class TransferBuilder extends BaseAssetTransactionBuilder {
|
|
5804
|
+
/**
|
|
5805
|
+
* Validate transfer parameters
|
|
5806
|
+
* @param {object} params - Transfer parameters
|
|
5807
|
+
* @throws {Error} If validation fails
|
|
5808
|
+
*/
|
|
5809
|
+
validateParams(params) {
|
|
5810
|
+
if (!params.assetName) {
|
|
5811
|
+
throw new Error('assetName is required');
|
|
5812
|
+
}
|
|
5813
|
+
|
|
5814
|
+
if (!Array.isArray(params.recipients) || params.recipients.length === 0) {
|
|
5815
|
+
throw new Error('recipients is required (non-empty array of { address, amount })');
|
|
5816
|
+
}
|
|
5817
|
+
|
|
5818
|
+
params.recipients.forEach((recipient, index) => {
|
|
5819
|
+
if (!recipient || !recipient.address) {
|
|
5820
|
+
throw new Error(`recipients[${index}].address is required`);
|
|
5821
|
+
}
|
|
5822
|
+
if (recipient.amount === undefined || recipient.amount === null) {
|
|
5823
|
+
throw new Error(`recipients[${index}].amount is required`);
|
|
5824
|
+
}
|
|
5825
|
+
if (recipient.amount <= 0) {
|
|
5826
|
+
throw new Error(`recipients[${index}].amount must be greater than 0`);
|
|
5827
|
+
}
|
|
5828
|
+
});
|
|
5829
|
+
|
|
5830
|
+
return true;
|
|
5831
|
+
}
|
|
5832
|
+
|
|
5833
|
+
/**
|
|
5834
|
+
* Build transfer transaction
|
|
5835
|
+
* @returns {Promise<object>} Transaction result
|
|
5836
|
+
*/
|
|
5837
|
+
async build() {
|
|
5838
|
+
// 1. Validate parameters
|
|
5839
|
+
this.validateParams(this.params);
|
|
5840
|
+
|
|
5841
|
+
const { assetName, recipients } = this.params;
|
|
5842
|
+
|
|
5843
|
+
// Total amount to send, in user-facing asset units (NOT raw 10^8 sats).
|
|
5844
|
+
// selectAssetUTXOs / the createrawtransaction transfer output both expect
|
|
5845
|
+
// display units and scale by 10^8 themselves — pre-multiplying would
|
|
5846
|
+
// double-scale (see UTXOSelector.selectAssetUTXOs / BaseBuilder.toSatoshis).
|
|
5847
|
+
const totalAssetUnits = recipients.reduce((sum, r) => sum + r.amount, 0);
|
|
5848
|
+
|
|
5849
|
+
// 2. Addresses
|
|
5850
|
+
const addresses = await this._getAddresses();
|
|
5851
|
+
const changeAddress = await this.getChangeAddress();
|
|
5852
|
+
|
|
5853
|
+
// 3. DePIN detection + owner token lookup (soulbound rule)
|
|
5854
|
+
const isDepin = AssetNameParser.isDepin(assetName);
|
|
5855
|
+
let ownerTokenName = null;
|
|
5856
|
+
let ownerTokenUTXO = null;
|
|
5857
|
+
if (isDepin) {
|
|
5858
|
+
ownerTokenName = AssetNameParser.getOwnerTokenName(assetName); // &NAME -> &NAME!
|
|
5859
|
+
try {
|
|
5860
|
+
ownerTokenUTXO = await this.ownerTokenManager.findOwnerTokenUTXO(
|
|
5861
|
+
ownerTokenName,
|
|
5862
|
+
addresses
|
|
5863
|
+
);
|
|
5864
|
+
} catch (error) {
|
|
5865
|
+
if (error instanceof OwnerTokenNotFoundError) {
|
|
5866
|
+
throw new OwnerTokenNotFoundError(
|
|
5867
|
+
`You must own the asset's owner token (${ownerTokenName}) to transfer ` +
|
|
5868
|
+
`this DePIN asset. DePIN assets are soulbound: the transfer must be ` +
|
|
5869
|
+
`authorized by the owner.`,
|
|
5870
|
+
ownerTokenName
|
|
5871
|
+
);
|
|
5872
|
+
}
|
|
5873
|
+
throw error;
|
|
5874
|
+
}
|
|
5875
|
+
}
|
|
5876
|
+
|
|
5877
|
+
// 4. Output addresses used only for the fee (vsize) estimate. Include every
|
|
5878
|
+
// potential output so the fee is never under-estimated.
|
|
5879
|
+
const outputAddresses = [
|
|
5880
|
+
changeAddress, // XNA change
|
|
5881
|
+
...recipients.map(r => r.address), // one transfer per recipient
|
|
5882
|
+
changeAddress, // asset change (harmless over-count if absent)
|
|
5883
|
+
...(isDepin ? [changeAddress] : []), // owner token return
|
|
5884
|
+
];
|
|
5885
|
+
|
|
5886
|
+
// 5. First (rough) fee estimate, then select asset + XNA UTXOs.
|
|
5887
|
+
const estimatedFee = await this.estimateFee(isDepin ? 3 : 2, outputAddresses);
|
|
5888
|
+
const utxoSelection = await this.selectUTXOs(estimatedFee, assetName, totalAssetUnits);
|
|
5889
|
+
const assetUTXOs = utxoSelection.assetUTXOs;
|
|
5890
|
+
const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
|
|
5891
|
+
let totalXNAInput = utxoSelection.totalXNA;
|
|
5892
|
+
|
|
5893
|
+
// Asset change computed in raw 10^8-sats to avoid float drift, then back to units.
|
|
5894
|
+
const assetInputRawSats = assetUTXOs.reduce((sum, u) => sum + u.satoshis, 0);
|
|
5895
|
+
const totalAssetRawSats = Math.round(totalAssetUnits * 100000000);
|
|
5896
|
+
const assetChangeRawSats = assetInputRawSats - totalAssetRawSats;
|
|
5897
|
+
const assetChangeUnits = assetChangeRawSats / 100000000;
|
|
5898
|
+
|
|
5899
|
+
// 6. Recompute the fee with the real inputs (PQ-aware), including the owner
|
|
5900
|
+
// token when DePIN, then top up XNA if the rough estimate fell short.
|
|
5901
|
+
const actualFeeInputs = [
|
|
5902
|
+
...baseCurrencyUTXOs,
|
|
5903
|
+
...assetUTXOs,
|
|
5904
|
+
...(isDepin ? [ownerTokenUTXO] : []),
|
|
5905
|
+
];
|
|
5906
|
+
const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
|
|
5907
|
+
|
|
5908
|
+
if (totalXNAInput < actualFee) {
|
|
5909
|
+
const additionalNeeded = actualFee - totalXNAInput + 0.001;
|
|
5910
|
+
const additionalSelection = await this.selectUTXOs(additionalNeeded, null, 0);
|
|
5911
|
+
baseCurrencyUTXOs.push(...additionalSelection.xnaUTXOs);
|
|
5912
|
+
totalXNAInput += additionalSelection.totalXNA;
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
// 7. XNA change (no burn for a transfer)
|
|
5916
|
+
const finalXNAInput = baseCurrencyUTXOs.reduce(
|
|
5917
|
+
(sum, utxo) => sum + utxo.satoshis / 100000000,
|
|
5918
|
+
0
|
|
5919
|
+
);
|
|
5920
|
+
const xnaChange = finalXNAInput - actualFee;
|
|
5921
|
+
|
|
5922
|
+
// 8. Build inputs: asset UTXOs + [owner token] + XNA UTXOs
|
|
5923
|
+
const inputs = [];
|
|
5924
|
+
|
|
5925
|
+
assetUTXOs.forEach(utxo => {
|
|
5926
|
+
inputs.push({
|
|
5927
|
+
txid: utxo.txid,
|
|
5928
|
+
vout: utxo.outputIndex,
|
|
5929
|
+
address: utxo.address,
|
|
5930
|
+
assetName: utxo.assetName,
|
|
5931
|
+
satoshis: utxo.satoshis,
|
|
5932
|
+
});
|
|
5933
|
+
});
|
|
5934
|
+
|
|
5935
|
+
if (isDepin) {
|
|
5936
|
+
inputs.push({
|
|
5937
|
+
txid: ownerTokenUTXO.txid,
|
|
5938
|
+
vout: ownerTokenUTXO.outputIndex,
|
|
5939
|
+
address: ownerTokenUTXO.address,
|
|
5940
|
+
assetName: ownerTokenUTXO.assetName,
|
|
5941
|
+
satoshis: ownerTokenUTXO.satoshis,
|
|
5942
|
+
});
|
|
5943
|
+
}
|
|
5944
|
+
|
|
5945
|
+
baseCurrencyUTXOs.forEach(utxo => {
|
|
5946
|
+
inputs.push({
|
|
5947
|
+
txid: utxo.txid,
|
|
5948
|
+
vout: utxo.outputIndex,
|
|
5949
|
+
address: utxo.address,
|
|
5950
|
+
satoshis: utxo.satoshis,
|
|
5951
|
+
});
|
|
5952
|
+
});
|
|
5953
|
+
|
|
5954
|
+
// 9. Build outputs (unordered — outputOrderer enforces protocol order)
|
|
5955
|
+
const outputs = [];
|
|
5956
|
+
|
|
5957
|
+
// XNA change
|
|
5958
|
+
if (xnaChange > 0.00000001) {
|
|
5959
|
+
outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
|
|
5960
|
+
}
|
|
5961
|
+
|
|
5962
|
+
// One transfer per recipient (display units; the daemon scales by 10^8)
|
|
5963
|
+
recipients.forEach(r => {
|
|
5964
|
+
outputs.push({ [r.address]: OutputFormatter.formatTransferOutput(assetName, r.amount) });
|
|
5965
|
+
});
|
|
5966
|
+
|
|
5967
|
+
// Asset change back to the sender
|
|
5968
|
+
if (assetChangeRawSats > 0) {
|
|
5969
|
+
outputs.push({
|
|
5970
|
+
[changeAddress]: OutputFormatter.formatTransferOutput(assetName, assetChangeUnits),
|
|
5971
|
+
});
|
|
5972
|
+
}
|
|
5973
|
+
|
|
5974
|
+
// DePIN: return the owner token (required so the tx contains a transfer of
|
|
5975
|
+
// &NAME! — satisfies the consensus `transfersOwnerToken` check).
|
|
5976
|
+
if (isDepin) {
|
|
5977
|
+
outputs.push(
|
|
5978
|
+
this.ownerTokenManager.createOwnerTokenReturnOutput(ownerTokenName, changeAddress)
|
|
5979
|
+
);
|
|
5980
|
+
}
|
|
5981
|
+
|
|
5982
|
+
// 10. Order outputs (protocol requirement)
|
|
5983
|
+
const orderedOutputs = this.outputOrderer.order(outputs);
|
|
5984
|
+
|
|
5985
|
+
// 11. Create raw transaction
|
|
5986
|
+
const rawTx = await this.buildRawTransaction(inputs, orderedOutputs);
|
|
5987
|
+
|
|
5988
|
+
// 12. Format and return result
|
|
5989
|
+
const allUTXOs = [
|
|
5990
|
+
...assetUTXOs,
|
|
5991
|
+
...(isDepin ? [ownerTokenUTXO] : []),
|
|
5992
|
+
...baseCurrencyUTXOs,
|
|
5993
|
+
];
|
|
5994
|
+
const xnaChangeOut = xnaChange > 0.00000001 ? parseFloat(xnaChange.toFixed(8)) : null;
|
|
5995
|
+
|
|
5996
|
+
return this.formatResult(
|
|
5997
|
+
rawTx,
|
|
5998
|
+
allUTXOs,
|
|
5999
|
+
inputs,
|
|
6000
|
+
orderedOutputs,
|
|
6001
|
+
actualFee,
|
|
6002
|
+
0, // burnAmount — transfers don't burn
|
|
6003
|
+
{
|
|
6004
|
+
assetName,
|
|
6005
|
+
recipients: recipients.map(r => ({ address: r.address, amount: r.amount })),
|
|
6006
|
+
assetChange: assetChangeRawSats > 0 ? assetChangeUnits : 0,
|
|
6007
|
+
isDepin,
|
|
6008
|
+
ownerTokenUsed: isDepin ? ownerTokenName : null,
|
|
6009
|
+
operationType: 'TRANSFER',
|
|
6010
|
+
localRawBuild: this.buildLocalRawBuild(
|
|
6011
|
+
'TRANSFER',
|
|
6012
|
+
inputs,
|
|
6013
|
+
null, // no burn
|
|
6014
|
+
changeAddress,
|
|
6015
|
+
xnaChangeOut,
|
|
6016
|
+
{
|
|
6017
|
+
assetName,
|
|
6018
|
+
transfers: recipients.map(r => ({
|
|
6019
|
+
address: r.address,
|
|
6020
|
+
assetName,
|
|
6021
|
+
amount: r.amount,
|
|
6022
|
+
})),
|
|
6023
|
+
assetChange: assetChangeRawSats > 0
|
|
6024
|
+
? { address: changeAddress, assetName, amount: assetChangeUnits }
|
|
6025
|
+
: null,
|
|
6026
|
+
ownerReturn: isDepin
|
|
6027
|
+
? { address: changeAddress, assetName: ownerTokenName, amount: 1 }
|
|
6028
|
+
: null,
|
|
6029
|
+
}
|
|
6030
|
+
),
|
|
6031
|
+
}
|
|
6032
|
+
);
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
|
|
6036
|
+
TransferBuilder_1 = TransferBuilder;
|
|
6037
|
+
return TransferBuilder_1;
|
|
6038
|
+
}
|
|
6039
|
+
|
|
5749
6040
|
/**
|
|
5750
6041
|
* Issue Unique Builder
|
|
5751
6042
|
* Builds transactions for creating UNIQUE assets (NFTs)
|
|
@@ -7382,6 +7673,7 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
7382
7673
|
const IssueSubBuilder = requireIssueSubBuilder();
|
|
7383
7674
|
const IssueDepinBuilder = requireIssueDepinBuilder();
|
|
7384
7675
|
const ReissueBuilder = requireReissueBuilder();
|
|
7676
|
+
const TransferBuilder = requireTransferBuilder();
|
|
7385
7677
|
|
|
7386
7678
|
// Advanced Builders
|
|
7387
7679
|
const IssueUniqueBuilder = requireIssueUniqueBuilder();
|
|
@@ -7400,6 +7692,7 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
7400
7692
|
IssueSubBuilder,
|
|
7401
7693
|
IssueDepinBuilder,
|
|
7402
7694
|
ReissueBuilder,
|
|
7695
|
+
TransferBuilder,
|
|
7403
7696
|
|
|
7404
7697
|
// Advanced Builders
|
|
7405
7698
|
IssueUniqueBuilder,
|
|
@@ -7452,7 +7745,8 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
7452
7745
|
ReissueBuilder,
|
|
7453
7746
|
ReissueRestrictedBuilder,
|
|
7454
7747
|
TagAddressBuilder,
|
|
7455
|
-
FreezeAddressBuilder
|
|
7748
|
+
FreezeAddressBuilder,
|
|
7749
|
+
TransferBuilder
|
|
7456
7750
|
} = requireBuilders();
|
|
7457
7751
|
|
|
7458
7752
|
class NeuraiAssets {
|
|
@@ -7569,6 +7863,32 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
7569
7863
|
return await builder.build();
|
|
7570
7864
|
}
|
|
7571
7865
|
|
|
7866
|
+
// ========================================
|
|
7867
|
+
// TRANSFER OPERATIONS
|
|
7868
|
+
// ========================================
|
|
7869
|
+
|
|
7870
|
+
/**
|
|
7871
|
+
* Transfer an existing asset to one or more recipients.
|
|
7872
|
+
*
|
|
7873
|
+
* Works for any asset type. DePIN (`&`) assets are soulbound: this method
|
|
7874
|
+
* automatically spends and returns the asset's owner token (`&NAME!`) so the
|
|
7875
|
+
* transfer satisfies Neurai consensus (bad-txns-depin-transfer-not-by-owner).
|
|
7876
|
+
* The owner token is returned to the change address (authority stays with the
|
|
7877
|
+
* sender). For non-DePIN assets no owner token is involved.
|
|
7878
|
+
*
|
|
7879
|
+
* @param {object} params - Transfer parameters
|
|
7880
|
+
* @param {string} params.assetName - Asset to transfer (e.g. 'TOKEN', '$SEC', '&DEVICE')
|
|
7881
|
+
* @param {Array<object>} params.recipients - Recipients
|
|
7882
|
+
* @param {string} params.recipients[].address - Destination address
|
|
7883
|
+
* @param {number} params.recipients[].amount - Amount in asset display units (> 0)
|
|
7884
|
+
* @param {string} [params.changeAddress] - Override change/owner-return address
|
|
7885
|
+
* @returns {Promise<object>} Transaction data
|
|
7886
|
+
*/
|
|
7887
|
+
async transferAsset(params) {
|
|
7888
|
+
const builder = new TransferBuilder(this.rpc, this._buildParams(params));
|
|
7889
|
+
return await builder.build();
|
|
7890
|
+
}
|
|
7891
|
+
|
|
7572
7892
|
// ========================================
|
|
7573
7893
|
// UNIQUE ASSET (NFT) OPERATIONS
|
|
7574
7894
|
// ========================================
|