@@ -7,9 +7,9 @@ import { createRequire } from 'module';
77import {
88 OPERATIONS_STEP_STATUS ,
99 DEFAULT_GAS_PRICE ,
10- DEFAULT_GAS_PRICE_WEI ,
1110 ZERO_ADDRESS ,
1211 NEUROWEB_INCENTIVE_TYPE_CHAINS ,
12+ FEE_HISTORY_BLOCK_COUNT ,
1313} from '../../constants/constants.js' ;
1414import emptyHooks from '../../util/empty-hooks.js' ;
1515import { sleepForMilliseconds } from '../utilities.js' ;
@@ -185,10 +185,10 @@ export default class BlockchainServiceBase {
185185 ) ;
186186 gasLimit = Math . round ( gasLimit * blockchain . gasLimitMultiplier ) ;
187187
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 );
192192 } else if (blockchain.forceReplaceTxs) {
193193 // Get the current transaction count (nonce) of the wallet, including pending transactions
194194 const currentNonce = await web3Instance.eth.getTransactionCount(publicKey, 'pending');
@@ -208,21 +208,23 @@ export default class BlockchainServiceBase {
208208 );
209209
210210 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 );
213213 } 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
215215 // Theoretically this should never happen
216216 gasPrice = Math.round(
217- ( blockchain . gasPrice || ( await this . getNetworkGasPrice ( blockchain ) ) ) * 1.2 ,
217+ (blockchain.gasPrice || (await this.getSmartGasPrice (blockchain))) * blockchain.retryTxGasPriceMultiplier ,
218218 );
219219 }
220220 } else {
221- gasPrice = blockchain . gasPrice || ( await this . getNetworkGasPrice ( blockchain ) ) ;
221+ gasPrice = blockchain.gasPrice || (await this.getSmartGasPrice (blockchain));
222222 }
223223 } 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 ) ) ;
226228
227229 if ( blockchain . simulateTxs ) {
228230 await web3Instance . eth . call ( {
@@ -317,7 +319,13 @@ export default class BlockchainServiceBase {
317319 }
318320 }
319321
320- async waitForEventFinality ( initialReceipt , eventName , expectedEventId , blockchain , confirmations = 1 ) {
322+ async waitForEventFinality (
323+ initialReceipt ,
324+ eventName ,
325+ expectedEventId ,
326+ blockchain ,
327+ confirmations = 1 ,
328+ ) {
321329 await this . ensureBlockchainInfo ( blockchain ) ;
322330 const web3Instance = await this . getWeb3Instance ( blockchain ) ;
323331
@@ -330,7 +338,10 @@ export default class BlockchainServiceBase {
330338 // eslint-disable-next-line no-constant-condition
331339 while ( true ) {
332340 // 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+ ) {
334345 await sleepForMilliseconds ( polling ) ;
335346 }
336347
@@ -352,7 +363,9 @@ export default class BlockchainServiceBase {
352363
353364 const idMatches =
354365 expectedEventId == null ||
355- ( eventData && eventData . id != null && eventData . id . toString ( ) === expectedEventId . toString ( ) ) ;
366+ ( eventData &&
367+ eventData . id != null &&
368+ eventData . id . toString ( ) === expectedEventId . toString ( ) ) ;
356369
357370 if ( eventData && idMatches ) {
358371 return { receipt : currentReceipt , eventData } ;
@@ -432,39 +445,49 @@ export default class BlockchainServiceBase {
432445 ) ;
433446 }
434447
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+
435469 async increaseKnowledgeCollectionAllowance ( sender , tokenAmount , blockchain ) {
436470 const knowledgeCollectionAddress = await this . getContractAddress (
437471 'KnowledgeCollection' ,
438472 blockchain ,
439473 ) ;
440474
441- const allowance = await this . callContractFunction (
442- 'Token' ,
443- 'allowance' ,
444- [ sender , knowledgeCollectionAddress ] ,
475+ const needsMoreAllowance = await this . needsMoreAllowance (
476+ sender ,
477+ tokenAmount ,
445478 blockchain ,
479+ knowledgeCollectionAddress ,
446480 ) ;
447481
448- const allowanceGap = BigInt ( tokenAmount ) - BigInt ( allowance ) ;
449-
450- if ( allowanceGap > 0 ) {
482+ if ( needsMoreAllowance ) {
483+ const allowanceThreshold = await this . maxAllowancePerTransaction ( sender , blockchain ) ;
451484 await this . executeContractFunction (
452485 'Token' ,
453- 'increaseAllowance ' ,
454- [ knowledgeCollectionAddress , allowanceGap ] ,
486+ 'approve ' ,
487+ [ knowledgeCollectionAddress , allowanceThreshold ] ,
455488 blockchain ,
456489 ) ;
457-
458- return {
459- allowanceIncreased : true ,
460- allowanceGap,
461- } ;
462490 }
463-
464- return {
465- allowanceIncreased : false ,
466- allowanceGap,
467- } ;
468491 }
469492
470493 // Knowledge assets operations
@@ -477,19 +500,16 @@ export default class BlockchainServiceBase {
477500 stepHooks = emptyHooks ,
478501 ) {
479502 const sender = await this . getPublicKey ( blockchain ) ;
480- let allowanceIncreased = false ;
481- let allowanceGap = 0 ;
482503
483504 try {
484505 if ( requestData ?. paymaster && requestData ?. paymaster !== ZERO_ADDRESS ) {
485506 // Handle the case when payer is passed
486507 } 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+ ) ;
493513 }
494514
495515 stepHooks . afterHook ( {
@@ -528,9 +548,7 @@ export default class BlockchainServiceBase {
528548
529549 return { knowledgeCollectionId : id , receipt } ;
530550 } catch ( error ) {
531- if ( allowanceIncreased ) {
532- await this . decreaseKnowledgeCollectionAllowance ( allowanceGap , blockchain ) ;
533- }
551+ console . error ( 'createKnowledgeCollection failed:' , error ) ;
534552 throw error ;
535553 }
536554 }
@@ -1371,6 +1389,111 @@ export default class BlockchainServiceBase {
13711389 }
13721390 }
13731391
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+
13741497 async getWalletBalances ( blockchain ) {
13751498 await this . ensureBlockchainInfo ( blockchain ) ;
13761499 const web3Instance = await this . getWeb3Instance ( blockchain ) ;
0 commit comments