@neuraiproject/neurai-assets 1.7.2 → 1.8.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 +42 -10
- package/dist/NeuraiAssets.global.js +158 -20
- package/dist/NeuraiAssets.global.js.map +1 -1
- package/dist/browser.js +158 -20
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +158 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +158 -20
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -37,6 +37,28 @@ Complete asset management library for Neurai blockchain. Supports creation, reis
|
|
|
37
37
|
> (full name, owner `!` included: 31 mainnet / 121 testnet-regtest, validated
|
|
38
38
|
> against a regtest node); peer `neurai-rpc ^0.6.0`.
|
|
39
39
|
|
|
40
|
+
## 1.8.1: Issue quantities given as decimal strings
|
|
41
|
+
|
|
42
|
+
`createRootAsset`, `createSubAsset`, `createDepinAsset` and
|
|
43
|
+
`createRestrictedAsset` failed with `Invalid parameter, missing asset data for
|
|
44
|
+
key: asset_quantity` when `quantity` was a decimal string such as `"1000"`.
|
|
45
|
+
The node reads `asset_quantity` and `change_quantity` only from JSON numbers,
|
|
46
|
+
and the value reached `createrawtransaction` quoted. It is now converted on the
|
|
47
|
+
copy sent to the node: a JavaScript number when that number writes back the
|
|
48
|
+
same decimal, otherwise an exact decimal token written as a bare JSON number by
|
|
49
|
+
`@neuraiproject/neurai-rpc` (same shape as its `rpcNumber()`). The outputs in
|
|
50
|
+
the build result keep the value as it was given. Numeric quantities are
|
|
51
|
+
unchanged.
|
|
52
|
+
|
|
53
|
+
## 1.8.0: Reset testnet marker policy
|
|
54
|
+
|
|
55
|
+
Testnet builders require the node's `getblockchaininfo.asset_marker` value
|
|
56
|
+
(or an explicit override) and reject a missing marker instead of silently
|
|
57
|
+
using `rvn` after the activation of `xna`. This release requires
|
|
58
|
+
`@neuraiproject/neurai-rpc` `^0.7.0` as a peer and
|
|
59
|
+
`@neuraiproject/neurai-create-transaction` `^0.9.2`. Development tests use
|
|
60
|
+
`@neuraiproject/neurai-sign-transaction` `^3.0.3`.
|
|
61
|
+
|
|
40
62
|
## Features
|
|
41
63
|
|
|
42
64
|
- ✅ **Non-custodial**: Library builds unsigned transactions, your wallet signs them
|
|
@@ -79,6 +101,14 @@ either silently inflates the minted supply (`× 10⁸ → 100,000,000` tokens
|
|
|
79
101
|
where the user asked for 1) or trips the daemon's
|
|
80
102
|
`ParseFixedPoint` cap with `Invalid amount (3): …`.
|
|
81
103
|
|
|
104
|
+
A quantity can be a number or a plain decimal string (`"1000.25"`). A string
|
|
105
|
+
keeps every digit; it is sent to the node as a JSON number, which is the only
|
|
106
|
+
form `createrawtransaction` accepts for `asset_quantity`. A decimal that no
|
|
107
|
+
JavaScript number carries exactly is sent as an exact token, which requires
|
|
108
|
+
the RPC function to serialize with `@neuraiproject/neurai-rpc` (its
|
|
109
|
+
`getRPC()` does); any other serializer makes the node reject the call instead
|
|
110
|
+
of changing the amount.
|
|
111
|
+
|
|
82
112
|
This was regressed in `1.2.2`/`1.3.x` (a hardcoded `× 10⁸` was added to
|
|
83
113
|
`BaseAssetTransactionBuilder.toSatoshis`) and fixed in the version after
|
|
84
114
|
`1.3.1`. If you write a custom builder, follow the same convention: pass
|
|
@@ -786,7 +816,7 @@ The library automatically validates that the owner token is returned in each ope
|
|
|
786
816
|
|
|
787
817
|
Asset payloads open with a 3-byte marker that NIP-040 migrates from the
|
|
788
818
|
Ravencoin-inherited `rvn` to `xna` at an activation height per network
|
|
789
|
-
(testnet:
|
|
819
|
+
(reset testnet: 10; regtest: 1; mainnet: not scheduled yet).
|
|
790
820
|
|
|
791
821
|
- Transactions built **through the node** (`createrawtransaction`) need
|
|
792
822
|
nothing: the node stamps the marker itself.
|
|
@@ -797,10 +827,12 @@ Ravencoin-inherited `rvn` to `xna` at an activation height per network
|
|
|
797
827
|
`'xna'` — offline builds or tests);
|
|
798
828
|
2. otherwise the node's `getblockchaininfo.asset_marker` (node commit
|
|
799
829
|
`347362b` or later);
|
|
800
|
-
3. `'rvn'` when the node predates that field
|
|
801
|
-
|
|
830
|
+
3. On mainnet, `'rvn'` when the node predates that field. Testnet requires
|
|
831
|
+
the field or an explicit `assetMarker` override.
|
|
802
832
|
|
|
803
|
-
|
|
833
|
+
The node reports the marker for the next block. On the reset testnet it reports
|
|
834
|
+
`rvn` with tip 8 and `xna` with tip 9. The builder does not infer a marker
|
|
835
|
+
from the network name.
|
|
804
836
|
|
|
805
837
|
### Failure policy (`assetMarkerPolicy`, 1.5.0+)
|
|
806
838
|
|
|
@@ -811,16 +843,16 @@ not answer at all is a different situation, and `assetMarkerPolicy` decides it:
|
|
|
811
843
|
const assets = new NeuraiAssets(rpc, {
|
|
812
844
|
network: 'xna-test',
|
|
813
845
|
addresses: [...],
|
|
814
|
-
assetMarkerPolicy: 'strict' // default
|
|
846
|
+
assetMarkerPolicy: 'strict' // default on testnet
|
|
815
847
|
});
|
|
816
848
|
```
|
|
817
849
|
|
|
818
850
|
| Policy | `getblockchaininfo` fails | Field absent/null | Unknown value |
|
|
819
851
|
| --- | --- | --- | --- |
|
|
820
|
-
| `legacy-fallback` (default
|
|
821
|
-
| `strict` | **throws** |
|
|
852
|
+
| `legacy-fallback` (mainnet default) | resolves `'rvn'` | mainnet: `'rvn'`; testnet: throws | throws |
|
|
853
|
+
| `strict` (testnet default) | **throws** | mainnet: `'rvn'`; testnet: throws | throws |
|
|
822
854
|
|
|
823
|
-
|
|
855
|
+
The reset testnet defaults to `strict`. Guessing `'rvn'`
|
|
824
856
|
there builds a transaction the node rejects with
|
|
825
857
|
`bad-txns-legacy-asset-marker-after-nip040`, so "the node did not answer" must
|
|
826
858
|
not silently become "the node said rvn". The rejection propagates out of the
|
|
@@ -893,8 +925,8 @@ Address types (every family is valid with the network label of its chain):
|
|
|
893
925
|
|
|
894
926
|
The old `nq1p...` / `tnq1p...` encoding of generic AuthScript v1 is rejected, like
|
|
895
927
|
in the node (same scriptPubKey: regenerate it as `nc1p...` / `tnc1p...`). The node
|
|
896
|
-
only protects a witness family where it is active
|
|
897
|
-
|
|
928
|
+
only protects a witness family where it is active. All three families activate
|
|
929
|
+
on the reset testnet from block 10 and on regtest from block 1.
|
|
898
930
|
|
|
899
931
|
The library accepts these network names. A label only selects the chain: the
|
|
900
932
|
address type always comes from the address itself.
|
|
@@ -1931,8 +1931,8 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
1931
1931
|
* Every transfer / new / owner / reissue payload opens with a 3-byte marker
|
|
1932
1932
|
* followed by the type byte. The marker is consensus: blocks below the NIP-040
|
|
1933
1933
|
* activation height of a network only accept `rvn` on new asset outputs and
|
|
1934
|
-
* blocks at or above it only accept `xna` (mainnet: not scheduled; testnet:
|
|
1935
|
-
*
|
|
1934
|
+
* blocks at or above it only accept `xna` (mainnet: not scheduled; reset testnet:
|
|
1935
|
+
* 10; regtest: 1). This library does NOT know chain state and never
|
|
1936
1936
|
* infers the marker from a network or an address: the caller passes the
|
|
1937
1937
|
* value reported by the node for the next block
|
|
1938
1938
|
* (`getblockchaininfo.asset_marker`, node commit 347362b) — or, when building
|
|
@@ -8299,6 +8299,140 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
8299
8299
|
return validators$1;
|
|
8300
8300
|
}
|
|
8301
8301
|
|
|
8302
|
+
/**
|
|
8303
|
+
* Asset amounts as `createrawtransaction` must receive them.
|
|
8304
|
+
*
|
|
8305
|
+
* The node reads `asset_quantity` (issue, issue_restricted, issue_qualifier,
|
|
8306
|
+
* reissue, reissue_restricted) and `change_quantity` (qualifier and tag
|
|
8307
|
+
* operations) only from JSON numbers: a quoted value fails `isNum()` and the
|
|
8308
|
+
* call is rejected with "missing asset data for key: asset_quantity". The
|
|
8309
|
+
* builders accept these amounts as decimal strings, which keep every digit the
|
|
8310
|
+
* caller wrote, so they are turned into JSON numbers here, on the copy of the
|
|
8311
|
+
* outputs sent to the node. The outputs returned to the caller keep the value
|
|
8312
|
+
* as it was given.
|
|
8313
|
+
*/
|
|
8314
|
+
|
|
8315
|
+
var rpcAmount;
|
|
8316
|
+
var hasRequiredRpcAmount;
|
|
8317
|
+
|
|
8318
|
+
function requireRpcAmount () {
|
|
8319
|
+
if (hasRequiredRpcAmount) return rpcAmount;
|
|
8320
|
+
hasRequiredRpcAmount = 1;
|
|
8321
|
+
const {
|
|
8322
|
+
expandScientificNotation,
|
|
8323
|
+
formatRawAsDecimal,
|
|
8324
|
+
normalizeDecimalText,
|
|
8325
|
+
scaleDecimalText
|
|
8326
|
+
} = requireAssetAmount();
|
|
8327
|
+
|
|
8328
|
+
/** Fields the node parses with `isNum()` inside an asset operation object. */
|
|
8329
|
+
const NUMERIC_OPERATION_FIELDS = ['asset_quantity', 'change_quantity'];
|
|
8330
|
+
|
|
8331
|
+
/** The largest magnitude whose 8-decimal values a double still names exactly. */
|
|
8332
|
+
const MAX_EXACT_FRACTIONAL = Number.MAX_SAFE_INTEGER / 1e8;
|
|
8333
|
+
|
|
8334
|
+
/**
|
|
8335
|
+
* An exact decimal serialized as a bare JSON number token.
|
|
8336
|
+
*
|
|
8337
|
+
* Same shape as `rpcNumber()` from @neuraiproject/neurai-rpc: its
|
|
8338
|
+
* `stringifyRpcJson` (lossless-json) writes any object flagged
|
|
8339
|
+
* `isLosslessNumber` through `toString()`. It is defined here instead of
|
|
8340
|
+
* imported because the bundles inline every dependency and neurai-rpc carries
|
|
8341
|
+
* its whole RPC reference. With another serializer it stays an object, which
|
|
8342
|
+
* the node rejects: it can never become a different amount.
|
|
8343
|
+
*/
|
|
8344
|
+
class RpcDecimal {
|
|
8345
|
+
/**
|
|
8346
|
+
* @param {string} text - Plain decimal text
|
|
8347
|
+
*/
|
|
8348
|
+
constructor(text) {
|
|
8349
|
+
this.isLosslessNumber = true;
|
|
8350
|
+
this.value = text;
|
|
8351
|
+
}
|
|
8352
|
+
|
|
8353
|
+
toString() {
|
|
8354
|
+
return this.value;
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
|
|
8358
|
+
/**
|
|
8359
|
+
* Convert an amount into a JSON-number value for `createrawtransaction`.
|
|
8360
|
+
*
|
|
8361
|
+
* Numbers pass through, as before. A decimal string becomes a JavaScript
|
|
8362
|
+
* number when that number writes back the same decimal (every JSON serializer
|
|
8363
|
+
* handles it), and an {@link RpcDecimal} otherwise, so no digit is rounded.
|
|
8364
|
+
*
|
|
8365
|
+
* @param {string|number} value - Display amount
|
|
8366
|
+
* @param {string} [label] - Field name, for error messages
|
|
8367
|
+
* @returns {number|RpcDecimal} JSON-number value
|
|
8368
|
+
* @throws {InvalidAmountError} If a string is not an exact 8-decimal amount
|
|
8369
|
+
*/
|
|
8370
|
+
function toRpcAmount(value, label = 'amount') {
|
|
8371
|
+
if (typeof value !== 'string') {
|
|
8372
|
+
return value;
|
|
8373
|
+
}
|
|
8374
|
+
const text = formatRawAsDecimal(scaleDecimalText(normalizeDecimalText(value, label), 8, label));
|
|
8375
|
+
const number = Number(text);
|
|
8376
|
+
const exact = Number.isFinite(number) &&
|
|
8377
|
+
expandScientificNotation(String(number)) === text &&
|
|
8378
|
+
(Number.isInteger(number) ? Number.isSafeInteger(number) : Math.abs(number) <= MAX_EXACT_FRACTIONAL);
|
|
8379
|
+
return exact ? number : new RpcDecimal(text);
|
|
8380
|
+
}
|
|
8381
|
+
|
|
8382
|
+
function isPlainObject(value) {
|
|
8383
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
8384
|
+
}
|
|
8385
|
+
|
|
8386
|
+
/**
|
|
8387
|
+
* Copy of `createrawtransaction` outputs with the numeric asset fields
|
|
8388
|
+
* converted by {@link toRpcAmount}. XNA amounts and every other field are left
|
|
8389
|
+
* as they are: the node reads those with `AmountFromValue`, which also accepts
|
|
8390
|
+
* strings.
|
|
8391
|
+
*
|
|
8392
|
+
* @param {Array<object>|object} outputs - Outputs as built (array of single-key objects, or one object)
|
|
8393
|
+
* @returns {Array<object>|object} Outputs to send, same shape
|
|
8394
|
+
*/
|
|
8395
|
+
function prepareOutputsForRpc(outputs) {
|
|
8396
|
+
const convertPayload = (payload) => {
|
|
8397
|
+
if (!isPlainObject(payload)) {
|
|
8398
|
+
return payload;
|
|
8399
|
+
}
|
|
8400
|
+
const converted = {};
|
|
8401
|
+
for (const [operation, params] of Object.entries(payload)) {
|
|
8402
|
+
if (!isPlainObject(params)) {
|
|
8403
|
+
converted[operation] = params;
|
|
8404
|
+
continue;
|
|
8405
|
+
}
|
|
8406
|
+
const copy = { ...params };
|
|
8407
|
+
for (const field of NUMERIC_OPERATION_FIELDS) {
|
|
8408
|
+
if (copy[field] !== undefined && copy[field] !== null) {
|
|
8409
|
+
copy[field] = toRpcAmount(copy[field], field);
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
converted[operation] = copy;
|
|
8413
|
+
}
|
|
8414
|
+
return converted;
|
|
8415
|
+
};
|
|
8416
|
+
|
|
8417
|
+
const convertOutput = (output) => {
|
|
8418
|
+
const converted = {};
|
|
8419
|
+
for (const [address, payload] of Object.entries(output)) {
|
|
8420
|
+
converted[address] = convertPayload(payload);
|
|
8421
|
+
}
|
|
8422
|
+
return converted;
|
|
8423
|
+
};
|
|
8424
|
+
|
|
8425
|
+
return Array.isArray(outputs) ? outputs.map(convertOutput) : convertOutput(outputs);
|
|
8426
|
+
}
|
|
8427
|
+
|
|
8428
|
+
rpcAmount = {
|
|
8429
|
+
RpcDecimal,
|
|
8430
|
+
toRpcAmount,
|
|
8431
|
+
prepareOutputsForRpc
|
|
8432
|
+
};
|
|
8433
|
+
return rpcAmount;
|
|
8434
|
+
}
|
|
8435
|
+
|
|
8302
8436
|
/**
|
|
8303
8437
|
* Base Asset Transaction Builder
|
|
8304
8438
|
* Abstract base class for all transaction builders
|
|
@@ -8335,6 +8469,7 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
8335
8469
|
toProtocolInteger,
|
|
8336
8470
|
sumProtocolIntegers
|
|
8337
8471
|
} = requireAssetAmount();
|
|
8472
|
+
const { prepareOutputsForRpc } = requireRpcAmount();
|
|
8338
8473
|
|
|
8339
8474
|
/** Outputs below this are dust and are dropped instead of created. */
|
|
8340
8475
|
const DUST_SATS = 1n;
|
|
@@ -8655,10 +8790,11 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
8655
8790
|
vout: input.vout !== undefined ? input.vout : input.outputIndex
|
|
8656
8791
|
}));
|
|
8657
8792
|
|
|
8658
|
-
// Call createrawtransaction
|
|
8793
|
+
// Call createrawtransaction. The node only accepts the asset quantities
|
|
8794
|
+
// as JSON numbers; the caller's outputs keep the values as given.
|
|
8659
8795
|
const rawTx = await this.rpc('createrawtransaction', [
|
|
8660
8796
|
formattedInputs,
|
|
8661
|
-
outputs
|
|
8797
|
+
prepareOutputsForRpc(outputs)
|
|
8662
8798
|
]);
|
|
8663
8799
|
|
|
8664
8800
|
return rawTx;
|
|
@@ -8799,11 +8935,15 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
8799
8935
|
* 10^8 — e.g. reissuing 1 token of a units=0 asset minted 100,000,000).
|
|
8800
8936
|
* The right answer is to send the value untouched.
|
|
8801
8937
|
*
|
|
8938
|
+
* A decimal string is also kept as it is: `buildRawTransaction` turns it
|
|
8939
|
+
* into the JSON number the node requires (see utils/rpcAmount.js), and the
|
|
8940
|
+
* outputs returned to the caller keep the string.
|
|
8941
|
+
*
|
|
8802
8942
|
* The `units` parameter is kept for API compatibility but is unused.
|
|
8803
8943
|
*
|
|
8804
|
-
* @param {number} amount - User-facing asset amount
|
|
8944
|
+
* @param {number|string} amount - User-facing asset amount
|
|
8805
8945
|
* @param {number} units - Asset decimal places (unused; kept for API)
|
|
8806
|
-
* @returns {number} The user-facing amount, ready for the JSON output
|
|
8946
|
+
* @returns {number|string} The user-facing amount, ready for the JSON output
|
|
8807
8947
|
*/
|
|
8808
8948
|
toSatoshis(amount, units) {
|
|
8809
8949
|
return amount;
|
|
@@ -8859,14 +8999,13 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
8859
8999
|
* 1. `params.assetMarker` (explicit caller override — offline builds,
|
|
8860
9000
|
* tests, or a node this library should not ask);
|
|
8861
9001
|
* 2. `getblockchaininfo.asset_marker` from the connected node;
|
|
8862
|
-
* 3. `'rvn'` when the
|
|
8863
|
-
* the
|
|
9002
|
+
* 3. On mainnet, `'rvn'` when the field is absent. Testnet requires
|
|
9003
|
+
* the field or an explicit override.
|
|
8864
9004
|
*
|
|
8865
9005
|
* An RPC *failure* is a different case, and `params.assetMarkerPolicy`
|
|
8866
9006
|
* decides it:
|
|
8867
|
-
* - `'legacy-fallback'` (default
|
|
8868
|
-
*
|
|
8869
|
-
* - `'strict'` propagates the failure. On a post-NIP-040 chain a build
|
|
9007
|
+
* - `'legacy-fallback'` (mainnet default) resolves `'rvn'`;
|
|
9008
|
+
* - `'strict'` (testnet default) propagates the failure. On a post-NIP-040 chain a build
|
|
8870
9009
|
* that guessed `'rvn'` produces a transaction the node rejects with
|
|
8871
9010
|
* `bad-txns-legacy-asset-marker-after-nip040`, so an online wallet
|
|
8872
9011
|
* should not treat "the node did not answer" as "the node said rvn".
|
|
@@ -8894,7 +9033,7 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
8894
9033
|
assetMarkerPolicy() {
|
|
8895
9034
|
const policy = this.params.assetMarkerPolicy;
|
|
8896
9035
|
if (policy === undefined || policy === null) {
|
|
8897
|
-
return 'legacy-fallback';
|
|
9036
|
+
return getNetworkConfig(this.network).baseNetwork === 'xna-test' ? 'strict' : 'legacy-fallback';
|
|
8898
9037
|
}
|
|
8899
9038
|
if (policy !== 'strict' && policy !== 'legacy-fallback') {
|
|
8900
9039
|
throw new Error(
|
|
@@ -8932,7 +9071,9 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
8932
9071
|
}
|
|
8933
9072
|
const marker = info ? info.asset_marker : undefined;
|
|
8934
9073
|
if (marker === undefined || marker === null) {
|
|
8935
|
-
|
|
9074
|
+
if (getNetworkConfig(this.network).baseNetwork === 'xna-test') {
|
|
9075
|
+
throw new Error('Testnet node did not report asset_marker; pass params.assetMarker for an offline build');
|
|
9076
|
+
}
|
|
8936
9077
|
return 'rvn';
|
|
8937
9078
|
}
|
|
8938
9079
|
if (marker !== 'rvn' && marker !== 'xna') {
|
|
@@ -12632,14 +12773,11 @@ var NeuraiAssetsBundle = (function (exports) {
|
|
|
12632
12773
|
* @param {string} config.toAddress - Default receiving address
|
|
12633
12774
|
* @param {('rvn'|'xna')} [config.assetMarker] - NIP-040 marker for locally
|
|
12634
12775
|
* built raw transactions. Omit to use the node's
|
|
12635
|
-
* getblockchaininfo.asset_marker
|
|
12636
|
-
*
|
|
12776
|
+
* getblockchaininfo.asset_marker. A missing field fails on testnet and
|
|
12777
|
+
* falls back to 'rvn' on mainnet. Per-operation params.assetMarker overrides this.
|
|
12637
12778
|
* @param {('strict'|'legacy-fallback')} [config.assetMarkerPolicy] - What to
|
|
12638
|
-
* do when getblockchaininfo
|
|
12639
|
-
*
|
|
12640
|
-
* post-NIP-040 chain a guessed 'rvn' builds a transaction the node
|
|
12641
|
-
* rejects. A node that simply predates the field still resolves 'rvn'
|
|
12642
|
-
* under both policies — that is an answer, not a failure.
|
|
12779
|
+
* do when getblockchaininfo fails. Testnet defaults to 'strict'; mainnet
|
|
12780
|
+
* defaults to 'legacy-fallback'. An absent field always fails on testnet.
|
|
12643
12781
|
*/
|
|
12644
12782
|
constructor(rpc, config = {}) {
|
|
12645
12783
|
if (!rpc || typeof rpc !== 'function') {
|