dkg.js 8.2.0 → 8.2.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.
@@ -219,6 +219,7 @@ export const DEFAULT_PARAMETERS = {
219
219
  SIMULATE_TXS: false,
220
220
  FORCE_REPLACE_TXS: false,
221
221
  GAS_LIMIT_MULTIPLIER: 1,
222
+ RETRY_TX_GAS_PRICE_MULTIPLIER: 3,
222
223
  };
223
224
 
224
225
  export const DEFAULT_GAS_PRICE = {
@@ -236,3 +237,5 @@ export const PARANET_KNOWLEDGE_ASSET_ACCESS_POLICY = {
236
237
  };
237
238
 
238
239
  export const CHUNK_BYTE_SIZE = 32;
240
+
241
+ export const FEE_HISTORY_BLOCK_COUNT = 5;
package/index.cjs CHANGED
@@ -273,6 +273,7 @@ const DEFAULT_PARAMETERS = {
273
273
  SIMULATE_TXS: false,
274
274
  FORCE_REPLACE_TXS: false,
275
275
  GAS_LIMIT_MULTIPLIER: 1,
276
+ RETRY_TX_GAS_PRICE_MULTIPLIER: 3,
276
277
  };
277
278
 
278
279
  const DEFAULT_GAS_PRICE = {
@@ -283,6 +284,8 @@ const DEFAULT_GAS_PRICE = {
283
284
 
284
285
  const CHUNK_BYTE_SIZE$1 = 32;
285
286
 
287
+ const FEE_HISTORY_BLOCK_COUNT = 5;
288
+
286
289
  function nodeSupported() {
287
290
  return typeof window === 'undefined';
288
291
  }
@@ -3507,10 +3510,10 @@ class BlockchainServiceBase {
3507
3510
  );
3508
3511
  gasLimit = Math.round(gasLimit * blockchain.gasLimitMultiplier);
3509
3512
 
3510
- let gasPrice;
3511
- if (blockchain.previousTxGasPrice && blockchain.retryTx) {
3512
- // Increase previous tx gas price by 20%
3513
- gasPrice = Math.round(blockchain.previousTxGasPrice * 1.2);
3513
+ // let gasPrice;
3514
+ /*if (blockchain.previousTxGasPrice && blockchain.retryTx) {
3515
+ // Increase previous tx gas price by retryTxGasPriceMultiplier
3516
+ gasPrice = Math.round(blockchain.previousTxGasPrice * blockchain.retryTxGasPriceMultiplier);
3514
3517
  } else if (blockchain.forceReplaceTxs) {
3515
3518
  // Get the current transaction count (nonce) of the wallet, including pending transactions
3516
3519
  const currentNonce = await web3Instance.eth.getTransactionCount(publicKey, 'pending');
@@ -3530,21 +3533,23 @@ class BlockchainServiceBase {
3530
3533
  );
3531
3534
 
3532
3535
  if (pendingTx) {
3533
- // If found, increase gas price of pending tx by 20%
3534
- gasPrice = Math.round(Number(pendingTx.gasPrice) * 1.2);
3536
+ // If found, increase gas price of pending tx by retryTxGasPriceMultiplier
3537
+ gasPrice = Math.round(Number(pendingTx.gasPrice) * blockchain.retryTxGasPriceMultiplier);
3535
3538
  } else {
3536
- // If not found, use default/network gas price increased by 20%
3539
+ // If not found, use default/network gas price increased by retryTxGasPriceMultiplier
3537
3540
  // Theoretically this should never happen
3538
3541
  gasPrice = Math.round(
3539
- (blockchain.gasPrice || (await this.getNetworkGasPrice(blockchain))) * 1.2,
3542
+ (blockchain.gasPrice || (await this.getSmartGasPrice(blockchain))) * blockchain.retryTxGasPriceMultiplier,
3540
3543
  );
3541
3544
  }
3542
3545
  } else {
3543
- gasPrice = blockchain.gasPrice || (await this.getNetworkGasPrice(blockchain));
3546
+ gasPrice = blockchain.gasPrice || (await this.getSmartGasPrice(blockchain));
3544
3547
  }
3545
3548
  } else {
3546
- gasPrice = blockchain.gasPrice || (await this.getNetworkGasPrice(blockchain));
3547
- }
3549
+ gasPrice = blockchain.gasPrice || (await this.getSmartGasPrice(blockchain));
3550
+ }*/
3551
+
3552
+ const gasPrice = blockchain.gasPrice ?? (await this.getSmartGasPrice(blockchain));
3548
3553
 
3549
3554
  if (blockchain.simulateTxs) {
3550
3555
  await web3Instance.eth.call({
@@ -3639,7 +3644,13 @@ class BlockchainServiceBase {
3639
3644
  }
3640
3645
  }
3641
3646
 
3642
- async waitForEventFinality(initialReceipt, eventName, expectedEventId, blockchain, confirmations = 1) {
3647
+ async waitForEventFinality(
3648
+ initialReceipt,
3649
+ eventName,
3650
+ expectedEventId,
3651
+ blockchain,
3652
+ confirmations = 1,
3653
+ ) {
3643
3654
  await this.ensureBlockchainInfo(blockchain);
3644
3655
  const web3Instance = await this.getWeb3Instance(blockchain);
3645
3656
 
@@ -3652,7 +3663,10 @@ class BlockchainServiceBase {
3652
3663
  // eslint-disable-next-line no-constant-condition
3653
3664
  while (true) {
3654
3665
  // 1. Wait until the block containing the tx is at the required depth
3655
- while (await web3Instance.eth.getBlockNumber() < receipt.blockNumber + confirmations) {
3666
+ while (
3667
+ (await web3Instance.eth.getBlockNumber()) <
3668
+ receipt.blockNumber + confirmations
3669
+ ) {
3656
3670
  await sleepForMilliseconds(polling);
3657
3671
  }
3658
3672
 
@@ -3674,7 +3688,9 @@ class BlockchainServiceBase {
3674
3688
 
3675
3689
  const idMatches =
3676
3690
  expectedEventId == null ||
3677
- (eventData && eventData.id != null && eventData.id.toString() === expectedEventId.toString());
3691
+ (eventData &&
3692
+ eventData.id != null &&
3693
+ eventData.id.toString() === expectedEventId.toString());
3678
3694
 
3679
3695
  if (eventData && idMatches) {
3680
3696
  return { receipt: currentReceipt, eventData };
@@ -3754,39 +3770,49 @@ class BlockchainServiceBase {
3754
3770
  );
3755
3771
  }
3756
3772
 
3773
+ async needsMoreAllowance(sender, tokenAmount, blockchain, knowledgeCollectionAddress) {
3774
+ const allowance = await this.callContractFunction(
3775
+ 'Token',
3776
+ 'allowance',
3777
+ [sender, knowledgeCollectionAddress],
3778
+ blockchain,
3779
+ );
3780
+
3781
+ if (BigInt(allowance) < BigInt(tokenAmount)) return true;
3782
+
3783
+ return false;
3784
+ }
3785
+
3786
+ async maxAllowancePerTransaction(sender, blockchain) {
3787
+ if (blockchain.maxAllowance) {
3788
+ return blockchain.maxAllowance;
3789
+ } else {
3790
+ return await this.callContractFunction('Token', 'balanceOf', [sender], blockchain);
3791
+ }
3792
+ }
3793
+
3757
3794
  async increaseKnowledgeCollectionAllowance(sender, tokenAmount, blockchain) {
3758
3795
  const knowledgeCollectionAddress = await this.getContractAddress(
3759
3796
  'KnowledgeCollection',
3760
3797
  blockchain,
3761
3798
  );
3762
3799
 
3763
- const allowance = await this.callContractFunction(
3764
- 'Token',
3765
- 'allowance',
3766
- [sender, knowledgeCollectionAddress],
3800
+ const needsMoreAllowance = await this.needsMoreAllowance(
3801
+ sender,
3802
+ tokenAmount,
3767
3803
  blockchain,
3804
+ knowledgeCollectionAddress,
3768
3805
  );
3769
3806
 
3770
- const allowanceGap = BigInt(tokenAmount) - BigInt(allowance);
3771
-
3772
- if (allowanceGap > 0) {
3807
+ if (needsMoreAllowance) {
3808
+ const allowanceThreshold = await this.maxAllowancePerTransaction(sender, blockchain);
3773
3809
  await this.executeContractFunction(
3774
3810
  'Token',
3775
- 'increaseAllowance',
3776
- [knowledgeCollectionAddress, allowanceGap],
3811
+ 'approve',
3812
+ [knowledgeCollectionAddress, allowanceThreshold],
3777
3813
  blockchain,
3778
3814
  );
3779
-
3780
- return {
3781
- allowanceIncreased: true,
3782
- allowanceGap,
3783
- };
3784
3815
  }
3785
-
3786
- return {
3787
- allowanceIncreased: false,
3788
- allowanceGap,
3789
- };
3790
3816
  }
3791
3817
 
3792
3818
  // Knowledge assets operations
@@ -3799,19 +3825,16 @@ class BlockchainServiceBase {
3799
3825
  stepHooks = emptyHooks$1,
3800
3826
  ) {
3801
3827
  const sender = await this.getPublicKey(blockchain);
3802
- let allowanceIncreased = false;
3803
- let allowanceGap = 0;
3804
3828
 
3805
3829
  try {
3806
3830
  if (requestData?.paymaster && requestData?.paymaster !== ZERO_ADDRESS) {
3807
3831
  // Handle the case when payer is passed
3808
3832
  } else {
3809
- ({ allowanceIncreased, allowanceGap } =
3810
- await this.increaseKnowledgeCollectionAllowance(
3811
- sender,
3812
- requestData.tokenAmount,
3813
- blockchain,
3814
- ));
3833
+ await this.increaseKnowledgeCollectionAllowance(
3834
+ sender,
3835
+ requestData.tokenAmount,
3836
+ blockchain,
3837
+ );
3815
3838
  }
3816
3839
 
3817
3840
  stepHooks.afterHook({
@@ -3850,9 +3873,7 @@ class BlockchainServiceBase {
3850
3873
 
3851
3874
  return { knowledgeCollectionId: id, receipt };
3852
3875
  } catch (error) {
3853
- if (allowanceIncreased) {
3854
- await this.decreaseKnowledgeCollectionAllowance(allowanceGap, blockchain);
3855
- }
3876
+ console.error('createKnowledgeCollection failed:', error);
3856
3877
  throw error;
3857
3878
  }
3858
3879
  }
@@ -4693,6 +4714,111 @@ class BlockchainServiceBase {
4693
4714
  }
4694
4715
  }
4695
4716
 
4717
+ /**
4718
+ * Get fee history from the last N blocks using eth_feeHistory RPC call
4719
+ * @param {Object} blockchain - Blockchain configuration
4720
+ * @param {number} blockCount - Number of blocks to fetch (default: 5)
4721
+ * @returns {Promise<Object>} Fee history data with baseFeePerGas and priorityFees arrays
4722
+ */
4723
+ async getFeeHistory(blockchain, blockCount = 5) {
4724
+ await this.ensureBlockchainInfo(blockchain);
4725
+ const web3Instance = await this.getWeb3Instance(blockchain);
4726
+
4727
+ try {
4728
+ // eth_feeHistory params: blockCount, newestBlock, rewardPercentiles
4729
+ // [50] = median priority fee per block
4730
+ const feeHistory = await web3Instance.eth.getFeeHistory(blockCount, 'latest', [50]);
4731
+
4732
+ // Extract median priority fees from each block (reward[blockIndex][percentileIndex])
4733
+ const priorityFees = feeHistory.reward
4734
+ ? feeHistory.reward.map((blockRewards) => BigInt(blockRewards[0] || 0))
4735
+ : [];
4736
+
4737
+ return {
4738
+ supported: true,
4739
+ oldestBlock: parseInt(feeHistory.oldestBlock, 16),
4740
+ baseFeePerGas: feeHistory.baseFeePerGas.map((bf) => BigInt(bf)),
4741
+ priorityFees,
4742
+ };
4743
+ } catch (error) {
4744
+ // eth_feeHistory not supported on this network
4745
+ return {
4746
+ supported: false,
4747
+ error: error.message,
4748
+ };
4749
+ }
4750
+ }
4751
+
4752
+ /**
4753
+ * Apply buffer percentage to a gas price
4754
+ * @param {BigInt} gasPrice - Gas price in wei
4755
+ * @param {number} gasPriceBufferPercent - Buffer percentage to add
4756
+ * @returns {BigInt} Gas price with buffer applied
4757
+ */
4758
+ applyGasPriceBuffer(gasPrice, gasPriceBufferPercent) {
4759
+ if (!gasPriceBufferPercent) return gasPrice;
4760
+ return (gasPrice * BigInt(100 + Number(gasPriceBufferPercent))) / 100n;
4761
+ }
4762
+
4763
+ /**
4764
+ * Estimate safe gas price using eth_feeHistory (EIP-1559 style)
4765
+ * Takes max base fee from last N blocks, adds a buffer for volatility,
4766
+ * and includes the priority fee (tip) for validator incentive
4767
+ * @param {Object} blockchain - Blockchain configuration
4768
+ * @returns {Promise<BigInt>} Estimated gas price in wei
4769
+ */
4770
+ async estimateGasPriceFromFeeHistory(blockchain) {
4771
+ const { gasPriceBufferPercent } = blockchain;
4772
+ const feeHistory = await this.getFeeHistory(blockchain, FEE_HISTORY_BLOCK_COUNT);
4773
+
4774
+ // Fallback to network gas price if feeHistory not supported or empty
4775
+ if (!feeHistory.supported) {
4776
+ return this.applyGasPriceBuffer(
4777
+ BigInt(await this.getNetworkGasPrice(blockchain)),
4778
+ gasPriceBufferPercent,
4779
+ );
4780
+ }
4781
+
4782
+ const baseFees = Array.from(feeHistory.baseFeePerGas);
4783
+ const priorityFees = Array.from(feeHistory.priorityFees);
4784
+
4785
+ if (baseFees.length === 0 || priorityFees.length === 0) {
4786
+ return this.applyGasPriceBuffer(
4787
+ BigInt(await this.getNetworkGasPrice(blockchain)),
4788
+ gasPriceBufferPercent,
4789
+ );
4790
+ }
4791
+
4792
+ // Find max base fee and priority fee from recent blocks
4793
+ const maxBaseFee = baseFees.reduce((max, bf) => (bf > max ? bf : max), 0n);
4794
+ const maxPriorityFee = priorityFees.reduce((max, pf) => (pf > max ? pf : max), 0n);
4795
+
4796
+ return this.applyGasPriceBuffer(maxBaseFee + maxPriorityFee, gasPriceBufferPercent);
4797
+ }
4798
+
4799
+ /**
4800
+ * Get gas price with EIP-1559 estimation (with fallback)
4801
+ * Tries eth_feeHistory first, falls back to legacy methods
4802
+ * @param {Object} blockchain - Blockchain configuration
4803
+ * @returns {Promise<string>} Gas price in wei (as string for web3 compatibility)
4804
+ */
4805
+ async getSmartGasPrice(blockchain) {
4806
+ try {
4807
+ const estimatedPrice = await this.estimateGasPriceFromFeeHistory(blockchain);
4808
+ return estimatedPrice.toString();
4809
+ } catch (eip1559Error) {
4810
+ try {
4811
+ return await this.getNetworkGasPrice(blockchain);
4812
+ } catch (fallbackError) {
4813
+ throw new Error(
4814
+ `All gas price estimation methods failed. ` +
4815
+ `EIP-1559: ${eip1559Error?.message || 'N/A'}. ` +
4816
+ `Fallback: ${fallbackError.message}`,
4817
+ );
4818
+ }
4819
+ }
4820
+ }
4821
+
4696
4822
  async getWalletBalances(blockchain) {
4697
4823
  await this.ensureBlockchainInfo(blockchain);
4698
4824
  const web3Instance = await this.getWeb3Instance(blockchain);
@@ -6105,6 +6231,17 @@ class InputService {
6105
6231
  BLOCKCHAINS[environment][name]?.gasPriceOracleLink ??
6106
6232
  undefined;
6107
6233
 
6234
+ const maxAllowance =
6235
+ options.blockchain?.maxAllowance ?? this.config.blockchain?.maxAllowance ?? undefined;
6236
+ const gasPriceBufferPercent =
6237
+ options.blockchain?.gasPriceBufferPercent ??
6238
+ this.config.blockchain?.gasPriceBufferPercent ??
6239
+ undefined;
6240
+ const retryTxGasPriceMultiplier =
6241
+ options.blockchain?.retryTxGasPriceMultiplier ??
6242
+ this.config.blockchain?.retryTxGasPriceMultiplier ??
6243
+ DEFAULT_PARAMETERS.RETRY_TX_GAS_PRICE_MULTIPLIER; // e.g., 1.2
6244
+
6108
6245
  const blockchainConfig = {
6109
6246
  name,
6110
6247
  rpc,
@@ -6118,6 +6255,9 @@ class InputService {
6118
6255
  simulateTxs,
6119
6256
  forceReplaceTxs,
6120
6257
  gasPriceOracleLink,
6258
+ maxAllowance,
6259
+ gasPriceBufferPercent,
6260
+ retryTxGasPriceMultiplier,
6121
6261
  };
6122
6262
 
6123
6263
  if (name && name.startsWith('otp')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dkg.js",
3
- "version": "8.2.0",
3
+ "version": "8.2.1",
4
4
  "description": "Javascript library for interaction with the OriginTrail Decentralized Knowledge Graph",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -63,6 +63,7 @@
63
63
  "os-browserify": "^0.3.0",
64
64
  "prettier": "^2.7.1",
65
65
  "rollup": "^4.28.1",
66
+ "@rollup/rollup-linux-x64-gnu": "^4.53.1",
66
67
  "stream-browserify": "^3.0.0",
67
68
  "stream-http": "^3.2.0",
68
69
  "terser-webpack-plugin": "^5.3.6",
@@ -7,9 +7,9 @@ import { createRequire } from 'module';
7
7
  import {
8
8
  OPERATIONS_STEP_STATUS,
9
9
  DEFAULT_GAS_PRICE,
10
- DEFAULT_GAS_PRICE_WEI,
11
10
  ZERO_ADDRESS,
12
11
  NEUROWEB_INCENTIVE_TYPE_CHAINS,
12
+ FEE_HISTORY_BLOCK_COUNT,
13
13
  } from '../../constants/constants.js';
14
14
  import emptyHooks from '../../util/empty-hooks.js';
15
15
  import { sleepForMilliseconds } from '../utilities.js';
@@ -185,10 +185,10 @@ export default class BlockchainServiceBase {
185
185
  );
186
186
  gasLimit = Math.round(gasLimit * blockchain.gasLimitMultiplier);
187
187
 
188
- let gasPrice;
189
- if (blockchain.previousTxGasPrice && blockchain.retryTx) {
190
- // Increase previous tx gas price by 20%
191
- gasPrice = Math.round(blockchain.previousTxGasPrice * 1.2);
188
+ // let gasPrice;
189
+ /*if (blockchain.previousTxGasPrice && blockchain.retryTx) {
190
+ // Increase previous tx gas price by retryTxGasPriceMultiplier
191
+ gasPrice = Math.round(blockchain.previousTxGasPrice * blockchain.retryTxGasPriceMultiplier);
192
192
  } else if (blockchain.forceReplaceTxs) {
193
193
  // Get the current transaction count (nonce) of the wallet, including pending transactions
194
194
  const currentNonce = await web3Instance.eth.getTransactionCount(publicKey, 'pending');
@@ -208,21 +208,23 @@ export default class BlockchainServiceBase {
208
208
  );
209
209
 
210
210
  if (pendingTx) {
211
- // If found, increase gas price of pending tx by 20%
212
- gasPrice = Math.round(Number(pendingTx.gasPrice) * 1.2);
211
+ // If found, increase gas price of pending tx by retryTxGasPriceMultiplier
212
+ gasPrice = Math.round(Number(pendingTx.gasPrice) * blockchain.retryTxGasPriceMultiplier);
213
213
  } else {
214
- // If not found, use default/network gas price increased by 20%
214
+ // If not found, use default/network gas price increased by retryTxGasPriceMultiplier
215
215
  // Theoretically this should never happen
216
216
  gasPrice = Math.round(
217
- (blockchain.gasPrice || (await this.getNetworkGasPrice(blockchain))) * 1.2,
217
+ (blockchain.gasPrice || (await this.getSmartGasPrice(blockchain))) * blockchain.retryTxGasPriceMultiplier,
218
218
  );
219
219
  }
220
220
  } else {
221
- gasPrice = blockchain.gasPrice || (await this.getNetworkGasPrice(blockchain));
221
+ gasPrice = blockchain.gasPrice || (await this.getSmartGasPrice(blockchain));
222
222
  }
223
223
  } else {
224
- gasPrice = blockchain.gasPrice || (await this.getNetworkGasPrice(blockchain));
225
- }
224
+ gasPrice = blockchain.gasPrice || (await this.getSmartGasPrice(blockchain));
225
+ }*/
226
+
227
+ const gasPrice = blockchain.gasPrice ?? (await this.getSmartGasPrice(blockchain));
226
228
 
227
229
  if (blockchain.simulateTxs) {
228
230
  await web3Instance.eth.call({
@@ -317,7 +319,13 @@ export default class BlockchainServiceBase {
317
319
  }
318
320
  }
319
321
 
320
- async waitForEventFinality(initialReceipt, eventName, expectedEventId, blockchain, confirmations = 1) {
322
+ async waitForEventFinality(
323
+ initialReceipt,
324
+ eventName,
325
+ expectedEventId,
326
+ blockchain,
327
+ confirmations = 1,
328
+ ) {
321
329
  await this.ensureBlockchainInfo(blockchain);
322
330
  const web3Instance = await this.getWeb3Instance(blockchain);
323
331
 
@@ -330,7 +338,10 @@ export default class BlockchainServiceBase {
330
338
  // eslint-disable-next-line no-constant-condition
331
339
  while (true) {
332
340
  // 1. Wait until the block containing the tx is at the required depth
333
- while (await web3Instance.eth.getBlockNumber() < receipt.blockNumber + confirmations) {
341
+ while (
342
+ (await web3Instance.eth.getBlockNumber()) <
343
+ receipt.blockNumber + confirmations
344
+ ) {
334
345
  await sleepForMilliseconds(polling);
335
346
  }
336
347
 
@@ -352,7 +363,9 @@ export default class BlockchainServiceBase {
352
363
 
353
364
  const idMatches =
354
365
  expectedEventId == null ||
355
- (eventData && eventData.id != null && eventData.id.toString() === expectedEventId.toString());
366
+ (eventData &&
367
+ eventData.id != null &&
368
+ eventData.id.toString() === expectedEventId.toString());
356
369
 
357
370
  if (eventData && idMatches) {
358
371
  return { receipt: currentReceipt, eventData };
@@ -432,39 +445,49 @@ export default class BlockchainServiceBase {
432
445
  );
433
446
  }
434
447
 
448
+ async needsMoreAllowance(sender, tokenAmount, blockchain, knowledgeCollectionAddress) {
449
+ const allowance = await this.callContractFunction(
450
+ 'Token',
451
+ 'allowance',
452
+ [sender, knowledgeCollectionAddress],
453
+ blockchain,
454
+ );
455
+
456
+ if (BigInt(allowance) < BigInt(tokenAmount)) return true;
457
+
458
+ return false;
459
+ }
460
+
461
+ async maxAllowancePerTransaction(sender, blockchain) {
462
+ if (blockchain.maxAllowance) {
463
+ return blockchain.maxAllowance;
464
+ } else {
465
+ return await this.callContractFunction('Token', 'balanceOf', [sender], blockchain);
466
+ }
467
+ }
468
+
435
469
  async increaseKnowledgeCollectionAllowance(sender, tokenAmount, blockchain) {
436
470
  const knowledgeCollectionAddress = await this.getContractAddress(
437
471
  'KnowledgeCollection',
438
472
  blockchain,
439
473
  );
440
474
 
441
- const allowance = await this.callContractFunction(
442
- 'Token',
443
- 'allowance',
444
- [sender, knowledgeCollectionAddress],
475
+ const needsMoreAllowance = await this.needsMoreAllowance(
476
+ sender,
477
+ tokenAmount,
445
478
  blockchain,
479
+ knowledgeCollectionAddress,
446
480
  );
447
481
 
448
- const allowanceGap = BigInt(tokenAmount) - BigInt(allowance);
449
-
450
- if (allowanceGap > 0) {
482
+ if (needsMoreAllowance) {
483
+ const allowanceThreshold = await this.maxAllowancePerTransaction(sender, blockchain);
451
484
  await this.executeContractFunction(
452
485
  'Token',
453
- 'increaseAllowance',
454
- [knowledgeCollectionAddress, allowanceGap],
486
+ 'approve',
487
+ [knowledgeCollectionAddress, allowanceThreshold],
455
488
  blockchain,
456
489
  );
457
-
458
- return {
459
- allowanceIncreased: true,
460
- allowanceGap,
461
- };
462
490
  }
463
-
464
- return {
465
- allowanceIncreased: false,
466
- allowanceGap,
467
- };
468
491
  }
469
492
 
470
493
  // Knowledge assets operations
@@ -477,19 +500,16 @@ export default class BlockchainServiceBase {
477
500
  stepHooks = emptyHooks,
478
501
  ) {
479
502
  const sender = await this.getPublicKey(blockchain);
480
- let allowanceIncreased = false;
481
- let allowanceGap = 0;
482
503
 
483
504
  try {
484
505
  if (requestData?.paymaster && requestData?.paymaster !== ZERO_ADDRESS) {
485
506
  // Handle the case when payer is passed
486
507
  } else {
487
- ({ allowanceIncreased, allowanceGap } =
488
- await this.increaseKnowledgeCollectionAllowance(
489
- sender,
490
- requestData.tokenAmount,
491
- blockchain,
492
- ));
508
+ await this.increaseKnowledgeCollectionAllowance(
509
+ sender,
510
+ requestData.tokenAmount,
511
+ blockchain,
512
+ );
493
513
  }
494
514
 
495
515
  stepHooks.afterHook({
@@ -528,9 +548,7 @@ export default class BlockchainServiceBase {
528
548
 
529
549
  return { knowledgeCollectionId: id, receipt };
530
550
  } catch (error) {
531
- if (allowanceIncreased) {
532
- await this.decreaseKnowledgeCollectionAllowance(allowanceGap, blockchain);
533
- }
551
+ console.error('createKnowledgeCollection failed:', error);
534
552
  throw error;
535
553
  }
536
554
  }
@@ -1371,6 +1389,111 @@ export default class BlockchainServiceBase {
1371
1389
  }
1372
1390
  }
1373
1391
 
1392
+ /**
1393
+ * Get fee history from the last N blocks using eth_feeHistory RPC call
1394
+ * @param {Object} blockchain - Blockchain configuration
1395
+ * @param {number} blockCount - Number of blocks to fetch (default: 5)
1396
+ * @returns {Promise<Object>} Fee history data with baseFeePerGas and priorityFees arrays
1397
+ */
1398
+ async getFeeHistory(blockchain, blockCount = 5) {
1399
+ await this.ensureBlockchainInfo(blockchain);
1400
+ const web3Instance = await this.getWeb3Instance(blockchain);
1401
+
1402
+ try {
1403
+ // eth_feeHistory params: blockCount, newestBlock, rewardPercentiles
1404
+ // [50] = median priority fee per block
1405
+ const feeHistory = await web3Instance.eth.getFeeHistory(blockCount, 'latest', [50]);
1406
+
1407
+ // Extract median priority fees from each block (reward[blockIndex][percentileIndex])
1408
+ const priorityFees = feeHistory.reward
1409
+ ? feeHistory.reward.map((blockRewards) => BigInt(blockRewards[0] || 0))
1410
+ : [];
1411
+
1412
+ return {
1413
+ supported: true,
1414
+ oldestBlock: parseInt(feeHistory.oldestBlock, 16),
1415
+ baseFeePerGas: feeHistory.baseFeePerGas.map((bf) => BigInt(bf)),
1416
+ priorityFees,
1417
+ };
1418
+ } catch (error) {
1419
+ // eth_feeHistory not supported on this network
1420
+ return {
1421
+ supported: false,
1422
+ error: error.message,
1423
+ };
1424
+ }
1425
+ }
1426
+
1427
+ /**
1428
+ * Apply buffer percentage to a gas price
1429
+ * @param {BigInt} gasPrice - Gas price in wei
1430
+ * @param {number} gasPriceBufferPercent - Buffer percentage to add
1431
+ * @returns {BigInt} Gas price with buffer applied
1432
+ */
1433
+ applyGasPriceBuffer(gasPrice, gasPriceBufferPercent) {
1434
+ if (!gasPriceBufferPercent) return gasPrice;
1435
+ return (gasPrice * BigInt(100 + Number(gasPriceBufferPercent))) / 100n;
1436
+ }
1437
+
1438
+ /**
1439
+ * Estimate safe gas price using eth_feeHistory (EIP-1559 style)
1440
+ * Takes max base fee from last N blocks, adds a buffer for volatility,
1441
+ * and includes the priority fee (tip) for validator incentive
1442
+ * @param {Object} blockchain - Blockchain configuration
1443
+ * @returns {Promise<BigInt>} Estimated gas price in wei
1444
+ */
1445
+ async estimateGasPriceFromFeeHistory(blockchain) {
1446
+ const { gasPriceBufferPercent } = blockchain;
1447
+ const feeHistory = await this.getFeeHistory(blockchain, FEE_HISTORY_BLOCK_COUNT);
1448
+
1449
+ // Fallback to network gas price if feeHistory not supported or empty
1450
+ if (!feeHistory.supported) {
1451
+ return this.applyGasPriceBuffer(
1452
+ BigInt(await this.getNetworkGasPrice(blockchain)),
1453
+ gasPriceBufferPercent,
1454
+ );
1455
+ }
1456
+
1457
+ const baseFees = Array.from(feeHistory.baseFeePerGas);
1458
+ const priorityFees = Array.from(feeHistory.priorityFees);
1459
+
1460
+ if (baseFees.length === 0 || priorityFees.length === 0) {
1461
+ return this.applyGasPriceBuffer(
1462
+ BigInt(await this.getNetworkGasPrice(blockchain)),
1463
+ gasPriceBufferPercent,
1464
+ );
1465
+ }
1466
+
1467
+ // Find max base fee and priority fee from recent blocks
1468
+ const maxBaseFee = baseFees.reduce((max, bf) => (bf > max ? bf : max), 0n);
1469
+ const maxPriorityFee = priorityFees.reduce((max, pf) => (pf > max ? pf : max), 0n);
1470
+
1471
+ return this.applyGasPriceBuffer(maxBaseFee + maxPriorityFee, gasPriceBufferPercent);
1472
+ }
1473
+
1474
+ /**
1475
+ * Get gas price with EIP-1559 estimation (with fallback)
1476
+ * Tries eth_feeHistory first, falls back to legacy methods
1477
+ * @param {Object} blockchain - Blockchain configuration
1478
+ * @returns {Promise<string>} Gas price in wei (as string for web3 compatibility)
1479
+ */
1480
+ async getSmartGasPrice(blockchain) {
1481
+ try {
1482
+ const estimatedPrice = await this.estimateGasPriceFromFeeHistory(blockchain);
1483
+ return estimatedPrice.toString();
1484
+ } catch (eip1559Error) {
1485
+ try {
1486
+ return await this.getNetworkGasPrice(blockchain);
1487
+ } catch (fallbackError) {
1488
+ throw new Error(
1489
+ `All gas price estimation methods failed. ` +
1490
+ `EIP-1559: ${eip1559Error?.message || 'N/A'}. ` +
1491
+ `Fallback: ${fallbackError.message}`,
1492
+ );
1493
+ }
1494
+ }
1495
+ }
1496
+
1374
1497
  async getWalletBalances(blockchain) {
1375
1498
  await this.ensureBlockchainInfo(blockchain);
1376
1499
  const web3Instance = await this.getWeb3Instance(blockchain);
@@ -192,6 +192,17 @@ export default class InputService {
192
192
  BLOCKCHAINS[environment][name]?.gasPriceOracleLink ??
193
193
  undefined;
194
194
 
195
+ const maxAllowance =
196
+ options.blockchain?.maxAllowance ?? this.config.blockchain?.maxAllowance ?? undefined;
197
+ const gasPriceBufferPercent =
198
+ options.blockchain?.gasPriceBufferPercent ??
199
+ this.config.blockchain?.gasPriceBufferPercent ??
200
+ undefined;
201
+ const retryTxGasPriceMultiplier =
202
+ options.blockchain?.retryTxGasPriceMultiplier ??
203
+ this.config.blockchain?.retryTxGasPriceMultiplier ??
204
+ DEFAULT_PARAMETERS.RETRY_TX_GAS_PRICE_MULTIPLIER; // e.g., 1.2
205
+
195
206
  const blockchainConfig = {
196
207
  name,
197
208
  rpc,
@@ -205,6 +216,9 @@ export default class InputService {
205
216
  simulateTxs,
206
217
  forceReplaceTxs,
207
218
  gasPriceOracleLink,
219
+ maxAllowance,
220
+ gasPriceBufferPercent,
221
+ retryTxGasPriceMultiplier,
208
222
  };
209
223
 
210
224
  if (name && name.startsWith('otp')) {