@neuraiproject/neurai-assets 1.8.0 → 1.8.2

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
@@ -37,6 +37,31 @@ 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.2: Transfer amounts given as decimal strings
41
+
42
+ `transferAsset` failed with `Invalid parameter, missing or invalid quantity`
43
+ on nodes before Neurai-DePIN `9834b03` (mainnet and testnet at the time of
44
+ writing) when a recipient `amount` was a decimal string such as `"5"`, or when
45
+ the asset change back to the sender was one that no JavaScript number carries
46
+ exactly (the builder writes those as text). Those nodes read the amounts of
47
+ `transfer` and `transferwithmessage` only from JSON numbers, like
48
+ `asset_quantity`. They are now converted the same way as in 1.8.1, on the copy
49
+ sent to the node; `message` and `expire_time` are left untouched. Numeric
50
+ amounts are unchanged.
51
+
52
+ ## 1.8.1: Issue quantities given as decimal strings
53
+
54
+ `createRootAsset`, `createSubAsset`, `createDepinAsset` and
55
+ `createRestrictedAsset` failed with `Invalid parameter, missing asset data for
56
+ key: asset_quantity` when `quantity` was a decimal string such as `"1000"`.
57
+ The node reads `asset_quantity` and `change_quantity` only from JSON numbers,
58
+ and the value reached `createrawtransaction` quoted. It is now converted on the
59
+ copy sent to the node: a JavaScript number when that number writes back the
60
+ same decimal, otherwise an exact decimal token written as a bare JSON number by
61
+ `@neuraiproject/neurai-rpc` (same shape as its `rpcNumber()`). The outputs in
62
+ the build result keep the value as it was given. Numeric quantities are
63
+ unchanged.
64
+
40
65
  ## 1.8.0: Reset testnet marker policy
41
66
 
42
67
  Testnet builders require the node's `getblockchaininfo.asset_marker` value
@@ -85,8 +110,17 @@ via `CheckAmountWithUnits`. Because the chain already does the ×10⁸ scaling
85
110
  itself, **the library must NOT pre-multiply** the value. Sending the raw
86
111
  display number is the only correct behavior; any extra factor on the wire
87
112
  either silently inflates the minted supply (`× 10⁸ → 100,000,000` tokens
88
- where the user asked for 1) or trips the daemon's
89
- `ParseFixedPoint` cap with `Invalid amount (3): …`.
113
+ where the user asked for 1) or falls outside the range the daemon's amount
114
+ parser accepts, with `Invalid amount (3): …`.
115
+
116
+ A quantity or transfer amount can be a number or a plain decimal string
117
+ (`"1000.25"`). A string keeps every digit; it is sent to the node as a JSON
118
+ number, the only form nodes before Neurai-DePIN `9834b03` accept for asset
119
+ amounts in `createrawtransaction` (newer nodes accept both). A decimal that no
120
+ JavaScript number carries exactly is sent as an exact token, which requires
121
+ the RPC function to serialize with `@neuraiproject/neurai-rpc` (its
122
+ `getRPC()` does); any other serializer makes the node reject the call instead
123
+ of changing the amount.
90
124
 
91
125
  This was regressed in `1.2.2`/`1.3.x` (a hardcoded `× 10⁸` was added to
92
126
  `BaseAssetTransactionBuilder.toSatoshis`) and fixed in the version after
@@ -8299,6 +8299,161 @@ var NeuraiAssetsBundle = (function (exports) {
8299
8299
  return validators$1;
8300
8300
  }
8301
8301
 
8302
+ /**
8303
+ * Asset amounts as `createrawtransaction` must receive them.
8304
+ *
8305
+ * Nodes before Neurai-DePIN 9834b03 read every asset amount of
8306
+ * `createrawtransaction` only from JSON numbers: `asset_quantity` (issue,
8307
+ * issue_restricted, issue_qualifier, reissue, reissue_restricted),
8308
+ * `change_quantity` (qualifier and tag operations) and the amounts of
8309
+ * `transfer` and `transferwithmessage`. A quoted value fails `isNum()` and the
8310
+ * call is rejected ("missing asset data for key: asset_quantity", "missing or
8311
+ * invalid quantity"). The builders accept these amounts as decimal strings,
8312
+ * which keep every digit the caller wrote, and produce some themselves (an
8313
+ * asset change that no JavaScript number carries exactly), so they are turned
8314
+ * into JSON numbers here, on the copy of the outputs sent to the node. Newer
8315
+ * nodes accept both forms. The outputs returned to the caller keep the value
8316
+ * as it was given.
8317
+ */
8318
+
8319
+ var rpcAmount;
8320
+ var hasRequiredRpcAmount;
8321
+
8322
+ function requireRpcAmount () {
8323
+ if (hasRequiredRpcAmount) return rpcAmount;
8324
+ hasRequiredRpcAmount = 1;
8325
+ const {
8326
+ expandScientificNotation,
8327
+ formatRawAsDecimal,
8328
+ normalizeDecimalText,
8329
+ scaleDecimalText
8330
+ } = requireAssetAmount();
8331
+
8332
+ /** Fields the node parses with `isNum()` inside an asset operation object. */
8333
+ const NUMERIC_OPERATION_FIELDS = ['asset_quantity', 'change_quantity'];
8334
+
8335
+ /**
8336
+ * Operations whose keys are asset names mapped to amounts. `transferwithmessage`
8337
+ * also carries `message` (a string) and `expire_time` (an integer), which are
8338
+ * not amounts and are left alone.
8339
+ */
8340
+ const TRANSFER_OPERATIONS = ['transfer', 'transferwithmessage'];
8341
+ const TRANSFER_NON_AMOUNT_KEYS = ['message', 'expire_time'];
8342
+
8343
+ /** The largest magnitude whose 8-decimal values a double still names exactly. */
8344
+ const MAX_EXACT_FRACTIONAL = Number.MAX_SAFE_INTEGER / 1e8;
8345
+
8346
+ /**
8347
+ * An exact decimal serialized as a bare JSON number token.
8348
+ *
8349
+ * Same shape as `rpcNumber()` from @neuraiproject/neurai-rpc: its
8350
+ * `stringifyRpcJson` (lossless-json) writes any object flagged
8351
+ * `isLosslessNumber` through `toString()`. It is defined here instead of
8352
+ * imported because the bundles inline every dependency and neurai-rpc carries
8353
+ * its whole RPC reference. With another serializer it stays an object, which
8354
+ * the node rejects: it can never become a different amount.
8355
+ */
8356
+ class RpcDecimal {
8357
+ /**
8358
+ * @param {string} text - Plain decimal text
8359
+ */
8360
+ constructor(text) {
8361
+ this.isLosslessNumber = true;
8362
+ this.value = text;
8363
+ }
8364
+
8365
+ toString() {
8366
+ return this.value;
8367
+ }
8368
+ }
8369
+
8370
+ /**
8371
+ * Convert an amount into a JSON-number value for `createrawtransaction`.
8372
+ *
8373
+ * Numbers pass through, as before. A decimal string becomes a JavaScript
8374
+ * number when that number writes back the same decimal (every JSON serializer
8375
+ * handles it), and an {@link RpcDecimal} otherwise, so no digit is rounded.
8376
+ *
8377
+ * @param {string|number} value - Display amount
8378
+ * @param {string} [label] - Field name, for error messages
8379
+ * @returns {number|RpcDecimal} JSON-number value
8380
+ * @throws {InvalidAmountError} If a string is not an exact 8-decimal amount
8381
+ */
8382
+ function toRpcAmount(value, label = 'amount') {
8383
+ if (typeof value !== 'string') {
8384
+ return value;
8385
+ }
8386
+ const text = formatRawAsDecimal(scaleDecimalText(normalizeDecimalText(value, label), 8, label));
8387
+ const number = Number(text);
8388
+ const exact = Number.isFinite(number) &&
8389
+ expandScientificNotation(String(number)) === text &&
8390
+ (Number.isInteger(number) ? Number.isSafeInteger(number) : Math.abs(number) <= MAX_EXACT_FRACTIONAL);
8391
+ return exact ? number : new RpcDecimal(text);
8392
+ }
8393
+
8394
+ function isPlainObject(value) {
8395
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
8396
+ }
8397
+
8398
+ /**
8399
+ * Copy of `createrawtransaction` outputs with the asset amounts converted by
8400
+ * {@link toRpcAmount}: `asset_quantity` and `change_quantity`, and the amounts
8401
+ * of `transfer` / `transferwithmessage`. XNA amounts and every other field are
8402
+ * left as they are: the node reads XNA amounts with `AmountFromValue`, which
8403
+ * also accepts strings.
8404
+ *
8405
+ * @param {Array<object>|object} outputs - Outputs as built (array of single-key objects, or one object)
8406
+ * @returns {Array<object>|object} Outputs to send, same shape
8407
+ */
8408
+ function prepareOutputsForRpc(outputs) {
8409
+ const convertPayload = (payload) => {
8410
+ if (!isPlainObject(payload)) {
8411
+ return payload;
8412
+ }
8413
+ const converted = {};
8414
+ for (const [operation, params] of Object.entries(payload)) {
8415
+ if (!isPlainObject(params)) {
8416
+ converted[operation] = params;
8417
+ continue;
8418
+ }
8419
+ const copy = { ...params };
8420
+ if (TRANSFER_OPERATIONS.includes(operation)) {
8421
+ for (const [key, value] of Object.entries(copy)) {
8422
+ if (!TRANSFER_NON_AMOUNT_KEYS.includes(key) && value !== undefined && value !== null) {
8423
+ copy[key] = toRpcAmount(value, `${operation} amount of ${key}`);
8424
+ }
8425
+ }
8426
+ } else {
8427
+ for (const field of NUMERIC_OPERATION_FIELDS) {
8428
+ if (copy[field] !== undefined && copy[field] !== null) {
8429
+ copy[field] = toRpcAmount(copy[field], field);
8430
+ }
8431
+ }
8432
+ }
8433
+ converted[operation] = copy;
8434
+ }
8435
+ return converted;
8436
+ };
8437
+
8438
+ const convertOutput = (output) => {
8439
+ const converted = {};
8440
+ for (const [address, payload] of Object.entries(output)) {
8441
+ converted[address] = convertPayload(payload);
8442
+ }
8443
+ return converted;
8444
+ };
8445
+
8446
+ return Array.isArray(outputs) ? outputs.map(convertOutput) : convertOutput(outputs);
8447
+ }
8448
+
8449
+ rpcAmount = {
8450
+ RpcDecimal,
8451
+ toRpcAmount,
8452
+ prepareOutputsForRpc
8453
+ };
8454
+ return rpcAmount;
8455
+ }
8456
+
8302
8457
  /**
8303
8458
  * Base Asset Transaction Builder
8304
8459
  * Abstract base class for all transaction builders
@@ -8335,6 +8490,7 @@ var NeuraiAssetsBundle = (function (exports) {
8335
8490
  toProtocolInteger,
8336
8491
  sumProtocolIntegers
8337
8492
  } = requireAssetAmount();
8493
+ const { prepareOutputsForRpc } = requireRpcAmount();
8338
8494
 
8339
8495
  /** Outputs below this are dust and are dropped instead of created. */
8340
8496
  const DUST_SATS = 1n;
@@ -8655,10 +8811,12 @@ var NeuraiAssetsBundle = (function (exports) {
8655
8811
  vout: input.vout !== undefined ? input.vout : input.outputIndex
8656
8812
  }));
8657
8813
 
8658
- // Call createrawtransaction
8814
+ // Call createrawtransaction. Nodes before Neurai-DePIN 9834b03 only
8815
+ // accept asset amounts as JSON numbers; the caller's outputs keep the
8816
+ // values as given.
8659
8817
  const rawTx = await this.rpc('createrawtransaction', [
8660
8818
  formattedInputs,
8661
- outputs
8819
+ prepareOutputsForRpc(outputs)
8662
8820
  ]);
8663
8821
 
8664
8822
  return rawTx;
@@ -8790,8 +8948,8 @@ var NeuraiAssetsBundle = (function (exports) {
8790
8948
  * (e.g. "1" for one token, "1.5" for one and a half tokens).
8791
8949
  * - The lib must NOT pre-multiply by 10^8 or 10^units; the daemon
8792
8950
  * does the 10^8 scaling itself, and any extra factor here lands
8793
- * duplicated and inflates the minted supply (or trips the
8794
- * ParseFixedPoint `exponent >= 18` cap → "Invalid amount (3)").
8951
+ * duplicated and inflates the minted supply (or falls outside the
8952
+ * range the node's amount parser accepts → "Invalid amount (3)").
8795
8953
  *
8796
8954
  * History: pre-1.2.2 multiplied by 10^units (correct only for units=0
8797
8955
  * assets, inflated everything else by 10^units). v1.2.2 changed to
@@ -8799,11 +8957,15 @@ var NeuraiAssetsBundle = (function (exports) {
8799
8957
  * 10^8 — e.g. reissuing 1 token of a units=0 asset minted 100,000,000).
8800
8958
  * The right answer is to send the value untouched.
8801
8959
  *
8960
+ * A decimal string is also kept as it is: `buildRawTransaction` turns it
8961
+ * into the JSON number the node requires (see utils/rpcAmount.js), and the
8962
+ * outputs returned to the caller keep the string.
8963
+ *
8802
8964
  * The `units` parameter is kept for API compatibility but is unused.
8803
8965
  *
8804
- * @param {number} amount - User-facing asset amount
8966
+ * @param {number|string} amount - User-facing asset amount
8805
8967
  * @param {number} units - Asset decimal places (unused; kept for API)
8806
- * @returns {number} The user-facing amount, ready for the JSON output
8968
+ * @returns {number|string} The user-facing amount, ready for the JSON output
8807
8969
  */
8808
8970
  toSatoshis(amount, units) {
8809
8971
  return amount;