mirror of
https://github.com/status-im/status-go.git
synced 2025-01-18 02:31:47 +00:00
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:
parent
d07f9b5b16
commit
4c6ca00520
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
@ -49,44 +50,35 @@ func TestCallRPC(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
request string
|
||||
expectError bool
|
||||
expectedContains string
|
||||
notContains bool
|
||||
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 {
|
||||
_, err := api.CallRPC(tt.request)
|
||||
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)
|
||||
}
|
||||
}
|
||||
require.Equal(t, tt.expectError, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
48
services/connector/commands/accounts.go
Normal file
48
services/connector/commands/accounts.go
Normal 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)
|
||||
}
|
64
services/connector/commands/accounts_test.go
Normal file
64
services/connector/commands/accounts_test.go
Normal 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])
|
||||
}
|
30
services/connector/commands/chain_id.go
Normal file
30
services/connector/commands/chain_id.go
Normal 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
|
||||
}
|
59
services/connector/commands/chain_id_test.go
Normal file
59
services/connector/commands/chain_id_test.go
Normal 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)
|
||||
}
|
165
services/connector/commands/client_handler.go
Normal file
165
services/connector/commands/client_handler.go
Normal 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
|
||||
}
|
28
services/connector/commands/client_handler_test.go
Normal file
28
services/connector/commands/client_handler_test.go
Normal 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)
|
||||
}
|
65
services/connector/commands/request_accounts.go
Normal file
65
services/connector/commands/request_accounts.go
Normal 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)
|
||||
}
|
153
services/connector/commands/request_accounts_test.go
Normal file
153
services/connector/commands/request_accounts_test.go
Normal 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)
|
||||
|
||||
}
|
83
services/connector/commands/rpc_traits.go
Normal file
83
services/connector/commands/rpc_traits.go
Normal 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
|
||||
}
|
81
services/connector/commands/send_transaction.go
Normal file
81
services/connector/commands/send_transaction.go
Normal 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
|
||||
}
|
175
services/connector/commands/send_transaction_test.go
Normal file
175
services/connector/commands/send_transaction_test.go
Normal 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)
|
||||
}
|
94
services/connector/commands/switch_ethereum_chain.go
Normal file
94
services/connector/commands/switch_ethereum_chain.go
Normal 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
|
||||
}
|
98
services/connector/commands/switch_ethereum_chain_test.go
Normal file
98
services/connector/commands/switch_ethereum_chain_test.go
Normal 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)
|
||||
}
|
88
services/connector/commands/test_helpers.go
Normal file
88
services/connector/commands/test_helpers.go
Normal 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
|
||||
}
|
112
services/connector/connector_flows_test.go
Normal file
112
services/connector/connector_flows_test.go
Normal 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)
|
||||
}
|
40
services/connector/database/persistence.go
Normal file
40
services/connector/database/persistence.go
Normal 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
|
||||
}
|
82
services/connector/database/persistence_test.go
Normal file
82
services/connector/database/persistence_test.go
Normal 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)
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package connector
|
||||
|
||||
type RPCCommand interface {
|
||||
Execute(inputJSON string) (string, error)
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
42
signal/events_connector.go
Normal file
42
signal/events_connector.go
Normal 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,
|
||||
})
|
||||
}
|
@ -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) {
|
||||
@ -832,79 +852,54 @@ 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,
|
||||
|
||||
"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,
|
||||
|
||||
"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,
|
||||
|
||||
"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
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user