status-desktop/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml
Stefan 758dbc55e5 feat(dapps) implement signing of messages
Implement infrastructure and integration with status-go to support
general session requests

Supported methods:
- personal_sign
- eth_signTypedData_v4

depends on status-go change that exposes the signing methods

Also

- support hex or utf8 encoding for personal_sign
- format the typed data for display in the modal

Tests are disabled for now, as they are crashing on CI

Close: #14927
2024-06-17 15:06:26 +02:00

180 lines
5.7 KiB
QML

import QtQuick 2.15
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import AppLayouts.Wallet.services.dapps 1.0
import AppLayouts.Profile.stores 1.0
import shared.stores 1.0
import shared.popups.walletconnect 1.0
import SortFilterProxyModel 0.2
import utils 1.0
import "types"
QObject {
id: root
required property WalletConnectSDKBase wcSDK
required property DAppsStore store
required property WalletStore walletStore
readonly property alias dappsModel: dappsProvider.dappsModel
readonly property alias requestHandler: requestHandler
readonly property var validAccounts: SortFilterProxyModel {
sourceModel: root.walletStore ? root.walletStore.accounts : null
filters: ValueFilter {
roleName: "walletType"
value: Constants.watchWalletType
inverted: true
}
}
readonly property var flatNetworks: root.walletStore ? root.walletStore.flatNetworks : null
function pair(uri) {
d.acceptedSessionProposal = null
wcSDK.pair(uri)
}
function approvePairSession(sessionProposal, approvedChainIds, approvedAccount) {
d.acceptedSessionProposal = sessionProposal
let approvedNamespaces = JSON.parse(
Helpers.buildSupportedNamespaces(approvedChainIds,
[approvedAccount.address],
requestHandler.getSupportedMethods())
)
wcSDK.buildApprovedNamespaces(sessionProposal.params, approvedNamespaces)
}
function rejectPairSession(id) {
wcSDK.rejectSession(id)
}
function disconnectDapp(sessionTopic) {
wcSDK.disconnectSession(sessionTopic)
}
signal connectDApp(var dappChains, var sessionProposal, var approvedNamespaces)
signal approveSessionResult(var session, var error)
signal sessionRequest(SessionRequestResolved request)
signal displayToastMessage(string message, bool error)
readonly property Connections sdkConnections: Connections {
target: wcSDK
function onSessionProposal(sessionProposal) {
d.currentSessionProposal = sessionProposal
let supportedNamespacesStr = Helpers.buildSupportedNamespacesFromModels(
root.flatNetworks, root.validAccounts, requestHandler.getSupportedMethods())
wcSDK.buildApprovedNamespaces(sessionProposal.params, JSON.parse(supportedNamespacesStr))
}
function onBuildApprovedNamespacesResult(approvedNamespaces, error) {
if(error) {
// TODO: error reporting
return
}
if (d.acceptedSessionProposal) {
wcSDK.approveSession(d.acceptedSessionProposal, approvedNamespaces)
} else {
let res = Helpers.extractChainsAndAccountsFromApprovedNamespaces(approvedNamespaces)
root.connectDApp(res.chains, d.currentSessionProposal, approvedNamespaces)
}
}
function onApproveSessionResult(session, err) {
if (err) {
// TODO #14676: handle the error
console.error("Failed to approve session", err)
return
}
// TODO #14754: implement custom dApp notification
let app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
root.displayToastMessage(qsTr("Connected to %1 via WalletConnect").arg(app_url), false)
// Persist session
if(!store.addWalletConnectSession(JSON.stringify(session))) {
console.error("Failed to persist session")
}
// Notify client
root.approveSessionResult(session, err)
dappsProvider.updateDapps()
}
function onRejectSessionResult(err) {
let app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
if(err) {
root.displayToastMessage(qsTr("Failed to reject connection request for %1").arg(app_url), true)
} else {
root.displayToastMessage(qsTr("Connection request for %1 was rejected").arg(app_url), false)
}
}
function onSessionDelete(topic, err) {
let app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
if(err) {
root.displayToastMessage(qsTr("Failed to disconnect from %1").arg(app_url), true)
} else {
root.displayToastMessage(qsTr("Disconnected from %1").arg(app_url), false)
}
}
}
QObject {
id: d
property var currentSessionProposal: null
property var acceptedSessionProposal: null
// TODO #14676: use it to check if already paired
function getPairingTopicFromPairingUrl(url)
{
if (!url.startsWith("wc:"))
{
return null;
}
const atIndex = url.indexOf("@");
if (atIndex < 0)
{
return null;
}
return url.slice(3, atIndex);
}
}
Component.onCompleted: {
dappsProvider.updateDapps()
}
DAppsRequestHandler {
id: requestHandler
sdk: root.wcSDK
store: root.store
walletStore: root.walletStore
onSessionRequest: (request) => {
root.sessionRequest(request)
}
onDisplayToastMessage: (message, error) => {
root.displayToastMessage(message, error)
}
}
DAppsListProvider {
id: dappsProvider
sdk: root.wcSDK
store: root.store
}
}