From 8641ec5dd528d1357b94c88a7648791ddd72080d Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Fri, 24 Nov 2023 16:39:36 +0100 Subject: [PATCH] feat(walletconnect)_: ethereum rpc calls support Reference: https://docs.walletconnect.com/advanced/rpc-reference/ethereum-rpc --- VERSION | 2 +- api/geth_backend.go | 2 +- params/defaults.go | 18 ++++++- services/wallet/api.go | 27 +++++++--- services/wallet/transfer/transaction.go | 2 +- services/wallet/walletconnect/rpc.go | 54 ++++++++++++------- services/wallet/walletconnect/service.go | 53 ++++++++++++++++-- .../wallet/walletconnect/walletconnect.go | 1 - transactions/rpc_wrapper.go | 7 ++- transactions/transactor.go | 46 ++++++++++++---- transactions/transactor_test.go | 4 +- 11 files changed, 169 insertions(+), 47 deletions(-) diff --git a/VERSION b/VERSION index 0715be036..47a884826 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.171.18 +0.171.19 diff --git a/api/geth_backend.go b/api/geth_backend.go index 52121a12e..6d2f640b2 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -1919,7 +1919,7 @@ func (b *GethStatusBackend) SendTransactionWithChainID(chainID uint64, sendArgs } func (b *GethStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) { - hash, err = b.transactor.SendTransactionWithSignature(b.transactor.NetworkID(), sendArgs, sig) + hash, err = b.transactor.BuildTransactionAndSendWithSignature(b.transactor.NetworkID(), sendArgs, sig) if err != nil { return } diff --git a/params/defaults.go b/params/defaults.go index 48f299ea9..63dafe725 100644 --- a/params/defaults.go +++ b/params/defaults.go @@ -6,17 +6,31 @@ const ( // StatusDatabase path relative to DataDir. StatusDatabase = "status-db" - // SendTransactionMethodName defines the name for a giving transaction. + // SendTransactionMethodName https://docs.walletconnect.com/advanced/rpc-reference/ethereum-rpc#eth_sendtransaction SendTransactionMethodName = "eth_sendTransaction" + // SendTransactionMethodName https://docs.walletconnect.com/advanced/rpc-reference/ethereum-rpc#eth_sendrawtransaction + SendRawTransactionMethodName = "eth_sendRawTransaction" + BalanceMethodName = "eth_getBalance" // AccountsMethodName defines the name for listing the currently signed accounts. AccountsMethodName = "eth_accounts" - // PersonalSignMethodName defines the name for `personal.sign` API. + // PersonalSignMethodName https://docs.walletconnect.com/advanced/rpc-reference/ethereum-rpc#personal_sign PersonalSignMethodName = "personal_sign" + // SignMethodName https://docs.walletconnect.com/advanced/rpc-reference/ethereum-rpc#eth_sign + SignMethodName = "eth_sign" + + // SignTransactionMethodName https://docs.walletconnect.com/advanced/rpc-reference/ethereum-rpc#eth_signtransaction + SignTransactionMethodName = "eth_signTransaction" + + // SignTypedDataMethodName https://docs.walletconnect.com/advanced/rpc-reference/ethereum-rpc#eth_signtypeddata + SignTypedDataMethodName = "eth_signTypedData" + + WalletSwitchEthereumChainMethodName = "wallet_switchEthereumChain" + // PersonalRecoverMethodName defines the name for `personal.recover` API. PersonalRecoverMethodName = "personal_ecRecover" diff --git a/services/wallet/api.go b/services/wallet/api.go index 4d0416229..34479ff81 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -26,7 +26,6 @@ import ( "github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/transfer" - "github.com/status-im/status-go/services/wallet/walletconnect" wc "github.com/status-im/status-go/services/wallet/walletconnect" "github.com/status-im/status-go/services/wallet/walletevent" "github.com/status-im/status-go/transactions" @@ -609,16 +608,32 @@ func (api *API) FetchChainIDForURL(ctx context.Context, rpcURL string) (*big.Int return client.ChainID(ctx) } +// WCSignMessage signs a message for the passed address using the provided password and returns the signature func (api *API) WCSignMessage(ctx context.Context, message types.HexBytes, address common.Address, password string) (string, error) { log.Debug("wallet.api.wc.SignMessage", "message", message, "address", address, "password", password) return api.s.walletConnect.SignMessage(message, address, password) } -func (api *API) WCSendTransaction(signature string) (response *walletconnect.SessionRequestResponse, err error) { - log.Debug("wallet.api.wc.SendTransaction", "signature", signature) +// WCBuildRawTransaction builds raw transaction using the provided signature and returns the RLP-encoded transaction object +func (api *API) WCBuildRawTransaction(signature string) (string, error) { + log.Debug("wallet.api.wc.BuildRawTransaction", "signature", signature) - return api.s.walletConnect.SendTransaction(signature) + return api.s.walletConnect.BuildRawTransaction(signature) +} + +// WCSendRawTransaction sends provided raw transaction and returns the transaction hash +func (api *API) WCSendRawTransaction(rawTx string) (string, error) { + log.Debug("wallet.api.wc.SendRawTransaction", "rawTx", rawTx) + + return api.s.walletConnect.SendRawTransaction(rawTx) +} + +// WCSendTransactionWithSignature sends transaction with the provided signature and returns the transaction hash +func (api *API) WCSendTransactionWithSignature(signature string) (string, error) { + log.Debug("wallet.api.wc.SendTransactionWithSignature", "signature", signature) + + return api.s.walletConnect.SendTransactionWithSignature(signature) } // WCPairSessionProposal responds to "session_proposal" event @@ -655,11 +670,11 @@ func (api *API) WCHasActivePairings(ctx context.Context) (bool, error) { } // WCSessionRequest responds to "session_request" event -func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string) (response *wc.SessionRequestResponse, err error) { +func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string) (*wc.SessionRequestResponse, error) { log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON)) var request wc.SessionRequest - err = json.Unmarshal([]byte(sessionRequestJSON), &request) + err := json.Unmarshal([]byte(sessionRequestJSON), &request) if err != nil { return nil, err } diff --git a/services/wallet/transfer/transaction.go b/services/wallet/transfer/transaction.go index ab3cea93e..fd47959ab 100644 --- a/services/wallet/transfer/transaction.go +++ b/services/wallet/transfer/transaction.go @@ -399,7 +399,7 @@ func (tm *TransactionManager) ProceedWithTransactionsSignatures(ctx context.Cont // send transactions hashes := make(map[uint64][]types.Hash) for _, desc := range tm.transactionsForKeycardSingning { - hash, err := tm.transactor.SendBuiltTransactionWithSignature(desc.chainID, desc.builtTx, desc.signature) + hash, err := tm.transactor.AddSignatureToTransactionAndSend(desc.chainID, desc.builtTx, desc.signature) if desc.unlock != nil { defer func() { desc.unlock(err == nil, desc.builtTx.Nonce()) diff --git a/services/wallet/walletconnect/rpc.go b/services/wallet/walletconnect/rpc.go index cff7439c5..e9ad81728 100644 --- a/services/wallet/walletconnect/rpc.go +++ b/services/wallet/walletconnect/rpc.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" - + "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/transactions" @@ -106,40 +106,41 @@ func (s *Service) buildTransaction(request SessionRequest) (response *SessionReq txBeingSigned: txBeingSigned, } - signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(s.txSignDetails.chainID)) + signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID)) return &SessionRequestResponse{ KeyUID: account.KeyUID, Address: account.Address, AddressPath: account.Path, SignOnKeycard: kp.MigratedToKeycard(), - MesageToSign: signer.Hash(s.txSignDetails.txBeingSigned), + MesageToSign: signer.Hash(txBeingSigned), }, nil } -func (s *Service) sendTransaction(signature string) (response *SessionRequestResponse, err error) { +func (s *Service) addSignatureToTransaction(signature string) (*ethTypes.Transaction, error) { if s.txSignDetails.txBeingSigned == nil { - return response, errors.New("no tx to sign") + return nil, errors.New("no tx to sign") } - signatureBytes, _ := hex.DecodeString(signature) - - hash, err := s.transactor.SendBuiltTransactionWithSignature(s.txSignDetails.chainID, s.txSignDetails.txBeingSigned, signatureBytes) + signatureBytes, err := hex.DecodeString(signature) if err != nil { return nil, err } - return &SessionRequestResponse{ - SignedMessage: hash, - }, nil + return s.transactor.AddSignatureToTransaction(s.txSignDetails.chainID, s.txSignDetails.txBeingSigned, signatureBytes) } -func (s *Service) buildPersonalSingMessage(request SessionRequest) (response *SessionRequestResponse, err error) { +func (s *Service) buildMessage(request SessionRequest, addressIndex int, messageIndex int, + handleTypedData bool) (response *SessionRequestResponse, err error) { if len(request.Params.Request.Params) != 2 { return nil, ErrorInvalidParamsCount } + if addressIndex > 1 || addressIndex < 0 || messageIndex > 1 || messageIndex < 0 { + return nil, ErrorInvalidAddressMsgIndex + } + var address types.Address - if err := json.Unmarshal(request.Params.Request.Params[1], &address); err != nil { + if err := json.Unmarshal(request.Params.Request.Params[addressIndex], &address); err != nil { return nil, err } @@ -153,12 +154,29 @@ func (s *Service) buildPersonalSingMessage(request SessionRequest) (response *Se return nil, err } - var dBytes types.HexBytes - if err := json.Unmarshal(request.Params.Request.Params[0], &dBytes); err != nil { - return nil, err - } + var hash []byte + if !handleTypedData { + var dBytes types.HexBytes + if err := json.Unmarshal(request.Params.Request.Params[messageIndex], &dBytes); err != nil { + return nil, err + } + hash = crypto.TextHash(dBytes) + } else { + var typedDataJSON string + if err := json.Unmarshal(request.Params.Request.Params[messageIndex], &typedDataJSON); err != nil { + return nil, err + } - hash := crypto.TextHash(dBytes) + var typedData apitypes.TypedData + if err := json.Unmarshal([]byte(typedDataJSON), &typedData); err != nil { + return nil, err + } + + hash, _, err = apitypes.TypedDataAndHash(typedData) + if err != nil { + return nil, err + } + } return &SessionRequestResponse{ KeyUID: account.KeyUID, diff --git a/services/wallet/walletconnect/service.go b/services/wallet/walletconnect/service.go index f4c74bc46..028571d7f 100644 --- a/services/wallet/walletconnect/service.go +++ b/services/wallet/walletconnect/service.go @@ -24,6 +24,7 @@ type txSigningDetails struct { chainID uint64 from common.Address txBeingSigned *ethTypes.Transaction + txHash common.Hash } type Service struct { @@ -62,8 +63,43 @@ func (s *Service) SignMessage(message types.HexBytes, address common.Address, pa return types.EncodeHex(signature), err } -func (s *Service) SendTransaction(signature string) (response *SessionRequestResponse, err error) { - return s.sendTransaction(signature) +func (s *Service) BuildRawTransaction(signature string) (string, error) { + txWithSignature, err := s.addSignatureToTransaction(signature) + if err != nil { + return "", err + } + + data, err := txWithSignature.MarshalBinary() + if err != nil { + return "", err + } + + s.txSignDetails.txHash = txWithSignature.Hash() + + return types.EncodeHex(data), nil +} + +func (s *Service) SendRawTransaction(rawTx string) (string, error) { + err := s.transactor.SendRawTransaction(s.txSignDetails.chainID, rawTx) + if err != nil { + return "", err + } + + return s.txSignDetails.txHash.Hex(), nil +} + +func (s *Service) SendTransactionWithSignature(signature string) (string, error) { + txWithSignature, err := s.addSignatureToTransaction(signature) + if err != nil { + return "", err + } + + hash, err := s.transactor.SendTransactionWithSignature(txWithSignature) + if err != nil { + return "", err + } + + return hash.Hex(), nil } func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionResponse, error) { @@ -129,7 +165,12 @@ func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionRes SupportedNamespaces: map[string]Namespace{ SupportedEip155Namespace: Namespace{ Methods: []string{params.SendTransactionMethodName, + params.SendRawTransactionMethodName, params.PersonalSignMethodName, + params.SignMethodName, + params.SignTransactionMethodName, + params.SignTypedDataMethodName, + params.WalletSwitchEthereumChainMethodName, }, Events: []string{"accountsChanged", "chainChanged"}, Chains: eipChains, @@ -169,8 +210,14 @@ func (s *Service) SessionRequest(request SessionRequest) (response *SessionReque if request.Params.Request.Method == params.SendTransactionMethodName { return s.buildTransaction(request) + } else if request.Params.Request.Method == params.SignTransactionMethodName { + return s.buildTransaction(request) } else if request.Params.Request.Method == params.PersonalSignMethodName { - return s.buildPersonalSingMessage(request) + return s.buildMessage(request, 1, 0, false) + } else if request.Params.Request.Method == params.SignMethodName { + return s.buildMessage(request, 0, 1, false) + } else if request.Params.Request.Method == params.SignTypedDataMethodName { + return s.buildMessage(request, 0, 1, true) } // TODO #12434: respond async diff --git a/services/wallet/walletconnect/walletconnect.go b/services/wallet/walletconnect/walletconnect.go index 76974fd22..c024c4455 100644 --- a/services/wallet/walletconnect/walletconnect.go +++ b/services/wallet/walletconnect/walletconnect.go @@ -108,7 +108,6 @@ type SessionRequestResponse struct { AddressPath string `json:"addressPath,omitempty"` SignOnKeycard bool `json:"signOnKeycard,omitempty"` MesageToSign interface{} `json:"messageToSign,omitempty"` - SignedMessage interface{} `json:"signedMessage,omitempty"` } // Valid namespace diff --git a/transactions/rpc_wrapper.go b/transactions/rpc_wrapper.go index d54d8ff1f..3e06e3201 100644 --- a/transactions/rpc_wrapper.go +++ b/transactions/rpc_wrapper.go @@ -55,6 +55,11 @@ func (w *rpcWrapper) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uin return uint64(hex), nil } +// Does the `eth_sendRawTransaction` call with the given raw transaction hex string. +func (w *rpcWrapper) SendRawTransaction(ctx context.Context, rawTx string) error { + return w.RPCClient.CallContext(ctx, nil, w.chainID, "eth_sendRawTransaction", rawTx) +} + // SendTransaction injects a signed transaction into the pending pool for execution. // // If the transaction was a contract creation use the TransactionReceipt method to get the @@ -64,7 +69,7 @@ func (w *rpcWrapper) SendTransaction(ctx context.Context, tx *gethtypes.Transact if err != nil { return err } - return w.RPCClient.CallContext(ctx, nil, w.chainID, "eth_sendRawTransaction", types.EncodeHex(data)) + return w.SendRawTransaction(ctx, types.EncodeHex(data)) } func toCallArg(msg ethereum.CallMsg) interface{} { diff --git a/transactions/transactor.go b/transactions/transactor.go index bed0bf681..196348577 100644 --- a/transactions/transactor.go +++ b/transactions/transactor.go @@ -116,33 +116,57 @@ func (t *Transactor) ValidateAndBuildTransaction(chainID uint64, sendArgs SendTx return } -func (t *Transactor) SendBuiltTransactionWithSignature(chainID uint64, tx *gethtypes.Transaction, sig []byte) (hash types.Hash, err error) { +func (t *Transactor) AddSignatureToTransaction(chainID uint64, tx *gethtypes.Transaction, sig []byte) (*gethtypes.Transaction, error) { if len(sig) != ValidSignatureSize { - return hash, ErrInvalidSignatureSize + return nil, ErrInvalidSignatureSize } rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) chID := big.NewInt(int64(rpcWrapper.chainID)) signer := gethtypes.NewLondonSigner(chID) - signedTx, err := tx.WithSignature(signer, sig) + txWithSignature, err := tx.WithSignature(signer, sig) if err != nil { - return hash, err + return nil, err } + return txWithSignature, nil +} + +func (t *Transactor) SendRawTransaction(chainID uint64, rawTx string) error { + rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) + ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) defer cancel() - if err := rpcWrapper.SendTransaction(ctx, signedTx); err != nil { - return hash, err - } - return types.Hash(signedTx.Hash()), nil + return rpcWrapper.SendRawTransaction(ctx, rawTx) } -// SendTransactionWithSignature receive a transaction and a signature, serialize them together and propage it to the network. +func (t *Transactor) SendTransactionWithSignature(tx *gethtypes.Transaction) (hash types.Hash, err error) { + rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, tx.ChainId().Uint64()) + + ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) + defer cancel() + + if err := rpcWrapper.SendTransaction(ctx, tx); err != nil { + return hash, err + } + return types.Hash(tx.Hash()), nil +} + +func (t *Transactor) AddSignatureToTransactionAndSend(chainID uint64, tx *gethtypes.Transaction, sig []byte) (hash types.Hash, err error) { + txWithSignature, err := t.AddSignatureToTransaction(chainID, tx, sig) + if err != nil { + return hash, err + } + + return t.SendTransactionWithSignature(txWithSignature) +} + +// BuildTransactionAndSendWithSignature receive a transaction and a signature, serialize them together and propage it to the network. // It's different from eth_sendRawTransaction because it receives a signature and not a serialized transaction with signature. // Since the transactions is already signed, we assume it was validated and used the right nonce. -func (t *Transactor) SendTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (hash types.Hash, err error) { +func (t *Transactor) BuildTransactionAndSendWithSignature(chainID uint64, args SendTxArgs, sig []byte) (hash types.Hash, err error) { if !args.Valid() { return hash, ErrInvalidSendTxArgs } @@ -167,7 +191,7 @@ func (t *Transactor) SendTransactionWithSignature(chainID uint64, args SendTxArg return hash, &ErrBadNonce{tx.Nonce(), expectedNonce} } - return t.SendBuiltTransactionWithSignature(chainID, tx, sig) + return t.AddSignatureToTransactionAndSend(chainID, tx, sig) } func (t *Transactor) HashTransaction(args SendTxArgs) (validatedArgs SendTxArgs, hash types.Hash, err error) { diff --git a/transactions/transactor_test.go b/transactions/transactor_test.go index 7b4b23760..46eb103c6 100644 --- a/transactions/transactor_test.go +++ b/transactions/transactor_test.go @@ -374,7 +374,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() { Return(common.Hash{}, nil) } - _, err = s.manager.SendTransactionWithSignature(s.nodeConfig.NetworkID, args, sig) + _, err = s.manager.BuildTransactionAndSendWithSignature(s.nodeConfig.NetworkID, args, sig) if scenario.expectError { s.Error(err) // local nonce should not be incremented @@ -393,7 +393,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() { func (s *TransactorSuite) TestSendTransactionWithSignature_InvalidSignature() { args := SendTxArgs{} - _, err := s.manager.SendTransactionWithSignature(1, args, []byte{}) + _, err := s.manager.BuildTransactionAndSendWithSignature(1, args, []byte{}) s.Equal(ErrInvalidSignatureSize, err) }