@neuraiproject/neurai-assets 1.8.0 → 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 CHANGED
@@ -37,6 +37,19 @@ 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
+
40
53
  ## 1.8.0: Reset testnet marker policy
41
54
 
42
55
  Testnet builders require the node's `getblockchaininfo.asset_marker` value
@@ -88,6 +101,14 @@ either silently inflates the minted supply (`× 10⁸ → 100,000,000` tokens
88
101
  where the user asked for 1) or trips the daemon's
89
102
  `ParseFixedPoint` cap with `Invalid amount (3): …`.
90
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
+
91
112
  This was regressed in `1.2.2`/`1.3.x` (a hardcoded `× 10⁸` was added to
92
113
  `BaseAssetTransactionBuilder.toSatoshis`) and fixed in the version after
93
114
  `1.3.1`. If you write a custom builder, follow the same convention: pass
@@ -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;