@@ -231,108 +231,158 @@ func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error)
231
231
return c .transact (opts , & c .address , nil )
232
232
}
233
233
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
240
236
value := opts .Value
241
237
if value == nil {
242
238
value = new (big.Int )
243
239
}
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 ))
247
244
if err != nil {
248
- return nil , fmt . Errorf ( "failed to retrieve account nonce: %v" , err )
245
+ return nil , err
249
246
}
250
- } else {
251
- nonce = opts .Nonce .Uint64 ()
247
+ gasTipCap = tip
252
248
}
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
+ )
256
256
}
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 )
258
271
if err != nil {
259
272
return nil , err
260
273
}
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
289
301
}
302
+ gasPrice = price
290
303
}
304
+ // Estimate GasLimit
291
305
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 )
304
309
if err != nil {
305
- return nil , fmt . Errorf ( "failed to estimate gas needed: %v" , err )
310
+ return nil , err
306
311
}
307
312
}
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
320
336
}
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 )
322
353
} 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 )
333
380
}
334
- rawTx = types .NewTx (baseTx )
335
381
}
382
+ if err != nil {
383
+ return nil , err
384
+ }
385
+ // Sign the transaction and schedule it for execution
336
386
if opts .Signer == nil {
337
387
return nil , errors .New ("no signer to authorize the transaction with" )
338
388
}
0 commit comments