feat(wallet) Wallet Connect Pair integration
Implement Controller to forward requests between status-go and SDK implementation in QML. Other changes: - Source Wallet Connect projectId from env vars - Mock controller in storybook Updates #12551
This commit is contained in:
parent
d4e15fe932
commit
bcf5b64298
|
@ -14,6 +14,8 @@ import ./overview/module as overview_module
|
|||
import ./send/module as send_module
|
||||
|
||||
import ./activity/controller as activityc
|
||||
import ./wallet_connect/controller as wcc
|
||||
|
||||
import app/modules/shared_modules/collectibles/controller as collectiblesc
|
||||
import app/modules/shared_modules/collectible_details/controller as collectible_detailsc
|
||||
|
||||
|
@ -81,6 +83,8 @@ type
|
|||
# instance to be used in temporary, short-lived, workflows (e.g. send popup)
|
||||
tmpActivityController: activityc.Controller
|
||||
|
||||
wcController: wcc.Controller
|
||||
|
||||
## Forward declaration
|
||||
proc onUpdatedKeypairsOperability*(self: Module, updatedKeypairs: seq[KeypairDto])
|
||||
proc onLocalPairingStatusUpdate*(self: Module, data: LocalPairingStatus)
|
||||
|
@ -137,7 +141,9 @@ proc newModule*(
|
|||
result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events)
|
||||
result.filter = initFilter(result.controller)
|
||||
|
||||
result.view = newView(result, result.activityController, result.tmpActivityController, result.collectiblesController, result.collectibleDetailsController)
|
||||
result.wcController = wcc.newController(events)
|
||||
|
||||
result.view = newView(result, result.activityController, result.tmpActivityController, result.collectiblesController, result.collectibleDetailsController, result.wcController)
|
||||
|
||||
method delete*(self: Module) =
|
||||
self.accountsModule.delete
|
||||
|
@ -152,6 +158,7 @@ method delete*(self: Module) =
|
|||
self.tmpActivityController.delete
|
||||
self.collectiblesController.delete
|
||||
self.collectibleDetailsController.delete
|
||||
self.wcController.delete
|
||||
|
||||
if not self.addAccountModule.isNil:
|
||||
self.addAccountModule.delete
|
||||
|
|
|
@ -5,6 +5,7 @@ import app/modules/shared_modules/collectibles/controller as collectiblesc
|
|||
import app/modules/shared_modules/collectible_details/controller as collectible_detailsc
|
||||
import ./io_interface
|
||||
import ../../shared_models/currency_amount
|
||||
import ./wallet_connect/controller as wcc
|
||||
|
||||
QtObject:
|
||||
type
|
||||
|
@ -21,6 +22,7 @@ QtObject:
|
|||
collectibleDetailsController: collectible_detailsc.Controller
|
||||
isNonArchivalNode: bool
|
||||
keypairOperabilityForObservedAccount: string
|
||||
wcController: wcc.Controller
|
||||
|
||||
proc setup(self: View) =
|
||||
self.QObject.setup
|
||||
|
@ -28,13 +30,15 @@ QtObject:
|
|||
proc delete*(self: View) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface, activityController: activityc.Controller, tmpActivityController: activityc.Controller, collectiblesController: collectiblesc.Controller, collectibleDetailsController: collectible_detailsc.Controller): View =
|
||||
proc newView*(delegate: io_interface.AccessInterface, activityController: activityc.Controller, tmpActivityController: activityc.Controller, collectiblesController: collectiblesc.Controller, collectibleDetailsController: collectible_detailsc.Controller, wcController: wcc.Controller): View =
|
||||
new(result, delete)
|
||||
result.delegate = delegate
|
||||
result.activityController = activityController
|
||||
result.tmpActivityController = tmpActivityController
|
||||
result.collectiblesController = collectiblesController
|
||||
result.collectibleDetailsController = collectibleDetailsController
|
||||
result.wcController = wcController
|
||||
|
||||
result.setup()
|
||||
|
||||
proc load*(self: View) =
|
||||
|
@ -203,3 +207,8 @@ QtObject:
|
|||
proc destroyKeypairImportPopup*(self: View) {.signal.}
|
||||
proc emitDestroyKeypairImportPopup*(self: View) =
|
||||
self.destroyKeypairImportPopup()
|
||||
|
||||
proc getWalletConnectController(self: View): QVariant {.slot.} =
|
||||
return newQVariant(self.wcController)
|
||||
QtProperty[QVariant] walletConnectController:
|
||||
read = getWalletConnectController
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import NimQml, logging, json
|
||||
|
||||
import backend/wallet_connect as backend
|
||||
|
||||
import app/core/eventemitter
|
||||
import app/core/signals/types
|
||||
|
||||
import constants
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Controller* = ref object of QObject
|
||||
events: EventEmitter
|
||||
|
||||
proc setup(self: Controller) =
|
||||
self.QObject.setup
|
||||
|
||||
proc delete*(self: Controller) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newController*(events: EventEmitter): Controller =
|
||||
new(result, delete)
|
||||
|
||||
result.events = events
|
||||
|
||||
result.setup()
|
||||
|
||||
# Register for wallet events
|
||||
result.events.on(SignalType.Wallet.event, proc(e: Args) =
|
||||
# TODO #12434: async processing
|
||||
discard
|
||||
)
|
||||
|
||||
# supportedNamespaces is a Namespace as defined in status-go: services/wallet/walletconnect/walletconnect.go
|
||||
proc proposeUserPair*(self: Controller, sessionProposalJson: string, supportedNamespacesJson: string) {.signal.}
|
||||
|
||||
proc pairSessionProposal(self: Controller, sessionProposalJson: string) {.slot.} =
|
||||
let ok = backend.pair(sessionProposalJson, proc (res: JsonNode) =
|
||||
let sessionProposalJson = if res.hasKey("sessionProposal"): $res["sessionProposal"] else: ""
|
||||
let supportedNamespacesJson = if res.hasKey("supportedNamespaces"): $res["supportedNamespaces"] else: ""
|
||||
|
||||
self.proposeUserPair(sessionProposalJson, supportedNamespacesJson)
|
||||
)
|
||||
|
||||
if not ok:
|
||||
error "Failed to pair session"
|
||||
|
||||
proc getProjectId*(self: Controller): string {.slot.} =
|
||||
return constants.WALLET_CONNECT_PROJECT_ID
|
||||
|
||||
QtProperty[string] projectId:
|
||||
read = getProjectId
|
|
@ -0,0 +1,26 @@
|
|||
import options
|
||||
import json
|
||||
import core, response_type
|
||||
|
||||
from gen import rpc
|
||||
import backend
|
||||
|
||||
# Declared in services/wallet/walletconnect/walletconnect.go
|
||||
#const eventWCTODO*: string = "wallet-wc-todo"
|
||||
|
||||
# Declared in services/wallet/walletconnect/walletconnect.go
|
||||
const ErrorChainsNotSupported*: string = "chains not supported"
|
||||
|
||||
rpc(wCPairSessionProposal, "wallet"):
|
||||
sessionProposalJson: string
|
||||
|
||||
# TODO #12434: async answer
|
||||
proc pair*(sessionProposalJson: string, callback: proc(response: JsonNode): void): bool =
|
||||
try:
|
||||
let response = wCPairSessionProposal(sessionProposalJson)
|
||||
if response.error == nil and response.result != nil:
|
||||
callback(response.result)
|
||||
return response.error == nil
|
||||
except Exception as e:
|
||||
echo "@dd wCPairSessionProposal response: ", e.msg
|
||||
return false
|
|
@ -53,4 +53,5 @@ let
|
|||
ALCHEMY_ARBITRUM_GOERLI_TOKEN_RESOLVED* = desktopConfig.alchemyArbitrumGoerliToken
|
||||
ALCHEMY_OPTIMISM_MAINNET_TOKEN_RESOLVED* = desktopConfig.alchemyOptimismMainnetToken
|
||||
ALCHEMY_OPTIMISM_GOERLI_TOKEN_RESOLVED* = desktopConfig.alchemyOptimismGoerliToken
|
||||
OPENSEA_API_KEY_RESOLVED* = desktopConfig.openseaApiKey
|
||||
OPENSEA_API_KEY_RESOLVED* = desktopConfig.openseaApiKey
|
||||
WALLET_CONNECT_PROJECT_ID* = BUILD_WALLET_CONNECT_PROJECT_ID
|
|
@ -20,6 +20,7 @@ const BASE_NAME_ALCHEMY_ARBITRUM_MAINNET_TOKEN = "ALCHEMY_ARBITRUM_MAINNET_TOKEN
|
|||
const BASE_NAME_ALCHEMY_ARBITRUM_GOERLI_TOKEN = "ALCHEMY_ARBITRUM_GOERLI_TOKEN"
|
||||
const BASE_NAME_ALCHEMY_OPTIMISM_MAINNET_TOKEN = "ALCHEMY_OPTIMISM_MAINNET_TOKEN"
|
||||
const BASE_NAME_ALCHEMY_OPTIMISM_GOERLI_TOKEN = "ALCHEMY_OPTIMISM_GOERLI_TOKEN"
|
||||
const BASE_NAME_WALLET_CONNECT_PROJECT_ID = "WALLET_CONNECT_PROJECT_ID"
|
||||
|
||||
|
||||
################################################################################
|
||||
|
@ -36,6 +37,9 @@ const BUILD_ALCHEMY_ARBITRUM_MAINNET_TOKEN = getEnv(BUILD_TIME_PREFIX & BASE_NAM
|
|||
const BUILD_ALCHEMY_ARBITRUM_GOERLI_TOKEN = getEnv(BUILD_TIME_PREFIX & BASE_NAME_ALCHEMY_ARBITRUM_GOERLI_TOKEN)
|
||||
const BUILD_ALCHEMY_OPTIMISM_MAINNET_TOKEN = getEnv(BUILD_TIME_PREFIX & BASE_NAME_ALCHEMY_OPTIMISM_MAINNET_TOKEN)
|
||||
const BUILD_ALCHEMY_OPTIMISM_GOERLI_TOKEN = getEnv(BUILD_TIME_PREFIX & BASE_NAME_ALCHEMY_OPTIMISM_GOERLI_TOKEN)
|
||||
const
|
||||
WALLET_CONNECT_STATUS_PROJECT_ID = "87815d72a81d739d2a7ce15c2cfdefb3"
|
||||
BUILD_WALLET_CONNECT_PROJECT_ID = getEnv(BUILD_TIME_PREFIX & BASE_NAME_WALLET_CONNECT_PROJECT_ID, WALLET_CONNECT_STATUS_PROJECT_ID)
|
||||
|
||||
################################################################################
|
||||
# Run time evaluated variables
|
||||
|
|
|
@ -19,6 +19,8 @@ import SortFilterProxyModel 0.2
|
|||
|
||||
import utils 1.0
|
||||
|
||||
import nim 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
@ -29,11 +31,17 @@ Item {
|
|||
WalletConnect {
|
||||
id: walletConnect
|
||||
|
||||
SplitView.preferredWidth: 400
|
||||
SplitView.fillWidth: true
|
||||
|
||||
projectId: SystemUtils.getEnvVar("WALLET_CONNECT_PROJECT_ID")
|
||||
backgroundColor: Theme.palette.statusAppLayout.backgroundColor
|
||||
|
||||
controller: WalletConnectController {
|
||||
pairSessionProposal: function(sessionProposalJson) {
|
||||
proposeUserPair(sessionProposalJson, `{"eip155":{"methods":["eth_sendTransaction","personal_sign"],"chains":["eip155:5"],"events":["accountsChanged","chainChanged"],"accounts":["eip155:5:0x53780d79E83876dAA21beB8AFa87fd64CC29990b","eip155:5:0xBd54A96c0Ae19a220C8E1234f54c940DFAB34639","eip155:5:0x5D7905390b77A937Ae8c444aA8BF7Fa9a6A7DBA0"]}}`)
|
||||
}
|
||||
projectId: SystemUtils.getEnvVar("STATUS_BUILD_WALLET_CONNECT_PROJECT_ID")
|
||||
}
|
||||
|
||||
clip: true
|
||||
}
|
||||
|
||||
|
@ -45,7 +53,8 @@ Item {
|
|||
|
||||
Text { text: "projectId" }
|
||||
Text {
|
||||
text: walletConnect.projectId.substring(0, 3) + "..." + walletConnect.projectId.substring(walletConnect.projectId.length - 3)
|
||||
readonly property string projectId: walletConnect.controller.projectId
|
||||
text: projectId.substring(0, 3) + "..." + projectId.substring(projectId.length - 3)
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
// Stub for Controller QObject defined in src/app/modules/main/wallet_section/wallet_connect/controller.nim
|
||||
Item {
|
||||
id: root
|
||||
|
||||
signal proposeUserPair(string sessionProposalJson, string supportedNamespacesJson)
|
||||
|
||||
// function pairSessionProposal(/*string*/ sessionProposalJson)
|
||||
required property var pairSessionProposal
|
||||
|
||||
required property string projectId
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
WalletConnectController 1.0 WalletConnectController.qml
|
|
@ -20,7 +20,7 @@ func loginToAccount(hashedPassword, userFolder, nodeConfigJson string) error {
|
|||
}
|
||||
accountsJson := statusgo.OpenAccounts(absUserFolder)
|
||||
accounts := make([]multiaccounts.Account, 0)
|
||||
err = getApiResponse(accountsJson, &accounts)
|
||||
err = getCAPIResponse(accountsJson, &accounts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ func loginToAccount(hashedPassword, userFolder, nodeConfigJson string) error {
|
|||
keystorePath := filepath.Join(filepath.Join(absUserFolder, "keystore/"), account.KeyUID)
|
||||
initKeystoreJson := statusgo.InitKeystore(keystorePath)
|
||||
apiResponse := statusgo.APIResponse{}
|
||||
err = getApiResponse(initKeystoreJson, &apiResponse)
|
||||
err = getCAPIResponse(initKeystoreJson, &apiResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func loginToAccount(hashedPassword, userFolder, nodeConfigJson string) error {
|
|||
return err
|
||||
}
|
||||
loginJson := statusgo.LoginWithConfig(string(accountJson), hashedPassword, nodeConfigJson)
|
||||
err = getApiResponse(loginJson, &apiResponse)
|
||||
err = getCAPIResponse(loginJson, &apiResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ type jsonrpcRequest struct {
|
|||
Params json.RawMessage `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
func callPrivateMethod(method string, params interface{}) string {
|
||||
func callPrivateMethod(method string, params []interface{}) string {
|
||||
var paramsJson json.RawMessage
|
||||
var err error
|
||||
if params != nil {
|
||||
|
@ -131,7 +131,7 @@ func processConfigArgs() (config *Config, nodeConfigJson string, userFolder stri
|
|||
return
|
||||
}
|
||||
|
||||
func getApiResponse[T any](responseJson string, res T) error {
|
||||
func getCAPIResponse[T any](responseJson string, res T) error {
|
||||
apiResponse := statusgo.APIResponse{}
|
||||
err := json.Unmarshal([]byte(responseJson), &apiResponse)
|
||||
if err == nil {
|
||||
|
@ -154,3 +154,48 @@ func getApiResponse[T any](responseJson string, res T) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
type jsonrpcSuccessfulResponse struct {
|
||||
jsonrpcMessage
|
||||
Result json.RawMessage `json:"result"`
|
||||
}
|
||||
|
||||
type jsonrpcErrorResponse struct {
|
||||
jsonrpcMessage
|
||||
Error jsonError `json:"error"`
|
||||
}
|
||||
|
||||
// jsonError represents Error message for JSON-RPC responses.
|
||||
type jsonError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func getRPCAPIResponse[T any](responseJson string, res T) error {
|
||||
errApiResponse := jsonrpcErrorResponse{}
|
||||
err := json.Unmarshal([]byte(responseJson), &errApiResponse)
|
||||
if err == nil && errApiResponse.Error.Code != 0 {
|
||||
return fmt.Errorf("API error: %#v", errApiResponse.Error)
|
||||
}
|
||||
|
||||
apiResponse := jsonrpcSuccessfulResponse{}
|
||||
err = json.Unmarshal([]byte(responseJson), &apiResponse)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal jsonrpcSuccessfulResponse: %w", err)
|
||||
}
|
||||
|
||||
typeOfT := reflect.TypeOf(res)
|
||||
kindOfT := typeOfT.Kind()
|
||||
|
||||
// Check for valid types: pointer, slice, map
|
||||
if kindOfT != reflect.Ptr && kindOfT != reflect.Slice && kindOfT != reflect.Map {
|
||||
return fmt.Errorf("type T must be a pointer, slice, or map")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(apiResponse.Result, &res); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
/>
|
||||
|
||||
<div id="buttonRow">
|
||||
<button id="pairButton" disabled>Pair</button>
|
||||
<!-- TODO DEV <button id="pairButton" disabled>Pair</button>-->
|
||||
<button id="pairButton">Pair</button>
|
||||
<button id="authButton" disabled>Auth</button>
|
||||
<button id="acceptButton" style="display: none">Accept</button>
|
||||
<button id="rejectButton" style="display: none">Reject</button>
|
||||
|
@ -75,64 +76,27 @@
|
|||
pairButton.addEventListener("click", function () {
|
||||
setStatus("Pairing...");
|
||||
try {
|
||||
// TODO DEV: remove harcode
|
||||
wc.pair(pairLinkInput.value)
|
||||
.then((sessionProposal) => {
|
||||
//let sessionProposal = JSON.parse(`{"id":1698771618724119,"params":{"id":1698771618724119,"pairingTopic":"4c36da43ac0d351336663276de6b5e2182f42068b7021e3cd1a460d9d7077e20","expiry":1698771923,"requiredNamespaces":{"eip155":{"methods":["eth_sendTransaction","personal_sign"],"chains":["eip155:5"],"events":["chainChanged","accountsChanged"]}},"optionalNamespaces":{"eip155":{"methods":["eth_signTransaction","eth_sign","eth_signTypedData","eth_signTypedData_v4"],"chains":["eip155:5"],"events":[]}},"relays":[{"protocol":"irn"}],"proposer":{"publicKey":"5d648503f2ec0e5a263578c347e490dcacb4e4359f59d66f12905bb91b9f182d","metadata":{"description":"React App for WalletConnect","url":"https://react-app.walletconnect.com","icons":["https://avatars.githubusercontent.com/u/37784886"],"name":"React App","verifyUrl":"https://verify.walletconnect.com"}}},"verifyContext":{"verified":{"verifyUrl":"https://verify.walletconnect.com","validation":"UNKNOWN","origin":"https://react-app.walletconnect.com"}}}`);
|
||||
setStatus(`Wait user pair`);
|
||||
setDetails(
|
||||
`Pair ID: ${sessionProposal.id} ; Topic: ${sessionProposal.params.pairingTopic}`
|
||||
);
|
||||
acceptButton.addEventListener("click", function () {
|
||||
window.wc.approveSession(sessionProposal).then(
|
||||
() => {
|
||||
goEcho(`Session ${sessionProposal.id} approved`);
|
||||
setStatus(
|
||||
`Session ${sessionProposal.id} approved`
|
||||
);
|
||||
acceptButton.style.display = "none";
|
||||
rejectButton.style.display = "none";
|
||||
},
|
||||
(err) => {
|
||||
goEcho(
|
||||
`Session ${sessionProposal.id} approve error: ${err.message}`
|
||||
);
|
||||
setStatus(
|
||||
`Session ${sessionProposal.id} approve error: ${err.message}`
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
rejectButton.addEventListener("click", function () {
|
||||
try {
|
||||
window.wc.rejectSession(sessionProposal.id).then(
|
||||
() => {
|
||||
goEcho(`Session ${sessionProposal.id} rejected`);
|
||||
setStatus(
|
||||
`Session ${sessionProposal.id} rejected`
|
||||
);
|
||||
acceptButton.style.display = "none";
|
||||
rejectButton.style.display = "none";
|
||||
},
|
||||
(err) => {
|
||||
goEcho(
|
||||
`Session ${sessionProposal.id} reject error`
|
||||
);
|
||||
setStatus(
|
||||
`Session ${sessionProposal.id} reject error`
|
||||
);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
goEcho(
|
||||
`Session ${sessionProposal.id} reject error: ${err.message}`
|
||||
);
|
||||
setStatus(
|
||||
`Session ${sessionProposal.id} reject error: ${err.message}`
|
||||
);
|
||||
}
|
||||
});
|
||||
acceptButton.style.display = "inline";
|
||||
rejectButton.style.display = "inline";
|
||||
window
|
||||
.pairSessionProposal(JSON.stringify(sessionProposal))
|
||||
.then((success) => {
|
||||
if (!success) {
|
||||
goEcho(
|
||||
`GO.pairSessionProposal call failed ${sessionProposal.id}`
|
||||
);
|
||||
setGoStatus(`GO.pairSessionProposal failed`, "red");
|
||||
return;
|
||||
}
|
||||
});
|
||||
// Waiting for "proposeUserPair" event
|
||||
})
|
||||
.catch((error) => {
|
||||
goEcho(`Pairing error ${JSON.stringify(error)}`);
|
||||
|
@ -149,9 +113,17 @@
|
|||
window.auth();
|
||||
});
|
||||
|
||||
window.wc.registerForSessionRequest(event => {
|
||||
window.wc.registerForSessionRequest((event) => {
|
||||
setStatus(`Session topic ${event.topic}`);
|
||||
})
|
||||
window.sessionRequest(event).then((success) => {
|
||||
if (!success) {
|
||||
goEcho(`Session request status-go call failed ${event.topic}`);
|
||||
setGoStatus(`Session ${event.id} rejected`, "purple");
|
||||
return;
|
||||
}
|
||||
// Waiting for userApproveSession event
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function goEcho(message) {
|
||||
|
@ -188,8 +160,65 @@
|
|||
setGoStatus("ready");
|
||||
statusGoReady();
|
||||
break;
|
||||
case "tokensAvailable":
|
||||
setDetails(`${JSON.stringify(event.payload)}\n`);
|
||||
case "proposeUserPair":
|
||||
setGoStatus("Session proposed");
|
||||
setDetails(JSON.stringify(event.payload.supportedNamespaces));
|
||||
let sessionProposal = event.payload.sessionProposal;
|
||||
acceptButton.addEventListener("click", function () {
|
||||
try {
|
||||
window.wc
|
||||
.approveSession(
|
||||
sessionProposal,
|
||||
event.payload.supportedNamespaces
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
goEcho(`Session ${sessionProposal.id} approved`);
|
||||
setStatus(`Session ${sessionProposal.id} approved`);
|
||||
acceptButton.style.display = "none";
|
||||
rejectButton.style.display = "none";
|
||||
},
|
||||
(err) => {
|
||||
goEcho(
|
||||
`Session ${sessionProposal.id} approve error: ${err}`
|
||||
);
|
||||
setStatus(
|
||||
`Session ${sessionProposal.id} approve error: ${err}`
|
||||
);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
goEcho(`WTF error: ${err.message}`);
|
||||
setStatus(`WTF error: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
rejectButton.addEventListener("click", function () {
|
||||
try {
|
||||
window.wc.rejectSession(sessionProposal.id).then(
|
||||
() => {
|
||||
goEcho(`Session ${sessionProposal.id} rejected`);
|
||||
setStatus(`Session ${sessionProposal.id} rejected`);
|
||||
acceptButton.style.display = "none";
|
||||
rejectButton.style.display = "none";
|
||||
},
|
||||
(err) => {
|
||||
goEcho(`Session ${sessionProposal.id} reject error`);
|
||||
setStatus(`Session ${sessionProposal.id} reject error`);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
goEcho(
|
||||
`Session ${sessionProposal.id} reject error: ${err.message}`
|
||||
);
|
||||
setStatus(
|
||||
`Session ${sessionProposal.id} reject error: ${err.message}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
acceptButton.style.display = "inline";
|
||||
rejectButton.style.display = "inline";
|
||||
break;
|
||||
default:
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
|
|
@ -6,9 +6,11 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
webview "github.com/webview/webview_go"
|
||||
|
||||
statusgo "github.com/status-im/status-go/mobile"
|
||||
wc "github.com/status-im/status-go/services/wallet/walletconnect"
|
||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||
"github.com/status-im/status-go/signal"
|
||||
)
|
||||
|
@ -22,8 +24,8 @@ type Configuration struct {
|
|||
}
|
||||
|
||||
type GoEvent struct {
|
||||
Name string `json:"name"`
|
||||
Payload string `json:"payload"`
|
||||
Name string `json:"name"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
var eventQueue chan GoEvent = make(chan GoEvent, 10000)
|
||||
|
@ -44,10 +46,25 @@ func signalHandler(jsonEvent string) {
|
|||
|
||||
if envelope.Type == signal.EventNodeReady {
|
||||
eventQueue <- GoEvent{Name: "nodeReady", Payload: ""}
|
||||
} else if envelope.Type == "wallet" {
|
||||
// parse envelope.Event to json
|
||||
walletEvent := walletevent.Event{}
|
||||
err := json.Unmarshal([]byte(jsonEvent), &walletEvent)
|
||||
if err != nil {
|
||||
fmt.Println("@dd Error parsing the wallet event: ", err)
|
||||
return
|
||||
}
|
||||
// TODO: continue from here
|
||||
if walletEvent.Type == "WalletConnectProposeUserPair" {
|
||||
eventQueue <- GoEvent{Name: "proposeUserPair", Payload: walletEvent.Message}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Setup status-go logger
|
||||
log.Root().SetHandler(log.StdoutHandler)
|
||||
|
||||
signal.SetDefaultNodeNotificationHandler(signalHandler)
|
||||
config, nodeConfigJson, userFolder, err := processConfigArgs()
|
||||
if err != nil {
|
||||
|
@ -64,10 +81,26 @@ func main() {
|
|||
w := webview.New(true)
|
||||
defer w.Destroy()
|
||||
w.SetTitle("WC status-go test")
|
||||
w.SetSize(480, 320, webview.HintNone)
|
||||
w.SetSize(620, 480, webview.HintNone)
|
||||
|
||||
w.Bind("pairSessionProposal", func(sessionProposalJson string) bool {
|
||||
sessionReqRes := callPrivateMethod("wallet_wCPairSessionProposal", []interface{}{sessionProposalJson})
|
||||
var apiResponse wc.PairSessionResponse
|
||||
err = getRPCAPIResponse(sessionReqRes, &apiResponse)
|
||||
if err != nil {
|
||||
log.Error("Error parsing the API response", "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
go func() {
|
||||
eventQueue <- GoEvent{Name: "proposeUserPair", Payload: apiResponse}
|
||||
}()
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
w.Bind("getConfiguration", func() Configuration {
|
||||
projectID := os.Getenv("WALLET_CONNECT_PROJECT_ID")
|
||||
projectID := os.Getenv("STATUS_BUILD_WALLET_CONNECT_PROJECT_ID")
|
||||
return Configuration{ProjectId: projectID}
|
||||
})
|
||||
|
||||
|
@ -88,11 +121,13 @@ func main() {
|
|||
|
||||
// Start a local server to serve the files
|
||||
http.HandleFunc("/bundle.js", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
|
||||
http.ServeFile(w, r, "../../../ui/app/AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js")
|
||||
})
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "index.html")
|
||||
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
|
||||
http.ServeFile(w, r, "./index.html")
|
||||
})
|
||||
|
||||
go http.ListenAndServe(":8080", nil)
|
||||
|
|
|
@ -24,6 +24,11 @@ import "../controls"
|
|||
import "../popups"
|
||||
import "../panels"
|
||||
|
||||
// TODO: remove DEV import
|
||||
import AppLayouts.Wallet.stores 1.0 as WalletStores
|
||||
import AppLayouts.Wallet.views.walletconnect 1.0
|
||||
// TODO end
|
||||
|
||||
SettingsContentBase {
|
||||
id: root
|
||||
|
||||
|
@ -151,6 +156,55 @@ SettingsContentBase {
|
|||
}
|
||||
}
|
||||
|
||||
StatusSettingsLineButton {
|
||||
anchors.leftMargin: 0
|
||||
anchors.rightMargin: 0
|
||||
text: qsTr("Debug Wallet Connect")
|
||||
visible: root.advancedStore.isDebugEnabled
|
||||
|
||||
onClicked: {
|
||||
wcLoader.active = true
|
||||
}
|
||||
|
||||
Component {
|
||||
id: wcDialogComponent
|
||||
StatusDialog {
|
||||
id: wcDialog
|
||||
|
||||
onOpenedChanged: {
|
||||
if (!opened) {
|
||||
wcLoader.active = false
|
||||
}
|
||||
}
|
||||
|
||||
WalletConnect {
|
||||
SplitView.preferredWidth: 400
|
||||
SplitView.preferredHeight: 600
|
||||
|
||||
backgroundColor: wcDialog.backgroundColor
|
||||
|
||||
controller: WalletStores.RootStore.walletConnectController
|
||||
}
|
||||
|
||||
clip: true
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: wcLoader
|
||||
|
||||
active: false
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === Loader.Ready) {
|
||||
wcLoader.item.open()
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: wcDialogComponent
|
||||
}
|
||||
}
|
||||
|
||||
Separator {
|
||||
width: parent.width
|
||||
}
|
||||
|
|
|
@ -27,12 +27,14 @@ QtObject {
|
|||
property var accountSensitiveSettings: localAccountSensitiveSettings
|
||||
property bool hideSignPhraseModal: accountSensitiveSettings.hideSignPhraseModal
|
||||
|
||||
// "walletSection" is a context property slow to lookup, so we cache it here
|
||||
property var walletSectionInst: walletSection
|
||||
property var totalCurrencyBalance: walletSection.totalCurrencyBalance
|
||||
property var activityController: walletSection.activityController
|
||||
property var tmpActivityController: walletSection.tmpActivityController
|
||||
property string signingPhrase: walletSection.signingPhrase
|
||||
property string mnemonicBackedUp: walletSection.isMnemonicBackedUp
|
||||
property var totalCurrencyBalance: walletSectionInst.totalCurrencyBalance
|
||||
property var activityController: walletSectionInst.activityController
|
||||
property var tmpActivityController: walletSectionInst.tmpActivityController
|
||||
property string signingPhrase: walletSectionInst.signingPhrase
|
||||
property string mnemonicBackedUp: walletSectionInst.isMnemonicBackedUp
|
||||
property var walletConnectController: walletSectionInst.walletConnectController
|
||||
|
||||
property CollectiblesStore collectiblesStore: CollectiblesStore {}
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
QtObject {
|
||||
enum RequestCodes {
|
||||
SdkInitSuccess,
|
||||
SdkInitError,
|
||||
|
||||
PairSuccess,
|
||||
PairError,
|
||||
|
||||
ApprovePairSuccess,
|
||||
ApprovePairError,
|
||||
|
||||
RejectPairSuccess,
|
||||
RejectPairError
|
||||
}
|
||||
}
|
|
@ -13,11 +13,10 @@ Item {
|
|||
implicitWidth: Math.min(mainLayout.implicitWidth, 400)
|
||||
implicitHeight: Math.min(mainLayout.implicitHeight, 700)
|
||||
|
||||
|
||||
required property string projectId
|
||||
required property color backgroundColor
|
||||
|
||||
property alias optionalSdkPath: sdkView.optionalSdkPath
|
||||
// wallet_connect.Controller \see wallet_section/wallet_connect/controller.nim
|
||||
required property var controller
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
|
@ -45,7 +44,7 @@ Item {
|
|||
statusText.text = "Pairing..."
|
||||
sdkView.pair(pairLinkInput.text)
|
||||
}
|
||||
enabled: pairLinkInput.text.length > 0 && sdkView.state === sdkView.disconnectedState
|
||||
enabled: pairLinkInput.text.length > 0 && sdkView.sdkReady
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
|
@ -54,30 +53,50 @@ Item {
|
|||
statusText.text = "Authenticating..."
|
||||
sdkView.auth()
|
||||
}
|
||||
enabled: false && pairLinkInput.text.length > 0 && sdkView.state === sdkView.disconnectedState
|
||||
enabled: false && pairLinkInput.text.length > 0 && sdkView.sdkReady
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
text: "Accept"
|
||||
onClicked: {
|
||||
sdkView.acceptPairing()
|
||||
sdkView.approvePairSession(d.sessionProposal, d.supportedNamespaces)
|
||||
}
|
||||
visible: sdkView.state === sdkView.waitingPairState
|
||||
visible: root.state === d.waitingPairState
|
||||
}
|
||||
StatusButton {
|
||||
text: "Reject"
|
||||
onClicked: {
|
||||
sdkView.rejectPairing()
|
||||
sdkView.rejectPairSession(d.sessionProposal.id)
|
||||
}
|
||||
visible: sdkView.state === sdkView.waitingPairState
|
||||
visible: root.state === d.waitingPairState
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
StatusBaseText {
|
||||
id: statusText
|
||||
text: "-"
|
||||
}
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 200
|
||||
Layout.maximumHeight: 400
|
||||
|
||||
contentWidth: detailsText.width
|
||||
contentHeight: detailsText.height
|
||||
|
||||
StatusBaseText {
|
||||
id: detailsText
|
||||
text: ""
|
||||
visible: text.length > 0
|
||||
|
||||
color: "#FF00FF"
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
clip: true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: DEBUG JS Loading in DMG
|
||||
|
@ -126,16 +145,104 @@ Item {
|
|||
WalletConnectSDK {
|
||||
id: sdkView
|
||||
|
||||
projectId: root.projectId
|
||||
projectId: controller.projectId
|
||||
backgroundColor: root.backgroundColor
|
||||
|
||||
Layout.fillWidth: true
|
||||
// Note that a too smaller height might cause the webview to generate rendering errors
|
||||
Layout.preferredHeight: 10
|
||||
|
||||
onSdkInit: function(success, info) {
|
||||
d.setDetailsText(info)
|
||||
if (success) {
|
||||
d.setStatusText("Ready to pair or auth")
|
||||
root.state = d.sdkReadyState
|
||||
} else {
|
||||
d.setStatusText("SDK Error", "red")
|
||||
root.state = ""
|
||||
}
|
||||
}
|
||||
|
||||
onPairSessionProposal: function(success, sessionProposal) {
|
||||
d.setDetailsText(sessionProposal)
|
||||
if (success) {
|
||||
d.setStatusText("Pair ID: " + sessionProposal.id + "; Topic: " + sessionProposal.params.pairingTopic)
|
||||
root.controller.pairSessionProposal(JSON.stringify(sessionProposal))
|
||||
// Expecting signal onProposeUserPair from controller
|
||||
} else {
|
||||
d.setStatusText("Pairing error", "red")
|
||||
}
|
||||
}
|
||||
|
||||
onPairAcceptedResult: function(success, result) {
|
||||
d.setDetailsText(result)
|
||||
if (success) {
|
||||
d.setStatusText("Pairing OK")
|
||||
root.state = d.pairedState
|
||||
} else {
|
||||
d.setStatusText("Pairing error", "red")
|
||||
root.state = d.sdkReadyState
|
||||
}
|
||||
}
|
||||
|
||||
onPairRejectedResult: function(success, result) {
|
||||
d.setDetailsText(result)
|
||||
root.state = d.sdkReadyState
|
||||
if (success) {
|
||||
d.setStatusText("Pairing rejected")
|
||||
} else {
|
||||
d.setStatusText("Rejecting pairing error", "red")
|
||||
}
|
||||
}
|
||||
|
||||
onStatusChanged: function(message) {
|
||||
statusText.text = message
|
||||
}
|
||||
|
||||
onResponseTimeout: {
|
||||
d.setStatusText(`Timeout waiting for response. Reusing URI?`, "red")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property var sessionProposal: null
|
||||
property var supportedNamespaces: null
|
||||
|
||||
readonly property string sdkReadyState: "sdk_ready"
|
||||
readonly property string waitingPairState: "waiting_pairing"
|
||||
readonly property string pairedState: "paired"
|
||||
|
||||
function setStatusText(message, textColor) {
|
||||
statusText.text = message
|
||||
if (textColor === undefined) {
|
||||
textColor = "green"
|
||||
}
|
||||
statusText.color = textColor
|
||||
}
|
||||
function setDetailsText(message) {
|
||||
if (message === undefined) {
|
||||
message = "undefined"
|
||||
} else if (typeof message !== "string") {
|
||||
message = JSON.stringify(message, null, 2)
|
||||
}
|
||||
detailsText.text = message
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.controller
|
||||
function onProposeUserPair(sessionProposalJson, supportedNamespacesJson) {
|
||||
d.setStatusText("Waiting user accept")
|
||||
|
||||
d.sessionProposal = JSON.parse(sessionProposalJson)
|
||||
d.supportedNamespaces = JSON.parse(supportedNamespacesJson)
|
||||
|
||||
d.setDetailsText(JSON.stringify(d.supportedNamespaces, null, 2))
|
||||
|
||||
root.state = d.waitingPairState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,30 +22,32 @@ WebView {
|
|||
required property string projectId
|
||||
required property color backgroundColor
|
||||
|
||||
readonly property string notReadyState: "not-ready"
|
||||
readonly property string disconnectedState: "disconnected"
|
||||
readonly property string waitingPairState: "waiting_pairing"
|
||||
readonly property string pairedState: "paired"
|
||||
readonly property alias sdkReady: d.sdkReady
|
||||
|
||||
state: root.notReadyState
|
||||
|
||||
property string optionalSdkPath: ""
|
||||
signal sdkInit(bool success, var result)
|
||||
signal pairSessionProposal(bool success, var sessionProposal)
|
||||
signal pairAcceptedResult(bool success, var sessionType)
|
||||
signal pairRejectedResult(bool success, var result)
|
||||
signal responseTimeout()
|
||||
|
||||
// TODO: proper report
|
||||
signal statusChanged(string message)
|
||||
|
||||
function pair(pairLink) {
|
||||
d.requestSdk(
|
||||
"wcResult = {error: null}; try { wc.pair(\"" + pairLink + "\").then((sessionProposal) => { wcResult = {state: \"" + root.waitingPairState + "\", error: null, sessionProposal: sessionProposal}; }).catch((error) => { wcResult = {error: error}; }); } catch (e) { wcResult = {error: \"Exception: \" + e.message}; }; wcResult"
|
||||
)
|
||||
let callStr = d.generateSdkCall("pair", `"${pairLink}"`, RequestCodes.PairSuccess, RequestCodes.PairError)
|
||||
d.requestSdk(callStr)
|
||||
}
|
||||
|
||||
function acceptPairing() {
|
||||
d.acceptPair(d.sessionProposal)
|
||||
function approvePairSession(sessionProposal, supportedNamespaces) {
|
||||
let callStr = d.generateSdkCall("approvePairSession", `${JSON.stringify(sessionProposal)}, ${JSON.stringify(supportedNamespaces)}`, RequestCodes.ApprovePairSuccess, RequestCodes.ApprovePairSuccess)
|
||||
|
||||
d.requestSdk(callStr)
|
||||
}
|
||||
|
||||
function rejectPairing() {
|
||||
d.rejectPair(d.sessionProposal.id)
|
||||
function rejectPairSession(id) {
|
||||
let callStr = d.generateSdkCall("rejectPairSession", id, RequestCodes.RejectPairSuccess, RequestCodes.RejectPairError)
|
||||
|
||||
d.requestSdk(callStr)
|
||||
}
|
||||
|
||||
// TODO #12434: remove debugging WebEngineView code
|
||||
|
@ -101,47 +103,55 @@ WebView {
|
|||
onTriggered: {
|
||||
root.runJavaScript(
|
||||
"wcResult",
|
||||
function(result) {
|
||||
if (!result) {
|
||||
function(wcResult) {
|
||||
if (!wcResult) {
|
||||
return
|
||||
}
|
||||
|
||||
let done = false
|
||||
if (result.error) {
|
||||
if (wcResult.error) {
|
||||
console.debug(`@dd wcResult - ${JSON.stringify(wcResult)}`)
|
||||
done = true
|
||||
if (root.state === root.notReadyState) {
|
||||
root.statusChanged(`<font color="red">[${timer.errorCount++}] Failed SDK init; error: ${result.error}</font>`)
|
||||
if (!d.sdkReady) {
|
||||
root.statusChanged(`<font color="red">[${timer.errorCount++}] Failed SDK init; error: ${wcResult.error}</font>`)
|
||||
} else {
|
||||
root.state = root.disconnectedState
|
||||
root.statusChanged(`<font color="red">[${timer.errorCount++}] Operation error: ${result.error}</font>`)
|
||||
root.statusChanged(`<font color="red">[${timer.errorCount++}] Operation error: ${wcResult.error}</font>`)
|
||||
}
|
||||
} else if (result.state) {
|
||||
switch (result.state) {
|
||||
case root.disconnectedState: {
|
||||
root.statusChanged(`<font color="green">Ready to pair or auth</font>`)
|
||||
}
|
||||
|
||||
if (wcResult.state !== undefined) {
|
||||
switch (wcResult.state) {
|
||||
case RequestCodes.SdkInitSuccess:
|
||||
d.sdkReady = true
|
||||
root.sdkInit(true, "")
|
||||
break
|
||||
}
|
||||
case root.waitingPairState: {
|
||||
d.sessionProposal = result.sessionProposal
|
||||
root.statusChanged("Pair ID: " + result.sessionProposal.id + "; Topic: " + result.sessionProposal.params.pairingTopic)
|
||||
case RequestCodes.SdkInitError:
|
||||
d.sdkReady = false
|
||||
root.sdkInit(false, wcResult.error)
|
||||
break
|
||||
}
|
||||
case root.pairedState: {
|
||||
d.sessionType = result.sessionType
|
||||
root.statusChanged(`<font color="blue">Paired: ${JSON.stringify(result.sessionType)}</font>`)
|
||||
case RequestCodes.PairSuccess:
|
||||
root.pairSessionProposal(true, wcResult.result)
|
||||
break
|
||||
}
|
||||
case root.disconnectedState: {
|
||||
root.statusChanged(`<font color="orange">User rejected PairID ${d.sessionProposal.id}</font>`)
|
||||
case RequestCodes.PairError:
|
||||
root.pairSessionProposal(false, wcResult.error)
|
||||
break
|
||||
case RequestCodes.ApprovePairSuccess:
|
||||
root.pairAcceptedResult(true, "")
|
||||
break
|
||||
case RequestCodes.ApprovePairError:
|
||||
root.pairAcceptedResult(false, wcResult.error)
|
||||
break
|
||||
case RequestCodes.RejectPairSuccess:
|
||||
root.pairRejectedResult(true, "")
|
||||
break
|
||||
case RequestCodes.RejectPairError:
|
||||
root.pairRejectedResult(false, wcResult.error)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
root.statusChanged(`<font color="red">[${timer.errorCount++}] Unknown state: ${result.state}</font>`)
|
||||
result.state = root.disconnectedState
|
||||
root.statusChanged(`<font color="red">[${timer.errorCount++}] Unknown state: ${wcResult.state}</font>`)
|
||||
}
|
||||
}
|
||||
|
||||
root.state = result.state
|
||||
done = true
|
||||
}
|
||||
|
||||
|
@ -162,8 +172,7 @@ WebView {
|
|||
|
||||
onTriggered: {
|
||||
timer.stop()
|
||||
root.state = root.disconnectedState
|
||||
root.statusChanged(`<font color="red">Timeout waiting for response. The pairing might have been already attempted for the URI.</font>`)
|
||||
root.responseTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,37 +181,27 @@ WebView {
|
|||
|
||||
property var sessionProposal: null
|
||||
property var sessionType: null
|
||||
property bool sdkReady: false
|
||||
|
||||
function isWaitingForSdk() {
|
||||
return timer.running
|
||||
}
|
||||
|
||||
|
||||
function generateSdkCall(methodName, paramsStr, successState, errorState) {
|
||||
return "wcResult = {error: null}; try { wc." + methodName + "(" + paramsStr + ").then((callRes) => { wcResult = {state: " + successState + ", error: null, result: callRes}; }).catch((error) => { wcResult = {state: " + errorState + ", error: error}; }); } catch (e) { wcResult = {state: " + errorState + ", error: \"Exception: \" + e.message}; }; wcResult"
|
||||
}
|
||||
function requestSdk(jsCode) {
|
||||
console.debug(`@dd WalletConnectSDK.requestSdk; jsCode: ${jsCode}`)
|
||||
root.runJavaScript(jsCode,
|
||||
function(result) {
|
||||
console.debug(`@dd WalletConnectSDK.requestSdk; result: ${JSON.stringify(result)}`)
|
||||
timer.restart()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function init(projectId) {
|
||||
d.requestSdk(
|
||||
"wcResult = {error: null}; try { wc.init(\"" + projectId + "\").then((wc) => { wcResult = {state: \"" + root.disconnectedState + "\", error: null}; }).catch((error) => { wcResult = {error: error}; }); } catch (e) { wcResult = {error: \"Exception: \" + e.message}; }; wcResult"
|
||||
)
|
||||
}
|
||||
|
||||
function acceptPair(sessionProposal) {
|
||||
d.requestSdk(
|
||||
"wcResult = {error: null}; try { wc.approveSession(" + JSON.stringify(sessionProposal) + ").then((sessionType) => { wcResult = {state: \"" + root.pairedState + "\", error: null, sessionType: sessionType}; }).catch((error) => { wcResult = {error: error}; }); } catch (e) { wcResult = {error: \"Exception: \" + e.message}; }; wcResult"
|
||||
)
|
||||
}
|
||||
|
||||
function rejectPair(id) {
|
||||
d.requestSdk(
|
||||
"wcResult = {error: null}; try { wc.rejectSession(" + JSON.stringify(id) + ").then(() => { wcResult = {state: \"" + root.disconnectedState + "\", error: null}; }).catch((error) => { wcResult = {error: error}; }); } catch (e) { wcResult = {error: \"Exception: \" + e.message}; }; wcResult"
|
||||
)
|
||||
console.debug(`@dd WC projectId - ${projectId}`)
|
||||
d.requestSdk(generateSdkCall("init", `"${projectId}"`, RequestCodes.SdkInitSuccess, RequestCodes.SdkInitError))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,25 @@
|
|||
# Wallet Connect Integration
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] test namespaces implementation https://se-sdk-dapp.vercel.app/
|
||||
|
||||
Design questions
|
||||
|
||||
- [ ] Do we report all chains and all accounts combination or let user select?
|
||||
- Wallet Connect require to report all chainIDs that were requested
|
||||
- Show error to user workflow.
|
||||
- [ ] Can't respond to sign messages if the wallet-connect dialog/view is closed (app is minimized)
|
||||
- Only apps that use deep links are expected to work seamlessly
|
||||
- [ ] Do we report **disabled chains**? **Update session** in case of enabled/disabled chains?
|
||||
- [ ] Allow user to **disconnect session**?
|
||||
- [ ] Support update session if one account is added/removed?
|
||||
- [ ] User awareness of session expiration?
|
||||
- Support extend session?
|
||||
- [ ] User error workflow: retry?
|
||||
- [ ] Check the `Auth` request for verifyContext <https://docs.walletconnect.com/web3wallet/verify>
|
||||
- [ ] What `description` and `icons` to use for the app? See `metadata` parameter in `Web3Wallet.init` call
|
||||
|
||||
## WalletConnect SDK management
|
||||
|
||||
Install dependencies steps by executing commands in this directory:
|
||||
|
@ -17,9 +37,6 @@ Install dependencies steps by executing commands in this directory:
|
|||
|
||||
Use the web demo test client https://react-app.walletconnect.com/ for wallet pairing and https://react-auth-dapp.walletconnect.com/ for authentication
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] test namespaces implementation https://se-sdk-dapp.vercel.app/
|
||||
|
||||
## Log
|
||||
|
||||
|
@ -37,6 +54,7 @@ npm run build
|
|||
To test SDK loading add the following to `ui/app/mainui/AppMain.qml`
|
||||
|
||||
```qml
|
||||
import AppLayouts.Wallet.stores 1.0 as WalletStores
|
||||
import AppLayouts.Wallet.views.walletconnect 1.0
|
||||
|
||||
// ...
|
||||
|
@ -49,8 +67,10 @@ StatusDialog {
|
|||
SplitView.preferredWidth: 400
|
||||
SplitView.preferredHeight: 600
|
||||
|
||||
projectId: "<Project ID>"
|
||||
backgroundColor: wcHelperDialog.backgroundColor
|
||||
|
||||
projectId: "<Project ID>"
|
||||
controller: WalletStores.RootStore.wcController
|
||||
}
|
||||
|
||||
clip: true
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -22,10 +22,9 @@ window.wc = {
|
|||
window.wc.web3wallet = await Web3Wallet.init({
|
||||
core: window.wc.core, // <- pass the shared `core` instance
|
||||
metadata: {
|
||||
// TODO: what values should be here?
|
||||
name: "Prototype",
|
||||
description: "Prototype Wallet/Peer",
|
||||
url: "https://github.com/status-im/status-desktop",
|
||||
name: "Status",
|
||||
description: "Status Wallet",
|
||||
url: "https://status.app",
|
||||
icons: ['https://status.im/img/status-footer-logo.svg'],
|
||||
},
|
||||
});
|
||||
|
@ -35,7 +34,7 @@ window.wc = {
|
|||
metadata: window.wc.web3wallet.metadata,
|
||||
})
|
||||
|
||||
resolve(window.wc)
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -45,10 +44,6 @@ window.wc = {
|
|||
pair: function (uri) {
|
||||
let pairingTopic = getPairingTopicFromPairingUrl(uri);
|
||||
|
||||
let pairPromise = window.wc.web3wallet
|
||||
.pair({ uri: uri })
|
||||
.catch((error) => console.error(error));
|
||||
|
||||
const pairings = window.wc.core.pairing.getPairings();
|
||||
// Find pairing by topic
|
||||
const pairing = pairings.find((p) => p.topic === pairingTopic);
|
||||
|
@ -60,6 +55,10 @@ window.wc = {
|
|||
}
|
||||
}
|
||||
|
||||
let pairPromise = window.wc.web3wallet
|
||||
.pair({ uri: uri })
|
||||
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
pairPromise
|
||||
.then(() => {
|
||||
|
@ -77,36 +76,24 @@ window.wc = {
|
|||
window.wc.web3wallet.on("session_request", callback);
|
||||
},
|
||||
|
||||
// TODO: ensure if session requests only one account we don't provide all accounts
|
||||
approveSession: function (sessionProposal) {
|
||||
registerForSessionDelete: function (callback) {
|
||||
window.wc.web3wallet.on("session_delete", callback);
|
||||
},
|
||||
|
||||
approvePairSession: function (sessionProposal, supportedNamespaces) {
|
||||
const { id, params } = sessionProposal;
|
||||
|
||||
// ------- namespaces builder util ------------ //
|
||||
const approvedNamespaces = buildApprovedNamespaces({
|
||||
proposal: params,
|
||||
// TODO: source this from wallet
|
||||
supportedNamespaces: {
|
||||
eip155: {
|
||||
chains: ["eip155:1", "eip155:5"],
|
||||
methods: ["eth_sendTransaction", "personal_sign"],
|
||||
events: ["accountsChanged", "chainChanged"],
|
||||
accounts: [
|
||||
"eip155:1:0x0000000000000000000000000000000000000001",
|
||||
"eip155:5:0xe74E17D586227691Cb7b64ed78b1b7B14828B034",
|
||||
],
|
||||
},
|
||||
},
|
||||
supportedNamespaces: supportedNamespaces,
|
||||
});
|
||||
// ------- end namespaces builder util ------------ //
|
||||
|
||||
const session = window.wc.web3wallet.approveSession({
|
||||
return window.wc.web3wallet.approveSession({
|
||||
id,
|
||||
namespaces: approvedNamespaces,
|
||||
});
|
||||
|
||||
return session;
|
||||
},
|
||||
rejectSession: function (id) {
|
||||
rejectPairSession: function (id) {
|
||||
return window.wc.web3wallet.rejectSession({
|
||||
id: id,
|
||||
reason: getSdkError("USER_REJECTED"), // TODO USER_REJECTED_METHODS, USER_REJECTED_CHAINS, USER_REJECTED_EVENTS
|
||||
|
@ -116,10 +103,6 @@ window.wc = {
|
|||
auth: function (uri) {
|
||||
let pairingTopic = getPairingTopicFromPairingUrl(uri);
|
||||
|
||||
let pairPromise = window.wc.authClient.core.pairing
|
||||
.pair({ uri })
|
||||
.catch((error) => console.error(error));
|
||||
|
||||
const pairings = window.wc.core.pairing.getPairings();
|
||||
// Find pairing by topic
|
||||
const pairing = pairings.find((p) => p.topic === pairingTopic);
|
||||
|
@ -131,6 +114,9 @@ window.wc = {
|
|||
}
|
||||
}
|
||||
|
||||
let pairPromise = window.wc.authClient.core.pairing
|
||||
.pair({ uri })
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
pairPromise
|
||||
.then(() => {
|
||||
|
|
Loading…
Reference in New Issue