Mat!
As far as I understand you don't need "gas price" (whatever that means) to create new EIP1559 transaction.
New dynamic transaction require 3 gas-related params: GasTipCap, GasFeeCap, Gas
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
GasTipCap: gasParams.GasTipCap, // <- We need this
GasFeeCap: gasParams.GasFeeCap, // <- and also this
Gas: gasParams.Gas, // <- and finally this
To: to,
Value: value,
Data: data,
})
I made a function which requests/calculates these 3 params, and this approach works great for me:
Params we need (gathered in one structure for usability and consistency):
type Gas1559Params struct {
GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas
GasFeeCap *big.Int // a.k.a. maxFeePerGas
Gas uint64
}
And finally function, that returns these three params together:
const GAS_TIP_CAP = 2_000_000_000 // Hardcoded Tip = 2Gwei, make adjustable?
func GetGasParameters(ethClient *ethclient.Client, from common.Address, to *common.Address, value *big.Int, data []byte) (Gas1559Params, error) {
header, err := ethClient.HeaderByNumber(context.Background(), nil)
if err != nil {
return Gas1559Params{}, fmt.Errorf("failed to request last block header: %s", err)
}
baseFee := header.BaseFee
gasTipCap := big.NewInt(GAS_TIP_CAP) // Calculate the gas tip cap (e.g., 2 Gwei)
gasFeeCap := new(big.Int).Add(baseFee, gasTipCap) // Calculate the max fee per gas (baseFee + gasTipCap)
gasLimit, err := estimateGas(ethClient, from, to, value, data)
if err != nil {
return Gas1559Params{}, err
}
return Gas1559Params{
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: gasLimit,
}, nil
}
func estimateGas(ethClient *ethclient.Client, from common.Address, to *common.Address, value *big.Int, data []byte) (gasLimit uint64, err error) {
msg := ethereum.CallMsg{
From: from,
To: to,
Value: value,
Data: data,
}
gasLimit, err = ethClient.EstimateGas(context.Background(), msg)
if err != nil {
return 0, fmt.Errorf("failed to estimate gas limit for given operation: %s", err)
}
return gasLimit, nil
}
I hope this helps :)