github.com/status-im/status-go@v1.1.0/transactions/transactor.go (about)

     1  package transactions
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  	"time"
    10  
    11  	ethereum "github.com/ethereum/go-ethereum"
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/ethereum/go-ethereum/common/hexutil"
    14  	gethtypes "github.com/ethereum/go-ethereum/core/types"
    15  	"github.com/ethereum/go-ethereum/log"
    16  
    17  	"github.com/status-im/status-go/account"
    18  	"github.com/status-im/status-go/eth-node/crypto"
    19  	"github.com/status-im/status-go/eth-node/types"
    20  	"github.com/status-im/status-go/params"
    21  	"github.com/status-im/status-go/rpc"
    22  	"github.com/status-im/status-go/services/wallet/bigint"
    23  	wallet_common "github.com/status-im/status-go/services/wallet/common"
    24  )
    25  
    26  const (
    27  	// sendTxTimeout defines how many seconds to wait before returning result in sentTransaction().
    28  	sendTxTimeout = 300 * time.Second
    29  
    30  	defaultGas = 90000
    31  
    32  	ValidSignatureSize = 65
    33  )
    34  
    35  // ErrInvalidSignatureSize is returned if a signature is not 65 bytes to avoid panic from go-ethereum
    36  var ErrInvalidSignatureSize = errors.New("signature size must be 65")
    37  
    38  type ErrBadNonce struct {
    39  	nonce         uint64
    40  	expectedNonce uint64
    41  }
    42  
    43  func (e *ErrBadNonce) Error() string {
    44  	return fmt.Sprintf("bad nonce. expected %d, got %d", e.expectedNonce, e.nonce)
    45  }
    46  
    47  // Transactor is an interface that defines the methods for validating and sending transactions.
    48  type TransactorIface interface {
    49  	NextNonce(rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error)
    50  	EstimateGas(network *params.Network, from common.Address, to common.Address, value *big.Int, input []byte) (uint64, error)
    51  	SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error)
    52  	SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error)
    53  	ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, nonce uint64, err error)
    54  	AddSignatureToTransaction(chainID uint64, tx *gethtypes.Transaction, sig []byte) (*gethtypes.Transaction, error)
    55  	SendRawTransaction(chainID uint64, rawTx string) error
    56  	BuildTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (*gethtypes.Transaction, error)
    57  	SendTransactionWithSignature(from common.Address, symbol string, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (hash types.Hash, err error)
    58  	StoreAndTrackPendingTx(from common.Address, symbol string, chainID uint64, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) error
    59  }
    60  
    61  // Transactor validates, signs transactions.
    62  // It uses upstream to propagate transactions to the Ethereum network.
    63  type Transactor struct {
    64  	rpcWrapper     *rpcWrapper
    65  	pendingTracker *PendingTxTracker
    66  	sendTxTimeout  time.Duration
    67  	rpcCallTimeout time.Duration
    68  	networkID      uint64
    69  	log            log.Logger
    70  }
    71  
    72  // NewTransactor returns a new Manager.
    73  func NewTransactor() *Transactor {
    74  	return &Transactor{
    75  		sendTxTimeout: sendTxTimeout,
    76  		log:           log.New("package", "status-go/transactions.Manager"),
    77  	}
    78  }
    79  
    80  // SetPendingTracker sets a pending tracker.
    81  func (t *Transactor) SetPendingTracker(tracker *PendingTxTracker) {
    82  	t.pendingTracker = tracker
    83  }
    84  
    85  // SetNetworkID selects a correct network.
    86  func (t *Transactor) SetNetworkID(networkID uint64) {
    87  	t.networkID = networkID
    88  }
    89  
    90  func (t *Transactor) NetworkID() uint64 {
    91  	return t.networkID
    92  }
    93  
    94  // SetRPC sets RPC params, a client and a timeout
    95  func (t *Transactor) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
    96  	t.rpcWrapper = newRPCWrapper(rpcClient, rpcClient.UpstreamChainID)
    97  	t.rpcCallTimeout = timeout
    98  }
    99  
   100  func (t *Transactor) NextNonce(rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error) {
   101  	wrapper := newRPCWrapper(rpcClient, chainID)
   102  	ctx := context.Background()
   103  	nonce, err := wrapper.PendingNonceAt(ctx, common.Address(from))
   104  	if err != nil {
   105  		return 0, err
   106  	}
   107  
   108  	// We need to take into consideration all pending transactions in case of Optimism, cause the network returns always
   109  	// the nonce of last executed tx + 1 for the next nonce value.
   110  	if chainID == wallet_common.OptimismMainnet ||
   111  		chainID == wallet_common.OptimismSepolia ||
   112  		chainID == wallet_common.OptimismGoerli {
   113  		if t.pendingTracker != nil {
   114  			countOfPendingTXs, err := t.pendingTracker.CountPendingTxsFromNonce(wallet_common.ChainID(chainID), common.Address(from), nonce)
   115  			if err != nil {
   116  				return 0, err
   117  			}
   118  			return nonce + countOfPendingTXs, nil
   119  		}
   120  	}
   121  
   122  	return nonce, err
   123  }
   124  
   125  func (t *Transactor) EstimateGas(network *params.Network, from common.Address, to common.Address, value *big.Int, input []byte) (uint64, error) {
   126  	rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, network.ChainID)
   127  
   128  	ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
   129  	defer cancel()
   130  
   131  	msg := ethereum.CallMsg{
   132  		From:  from,
   133  		To:    &to,
   134  		Value: value,
   135  		Data:  input,
   136  	}
   137  
   138  	return rpcWrapper.EstimateGas(ctx, msg)
   139  }
   140  
   141  // SendTransaction is an implementation of eth_sendTransaction. It queues the tx to the sign queue.
   142  func (t *Transactor) SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) {
   143  	hash, nonce, err = t.validateAndPropagate(t.rpcWrapper, verifiedAccount, sendArgs, lastUsedNonce)
   144  	return
   145  }
   146  
   147  func (t *Transactor) SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error) {
   148  	wrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID)
   149  	hash, nonce, err = t.validateAndPropagate(wrapper, verifiedAccount, sendArgs, lastUsedNonce)
   150  	return
   151  }
   152  
   153  func (t *Transactor) ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, nonce uint64, err error) {
   154  	wrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID)
   155  	tx, err = t.validateAndBuildTransaction(wrapper, sendArgs, lastUsedNonce)
   156  	return tx, tx.Nonce(), err
   157  }
   158  
   159  func (t *Transactor) AddSignatureToTransaction(chainID uint64, tx *gethtypes.Transaction, sig []byte) (*gethtypes.Transaction, error) {
   160  	if len(sig) != ValidSignatureSize {
   161  		return nil, ErrInvalidSignatureSize
   162  	}
   163  
   164  	rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID)
   165  	chID := big.NewInt(int64(rpcWrapper.chainID))
   166  
   167  	signer := gethtypes.NewLondonSigner(chID)
   168  	txWithSignature, err := tx.WithSignature(signer, sig)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	return txWithSignature, nil
   174  }
   175  
   176  func (t *Transactor) SendRawTransaction(chainID uint64, rawTx string) error {
   177  	rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID)
   178  
   179  	ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
   180  	defer cancel()
   181  
   182  	return rpcWrapper.SendRawTransaction(ctx, rawTx)
   183  }
   184  
   185  func createPendingTransaction(from common.Address, symbol string, chainID uint64, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (pTx *PendingTransaction) {
   186  
   187  	pTx = &PendingTransaction{
   188  		Hash:               tx.Hash(),
   189  		Timestamp:          uint64(time.Now().Unix()),
   190  		Value:              bigint.BigInt{Int: tx.Value()},
   191  		From:               from,
   192  		To:                 *tx.To(),
   193  		Nonce:              tx.Nonce(),
   194  		Data:               string(tx.Data()),
   195  		Type:               WalletTransfer,
   196  		ChainID:            wallet_common.ChainID(chainID),
   197  		MultiTransactionID: multiTransactionID,
   198  		Symbol:             symbol,
   199  		AutoDelete:         new(bool),
   200  	}
   201  	// Transaction downloader will delete pending transaction as soon as it is confirmed
   202  	*pTx.AutoDelete = false
   203  	return
   204  }
   205  
   206  func (t *Transactor) StoreAndTrackPendingTx(from common.Address, symbol string, chainID uint64, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) error {
   207  	if t.pendingTracker == nil {
   208  		return nil
   209  	}
   210  
   211  	pTx := createPendingTransaction(from, symbol, chainID, multiTransactionID, tx)
   212  	return t.pendingTracker.StoreAndTrackPendingTx(pTx)
   213  }
   214  
   215  func (t *Transactor) sendTransaction(rpcWrapper *rpcWrapper, from common.Address, symbol string,
   216  	multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (hash types.Hash, err error) {
   217  	ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
   218  	defer cancel()
   219  
   220  	if err := rpcWrapper.SendTransaction(ctx, tx); err != nil {
   221  		return hash, err
   222  	}
   223  
   224  	err = t.StoreAndTrackPendingTx(from, symbol, rpcWrapper.chainID, multiTransactionID, tx)
   225  	if err != nil {
   226  		return hash, err
   227  	}
   228  
   229  	return types.Hash(tx.Hash()), nil
   230  }
   231  
   232  func (t *Transactor) SendTransactionWithSignature(from common.Address, symbol string,
   233  	multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (hash types.Hash, err error) {
   234  	rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, tx.ChainId().Uint64())
   235  
   236  	return t.sendTransaction(rpcWrapper, from, symbol, multiTransactionID, tx)
   237  }
   238  
   239  // BuildTransactionAndSendWithSignature receive a transaction and a signature, serialize them together
   240  // It's different from eth_sendRawTransaction because it receives a signature and not a serialized transaction with signature.
   241  // Since the transactions is already signed, we assume it was validated and used the right nonce.
   242  func (t *Transactor) BuildTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (*gethtypes.Transaction, error) {
   243  	if !args.Valid() {
   244  		return nil, ErrInvalidSendTxArgs
   245  	}
   246  
   247  	if len(sig) != ValidSignatureSize {
   248  		return nil, ErrInvalidSignatureSize
   249  	}
   250  
   251  	tx := t.buildTransaction(args)
   252  	expectedNonce, err := t.NextNonce(t.rpcWrapper.RPCClient, chainID, args.From)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	if tx.Nonce() != expectedNonce {
   258  		return nil, &ErrBadNonce{tx.Nonce(), expectedNonce}
   259  	}
   260  
   261  	txWithSignature, err := t.AddSignatureToTransaction(chainID, tx, sig)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	return txWithSignature, nil
   267  }
   268  
   269  func (t *Transactor) HashTransaction(args SendTxArgs) (validatedArgs SendTxArgs, hash types.Hash, err error) {
   270  	if !args.Valid() {
   271  		return validatedArgs, hash, ErrInvalidSendTxArgs
   272  	}
   273  
   274  	validatedArgs = args
   275  
   276  	nonce, err := t.NextNonce(t.rpcWrapper.RPCClient, t.rpcWrapper.chainID, args.From)
   277  	if err != nil {
   278  		return validatedArgs, hash, err
   279  	}
   280  
   281  	gasPrice := (*big.Int)(args.GasPrice)
   282  	gasFeeCap := (*big.Int)(args.MaxFeePerGas)
   283  	gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas)
   284  	if args.GasPrice == nil && !args.IsDynamicFeeTx() {
   285  		ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
   286  		defer cancel()
   287  		gasPrice, err = t.rpcWrapper.SuggestGasPrice(ctx)
   288  		if err != nil {
   289  			return validatedArgs, hash, err
   290  		}
   291  	}
   292  
   293  	chainID := big.NewInt(int64(t.networkID))
   294  	value := (*big.Int)(args.Value)
   295  
   296  	var gas uint64
   297  	if args.Gas == nil {
   298  		ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
   299  		defer cancel()
   300  
   301  		var (
   302  			gethTo    common.Address
   303  			gethToPtr *common.Address
   304  		)
   305  		if args.To != nil {
   306  			gethTo = common.Address(*args.To)
   307  			gethToPtr = &gethTo
   308  		}
   309  		if args.IsDynamicFeeTx() {
   310  			gas, err = t.rpcWrapper.EstimateGas(ctx, ethereum.CallMsg{
   311  				From:      common.Address(args.From),
   312  				To:        gethToPtr,
   313  				GasFeeCap: gasFeeCap,
   314  				GasTipCap: gasTipCap,
   315  				Value:     value,
   316  				Data:      args.GetInput(),
   317  			})
   318  		} else {
   319  			gas, err = t.rpcWrapper.EstimateGas(ctx, ethereum.CallMsg{
   320  				From:     common.Address(args.From),
   321  				To:       gethToPtr,
   322  				GasPrice: gasPrice,
   323  				Value:    value,
   324  				Data:     args.GetInput(),
   325  			})
   326  		}
   327  		if err != nil {
   328  			return validatedArgs, hash, err
   329  		}
   330  	} else {
   331  		gas = uint64(*args.Gas)
   332  	}
   333  
   334  	newNonce := hexutil.Uint64(nonce)
   335  	newGas := hexutil.Uint64(gas)
   336  	validatedArgs.Nonce = &newNonce
   337  	if !args.IsDynamicFeeTx() {
   338  		validatedArgs.GasPrice = (*hexutil.Big)(gasPrice)
   339  	} else {
   340  		validatedArgs.MaxPriorityFeePerGas = (*hexutil.Big)(gasTipCap)
   341  		validatedArgs.MaxPriorityFeePerGas = (*hexutil.Big)(gasFeeCap)
   342  	}
   343  	validatedArgs.Gas = &newGas
   344  
   345  	tx := t.buildTransaction(validatedArgs)
   346  	hash = types.Hash(gethtypes.NewLondonSigner(chainID).Hash(tx))
   347  
   348  	return validatedArgs, hash, nil
   349  }
   350  
   351  // make sure that only account which created the tx can complete it
   352  func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.SelectedExtKey) error {
   353  	if selectedAccount == nil {
   354  		return account.ErrNoAccountSelected
   355  	}
   356  
   357  	if !bytes.Equal(args.From.Bytes(), selectedAccount.Address.Bytes()) {
   358  		return ErrInvalidTxSender
   359  	}
   360  
   361  	return nil
   362  }
   363  
   364  func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, err error) {
   365  	if !args.Valid() {
   366  		return tx, ErrInvalidSendTxArgs
   367  	}
   368  
   369  	var nonce uint64
   370  	if args.Nonce != nil {
   371  		nonce = uint64(*args.Nonce)
   372  	} else {
   373  		// some chains, like arbitrum doesn't count pending txs in the nonce, so we need to calculate it manually
   374  		if lastUsedNonce < 0 {
   375  			nonce, err = t.NextNonce(rpcWrapper.RPCClient, rpcWrapper.chainID, args.From)
   376  			if err != nil {
   377  				return tx, err
   378  			}
   379  		} else {
   380  			nonce = uint64(lastUsedNonce) + 1
   381  		}
   382  	}
   383  
   384  	ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
   385  	defer cancel()
   386  
   387  	gasPrice := (*big.Int)(args.GasPrice)
   388  	// GasPrice should be estimated only for LegacyTx
   389  	if !args.IsDynamicFeeTx() && args.GasPrice == nil {
   390  		gasPrice, err = rpcWrapper.SuggestGasPrice(ctx)
   391  		if err != nil {
   392  			return tx, err
   393  		}
   394  	}
   395  
   396  	value := (*big.Int)(args.Value)
   397  	var gas uint64
   398  	if args.Gas != nil {
   399  		gas = uint64(*args.Gas)
   400  	} else {
   401  		ctx, cancel = context.WithTimeout(context.Background(), t.rpcCallTimeout)
   402  		defer cancel()
   403  
   404  		var (
   405  			gethTo    common.Address
   406  			gethToPtr *common.Address
   407  		)
   408  		if args.To != nil {
   409  			gethTo = common.Address(*args.To)
   410  			gethToPtr = &gethTo
   411  		}
   412  		if args.IsDynamicFeeTx() {
   413  			gasFeeCap := (*big.Int)(args.MaxFeePerGas)
   414  			gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas)
   415  			gas, err = t.rpcWrapper.EstimateGas(ctx, ethereum.CallMsg{
   416  				From:      common.Address(args.From),
   417  				To:        gethToPtr,
   418  				GasFeeCap: gasFeeCap,
   419  				GasTipCap: gasTipCap,
   420  				Value:     value,
   421  				Data:      args.GetInput(),
   422  			})
   423  		} else {
   424  			gas, err = t.rpcWrapper.EstimateGas(ctx, ethereum.CallMsg{
   425  				From:     common.Address(args.From),
   426  				To:       gethToPtr,
   427  				GasPrice: gasPrice,
   428  				Value:    value,
   429  				Data:     args.GetInput(),
   430  			})
   431  		}
   432  		if err != nil {
   433  			return tx, err
   434  		}
   435  	}
   436  
   437  	tx = t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args)
   438  	return tx, nil
   439  }
   440  
   441  func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) {
   442  	if err = t.validateAccount(args, selectedAccount); err != nil {
   443  		return hash, nonce, err
   444  	}
   445  
   446  	tx, err := t.validateAndBuildTransaction(rpcWrapper, args, lastUsedNonce)
   447  	if err != nil {
   448  		return hash, nonce, err
   449  	}
   450  
   451  	chainID := big.NewInt(int64(rpcWrapper.chainID))
   452  	signedTx, err := gethtypes.SignTx(tx, gethtypes.NewLondonSigner(chainID), selectedAccount.AccountKey.PrivateKey)
   453  	if err != nil {
   454  		return hash, nonce, err
   455  	}
   456  
   457  	hash, err = t.sendTransaction(rpcWrapper, common.Address(args.From), args.Symbol, args.MultiTransactionID, signedTx)
   458  	return hash, tx.Nonce(), err
   459  }
   460  
   461  func (t *Transactor) buildTransaction(args SendTxArgs) *gethtypes.Transaction {
   462  	var (
   463  		nonce    uint64
   464  		value    *big.Int
   465  		gas      uint64
   466  		gasPrice *big.Int
   467  	)
   468  	if args.Nonce != nil {
   469  		nonce = uint64(*args.Nonce)
   470  	}
   471  	if args.Value != nil {
   472  		value = (*big.Int)(args.Value)
   473  	}
   474  	if args.Gas != nil {
   475  		gas = uint64(*args.Gas)
   476  	}
   477  	if args.GasPrice != nil {
   478  		gasPrice = (*big.Int)(args.GasPrice)
   479  	}
   480  
   481  	return t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args)
   482  }
   483  
   484  func (t *Transactor) buildTransactionWithOverrides(nonce uint64, value *big.Int, gas uint64, gasPrice *big.Int, args SendTxArgs) *gethtypes.Transaction {
   485  	var tx *gethtypes.Transaction
   486  
   487  	if args.To != nil {
   488  		to := common.Address(*args.To)
   489  		var txData gethtypes.TxData
   490  
   491  		if args.IsDynamicFeeTx() {
   492  			gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas)
   493  			gasFeeCap := (*big.Int)(args.MaxFeePerGas)
   494  
   495  			txData = &gethtypes.DynamicFeeTx{
   496  				Nonce:     nonce,
   497  				Gas:       gas,
   498  				GasTipCap: gasTipCap,
   499  				GasFeeCap: gasFeeCap,
   500  				To:        &to,
   501  				Value:     value,
   502  				Data:      args.GetInput(),
   503  			}
   504  		} else {
   505  			txData = &gethtypes.LegacyTx{
   506  				Nonce:    nonce,
   507  				GasPrice: gasPrice,
   508  				Gas:      gas,
   509  				To:       &to,
   510  				Value:    value,
   511  				Data:     args.GetInput(),
   512  			}
   513  		}
   514  		tx = gethtypes.NewTx(txData)
   515  		t.logNewTx(args, gas, gasPrice, value)
   516  	} else {
   517  		if args.IsDynamicFeeTx() {
   518  			gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas)
   519  			gasFeeCap := (*big.Int)(args.MaxFeePerGas)
   520  
   521  			txData := &gethtypes.DynamicFeeTx{
   522  				Nonce:     nonce,
   523  				Value:     value,
   524  				Gas:       gas,
   525  				GasTipCap: gasTipCap,
   526  				GasFeeCap: gasFeeCap,
   527  				Data:      args.GetInput(),
   528  			}
   529  			tx = gethtypes.NewTx(txData)
   530  		} else {
   531  			tx = gethtypes.NewContractCreation(nonce, value, gas, gasPrice, args.GetInput())
   532  		}
   533  		t.logNewContract(args, gas, gasPrice, value, nonce)
   534  	}
   535  
   536  	return tx
   537  }
   538  
   539  func (t *Transactor) logNewTx(args SendTxArgs, gas uint64, gasPrice *big.Int, value *big.Int) {
   540  	t.log.Info("New transaction",
   541  		"From", args.From,
   542  		"To", *args.To,
   543  		"Gas", gas,
   544  		"GasPrice", gasPrice,
   545  		"Value", value,
   546  	)
   547  }
   548  
   549  func (t *Transactor) logNewContract(args SendTxArgs, gas uint64, gasPrice *big.Int, value *big.Int, nonce uint64) {
   550  	t.log.Info("New contract",
   551  		"From", args.From,
   552  		"Gas", gas,
   553  		"GasPrice", gasPrice,
   554  		"Value", value,
   555  		"Contract address", crypto.CreateAddress(args.From, nonce),
   556  	)
   557  }