Feat: implement connector service for browser plugin (#5433)

* feat(connector)_: impl `eth_requestAccounts` for browser plugin

* feat(connector)_: add impl for `wallet_switchEthereumChain` and `eth_chainId`

* feat(connector)_: add impl for `eth_sendTransaction`

* feat(connector)_: add a signal and an endpoint for wallet ui side

* chore_: refactor connector tests

* feat(connector)_: impl `eth_requestAccounts` with signal

* chore(connector)_: Add test, covering full transaction flow

And polish impl & test for connector endpoints

* fix(connector)_: temporary allow all origins for ws connection

* chore_: review fixes

* fix(connector)_: make user select chain id for dApp

* fix(connector)_: add requestID and fine tune endpoints

* chore(connector)_: naming fixes and tests improvments
This commit is contained in:
Mikhail Rogachev 2024-07-18 17:30:10 +02:00 committed by GitHub
parent d07f9b5b16
commit 4c6ca00520
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1722 additions and 173 deletions

View File

@ -117,6 +117,8 @@ func newGethNodeConfig(config *params.NodeConfig) (*node.Config, error) {
nc.WSModules = config.FormatAPIModules()
nc.WSHost = config.WSHost
nc.WSPort = config.WSPort
// FIXME: this is a temporary solution to allow all origins
nc.WSOrigins = []string{"*"}
}
if config.ClusterConfig.Enabled {

View File

@ -429,7 +429,7 @@ func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfi
func (b *StatusNode) connectorService() *connector.Service {
if b.connectorSrvc == nil {
b.connectorSrvc = connector.NewService(b.rpcClient, b.connectorSrvc)
b.connectorSrvc = connector.NewService(b.walletDB, b.rpcClient, b.rpcClient.NetworkManager)
}
return b.connectorSrvc
}

View File

@ -1,42 +1,75 @@
package connector
import (
"encoding/json"
"fmt"
"github.com/status-im/status-go/services/connector/commands"
persistence "github.com/status-im/status-go/services/connector/database"
)
type API struct {
s *Service
r *CommandRegistry
c *commands.ClientSideHandler
}
func NewAPI(s *Service) *API {
r := NewCommandRegistry()
c := commands.NewClientSideHandler()
r.Register("eth_sendTransaction", &commands.SendTransactionCommand{
Db: s.db,
ClientHandler: c,
})
// Accounts query and dapp permissions
r.Register("eth_accounts", &commands.AccountsCommand{Db: s.db})
r.Register("eth_requestAccounts", &commands.RequestAccountsCommand{
ClientHandler: c,
AccountsCommand: commands.AccountsCommand{Db: s.db},
})
// Active chain per dapp management
r.Register("eth_chainId", &commands.ChainIDCommand{Db: s.db})
r.Register("wallet_switchEthereumChain", &commands.SwitchEthereumChainCommand{
Db: s.db,
NetworkManager: s.nm,
})
return &API{
s: s,
r: r,
c: c,
}
}
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params []interface{} `json:"params"`
}
func (api *API) CallRPC(inputJSON string) (string, error) {
var request RPCRequest
err := json.Unmarshal([]byte(inputJSON), &request)
request, err := commands.RPCRequestFromJSON(inputJSON)
if err != nil {
return "", fmt.Errorf("error unmarshalling JSON: %v", err)
return "", err
}
if command, exists := api.r.GetCommand(request.Method); exists {
return command.Execute(inputJSON)
return command.Execute(request)
}
return api.s.rpcClient.CallRaw(inputJSON), nil
return api.s.rpc.CallRaw(inputJSON), nil
}
func (api *API) RecallDAppPermission(origin string) error {
return persistence.DeleteDApp(api.s.db, origin)
}
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)
}

View File

@ -9,15 +9,16 @@ import (
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/params"
statusRPC "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/connector/commands"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/transactions/fake"
"github.com/status-im/status-go/walletdatabase"
)
func createDB(t *testing.T) (*sql.DB, func()) {
db, cleanup, err := helpers.SetupTestSQLDB(appdatabase.DbInitializer{}, "provider-tests-")
db, cleanup, err := helpers.SetupTestSQLDB(walletdatabase.DbInitializer{}, "provider-tests-")
require.NoError(t, err)
return db, func() { require.NoError(t, cleanup()) }
}
@ -38,7 +39,7 @@ func setupTestAPI(t *testing.T) (*API, func()) {
rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db)
require.NoError(t, err)
service := NewService(rpcClient, nil)
service := NewService(db, rpcClient, nil)
return NewAPI(service), cancel
}
@ -48,45 +49,36 @@ func TestCallRPC(t *testing.T) {
defer cancel()
tests := []struct {
request string
expectError bool
expectedContains string
notContains bool
request string
expectError error
}{
{
request: "{\"method\": \"eth_blockNumber\", \"params\": []}",
expectError: false,
expectedContains: "does not exist/is not available",
notContains: true,
request: "{\"method\": \"eth_chainId\", \"params\": []}",
expectError: commands.ErrRequestMissingDAppData,
},
{
request: "{\"method\": \"eth_blockNumbers\", \"params\": []}",
expectError: false,
expectedContains: "does not exist/is not available",
notContains: false,
request: "{\"method\": \"eth_accounts\", \"params\": []}",
expectError: commands.ErrRequestMissingDAppData,
},
{
request: "",
expectError: true,
expectedContains: "does not exist/is not available",
notContains: true,
request: "{\"method\": \"eth_requestAccounts\", \"params\": []}",
expectError: commands.ErrRequestMissingDAppData,
},
{
request: "{\"method\": \"eth_sendTransaction\", \"params\": []}",
expectError: commands.ErrRequestMissingDAppData,
},
{
request: "{\"method\": \"wallet_switchEthereumChain\", \"params\": []}",
expectError: commands.ErrRequestMissingDAppData,
},
}
for _, tt := range tests {
t.Run(tt.request, func(t *testing.T) {
response, err := api.CallRPC(tt.request)
if tt.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.NotEmpty(t, response)
if tt.notContains {
require.NotContains(t, response, tt.expectedContains)
} else {
require.Contains(t, response, tt.expectedContains)
}
}
_, err := api.CallRPC(tt.request)
require.Error(t, err)
require.Equal(t, tt.expectError, err)
})
}
}

View File

@ -1,20 +1,22 @@
package connector
import "github.com/status-im/status-go/services/connector/commands"
type CommandRegistry struct {
commands map[string]RPCCommand
commands map[string]commands.RPCCommand
}
func NewCommandRegistry() *CommandRegistry {
return &CommandRegistry{
commands: make(map[string]RPCCommand),
commands: make(map[string]commands.RPCCommand),
}
}
func (r *CommandRegistry) Register(method string, command RPCCommand) {
func (r *CommandRegistry) Register(method string, command commands.RPCCommand) {
r.commands[method] = command
}
func (r *CommandRegistry) GetCommand(method string) (RPCCommand, bool) {
func (r *CommandRegistry) GetCommand(method string) (commands.RPCCommand, bool) {
command, exists := r.commands[method]
return command, exists
}

View File

@ -0,0 +1,48 @@
package commands
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/status-im/status-go/eth-node/types"
persistence "github.com/status-im/status-go/services/connector/database"
)
type AccountsCommand struct {
Db *sql.DB
}
type AccountsResponse struct {
Accounts []types.Address `json:"accounts"`
}
func (c *AccountsCommand) dAppToAccountsResponse(dApp *persistence.DApp) (string, error) {
response := AccountsResponse{
Accounts: []types.Address{dApp.SharedAccount},
}
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("failed to marshal response: %v", err)
}
return string(responseJSON), nil
}
func (c *AccountsCommand) Execute(request RPCRequest) (string, error) {
err := request.Validate()
if err != nil {
return "", err
}
dApp, err := persistence.SelectDAppByUrl(c.Db, request.URL)
if err != nil {
return "", err
}
if dApp == nil {
return "", ErrDAppIsNotPermittedByUser
}
return c.dAppToAccountsResponse(dApp)
}

View File

@ -0,0 +1,64 @@
package commands
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/status-im/status-go/eth-node/types"
)
func TestFailToGetAccountWithMissingDAppFields(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &AccountsCommand{Db: db}
// Missing DApp fields
request, err := ConstructRPCRequest("eth_accounts", []interface{}{}, nil)
assert.NoError(t, err)
result, err := cmd.Execute(request)
assert.Equal(t, ErrRequestMissingDAppData, err)
assert.Empty(t, result)
}
func TestFailToGetAccountForUnpermittedDApp(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &AccountsCommand{Db: db}
request, err := ConstructRPCRequest("eth_accounts", []interface{}{}, &testDAppData)
assert.NoError(t, err)
result, err := cmd.Execute(request)
assert.Equal(t, ErrDAppIsNotPermittedByUser, err)
assert.Empty(t, result)
}
func TestGetAccountForPermittedDApp(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &AccountsCommand{Db: db}
sharedAccount := types.HexToAddress("0x6d0aa2a774b74bb1d36f97700315adf962c69fcg")
err := PersistDAppData(db, testDAppData, sharedAccount, 0x123)
assert.NoError(t, err)
request, err := ConstructRPCRequest("eth_accounts", []interface{}{}, &testDAppData)
assert.NoError(t, err)
response, err := cmd.Execute(request)
assert.NoError(t, err)
result := &AccountsResponse{}
err = json.Unmarshal([]byte(response), result)
assert.NoError(t, err)
assert.Len(t, result.Accounts, 1)
assert.Equal(t, sharedAccount, result.Accounts[0])
}

View File

@ -0,0 +1,30 @@
package commands
import (
"database/sql"
persistence "github.com/status-im/status-go/services/connector/database"
walletCommon "github.com/status-im/status-go/services/wallet/common"
)
type ChainIDCommand struct {
Db *sql.DB
}
func (c *ChainIDCommand) Execute(request RPCRequest) (string, error) {
err := request.Validate()
if err != nil {
return "", err
}
dApp, err := persistence.SelectDAppByUrl(c.Db, request.URL)
if err != nil {
return "", err
}
if dApp == nil {
return "", ErrDAppIsNotPermittedByUser
}
return walletCommon.ChainID(dApp.ChainID).String(), nil
}

View File

@ -0,0 +1,59 @@
package commands
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/status-im/status-go/eth-node/types"
walletCommon "github.com/status-im/status-go/services/wallet/common"
)
func TestFailToGetChainIdWithMissingDAppFields(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &ChainIDCommand{Db: db}
// Missing DApp fields
request, err := ConstructRPCRequest("eth_chainId", []interface{}{}, nil)
assert.NoError(t, err)
result, err := cmd.Execute(request)
assert.Equal(t, ErrRequestMissingDAppData, err)
assert.Empty(t, result)
}
func TestFailToGetChainIdForUnpermittedDApp(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &ChainIDCommand{Db: db}
request, err := ConstructRPCRequest("eth_chainId", []interface{}{}, &testDAppData)
assert.NoError(t, err)
result, err := cmd.Execute(request)
assert.Equal(t, ErrDAppIsNotPermittedByUser, err)
assert.Empty(t, result)
}
func TestGetChainIdForPermittedDApp(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &ChainIDCommand{Db: db}
sharedAccount := types.HexToAddress("0x6d0aa2a774b74bb1d36f97700315adf962c69fcg")
chainID := uint64(0x123)
err := PersistDAppData(db, testDAppData, sharedAccount, chainID)
assert.NoError(t, err)
request, err := ConstructRPCRequest("eth_chainId", []interface{}{}, &testDAppData)
assert.NoError(t, err)
response, err := cmd.Execute(request)
assert.NoError(t, err)
assert.Equal(t, walletCommon.ChainID(chainID).String(), response)
}

View File

@ -0,0 +1,165 @@
package commands
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sync/atomic"
"time"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions"
)
var (
WalletResponseMaxInterval = 20 * time.Minute
ErrWalletResponseTimeout = fmt.Errorf("timeout waiting for wallet response")
ErrEmptyAccountsShared = fmt.Errorf("empty accounts were shared by wallet")
ErrRequestAccountsRejectedByUser = fmt.Errorf("request accounts was rejected by user")
ErrSendTransactionRejectedByUser = fmt.Errorf("send transaction was rejected by user")
ErrEmptyRequestID = fmt.Errorf("empty requestID")
ErrAnotherConnectorOperationIsAwaitingFor = fmt.Errorf("another connector operation is awaiting for user input")
)
type MessageType int
const (
RequestAccountsAccepted MessageType = iota
SendTransactionAccepted
Rejected
)
type Message struct {
Type MessageType
Data interface{}
}
type ClientSideHandler struct {
responseChannel chan Message
isRequestRunning int32
}
func NewClientSideHandler() *ClientSideHandler {
return &ClientSideHandler{
responseChannel: make(chan Message, 1), // Buffer of 1 to avoid blocking
isRequestRunning: 0,
}
}
func (c *ClientSideHandler) generateRequestID(dApp signal.ConnectorDApp) string {
rawID := fmt.Sprintf("%d%s", time.Now().UnixMilli(), dApp.URL)
hash := sha256.Sum256([]byte(rawID))
return hex.EncodeToString(hash[:])
}
func (c *ClientSideHandler) setRequestRunning() bool {
return atomic.CompareAndSwapInt32(&c.isRequestRunning, 0, 1)
}
func (c *ClientSideHandler) clearRequestRunning() {
atomic.StoreInt32(&c.isRequestRunning, 0)
}
func (c *ClientSideHandler) RequestShareAccountForDApp(dApp signal.ConnectorDApp) (types.Address, uint64, error) {
if !c.setRequestRunning() {
return types.Address{}, 0, ErrAnotherConnectorOperationIsAwaitingFor
}
defer c.clearRequestRunning()
requestID := c.generateRequestID(dApp)
signal.SendConnectorSendRequestAccounts(dApp, requestID)
timeout := time.After(WalletResponseMaxInterval)
for {
select {
case msg := <-c.responseChannel:
switch msg.Type {
case RequestAccountsAccepted:
response := msg.Data.(RequestAccountsAcceptedArgs)
if response.RequestID == requestID {
return response.Account, response.ChainID, nil
}
case Rejected:
response := msg.Data.(RejectedArgs)
if response.RequestID == requestID {
return types.Address{}, 0, ErrRequestAccountsRejectedByUser
}
}
case <-timeout:
return types.Address{}, 0, ErrWalletResponseTimeout
}
}
}
func (c *ClientSideHandler) RequestSendTransaction(dApp signal.ConnectorDApp, chainID uint64, txArgs *transactions.SendTxArgs) (types.Hash, error) {
if !c.setRequestRunning() {
return types.Hash{}, ErrAnotherConnectorOperationIsAwaitingFor
}
defer c.clearRequestRunning()
txArgsJson, err := json.Marshal(txArgs)
if err != nil {
return types.Hash{}, fmt.Errorf("failed to marshal txArgs: %v", err)
}
requestID := c.generateRequestID(dApp)
signal.SendConnectorSendTransaction(dApp, chainID, string(txArgsJson), requestID)
timeout := time.After(WalletResponseMaxInterval)
for {
select {
case msg := <-c.responseChannel:
switch msg.Type {
case SendTransactionAccepted:
response := msg.Data.(SendTransactionAcceptedArgs)
if response.RequestID == requestID {
return response.Hash, nil
}
case Rejected:
response := msg.Data.(RejectedArgs)
if response.RequestID == requestID {
return types.Hash{}, ErrSendTransactionRejectedByUser
}
}
case <-timeout:
return types.Hash{}, ErrWalletResponseTimeout
}
}
}
func (c *ClientSideHandler) RequestAccountsAccepted(args RequestAccountsAcceptedArgs) error {
c.responseChannel <- Message{Type: RequestAccountsAccepted, Data: args}
return nil
}
func (c *ClientSideHandler) RequestAccountsRejected(args RejectedArgs) error {
if args.RequestID == "" {
return ErrEmptyRequestID
}
c.responseChannel <- Message{Type: Rejected, Data: args}
return nil
}
func (c *ClientSideHandler) SendTransactionAccepted(args SendTransactionAcceptedArgs) error {
if args.RequestID == "" {
return ErrEmptyRequestID
}
c.responseChannel <- Message{Type: SendTransactionAccepted, Data: args}
return nil
}
func (c *ClientSideHandler) SendTransactionRejected(args RejectedArgs) error {
if args.RequestID == "" {
return ErrEmptyRequestID
}
c.responseChannel <- Message{Type: Rejected, Data: args}
return nil
}

View File

@ -0,0 +1,28 @@
package commands
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestClientHandlerTimeout(t *testing.T) {
clientHandler := NewClientSideHandler()
backupWalletResponseMaxInterval := WalletResponseMaxInterval
WalletResponseMaxInterval = 1 * time.Millisecond
_, _, err := clientHandler.RequestShareAccountForDApp(testDAppData)
assert.Equal(t, ErrWalletResponseTimeout, err)
WalletResponseMaxInterval = backupWalletResponseMaxInterval
}
func TestRequestRejectedWhileWaiting(t *testing.T) {
clientHandler := NewClientSideHandler()
clientHandler.setRequestRunning()
_, _, err := clientHandler.RequestShareAccountForDApp(testDAppData)
assert.Equal(t, ErrAnotherConnectorOperationIsAwaitingFor, err)
}

View File

@ -0,0 +1,65 @@
package commands
import (
"errors"
"github.com/status-im/status-go/multiaccounts/accounts"
persistence "github.com/status-im/status-go/services/connector/database"
"github.com/status-im/status-go/signal"
)
// errors
var (
ErrAccountsRequestDeniedByUser = errors.New("accounts request denied by user")
ErrNoAccountsAvailable = errors.New("no accounts available")
)
type RequestAccountsCommand struct {
ClientHandler ClientSideHandlerInterface
AccountsCommand
}
type RawAccountsResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result []accounts.Account `json:"result"`
}
func (c *RequestAccountsCommand) Execute(request RPCRequest) (string, error) {
err := request.Validate()
if err != nil {
return "", err
}
dApp, err := persistence.SelectDAppByUrl(c.Db, request.URL)
if err != nil {
return "", err
}
// FIXME: this may have a security issue in case some malicious software tries to fake the origin
if dApp == nil {
account, chainID, err := c.ClientHandler.RequestShareAccountForDApp(signal.ConnectorDApp{
URL: request.URL,
Name: request.Name,
IconURL: request.IconURL,
})
if err != nil {
return "", err
}
dApp = &persistence.DApp{
URL: request.URL,
Name: request.Name,
IconURL: request.IconURL,
SharedAccount: account,
ChainID: chainID,
}
err = persistence.UpsertDApp(c.Db, dApp)
if err != nil {
return "", err
}
}
return c.dAppToAccountsResponse(dApp)
}

View File

@ -0,0 +1,153 @@
package commands
import (
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/status-im/status-go/eth-node/types"
persistence "github.com/status-im/status-go/services/connector/database"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/signal"
)
func TestFailToRequestAccountsWithMissingDAppFields(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &RequestAccountsCommand{AccountsCommand: AccountsCommand{Db: db}}
// Missing DApp fields
request, err := ConstructRPCRequest("eth_requestAccounts", []interface{}{}, nil)
assert.NoError(t, err)
result, err := cmd.Execute(request)
assert.Equal(t, ErrRequestMissingDAppData, err)
assert.Empty(t, result)
}
func TestRequestAccountsWithSignalTimeout(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
clientHandler := NewClientSideHandler()
cmd := &RequestAccountsCommand{
ClientHandler: clientHandler,
AccountsCommand: AccountsCommand{Db: db},
}
request, err := prepareSendTransactionRequest(testDAppData, types.Address{0x01})
assert.NoError(t, err)
backupWalletResponseMaxInterval := WalletResponseMaxInterval
WalletResponseMaxInterval = 1 * time.Millisecond
_, err = cmd.Execute(request)
assert.Equal(t, ErrWalletResponseTimeout, err)
WalletResponseMaxInterval = backupWalletResponseMaxInterval
}
func TestRequestAccountsAcceptedAndRequestAgain(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
clientHandler := NewClientSideHandler()
cmd := &RequestAccountsCommand{
ClientHandler: clientHandler,
AccountsCommand: AccountsCommand{Db: db},
}
request, err := ConstructRPCRequest("eth_requestAccounts", []interface{}{}, &testDAppData)
assert.NoError(t, err)
accountAddress := types.Address{0x03}
signal.SetMobileSignalHandler(signal.MobileSignalHandler(func(s []byte) {
var evt EventType
err := json.Unmarshal(s, &evt)
assert.NoError(t, err)
switch evt.Type {
case signal.EventConnectorSendRequestAccounts:
var ev signal.ConnectorSendRequestAccountsSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
err = clientHandler.RequestAccountsAccepted(RequestAccountsAcceptedArgs{
RequestID: ev.RequestID,
Account: accountAddress,
ChainID: walletCommon.EthereumMainnet,
})
assert.NoError(t, err)
}
}))
response, err := cmd.Execute(request)
assert.NoError(t, err)
result := &AccountsResponse{}
err = json.Unmarshal([]byte(response), result)
assert.NoError(t, err)
assert.Len(t, result.Accounts, 1)
assert.Equal(t, accountAddress, result.Accounts[0])
// Check dApp in the database
dApp, err := persistence.SelectDAppByUrl(db, request.URL)
assert.NoError(t, err)
assert.Equal(t, request.Name, dApp.Name)
assert.Equal(t, request.IconURL, dApp.IconURL)
assert.Equal(t, accountAddress, dApp.SharedAccount)
assert.Equal(t, walletCommon.EthereumMainnet, dApp.ChainID)
// This should not invoke UI side
response, err = cmd.Execute(request)
assert.NoError(t, err)
err = json.Unmarshal([]byte(response), result)
assert.NoError(t, err)
assert.Len(t, result.Accounts, 1)
assert.Equal(t, accountAddress, result.Accounts[0])
}
func TestRequestAccountsRejected(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
clientHandler := NewClientSideHandler()
cmd := &RequestAccountsCommand{
ClientHandler: clientHandler,
AccountsCommand: AccountsCommand{Db: db},
}
request, err := ConstructRPCRequest("eth_requestAccounts", []interface{}{}, &testDAppData)
assert.NoError(t, err)
signal.SetMobileSignalHandler(signal.MobileSignalHandler(func(s []byte) {
var evt EventType
err := json.Unmarshal(s, &evt)
assert.NoError(t, err)
switch evt.Type {
case signal.EventConnectorSendRequestAccounts:
var ev signal.ConnectorSendRequestAccountsSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
err = clientHandler.RequestAccountsRejected(RejectedArgs{
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
_, err = cmd.Execute(request)
assert.Equal(t, ErrRequestAccountsRejectedByUser, err)
}

View File

@ -0,0 +1,83 @@
package commands
import (
"encoding/json"
"errors"
"fmt"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions"
)
// errors
var (
ErrRequestMissingDAppData = errors.New("request missing dApp data")
ErrDAppIsNotPermittedByUser = errors.New("dApp is not permitted by user")
ErrEmptyRPCParams = errors.New("empty rpc params")
)
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params []interface{} `json:"params"`
URL string `json:"url"`
Name string `json:"name"`
IconURL string `json:"iconUrl"`
}
type RPCCommand interface {
Execute(request RPCRequest) (string, error)
}
type RequestAccountsAcceptedArgs struct {
RequestID string `json:"requestId"`
Account types.Address `json:"account"`
ChainID uint64 `json:"chainId"`
}
type SendTransactionAcceptedArgs struct {
RequestID string `json:"requestId"`
Hash types.Hash `json:"hash"`
}
type RejectedArgs struct {
RequestID string `json:"requestId"`
}
type ClientSideHandlerInterface interface {
RequestShareAccountForDApp(dApp signal.ConnectorDApp) (types.Address, uint64, error)
RequestSendTransaction(dApp signal.ConnectorDApp, chainID uint64, txArgs *transactions.SendTxArgs) (types.Hash, error)
RequestAccountsAccepted(args RequestAccountsAcceptedArgs) error
RequestAccountsRejected(args RejectedArgs) error
SendTransactionAccepted(args SendTransactionAcceptedArgs) error
SendTransactionRejected(args RejectedArgs) error
}
type NetworkManagerInterface interface {
GetActiveNetworks() ([]*params.Network, error)
}
type RPCClientInterface interface {
CallRaw(body string) string
}
func RPCRequestFromJSON(inputJSON string) (RPCRequest, error) {
var request RPCRequest
err := json.Unmarshal([]byte(inputJSON), &request)
if err != nil {
return RPCRequest{}, fmt.Errorf("error unmarshalling JSON: %v", err)
}
return request, nil
}
func (r *RPCRequest) Validate() error {
if r.URL == "" || r.Name == "" {
return ErrRequestMissingDAppData
}
return nil
}

View File

@ -0,0 +1,81 @@
package commands
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
persistence "github.com/status-im/status-go/services/connector/database"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions"
)
var (
ErrParamsFromAddressIsNotShared = errors.New("from parameter address is not dApp's shared account")
ErrNoTransactionParamsFound = errors.New("no transaction in params found")
)
type SendTransactionCommand struct {
Db *sql.DB
ClientHandler ClientSideHandlerInterface
}
func (r *RPCRequest) getSendTransactionParams() (*transactions.SendTxArgs, error) {
if r.Params == nil || len(r.Params) == 0 {
return nil, ErrEmptyRPCParams
}
paramMap, ok := r.Params[0].(map[string]interface{})
if !ok {
return nil, ErrNoTransactionParamsFound
}
paramBytes, err := json.Marshal(paramMap)
if err != nil {
return nil, fmt.Errorf("error marshalling first transaction param: %v", err)
}
var sendTxArgs transactions.SendTxArgs
err = json.Unmarshal(paramBytes, &sendTxArgs)
if err != nil {
return nil, fmt.Errorf("error unmarshalling first transaction param to SendTxArgs: %v", err)
}
return &sendTxArgs, nil
}
func (c *SendTransactionCommand) Execute(request RPCRequest) (string, error) {
err := request.Validate()
if err != nil {
return "", err
}
dApp, err := persistence.SelectDAppByUrl(c.Db, request.URL)
if err != nil {
return "", err
}
if dApp == nil {
return "", ErrDAppIsNotPermittedByUser
}
params, err := request.getSendTransactionParams()
if err != nil {
return "", err
}
if params.From != dApp.SharedAccount {
return "", ErrParamsFromAddressIsNotShared
}
hash, err := c.ClientHandler.RequestSendTransaction(signal.ConnectorDApp{
URL: request.URL,
Name: request.Name,
IconURL: request.IconURL,
}, dApp.ChainID, params)
if err != nil {
return "", err
}
return hash.String(), nil
}

View File

@ -0,0 +1,175 @@
package commands
import (
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
hexutil "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions"
)
func prepareSendTransactionRequest(dApp signal.ConnectorDApp, from types.Address) (RPCRequest, error) {
sendArgs := transactions.SendTxArgs{
From: from,
To: &types.Address{0x02},
Value: &hexutil.Big{},
Data: types.HexBytes("0x0"),
}
sendArgsJSON, err := json.Marshal(sendArgs)
if err != nil {
return RPCRequest{}, err
}
var sendArgsMap map[string]interface{}
err = json.Unmarshal(sendArgsJSON, &sendArgsMap)
if err != nil {
return RPCRequest{}, err
}
params := []interface{}{sendArgsMap}
return ConstructRPCRequest("eth_sendTransaction", params, &dApp)
}
func TestFailToSendTransactionWithoutPermittedDApp(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &SendTransactionCommand{Db: db}
// Don't save dApp in the database
request, err := prepareSendTransactionRequest(testDAppData, types.Address{0x1})
assert.NoError(t, err)
_, err = cmd.Execute(request)
assert.Equal(t, ErrDAppIsNotPermittedByUser, err)
}
func TestFailToSendTransactionWithWrongAddress(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &SendTransactionCommand{Db: db}
err := PersistDAppData(db, testDAppData, types.Address{0x01}, uint64(0x1))
assert.NoError(t, err)
request, err := prepareSendTransactionRequest(testDAppData, types.Address{0x02})
assert.NoError(t, err)
_, err = cmd.Execute(request)
assert.Equal(t, ErrParamsFromAddressIsNotShared, err)
}
func TestSendTransactionWithSignalTimout(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
clientHandler := NewClientSideHandler()
cmd := &SendTransactionCommand{
Db: db,
ClientHandler: clientHandler,
}
err := PersistDAppData(db, testDAppData, types.Address{0x01}, uint64(0x1))
assert.NoError(t, err)
request, err := prepareSendTransactionRequest(testDAppData, types.Address{0x01})
assert.NoError(t, err)
backupWalletResponseMaxInterval := WalletResponseMaxInterval
WalletResponseMaxInterval = 1 * time.Millisecond
_, err = cmd.Execute(request)
assert.Equal(t, ErrWalletResponseTimeout, err)
WalletResponseMaxInterval = backupWalletResponseMaxInterval
}
func TestSendTransactionWithSignalAccepted(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
fakedTransactionHash := types.Hash{0x051}
clientHandler := NewClientSideHandler()
cmd := &SendTransactionCommand{
Db: db,
ClientHandler: clientHandler,
}
err := PersistDAppData(db, testDAppData, types.Address{0x01}, uint64(0x1))
assert.NoError(t, err)
request, err := prepareSendTransactionRequest(testDAppData, types.Address{0x01})
assert.NoError(t, err)
signal.SetMobileSignalHandler(signal.MobileSignalHandler(func(s []byte) {
var evt EventType
err := json.Unmarshal(s, &evt)
assert.NoError(t, err)
switch evt.Type {
case signal.EventConnectorSendTransaction:
var ev signal.ConnectorSendTransactionSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
err = clientHandler.SendTransactionAccepted(SendTransactionAcceptedArgs{
Hash: fakedTransactionHash,
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
response, err := cmd.Execute(request)
assert.NoError(t, err)
assert.Equal(t, response, fakedTransactionHash.String())
}
func TestSendTransactionWithSignalRejected(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
clientHandler := NewClientSideHandler()
cmd := &SendTransactionCommand{
Db: db,
ClientHandler: clientHandler,
}
err := PersistDAppData(db, testDAppData, types.Address{0x01}, uint64(0x1))
assert.NoError(t, err)
request, err := prepareSendTransactionRequest(testDAppData, types.Address{0x01})
assert.NoError(t, err)
signal.SetMobileSignalHandler(signal.MobileSignalHandler(func(s []byte) {
var evt EventType
err := json.Unmarshal(s, &evt)
assert.NoError(t, err)
switch evt.Type {
case signal.EventConnectorSendTransaction:
var ev signal.ConnectorSendTransactionSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
err = clientHandler.SendTransactionRejected(RejectedArgs{
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
_, err = cmd.Execute(request)
assert.Equal(t, ErrSendTransactionRejectedByUser, err)
}

View File

@ -0,0 +1,94 @@
package commands
import (
"database/sql"
"errors"
"slices"
persistence "github.com/status-im/status-go/services/connector/database"
walletCommon "github.com/status-im/status-go/services/wallet/common"
)
// errors
var (
ErrNoActiveNetworks = errors.New("no active networks")
ErrUnsupportedNetwork = errors.New("unsupported network")
ErrNoChainIDParamsFound = errors.New("no chain id in params found")
)
type SwitchEthereumChainCommand struct {
NetworkManager NetworkManagerInterface
Db *sql.DB
}
func (r *RPCRequest) getChainID() (uint64, error) {
if r.Params == nil || len(r.Params) == 0 {
return 0, ErrEmptyRPCParams
}
switch v := r.Params[0].(type) {
case float64:
return uint64(v), nil
case int:
return uint64(v), nil
case int64:
return uint64(v), nil
case uint64:
return v, nil
default:
return 0, ErrNoChainIDParamsFound
}
}
func (c *SwitchEthereumChainCommand) getSupportedChainIDs() ([]uint64, error) {
activeNetworks, err := c.NetworkManager.GetActiveNetworks()
if err != nil {
return nil, err
}
if len(activeNetworks) < 1 {
return nil, ErrNoActiveNetworks
}
chainIDs := make([]uint64, len(activeNetworks))
for i, network := range activeNetworks {
chainIDs[i] = network.ChainID
}
return chainIDs, nil
}
func (c *SwitchEthereumChainCommand) Execute(request RPCRequest) (string, error) {
err := request.Validate()
if err != nil {
return "", err
}
requestedChainID, err := request.getChainID()
if err != nil {
return "", err
}
chainIDs, err := c.getSupportedChainIDs()
if err != nil {
return "", err
}
if !slices.Contains(chainIDs, requestedChainID) {
return "", ErrUnsupportedNetwork
}
dApp, err := persistence.SelectDAppByUrl(c.Db, request.URL)
if err != nil {
return "", err
}
dApp.ChainID = requestedChainID
err = persistence.UpsertDApp(c.Db, dApp)
if err != nil {
return "", err
}
return walletCommon.ChainID(dApp.ChainID).String(), nil
}

View File

@ -0,0 +1,98 @@
package commands
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
walletCommon "github.com/status-im/status-go/services/wallet/common"
)
func TestFailToSwitchEthereumChainWithMissingDAppFields(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &SwitchEthereumChainCommand{Db: db}
// Missing DApp fields
request, err := ConstructRPCRequest("wallet_switchEthereumChain", []interface{}{}, nil)
assert.NoError(t, err)
result, err := cmd.Execute(request)
assert.Equal(t, ErrRequestMissingDAppData, err)
assert.Empty(t, result)
}
func TestFailToSwitchEthereumChainWithNoChainId(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
cmd := &SwitchEthereumChainCommand{Db: db}
request, err := ConstructRPCRequest("wallet_switchEthereumChain", []interface{}{}, &testDAppData)
assert.NoError(t, err)
_, err = cmd.Execute(request)
assert.Equal(t, ErrEmptyRPCParams, err)
}
func TestFailToSwitchEthereumChainWithUnsupportedChainId(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
nm := NetworkManagerMock{}
nm.SetNetworks([]*params.Network{
{
ChainID: walletCommon.EthereumMainnet,
},
})
cmd := &SwitchEthereumChainCommand{
Db: db,
NetworkManager: &nm,
}
params := make([]interface{}, 1)
params[0] = walletCommon.BinanceTestChainID // some unrecoginzed chain id
request, err := ConstructRPCRequest("wallet_switchEthereumChain", params, &testDAppData)
assert.NoError(t, err)
_, err = cmd.Execute(request)
assert.Equal(t, ErrUnsupportedNetwork, err)
}
func TestSwitchEthereumChain(t *testing.T) {
db, close := SetupTestDB(t)
defer close()
nm := NetworkManagerMock{}
nm.SetNetworks([]*params.Network{
{
ChainID: walletCommon.EthereumMainnet,
},
{
ChainID: walletCommon.EthereumGoerli,
},
})
cmd := &SwitchEthereumChainCommand{
Db: db,
NetworkManager: &nm,
}
params := make([]interface{}, 1)
params[0] = walletCommon.EthereumMainnet
request, err := ConstructRPCRequest("wallet_switchEthereumChain", params, &testDAppData)
assert.NoError(t, err)
err = PersistDAppData(db, testDAppData, types.HexToAddress("0x6d0aa2a774b74bb1d36f97700315adf962c69fcg"), walletCommon.EthereumMainnet)
assert.NoError(t, err)
response, err := cmd.Execute(request)
assert.NoError(t, err)
assert.Equal(t, walletCommon.ChainID(walletCommon.EthereumMainnet).String(), response)
}

View File

@ -0,0 +1,88 @@
package commands
import (
"database/sql"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
persistence "github.com/status-im/status-go/services/connector/database"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
)
var testDAppData = signal.ConnectorDApp{
URL: "http://testDAppURL",
Name: "testDAppName",
IconURL: "http://testDAppIconUrl",
}
type RPCClientMock struct {
response string
}
type NetworkManagerMock struct {
networks []*params.Network
}
type EventType struct {
Type string `json:"type"`
Event json.RawMessage `json:"event"`
}
func (c *RPCClientMock) CallRaw(request string) string {
return c.response
}
func (c *RPCClientMock) SetResponse(response string) {
c.response = response
}
func (nm *NetworkManagerMock) GetActiveNetworks() ([]*params.Network, error) {
return nm.networks, nil
}
func (nm *NetworkManagerMock) SetNetworks(networks []*params.Network) {
nm.networks = networks
}
func SetupTestDB(t *testing.T) (db *sql.DB, close func()) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
return db, func() {
require.NoError(t, db.Close())
}
}
func PersistDAppData(db *sql.DB, dApp signal.ConnectorDApp, sharedAccount types.Address, chainID uint64) error {
dAppDb := persistence.DApp{
URL: dApp.URL,
Name: dApp.Name,
IconURL: dApp.IconURL,
SharedAccount: sharedAccount,
ChainID: chainID,
}
return persistence.UpsertDApp(db, &dAppDb)
}
func ConstructRPCRequest(method string, params []interface{}, dApp *signal.ConnectorDApp) (RPCRequest, error) {
request := RPCRequest{
JSONRPC: "2.0",
ID: 1,
Method: method,
Params: params,
}
if dApp != nil {
request.URL = dApp.URL
request.Name = dApp.Name
request.IconURL = dApp.IconURL
}
return request, nil
}

View File

@ -0,0 +1,112 @@
package connector
import (
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/connector/commands"
"github.com/status-im/status-go/signal"
)
func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
db, close := createDB(t)
defer close()
nm := commands.NetworkManagerMock{}
nm.SetNetworks([]*params.Network{
{
ChainID: 0x1,
Layer: 1,
},
{
ChainID: 0x5,
Layer: 1,
},
})
rpc := commands.RPCClientMock{}
service := NewService(db, &rpc, &nm)
api := NewAPI(service)
// Try to request accounts without permission
request := "{\"method\":\"eth_accounts\",\"params\":[],\"url\":\"http://testDAppURL123\",\"name\":\"testDAppName\",\"iconUrl\":\"http://testDAppIconUrl\"}"
response, err := api.CallRPC(request)
assert.Empty(t, response)
assert.Error(t, err)
assert.Equal(t, commands.ErrDAppIsNotPermittedByUser, err)
accountAddress := types.BytesToAddress(types.FromHex("0x6d0aa2a774b74bb1d36f97700315adf962c69fcg"))
expectedHash := types.BytesToHash(types.FromHex("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"))
signal.SetMobileSignalHandler(signal.MobileSignalHandler(func(s []byte) {
var evt commands.EventType
err := json.Unmarshal(s, &evt)
assert.NoError(t, err)
switch evt.Type {
case signal.EventConnectorSendRequestAccounts:
var ev signal.ConnectorSendRequestAccountsSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
err = api.RequestAccountsAccepted(commands.RequestAccountsAcceptedArgs{
RequestID: ev.RequestID,
Account: accountAddress,
ChainID: 0x1,
})
assert.NoError(t, err)
case signal.EventConnectorSendTransaction:
var ev signal.ConnectorSendTransactionSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
err = api.SendTransactionAccepted(commands.SendTransactionAcceptedArgs{
RequestID: ev.RequestID,
Hash: expectedHash,
})
assert.NoError(t, err)
}
}))
// Request accounts, now for real
request = "{\"method\": \"eth_requestAccounts\", \"params\": [], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }"
expectedResponse := strings.ToLower(fmt.Sprintf(`{"accounts":["%s"]}`, accountAddress.Hex()))
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
// Request to switch ethereum chain
expectedChainId := 0x5
request = fmt.Sprintf("{\"method\": \"wallet_switchEthereumChain\", \"params\": [%d], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }", expectedChainId)
expectedResponse = fmt.Sprintf(`%d`, expectedChainId)
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
// Check if the chain was switched
request = "{\"method\": \"eth_chainId\", \"params\": [], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }"
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
// Check the account after switching chain
request = "{\"method\": \"eth_accounts\", \"params\": [], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }"
expectedResponse = strings.ToLower(fmt.Sprintf(`{"accounts":["%s"]}`, accountAddress.Hex()))
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
// Send transaction
request = fmt.Sprintf("{\"method\": \"eth_sendTransaction\", \"params\":[{\"from\":\"%s\",\"to\":\"0x0200000000000000000000000000000000000000\",\"value\":\"0x12345\",\"data\":\"0x307830\"}], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }", accountAddress.Hex())
expectedResponse = expectedHash.Hex()
response, err = api.CallRPC(request)
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
}

View File

@ -0,0 +1,40 @@
package persistence
import (
"database/sql"
"github.com/status-im/status-go/eth-node/types"
)
const upsertDAppQuery = "INSERT INTO connector_dapps (url, name, icon_url, shared_account, chain_id) VALUES (?, ?, ?, ?, ?) ON CONFLICT(url) DO UPDATE SET name = excluded.name, icon_url = excluded.icon_url, shared_account = excluded.shared_account, chain_id = excluded.chain_id"
const selectDAppByUrlQuery = "SELECT name, icon_url, shared_account, chain_id FROM connector_dapps WHERE url = ?"
const deleteDAppQuery = "DELETE FROM connector_dapps WHERE url = ?"
type DApp struct {
URL string `json:"url"`
Name string `json:"name"`
IconURL string `json:"iconUrl"`
SharedAccount types.Address `json:"sharedAccount"`
ChainID uint64 `json:"chainId"`
}
func UpsertDApp(db *sql.DB, dApp *DApp) error {
_, err := db.Exec(upsertDAppQuery, dApp.URL, dApp.Name, dApp.IconURL, dApp.SharedAccount, dApp.ChainID)
return err
}
func SelectDAppByUrl(db *sql.DB, url string) (*DApp, error) {
dApp := &DApp{
URL: url,
}
err := db.QueryRow(selectDAppByUrlQuery, url).Scan(&dApp.Name, &dApp.IconURL, &dApp.SharedAccount, &dApp.ChainID)
if err == sql.ErrNoRows {
return nil, nil
}
return dApp, err
}
func DeleteDApp(db *sql.DB, url string) error {
_, err := db.Exec(deleteDAppQuery, url)
return err
}

View File

@ -0,0 +1,82 @@
package persistence
import (
"testing"
"database/sql"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
"github.com/stretchr/testify/require"
)
var testDApp = DApp{
Name: "Test DApp",
URL: "https://test-dapp-url.com",
IconURL: "https://test-dapp-icon-url.com",
SharedAccount: types.HexToAddress("0x1234567890"),
ChainID: 0x1,
}
func setupTestDB(t *testing.T) (db *sql.DB, close func()) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
return db, func() {
require.NoError(t, db.Close())
}
}
func TestInsertAndSelectDApp(t *testing.T) {
db, close := setupTestDB(t)
defer close()
err := UpsertDApp(db, &testDApp)
require.NoError(t, err)
dAppBack, err := SelectDAppByUrl(db, testDApp.URL)
require.NoError(t, err)
require.Equal(t, &testDApp, dAppBack)
}
func TestInsertAndUpdateDApp(t *testing.T) {
db, close := setupTestDB(t)
defer close()
err := UpsertDApp(db, &testDApp)
require.NoError(t, err)
updatedDApp := DApp{
Name: "Updated Test DApp",
URL: testDApp.URL,
IconURL: "https://updated-test-dapp-icon-url.com",
}
err = UpsertDApp(db, &updatedDApp)
require.NoError(t, err)
dAppBack, err := SelectDAppByUrl(db, testDApp.URL)
require.NoError(t, err)
require.Equal(t, &updatedDApp, dAppBack)
require.NotEqual(t, &testDApp, dAppBack)
}
func TestInsertAndRemoveDApp(t *testing.T) {
db, close := setupTestDB(t)
defer close()
err := UpsertDApp(db, &testDApp)
require.NoError(t, err)
dAppBack, err := SelectDAppByUrl(db, testDApp.URL)
require.NoError(t, err)
require.Equal(t, &testDApp, dAppBack)
err = DeleteDApp(db, testDApp.URL)
require.NoError(t, err)
dAppBack, err = SelectDAppByUrl(db, testDApp.URL)
require.NoError(t, err)
require.Empty(t, dAppBack)
}

View File

@ -1,5 +0,0 @@
package connector
type RPCCommand interface {
Execute(inputJSON string) (string, error)
}

View File

@ -1,22 +1,26 @@
package connector
import (
"database/sql"
"github.com/ethereum/go-ethereum/p2p"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/connector/commands"
)
func NewService(rpcClient *rpc.Client, connectorSrvc *Service) *Service {
func NewService(db *sql.DB, rpc commands.RPCClientInterface, nm commands.NetworkManagerInterface) *Service {
return &Service{
rpcClient: rpcClient,
connectorSrvc: connectorSrvc,
db: db,
rpc: rpc,
nm: nm,
}
}
type Service struct {
rpcClient *rpc.Client
connectorSrvc *Service
db *sql.DB
rpc commands.RPCClientInterface
nm commands.NetworkManagerInterface
}
func (s *Service) Start() error {

View File

@ -9,13 +9,14 @@ import (
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
statusRPC "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/connector/commands"
"github.com/status-im/status-go/transactions/fake"
)
func TestNewService(t *testing.T) {
db, _ := createDB(t)
db, close := createDB(t)
defer close()
txServiceMockCtrl := gomock.NewController(t)
server, _ := fake.NewTestServer(txServiceMockCtrl)
@ -30,22 +31,26 @@ func TestNewService(t *testing.T) {
rpcClient, err := statusRPC.NewClient(client, 1, upstreamConfig, nil, db)
require.NoError(t, err)
mockConnectorService := &Service{}
service := NewService(rpcClient, mockConnectorService)
service := NewService(db, rpcClient, rpcClient.NetworkManager)
assert.NotNil(t, service)
assert.Equal(t, rpcClient, service.rpcClient)
assert.Equal(t, mockConnectorService, service.connectorSrvc)
assert.Equal(t, rpcClient.NetworkManager, service.nm)
}
func TestService_Start(t *testing.T) {
service := NewService(&rpc.Client{}, &Service{})
db, close := createDB(t)
defer close()
service := NewService(db, &commands.RPCClientMock{}, &commands.NetworkManagerMock{})
err := service.Start()
assert.NoError(t, err)
}
func TestService_Stop(t *testing.T) {
service := NewService(&rpc.Client{}, &Service{})
db, close := createDB(t)
defer close()
service := NewService(db, &commands.RPCClientMock{}, &commands.NetworkManagerMock{})
err := service.Stop()
assert.NoError(t, err)
}
@ -63,7 +68,10 @@ func TestService_APIs(t *testing.T) {
}
func TestService_Protocols(t *testing.T) {
service := NewService(&rpc.Client{}, &Service{})
db, close := createDB(t)
defer close()
service := NewService(db, &commands.RPCClientMock{}, &commands.NetworkManagerMock{})
protocols := service.Protocols()
assert.Nil(t, protocols)
}

View File

@ -0,0 +1,42 @@
package signal
const (
EventConnectorSendRequestAccounts = "connector.sendRequestAccounts"
EventConnectorSendTransaction = "connector.sendTransaction"
)
type ConnectorDApp struct {
URL string `json:"url"`
Name string `json:"name"`
IconURL string `json:"iconUrl"`
}
// ConnectorSendRequestAccountsSignal is triggered when a request for accounts is sent.
type ConnectorSendRequestAccountsSignal struct {
ConnectorDApp
RequestID string `json:"requestId"`
}
// ConnectorSendTransactionSignal is triggered when a transaction is requested to be sent.
type ConnectorSendTransactionSignal struct {
ConnectorDApp
RequestID string `json:"requestId"`
ChainID uint64 `json:"chainId"`
TxArgs string `json:"txArgs"`
}
func SendConnectorSendRequestAccounts(dApp ConnectorDApp, requestID string) {
send(EventConnectorSendRequestAccounts, ConnectorSendRequestAccountsSignal{
ConnectorDApp: dApp,
RequestID: requestID,
})
}
func SendConnectorSendTransaction(dApp ConnectorDApp, chainID uint64, txArgs string, requestID string) {
send(EventConnectorSendTransaction, ConnectorSendTransactionSignal{
ConnectorDApp: dApp,
RequestID: requestID,
ChainID: chainID,
TxArgs: txArgs,
})
}

View File

@ -31,6 +31,7 @@
// 1716313614_add_rpc_limits_table.up.sql (203B)
// 1716912885_add_wallet_connect_dapps.up.sql (750B)
// 1721136888_recreate_indices_balance_history_remove_dups.up.sql (861B)
// 1721306883_add_connector_dapps.up.sql (360B)
// doc.go (94B)
package migrations
@ -41,7 +42,6 @@ import (
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -51,7 +51,7 @@ import (
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
var buf bytes.Buffer
@ -59,7 +59,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
if clErr != nil {
return nil, err
@ -720,6 +720,26 @@ func _1721136888_recreate_indices_balance_history_remove_dupsUpSql() (*asset, er
return a, nil
}
var __1721306883_add_connector_dappsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8e\xc1\x4e\x32\x31\x14\x85\xf7\x7d\x8a\xb3\xfc\xff\xc4\x79\x02\x57\x83\x54\x6c\xc4\xc1\x0c\x25\xc0\x6a\x52\xda\xab\x73\x43\x6d\x27\x6d\x07\x5e\xdf\x0c\x48\x62\xd4\xdd\xcd\x39\x5f\xce\xfd\xaa\x0a\x36\x86\x40\xb6\xc4\xd4\x39\x33\x0c\x19\xc5\x1c\x3c\xe1\x48\x34\xdd\xc9\xd8\x23\xe2\xdb\x0d\x22\x07\x57\x5f\xa0\x88\x21\xc5\x13\x3b\x82\x81\xe7\x70\x9c\x92\xd2\x13\x27\x70\x70\x7c\x62\x37\x1a\x8f\x4c\x39\x73\x0c\x59\x54\x15\x72\x1f\x47\xef\x70\x20\x18\xcf\xef\x81\x1c\xce\x5c\x7a\x9c\x8d\xf7\x54\xba\xaf\xfd\xef\x0a\x42\x3c\xb4\xb2\xd6\x12\xba\x9e\x2d\x25\xd4\x23\x9a\x95\x86\xdc\xa9\xb5\x5e\xff\x92\xfe\x27\x00\x60\x4c\x1e\x5a\xee\x34\x5e\x5b\xf5\x52\xb7\x7b\x3c\xcb\xfd\xdd\xa5\x09\xe6\x83\xae\xd5\x34\xd2\x6c\x96\xcb\x6b\x9e\x7b\x93\xc8\x75\xc6\xda\x38\x86\xf2\x17\x61\x7b\xc3\xa1\x63\x87\x4d\xb3\x56\x8b\x46\xce\x31\x53\x0b\xd5\xfc\xc4\xd8\xc6\xd0\xdd\xfe\x8b\xff\xd8\x2a\xfd\xb4\xda\x68\xb4\xab\xad\x9a\xdf\x8b\xcf\x00\x00\x00\xff\xff\x97\x7a\xcf\x29\x68\x01\x00\x00")
func _1721306883_add_connector_dappsUpSqlBytes() ([]byte, error) {
return bindataRead(
__1721306883_add_connector_dappsUpSql,
"1721306883_add_connector_dapps.up.sql",
)
}
func _1721306883_add_connector_dappsUpSql() (*asset, error) {
bytes, err := _1721306883_add_connector_dappsUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1721306883_add_connector_dapps.up.sql", size: 360, mode: os.FileMode(0644), modTime: time.Unix(1700000000, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6d, 0xb4, 0x56, 0x49, 0x98, 0x3d, 0x7e, 0x87, 0x93, 0xd2, 0x10, 0xd8, 0x29, 0x1a, 0x3d, 0xca, 0xa1, 0x31, 0x54, 0x7c, 0x10, 0xa5, 0x6b, 0xf4, 0xbe, 0x61, 0xc4, 0x9c, 0x42, 0x12, 0x9d, 0x5a}}
return a, nil
}
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xcb\x41\x0e\x02\x31\x08\x05\xd0\x7d\x4f\xf1\x2f\x00\xe8\xca\xc4\xc4\xc3\xa0\x43\x08\x19\x5b\xc6\x96\xfb\xc7\x4d\xdf\xfe\x5d\xfa\x39\xd5\x0d\xeb\xf7\x6d\x4d\xc4\xf3\xe9\x36\x6c\x6a\x19\x3c\xe9\x1d\xe3\xd0\x52\x50\xcf\xa3\xa2\xdb\xeb\xfe\xb8\x6d\xa0\xeb\x74\xf4\xf0\xa9\x15\x39\x16\x28\xc1\x2c\x7b\xb0\x27\x58\xda\x3f\x00\x00\xff\xff\x57\xd4\xd5\x90\x5e\x00\x00\x00")
func docGoBytes() ([]byte, error) {
@ -831,80 +851,55 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"1691753758_initial.up.sql": _1691753758_initialUpSql,
"1692701329_add_collectibles_and_collections_data_cache.up.sql": _1692701329_add_collectibles_and_collections_data_cacheUpSql,
"1692701339_add_scope_to_pending.up.sql": _1692701339_add_scope_to_pendingUpSql,
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": _1694540071_add_collectibles_ownership_update_timestampUpSql,
"1694692748_add_raw_balance_to_token_balances.up.sql": _1694692748_add_raw_balance_to_token_balancesUpSql,
"1691753758_initial.up.sql": _1691753758_initialUpSql,
"1692701329_add_collectibles_and_collections_data_cache.up.sql": _1692701329_add_collectibles_and_collections_data_cacheUpSql,
"1692701339_add_scope_to_pending.up.sql": _1692701339_add_scope_to_pendingUpSql,
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": _1694540071_add_collectibles_ownership_update_timestampUpSql,
"1694692748_add_raw_balance_to_token_balances.up.sql": _1694692748_add_raw_balance_to_token_balancesUpSql,
"1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql": _1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql,
"1695932536_balance_history_v2.up.sql": _1695932536_balance_history_v2UpSql,
"1696853635_input_data.up.sql": _1696853635_input_dataUpSql,
"1698117918_add_community_id_to_tokens.up.sql": _1698117918_add_community_id_to_tokensUpSql,
"1698257443_add_community_metadata_to_wallet_db.up.sql": _1698257443_add_community_metadata_to_wallet_dbUpSql,
"1699987075_add_timestamp_and_state_to_community_data_cache.up.sql": _1699987075_add_timestamp_and_state_to_community_data_cacheUpSql,
"1700414564_add_wallet_connect_pairings_table.up.sql": _1700414564_add_wallet_connect_pairings_tableUpSql,
"1701101493_add_token_blocks_range.up.sql": _1701101493_add_token_blocks_rangeUpSql,
"1702467441_wallet_connect_sessions_instead_of_pairings.up.sql": _1702467441_wallet_connect_sessions_instead_of_pairingsUpSql,
"1702577524_add_community_collections_and_collectibles_images_cache.up.sql": _1702577524_add_community_collections_and_collectibles_images_cacheUpSql,
"1702867707_add_balance_to_collectibles_ownership_cache.up.sql": _1702867707_add_balance_to_collectibles_ownership_cacheUpSql,
"1703686612_add_color_to_saved_addresses.up.sql": _1703686612_add_color_to_saved_addressesUpSql,
"1695932536_balance_history_v2.up.sql": _1695932536_balance_history_v2UpSql,
"1696853635_input_data.up.sql": _1696853635_input_dataUpSql,
"1698117918_add_community_id_to_tokens.up.sql": _1698117918_add_community_id_to_tokensUpSql,
"1698257443_add_community_metadata_to_wallet_db.up.sql": _1698257443_add_community_metadata_to_wallet_dbUpSql,
"1699987075_add_timestamp_and_state_to_community_data_cache.up.sql": _1699987075_add_timestamp_and_state_to_community_data_cacheUpSql,
"1700414564_add_wallet_connect_pairings_table.up.sql": _1700414564_add_wallet_connect_pairings_tableUpSql,
"1701101493_add_token_blocks_range.up.sql": _1701101493_add_token_blocks_rangeUpSql,
"1702467441_wallet_connect_sessions_instead_of_pairings.up.sql": _1702467441_wallet_connect_sessions_instead_of_pairingsUpSql,
"1702577524_add_community_collections_and_collectibles_images_cache.up.sql": _1702577524_add_community_collections_and_collectibles_images_cacheUpSql,
"1702867707_add_balance_to_collectibles_ownership_cache.up.sql": _1702867707_add_balance_to_collectibles_ownership_cacheUpSql,
"1703686612_add_color_to_saved_addresses.up.sql": _1703686612_add_color_to_saved_addressesUpSql,
"1704701942_remove_favourite_and_change_primary_key_for_saved_addresses.up.sql": _1704701942_remove_favourite_and_change_primary_key_for_saved_addressesUpSql,
"1704913491_add_type_and_tx_timestamp_to_collectibles_ownership_cache.up.sql": _1704913491_add_type_and_tx_timestamp_to_collectibles_ownership_cacheUpSql,
"1705664490_add_balance_check_fields_blocks_ranges_sequential.up.sql": _1705664490_add_balance_check_fields_blocks_ranges_sequentialUpSql,
"1706531789_remove_gasfee-only-eth-transfers.up.sql": _1706531789_remove_gasfeeOnlyEthTransfersUpSql,
"1707160323_add_contract_type_table.up.sql": _1707160323_add_contract_type_tableUpSql,
"1708089811_add_nullable_fiesl_blocks_ranges.up.sql": _1708089811_add_nullable_fiesl_blocks_rangesUpSql,
"1710189541_add_nonce_to_pending_transactions.up.sql": _1710189541_add_nonce_to_pending_transactionsUpSql,
"1712567001_add_soulbound_collectible_cache.up.sql": _1712567001_add_soulbound_collectible_cacheUpSql,
"1714670633_add_id_to_multi_transaction_table.up.sql": _1714670633_add_id_to_multi_transaction_tableUpSql,
"1715637927_add_collection_socials.up.sql": _1715637927_add_collection_socialsUpSql,
"1715839555_rename_chain_prefixes.up.sql": _1715839555_rename_chain_prefixesUpSql,
"1716313614_add_rpc_limits_table.up.sql": _1716313614_add_rpc_limits_tableUpSql,
"1716912885_add_wallet_connect_dapps.up.sql": _1716912885_add_wallet_connect_dappsUpSql,
"1721136888_recreate_indices_balance_history_remove_dups.up.sql": _1721136888_recreate_indices_balance_history_remove_dupsUpSql,
"doc.go": docGo,
"1704913491_add_type_and_tx_timestamp_to_collectibles_ownership_cache.up.sql": _1704913491_add_type_and_tx_timestamp_to_collectibles_ownership_cacheUpSql,
"1705664490_add_balance_check_fields_blocks_ranges_sequential.up.sql": _1705664490_add_balance_check_fields_blocks_ranges_sequentialUpSql,
"1706531789_remove_gasfee-only-eth-transfers.up.sql": _1706531789_remove_gasfeeOnlyEthTransfersUpSql,
"1707160323_add_contract_type_table.up.sql": _1707160323_add_contract_type_tableUpSql,
"1708089811_add_nullable_fiesl_blocks_ranges.up.sql": _1708089811_add_nullable_fiesl_blocks_rangesUpSql,
"1710189541_add_nonce_to_pending_transactions.up.sql": _1710189541_add_nonce_to_pending_transactionsUpSql,
"1712567001_add_soulbound_collectible_cache.up.sql": _1712567001_add_soulbound_collectible_cacheUpSql,
"1714670633_add_id_to_multi_transaction_table.up.sql": _1714670633_add_id_to_multi_transaction_tableUpSql,
"1715637927_add_collection_socials.up.sql": _1715637927_add_collection_socialsUpSql,
"1715839555_rename_chain_prefixes.up.sql": _1715839555_rename_chain_prefixesUpSql,
"1716313614_add_rpc_limits_table.up.sql": _1716313614_add_rpc_limits_tableUpSql,
"1716912885_add_wallet_connect_dapps.up.sql": _1716912885_add_wallet_connect_dappsUpSql,
"1721136888_recreate_indices_balance_history_remove_dups.up.sql": _1721136888_recreate_indices_balance_history_remove_dupsUpSql,
"1721306883_add_connector_dapps.up.sql": _1721306883_add_connector_dappsUpSql,
"doc.go": docGo,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
//
// data/
// foo.txt
// img/
// a.png
// b.png
//
// then AssetDir("data") would return []string{"foo.txt", "img"},
// AssetDir("data/img") would return []string{"a.png", "b.png"},
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
@ -937,38 +932,39 @@ type bintree struct {
}
var _bintree = &bintree{nil, map[string]*bintree{
"1691753758_initial.up.sql": &bintree{_1691753758_initialUpSql, map[string]*bintree{}},
"1692701329_add_collectibles_and_collections_data_cache.up.sql": &bintree{_1692701329_add_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1692701339_add_scope_to_pending.up.sql": &bintree{_1692701339_add_scope_to_pendingUpSql, map[string]*bintree{}},
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": &bintree{_1694540071_add_collectibles_ownership_update_timestampUpSql, map[string]*bintree{}},
"1694692748_add_raw_balance_to_token_balances.up.sql": &bintree{_1694692748_add_raw_balance_to_token_balancesUpSql, map[string]*bintree{}},
"1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql": &bintree{_1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1695932536_balance_history_v2.up.sql": &bintree{_1695932536_balance_history_v2UpSql, map[string]*bintree{}},
"1696853635_input_data.up.sql": &bintree{_1696853635_input_dataUpSql, map[string]*bintree{}},
"1698117918_add_community_id_to_tokens.up.sql": &bintree{_1698117918_add_community_id_to_tokensUpSql, map[string]*bintree{}},
"1698257443_add_community_metadata_to_wallet_db.up.sql": &bintree{_1698257443_add_community_metadata_to_wallet_dbUpSql, map[string]*bintree{}},
"1699987075_add_timestamp_and_state_to_community_data_cache.up.sql": &bintree{_1699987075_add_timestamp_and_state_to_community_data_cacheUpSql, map[string]*bintree{}},
"1700414564_add_wallet_connect_pairings_table.up.sql": &bintree{_1700414564_add_wallet_connect_pairings_tableUpSql, map[string]*bintree{}},
"1701101493_add_token_blocks_range.up.sql": &bintree{_1701101493_add_token_blocks_rangeUpSql, map[string]*bintree{}},
"1702467441_wallet_connect_sessions_instead_of_pairings.up.sql": &bintree{_1702467441_wallet_connect_sessions_instead_of_pairingsUpSql, map[string]*bintree{}},
"1702577524_add_community_collections_and_collectibles_images_cache.up.sql": &bintree{_1702577524_add_community_collections_and_collectibles_images_cacheUpSql, map[string]*bintree{}},
"1702867707_add_balance_to_collectibles_ownership_cache.up.sql": &bintree{_1702867707_add_balance_to_collectibles_ownership_cacheUpSql, map[string]*bintree{}},
"1703686612_add_color_to_saved_addresses.up.sql": &bintree{_1703686612_add_color_to_saved_addressesUpSql, map[string]*bintree{}},
"1704701942_remove_favourite_and_change_primary_key_for_saved_addresses.up.sql": &bintree{_1704701942_remove_favourite_and_change_primary_key_for_saved_addressesUpSql, map[string]*bintree{}},
"1704913491_add_type_and_tx_timestamp_to_collectibles_ownership_cache.up.sql": &bintree{_1704913491_add_type_and_tx_timestamp_to_collectibles_ownership_cacheUpSql, map[string]*bintree{}},
"1705664490_add_balance_check_fields_blocks_ranges_sequential.up.sql": &bintree{_1705664490_add_balance_check_fields_blocks_ranges_sequentialUpSql, map[string]*bintree{}},
"1706531789_remove_gasfee-only-eth-transfers.up.sql": &bintree{_1706531789_remove_gasfeeOnlyEthTransfersUpSql, map[string]*bintree{}},
"1707160323_add_contract_type_table.up.sql": &bintree{_1707160323_add_contract_type_tableUpSql, map[string]*bintree{}},
"1708089811_add_nullable_fiesl_blocks_ranges.up.sql": &bintree{_1708089811_add_nullable_fiesl_blocks_rangesUpSql, map[string]*bintree{}},
"1710189541_add_nonce_to_pending_transactions.up.sql": &bintree{_1710189541_add_nonce_to_pending_transactionsUpSql, map[string]*bintree{}},
"1712567001_add_soulbound_collectible_cache.up.sql": &bintree{_1712567001_add_soulbound_collectible_cacheUpSql, map[string]*bintree{}},
"1714670633_add_id_to_multi_transaction_table.up.sql": &bintree{_1714670633_add_id_to_multi_transaction_tableUpSql, map[string]*bintree{}},
"1715637927_add_collection_socials.up.sql": &bintree{_1715637927_add_collection_socialsUpSql, map[string]*bintree{}},
"1715839555_rename_chain_prefixes.up.sql": &bintree{_1715839555_rename_chain_prefixesUpSql, map[string]*bintree{}},
"1716313614_add_rpc_limits_table.up.sql": &bintree{_1716313614_add_rpc_limits_tableUpSql, map[string]*bintree{}},
"1716912885_add_wallet_connect_dapps.up.sql": &bintree{_1716912885_add_wallet_connect_dappsUpSql, map[string]*bintree{}},
"1721136888_recreate_indices_balance_history_remove_dups.up.sql": &bintree{_1721136888_recreate_indices_balance_history_remove_dupsUpSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}},
"1691753758_initial.up.sql": {_1691753758_initialUpSql, map[string]*bintree{}},
"1692701329_add_collectibles_and_collections_data_cache.up.sql": {_1692701329_add_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1692701339_add_scope_to_pending.up.sql": {_1692701339_add_scope_to_pendingUpSql, map[string]*bintree{}},
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": {_1694540071_add_collectibles_ownership_update_timestampUpSql, map[string]*bintree{}},
"1694692748_add_raw_balance_to_token_balances.up.sql": {_1694692748_add_raw_balance_to_token_balancesUpSql, map[string]*bintree{}},
"1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql": {_1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1695932536_balance_history_v2.up.sql": {_1695932536_balance_history_v2UpSql, map[string]*bintree{}},
"1696853635_input_data.up.sql": {_1696853635_input_dataUpSql, map[string]*bintree{}},
"1698117918_add_community_id_to_tokens.up.sql": {_1698117918_add_community_id_to_tokensUpSql, map[string]*bintree{}},
"1698257443_add_community_metadata_to_wallet_db.up.sql": {_1698257443_add_community_metadata_to_wallet_dbUpSql, map[string]*bintree{}},
"1699987075_add_timestamp_and_state_to_community_data_cache.up.sql": {_1699987075_add_timestamp_and_state_to_community_data_cacheUpSql, map[string]*bintree{}},
"1700414564_add_wallet_connect_pairings_table.up.sql": {_1700414564_add_wallet_connect_pairings_tableUpSql, map[string]*bintree{}},
"1701101493_add_token_blocks_range.up.sql": {_1701101493_add_token_blocks_rangeUpSql, map[string]*bintree{}},
"1702467441_wallet_connect_sessions_instead_of_pairings.up.sql": {_1702467441_wallet_connect_sessions_instead_of_pairingsUpSql, map[string]*bintree{}},
"1702577524_add_community_collections_and_collectibles_images_cache.up.sql": {_1702577524_add_community_collections_and_collectibles_images_cacheUpSql, map[string]*bintree{}},
"1702867707_add_balance_to_collectibles_ownership_cache.up.sql": {_1702867707_add_balance_to_collectibles_ownership_cacheUpSql, map[string]*bintree{}},
"1703686612_add_color_to_saved_addresses.up.sql": {_1703686612_add_color_to_saved_addressesUpSql, map[string]*bintree{}},
"1704701942_remove_favourite_and_change_primary_key_for_saved_addresses.up.sql": {_1704701942_remove_favourite_and_change_primary_key_for_saved_addressesUpSql, map[string]*bintree{}},
"1704913491_add_type_and_tx_timestamp_to_collectibles_ownership_cache.up.sql": {_1704913491_add_type_and_tx_timestamp_to_collectibles_ownership_cacheUpSql, map[string]*bintree{}},
"1705664490_add_balance_check_fields_blocks_ranges_sequential.up.sql": {_1705664490_add_balance_check_fields_blocks_ranges_sequentialUpSql, map[string]*bintree{}},
"1706531789_remove_gasfee-only-eth-transfers.up.sql": {_1706531789_remove_gasfeeOnlyEthTransfersUpSql, map[string]*bintree{}},
"1707160323_add_contract_type_table.up.sql": {_1707160323_add_contract_type_tableUpSql, map[string]*bintree{}},
"1708089811_add_nullable_fiesl_blocks_ranges.up.sql": {_1708089811_add_nullable_fiesl_blocks_rangesUpSql, map[string]*bintree{}},
"1710189541_add_nonce_to_pending_transactions.up.sql": {_1710189541_add_nonce_to_pending_transactionsUpSql, map[string]*bintree{}},
"1712567001_add_soulbound_collectible_cache.up.sql": {_1712567001_add_soulbound_collectible_cacheUpSql, map[string]*bintree{}},
"1714670633_add_id_to_multi_transaction_table.up.sql": {_1714670633_add_id_to_multi_transaction_tableUpSql, map[string]*bintree{}},
"1715637927_add_collection_socials.up.sql": {_1715637927_add_collection_socialsUpSql, map[string]*bintree{}},
"1715839555_rename_chain_prefixes.up.sql": {_1715839555_rename_chain_prefixesUpSql, map[string]*bintree{}},
"1716313614_add_rpc_limits_table.up.sql": {_1716313614_add_rpc_limits_tableUpSql, map[string]*bintree{}},
"1716912885_add_wallet_connect_dapps.up.sql": {_1716912885_add_wallet_connect_dappsUpSql, map[string]*bintree{}},
"1721136888_recreate_indices_balance_history_remove_dups.up.sql": {_1721136888_recreate_indices_balance_history_remove_dupsUpSql, map[string]*bintree{}},
"1721306883_add_connector_dapps.up.sql": {_1721306883_add_connector_dappsUpSql, map[string]*bintree{}},
"doc.go": {docGo, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory.
@ -985,7 +981,7 @@ func RestoreAsset(dir, name string) error {
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
err = os.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}

View File

@ -0,0 +1,10 @@
-- connector_dapps table keeps track of connected dApps to provide a link to their individual sessions
-- should be aligned with wallet_connect_dapps table
CREATE TABLE IF NOT EXISTS connector_dapps (
url TEXT PRIMARY KEY,
name TEXT NOT NULL,
shared_account TEXT NOT NULL,
chain_id UNSIGNED BIGINT NOT NULL,
icon_url TEXT
) WITHOUT ROWID;