@neuraiproject/neurai-assets 1.5.0 → 1.5.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
@@ -613,12 +613,26 @@ rejects with `unit must be larger than current unit selection`.
613
613
  The value read from the chain is still used, to check that the requested
614
614
  `quantity` fits the asset's precision.
615
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.
616
+ ### Reissue is built locally
617
+
618
+ `createrawtransaction`'s `reissue` object has no field for units, so the node
619
+ fills in `0` and refuses any asset whose units are above zero
620
+ (`unit must be larger than current unit selection`). Since 1.5.0 the two
621
+ reissue operations therefore skip that RPC and build their `rawTx` with
622
+ `createFromOperation`, which can say "keep the current units". They report
623
+ `buildStrategy: 'local-builder'`; every other operation still reports
624
+ `'rpc-node'`.
625
+
626
+ This is why `@neuraiproject/neurai-create-transaction` is a runtime
627
+ **dependency**, not just a dev one.
628
+
629
+ One consequence to know about: on the local path `result.outputs` and `rawTx`
630
+ describe the same operation but not the same output list. The node
631
+ auto-generates the owner-token return while processing a reissue entry, so the
632
+ RPC envelope omits it, while the locally built transaction carries it
633
+ explicitly — three entries in `outputs` against four vouts in `rawTx`. Both are
634
+ valid; parse `rawTx` when you need the outputs the chain will see, and do not
635
+ index `outputs` against its vouts.
622
636
 
623
637
  ## Owner Tokens - IMPORTANT
624
638
 
@@ -6020,7 +6020,12 @@ var NeuraiAssetsBundle = (function (exports) {
6020
6020
  return 0;
6021
6021
  }
6022
6022
 
6023
- const nameLength = Buffer.byteLength(String(descriptor.assetName), 'ascii');
6023
+ // One byte per character, matching how the payload encodes the name
6024
+ // (`serializeString` -> `asciiBytes`, which writes a single byte per char).
6025
+ // Node's byte-length helper would be equivalent here, but it hangs off a
6026
+ // global that browsers do not have: using it broke the extension bundle with
6027
+ // "Buffer is not defined", and this library does much of its work there.
6028
+ const nameLength = String(descriptor.assetName).length;
6024
6029
  const kind = descriptor.kind || 'transfer';
6025
6030
 
6026
6031
  // marker(3) + type(1) + CompactSize name length(1) + name
@@ -6304,11 +6309,14 @@ var NeuraiAssetsBundle = (function (exports) {
6304
6309
  * @throws {InsufficientFundsError} If not enough funds
6305
6310
  */
6306
6311
  async selectBaseCurrencyUTXOs(addresses, requiredAmount, buffer = 0.1, options = {}) {
6307
- // Get all XNA UTXOs
6308
- const allUTXOs = await this.getUTXOs(addresses, null);
6309
-
6310
- // Get mempool and filter
6311
- const mempool = await this.getMempoolEntries(addresses);
6312
+ // Both reads describe the same addresses and neither feeds the other: the
6313
+ // mempool result only filters the UTXO result afterwards. Awaiting them in
6314
+ // sequence spent one extra network round trip per selection, which on a
6315
+ // remote RPC proxy is most of the time a wallet spends building anything.
6316
+ const [allUTXOs, mempool] = await Promise.all([
6317
+ this.getUTXOs(addresses, null),
6318
+ this.getMempoolEntries(addresses)
6319
+ ]);
6312
6320
  const unspentUTXOs = this.filterMempoolSpentUTXOs(allUTXOs, mempool);
6313
6321
 
6314
6322
  // Drop outpoints the caller already spends elsewhere in this transaction
@@ -6374,11 +6382,14 @@ var NeuraiAssetsBundle = (function (exports) {
6374
6382
  throw new Error('Asset name is required');
6375
6383
  }
6376
6384
 
6377
- // Get all asset UTXOs
6378
- const allUTXOs = await this.getUTXOs(addresses, assetName);
6379
-
6380
- // Get mempool and filter
6381
- const mempool = await this.getMempoolEntries(addresses);
6385
+ // Both reads describe the same addresses and neither feeds the other: the
6386
+ // mempool result only filters the UTXO result afterwards. Awaiting them in
6387
+ // sequence spent one extra network round trip per selection, which on a
6388
+ // remote RPC proxy is most of the time a wallet spends building anything.
6389
+ const [allUTXOs, mempool] = await Promise.all([
6390
+ this.getUTXOs(addresses, assetName),
6391
+ this.getMempoolEntries(addresses)
6392
+ ]);
6382
6393
  const unspentUTXOs = this.filterMempoolSpentUTXOs(allUTXOs, mempool);
6383
6394
 
6384
6395
  const excluded = toOutpointSet(options.exclude);
@@ -6500,8 +6511,10 @@ var NeuraiAssetsBundle = (function (exports) {
6500
6511
  * @returns {Promise<bigint>} Total balance in 10^8-scaled units
6501
6512
  */
6502
6513
  async getBalanceRaw(addresses, assetName = null) {
6503
- const utxos = await this.getUTXOs(addresses, assetName);
6504
- const mempool = await this.getMempoolEntries(addresses);
6514
+ const [utxos, mempool] = await Promise.all([
6515
+ this.getUTXOs(addresses, assetName),
6516
+ this.getMempoolEntries(addresses)
6517
+ ]);
6505
6518
  const availableUTXOs = this.filterMempoolSpentUTXOs(utxos, mempool);
6506
6519
 
6507
6520
  return sumProtocolIntegers(availableUTXOs, 'satoshis', `${assetName || 'XNA'} utxo.satoshis`);
@@ -7989,6 +8002,44 @@ var NeuraiAssetsBundle = (function (exports) {
7989
8002
  throw new Error('build must be implemented by subclass');
7990
8003
  }
7991
8004
 
8005
+ /**
8006
+ * Start the fee-rate lookup without waiting for it.
8007
+ *
8008
+ * Every build needs the fee rate, and it depends on nothing the build
8009
+ * computes, so there is no reason for it to wait its turn behind the reads
8010
+ * that come first. Kicking it off early lets it share a round trip with
8011
+ * them; `estimateFee`/`estimateFeeSats` await the same memoised promise, so
8012
+ * the call still happens exactly once and a failure still surfaces there.
8013
+ *
8014
+ * The trailing `catch` only marks the promise as handled while nothing is
8015
+ * awaiting it — it attaches to a derived promise, so the original still
8016
+ * rejects for whoever awaits it later.
8017
+ *
8018
+ * @returns {void}
8019
+ */
8020
+ warmFeeRate() {
8021
+ if (this._feeRatePromise) {
8022
+ return;
8023
+ }
8024
+ this._feeRatePromise = this.utxoSelector.getFeeRate();
8025
+ this._feeRatePromise.catch(() => {});
8026
+ }
8027
+
8028
+ /**
8029
+ * Start every read a build needs but that depends on nothing the build
8030
+ * computes: the fee rate and the NIP-040 asset marker.
8031
+ *
8032
+ * Both are memoised, so warming them costs no extra call — it only moves
8033
+ * them off the critical path. Every builder that reaches here goes on to
8034
+ * stamp a marker, so neither read is ever speculative.
8035
+ *
8036
+ * @returns {void}
8037
+ */
8038
+ warmChainReads() {
8039
+ this.warmFeeRate();
8040
+ this.resolveAssetMarker().catch(() => {});
8041
+ }
8042
+
7992
8043
  /**
7993
8044
  * Estimate transaction fee.
7994
8045
  *
@@ -8002,9 +8053,7 @@ var NeuraiAssetsBundle = (function (exports) {
8002
8053
  * @returns {Promise<number>} Estimated fee in XNA
8003
8054
  */
8004
8055
  async estimateFee(inputs, outputs) {
8005
- if (!this._feeRatePromise) {
8006
- this._feeRatePromise = this.utxoSelector.getFeeRate();
8007
- }
8056
+ this.warmFeeRate();
8008
8057
  const feeRate = await this._feeRatePromise;
8009
8058
  return this.utxoSelector.estimateFee(inputs, outputs, feeRate);
8010
8059
  }
@@ -8035,9 +8084,7 @@ var NeuraiAssetsBundle = (function (exports) {
8035
8084
  * @returns {Promise<bigint>} Estimated fee in satoshis
8036
8085
  */
8037
8086
  async estimateFeeSats(inputs, outputs) {
8038
- if (!this._feeRatePromise) {
8039
- this._feeRatePromise = this.utxoSelector.getFeeRate();
8040
- }
8087
+ this.warmFeeRate();
8041
8088
  const feeRate = await this._feeRatePromise;
8042
8089
  return this.utxoSelector.estimateFeeSats(inputs, outputs, feeRate);
8043
8090
  }
@@ -8080,6 +8127,10 @@ var NeuraiAssetsBundle = (function (exports) {
8080
8127
  initialInputHint = 1
8081
8128
  } = options;
8082
8129
 
8130
+ // Covers the builders that never call assetExists, whose first read is
8131
+ // this one.
8132
+ this.warmChainReads();
8133
+
8083
8134
  const addresses = await this._getAddresses();
8084
8135
  const excluded = UTXOSelector.toOutpointSet(exclude);
8085
8136
  // toOutpointSet returns the caller's Set untouched when it already is one;
@@ -8709,6 +8760,9 @@ var NeuraiAssetsBundle = (function (exports) {
8709
8760
  * @returns {Promise<boolean>} True if exists
8710
8761
  */
8711
8762
  async assetExists(assetName) {
8763
+ // This is the first read a build performs and its answer gates nothing but
8764
+ // the guard below, so let the build's other chain reads travel alongside it.
8765
+ this.warmChainReads();
8712
8766
  try {
8713
8767
  const assetData = await this.rpc('getassetdata', [assetName]);
8714
8768
  return assetData !== null && assetData !== undefined;
@@ -8728,6 +8782,9 @@ var NeuraiAssetsBundle = (function (exports) {
8728
8782
  * @returns {Promise<object|null>} Asset data or null if not found
8729
8783
  */
8730
8784
  async getAssetData(assetName) {
8785
+ // This is the first read a build performs and its answer gates nothing but
8786
+ // the guard below, so let the build's other chain reads travel alongside it.
8787
+ this.warmChainReads();
8731
8788
  try {
8732
8789
  return await this.rpc('getassetdata', [assetName]);
8733
8790
  } catch (error) {