feat(walletconnect)_: ethereum rpc calls support

Reference: https://docs.walletconnect.com/advanced/rpc-reference/ethereum-rpc
This commit is contained in:
Sale Djenic 2023-11-24 16:39:36 +01:00 committed by saledjenic
parent d4ca8616fc
commit 8641ec5dd5
11 changed files with 169 additions and 47 deletions

View File

@ -1 +1 @@
0.171.18
0.171.19

View File

@ -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
}

View File

@ -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"

View File

@ -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
}

View File

@ -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())

View File

@ -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 hash []byte
if !handleTypedData {
var dBytes types.HexBytes
if err := json.Unmarshal(request.Params.Request.Params[0], &dBytes); err != nil {
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,

View File

@ -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

View File

@ -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

View File

@ -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{} {

View File

@ -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) {

View File

@ -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)
}