@neuraiproject/neurai-assets 1.2.5 → 1.3.0

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,48 @@ 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 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`.
660
+
661
+ 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.
662
+
663
+ ```js
664
+ const {
665
+ VBYTES,
666
+ estimateInputVbytes,
667
+ estimateOutputBytes,
668
+ estimateTransactionVbytes,
669
+ isPQAddress,
670
+ isPQScript,
671
+ } = require('@neuraiproject/neurai-assets/src/utils/feeSizing');
672
+
673
+ VBYTES.legacyInputVbytes; // 148
674
+ VBYTES.pqInputVbytes; // 977
675
+ VBYTES.legacyOutputBytes; // 34
676
+ VBYTES.pqOutputBytes; // 43
677
+
678
+ estimateInputVbytes({ script: '5120…' }); // 977
679
+ estimateInputVbytes({ address: 'nq1…' }); // 977
680
+ estimateInputVbytes({ address: 'mgRYHdMq…' }); // 148
681
+ estimateOutputBytes('tnq1…'); // 43
682
+
683
+ const vbytes = estimateTransactionVbytes(
684
+ [{ script: '5120…' }, { address: 'mgRYHdMq…' }], // 1 PQ + 1 legacy input
685
+ ['nq1qchange…', 'mgRYHdMqburn…'], // 1 PQ + 1 legacy output
686
+ );
687
+ ```
688
+
689
+ 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.
690
+
691
+ ### Limitations
692
+
693
+ The estimator assumes the most common spend layout for every input:
694
+
695
+ - legacy inputs → P2PKH `scriptSig` worst case (DER signature + compressed pubkey)
696
+ - PQ inputs → AuthScript v1 with the **default** `OP_TRUE` `witnessScript` and **no** `functionalArgs`
697
+
698
+ 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
@@ -4330,14 +4452,20 @@ var NeuraiAssetsBundle = (function (exports) {
4330
4452
  }
4331
4453
 
4332
4454
  /**
4333
- * Estimate transaction fee
4334
- * @param {number} inputCount - Number of inputs
4335
- * @param {number} outputCount - Number of outputs
4455
+ * Estimate transaction fee.
4456
+ *
4457
+ * Both arguments accept either a count (legacy) or an array of descriptors
4458
+ * that lets the underlying estimator distinguish PQ AuthScript inputs/outputs
4459
+ * from legacy P2PKH ones. Pass arrays whenever you have actual UTXOs and
4460
+ * output addresses on hand — counts produce a legacy-only estimate.
4461
+ *
4462
+ * @param {number|Array} inputs - Input count or array of UTXO-like descriptors
4463
+ * @param {number|Array} outputs - Output count or array of address-like descriptors
4336
4464
  * @returns {Promise<number>} Estimated fee in XNA
4337
4465
  */
4338
- async estimateFee(inputCount, outputCount) {
4466
+ async estimateFee(inputs, outputs) {
4339
4467
  const feeRate = await this.utxoSelector.getFeeRate();
4340
- return this.utxoSelector.estimateFee(inputCount, outputCount, feeRate);
4468
+ return this.utxoSelector.estimateFee(inputs, outputs, feeRate);
4341
4469
  }
4342
4470
 
4343
4471
  /**
@@ -4799,7 +4927,8 @@ var NeuraiAssetsBundle = (function (exports) {
4799
4927
  const changeAddress = await this.getChangeAddress();
4800
4928
 
4801
4929
  // 5. Estimate fee (rough estimate for initial UTXO selection)
4802
- const estimatedFee = await this.estimateFee(1, 3);
4930
+ const outputAddresses = [burnInfo.address, changeAddress, toAddress];
4931
+ const estimatedFee = await this.estimateFee(1, outputAddresses);
4803
4932
 
4804
4933
  // 6. Calculate total XNA needed
4805
4934
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -4809,8 +4938,8 @@ var NeuraiAssetsBundle = (function (exports) {
4809
4938
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
4810
4939
  const totalXNAInput = utxoSelection.totalXNA;
4811
4940
 
4812
- // 8. Recalculate fee with actual input count
4813
- const actualFee = await this.estimateFee(baseCurrencyUTXOs.length, 3);
4941
+ // 8. Recalculate fee with actual inputs (PQ-aware)
4942
+ const actualFee = await this.estimateFee(baseCurrencyUTXOs, outputAddresses);
4814
4943
 
4815
4944
  // 9. Verify we still have enough after fee recalculation
4816
4945
  const totalRequired = burnInfo.amount + actualFee;
@@ -5034,7 +5163,13 @@ var NeuraiAssetsBundle = (function (exports) {
5034
5163
  // 8. Estimate fee
5035
5164
  // Inputs: XNA UTXOs + owner token UTXO
5036
5165
  // Outputs: burn + change + owner token return + issue operation
5037
- const estimatedFee = await this.estimateFee(2, 4);
5166
+ const outputAddresses = [
5167
+ burnInfo.address,
5168
+ changeAddress,
5169
+ changeAddress, // owner token return goes to change address
5170
+ toAddress,
5171
+ ];
5172
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
5038
5173
 
5039
5174
  // 9. Calculate total XNA needed
5040
5175
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -5044,9 +5179,9 @@ var NeuraiAssetsBundle = (function (exports) {
5044
5179
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
5045
5180
  const totalXNAInput = utxoSelection.totalXNA;
5046
5181
 
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);
5182
+ // 11. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
5183
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
5184
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
5050
5185
 
5051
5186
  // 12. Verify we have enough XNA
5052
5187
  const totalRequired = burnInfo.amount + actualFee;
@@ -5248,14 +5383,15 @@ var NeuraiAssetsBundle = (function (exports) {
5248
5383
  const toAddress = await this.getToAddress();
5249
5384
  const changeAddress = await this.getChangeAddress();
5250
5385
 
5251
- const estimatedFee = await this.estimateFee(1, 3);
5386
+ const outputAddresses = [burnInfo.address, changeAddress, toAddress];
5387
+ const estimatedFee = await this.estimateFee(1, outputAddresses);
5252
5388
  const totalXNANeeded = burnInfo.amount + estimatedFee;
5253
5389
 
5254
5390
  const utxoSelection = await this.selectUTXOs(totalXNANeeded, null, 0);
5255
5391
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
5256
5392
  const totalXNAInput = utxoSelection.totalXNA;
5257
5393
 
5258
- const actualFee = await this.estimateFee(baseCurrencyUTXOs.length, 3);
5394
+ const actualFee = await this.estimateFee(baseCurrencyUTXOs, outputAddresses);
5259
5395
  const totalRequired = burnInfo.amount + actualFee;
5260
5396
 
5261
5397
  if (totalXNAInput < totalRequired) {
@@ -5470,9 +5606,14 @@ var NeuraiAssetsBundle = (function (exports) {
5470
5606
 
5471
5607
  // 8. Estimate fee
5472
5608
  // 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);
5609
+ // Outputs: burn + change + owner token return + reissue operation
5610
+ const outputAddresses = [
5611
+ burnInfo.address,
5612
+ changeAddress,
5613
+ changeAddress, // owner token return goes to change address
5614
+ toAddress,
5615
+ ];
5616
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
5476
5617
 
5477
5618
  // 9. Calculate total XNA needed
5478
5619
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -5482,9 +5623,9 @@ var NeuraiAssetsBundle = (function (exports) {
5482
5623
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
5483
5624
  const totalXNAInput = utxoSelection.totalXNA;
5484
5625
 
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);
5626
+ // 11. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
5627
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
5628
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
5488
5629
 
5489
5630
  // 12. Verify we have enough XNA
5490
5631
  const totalRequired = burnInfo.amount + actualFee;
@@ -5745,7 +5886,13 @@ var NeuraiAssetsBundle = (function (exports) {
5745
5886
  // 7. Estimate fee
5746
5887
  // Inputs: XNA UTXOs + owner token UTXO
5747
5888
  // Outputs: burn + change + owner token return + issue_unique operation
5748
- const estimatedFee = await this.estimateFee(2, 4);
5889
+ const outputAddresses = [
5890
+ burnInfo.address,
5891
+ changeAddress,
5892
+ changeAddress, // owner token return goes to change address
5893
+ toAddress,
5894
+ ];
5895
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
5749
5896
 
5750
5897
  // 8. Calculate total XNA needed
5751
5898
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -5755,9 +5902,9 @@ var NeuraiAssetsBundle = (function (exports) {
5755
5902
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
5756
5903
  const totalXNAInput = utxoSelection.totalXNA;
5757
5904
 
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);
5905
+ // 10. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
5906
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
5907
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
5761
5908
 
5762
5909
  // 11. Verify we have enough XNA
5763
5910
  const totalRequired = burnInfo.amount + actualFee;
@@ -6005,8 +6152,8 @@ var NeuraiAssetsBundle = (function (exports) {
6005
6152
  const changeAddress = await this.getChangeAddress();
6006
6153
 
6007
6154
  // 7. Estimate fee
6008
- const outputCount = 3;
6009
- const estimatedFee = await this.estimateFee(2, outputCount);
6155
+ const outputAddresses = [burnInfo.address, changeAddress, toAddress];
6156
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
6010
6157
 
6011
6158
  // 8. Calculate total XNA needed
6012
6159
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -6016,9 +6163,9 @@ var NeuraiAssetsBundle = (function (exports) {
6016
6163
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
6017
6164
  const totalXNAInput = utxoSelection.totalXNA;
6018
6165
 
6019
- // 10. Recalculate fee with actual input count
6020
- const actualInputCount = baseCurrencyUTXOs.length + parentQualifierUTXOs.length;
6021
- const actualFee = await this.estimateFee(actualInputCount, outputCount);
6166
+ // 10. Recalculate fee with actual inputs (PQ-aware), including parent qualifier UTXOs
6167
+ const actualFeeInputs = [...baseCurrencyUTXOs, ...parentQualifierUTXOs];
6168
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
6022
6169
 
6023
6170
  // 11. Verify we have enough XNA
6024
6171
  const totalRequired = burnInfo.amount + actualFee;
@@ -6257,7 +6404,13 @@ var NeuraiAssetsBundle = (function (exports) {
6257
6404
  }
6258
6405
 
6259
6406
  // 7. Estimate fee (+1 for owner token input)
6260
- const estimatedFee = await this.estimateFee(2, 4);
6407
+ const outputAddresses = [
6408
+ burnInfo.address,
6409
+ changeAddress,
6410
+ changeAddress, // owner token return goes to change address
6411
+ toAddress,
6412
+ ];
6413
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
6261
6414
 
6262
6415
  // 8. Calculate total XNA needed
6263
6416
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -6267,8 +6420,9 @@ var NeuraiAssetsBundle = (function (exports) {
6267
6420
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
6268
6421
  const totalXNAInput = utxoSelection.totalXNA;
6269
6422
 
6270
- // 10. Recalculate fee with actual input count (+1 for owner token)
6271
- const actualFee = await this.estimateFee(baseCurrencyUTXOs.length + 1, 4);
6423
+ // 10. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
6424
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
6425
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
6272
6426
 
6273
6427
  // 11. Verify we have enough XNA
6274
6428
  const totalRequired = burnInfo.amount + actualFee;
@@ -6525,7 +6679,13 @@ var NeuraiAssetsBundle = (function (exports) {
6525
6679
  const burnInfo = this.burnManager.getReissueBurn();
6526
6680
 
6527
6681
  // 8. Estimate fee
6528
- const estimatedFee = await this.estimateFee(2, 4);
6682
+ const outputAddresses = [
6683
+ burnInfo.address,
6684
+ changeAddress,
6685
+ changeAddress, // owner token return goes to change address
6686
+ toAddress,
6687
+ ];
6688
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
6529
6689
 
6530
6690
  // 9. Calculate total XNA needed
6531
6691
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -6535,9 +6695,9 @@ var NeuraiAssetsBundle = (function (exports) {
6535
6695
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
6536
6696
  const totalXNAInput = utxoSelection.totalXNA;
6537
6697
 
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);
6698
+ // 11. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
6699
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
6700
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
6541
6701
 
6542
6702
  // 12. Verify we have enough XNA
6543
6703
  const totalRequired = burnInfo.amount + actualFee;
@@ -6772,7 +6932,9 @@ var NeuraiAssetsBundle = (function (exports) {
6772
6932
  : this.burnManager.getTagAddressBurn(addressCount);
6773
6933
 
6774
6934
  // 6. Estimate fee
6775
- const estimatedFee = await this.estimateFee(2, 3);
6935
+ // Outputs: burn + XNA change + tag/untag operation (sent to changeAddress)
6936
+ const outputAddresses = [burnInfo.address, changeAddress, changeAddress];
6937
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
6776
6938
 
6777
6939
  // 7. Calculate total XNA needed
6778
6940
  const totalXNANeeded = burnInfo.amount + estimatedFee;
@@ -6782,9 +6944,9 @@ var NeuraiAssetsBundle = (function (exports) {
6782
6944
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
6783
6945
  const totalXNAInput = utxoSelection.totalXNA;
6784
6946
 
6785
- // 9. Recalculate fee with actual input count
6786
- const actualInputCount = baseCurrencyUTXOs.length + qualifierUTXOs.length;
6787
- const actualFee = await this.estimateFee(actualInputCount, 3);
6947
+ // 9. Recalculate fee with actual inputs (PQ-aware), including qualifier UTXOs
6948
+ const actualFeeInputs = [...baseCurrencyUTXOs, ...qualifierUTXOs];
6949
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
6788
6950
 
6789
6951
  // 10. Verify we have enough XNA
6790
6952
  const totalRequired = burnInfo.amount + actualFee;
@@ -7019,16 +7181,18 @@ var NeuraiAssetsBundle = (function (exports) {
7019
7181
  const burnAmount = 0;
7020
7182
 
7021
7183
  // 6. Estimate fee
7022
- const estimatedFee = await this.estimateFee(2, 3);
7184
+ // Outputs: XNA change + freeze/unfreeze operation (sent to changeAddress)
7185
+ const outputAddresses = [changeAddress, changeAddress];
7186
+ const estimatedFee = await this.estimateFee(2, outputAddresses);
7023
7187
 
7024
7188
  // 7. Select XNA UTXOs (only for fee, no burn)
7025
7189
  const utxoSelection = await this.selectUTXOs(estimatedFee, null, 0);
7026
7190
  const baseCurrencyUTXOs = utxoSelection.xnaUTXOs;
7027
7191
  const totalXNAInput = utxoSelection.totalXNA;
7028
7192
 
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);
7193
+ // 8. Recalculate fee with actual inputs (PQ-aware), including owner token UTXO
7194
+ const actualFeeInputs = [...baseCurrencyUTXOs, ownerTokenUTXO];
7195
+ const actualFee = await this.estimateFee(actualFeeInputs, outputAddresses);
7032
7196
 
7033
7197
  // 9. Verify we have enough XNA for fee
7034
7198
  if (totalXNAInput < actualFee) {