diff --git a/api/geth_backend.go b/api/geth_backend.go index 359f6094c..864ddb2dd 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -2228,7 +2228,8 @@ func (b *GethStatusBackend) SendTransaction(sendArgs transactions.SendTxArgs, pa return hash, err } - return b.transactor.SendTransaction(sendArgs, verifiedAccount) + hash, _, err = b.transactor.SendTransaction(sendArgs, verifiedAccount, -1) + return hash, err } func (b *GethStatusBackend) SendTransactionWithChainID(chainID uint64, sendArgs transactions.SendTxArgs, password string) (hash types.Hash, err error) { @@ -2237,7 +2238,8 @@ func (b *GethStatusBackend) SendTransactionWithChainID(chainID uint64, sendArgs return hash, err } - return b.transactor.SendTransactionWithChainID(chainID, sendArgs, verifiedAccount) + hash, _, err = b.transactor.SendTransactionWithChainID(chainID, sendArgs, -1, verifiedAccount) + return hash, err } func (b *GethStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) { diff --git a/services/communitytokens/estimations.go b/services/communitytokens/estimations.go index 5b1816ca0..c9b85bb5d 100644 --- a/services/communitytokens/estimations.go +++ b/services/communitytokens/estimations.go @@ -388,7 +388,7 @@ func (s *Service) suggestedFeesToSendTxArgs(from common.Address, to *common.Addr } func (s *Service) estimateL1Fee(ctx context.Context, chainID uint64, sendArgs transactions.SendTxArgs) (uint64, error) { - transaction, err := s.transactor.ValidateAndBuildTransaction(chainID, sendArgs) + transaction, _, err := s.transactor.ValidateAndBuildTransaction(chainID, sendArgs, -1) if err != nil { return 0, err } diff --git a/services/wallet/router/pathprocessor/mock_pathprocessor/processor.go b/services/wallet/router/pathprocessor/mock_pathprocessor/processor.go index 45a051ad1..fd217c338 100644 --- a/services/wallet/router/pathprocessor/mock_pathprocessor/processor.go +++ b/services/wallet/router/pathprocessor/mock_pathprocessor/processor.go @@ -8,10 +8,9 @@ import ( big "math/big" reflect "reflect" - gomock "github.com/golang/mock/gomock" - common "github.com/ethereum/go-ethereum/common" types "github.com/ethereum/go-ethereum/core/types" + gomock "github.com/golang/mock/gomock" account "github.com/status-im/status-go/account" types0 "github.com/status-im/status-go/eth-node/types" pathprocessor "github.com/status-im/status-go/services/wallet/router/pathprocessor" @@ -56,33 +55,19 @@ func (mr *MockPathProcessorMockRecorder) AvailableFor(params interface{}) *gomoc } // BuildTransaction mocks base method. -func (m *MockPathProcessor) BuildTransaction(sendArgs *pathprocessor.MultipathProcessorTxArgs) (*types.Transaction, error) { +func (m *MockPathProcessor) BuildTransaction(sendArgs *pathprocessor.MultipathProcessorTxArgs, lastUsedNonce int64) (*types.Transaction, uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BuildTransaction", sendArgs) + ret := m.ctrl.Call(m, "BuildTransaction", sendArgs, lastUsedNonce) ret0, _ := ret[0].(*types.Transaction) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // BuildTransaction indicates an expected call of BuildTransaction. -func (mr *MockPathProcessorMockRecorder) BuildTransaction(sendArgs interface{}) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) BuildTransaction(sendArgs, lastUsedNonce interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTransaction", reflect.TypeOf((*MockPathProcessor)(nil).BuildTransaction), sendArgs) -} - -// BuildTx mocks base method. -func (m *MockPathProcessor) BuildTx(params pathprocessor.ProcessorInputParams) (*types.Transaction, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BuildTx", params) - ret0, _ := ret[0].(*types.Transaction) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// BuildTx indicates an expected call of BuildTx. -func (mr *MockPathProcessorMockRecorder) BuildTx(params interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTx", reflect.TypeOf((*MockPathProcessor)(nil).BuildTx), params) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTransaction", reflect.TypeOf((*MockPathProcessor)(nil).BuildTransaction), sendArgs, lastUsedNonce) } // CalculateAmountOut mocks base method. @@ -116,18 +101,6 @@ func (mr *MockPathProcessorMockRecorder) CalculateFees(params interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateFees", reflect.TypeOf((*MockPathProcessor)(nil).CalculateFees), params) } -// Clear mocks base method. -func (m *MockPathProcessor) Clear() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Clear") -} - -// Clear indicates an expected call of Clear. -func (mr *MockPathProcessorMockRecorder) Clear() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockPathProcessor)(nil).Clear)) -} - // EstimateGas mocks base method. func (m *MockPathProcessor) EstimateGas(params pathprocessor.ProcessorInputParams) (uint64, error) { m.ctrl.T.Helper() @@ -188,16 +161,52 @@ func (mr *MockPathProcessorMockRecorder) PackTxInputData(params interface{}) *go } // Send mocks base method. -func (m *MockPathProcessor) Send(sendArgs *pathprocessor.MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (types0.Hash, error) { +func (m *MockPathProcessor) Send(sendArgs *pathprocessor.MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types0.Hash, uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Send", sendArgs, verifiedAccount) + ret := m.ctrl.Call(m, "Send", sendArgs, lastUsedNonce, verifiedAccount) ret0, _ := ret[0].(types0.Hash) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // Send indicates an expected call of Send. -func (mr *MockPathProcessorMockRecorder) Send(sendArgs, verifiedAccount interface{}) *gomock.Call { +func (mr *MockPathProcessorMockRecorder) Send(sendArgs, lastUsedNonce, verifiedAccount interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockPathProcessor)(nil).Send), sendArgs, verifiedAccount) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockPathProcessor)(nil).Send), sendArgs, lastUsedNonce, verifiedAccount) +} + +// MockPathProcessorClearable is a mock of PathProcessorClearable interface. +type MockPathProcessorClearable struct { + ctrl *gomock.Controller + recorder *MockPathProcessorClearableMockRecorder +} + +// MockPathProcessorClearableMockRecorder is the mock recorder for MockPathProcessorClearable. +type MockPathProcessorClearableMockRecorder struct { + mock *MockPathProcessorClearable +} + +// NewMockPathProcessorClearable creates a new mock instance. +func NewMockPathProcessorClearable(ctrl *gomock.Controller) *MockPathProcessorClearable { + mock := &MockPathProcessorClearable{ctrl: ctrl} + mock.recorder = &MockPathProcessorClearableMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPathProcessorClearable) EXPECT() *MockPathProcessorClearableMockRecorder { + return m.recorder +} + +// Clear mocks base method. +func (m *MockPathProcessorClearable) Clear() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Clear") +} + +// Clear indicates an expected call of Clear. +func (mr *MockPathProcessorClearableMockRecorder) Clear() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockPathProcessorClearable)(nil).Clear)) } diff --git a/services/wallet/router/pathprocessor/processor.go b/services/wallet/router/pathprocessor/processor.go index a82fa9d94..d337439b4 100644 --- a/services/wallet/router/pathprocessor/processor.go +++ b/services/wallet/router/pathprocessor/processor.go @@ -25,14 +25,12 @@ type PathProcessor interface { EstimateGas(params ProcessorInputParams) (uint64, error) // CalculateAmountOut calculates the amount out CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) - // Send sends the tx - Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (types.Hash, error) + // Send sends the tx, returns the hash and the used nonce (lastUsedNonce is -1 if it's the first tx) + Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) // GetContractAddress returns the contract address GetContractAddress(params ProcessorInputParams) (common.Address, error) - // BuildTransaction builds the transaction based on MultipathProcessorTxArgs - BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) - // BuildTx builds the transaction based on ProcessorInputParams - BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) + // BuildTransaction builds the transaction based on MultipathProcessorTxArgs, returns the transaction and the used nonce (lastUsedNonce is -1 if it's the first tx) + BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) } type PathProcessorClearable interface { diff --git a/services/wallet/router/pathprocessor/processor_bridge_celar.go b/services/wallet/router/pathprocessor/processor_bridge_celar.go index 38afb41a3..1b11513d1 100644 --- a/services/wallet/router/pathprocessor/processor_bridge_celar.go +++ b/services/wallet/router/pathprocessor/processor_bridge_celar.go @@ -283,27 +283,6 @@ func (s *CelerBridgeProcessor) EstimateGas(params ProcessorInputParams) (uint64, return uint64(increasedEstimation), nil } -func (s *CelerBridgeProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - toAddr := types.Address(params.ToAddr) - sendArgs := &MultipathProcessorTxArgs{ - CbridgeTx: &CelerBridgeTxArgs{ - SendTxArgs: transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(params.AmountIn), - Data: types.HexBytes("0x0"), - }, - ChainID: params.ToChain.ChainID, - Symbol: params.FromToken.Symbol, - Recipient: params.ToAddr, - Amount: (*hexutil.Big)(params.AmountIn), - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) -} - func (s *CelerBridgeProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { transferConfig, err := s.getTransferConfig(params.FromChain.IsTest) if err != nil { @@ -322,7 +301,7 @@ func (s *CelerBridgeProcessor) GetContractAddress(params ProcessorInputParams) ( return common.Address{}, ErrContractNotFound } -func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn) (*ethTypes.Transaction, error) { +func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (*ethTypes.Transaction, error) { fromChain := s.rpcClient.NetworkManager.Find(sendArgs.ChainID) if fromChain == nil { return nil, ErrNetworkNotFound @@ -347,6 +326,11 @@ func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, s return nil, createBridgeCellerErrorResponse(err) } + if lastUsedNonce >= 0 { + lastUsedNonceHexUtil := hexutil.Uint64(uint64(lastUsedNonce) + 1) + sendArgs.CbridgeTx.Nonce = &lastUsedNonceHexUtil + } + var tx *ethTypes.Transaction txOpts := sendArgs.CbridgeTx.ToTransactOpts(signerFn) if token.IsNative() { @@ -379,17 +363,18 @@ func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, s return tx, nil } -func (s *CelerBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (types.Hash, error) { - tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount)) +func (s *CelerBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) { + tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount), lastUsedNonce) if err != nil { - return types.HexToHash(""), createBridgeCellerErrorResponse(err) + return types.HexToHash(""), 0, createBridgeCellerErrorResponse(err) } - return types.Hash(tx.Hash()), nil + return types.Hash(tx.Hash()), tx.Nonce(), nil } -func (s *CelerBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return s.sendOrBuild(sendArgs, nil) +func (s *CelerBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, err := s.sendOrBuild(sendArgs, nil, lastUsedNonce) + return tx, tx.Nonce(), err } func (s *CelerBridgeProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_bridge_hop.go b/services/wallet/router/pathprocessor/processor_bridge_hop.go index c27354888..6c28e26e3 100644 --- a/services/wallet/router/pathprocessor/processor_bridge_hop.go +++ b/services/wallet/router/pathprocessor/processor_bridge_hop.go @@ -277,34 +277,12 @@ func (h *HopBridgeProcessor) EstimateGas(params ProcessorInputParams) (uint64, e return uint64(increasedEstimation), nil } -func (h *HopBridgeProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - toAddr := types.Address(params.ToAddr) - sendArgs := &MultipathProcessorTxArgs{ - HopTx: &HopBridgeTxArgs{ - SendTxArgs: transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(params.AmountIn), - Data: types.HexBytes("0x0"), - }, - Symbol: params.FromToken.Symbol, - Recipient: params.ToAddr, - Amount: (*hexutil.Big)(params.AmountIn), - BonderFee: (*hexutil.Big)(params.BonderFee), - ChainID: params.ToChain.ChainID, - }, - ChainID: params.FromChain.ChainID, - } - - return h.BuildTransaction(sendArgs) -} - func (h *HopBridgeProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { address, _, err := hop.GetContractAddress(params.FromChain.ChainID, params.FromToken.Symbol) return address, createBridgeHopErrorResponse(err) } -func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) { +func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { fromChain := h.networkManager.Find(sendArgs.HopTx.ChainID) if fromChain == nil { return tx, fmt.Errorf("ChainID not supported %d", sendArgs.HopTx.ChainID) @@ -312,9 +290,14 @@ func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, sig token := h.tokenManager.FindToken(fromChain, sendArgs.HopTx.Symbol) - nonce, err := h.transactor.NextNonce(h.contractMaker.RPCClient, fromChain.ChainID, sendArgs.HopTx.From) - if err != nil { - return tx, createBridgeHopErrorResponse(err) + var nonce uint64 + if lastUsedNonce < 0 { + nonce, err = h.transactor.NextNonce(h.contractMaker.RPCClient, fromChain.ChainID, sendArgs.HopTx.From) + if err != nil { + return tx, createBridgeHopErrorResponse(err) + } + } else { + nonce = uint64(lastUsedNonce) + 1 } argNonce := hexutil.Uint64(nonce) @@ -366,16 +349,17 @@ func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, sig return tx, nil } -func (h *HopBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { - tx, err := h.sendOrBuild(sendArgs, getSigner(sendArgs.HopTx.ChainID, sendArgs.HopTx.From, verifiedAccount)) +func (h *HopBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error) { + tx, err := h.sendOrBuild(sendArgs, getSigner(sendArgs.HopTx.ChainID, sendArgs.HopTx.From, verifiedAccount), lastUsedNonce) if err != nil { - return types.Hash{}, createBridgeHopErrorResponse(err) + return types.Hash{}, 0, createBridgeHopErrorResponse(err) } - return types.Hash(tx.Hash()), nil + return types.Hash(tx.Hash()), tx.Nonce(), nil } -func (h *HopBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return h.sendOrBuild(sendArgs, nil) +func (h *HopBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, err := h.sendOrBuild(sendArgs, nil, lastUsedNonce) + return tx, tx.Nonce(), createBridgeHopErrorResponse(err) } func (h *HopBridgeProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_ens_public_key.go b/services/wallet/router/pathprocessor/processor_ens_public_key.go index bbc2c90b3..71913bc33 100644 --- a/services/wallet/router/pathprocessor/processor_ens_public_key.go +++ b/services/wallet/router/pathprocessor/processor_ens_public_key.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts" @@ -104,32 +103,12 @@ func (s *ENSPublicKeyProcessor) EstimateGas(params ProcessorInputParams) (uint64 return uint64(increasedEstimation), nil } -func (s *ENSPublicKeyProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - toAddr := types.Address(params.ToAddr) - inputData, err := s.PackTxInputData(params) - if err != nil { - return nil, createENSPublicKeyErrorResponse(err) - } - - sendArgs := &MultipathProcessorTxArgs{ - TransferTx: &transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(ZeroBigIntValue), - Data: inputData, - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) +func (s *ENSPublicKeyProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) } -func (s *ENSPublicKeyProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { - return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount) -} - -func (s *ENSPublicKeyProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx) +func (s *ENSPublicKeyProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } func (s *ENSPublicKeyProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_ens_register.go b/services/wallet/router/pathprocessor/processor_ens_register.go index eac6e0c96..2eed73c2c 100644 --- a/services/wallet/router/pathprocessor/processor_ens_register.go +++ b/services/wallet/router/pathprocessor/processor_ens_register.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts" @@ -140,32 +139,12 @@ func (s *ENSRegisterProcessor) EstimateGas(params ProcessorInputParams) (uint64, return uint64(increasedEstimation), nil } -func (s *ENSRegisterProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - toAddr := types.Address(params.ToAddr) - inputData, err := s.PackTxInputData(params) - if err != nil { - return nil, createENSRegisterProcessorErrorResponse(err) - } - - sendArgs := &MultipathProcessorTxArgs{ - TransferTx: &transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(ZeroBigIntValue), - Data: inputData, - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) +func (s *ENSRegisterProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) } -func (s *ENSRegisterProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { - return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount) -} - -func (s *ENSRegisterProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx) +func (s *ENSRegisterProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } func (s *ENSRegisterProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_ens_release.go b/services/wallet/router/pathprocessor/processor_ens_release.go index 76a61dec5..70772b8f6 100644 --- a/services/wallet/router/pathprocessor/processor_ens_release.go +++ b/services/wallet/router/pathprocessor/processor_ens_release.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts" @@ -103,32 +102,12 @@ func (s *ENSReleaseProcessor) EstimateGas(params ProcessorInputParams) (uint64, return uint64(increasedEstimation), nil } -func (s *ENSReleaseProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - toAddr := types.Address(params.ToAddr) - inputData, err := s.PackTxInputData(params) - if err != nil { - return nil, createENSReleaseErrorResponse(err) - } - - sendArgs := &MultipathProcessorTxArgs{ - TransferTx: &transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(ZeroBigIntValue), - Data: inputData, - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) +func (s *ENSReleaseProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) } -func (s *ENSReleaseProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { - return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount) -} - -func (s *ENSReleaseProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx) +func (s *ENSReleaseProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } func (s *ENSReleaseProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_erc1155.go b/services/wallet/router/pathprocessor/processor_erc1155.go index 4dd6e6b03..b4e68710c 100644 --- a/services/wallet/router/pathprocessor/processor_erc1155.go +++ b/services/wallet/router/pathprocessor/processor_erc1155.go @@ -108,34 +108,7 @@ func (s *ERC1155Processor) EstimateGas(params ProcessorInputParams) (uint64, err return uint64(increasedEstimation), nil } -func (s *ERC1155Processor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - contractAddress := types.Address(params.FromToken.Address) - - // We store ERC1155 Token ID using big.Int.String() in token.Symbol - tokenID, success := new(big.Int).SetString(params.FromToken.Symbol, 10) - if !success { - return nil, createERC1155ErrorResponse(fmt.Errorf("failed to convert ERC1155's Symbol %s to big.Int", params.FromToken.Symbol)) - } - - sendArgs := &MultipathProcessorTxArgs{ - ERC1155TransferTx: &ERC1155TxArgs{ - SendTxArgs: transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &contractAddress, - Value: (*hexutil.Big)(params.AmountIn), - Data: types.HexBytes("0x0"), - }, - TokenID: (*hexutil.Big)(tokenID), - Recipient: params.ToAddr, - Amount: (*hexutil.Big)(params.AmountIn), - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) -} - -func (s *ERC1155Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) { +func (s *ERC1155Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID) if err != nil { return tx, createERC1155ErrorResponse(err) @@ -146,9 +119,14 @@ func (s *ERC1155Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signe return tx, createERC1155ErrorResponse(err) } - nonce, err := s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC1155TransferTx.From) - if err != nil { - return tx, createERC1155ErrorResponse(err) + var nonce uint64 + if lastUsedNonce < 0 { + nonce, err = s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC1155TransferTx.From) + if err != nil { + return tx, createERC1155ErrorResponse(err) + } + } else { + nonce = uint64(lastUsedNonce) + 1 } argNonce := hexutil.Uint64(nonce) @@ -172,16 +150,17 @@ func (s *ERC1155Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signe return tx, nil } -func (s *ERC1155Processor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { - tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC1155TransferTx.From, verifiedAccount)) +func (s *ERC1155Processor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC1155TransferTx.From, verifiedAccount), lastUsedNonce) if err != nil { - return hash, createERC1155ErrorResponse(err) + return hash, 0, createERC1155ErrorResponse(err) } - return types.Hash(tx.Hash()), nil + return types.Hash(tx.Hash()), tx.Nonce(), nil } -func (s *ERC1155Processor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return s.sendOrBuild(sendArgs, nil) +func (s *ERC1155Processor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, err := s.sendOrBuild(sendArgs, nil, lastUsedNonce) + return tx, tx.Nonce(), err } func (s *ERC1155Processor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_erc721.go b/services/wallet/router/pathprocessor/processor_erc721.go index b8f14557f..fe5f74f7c 100644 --- a/services/wallet/router/pathprocessor/processor_erc721.go +++ b/services/wallet/router/pathprocessor/processor_erc721.go @@ -106,33 +106,7 @@ func (s *ERC721Processor) EstimateGas(params ProcessorInputParams) (uint64, erro return uint64(increasedEstimation), nil } -func (s *ERC721Processor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - contractAddress := types.Address(params.FromToken.Address) - - // We store ERC721 Token ID using big.Int.String() in token.Symbol - tokenID, success := new(big.Int).SetString(params.FromToken.Symbol, 10) - if !success { - return nil, createERC721ErrorResponse(fmt.Errorf("failed to convert ERC721's Symbol %s to big.Int", params.FromToken.Symbol)) - } - - sendArgs := &MultipathProcessorTxArgs{ - ERC721TransferTx: &ERC721TxArgs{ - SendTxArgs: transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &contractAddress, - Value: (*hexutil.Big)(params.AmountIn), - Data: types.HexBytes("0x0"), - }, - TokenID: (*hexutil.Big)(tokenID), - Recipient: params.ToAddr, - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) -} - -func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) { +func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID) if err != nil { return tx, createERC721ErrorResponse(err) @@ -143,9 +117,14 @@ func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signer return tx, createERC721ErrorResponse(err) } - nonce, err := s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC721TransferTx.From) - if err != nil { - return tx, createERC721ErrorResponse(err) + var nonce uint64 + if lastUsedNonce < 0 { + nonce, err = s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC721TransferTx.From) + if err != nil { + return tx, createERC721ErrorResponse(err) + } + } else { + nonce = uint64(lastUsedNonce) + 1 } argNonce := hexutil.Uint64(nonce) @@ -165,16 +144,17 @@ func (s *ERC721Processor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signer return tx, nil } -func (s *ERC721Processor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { - tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC721TransferTx.From, verifiedAccount)) +func (s *ERC721Processor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC721TransferTx.From, verifiedAccount), lastUsedNonce) if err != nil { - return hash, createERC721ErrorResponse(err) + return hash, 0, createERC721ErrorResponse(err) } - return types.Hash(tx.Hash()), nil + return types.Hash(tx.Hash()), tx.Nonce(), nil } -func (s *ERC721Processor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return s.sendOrBuild(sendArgs, nil) +func (s *ERC721Processor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, err := s.sendOrBuild(sendArgs, nil, lastUsedNonce) + return tx, tx.Nonce(), err } func (s *ERC721Processor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_stickers_buy.go b/services/wallet/router/pathprocessor/processor_stickers_buy.go index c8433adfe..540ad3b6e 100644 --- a/services/wallet/router/pathprocessor/processor_stickers_buy.go +++ b/services/wallet/router/pathprocessor/processor_stickers_buy.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts" @@ -132,32 +131,12 @@ func (s *StickersBuyProcessor) EstimateGas(params ProcessorInputParams) (uint64, return uint64(increasedEstimation), nil } -func (s *StickersBuyProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - toAddr := types.Address(params.ToAddr) - inputData, err := s.PackTxInputData(params) - if err != nil { - return nil, createStickersBuyErrorResponse(err) - } - - sendArgs := &MultipathProcessorTxArgs{ - TransferTx: &transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(ZeroBigIntValue), - Data: inputData, - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) +func (s *StickersBuyProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) } -func (s *StickersBuyProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { - return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount) -} - -func (s *StickersBuyProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx) +func (s *StickersBuyProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } func (s *StickersBuyProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_swap_paraswap.go b/services/wallet/router/pathprocessor/processor_swap_paraswap.go index e6e247a31..649e02ac6 100644 --- a/services/wallet/router/pathprocessor/processor_swap_paraswap.go +++ b/services/wallet/router/pathprocessor/processor_swap_paraswap.go @@ -209,27 +209,6 @@ func (s *SwapParaswapProcessor) GetContractAddress(params ProcessorInputParams) return } -func (s *SwapParaswapProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - toAddr := types.Address(params.ToAddr) - sendArgs := &MultipathProcessorTxArgs{ - SwapTx: &SwapParaswapTxArgs{ - SendTxArgs: transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(params.AmountIn), - Data: types.HexBytes("0x0"), - Symbol: params.FromToken.Symbol, - }, - ChainID: params.FromChain.ChainID, - ChainIDTo: params.ToChain.ChainID, - TokenIDFrom: params.FromToken.Symbol, - TokenIDTo: params.ToToken.Symbol, - }, - } - - return s.BuildTransaction(sendArgs) -} - func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorTxArgs) error { slippageBP := uint(sendArgs.SwapTx.SlippagePercentage * 100) // convert to basis points @@ -276,21 +255,21 @@ func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorT return nil } -func (s *SwapParaswapProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { +func (s *SwapParaswapProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { err := s.prepareTransaction(sendArgs) if err != nil { - return nil, createSwapParaswapErrorResponse(err) + return nil, 0, createSwapParaswapErrorResponse(err) } - return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, sendArgs.SwapTx.SendTxArgs) + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, sendArgs.SwapTx.SendTxArgs, lastUsedNonce) } -func (s *SwapParaswapProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (types.Hash, error) { +func (s *SwapParaswapProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) { err := s.prepareTransaction(sendArgs) if err != nil { - return types.Hash{}, createSwapParaswapErrorResponse(err) + return types.Hash{}, 0, createSwapParaswapErrorResponse(err) } - return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, sendArgs.SwapTx.SendTxArgs, verifiedAccount) + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, sendArgs.SwapTx.SendTxArgs, lastUsedNonce, verifiedAccount) } func (s *SwapParaswapProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/router/pathprocessor/processor_transfer.go b/services/wallet/router/pathprocessor/processor_transfer.go index 15bb974c7..33baf256b 100644 --- a/services/wallet/router/pathprocessor/processor_transfer.go +++ b/services/wallet/router/pathprocessor/processor_transfer.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts/ierc20" @@ -114,51 +113,12 @@ func (s *TransferProcessor) EstimateGas(params ProcessorInputParams) (uint64, er return uint64(increasedEstimation), nil } -func (s *TransferProcessor) BuildTx(params ProcessorInputParams) (*ethTypes.Transaction, error) { - toAddr := types.Address(params.ToAddr) - if params.FromToken.IsNative() { - sendArgs := &MultipathProcessorTxArgs{ - TransferTx: &transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(params.AmountIn), - Data: types.HexBytes("0x0"), - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) - } - abi, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) - if err != nil { - return nil, createTransferErrorResponse(err) - } - input, err := abi.Pack("transfer", - params.ToAddr, - params.AmountIn, - ) - if err != nil { - return nil, createTransferErrorResponse(err) - } - sendArgs := &MultipathProcessorTxArgs{ - TransferTx: &transactions.SendTxArgs{ - From: types.Address(params.FromAddr), - To: &toAddr, - Value: (*hexutil.Big)(ZeroBigIntValue), - Data: input, - }, - ChainID: params.FromChain.ChainID, - } - - return s.BuildTransaction(sendArgs) +func (s *TransferProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) } -func (s *TransferProcessor) Send(sendArgs *MultipathProcessorTxArgs, verifiedAccount *account.SelectedExtKey) (types.Hash, error) { - return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount) -} - -func (s *TransferProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs) (*ethTypes.Transaction, error) { - return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx) +func (s *TransferProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) } func (s *TransferProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { diff --git a/services/wallet/transfer/helpers.go b/services/wallet/transfer/helpers.go index 6a8ee9547..8733bea22 100644 --- a/services/wallet/transfer/helpers.go +++ b/services/wallet/transfer/helpers.go @@ -158,12 +158,21 @@ func sendTransactions(data []*pathprocessor.MultipathProcessorTxArgs, pathProces map[uint64][]types.Hash, error) { hashes := make(map[uint64][]types.Hash) + usedNonces := make(map[uint64]int64) for _, tx := range data { - hash, err := pathProcessors[tx.Name].Send(tx, account) + + lastUsedNonce := int64(-1) + if nonce, ok := usedNonces[tx.ChainID]; ok { + lastUsedNonce = nonce + } + + hash, usedNonce, err := pathProcessors[tx.Name].Send(tx, lastUsedNonce, account) if err != nil { return nil, err // TODO: One of transfers within transaction could have been sent. Need to notify user about it } + hashes[tx.ChainID] = append(hashes[tx.ChainID], hash) + usedNonces[tx.ChainID] = int64(usedNonce) } return hashes, nil } diff --git a/services/wallet/transfer/transaction_manager.go b/services/wallet/transfer/transaction_manager.go index 184cb9718..c839d4f6c 100644 --- a/services/wallet/transfer/transaction_manager.go +++ b/services/wallet/transfer/transaction_manager.go @@ -182,7 +182,7 @@ func (tm *TransactionManager) BuildTransaction(chainID uint64, sendArgs transact return nil, err } - txBeingSigned, err := tm.transactor.ValidateAndBuildTransaction(chainID, sendArgs) + txBeingSigned, _, err := tm.transactor.ValidateAndBuildTransaction(chainID, sendArgs, -1) if err != nil { return nil, err } diff --git a/services/wallet/transfer/transaction_manager_internal.go b/services/wallet/transfer/transaction_manager_internal.go index 507283af6..f50991d13 100644 --- a/services/wallet/transfer/transaction_manager_internal.go +++ b/services/wallet/transfer/transaction_manager_internal.go @@ -11,8 +11,15 @@ import ( func (tm *TransactionManager) buildTransactions(pathProcessors map[string]pathprocessor.PathProcessor) ([]string, error) { tm.transactionsForKeycardSigning = make(map[common.Hash]*TransactionDescription) var hashes []string + usedNonces := make(map[uint64]int64) for _, bridgeTx := range tm.multipathTransactionsData { - builtTx, err := pathProcessors[bridgeTx.Name].BuildTransaction(bridgeTx) + + lastUsedNonce := int64(-1) + if nonce, ok := usedNonces[bridgeTx.ChainID]; ok { + lastUsedNonce = nonce + } + + builtTx, usedNonce, err := pathProcessors[bridgeTx.Name].BuildTransaction(bridgeTx, lastUsedNonce) if err != nil { return hashes, err } @@ -26,6 +33,8 @@ func (tm *TransactionManager) buildTransactions(pathProcessors map[string]pathpr builtTx: builtTx, } + usedNonces[bridgeTx.ChainID] = int64(usedNonce) + hashes = append(hashes, txHash.String()) } diff --git a/services/wallet/transfer/transaction_manager_multitransaction_test.go b/services/wallet/transfer/transaction_manager_multitransaction_test.go index 2aa4c9032..b3a09530e 100644 --- a/services/wallet/transfer/transaction_manager_multitransaction_test.go +++ b/services/wallet/transfer/transaction_manager_multitransaction_test.go @@ -166,7 +166,7 @@ func TestSendTransactionsETHSuccess(t *testing.T) { // Verify that the SendTransactionWithChainID method is called for each transaction with proper arguments // Return values are not checked, because they must be checked in Transactor tests for _, tx := range expectedData { - transactor.EXPECT().SendTransactionWithChainID(tx.ChainID, *(tx.TransferTx), account).Return(types.Hash{}, nil) + transactor.EXPECT().SendTransactionWithChainID(tx.ChainID, *(tx.TransferTx), int64(-1), account).Return(types.Hash{}, uint64(0), nil) } // Call the SendTransactions method @@ -182,7 +182,7 @@ func TestSendTransactionsApproveSuccess(t *testing.T) { // Verify that the SendTransactionWithChainID method is called for each transaction with proper arguments // Return values are not checked, because they must be checked in Transactor tests for _, tx := range expectedData { - transactor.EXPECT().SendTransactionWithChainID(tx.ChainID, *(tx.TransferTx), account).Return(types.Hash{}, nil) + transactor.EXPECT().SendTransactionWithChainID(tx.ChainID, *(tx.TransferTx), int64(-1), account).Return(types.Hash{}, uint64(0), nil) } // Call the SendTransactions method @@ -205,7 +205,7 @@ func TestSendTransactionsETHFailOnBridge(t *testing.T) { expectedErr := transactions.ErrInvalidTxSender // Any error to verify // In case of bridge error, verify that the error is returned - transferBridge.EXPECT().Send(gomock.Any(), gomock.Any()).Return(types.Hash{}, transactions.ErrInvalidTxSender) + transferBridge.EXPECT().Send(gomock.Any(), int64(-1), gomock.Any()).Return(types.Hash{}, uint64(0), transactions.ErrInvalidTxSender) // Call the SendTransactions method _, err := tm.SendTransactions(context.Background(), multiTransaction, data, bridges, account) @@ -220,8 +220,8 @@ func TestSendTransactionsETHFailOnTransactor(t *testing.T) { // Verify that the SendTransactionWithChainID method is called for each transaction with proper arguments // Return values are not checked, because they must be checked in Transactor tests. Only error propagation matters here expectedErr := transactions.ErrInvalidTxSender // Any error to verify - transactor.EXPECT().SendTransactionWithChainID(expectedData[0].ChainID, *(expectedData[0].TransferTx), account).Return(types.Hash{}, nil) - transactor.EXPECT().SendTransactionWithChainID(expectedData[1].ChainID, *(expectedData[1].TransferTx), account).Return(types.Hash{}, expectedErr) + transactor.EXPECT().SendTransactionWithChainID(expectedData[0].ChainID, *(expectedData[0].TransferTx), int64(-1), account).Return(types.Hash{}, uint64(0), nil) + transactor.EXPECT().SendTransactionWithChainID(expectedData[1].ChainID, *(expectedData[1].TransferTx), int64(-1), account).Return(types.Hash{}, uint64(0), expectedErr) // Call the SendTransactions method _, err := tm.SendTransactions(context.Background(), multiTransaction, data, bridges, account) diff --git a/services/wallet/transfer/transaction_manager_test.go b/services/wallet/transfer/transaction_manager_test.go index b13ac6b61..11e7f9e29 100644 --- a/services/wallet/transfer/transaction_manager_test.go +++ b/services/wallet/transfer/transaction_manager_test.go @@ -280,7 +280,7 @@ func TestBuildTransaction(t *testing.T) { } expectedTx := gethtypes.NewTransaction(nonce, common.Address(*sendArgs.To), sendArgs.Value.ToInt(), gas, sendArgs.GasPrice.ToInt(), nil) - transactor.EXPECT().ValidateAndBuildTransaction(chainID, sendArgs).Return(expectedTx, nil) + transactor.EXPECT().ValidateAndBuildTransaction(chainID, sendArgs, int64(-1)).Return(expectedTx, uint64(0), nil) response, err := manager.BuildTransaction(chainID, sendArgs) require.NoError(t, err) @@ -334,7 +334,7 @@ func TestBuildTransaction_InvalidSendTxArgs(t *testing.T) { } expectedErr := fmt.Errorf("invalid SendTxArgs") - transactor.EXPECT().ValidateAndBuildTransaction(chainID, sendArgs).Return(nil, expectedErr) + transactor.EXPECT().ValidateAndBuildTransaction(chainID, sendArgs, int64(-1)).Return(nil, uint64(0), expectedErr) tx, err := manager.BuildTransaction(chainID, sendArgs) require.Equal(t, expectedErr, err) require.Nil(t, tx) diff --git a/services/web3provider/signature.go b/services/web3provider/signature.go index 6f20d7c0d..1a4dd989e 100644 --- a/services/web3provider/signature.go +++ b/services/web3provider/signature.go @@ -79,7 +79,7 @@ func (api *API) sendTransaction(chainID uint64, sendArgs transactions.SendTxArgs return hash, err } - hash, err = api.s.transactor.SendTransactionWithChainID(chainID, sendArgs, verifiedAccount) + hash, _, err = api.s.transactor.SendTransactionWithChainID(chainID, sendArgs, -1, verifiedAccount) if err != nil { return } diff --git a/transactions/mock_transactor/transactor.go b/transactions/mock_transactor/transactor.go index 09982e599..e927f7997 100644 --- a/transactions/mock_transactor/transactor.go +++ b/transactions/mock_transactor/transactor.go @@ -8,10 +8,9 @@ import ( big "math/big" reflect "reflect" - gomock "github.com/golang/mock/gomock" - common "github.com/ethereum/go-ethereum/common" types "github.com/ethereum/go-ethereum/core/types" + gomock "github.com/golang/mock/gomock" account "github.com/status-im/status-go/account" types0 "github.com/status-im/status-go/eth-node/types" params "github.com/status-im/status-go/params" @@ -118,33 +117,35 @@ func (mr *MockTransactorIfaceMockRecorder) SendRawTransaction(chainID, rawTx int } // SendTransaction mocks base method. -func (m *MockTransactorIface) SendTransaction(sendArgs transactions.SendTxArgs, verifiedAccount *account.SelectedExtKey) (types0.Hash, error) { +func (m *MockTransactorIface) SendTransaction(sendArgs transactions.SendTxArgs, verifiedAccount *account.SelectedExtKey, lastUsedNonce int64) (types0.Hash, uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendTransaction", sendArgs, verifiedAccount) + ret := m.ctrl.Call(m, "SendTransaction", sendArgs, verifiedAccount, lastUsedNonce) ret0, _ := ret[0].(types0.Hash) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // SendTransaction indicates an expected call of SendTransaction. -func (mr *MockTransactorIfaceMockRecorder) SendTransaction(sendArgs, verifiedAccount interface{}) *gomock.Call { +func (mr *MockTransactorIfaceMockRecorder) SendTransaction(sendArgs, verifiedAccount, lastUsedNonce interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransaction", reflect.TypeOf((*MockTransactorIface)(nil).SendTransaction), sendArgs, verifiedAccount) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransaction", reflect.TypeOf((*MockTransactorIface)(nil).SendTransaction), sendArgs, verifiedAccount, lastUsedNonce) } // SendTransactionWithChainID mocks base method. -func (m *MockTransactorIface) SendTransactionWithChainID(chainID uint64, sendArgs transactions.SendTxArgs, verifiedAccount *account.SelectedExtKey) (types0.Hash, error) { +func (m *MockTransactorIface) SendTransactionWithChainID(chainID uint64, sendArgs transactions.SendTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types0.Hash, uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendTransactionWithChainID", chainID, sendArgs, verifiedAccount) + ret := m.ctrl.Call(m, "SendTransactionWithChainID", chainID, sendArgs, lastUsedNonce, verifiedAccount) ret0, _ := ret[0].(types0.Hash) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // SendTransactionWithChainID indicates an expected call of SendTransactionWithChainID. -func (mr *MockTransactorIfaceMockRecorder) SendTransactionWithChainID(chainID, sendArgs, verifiedAccount interface{}) *gomock.Call { +func (mr *MockTransactorIfaceMockRecorder) SendTransactionWithChainID(chainID, sendArgs, lastUsedNonce, verifiedAccount interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransactionWithChainID", reflect.TypeOf((*MockTransactorIface)(nil).SendTransactionWithChainID), chainID, sendArgs, verifiedAccount) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendTransactionWithChainID", reflect.TypeOf((*MockTransactorIface)(nil).SendTransactionWithChainID), chainID, sendArgs, lastUsedNonce, verifiedAccount) } // SendTransactionWithSignature mocks base method. @@ -177,16 +178,17 @@ func (mr *MockTransactorIfaceMockRecorder) StoreAndTrackPendingTx(from, symbol, } // ValidateAndBuildTransaction mocks base method. -func (m *MockTransactorIface) ValidateAndBuildTransaction(chainID uint64, sendArgs transactions.SendTxArgs) (*types.Transaction, error) { +func (m *MockTransactorIface) ValidateAndBuildTransaction(chainID uint64, sendArgs transactions.SendTxArgs, lastUsedNonce int64) (*types.Transaction, uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateAndBuildTransaction", chainID, sendArgs) + ret := m.ctrl.Call(m, "ValidateAndBuildTransaction", chainID, sendArgs, lastUsedNonce) ret0, _ := ret[0].(*types.Transaction) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // ValidateAndBuildTransaction indicates an expected call of ValidateAndBuildTransaction. -func (mr *MockTransactorIfaceMockRecorder) ValidateAndBuildTransaction(chainID, sendArgs interface{}) *gomock.Call { +func (mr *MockTransactorIfaceMockRecorder) ValidateAndBuildTransaction(chainID, sendArgs, lastUsedNonce interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAndBuildTransaction", reflect.TypeOf((*MockTransactorIface)(nil).ValidateAndBuildTransaction), chainID, sendArgs) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAndBuildTransaction", reflect.TypeOf((*MockTransactorIface)(nil).ValidateAndBuildTransaction), chainID, sendArgs, lastUsedNonce) } diff --git a/transactions/transactor.go b/transactions/transactor.go index 66395748f..1dee6d799 100644 --- a/transactions/transactor.go +++ b/transactions/transactor.go @@ -48,9 +48,9 @@ func (e *ErrBadNonce) Error() string { type TransactorIface interface { NextNonce(rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error) EstimateGas(network *params.Network, from common.Address, to common.Address, value *big.Int, input []byte) (uint64, error) - SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) - SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) - ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs) (tx *gethtypes.Transaction, err error) + SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) + SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error) + ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, nonce uint64, err error) AddSignatureToTransaction(chainID uint64, tx *gethtypes.Transaction, sig []byte) (*gethtypes.Transaction, error) SendRawTransaction(chainID uint64, rawTx string) error BuildTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (*gethtypes.Transaction, error) @@ -139,21 +139,21 @@ func (t *Transactor) EstimateGas(network *params.Network, from common.Address, t } // SendTransaction is an implementation of eth_sendTransaction. It queues the tx to the sign queue. -func (t *Transactor) SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { - hash, err = t.validateAndPropagate(t.rpcWrapper, verifiedAccount, sendArgs) +func (t *Transactor) SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) { + hash, nonce, err = t.validateAndPropagate(t.rpcWrapper, verifiedAccount, sendArgs, lastUsedNonce) return } -func (t *Transactor) SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { +func (t *Transactor) SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error) { wrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) - hash, err = t.validateAndPropagate(wrapper, verifiedAccount, sendArgs) + hash, nonce, err = t.validateAndPropagate(wrapper, verifiedAccount, sendArgs, lastUsedNonce) return } -func (t *Transactor) ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs) (tx *gethtypes.Transaction, err error) { +func (t *Transactor) ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, nonce uint64, err error) { wrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) - tx, err = t.validateAndBuildTransaction(wrapper, sendArgs) - return + tx, err = t.validateAndBuildTransaction(wrapper, sendArgs, lastUsedNonce) + return tx, tx.Nonce(), err } func (t *Transactor) AddSignatureToTransaction(chainID uint64, tx *gethtypes.Transaction, sig []byte) (*gethtypes.Transaction, error) { @@ -361,7 +361,7 @@ func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.S return nil } -func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args SendTxArgs) (tx *gethtypes.Transaction, err error) { +func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, err error) { if !args.Valid() { return tx, ErrInvalidSendTxArgs } @@ -370,9 +370,14 @@ func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args Se if args.Nonce != nil { nonce = uint64(*args.Nonce) } else { - nonce, err = t.NextNonce(rpcWrapper.RPCClient, rpcWrapper.chainID, args.From) - if err != nil { - return tx, err + // some chains, like arbitrum doesn't count pending txs in the nonce, so we need to calculate it manually + if lastUsedNonce < 0 { + nonce, err = t.NextNonce(rpcWrapper.RPCClient, rpcWrapper.chainID, args.From) + if err != nil { + return tx, err + } + } else { + nonce = uint64(lastUsedNonce) + 1 } } @@ -433,23 +438,24 @@ func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args Se return tx, nil } -func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs) (hash types.Hash, err error) { +func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) { if err = t.validateAccount(args, selectedAccount); err != nil { - return hash, err + return hash, nonce, err } - tx, err := t.validateAndBuildTransaction(rpcWrapper, args) + tx, err := t.validateAndBuildTransaction(rpcWrapper, args, lastUsedNonce) if err != nil { - return hash, err + return hash, nonce, err } chainID := big.NewInt(int64(rpcWrapper.chainID)) signedTx, err := gethtypes.SignTx(tx, gethtypes.NewLondonSigner(chainID), selectedAccount.AccountKey.PrivateKey) if err != nil { - return hash, err + return hash, nonce, err } - return t.sendTransaction(rpcWrapper, common.Address(args.From), args.Symbol, args.MultiTransactionID, signedTx) + hash, err = t.sendTransaction(rpcWrapper, common.Address(args.From), args.Symbol, args.MultiTransactionID, signedTx) + return hash, tx.Nonce(), err } func (t *Transactor) buildTransaction(args SendTxArgs) *gethtypes.Transaction { diff --git a/transactions/transactor_test.go b/transactions/transactor_test.go index 68bac9e39..8ff005713 100644 --- a/transactions/transactor_test.go +++ b/transactions/transactor_test.go @@ -207,7 +207,7 @@ func (s *TransactorSuite) TestGasValues() { } s.setupTransactionPoolAPI(args, testNonce, testNonce, selectedAccount, nil) - hash, err := s.manager.SendTransaction(args, selectedAccount) + hash, _, err := s.manager.SendTransaction(args, selectedAccount, -1) s.NoError(err) s.False(reflect.DeepEqual(hash, common.Hash{})) }) @@ -256,7 +256,7 @@ func (s *TransactorSuite) TestBuildAndValidateTransaction() { } s.setupBuildTransactionMocks(args, selectedAccount) - tx, err := s.manager.ValidateAndBuildTransaction(chainID, args) + tx, _, err := s.manager.ValidateAndBuildTransaction(chainID, args, -1) s.NoError(err) s.Equal(tx.Gas(), uint64(gas), "The gas shouldn't be estimated, but should use the gas from the Tx") s.Equal(tx.GasFeeCap(), expectedGasPrice, "The maxFeePerGas should be the same as in the original Tx") @@ -275,7 +275,7 @@ func (s *TransactorSuite) TestBuildAndValidateTransaction() { } s.setupBuildTransactionMocks(args, selectedAccount) - tx, err := s.manager.ValidateAndBuildTransaction(chainID, args) + tx, _, err := s.manager.ValidateAndBuildTransaction(chainID, args, -1) s.NoError(err) s.Equal(tx.Gas(), expectedGas, "The gas should be estimated if not present in the original Tx") s.Equal(tx.Nonce(), expectedNonce, "The nonce should be added if not present in the original Tx") @@ -298,7 +298,7 @@ func (s *TransactorSuite) TestBuildAndValidateTransaction() { } s.setupBuildTransactionMocks(args, selectedAccount) - tx, err := s.manager.ValidateAndBuildTransaction(chainID, args) + tx, _, err := s.manager.ValidateAndBuildTransaction(chainID, args, -1) s.NoError(err) s.Equal(tx.Gas(), uint64(gas), "The gas shouldn't be estimated, but should use the gas from the Tx") s.Equal(tx.GasPrice(), expectedGasPrice, "The gasPrice should be the same as in the original Tx") @@ -314,7 +314,7 @@ func (s *TransactorSuite) TestBuildAndValidateTransaction() { } s.setupBuildTransactionMocks(args, selectedAccount) - tx, err := s.manager.ValidateAndBuildTransaction(chainID, args) + tx, _, err := s.manager.ValidateAndBuildTransaction(chainID, args, -1) s.NoError(err) s.Equal(tx.Gas(), expectedGas, "The gas should be estimated if not present in the original Tx") s.Equal(tx.GasPrice(), expectedGasPrice, "The gasPrice should be estimated if not present in the original Tx") @@ -333,7 +333,7 @@ func (s *TransactorSuite) TestArgsValidation() { selectedAccount := &account.SelectedExtKey{ Address: account.FromAddress(utils.TestConfig.Account1.WalletAddress), } - _, err := s.manager.SendTransaction(args, selectedAccount) + _, _, err := s.manager.SendTransaction(args, selectedAccount, -1) s.EqualError(err, ErrInvalidSendTxArgs.Error()) } @@ -346,14 +346,14 @@ func (s *TransactorSuite) TestAccountMismatch() { var err error // missing account - _, err = s.manager.SendTransaction(args, nil) + _, _, err = s.manager.SendTransaction(args, nil, -1) s.EqualError(err, account.ErrNoAccountSelected.Error()) // mismatched accounts selectedAccount := &account.SelectedExtKey{ Address: account.FromAddress(utils.TestConfig.Account2.WalletAddress), } - _, err = s.manager.SendTransaction(args, selectedAccount) + _, _, err = s.manager.SendTransaction(args, selectedAccount, -1) s.EqualError(err, ErrInvalidTxSender.Error()) }