package transfer import ( "fmt" "math/big" "time" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/event" "github.com/status-im/status-go/account" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/params" wallet_common "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/router/pathprocessor" "github.com/status-im/status-go/services/wallet/router/routes" "github.com/status-im/status-go/transactions" ) type SignatureDetails struct { R string `json:"r"` S string `json:"s"` V string `json:"v"` } func (sd *SignatureDetails) Validate() error { if len(sd.R) != 64 || len(sd.S) != 64 || len(sd.V) != 2 { return ErrInvalidSignatureDetails } return nil } // TODO: remove this struct once mobile switches to the new approach type TransactionDescription struct { chainID uint64 from common.Address builtTx *ethTypes.Transaction signature []byte } type TransactionData struct { TxArgs *transactions.SendTxArgs Tx *ethTypes.Transaction HashToSign types.Hash Signature []byte SentHash types.Hash } func (txd *TransactionData) IsTxPlaced() bool { return txd.SentHash != types.Hash(wallet_common.ZeroHash()) } type RouterTransactionDetails struct { RouterPath *routes.Path TxData *TransactionData ApprovalTxData *TransactionData } func (rtd *RouterTransactionDetails) IsTxPlaced() bool { return rtd.TxData != nil && rtd.TxData.IsTxPlaced() } func (rtd *RouterTransactionDetails) IsApprovalPlaced() bool { return rtd.ApprovalTxData != nil && rtd.ApprovalTxData.IsTxPlaced() } type TransactionManager struct { storage MultiTransactionStorage gethManager *account.GethManager transactor transactions.TransactorIface config *params.NodeConfig accountsDB accounts.AccountsStorage pendingTracker *transactions.PendingTxTracker eventFeed *event.Feed // TODO: remove this struct once mobile switches to the new approach multiTransactionForKeycardSigning *MultiTransaction multipathTransactionsData []*pathprocessor.MultipathProcessorTxArgs transactionsForKeycardSigning map[common.Hash]*TransactionDescription // used in a new approach routerTransactions []*RouterTransactionDetails } type MultiTransactionStorage interface { CreateMultiTransaction(tx *MultiTransaction) error ReadMultiTransactions(details *MultiTxDetails) ([]*MultiTransaction, error) UpdateMultiTransaction(tx *MultiTransaction) error DeleteMultiTransaction(id wallet_common.MultiTransactionIDType) error } func NewTransactionManager( storage MultiTransactionStorage, gethManager *account.GethManager, transactor transactions.TransactorIface, config *params.NodeConfig, accountsDB accounts.AccountsStorage, pendingTxManager *transactions.PendingTxTracker, eventFeed *event.Feed, ) *TransactionManager { return &TransactionManager{ storage: storage, gethManager: gethManager, transactor: transactor, config: config, accountsDB: accountsDB, pendingTracker: pendingTxManager, eventFeed: eventFeed, } } var ( emptyHash = common.Hash{} ) type MultiTransactionType uint8 const ( MultiTransactionSend = iota MultiTransactionSwap MultiTransactionBridge MultiTransactionApprove MultiTransactionTypeInvalid = 255 ) type MultiTransaction struct { ID wallet_common.MultiTransactionIDType `json:"id"` Timestamp uint64 `json:"timestamp"` FromNetworkID uint64 `json:"fromNetworkID"` ToNetworkID uint64 `json:"toNetworkID"` FromTxHash common.Hash `json:"fromTxHash"` ToTxHash common.Hash `json:"toTxHash"` FromAddress common.Address `json:"fromAddress"` ToAddress common.Address `json:"toAddress"` FromAsset string `json:"fromAsset"` ToAsset string `json:"toAsset"` FromAmount *hexutil.Big `json:"fromAmount"` ToAmount *hexutil.Big `json:"toAmount"` Type MultiTransactionType `json:"type"` CrossTxID string } type MultiTransactionCommand struct { FromAddress common.Address `json:"fromAddress"` ToAddress common.Address `json:"toAddress"` FromAsset string `json:"fromAsset"` ToAsset string `json:"toAsset"` FromAmount *hexutil.Big `json:"fromAmount"` ToAmount *hexutil.Big `json:"toAmount"` Type MultiTransactionType `json:"type"` } type MultiTransactionCommandResult struct { ID int64 `json:"id"` Hashes map[uint64][]types.Hash `json:"hashes"` } type TransactionIdentity struct { ChainID wallet_common.ChainID `json:"chainId"` Hash common.Hash `json:"hash"` Address common.Address `json:"address"` } type TxResponse struct { KeyUID string `json:"keyUid,omitempty"` Address types.Address `json:"address,omitempty"` AddressPath string `json:"addressPath,omitempty"` SignOnKeycard bool `json:"signOnKeycard,omitempty"` ChainID uint64 `json:"chainId,omitempty"` MessageToSign interface{} `json:"messageToSign,omitempty"` TxArgs transactions.SendTxArgs `json:"txArgs,omitempty"` RawTx string `json:"rawTx,omitempty"` TxHash common.Hash `json:"txHash,omitempty"` } func NewMultiTransaction(timestamp uint64, fromNetworkID, toNetworkID uint64, fromTxHash, toTxHash common.Hash, fromAddress, toAddress common.Address, fromAsset, toAsset string, fromAmount, toAmount *hexutil.Big, txType MultiTransactionType, crossTxID string) *MultiTransaction { if timestamp == 0 { timestamp = uint64(time.Now().Unix()) } return &MultiTransaction{ ID: multiTransactionIDGenerator(), Timestamp: timestamp, FromNetworkID: fromNetworkID, ToNetworkID: toNetworkID, FromTxHash: fromTxHash, ToTxHash: toTxHash, FromAddress: fromAddress, ToAddress: toAddress, FromAsset: fromAsset, ToAsset: toAsset, FromAmount: fromAmount, ToAmount: toAmount, Type: txType, CrossTxID: crossTxID, } } func (tm *TransactionManager) SignMessage(message types.HexBytes, account *types.Key) (string, error) { if account == nil || account.PrivateKey == nil { return "", fmt.Errorf("account or private key is nil") } signature, err := crypto.Sign(message[:], account.PrivateKey) return types.EncodeHex(signature), err } func (tm *TransactionManager) BuildTransaction(chainID uint64, sendArgs transactions.SendTxArgs) (response *TxResponse, err error) { account, err := tm.accountsDB.GetAccountByAddress(sendArgs.From) if err != nil { return nil, fmt.Errorf("failed to resolve account: %w", err) } kp, err := tm.accountsDB.GetKeypairByKeyUID(account.KeyUID) if err != nil { return nil, err } txBeingSigned, _, err := tm.transactor.ValidateAndBuildTransaction(chainID, sendArgs, -1) if err != nil { return nil, err } // Set potential missing fields that were added while building the transaction if sendArgs.Value == nil { value := hexutil.Big(*txBeingSigned.Value()) sendArgs.Value = &value } if sendArgs.Nonce == nil { nonce := hexutil.Uint64(txBeingSigned.Nonce()) sendArgs.Nonce = &nonce } if sendArgs.Gas == nil { gas := hexutil.Uint64(txBeingSigned.Gas()) sendArgs.Gas = &gas } if sendArgs.GasPrice == nil { gasPrice := hexutil.Big(*txBeingSigned.GasPrice()) sendArgs.GasPrice = &gasPrice } if sendArgs.IsDynamicFeeTx() { if sendArgs.MaxPriorityFeePerGas == nil { maxPriorityFeePerGas := hexutil.Big(*txBeingSigned.GasTipCap()) sendArgs.MaxPriorityFeePerGas = &maxPriorityFeePerGas } if sendArgs.MaxFeePerGas == nil { maxFeePerGas := hexutil.Big(*txBeingSigned.GasFeeCap()) sendArgs.MaxFeePerGas = &maxFeePerGas } } signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID)) return &TxResponse{ KeyUID: account.KeyUID, Address: account.Address, AddressPath: account.Path, SignOnKeycard: kp.MigratedToKeycard(), ChainID: chainID, MessageToSign: signer.Hash(txBeingSigned), TxArgs: sendArgs, }, nil } func (tm *TransactionManager) BuildRawTransaction(chainID uint64, sendArgs transactions.SendTxArgs, signature []byte) (response *TxResponse, err error) { tx, err := tm.transactor.BuildTransactionWithSignature(chainID, sendArgs, signature) if err != nil { return nil, err } data, err := tx.MarshalBinary() if err != nil { return nil, err } return &TxResponse{ ChainID: chainID, TxArgs: sendArgs, RawTx: types.EncodeHex(data), TxHash: tx.Hash(), }, nil } func (tm *TransactionManager) SendTransactionWithSignature(chainID uint64, sendArgs transactions.SendTxArgs, signature []byte) (hash types.Hash, err error) { txWithSignature, err := tm.transactor.BuildTransactionWithSignature(chainID, sendArgs, signature) if err != nil { return hash, err } hash, err = tm.transactor.SendTransactionWithSignature(common.Address(sendArgs.From), sendArgs.Symbol, sendArgs.MultiTransactionID, txWithSignature) return hash, err }