@@ -231,108 +231,158 @@ func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error)
231231 return c .transact (opts , & c .address , nil )
232232}
233233
234- // transact executes an actual transaction invocation, first deriving any missing
235- // authorization fields, and then scheduling the transaction for execution.
236- func (c * BoundContract ) transact (opts * TransactOpts , contract * common.Address , input []byte ) (* types.Transaction , error ) {
237- var err error
238-
239- // Ensure a valid value field and resolve the account nonce
234+ func (c * BoundContract ) createDynamicTx (opts * TransactOpts , contract * common.Address , input []byte , head * types.Header ) (* types.Transaction , error ) {
235+ // Normalize value
240236 value := opts .Value
241237 if value == nil {
242238 value = new (big.Int )
243239 }
244- var nonce uint64
245- if opts .Nonce == nil {
246- nonce , err = c .transactor .PendingNonceAt (ensureContext (opts .Context ), opts .From )
240+ // Estimate TipCap
241+ gasTipCap := opts .GasTipCap
242+ if gasTipCap == nil {
243+ tip , err := c .transactor .SuggestGasTipCap (ensureContext (opts .Context ))
247244 if err != nil {
248- return nil , fmt . Errorf ( "failed to retrieve account nonce: %v" , err )
245+ return nil , err
249246 }
250- } else {
251- nonce = opts .Nonce .Uint64 ()
247+ gasTipCap = tip
252248 }
253- // Figure out reasonable gas price values
254- if opts .GasPrice != nil && (opts .GasFeeCap != nil || opts .GasTipCap != nil ) {
255- return nil , errors .New ("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified" )
249+ // Estimate FeeCap
250+ gasFeeCap := opts .GasFeeCap
251+ if gasFeeCap == nil {
252+ gasFeeCap = new (big.Int ).Add (
253+ gasTipCap ,
254+ new (big.Int ).Mul (head .BaseFee , big .NewInt (2 )),
255+ )
256256 }
257- head , err := c .transactor .HeaderByNumber (ensureContext (opts .Context ), nil )
257+ if gasFeeCap .Cmp (gasTipCap ) < 0 {
258+ return nil , fmt .Errorf ("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)" , gasFeeCap , gasTipCap )
259+ }
260+ // Estimate GasLimit
261+ gasLimit := opts .GasLimit
262+ if opts .GasLimit == 0 {
263+ var err error
264+ gasLimit , err = c .estimateGasLimit (opts , contract , input , nil , gasTipCap , gasFeeCap , value )
265+ if err != nil {
266+ return nil , err
267+ }
268+ }
269+ // create the transaction
270+ nonce , err := c .getNonce (opts )
258271 if err != nil {
259272 return nil , err
260273 }
261- if head .BaseFee != nil && opts .GasPrice == nil {
262- if opts .GasTipCap == nil {
263- tip , err := c .transactor .SuggestGasTipCap (ensureContext (opts .Context ))
264- if err != nil {
265- return nil , err
266- }
267- opts .GasTipCap = tip
268- }
269- if opts .GasFeeCap == nil {
270- gasFeeCap := new (big.Int ).Add (
271- opts .GasTipCap ,
272- new (big.Int ).Mul (head .BaseFee , big .NewInt (2 )),
273- )
274- opts .GasFeeCap = gasFeeCap
275- }
276- if opts .GasFeeCap .Cmp (opts .GasTipCap ) < 0 {
277- return nil , fmt .Errorf ("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)" , opts .GasFeeCap , opts .GasTipCap )
278- }
279- } else {
280- if opts .GasFeeCap != nil || opts .GasTipCap != nil {
281- return nil , errors .New ("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet" )
282- }
283- if opts .GasPrice == nil {
284- price , err := c .transactor .SuggestGasPrice (ensureContext (opts .Context ))
285- if err != nil {
286- return nil , err
287- }
288- opts .GasPrice = price
274+ baseTx := & types.DynamicFeeTx {
275+ To : contract ,
276+ Nonce : nonce ,
277+ GasFeeCap : gasFeeCap ,
278+ GasTipCap : gasTipCap ,
279+ Gas : gasLimit ,
280+ Value : value ,
281+ Data : input ,
282+ }
283+ return types .NewTx (baseTx ), nil
284+ }
285+
286+ func (c * BoundContract ) createLegacyTx (opts * TransactOpts , contract * common.Address , input []byte ) (* types.Transaction , error ) {
287+ if opts .GasFeeCap != nil || opts .GasTipCap != nil {
288+ return nil , errors .New ("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet" )
289+ }
290+ // Normalize value
291+ value := opts .Value
292+ if value == nil {
293+ value = new (big.Int )
294+ }
295+ // Estimate GasPrice
296+ gasPrice := opts .GasPrice
297+ if gasPrice == nil {
298+ price , err := c .transactor .SuggestGasPrice (ensureContext (opts .Context ))
299+ if err != nil {
300+ return nil , err
289301 }
302+ gasPrice = price
290303 }
304+ // Estimate GasLimit
291305 gasLimit := opts .GasLimit
292- if gasLimit == 0 {
293- // Gas estimation cannot succeed without code for method invocations
294- if contract != nil {
295- if code , err := c .transactor .PendingCodeAt (ensureContext (opts .Context ), c .address ); err != nil {
296- return nil , err
297- } else if len (code ) == 0 {
298- return nil , ErrNoCode
299- }
300- }
301- // If the contract surely has code (or code is not needed), estimate the transaction
302- msg := ethereum.CallMsg {From : opts .From , To : contract , GasPrice : opts .GasPrice , GasTipCap : opts .GasTipCap , GasFeeCap : opts .GasFeeCap , Value : value , Data : input }
303- gasLimit , err = c .transactor .EstimateGas (ensureContext (opts .Context ), msg )
306+ if opts .GasLimit == 0 {
307+ var err error
308+ gasLimit , err = c .estimateGasLimit (opts , contract , input , gasPrice , nil , nil , value )
304309 if err != nil {
305- return nil , fmt . Errorf ( "failed to estimate gas needed: %v" , err )
310+ return nil , err
306311 }
307312 }
308- // Create the transaction, sign it and schedule it for execution
309- var rawTx * types.Transaction
310- if opts .GasFeeCap == nil {
311- baseTx := & types.LegacyTx {
312- Nonce : nonce ,
313- GasPrice : opts .GasPrice ,
314- Gas : gasLimit ,
315- Value : value ,
316- Data : input ,
317- }
318- if contract != nil {
319- baseTx .To = & c .address
313+ // create the transaction
314+ nonce , err := c .getNonce (opts )
315+ if err != nil {
316+ return nil , err
317+ }
318+ baseTx := & types.LegacyTx {
319+ To : contract ,
320+ Nonce : nonce ,
321+ GasPrice : gasPrice ,
322+ Gas : gasLimit ,
323+ Value : value ,
324+ Data : input ,
325+ }
326+ return types .NewTx (baseTx ), nil
327+ }
328+
329+ func (c * BoundContract ) estimateGasLimit (opts * TransactOpts , contract * common.Address , input []byte , gasPrice , gasTipCap , gasFeeCap , value * big.Int ) (uint64 , error ) {
330+ if contract != nil {
331+ // Gas estimation cannot succeed without code for method invocations.
332+ if code , err := c .transactor .PendingCodeAt (ensureContext (opts .Context ), c .address ); err != nil {
333+ return 0 , err
334+ } else if len (code ) == 0 {
335+ return 0 , ErrNoCode
320336 }
321- rawTx = types .NewTx (baseTx )
337+ }
338+ msg := ethereum.CallMsg {
339+ From : opts .From ,
340+ To : contract ,
341+ GasPrice : gasPrice ,
342+ GasTipCap : gasTipCap ,
343+ GasFeeCap : gasFeeCap ,
344+ Value : value ,
345+ Data : input ,
346+ }
347+ return c .transactor .EstimateGas (ensureContext (opts .Context ), msg )
348+ }
349+
350+ func (c * BoundContract ) getNonce (opts * TransactOpts ) (uint64 , error ) {
351+ if opts .Nonce == nil {
352+ return c .transactor .PendingNonceAt (ensureContext (opts .Context ), opts .From )
322353 } else {
323- baseTx := & types.DynamicFeeTx {
324- Nonce : nonce ,
325- GasFeeCap : opts .GasFeeCap ,
326- GasTipCap : opts .GasTipCap ,
327- Gas : gasLimit ,
328- Value : value ,
329- Data : input ,
330- }
331- if contract != nil {
332- baseTx .To = & c .address
354+ return opts .Nonce .Uint64 (), nil
355+ }
356+ }
357+
358+ // transact executes an actual transaction invocation, first deriving any missing
359+ // authorization fields, and then scheduling the transaction for execution.
360+ func (c * BoundContract ) transact (opts * TransactOpts , contract * common.Address , input []byte ) (* types.Transaction , error ) {
361+ if opts .GasPrice != nil && (opts .GasFeeCap != nil || opts .GasTipCap != nil ) {
362+ return nil , errors .New ("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified" )
363+ }
364+ // Create the transaction
365+ var (
366+ rawTx * types.Transaction
367+ err error
368+ )
369+ if opts .GasPrice != nil {
370+ rawTx , err = c .createLegacyTx (opts , contract , input )
371+ } else {
372+ // Only query for basefee if gasPrice not specified
373+ if head , errHead := c .transactor .HeaderByNumber (ensureContext (opts .Context ), nil ); err != nil {
374+ return nil , errHead
375+ } else if head .BaseFee != nil {
376+ rawTx , err = c .createDynamicTx (opts , contract , input , head )
377+ } else {
378+ // Chain is not London ready -> use legacy transaction
379+ rawTx , err = c .createLegacyTx (opts , contract , input )
333380 }
334- rawTx = types .NewTx (baseTx )
335381 }
382+ if err != nil {
383+ return nil , err
384+ }
385+ // Sign the transaction and schedule it for execution
336386 if opts .Signer == nil {
337387 return nil , errors .New ("no signer to authorize the transaction with" )
338388 }
0 commit comments