status-im/status-go

View on GitHub
services/connector/api.go

Summary

Maintainability
A
0 mins
Test Coverage
C
72%
package connector

import (
    "context"
    "encoding/json"
    "errors"
    "fmt"

    "github.com/status-im/status-go/services/connector/commands"
    persistence "github.com/status-im/status-go/services/connector/database"
)

var (
    ErrInvalidResponseFromForwardedRpc = errors.New("invalid response from forwarded RPC")
)

type API struct {
    s *Service
    r *CommandRegistry
    c *commands.ClientSideHandler
}

func NewAPI(s *Service) *API {
    r := NewCommandRegistry()
    c := commands.NewClientSideHandler()

    // Transactions and signing
    r.Register("eth_sendTransaction", &commands.SendTransactionCommand{
        RpcClient:     s.rpc,
        Db:            s.db,
        ClientHandler: c,
    })
    r.Register("personal_sign", &commands.PersonalSignCommand{
        Db:            s.db,
        ClientHandler: c,
    })

    // Accounts query and dapp permissions
    // NOTE: Some dApps expect same behavior for both eth_accounts and eth_requestAccounts
    accountsCommand := &commands.RequestAccountsCommand{
        ClientHandler: c,
        Db:            s.db,
    }
    r.Register("eth_accounts", accountsCommand)
    r.Register("eth_requestAccounts", accountsCommand)

    // Active chain per dapp management
    r.Register("eth_chainId", &commands.ChainIDCommand{
        Db:             s.db,
        NetworkManager: s.nm,
    })
    r.Register("wallet_switchEthereumChain", &commands.SwitchEthereumChainCommand{
        Db:             s.db,
        NetworkManager: s.nm,
    })

    // Permissions
    r.Register("wallet_requestPermissions", &commands.RequestPermissionsCommand{})
    r.Register("wallet_revokePermissions", &commands.RevokePermissionsCommand{
        Db: s.db,
    })

    return &API{
        s: s,
        r: r,
        c: c,
    }
}

func (api *API) forwardRPC(URL string, request commands.RPCRequest) (interface{}, error) {
    dApp, err := persistence.SelectDAppByUrl(api.s.db, URL)
    if err != nil {
        return "", err
    }

    if dApp == nil {
        return "", commands.ErrDAppIsNotPermittedByUser
    }

    if request.ChainID != dApp.ChainID {
        request.ChainID = dApp.ChainID
    }

    var response map[string]interface{}
    byteRequest, err := json.Marshal(request)
    if err != nil {
        return "", err
    }

    rawResponse := api.s.rpc.CallRaw(string(byteRequest))
    if err := json.Unmarshal([]byte(rawResponse), &response); err != nil {
        return "", err
    }

    if errorField, ok := response["error"]; ok {
        errorMap, _ := errorField.(map[string]interface{})
        errorCode, _ := errorMap["code"].(float64)
        errorMessage, _ := errorMap["message"].(string)
        return nil, fmt.Errorf("error code %v: %s", errorCode, errorMessage)
    }

    if result, ok := response["result"]; ok {
        return result, nil
    }

    return nil, ErrInvalidResponseFromForwardedRpc
}

func (api *API) CallRPC(ctx context.Context, inputJSON string) (interface{}, error) {
    request, err := commands.RPCRequestFromJSON(inputJSON)
    if err != nil {
        return "", err
    }

    if command, exists := api.r.GetCommand(request.Method); exists {
        return command.Execute(ctx, request)
    }

    return api.forwardRPC(request.URL, request)
}

func (api *API) RecallDAppPermission(origin string) error {
    // TODO: close the websocket connection
    return persistence.DeleteDApp(api.s.db, origin)
}

func (api *API) GetPermittedDAppsList() ([]persistence.DApp, error) {
    return persistence.SelectAllDApps(api.s.db)
}

func (api *API) RequestAccountsAccepted(args commands.RequestAccountsAcceptedArgs) error {
    return api.c.RequestAccountsAccepted(args)
}

func (api *API) RequestAccountsRejected(args commands.RejectedArgs) error {
    return api.c.RequestAccountsRejected(args)
}

func (api *API) SendTransactionAccepted(args commands.SendTransactionAcceptedArgs) error {
    return api.c.SendTransactionAccepted(args)
}

func (api *API) SendTransactionRejected(args commands.RejectedArgs) error {
    return api.c.SendTransactionRejected(args)
}

func (api *API) PersonalSignAccepted(args commands.PersonalSignAcceptedArgs) error {
    return api.c.PersonalSignAccepted(args)
}

func (api *API) PersonalSignRejected(args commands.RejectedArgs) error {
    return api.c.PersonalSignRejected(args)
}