package walletconnect import ( "database/sql" "fmt" "strings" "time" "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 txHash common.Hash } type Service struct { db *sql.DB networkManager *network.Manager accountsDB *accounts.Database eventFeed *event.Feed transactor *transactions.Transactor gethManager *account.GethManager config *params.NodeConfig txSignDetails *txSigningDetails } func NewService(db *sql.DB, networkManager *network.Manager, accountsDB *accounts.Database, transactor *transactions.Transactor, gethManager *account.GethManager, eventFeed *event.Feed, config *params.NodeConfig) *Service { return &Service{ db: db, 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) 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) { if !proposal.Valid() { return nil, ErrorInvalidSessionProposal } var ( chains []uint64 eipChains []string ) if len(proposal.Params.RequiredNamespaces) == 0 { // return all we support allChains, err := s.networkManager.GetAll() if err != nil { return nil, fmt.Errorf("failed to get all chains: %w", err) } for _, chain := range allChains { chains = append(chains, chain.ChainID) eipChains = append(eipChains, fmt.Sprintf("%s:%d", SupportedEip155Namespace, chain.ChainID)) } } else { var proposedChains []string for key, ns := range proposal.Params.RequiredNamespaces { if !strings.Contains(key, SupportedEip155Namespace) { log.Warn("Some namespaces are not supported; wanted: ", key, "; supported: ", SupportedEip155Namespace) return nil, ErrorNamespaceNotSupported } if strings.Contains(key, ":") { proposedChains = append(proposedChains, key) } else { proposedChains = append(proposedChains, ns.Chains...) } } chains, eipChains = sessionProposalToSupportedChain(proposedChains, func(chainID uint64) bool { return s.networkManager.Find(chainID) != nil }) if len(chains) != len(proposedChains) { log.Warn("Some chains are not supported; wanted: ", proposedChains, "; supported: ", chains) return nil, ErrorChainsNotSupported } } activeAccounts, err := s.accountsDB.GetActiveAccounts() if err != nil { return nil, fmt.Errorf("failed to get active accounts: %w", err) } allWalletAccountsReadyForTransaction := make([]*accounts.Account, 0, 1) for _, acc := range activeAccounts { if !acc.IsWalletAccountReadyForTransaction() { continue } allWalletAccountsReadyForTransaction = append(allWalletAccountsReadyForTransaction, acc) } result := &PairSessionResponse{ SessionProposal: proposal, 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, Accounts: caip10Accounts(allWalletAccountsReadyForTransaction, chains), }, }, } // TODO #12434: respond async return result, nil } func (s *Service) RecordSuccessfulPairing(proposal SessionProposal) error { var icon string if len(proposal.Params.Proposer.Metadata.Icons) > 0 { icon = proposal.Params.Proposer.Metadata.Icons[0] } return InsertPairing(s.db, Pairing{ Topic: proposal.Params.PairingTopic, Expiry: proposal.Params.Expiry, Active: true, AppName: proposal.Params.Proposer.Metadata.Name, URL: proposal.Params.Proposer.Metadata.URL, Description: proposal.Params.Proposer.Metadata.Description, Icon: icon, Verified: proposal.Params.Verify.Verified, }) } func (s *Service) HasActivePairings() (bool, error) { return HasActivePairings(s.db, time.Now().Unix()) } 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.buildTransaction(request) } else if request.Params.Request.Method == params.SignTransactionMethodName { return s.buildTransaction(request) } else if request.Params.Request.Method == params.PersonalSignMethodName { 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 return nil, ErrorMethodNotSupported }