@neuraiproject/neurai-assets 1.2.5 → 1.3.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
@@ -651,3 +651,50 @@ console.log('Transaction ID:', txid);
651
651
  For AuthScript wallets, derive addresses externally with `neurai-key`, then initialize
652
652
  `NeuraiAssets` with those `nq1...` / `tnq1...` addresses. The recommended network labels
653
653
  are `xna` and `xna-test`; `xna-pq` and `xna-pq-test` remain available as compatibility aliases.
654
+
655
+ ## Fee estimation (PQ-aware)
656
+
657
+ Asset transactions are usually built with one or two XNA inputs plus, depending on the operation, an owner-token or qualifier UTXO. The library estimates the fee twice per build: a rough pre-estimate to size the initial XNA selection, and a final estimate once the actual UTXOs are known.
658
+
659
+ Both estimates share a single `estimatesmartfee` lookup. The fee rate is stable for the lifetime of one build, so it is fetched on the first `estimateFee` call and cached on the builder instance for the second — half as many RPC round trips as before `1.3.1`.
660
+
661
+ Both estimates use the helpers in [`src/utils/feeSizing.js`](src/utils/feeSizing.js) and distinguish PQ AuthScript inputs/outputs from legacy P2PKH ones. PQ inputs spend ~977 vbytes vs ~148 for legacy — without this distinction, transactions built from PQ addresses fall under the node's `min relay fee` and are rejected with `code -26: min relay fee not met`.
662
+
663
+ You should not need to call these helpers directly; they are wired into every builder. They are documented here so you can audit the fee math or use the same constants if you compose transactions outside the standard builder flow.
664
+
665
+ ```js
666
+ const {
667
+ VBYTES,
668
+ estimateInputVbytes,
669
+ estimateOutputBytes,
670
+ estimateTransactionVbytes,
671
+ isPQAddress,
672
+ isPQScript,
673
+ } = require('@neuraiproject/neurai-assets/src/utils/feeSizing');
674
+
675
+ VBYTES.legacyInputVbytes; // 148
676
+ VBYTES.pqInputVbytes; // 977
677
+ VBYTES.legacyOutputBytes; // 34
678
+ VBYTES.pqOutputBytes; // 43
679
+
680
+ estimateInputVbytes({ script: '5120…' }); // 977
681
+ estimateInputVbytes({ address: 'nq1…' }); // 977
682
+ estimateInputVbytes({ address: 'mgRYHdMq…' }); // 148
683
+ estimateOutputBytes('tnq1…'); // 43
684
+
685
+ const vbytes = estimateTransactionVbytes(
686
+ [{ script: '5120…' }, { address: 'mgRYHdMq…' }], // 1 PQ + 1 legacy input
687
+ ['nq1qchange…', 'mgRYHdMqburn…'], // 1 PQ + 1 legacy output
688
+ );
689
+ ```
690
+
691
+ The constants mirror those exported from `@neuraiproject/neurai-sign-transaction` (`VBYTES`). They are inlined here on purpose: depending on the full signer would pull `bitcoinjs-lib` and `@noble/post-quantum` into the IIFE / browser bundles, far more weight than these constants need. The signer remains the source of truth — if it ever bumps a vbytes value, this file must follow.
692
+
693
+ ### Limitations
694
+
695
+ The estimator assumes the most common spend layout for every input:
696
+
697
+ - legacy inputs → P2PKH `scriptSig` worst case (DER signature + compressed pubkey)
698
+ - PQ inputs → AuthScript v1 with the **default** `OP_TRUE` `witnessScript` and **no** `functionalArgs`
699
+
700
+ That covers all standard asset operations. If you build transactions whose PQ inputs use covenant `witnessScript`s, NoAuth (`authType=0x00`) or Legacy AuthScript (`authType=0x02`) witnesses, compute the witness size yourself and add it to the result of `estimateTransactionVbytes` (or use `estimateVirtualSize` from `@neuraiproject/neurai-sign-transaction` after building the raw transaction, which fills dummy witnesses of the worst-case size and returns the exact post-signing vsize).
@@ -2725,6 +2725,118 @@ var NeuraiAssetsBundle = (function (exports) {
2725
2725
  return OwnerTokenManager_1;
2726
2726
  }
2727
2727
 
2728
+ /**
2729
+ * Fee / size helpers for Neurai transactions.
2730
+ *
2731
+ * These constants and classifiers are the same ones exposed by
2732
+ * `@neuraiproject/neurai-sign-transaction` (`VBYTES`, `isPQAddress`,
2733
+ * `isPQScript`, `estimateInputVbytes`, `estimateOutputBytes`,
2734
+ * `estimateTransactionVbytes`). They are inlined here to keep the assets
2735
+ * package light: depending on the full signer would pull `bitcoinjs-lib`
2736
+ * and `@noble/post-quantum` into the IIFE / browser bundles, which is far
2737
+ * more weight than the few constants we actually need for fee estimation.
2738
+ *
2739
+ * SOURCE OF TRUTH: `@neuraiproject/neurai-sign-transaction` `src/estimate.ts`.
2740
+ * Keep these values in sync with the signer's `VBYTES`. Mismatches surface
2741
+ * immediately as `min relay fee not met` failures from the node.
2742
+ */
2743
+
2744
+ var feeSizing;
2745
+ var hasRequiredFeeSizing;
2746
+
2747
+ function requireFeeSizing () {
2748
+ if (hasRequiredFeeSizing) return feeSizing;
2749
+ hasRequiredFeeSizing = 1;
2750
+ /** Per-component byte sizes used across the Neurai stack for fee estimation. */
2751
+ const VBYTES = Object.freeze({
2752
+ /** Raw transaction overhead: version (4) + in-count varint (1) + out-count varint (1) + locktime (4). */
2753
+ baseTxOverheadBytes: 10,
2754
+ /** Extra weight contributed by the segwit marker + flag bytes when any input is PQ. */
2755
+ segwitMarkerVbytes: 1,
2756
+ /** vbytes for a typical legacy P2PKH input (worst-case scriptSig). */
2757
+ legacyInputVbytes: 148,
2758
+ /** vbytes for a typical PQ AuthScript input with the default OP_TRUE witnessScript. */
2759
+ pqInputVbytes: 977,
2760
+ /** Bytes of a legacy P2PKH output (8-byte value + 1-byte script length + 25-byte scriptPubKey). */
2761
+ legacyOutputBytes: 34,
2762
+ /** Bytes of an AuthScript-v1 output (8-byte value + 1-byte script length + 34-byte scriptPubKey). */
2763
+ pqOutputBytes: 43,
2764
+ });
2765
+
2766
+ /** True for Neurai PQ AuthScript bech32 destinations (`nq1…` mainnet, `tnq1…` testnet). */
2767
+ function isPQAddress(address) {
2768
+ return (
2769
+ typeof address === 'string' &&
2770
+ (address.startsWith('nq1') || address.startsWith('tnq1'))
2771
+ );
2772
+ }
2773
+
2774
+ /** True for AuthScript-v1 scriptPubKey hex (witness v1, 32-byte program — `5120…`). */
2775
+ function isPQScript(scriptHex) {
2776
+ if (typeof scriptHex !== 'string' || scriptHex.length < 4) return false;
2777
+ return scriptHex.toLowerCase().startsWith('5120');
2778
+ }
2779
+
2780
+ /**
2781
+ * Estimate the vbytes contributed by spending one UTXO. Uses the UTXO's
2782
+ * `script` if available, otherwise falls back to its `address`. Unknown
2783
+ * prevouts are treated as legacy.
2784
+ */
2785
+ function estimateInputVbytes(utxo) {
2786
+ const script = utxo && utxo.script;
2787
+ if (typeof script === 'string' && script.length > 0) {
2788
+ return isPQScript(script) ? VBYTES.pqInputVbytes : VBYTES.legacyInputVbytes;
2789
+ }
2790
+ const address = utxo && utxo.address;
2791
+ if (typeof address === 'string' && isPQAddress(address)) {
2792
+ return VBYTES.pqInputVbytes;
2793
+ }
2794
+ return VBYTES.legacyInputVbytes;
2795
+ }
2796
+
2797
+ /** Estimate the bytes contributed by an output (address string or `{address}`). */
2798
+ function estimateOutputBytes(target) {
2799
+ const address =
2800
+ typeof target === 'string' ? target : (target && target.address) || '';
2801
+ return isPQAddress(address) ? VBYTES.pqOutputBytes : VBYTES.legacyOutputBytes;
2802
+ }
2803
+
2804
+ /**
2805
+ * Sum the per-input/per-output contributions, plus base overhead and segwit
2806
+ * marker (added once when any input is PQ). Inputs may be partial UTXO-like
2807
+ * objects with `script` and/or `address`. Outputs may be address strings or
2808
+ * `{ address }` descriptors.
2809
+ */
2810
+ function estimateTransactionVbytes(inputs, outputs) {
2811
+ let vbytes = VBYTES.baseTxOverheadBytes;
2812
+ let hasPQInput = false;
2813
+
2814
+ for (const inp of inputs) {
2815
+ const v = estimateInputVbytes(inp);
2816
+ vbytes += v;
2817
+ if (v === VBYTES.pqInputVbytes) hasPQInput = true;
2818
+ }
2819
+
2820
+ for (const out of outputs) {
2821
+ vbytes += estimateOutputBytes(out);
2822
+ }
2823
+
2824
+ if (hasPQInput) vbytes += VBYTES.segwitMarkerVbytes;
2825
+
2826
+ return vbytes;
2827
+ }
2828
+
2829
+ feeSizing = {
2830
+ VBYTES,
2831
+ isPQAddress,
2832
+ isPQScript,
2833
+ estimateInputVbytes,
2834
+ estimateOutputBytes,
2835
+ estimateTransactionVbytes,
2836
+ };
2837
+ return feeSizing;
2838
+ }
2839
+
2728
2840
  /**
2729
2841
  * UTXO Selector
2730
2842
  * Selects appropriate UTXOs for asset transactions
@@ -2742,6 +2854,7 @@ var NeuraiAssetsBundle = (function (exports) {
2742
2854
  if (hasRequiredUTXOSelector) return UTXOSelector_1;
2743
2855
  hasRequiredUTXOSelector = 1;
2744
2856
  const { InsufficientFundsError } = requireErrors();
2857
+ const { estimateTransactionVbytes } = requireFeeSizing();
2745
2858
 
2746
2859
  class UTXOSelector {
2747
2860
  /**
@@ -2980,35 +3093,44 @@ var NeuraiAssetsBundle = (function (exports) {
2980
3093
  }
2981
3094
 
2982
3095
  /**
2983
- * Estimate transaction size in bytes
2984
- * Used for fee calculation
3096
+ * Estimate transaction size in vbytes for fee calculation.
3097
+ *
3098
+ * Both arguments accept either a count (legacy callers) or an array of
3099
+ * descriptors that allow the estimator to distinguish PQ AuthScript
3100
+ * inputs/outputs from legacy P2PKH ones — PQ inputs are roughly six
3101
+ * times larger than legacy inputs and would otherwise underflow the
3102
+ * node's `min relay fee`.
2985
3103
  *
2986
- * @param {number} inputCount - Number of inputs
2987
- * @param {number} outputCount - Number of outputs
2988
- * @returns {number} Estimated size in bytes
3104
+ * Input descriptors may be UTXO-like objects with `script` and/or
3105
+ * `address`. Output descriptors may be address strings or `{ address }`.
3106
+ * When a count is provided instead of an array, every input/output is
3107
+ * treated as legacy.
3108
+ *
3109
+ * @param {number|Array} inputs - Input count or array of UTXO-like descriptors
3110
+ * @param {number|Array} outputs - Output count or array of address-like descriptors
3111
+ * @returns {number} Estimated vbytes
2989
3112
  */
2990
- estimateTransactionSize(inputCount, outputCount) {
2991
- // Rough estimation:
2992
- // - Each input: ~180 bytes
2993
- // - Each output: ~34 bytes
2994
- // - Transaction overhead: ~10 bytes
2995
- const inputSize = inputCount * 180;
2996
- const outputSize = outputCount * 34;
2997
- const overhead = 10;
2998
-
2999
- return inputSize + outputSize + overhead;
3113
+ estimateTransactionSize(inputs, outputs) {
3114
+ const inputDescriptors = Array.isArray(inputs)
3115
+ ? inputs
3116
+ : new Array(inputs).fill({});
3117
+ const outputDescriptors = Array.isArray(outputs)
3118
+ ? outputs
3119
+ : new Array(outputs).fill({});
3120
+ return estimateTransactionVbytes(inputDescriptors, outputDescriptors);
3000
3121
  }
3001
3122
 
3002
3123
  /**
3003
- * Estimate fee for a transaction
3004
- * @param {number} inputCount - Number of inputs
3005
- * @param {number} outputCount - Number of outputs
3124
+ * Estimate fee for a transaction.
3125
+ *
3126
+ * @param {number|Array} inputs - Input count or array of UTXO-like descriptors
3127
+ * @param {number|Array} outputs - Output count or array of address-like descriptors
3006
3128
  * @param {number} feeRate - Fee rate in XNA per KB (default: 0.015)
3007
3129
  * @returns {number} Estimated fee in XNA
3008
3130
  */
3009
- estimateFee(inputCount, outputCount, feeRate = 0.015) {
3010
- const sizeBytes = this.estimateTransactionSize(inputCount, outputCount);
3011
- const sizeKB = sizeBytes / 1000;
3131
+ estimateFee(inputs, outputs, feeRate = 0.015) {
3132
+ const sizeVbytes = this.estimateTransactionSize(inputs, outputs);
3133
+ const sizeKB = sizeVbytes / 1000;
3012
3134
  const fee = sizeKB * feeRate;
3013
3135
 
3014
3136
  // Round up to 8 decimals
@@ -4296,6 +4418,11 @@ var NeuraiAssetsBundle = (function (exports) {
4296
4418
  this.ownerTokenManager = new OwnerTokenManager(rpc);
4297
4419
  this.utxoSelector = new UTXOSelector(rpc);
4298
4420
  this.outputOrderer = new OutputOrderer();
4421
+
4422
+ // `estimatesmartfee` is called twice per build (pre-selection guess and
4423
+ // post-selection recompute). The fee rate is stable for the duration of
4424
+ // a single build, so cache the first lookup and reuse it.
4425
+ this._feeRatePromise = null;
4299
4426
  }
4300
4427
 
4301
4428
  /**
@@ -4330,14 +4457,23 @@ var NeuraiAssetsBundle = (function (exports) {
4330
4457
  }
4331
4458
 
4332
4459
  /**
4333
- * Estimate transaction fee
4334
- * @param {number} inputCount - Number of inputs
4335
- * @param {number} outputCount - Number of outputs
4460
+ * Estimate transaction fee.
4461
+ *
4462
+ * Both arguments accept either a count (legacy) or an array of descriptors
4463
+ * that lets the underlying estimator distinguish PQ AuthScript inputs/outputs
4464
+ * from legacy P2PKH ones. Pass arrays whenever you have actual UTXOs and
4465
+ * output addresses on hand — counts produce a legacy-only estimate.
4466
+ *
4467
+ * @param {number|Array} inputs - Input count or array of UTXO-like descriptors
4468
+ * @param {number|Array} outputs - Output count or array of address-like descriptors
4336
4469
  * @returns {Promise<number>} Estimated fee in XNA
4337
4470
  */
4338
- async estimateFee(inputCount, outputCount) {
4339
- const feeRate = await this.utxoSelector.getFeeRate();
4340
- return this.utxoSelector.estimateFee(inputCount, outputCount, feeRate);
4471
+ async estimateFee(inputs, outputs) {
4472
+ if (!this._feeRatePromise) {
4473
+ this._feeRatePromise = this.utxoSelector.getFeeRate();
4474
+ }
4475
+ const feeRate = await this._feeRatePromise;
4476
+ return this.utxoSelector.estimateFee(inputs, outputs, feeRate);
4341
4477
  }
4342
4478
 
4343
4479
  /**
@@ -4799,7 +4935,8 @@ var NeuraiAssetsBundle = (function (exports) {
4799
4935
  const changeAddress = await this.getChangeAddress();
4800
4936
 
4801
4937
  // 5. Estimate fee (rough estimate for initial UTXO selection)
4802
- const estimatedFee = await this.estimateFee(1, 3);
4938
+ const outputAddresses = [burnInfo.address, changeAddress, toAddress];
4939
+ const estimatedFee = await this.estimateFee(1, outputAddresses);
4803
4940
 
4804
4941
  // 6. Calculate total XNA needed
4805
4942
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -4809,8 +4946,8 @@ var NeuraiAssetsBundle = (function (exports) {
4809
4946
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
4810
4947
  const totalXNAInput = utxoSelection.totalXNA;
4811
4948
 
4812
- // 8. Recalculate fee with actual input count
4813
- const actualFee = await this.estimateFee(baseCurrencyUTXOs.length, 3);
4949
+ // 8. Recalculate fee with actual inputs (PQ-aware)
4950
+ const actualFee = await this.estimateFee(baseCurrencyUTXOs, outputAddresses);
4814
4951
 
4815
4952
  // 9. Verify we still have enough after fee recalculation
4816
4953
  const totalRequired = burnInfo.amount + actualFee;
@@ -5034,7 +5171,13 @@ var NeuraiAssetsBundle = (function (exports) {
5034
5171
  // 8. Estimate fee
5035
5172
  // Inputs: XNA UTXOs + owner token UTXO
5036
5173
  // Outputs: burn + change + owner token return + issue operation
5037
- const estimatedFee = await this.estimateFee(2, 4);
5174
+ const outputAddresses = [
5175
+ burnInfo.address,
5176
+ changeAddress,
5177
+ changeAddress, // owner token return goes to change address
5178
+ toAddress,
5179
+ ];
5180
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
5038
5181
 
5039
5182
  // 9. Calculate total XNA needed
5040
5183
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -5044,9 +5187,9 @@ var NeuraiAssetsBundle = (function (exports) {
5044
5187
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
5045
5188
  const totalXNAInput = utxoSelection.totalXNA;
5046
5189
 
5047
- // 11. Recalculate fee with actual input count
5048
- const actualInputCount = baseCurrencyUTXOs.length + 1; // +1 for owner token
5049
- const actualFee = await this.estimateFee(actualInputCount, 4);
5190
+ // 11. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
5191
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
5192
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
5050
5193
 
5051
5194
  // 12. Verify we have enough XNA
5052
5195
  const totalRequired = burnInfo.amount + actualFee;
@@ -5248,14 +5391,15 @@ var NeuraiAssetsBundle = (function (exports) {
5248
5391
  const toAddress = await this.getToAddress();
5249
5392
  const changeAddress = await this.getChangeAddress();
5250
5393
 
5251
- const estimatedFee = await this.estimateFee(1, 3);
5394
+ const outputAddresses = [burnInfo.address, changeAddress, toAddress];
5395
+ const estimatedFee = await this.estimateFee(1, outputAddresses);
5252
5396
  const totalXNANeeded = burnInfo.amount + estimatedFee;
5253
5397
 
5254
5398
  const utxoSelection = await this.selectUTXOs(totalXNANeeded, null, 0);
5255
5399
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
5256
5400
  const totalXNAInput = utxoSelection.totalXNA;
5257
5401
 
5258
- const actualFee = await this.estimateFee(baseCurrencyUTXOs.length, 3);
5402
+ const actualFee = await this.estimateFee(baseCurrencyUTXOs, outputAddresses);
5259
5403
  const totalRequired = burnInfo.amount + actualFee;
5260
5404
 
5261
5405
  if (totalXNAInput < totalRequired) {
@@ -5470,9 +5614,14 @@ var NeuraiAssetsBundle = (function (exports) {
5470
5614
 
5471
5615
  // 8. Estimate fee
5472
5616
  // Inputs: XNA UTXOs + owner token UTXO
5473
- // Outputs: burn + change + reissue operation
5474
- // (node auto-generates owner token return from the reissue entry, total = 4 physical outputs)
5475
- const estimatedFee = await this.estimateFee(2, 4);
5617
+ // Outputs: burn + change + owner token return + reissue operation
5618
+ const outputAddresses = [
5619
+ burnInfo.address,
5620
+ changeAddress,
5621
+ changeAddress, // owner token return goes to change address
5622
+ toAddress,
5623
+ ];
5624
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
5476
5625
 
5477
5626
  // 9. Calculate total XNA needed
5478
5627
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -5482,9 +5631,9 @@ var NeuraiAssetsBundle = (function (exports) {
5482
5631
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
5483
5632
  const totalXNAInput = utxoSelection.totalXNA;
5484
5633
 
5485
- // 11. Recalculate fee with actual input count
5486
- const actualInputCount = baseCurrencyUTXOs.length + 1; // +1 for owner token
5487
- const actualFee = await this.estimateFee(actualInputCount, 4);
5634
+ // 11. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
5635
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
5636
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
5488
5637
 
5489
5638
  // 12. Verify we have enough XNA
5490
5639
  const totalRequired = burnInfo.amount + actualFee;
@@ -5745,7 +5894,13 @@ var NeuraiAssetsBundle = (function (exports) {
5745
5894
  // 7. Estimate fee
5746
5895
  // Inputs: XNA UTXOs + owner token UTXO
5747
5896
  // Outputs: burn + change + owner token return + issue_unique operation
5748
- const estimatedFee = await this.estimateFee(2, 4);
5897
+ const outputAddresses = [
5898
+ burnInfo.address,
5899
+ changeAddress,
5900
+ changeAddress, // owner token return goes to change address
5901
+ toAddress,
5902
+ ];
5903
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
5749
5904
 
5750
5905
  // 8. Calculate total XNA needed
5751
5906
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -5755,9 +5910,9 @@ var NeuraiAssetsBundle = (function (exports) {
5755
5910
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
5756
5911
  const totalXNAInput = utxoSelection.totalXNA;
5757
5912
 
5758
- // 10. Recalculate fee with actual input count
5759
- const actualInputCount = baseCurrencyUTXOs.length + 1; // +1 for owner token
5760
- const actualFee = await this.estimateFee(actualInputCount, 4);
5913
+ // 10. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
5914
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
5915
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
5761
5916
 
5762
5917
  // 11. Verify we have enough XNA
5763
5918
  const totalRequired = burnInfo.amount + actualFee;
@@ -6005,8 +6160,8 @@ var NeuraiAssetsBundle = (function (exports) {
6005
6160
  const changeAddress = await this.getChangeAddress();
6006
6161
 
6007
6162
  // 7. Estimate fee
6008
- const outputCount = 3;
6009
- const estimatedFee = await this.estimateFee(2, outputCount);
6163
+ const outputAddresses = [burnInfo.address, changeAddress, toAddress];
6164
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
6010
6165
 
6011
6166
  // 8. Calculate total XNA needed
6012
6167
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -6016,9 +6171,9 @@ var NeuraiAssetsBundle = (function (exports) {
6016
6171
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
6017
6172
  const totalXNAInput = utxoSelection.totalXNA;
6018
6173
 
6019
- // 10. Recalculate fee with actual input count
6020
- const actualInputCount = baseCurrencyUTXOs.length + parentQualifierUTXOs.length;
6021
- const actualFee = await this.estimateFee(actualInputCount, outputCount);
6174
+ // 10. Recalculate fee with actual inputs (PQ-aware), including parent qualifier UTXOs
6175
+ const actualFeeInputs = [...baseCurrencyUTXOs, ...parentQualifierUTXOs];
6176
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
6022
6177
 
6023
6178
  // 11. Verify we have enough XNA
6024
6179
  const totalRequired = burnInfo.amount + actualFee;
@@ -6257,7 +6412,13 @@ var NeuraiAssetsBundle = (function (exports) {
6257
6412
  }
6258
6413
 
6259
6414
  // 7. Estimate fee (+1 for owner token input)
6260
- const estimatedFee = await this.estimateFee(2, 4);
6415
+ const outputAddresses = [
6416
+ burnInfo.address,
6417
+ changeAddress,
6418
+ changeAddress, // owner token return goes to change address
6419
+ toAddress,
6420
+ ];
6421
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
6261
6422
 
6262
6423
  // 8. Calculate total XNA needed
6263
6424
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -6267,8 +6428,9 @@ var NeuraiAssetsBundle = (function (exports) {
6267
6428
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
6268
6429
  const totalXNAInput = utxoSelection.totalXNA;
6269
6430
 
6270
- // 10. Recalculate fee with actual input count (+1 for owner token)
6271
- const actualFee = await this.estimateFee(baseCurrencyUTXOs.length + 1, 4);
6431
+ // 10. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
6432
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
6433
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
6272
6434
 
6273
6435
  // 11. Verify we have enough XNA
6274
6436
  const totalRequired = burnInfo.amount + actualFee;
@@ -6525,7 +6687,13 @@ var NeuraiAssetsBundle = (function (exports) {
6525
6687
  const burnInfo = this.burnManager.getReissueBurn();
6526
6688
 
6527
6689
  // 8. Estimate fee
6528
- const estimatedFee = await this.estimateFee(2, 4);
6690
+ const outputAddresses = [
6691
+ burnInfo.address,
6692
+ changeAddress,
6693
+ changeAddress, // owner token return goes to change address
6694
+ toAddress,
6695
+ ];
6696
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
6529
6697
 
6530
6698
  // 9. Calculate total XNA needed
6531
6699
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -6535,9 +6703,9 @@ var NeuraiAssetsBundle = (function (exports) {
6535
6703
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
6536
6704
  const totalXNAInput = utxoSelection.totalXNA;
6537
6705
 
6538
- // 11. Recalculate fee with actual input count
6539
- const actualInputCount = baseCurrencyUTXOs.length + 1; // +1 for owner token
6540
- const actualFee = await this.estimateFee(actualInputCount, 4);
6706
+ // 11. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
6707
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
6708
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
6541
6709
 
6542
6710
  // 12. Verify we have enough XNA
6543
6711
  const totalRequired = burnInfo.amount + actualFee;
@@ -6772,7 +6940,9 @@ var NeuraiAssetsBundle = (function (exports) {
6772
6940
  : this.burnManager.getTagAddressBurn(addressCount);
6773
6941
 
6774
6942
  // 6. Estimate fee
6775
- const estimatedFee = await this.estimateFee(2, 3);
6943
+ // Outputs: burn + XNA change + tag/untag operation (sent to changeAddress)
6944
+ const outputAddresses = [burnInfo.address, changeAddress, changeAddress];
6945
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
6776
6946
 
6777
6947
  // 7. Calculate total XNA needed
6778
6948
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -6782,9 +6952,9 @@ var NeuraiAssetsBundle = (function (exports) {
6782
6952
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
6783
6953
  const totalXNAInput = utxoSelection.totalXNA;
6784
6954
 
6785
- // 9. Recalculate fee with actual input count
6786
- const actualInputCount = baseCurrencyUTXOs.length + qualifierUTXOs.length;
6787
- const actualFee = await this.estimateFee(actualInputCount, 3);
6955
+ // 9. Recalculate fee with actual inputs (PQ-aware), including qualifier UTXOs
6956
+ const actualFeeInputs = [...baseCurrencyUTXOs, ...qualifierUTXOs];
6957
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
6788
6958
 
6789
6959
  // 10. Verify we have enough XNA
6790
6960
  const totalRequired = burnInfo.amount + actualFee;
@@ -7019,16 +7189,18 @@ var NeuraiAssetsBundle = (function (exports) {
7019
7189
  const burnAmount = 0;
7020
7190
 
7021
7191
  // 6. Estimate fee
7022
- const estimatedFee = await this.estimateFee(2, 3);
7192
+ // Outputs: XNA change + freeze/unfreeze operation (sent to changeAddress)
7193
+ const outputAddresses = [changeAddress, changeAddress];
7194
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
7023
7195
 
7024
7196
  // 7. Select XNA UTXOs (only for fee, no burn)
7025
7197
  const utxoSelection = await this.selectUTXOs(estimatedFee, null, 0);
7026
7198
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
7027
7199
  const totalXNAInput = utxoSelection.totalXNA;
7028
7200
 
7029
- // 8. Recalculate fee with actual input count
7030
- const actualInputCount = baseCurrencyUTXOs.length + 1; // +1 for owner token
7031
- const actualFee = await this.estimateFee(actualInputCount, 3);
7201
+ // 8. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
7202
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
7203
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
7032
7204
 
7033
7205
  // 9. Verify we have enough XNA for fee
7034
7206
  if (totalXNAInput < actualFee) {