feat(walletconnect)_: support the tx and personal signing from within the app or keycard
This commit is contained in:
parent
5555f98dd5
commit
5e2af9e4fa
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestIsOwnAccount(t *testing.T) {
|
||||
account := Account{Wallet: true}
|
||||
account := Account{Wallet: true, Type: AccountTypeGenerated}
|
||||
require.True(t, account.IsWalletNonWatchOnlyAccount())
|
||||
|
||||
account = Account{
|
||||
|
|
|
@ -116,7 +116,7 @@ const (
|
|||
|
||||
// Returns true if an account is a wallet account that logged in user has a control over, otherwise returns false.
|
||||
func (a *Account) IsWalletNonWatchOnlyAccount() bool {
|
||||
return !a.Chat && a.Type != AccountTypeWatch
|
||||
return !a.Chat && len(a.Type) > 0 && a.Type != AccountTypeWatch
|
||||
}
|
||||
|
||||
// Returns true if an account is a wallet account that is ready for sending transactions, otherwise returns false.
|
||||
|
|
|
@ -26,6 +26,7 @@ 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"
|
||||
|
@ -608,6 +609,18 @@ func (api *API) FetchChainIDForURL(ctx context.Context, rpcURL string) (*big.Int
|
|||
return client.ChainID(ctx)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return api.s.walletConnect.SendTransaction(signature)
|
||||
}
|
||||
|
||||
// WCPairSessionProposal responds to "session_proposal" event
|
||||
func (api *API) WCPairSessionProposal(ctx context.Context, sessionProposalJSON string) (*wc.PairSessionResponse, error) {
|
||||
log.Debug("wallet.api.wc.PairSessionProposal", "proposal.len", len(sessionProposalJSON))
|
||||
|
@ -622,8 +635,8 @@ func (api *API) WCPairSessionProposal(ctx context.Context, sessionProposalJSON s
|
|||
}
|
||||
|
||||
// WCSessionRequest responds to "session_request" event
|
||||
func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string, hashedPassword string) (response *wc.SessionRequestResponse, err error) {
|
||||
log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON), "hashedPassword.len", len(hashedPassword))
|
||||
func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string) (response *wc.SessionRequestResponse, err error) {
|
||||
log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON))
|
||||
|
||||
var request wc.SessionRequest
|
||||
err = json.Unmarshal([]byte(sessionRequestJSON), &request)
|
||||
|
@ -631,5 +644,5 @@ func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return api.s.walletConnect.SessionRequest(request, hashedPassword)
|
||||
return api.s.walletConnect.SessionRequest(request)
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ func NewService(
|
|||
|
||||
activity := activity.NewService(db, tokenManager, collectiblesManager, feed)
|
||||
|
||||
walletconnect := walletconnect.NewService(rpcClient.NetworkManager, accountsDB, transactor, gethManager, feed)
|
||||
walletconnect := walletconnect.NewService(rpcClient.NetworkManager, accountsDB, transactor, gethManager, feed, config)
|
||||
|
||||
return &Service{
|
||||
db: db,
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
package walletconnect
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethTypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"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"
|
||||
|
@ -58,39 +64,75 @@ func (n *sendTransactionParams) MarshalJSON() ([]byte, error) {
|
|||
return json.Marshal(n.SendTxArgs)
|
||||
}
|
||||
|
||||
func (s *Service) sendTransaction(request SessionRequest, hashedPassword string) (response *SessionRequestResponse, err error) {
|
||||
func (s *Service) buildTransaction(request SessionRequest) (response *SessionRequestResponse, err error) {
|
||||
if len(request.Params.Request.Params) != 1 {
|
||||
return nil, ErrorInvalidParamsCount
|
||||
}
|
||||
|
||||
var params sendTransactionParams
|
||||
if err := json.Unmarshal(request.Params.Request.Params[0], ¶ms); err != nil {
|
||||
if err = json.Unmarshal(request.Params.Request.Params[0], ¶ms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acc, err := s.gethManager.GetVerifiedWalletAccount(s.accountsDB, params.From.Hex(), hashedPassword)
|
||||
account, err := s.accountsDB.GetAccountByAddress(params.From)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get active account: %w", err)
|
||||
}
|
||||
|
||||
kp, err := s.accountsDB.GetKeypairByKeyUID(account.KeyUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: export it as a JSON parsable type
|
||||
chainID, err := parseCaip2ChainID(request.Params.ChainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash, err := s.transactor.SendTransactionWithChainID(chainID, params.SendTxArgs, acc)
|
||||
// In this case we can ignore `unlock` function received from `ValidateAndBuildTransaction` cause `Nonce`
|
||||
// will be always set by the initiator of this transaction (by the dapp).
|
||||
// Though we will need sort out completely that part since Nonce kept in the local cache is not the most recent one,
|
||||
// instead of that we should always ask network what's the most recent known Nonce for the account.
|
||||
// Logged issue to handle that: https://github.com/status-im/status-go/issues/4335
|
||||
txBeingSigned, _, err := s.transactor.ValidateAndBuildTransaction(chainID, params.SendTxArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.txSignDetails = &txSigningDetails{
|
||||
from: common.Address(account.Address),
|
||||
chainID: chainID,
|
||||
txBeingSigned: txBeingSigned,
|
||||
}
|
||||
|
||||
signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(s.txSignDetails.chainID))
|
||||
return &SessionRequestResponse{
|
||||
KeyUID: account.KeyUID,
|
||||
Address: account.Address,
|
||||
AddressPath: account.Path,
|
||||
SignOnKeycard: kp.MigratedToKeycard(),
|
||||
MesageToSign: signer.Hash(s.txSignDetails.txBeingSigned),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) sendTransaction(signature string) (response *SessionRequestResponse, err error) {
|
||||
if s.txSignDetails.txBeingSigned == nil {
|
||||
return response, errors.New("no tx to sign")
|
||||
}
|
||||
|
||||
signatureBytes, _ := hex.DecodeString(signature)
|
||||
|
||||
hash, err := s.transactor.SendBuiltTransactionWithSignature(s.txSignDetails.chainID, s.txSignDetails.txBeingSigned, signatureBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SessionRequestResponse{
|
||||
SessionRequest: request,
|
||||
Signed: hash.Bytes(),
|
||||
SignedMessage: hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) personalSign(request SessionRequest, hashedPassword string) (response *SessionRequestResponse, err error) {
|
||||
func (s *Service) buildPersonalSingMessage(request SessionRequest) (response *SessionRequestResponse, err error) {
|
||||
if len(request.Params.Request.Params) != 2 {
|
||||
return nil, ErrorInvalidParamsCount
|
||||
}
|
||||
|
@ -100,7 +142,12 @@ func (s *Service) personalSign(request SessionRequest, hashedPassword string) (r
|
|||
return nil, err
|
||||
}
|
||||
|
||||
acc, err := s.gethManager.GetVerifiedWalletAccount(s.accountsDB, address.Hex(), hashedPassword)
|
||||
account, err := s.accountsDB.GetAccountByAddress(address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get active account: %w", err)
|
||||
}
|
||||
|
||||
kp, err := s.accountsDB.GetKeypairByKeyUID(account.KeyUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -112,15 +159,11 @@ func (s *Service) personalSign(request SessionRequest, hashedPassword string) (r
|
|||
|
||||
hash := crypto.TextHash(dBytes)
|
||||
|
||||
sig, err := crypto.Sign(hash, acc.AccountKey.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||
|
||||
return &SessionRequestResponse{
|
||||
SessionRequest: request,
|
||||
Signed: types.HexBytes(sig),
|
||||
KeyUID: account.KeyUID,
|
||||
Address: account.Address,
|
||||
AddressPath: account.Path,
|
||||
SignOnKeycard: kp.MigratedToKeycard(),
|
||||
MesageToSign: types.HexBytes(hash),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -3,15 +3,25 @@ package walletconnect
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethTypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"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/rpc/network"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
type txSigningDetails struct {
|
||||
chainID uint64
|
||||
from common.Address
|
||||
txBeingSigned *ethTypes.Transaction
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
networkManager *network.Manager
|
||||
accountsDB *accounts.Database
|
||||
|
@ -19,18 +29,38 @@ type Service struct {
|
|||
|
||||
transactor *transactions.Transactor
|
||||
gethManager *account.GethManager
|
||||
|
||||
config *params.NodeConfig
|
||||
txSignDetails *txSigningDetails
|
||||
}
|
||||
|
||||
func NewService(networkManager *network.Manager, accountsDB *accounts.Database, transactor *transactions.Transactor, gethManager *account.GethManager, eventFeed *event.Feed) *Service {
|
||||
func NewService(networkManager *network.Manager, accountsDB *accounts.Database, transactor *transactions.Transactor,
|
||||
gethManager *account.GethManager, eventFeed *event.Feed, config *params.NodeConfig) *Service {
|
||||
return &Service{
|
||||
networkManager: networkManager,
|
||||
accountsDB: accountsDB,
|
||||
eventFeed: eventFeed,
|
||||
transactor: transactor,
|
||||
gethManager: gethManager,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SignMessage(message types.HexBytes, address common.Address, password string) (string, error) {
|
||||
selectedAccount, err := s.gethManager.VerifyAccountPassword(s.config.KeyStoreDir, address.Hex(), password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signature, err := crypto.Sign(message[:], selectedAccount.PrivateKey)
|
||||
|
||||
return types.EncodeHex(signature), err
|
||||
}
|
||||
|
||||
func (s *Service) SendTransaction(signature string) (response *SessionRequestResponse, err error) {
|
||||
return s.sendTransaction(signature)
|
||||
}
|
||||
|
||||
func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionResponse, error) {
|
||||
namespace := Namespace{
|
||||
Methods: []string{params.SendTransactionMethodName, params.PersonalSignMethodName},
|
||||
|
@ -55,7 +85,7 @@ func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionRes
|
|||
// Filter out non-own accounts
|
||||
usableAccounts := make([]*accounts.Account, 0, 1)
|
||||
for _, acc := range activeAccounts {
|
||||
if !acc.IsOwnAccount() || acc.Operable != accounts.AccountFullyOperable {
|
||||
if !acc.IsWalletAccountReadyForTransaction() {
|
||||
continue
|
||||
}
|
||||
usableAccounts = append(usableAccounts, acc)
|
||||
|
@ -73,14 +103,14 @@ func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionRes
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) SessionRequest(request SessionRequest, hashedPassword string) (response *SessionRequestResponse, err error) {
|
||||
func (s *Service) SessionRequest(request SessionRequest) (response *SessionRequestResponse, err error) {
|
||||
// TODO #12434: should we check topic for validity? It might make sense if we
|
||||
// want to cache the paired sessions
|
||||
|
||||
if request.Params.Request.Method == params.SendTransactionMethodName {
|
||||
return s.sendTransaction(request, hashedPassword)
|
||||
return s.buildTransaction(request)
|
||||
} else if request.Params.Request.Method == params.PersonalSignMethodName {
|
||||
return s.personalSign(request, hashedPassword)
|
||||
return s.buildPersonalSingMessage(request)
|
||||
}
|
||||
|
||||
// TODO #12434: respond async
|
||||
|
|
|
@ -88,8 +88,12 @@ type SessionRequest struct {
|
|||
}
|
||||
|
||||
type SessionRequestResponse struct {
|
||||
SessionRequest SessionRequest `json:"sessionRequest"`
|
||||
Signed types.HexBytes `json:"signed"`
|
||||
KeyUID string `json:"keyUid,omitempty"`
|
||||
Address types.Address `json:"address,omitempty"`
|
||||
AddressPath string `json:"addressPath,omitempty"`
|
||||
SignOnKeycard bool `json:"signOnKeycard,omitempty"`
|
||||
MesageToSign interface{} `json:"messageToSign,omitempty"`
|
||||
SignedMessage interface{} `json:"signedMessage,omitempty"`
|
||||
}
|
||||
|
||||
func sessionProposalToSupportedChain(caipChains []string, supportsChain func(uint64) bool) (chains []uint64, eipChains []string) {
|
||||
|
|
Loading…
Reference in New Issue