diff --git a/docs/docs/guides/02_web3_providers_guide/index.md b/docs/docs/guides/02_web3_providers_guide/index.md index b17c5622531..23937a5b84f 100644 --- a/docs/docs/guides/02_web3_providers_guide/index.md +++ b/docs/docs/guides/02_web3_providers_guide/index.md @@ -111,6 +111,28 @@ await web3.eth.getBlockNumber(); // ↳ 18849658n ``` +#### Websocket Providers will only terminate when closed + +When connected to a WebSocket provider, the program will not automatically terminate after the code finishes running. This is to ensure the WebSocket connection remains open, allowing the program to continue listening for events. + +The program will remain active until you explicitly disconnect from the WebSocket provider: + +```ts +const web3 = new Web3(wsUrl); +// The program will keep running to listen for events. +``` + +#### Terminating the Program + +When you're ready to stop the program, you can manually disconnect from the WebSocket provider by calling the disconnect method. This will properly close the connection and terminate the program: + +```ts +const web3 = new Web3(wsUrl); +// When you are ready to terminate your program +web3.currentProvider?.disconnect(); +// The program will now terminate +``` + #### Configuring WebSocket Providers The [`WebSocketProvider` constructor](/api/web3-providers-ws/class/WebSocketProvider#constructor) accepts two optional parameters that can be used to configure the behavior of the `WebSocketProvider`: the first parameter must be of type [`ClientRequestArgs`](https://microsoft.github.io/PowerBI-JavaScript/interfaces/_node_modules__types_node_http_d_._http_.clientrequestargs.html) or of [`ClientOptions`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e5ee5eae6a592198e469ad9f412bab8d223fcbb6/types/ws/index.d.ts#L243) and the second parameter must be of type [`ReconnectOptions`](/api/web3/namespace/utils#ReconnectOptions). diff --git a/docs/docs/guides/05_smart_contracts/tips_and_tricks.md b/docs/docs/guides/05_smart_contracts/tips_and_tricks.md index 189daec7727..35964550ca5 100644 --- a/docs/docs/guides/05_smart_contracts/tips_and_tricks.md +++ b/docs/docs/guides/05_smart_contracts/tips_and_tricks.md @@ -9,6 +9,19 @@ sidebar_label: 'Tips and Tricks' 📝 This article offers insights into **Smart Contracts** with helpful tips and tricks. If you have suggestions or questions, feel free to open an issue. We also welcome contributions through PRs. ::: +## Ignoring Web3.js autofill gas prices + +When interacting with methods in contracts, Web3.js will automatically fill the gas. If you are using metamask or a similar provider and would rather have a suggestion elsewhere, the `ignoreGasPricing` option enables you to send transactions or interact with contracts without having web3.js automatically fill in the gas estimate. + +#### Contract example + +```ts +let contractDeployed: Contract; +// instantiate contract... +contractDeployed.config.ignoreGasPricing = true; +const receipt = await contractDeployed.methods.setValues(1, 'string value', true).send(sendOptions); +``` + ## Calling Smart Contracts Methods with Parameter Overloading ### Overview of Function Overloading diff --git a/docs/docs/guides/09_web3_config/index.md b/docs/docs/guides/09_web3_config/index.md index de8a8b9db7c..3642aad951d 100644 --- a/docs/docs/guides/09_web3_config/index.md +++ b/docs/docs/guides/09_web3_config/index.md @@ -29,6 +29,7 @@ There is list of configuration params that can be set for modifying behavior of - [defaultMaxPriorityFeePerGas](/guides/web3_config/#defaultmaxpriorityfeepergas) - [customTransactionSchema](/guides/web3_config/#customTransactionSchema) - [defaultReturnFormat](/guides/web3_config/#defaultreturnformat) +- [ignoreGasPricing](/guides/web3_config/#ignoreGasPricing) ## Global level Config @@ -477,3 +478,30 @@ export enum FMT_BYTES { UINT8ARRAY = 'BYTES_UINT8ARRAY', } ``` + +### [ignoreGasPricing] + +The `ignoreGasPricing` option enables you to send transactions or interact with contracts without having web3.js automatically fill in the gas estimate. This feature is particularly useful when you prefer to let wallets or providers handle gas estimation instead. + +#### Send transaction example + +```ts +const web3 = new Web3(PROVIDER); +web3.config.ignoreGasPricing = true; // when setting configurations for the web3 object, this will also apply to newly created contracts from the web3 object +const transaction: TransactionWithToLocalWalletIndex = { + from: tempAcc.address, + to: '0x0000000000000000000000000000000000000000', + value: BigInt(1), + data: '0x64edfbf0e2c706ba4a09595315c45355a341a576cc17f3a19f43ac1c02f814ee', +}; +const receipt = await web3.eth.sendTransaction(transaction); // web3.js will not estimate gas now. +``` + +#### Contract example + +```ts +let contractDeployed: Contract; +// instantiate contract... +contractDeployed.config.ignoreGasPricing = true; +const receipt = await contractDeployed.methods.setValues(1, 'string value', true).send(sendOptions); +``` diff --git a/packages/web3-core/CHANGELOG.md b/packages/web3-core/CHANGELOG.md index 0ea27e97e22..b02756a075d 100644 --- a/packages/web3-core/CHANGELOG.md +++ b/packages/web3-core/CHANGELOG.md @@ -240,3 +240,7 @@ Documentation: - Adds a new property (`customTransactionSchema`) to `Web3ConfigOptions`(#7227) ## [Unreleased] + +### Added + +- Added new property `ignoreGasPricing` to `Web3ConfigOptions`. If `ignoreGasPricing` is true, gasPrice will not be estimated (#7320) diff --git a/packages/web3-core/src/web3_config.ts b/packages/web3-core/src/web3_config.ts index ee397ea9daf..6091bf35d4f 100644 --- a/packages/web3-core/src/web3_config.ts +++ b/packages/web3-core/src/web3_config.ts @@ -48,6 +48,7 @@ export interface Web3ConfigOptions { defaultNetworkId?: Numbers; defaultChain: string; defaultHardfork: string; + ignoreGasPricing: boolean; defaultCommon?: Common; defaultTransactionType: Numbers; @@ -104,6 +105,7 @@ export abstract class Web3Config transactionTypeParser: undefined, customTransactionSchema: undefined, defaultReturnFormat: DEFAULT_RETURN_FORMAT, + ignoreGasPricing: false, }; public constructor(options?: Partial) { @@ -208,7 +210,7 @@ export abstract class Web3Config * - `"latest"` - String: The latest block (current head of the blockchain) * - `"pending"` - String: The currently mined block (including pending transactions) * - `"finalized"` - String: (For POS networks) The finalized block is one which has been accepted as canonical by greater than 2/3 of validators - * - `"safe"` - String: (For POS networks) The safe head block is one which under normal network conditions, is expected to be included in the canonical chain. Under normal network conditions the safe head and the actual tip of the chain will be equivalent (with safe head trailing only by a few seconds). Safe heads will be less likely to be reorged than the proof of work network`s latest blocks. + * - `"safe"` - String: (For POS networks) The safe head block is one which under normal network conditions, is expected to be included in the canonical chain. Under normal network conditions the safe head and the actual tip of the chain will be equivalent (with safe head trailing only by a few seconds). Safe heads will be less likely to be reorged than the proof of work network's latest blocks. */ public set defaultBlock(val) { this._triggerConfigChange('defaultBlock', val); @@ -485,6 +487,17 @@ export abstract class Web3Config this.config.defaultCommon = val; } + /** + * Will get the ignoreGasPricing property. When true, the gasPrice, maxPriorityFeePerGas, and maxFeePerGas will not be autofilled in the transaction object. + * Useful when you want wallets to handle gas pricing. + */ + public get ignoreGasPricing() { + return this.config.ignoreGasPricing; + } + public set ignoreGasPricing(val) { + this._triggerConfigChange('ignoreGasPricing', val); + this.config.ignoreGasPricing = val; + } public get defaultTransactionType() { return this.config.defaultTransactionType; } diff --git a/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap b/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap index 7d280405bfb..086b4a57991 100644 --- a/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap +++ b/packages/web3-core/test/unit/__snapshots__/web3_context.test.ts.snap @@ -24,6 +24,7 @@ exports[`Web3Context getContextObject should return correct context object 1`] = "useSubscriptionWhenCheckingBlockTimeout": false, }, "handleRevert": false, + "ignoreGasPricing": false, "maxListenersWarningThreshold": 100, "transactionBlockTimeout": 50, "transactionBuilder": undefined, diff --git a/packages/web3-core/test/unit/web3_config.test.ts b/packages/web3-core/test/unit/web3_config.test.ts index 7238c1fcb33..07dd05d355b 100644 --- a/packages/web3-core/test/unit/web3_config.test.ts +++ b/packages/web3-core/test/unit/web3_config.test.ts @@ -50,6 +50,7 @@ const defaultConfig = { transactionBuilder: undefined, transactionTypeParser: undefined, customTransactionSchema: undefined, + ignoreGasPricing: false, }; const setValue = { string: 'newValue', diff --git a/packages/web3-eth-contract/test/integration/contract_methods.test.ts b/packages/web3-eth-contract/test/integration/contract_methods.test.ts index 46a1490c119..152c3feec8a 100644 --- a/packages/web3-eth-contract/test/integration/contract_methods.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_methods.test.ts @@ -92,7 +92,48 @@ describe('contract', () => { }); describe('send', () => { - it('should returns a receipt', async () => { + beforeEach(() => { + contractDeployed.config.ignoreGasPricing = false; + }); + it('should return a receipt', async () => { + const receipt = await contractDeployed.methods + .setValues(1, 'string value', true) + .send(sendOptions); + + expect(receipt.events).toBeUndefined(); + expect(receipt).toEqual( + expect.objectContaining({ + // status: BigInt(1), + transactionHash: expect.any(String), + }), + ); + + // To avoid issue with the `objectContaining` and `cypress` had to add + // these expectations explicitly on each attribute + expect(receipt.status).toEqual(BigInt(1)); + }); + + it('should return a receipt with ignoring gas config true', async () => { + contractDeployed.config.ignoreGasPricing = true; + const receipt = await contractDeployed.methods + .setValues(1, 'string value', true) + .send(sendOptions); + + expect(receipt.events).toBeUndefined(); + expect(receipt).toEqual( + expect.objectContaining({ + // status: BigInt(1), + transactionHash: expect.any(String), + }), + ); + + // To avoid issue with the `objectContaining` and `cypress` had to add + // these expectations explicitly on each attribute + expect(receipt.status).toEqual(BigInt(1)); + }); + + it('should return a receipt with ignoring gas config false', async () => { + contractDeployed.config.ignoreGasPricing = false; const receipt = await contractDeployed.methods .setValues(1, 'string value', true) .send(sendOptions); diff --git a/packages/web3-eth/CHANGELOG.md b/packages/web3-eth/CHANGELOG.md index 71fd65e4f9e..8d89960bd8e 100644 --- a/packages/web3-eth/CHANGELOG.md +++ b/packages/web3-eth/CHANGELOG.md @@ -284,3 +284,7 @@ Documentation: ### Changed - Allow `getEthereumjsTxDataFrom` to return additional fields that may be passed if using a `customTransactionSchema`. + +### Added + +- `populateGasPrice` function now checks `Web3Context.config.ignoreGasPricing`. If `ignoreGasPricing` is true, gasPrice will not be estimated (#7320) diff --git a/packages/web3-eth/src/utils/send_tx_helper.ts b/packages/web3-eth/src/utils/send_tx_helper.ts index fbe48c0e868..e28b353cda1 100644 --- a/packages/web3-eth/src/utils/send_tx_helper.ts +++ b/packages/web3-eth/src/utils/send_tx_helper.ts @@ -163,6 +163,7 @@ export class SendTxHelper< }): Promise { let result = transactionFormatted; if ( + !this.web3Context.config.ignoreGasPricing && !this.options?.ignoreGasPricing && isNullish((transactionFormatted as Transaction).gasPrice) && (isNullish((transaction as Transaction).maxPriorityFeePerGas) || diff --git a/packages/web3-eth/src/validation.ts b/packages/web3-eth/src/validation.ts index b318e16dd41..81a5ebd1090 100644 --- a/packages/web3-eth/src/validation.ts +++ b/packages/web3-eth/src/validation.ts @@ -27,13 +27,7 @@ import { TransactionWithSenderAPI, ETH_DATA_FORMAT, } from 'web3-types'; -import { - isAddress, - isHexStrict, - isHexString32Bytes, - isNullish, - isUInt, -} from 'web3-validator'; +import { isAddress, isHexStrict, isHexString32Bytes, isNullish, isUInt } from 'web3-validator'; import { ChainMismatchError, HardforkMismatchError, diff --git a/packages/web3-eth/test/integration/web3_eth/send_transaction.test.ts b/packages/web3-eth/test/integration/web3_eth/send_transaction.test.ts index b16479d886b..3de0778c500 100644 --- a/packages/web3-eth/test/integration/web3_eth/send_transaction.test.ts +++ b/packages/web3-eth/test/integration/web3_eth/send_transaction.test.ts @@ -199,7 +199,34 @@ describe('Web3Eth.sendTransaction', () => { const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); expect(minedTransactionData).toMatchObject(transaction); }); + it('should send a transaction while ignoring gas price successfully', async () => { + const transaction: TransactionWithToLocalWalletIndex = { + from: tempAcc.address, + to: '0x0000000000000000000000000000000000000000', + value: BigInt(1), + data: '0x64edfbf0e2c706ba4a09595315c45355a341a576cc17f3a19f43ac1c02f814ee', + }; + const localWeb3Eth = new Web3Eth(getSystemTestProvider()); + localWeb3Eth.config.ignoreGasPricing = true; + const response = await localWeb3Eth.sendTransaction(transaction); + expect(response.status).toBe(BigInt(1)); + await closeOpenConnection(localWeb3Eth); + }); + it('should send a transaction with automated gas price successfully', async () => { + const transaction: TransactionWithToLocalWalletIndex = { + from: tempAcc.address, + to: '0x0000000000000000000000000000000000000000', + value: BigInt(1), + data: '0x64edfbf0e2c706ba4a09595315c45355a341a576cc17f3a19f43ac1c02f814ee', + }; + + const localWeb3Eth = new Web3Eth(getSystemTestProvider()); + localWeb3Eth.config.ignoreGasPricing = false; + const response = await localWeb3Eth.sendTransaction(transaction); + expect(response.status).toBe(BigInt(1)); + await closeOpenConnection(localWeb3Eth); + }); describe('Deploy and interact with contract', () => { let greeterContractAddress: string; diff --git a/packages/web3-eth/test/unit/send_tx_helper.test.ts b/packages/web3-eth/test/unit/send_tx_helper.test.ts index 23e0d2aa526..6e25a0872b4 100644 --- a/packages/web3-eth/test/unit/send_tx_helper.test.ts +++ b/packages/web3-eth/test/unit/send_tx_helper.test.ts @@ -89,7 +89,8 @@ describe('sendTxHelper class', () => { let sendTxHelper: SendTxHelper; let promiEvent: Web3PromiEvent; let web3Context: Web3Context; - beforeAll(() => { + beforeEach(() => { + jest.clearAllMocks(); web3Context = new Web3Context(); promiEvent = new Web3PromiEvent(resolve => { resolve({} as unknown as TransactionReceipt); @@ -267,4 +268,50 @@ describe('sendTxHelper class', () => { expect(utils.trySendTransaction).toHaveBeenCalled(); expect(wallet.signTransaction).toHaveBeenCalledWith(receipt); }); + it('should not call getTransactionGasPricing when ignoreGasPricing is true', async () => { + web3Context.config.ignoreGasPricing = true; + const transaction = { + from: '0xa7d9ddbe1f17865597fbd27ec712455208b6b76d', + input: '0x68656c6c6f21', + nonce: '0x15', + to: '0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb', + value: '0xf3dbb76162000', + type: '0x0', + chainId: '0x1', + }; + const _sendTxHelper = new SendTxHelper({ + web3Context, + promiEvent: promiEvent as PromiEvent, + options: {}, + returnFormat: DEFAULT_RETURN_FORMAT, + }); + await _sendTxHelper.populateGasPrice({ + transactionFormatted: transaction, + transaction, + }); + expect(getTransactionGasPricing).not.toHaveBeenCalled(); + }); + it('should call getTransactionGasPricing when ignoreGasPricing is false', async () => { + web3Context.config.ignoreGasPricing = false; + const transaction = { + from: '0xa7d9ddbe1f17865597fbd27ec712455208b6b76d', + input: '0x68656c6c6f21', + nonce: '0x15', + to: '0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb', + value: '0xf3dbb76162000', + type: '0x0', + chainId: '0x1', + }; + const _sendTxHelper = new SendTxHelper({ + web3Context, + promiEvent: promiEvent as PromiEvent, + options: {}, + returnFormat: DEFAULT_RETURN_FORMAT, + }); + await _sendTxHelper.populateGasPrice({ + transactionFormatted: transaction, + transaction, + }); + expect(getTransactionGasPricing).toHaveBeenCalled(); + }); }); diff --git a/packages/web3-types/src/eth_contract_types.ts b/packages/web3-types/src/eth_contract_types.ts index 00eba77e5ed..5a0381e6362 100644 --- a/packages/web3-types/src/eth_contract_types.ts +++ b/packages/web3-types/src/eth_contract_types.ts @@ -168,4 +168,8 @@ export interface ContractOptions { * The max fee per gas to use for transactions. */ maxFeePerGas?: Uint; + /** + * Ignore gas price, turn on for metamask suggestion fee + */ + ignoreGasPricing?: boolean; }