diff --git a/VERSION b/VERSION index 367f511b4..951cae396 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.171.21 +0.171.22 \ No newline at end of file diff --git a/services/wallet/api.go b/services/wallet/api.go index d3a188a7b..9161e6b01 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -2,6 +2,7 @@ package wallet import ( "context" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -524,6 +525,37 @@ func (api *API) GetAddressDetails(ctx context.Context, chainID uint64, address s return result, nil } +func (api *API) SignMessage(ctx context.Context, message types.HexBytes, address common.Address, password string) (string, error) { + log.Debug("[WalletAPI::SignMessage]", "message", message, "address", address) + return api.s.transactionManager.SignMessage(message, address, password) +} + +func (api *API) BuildTransaction(ctx context.Context, chainID uint64, sendTxArgsJSON string) (response *transfer.TxResponse, err error) { + log.Debug("[WalletAPI::BuildTransaction]", "chainID", chainID, "sendTxArgsJSON", sendTxArgsJSON) + var params transactions.SendTxArgs + err = json.Unmarshal([]byte(sendTxArgsJSON), ¶ms) + if err != nil { + return nil, err + } + return api.s.transactionManager.BuildTransaction(chainID, params) +} + +func (api *API) SendTransactionWithSignature(ctx context.Context, chainID uint64, txType transactions.PendingTrxType, + sendTxArgsJSON string, signature string) (hash types.Hash, err error) { + log.Debug("[WalletAPI::SendTransactionWithSignature]", "chainID", chainID, "txType", txType, "sendTxArgsJSON", sendTxArgsJSON, "signature", signature) + sig, err := hex.DecodeString(signature) + if err != nil { + return hash, err + } + + var params transactions.SendTxArgs + err = json.Unmarshal([]byte(sendTxArgsJSON), ¶ms) + if err != nil { + return hash, err + } + return api.s.transactionManager.SendTransactionWithSignature(chainID, txType, params, sig) +} + func (api *API) CreateMultiTransaction(ctx context.Context, multiTransactionCommand *transfer.MultiTransactionCommand, data []*bridge.TransactionBridge, password string) (*transfer.MultiTransactionCommandResult, error) { log.Debug("[WalletAPI:: CreateMultiTransaction] create multi transaction") return api.s.transactionManager.CreateMultiTransactionFromCommand(ctx, multiTransactionCommand, data, api.router.bridges, password) diff --git a/services/wallet/transfer/transaction_manager.go b/services/wallet/transfer/transaction_manager.go new file mode 100644 index 000000000..1ea936cdc --- /dev/null +++ b/services/wallet/transfer/transaction_manager.go @@ -0,0 +1,225 @@ +package transfer + +import ( + "database/sql" + "fmt" + "math/big" + + 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" + "github.com/status-im/status-go/services/wallet/bridge" + wallet_common "github.com/status-im/status-go/services/wallet/common" + "github.com/status-im/status-go/services/wallet/walletevent" + "github.com/status-im/status-go/transactions" +) + +type MultiTransactionIDType int64 + +const ( + NoMultiTransactionID = MultiTransactionIDType(0) + + // EventMTTransactionUpdate is emitted when a multi-transaction is updated (added or deleted) + EventMTTransactionUpdate walletevent.EventType = "multi-transaction-update" +) + +type SignatureDetails struct { + R string `json:"r"` + S string `json:"s"` + V string `json:"v"` +} + +type TransactionDescription struct { + chainID uint64 + builtTx *ethTypes.Transaction + signature []byte + unlock transactions.UnlockNonceFunc +} + +type TransactionManager struct { + db *sql.DB + gethManager *account.GethManager + transactor *transactions.Transactor + config *params.NodeConfig + accountsDB *accounts.Database + pendingTracker *transactions.PendingTxTracker + eventFeed *event.Feed + + multiTransactionForKeycardSigning *MultiTransaction + transactionsBridgeData []*bridge.TransactionBridge + transactionsForKeycardSingning map[common.Hash]*TransactionDescription +} + +func NewTransactionManager( + db *sql.DB, + gethManager *account.GethManager, + transactor *transactions.Transactor, + config *params.NodeConfig, + accountsDB *accounts.Database, + pendingTxManager *transactions.PendingTxTracker, + eventFeed *event.Feed, +) *TransactionManager { + return &TransactionManager{ + db: db, + gethManager: gethManager, + transactor: transactor, + config: config, + accountsDB: accountsDB, + pendingTracker: pendingTxManager, + eventFeed: eventFeed, + } +} + +var ( + emptyHash = common.Hash{} +) + +type MultiTransactionType uint8 + +const ( + MultiTransactionSend = iota + MultiTransactionSwap + MultiTransactionBridge +) + +type MultiTransaction struct { + ID uint `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"` + 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"` + MesageToSign interface{} `json:"messageToSign,omitempty"` + TxArgs transactions.SendTxArgs `json:"txArgs,omitempty"` +} + +func (tm *TransactionManager) SignMessage(message types.HexBytes, address common.Address, password string) (string, error) { + selectedAccount, err := tm.gethManager.VerifyAccountPassword(tm.config.KeyStoreDir, address.Hex(), password) + if err != nil { + return "", err + } + + signature, err := crypto.Sign(message[:], selectedAccount.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, unlock, err := tm.transactor.ValidateAndBuildTransaction(chainID, sendArgs) + if err != nil { + return nil, err + } + // We have to unlock the nonce, cause we don't know what will happen on the client side (will user accept/reject) an action. + if unlock != nil { + defer func() { + unlock(false, 0) + }() + } + + // 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.GasPrice == nil { + gasPrice := hexutil.Big(*txBeingSigned.GasPrice()) + sendArgs.GasPrice = &gasPrice + } + 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, + MesageToSign: signer.Hash(txBeingSigned), + TxArgs: sendArgs, + }, nil +} + +func (tm *TransactionManager) SendTransactionWithSignature(chainID uint64, txType transactions.PendingTrxType, sendArgs transactions.SendTxArgs, signature []byte) (hash types.Hash, err error) { + hash, err = tm.transactor.BuildTransactionAndSendWithSignature(chainID, sendArgs, signature) + if err != nil { + return hash, err + } + + err = tm.pendingTracker.TrackPendingTransaction( + wallet_common.ChainID(chainID), + common.Hash(hash), + common.Address(sendArgs.From), + txType, + transactions.AutoDelete, + ) + if err != nil { + return hash, err + } + + return hash, nil +} diff --git a/services/wallet/transfer/transaction.go b/services/wallet/transfer/transaction_manager_multitransaction.go similarity index 83% rename from services/wallet/transfer/transaction.go rename to services/wallet/transfer/transaction_manager_multitransaction.go index fd47959ab..809972c64 100644 --- a/services/wallet/transfer/transaction.go +++ b/services/wallet/transfer/transaction_manager_multitransaction.go @@ -19,8 +19,6 @@ import ( "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" "github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bridge" wallet_common "github.com/status-im/status-go/services/wallet/common" @@ -29,111 +27,6 @@ import ( "github.com/status-im/status-go/transactions" ) -type MultiTransactionIDType int64 - -const ( - NoMultiTransactionID = MultiTransactionIDType(0) - - // EventMTTransactionUpdate is emitted when a multi-transaction is updated (added or deleted) - EventMTTransactionUpdate walletevent.EventType = "multi-transaction-update" -) - -type SignatureDetails struct { - R string `json:"r"` - S string `json:"s"` - V string `json:"v"` -} - -type TransactionDescription struct { - chainID uint64 - builtTx *ethTypes.Transaction - signature []byte - unlock transactions.UnlockNonceFunc -} - -type TransactionManager struct { - db *sql.DB - gethManager *account.GethManager - transactor *transactions.Transactor - config *params.NodeConfig - accountsDB *accounts.Database - pendingTracker *transactions.PendingTxTracker - eventFeed *event.Feed - - multiTransactionForKeycardSigning *MultiTransaction - transactionsBridgeData []*bridge.TransactionBridge - transactionsForKeycardSingning map[common.Hash]*TransactionDescription -} - -func NewTransactionManager( - db *sql.DB, - gethManager *account.GethManager, - transactor *transactions.Transactor, - config *params.NodeConfig, - accountsDB *accounts.Database, - pendingTxManager *transactions.PendingTxTracker, - eventFeed *event.Feed, -) *TransactionManager { - return &TransactionManager{ - db: db, - gethManager: gethManager, - transactor: transactor, - config: config, - accountsDB: accountsDB, - pendingTracker: pendingTxManager, - eventFeed: eventFeed, - } -} - -var ( - emptyHash = common.Hash{} -) - -type MultiTransactionType uint8 - -const ( - MultiTransactionSend = iota - MultiTransactionSwap - MultiTransactionBridge -) - -type MultiTransaction struct { - ID uint `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"` - 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"` -} - const multiTransactionColumns = "from_network_id, from_tx_hash, from_address, from_asset, from_amount, to_network_id, to_tx_hash, to_address, to_asset, to_amount, type, cross_tx_id, timestamp" const selectMultiTransactionColumns = "COALESCE(from_network_id, 0), from_tx_hash, from_address, from_asset, from_amount, COALESCE(to_network_id, 0), to_tx_hash, to_address, to_asset, to_amount, type, cross_tx_id, timestamp" diff --git a/services/wallet/transfer/transaction_test.go b/services/wallet/transfer/transaction_manager_test.go similarity index 100% rename from services/wallet/transfer/transaction_test.go rename to services/wallet/transfer/transaction_manager_test.go diff --git a/transactions/transactor.go b/transactions/transactor.go index 196348577..39d36d076 100644 --- a/transactions/transactor.go +++ b/transactions/transactor.go @@ -191,7 +191,8 @@ func (t *Transactor) BuildTransactionAndSendWithSignature(chainID uint64, args S return hash, &ErrBadNonce{tx.Nonce(), expectedNonce} } - return t.AddSignatureToTransactionAndSend(chainID, tx, sig) + hash, err = t.AddSignatureToTransactionAndSend(chainID, tx, sig) + return hash, err } func (t *Transactor) HashTransaction(args SendTxArgs) (validatedArgs SendTxArgs, hash types.Hash, err error) { @@ -387,10 +388,24 @@ func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccoun } func (t *Transactor) buildTransaction(args SendTxArgs) *gethtypes.Transaction { - nonce := uint64(*args.Nonce) - value := (*big.Int)(args.Value) - gas := uint64(*args.Gas) - gasPrice := (*big.Int)(args.GasPrice) + var ( + nonce uint64 + value *big.Int + gas uint64 + gasPrice *big.Int + ) + if args.Nonce != nil { + nonce = uint64(*args.Nonce) + } + if args.Value != nil { + value = (*big.Int)(args.Value) + } + if args.Gas != nil { + gas = uint64(*args.Gas) + } + if args.GasPrice != nil { + gasPrice = (*big.Int)(args.GasPrice) + } return t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args) }