From bef66612c55452bfec0dc3662048cd8700f2f727 Mon Sep 17 00:00:00 2001 From: Godfrain Jacques Date: Tue, 6 Aug 2024 13:20:08 -0700 Subject: [PATCH] feature(connector)_: Add model join to handle connected dApps (#15954) * feature(connector)_: Add model join to handle connected dApps * Fix review comments * chore: bump status-go --- .../core/signals/remote_signals/connector.nim | 22 ++++++ .../signals/remote_signals/signal_type.nim | 2 + src/app/core/signals/signals_manager.nim | 2 + .../shared_modules/connector/controller.nim | 27 +++++++ src/app_service/service/connector/service.nim | 26 +++++++ src/backend/connector.nim | 11 ++- .../dapps/ConnectorDAppsListProvider.qml | 69 ++++++++++++++++++ .../services/dapps/DappsConnectorSDK.qml | 73 +++++++++++++++++-- .../services/dapps/WalletConnectService.qml | 25 ++++++- .../AppLayouts/Wallet/services/dapps/qmldir | 1 + vendor/status-go | 2 +- 11 files changed, 251 insertions(+), 9 deletions(-) create mode 100644 ui/app/AppLayouts/Wallet/services/dapps/ConnectorDAppsListProvider.qml diff --git a/src/app/core/signals/remote_signals/connector.nim b/src/app/core/signals/remote_signals/connector.nim index 0055f9752d..d7825d3905 100644 --- a/src/app/core/signals/remote_signals/connector.nim +++ b/src/app/core/signals/remote_signals/connector.nim @@ -17,6 +17,16 @@ type ConnectorSendTransactionSignal* = ref object of Signal chainId*: int txArgs*: string +type ConnectorGrantDAppPermissionSignal* = ref object of Signal + url*: string + name*: string + iconUrl*: string + +type ConnectorRevokeDAppPermissionSignal* = ref object of Signal + url*: string + name*: string + iconUrl*: string + proc fromEvent*(T: type ConnectorSendRequestAccountsSignal, event: JsonNode): ConnectorSendRequestAccountsSignal = result = ConnectorSendRequestAccountsSignal() result.url = event["event"]{"url"}.getStr() @@ -32,3 +42,15 @@ proc fromEvent*(T: type ConnectorSendTransactionSignal, event: JsonNode): Connec result.requestId = event["event"]{"requestId"}.getStr() result.chainId = event["event"]{"chainId"}.getInt() result.txArgs = event["event"]{"txArgs"}.getStr() + +proc fromEvent*(T: type ConnectorGrantDAppPermissionSignal, event: JsonNode): ConnectorGrantDAppPermissionSignal = + result = ConnectorGrantDAppPermissionSignal() + result.url = event["event"]{"url"}.getStr() + result.name = event["event"]{"name"}.getStr() + result.iconUrl = event["event"]{"iconUrl"}.getStr() + +proc fromEvent*(T: type ConnectorRevokeDAppPermissionSignal, event: JsonNode): ConnectorRevokeDAppPermissionSignal = + result = ConnectorRevokeDAppPermissionSignal() + result.url = event["event"]{"url"}.getStr() + result.name = event["event"]{"name"}.getStr() + result.iconUrl = event["event"]{"iconUrl"}.getStr() diff --git a/src/app/core/signals/remote_signals/signal_type.nim b/src/app/core/signals/remote_signals/signal_type.nim index 94ad9963ae..a0218d73f3 100644 --- a/src/app/core/signals/remote_signals/signal_type.nim +++ b/src/app/core/signals/remote_signals/signal_type.nim @@ -68,6 +68,8 @@ type SignalType* {.pure.} = enum CommunityTokenAction = "communityToken.communityTokenAction" ConnectorSendRequestAccounts = "connector.sendRequestAccounts" ConnectorSendTransaction = "connector.sendTransaction" + ConnectorGrantDAppPermission = "connector.dAppPermissionGranted" + ConnectorRevokeDAppPermission = "connector.dAppPermissionRevoked" Unknown proc event*(self:SignalType):string = diff --git a/src/app/core/signals/signals_manager.nim b/src/app/core/signals/signals_manager.nim index 9f5eff5e7a..616b3b4ed2 100644 --- a/src/app/core/signals/signals_manager.nim +++ b/src/app/core/signals/signals_manager.nim @@ -137,6 +137,8 @@ QtObject: of SignalType.CommunityTokenAction: CommunityTokenActionSignal.fromEvent(jsonSignal) of SignalType.ConnectorSendRequestAccounts: ConnectorSendRequestAccountsSignal.fromEvent(jsonSignal) of SignalType.ConnectorSendTransaction: ConnectorSendTransactionSignal.fromEvent(jsonSignal) + of SignalType.ConnectorGrantDAppPermission: ConnectorGrantDAppPermissionSignal.fromEvent(jsonSignal) + of SignalType.ConnectorRevokeDAppPermission: ConnectorRevokeDAppPermissionSignal.fromEvent(jsonSignal) else: Signal() result.signalType = signalType diff --git a/src/app/modules/shared_modules/connector/controller.nim b/src/app/modules/shared_modules/connector/controller.nim index 7a35db2890..883a4aedf3 100644 --- a/src/app/modules/shared_modules/connector/controller.nim +++ b/src/app/modules/shared_modules/connector/controller.nim @@ -11,6 +11,8 @@ import app_service/common/utils const SIGNAL_CONNECTOR_SEND_REQUEST_ACCOUNTS* = "ConnectorSendRequestAccounts" const SIGNAL_CONNECTOR_EVENT_CONNECTOR_SEND_TRANSACTION* = "ConnectorSendTransaction" +const SIGNAL_CONNECTOR_GRANT_DAPP_PERMISSION* = "ConnectorGrantDAppPermission" +const SIGNAL_CONNECTOR_REVOKE_DAPP_PERMISSION* = "ConnectorRevokeDAppPermission" logScope: topics = "connector-controller" @@ -26,6 +28,8 @@ QtObject: proc dappRequestsToConnect*(self: Controller, requestId: string, payload: string) {.signal.} proc dappValidatesTransaction*(self: Controller, requestId: string, payload: string) {.signal.} + proc dappGrantDAppPermission*(self: Controller, payload: string) {.signal.} + proc dappRevokeDAppPermission*(self: Controller, payload: string) {.signal.} proc newController*(service: connector_service.Service, events: EventEmitter): Controller = new(result, delete) @@ -61,6 +65,26 @@ QtObject: controller.dappValidatesTransaction(params.requestId, dappInfo.toJson()) + result.events.on(SIGNAL_CONNECTOR_GRANT_DAPP_PERMISSION) do(e: Args): + let params = ConnectorGrantDAppPermissionSignal(e) + let dappInfo = %*{ + "icon": params.iconUrl, + "name": params.name, + "url": params.url, + } + + controller.dappGrantDAppPermission(dappInfo.toJson()) + + result.events.on(SIGNAL_CONNECTOR_REVOKE_DAPP_PERMISSION) do(e: Args): + let params = ConnectorRevokeDAppPermissionSignal(e) + let dappInfo = %*{ + "icon": params.iconUrl, + "name": params.name, + "url": params.url, + } + + controller.dappRevokeDAppPermission(dappInfo.toJson()) + result.QObject.setup proc parseSingleUInt(chainIDsString: string): uint = @@ -87,3 +111,6 @@ QtObject: proc rejectTransactionSigning*(self: Controller, requestId: string): bool {.slot.} = return self.service.rejectTransactionSigning(requestId) + + proc recallDAppPermission*(self: Controller, dAppUrl: string): bool {.slot.} = + return self.service.recallDAppPermission(dAppUrl) diff --git a/src/app_service/service/connector/service.nim b/src/app_service/service/connector/service.nim index adb387bbd5..055ab90c79 100644 --- a/src/app_service/service/connector/service.nim +++ b/src/app_service/service/connector/service.nim @@ -14,6 +14,8 @@ logScope: const SIGNAL_CONNECTOR_SEND_REQUEST_ACCOUNTS* = "ConnectorSendRequestAccounts" const SIGNAL_CONNECTOR_EVENT_CONNECTOR_SEND_TRANSACTION* = "ConnectorSendTransaction" +const SIGNAL_CONNECTOR_GRANT_DAPP_PERMISSION* = "ConnectorGrantDAppPermission" +const SIGNAL_CONNECTOR_REVOKE_DAPP_PERMISSION* = "ConnectorRevokeDAppPermission" # Enum with events type Event* = enum @@ -65,6 +67,22 @@ QtObject: self.events.emit(SIGNAL_CONNECTOR_EVENT_CONNECTOR_SEND_TRANSACTION, data) ) + self.events.on(SignalType.ConnectorGrantDAppPermission.event, proc(e: Args) = + if self.eventHandler == nil: + return + + var data = ConnectorGrantDAppPermissionSignal(e) + + self.events.emit(SIGNAL_CONNECTOR_GRANT_DAPP_PERMISSION, data) + ) + self.events.on(SignalType.ConnectorRevokeDAppPermission.event, proc(e: Args) = + if self.eventHandler == nil: + return + + var data = ConnectorRevokeDAppPermissionSignal(e) + + self.events.emit(SIGNAL_CONNECTOR_REVOKE_DAPP_PERMISSION, data) + ) proc registerEventsHandler*(self: Service, handler: EventHandlerFn) = self.eventHandler = handler @@ -112,3 +130,11 @@ QtObject: proc rejectDappConnect*(self: Service, requestId: string): bool = rejectRequest(self, requestId, status_go.requestAccountsRejectedFinishedRpc, "requestAccountsRejectedFinishedRpc failed: ") + + proc recallDAppPermission*(self: Service, dAppUrl: string): bool = + try: + return status_go.recallDAppPermissionFinishedRpc(dAppUrl) + + except Exception as e: + error "recallDAppPermissionFinishedRpc failed: ", err=e.msg + return false \ No newline at end of file diff --git a/src/backend/connector.nim b/src/backend/connector.nim index 6779533205..b1cd65ee95 100644 --- a/src/backend/connector.nim +++ b/src/backend/connector.nim @@ -20,6 +20,9 @@ type SendTransactionAcceptedArgs* = ref object of RootObj type RejectedArgs* = ref object of RootObj requestId* {.serializedFieldName("requestId").}: string +type RecallDAppPermissionArgs* = ref object of RootObj + dAppUrl* {.serializedFieldName("dAppUrl").}: string + rpc(requestAccountsAccepted, "connector"): args: RequestAccountsAcceptedArgs @@ -32,6 +35,9 @@ rpc(sendTransactionRejected, "connector"): rpc(requestAccountsRejected, "connector"): args: RejectedArgs +rpc(recallDAppPermission, "connector"): + dAppUrl: string + proc isSuccessResponse(rpcResponse: RpcResponse[JsonNode]): bool = return rpcResponse.error.isNil @@ -45,4 +51,7 @@ proc sendTransactionAcceptedFinishedRpc*(args: SendTransactionAcceptedArgs): boo return isSuccessResponse(sendTransactionAccepted(args)) proc sendTransactionRejectedFinishedRpc*(args: RejectedArgs): bool = - return isSuccessResponse(sendTransactionRejected(args)) \ No newline at end of file + return isSuccessResponse(sendTransactionRejected(args)) + +proc recallDAppPermissionFinishedRpc*(dAppUrl: string): bool = + return isSuccessResponse(recallDAppPermission(dAppUrl)) \ No newline at end of file diff --git a/ui/app/AppLayouts/Wallet/services/dapps/ConnectorDAppsListProvider.qml b/ui/app/AppLayouts/Wallet/services/dapps/ConnectorDAppsListProvider.qml new file mode 100644 index 0000000000..ba49a136e1 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/services/dapps/ConnectorDAppsListProvider.qml @@ -0,0 +1,69 @@ +import QtQuick 2.15 +import StatusQ.Core.Utils 0.1 +import AppLayouts.Wallet.services.dapps 1.0 +import shared.stores 1.0 +import utils 1.0 + +QObject { + id: root + + readonly property alias dappsModel: d.dappsModel + + function addSession(session) { + d.addSession(session) + } + + function revokeSession(session) { + d.revokeSession(session) + } + + function getActiveSession(dAppUrl) { + return d.getActionSession(dAppUrl) + } + + QObject { + id: d + + property ListModel dappsModel: ListModel { + id: dapps + } + + function addSession(dappInfo) { + let dappItem = JSON.parse(dappInfo) + dapps.append(dappItem) + } + + function revokeSession(dappInfo) { + let dappItem = JSON.parse(dappInfo) + for (let i = 0; i < dapps.count; i++) { + let existingDapp = dapps.get(i) + if (existingDapp.url === dappItem.url) { + dapps.remove(i) + break + } + } + } + + function revokeAllSessions() { + for (let i = 0; i < dapps.count; i++) { + dapps.remove(i) + } + } + + function getActionSession(dAppUrl) { + for (let i = 0; i < dapps.count; i++) { + let existingDapp = dapps.get(i) + + if (existingDapp.url === dAppUrl) { + return JSON.stringify({ + name: existingDapp.name, + url: existingDapp.url, + icon: existingDapp.iconUrl + }); + } + } + + return null + } + } +} diff --git a/ui/app/AppLayouts/Wallet/services/dapps/DappsConnectorSDK.qml b/ui/app/AppLayouts/Wallet/services/dapps/DappsConnectorSDK.qml index 33681b57f0..f3a334ce1b 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/DappsConnectorSDK.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/DappsConnectorSDK.qml @@ -39,6 +39,8 @@ WalletConnectSDKBase { property string requestId: "" property alias requestsModel: requests + readonly property string invalidDAppUrlError: "Invalid dappInfo: URL is missing" + projectId: "" implicitWidth: 1 @@ -103,10 +105,10 @@ WalletConnectSDKBase { return null } - let session = getActiveSessions(root.dappInfo) + let session = getActiveSession(root.dappInfo) if (session === null) { - console.error("DAppsRequestHandler.lookupSession: error finding session for topic", obj.topic) + console.error("Connector.lookupSession: error finding session for requestId ", obj.requestId) return } obj.resolveDappInfoFromSession(session) @@ -254,7 +256,7 @@ WalletConnectSDKBase { } function acceptSessionRequest(topic, method, id, signature) { - console.debug(`WC DappsConnectorSDK.acceptSessionRequest; topic: "${topic}", id: ${root.requestId}, signature: "${signature}"`) + console.debug(`Connector DappsConnectorSDK.acceptSessionRequest; requestId: ${root.requestId}, signature: "${signature}"`) sessionRequestLoader.active = false controller.approveTransactionRequest(requestId, signature) @@ -262,7 +264,7 @@ WalletConnectSDKBase { root.wcService.displayToastMessage(qsTr("Successfully signed transaction from %1").arg(root.dappInfo.url), false) } - function getActiveSessions(dappInfos) { + function getActiveSession(dappInfos) { let sessionTemplate = (dappUrl, dappName, dappIcon) => { return { "peer": { @@ -279,7 +281,16 @@ WalletConnectSDKBase { }; } - return sessionTemplate(dappInfos.url, dappInfos.name, dappInfos.icon) + let sessionString = root.wcService.connectorDAppsProvider.getActiveSession(dappInfos.url) + if (sessionString === null) { + console.error("Connector.lookupSession: error finding session for requestId ", root.requestId) + + return + } + + let session = JSON.parse(sessionString); + + return sessionTemplate(session.url, session.name, session.icon) } function authenticate(request) { @@ -350,6 +361,11 @@ WalletConnectSDKBase { connectDappLoader.active = false rejectSession(root.requestId) } + + onDisconnect: { + connectDappLoader.active = false; + controller.recallDAppPermission(root.dappInfo.url) + } } } @@ -454,6 +470,22 @@ WalletConnectSDKBase { id: requests } + Connections { + target: root.wcService + + function onRevokeSession(dAppUrl) { + if (!dAppUrl) { + console.warn(invalidDAppUrlError) + return + } + + controller.recallDAppPermission(dAppUrl) + const session = { url: dAppUrl, name: "", icon: "" } + root.wcService.connectorDAppsProvider.revokeSession(JSON.stringify(session)) + root.wcService.displayToastMessage(qsTr("Disconnected from %1").arg(dAppUrl), false) + } + } + Connections { target: controller @@ -518,11 +550,40 @@ WalletConnectSDKBase { connectDappLoader.active = true root.requestId = requestId } + + onDappGrantDAppPermission: function(dappInfoString) { + let dappItem = JSON.parse(dappInfoString) + const { url, name, icon: iconUrl } = dappItem + + if (!url) { + console.warn(invalidDAppUrlError) + return + } + const session = { url, name, iconUrl } + root.wcService.connectorDAppsProvider.addSession(JSON.stringify(session)) + } + + onDappRevokeDAppPermission: function(dappInfoString) { + let dappItem = JSON.parse(dappInfoString) + let session = { + "url": dappItem.url, + "name": dappItem.name, + "iconUrl": dappItem.icon + } + + if (!session.url) { + console.warn(invalidDAppUrlError) + return + } + root.wcService.connectorDAppsProvider.revokeSession(JSON.stringify(session)) + root.wcService.displayToastMessage(qsTr("Disconnected from %1").arg(dappItem.url), false) + } } approveSession: function(requestId, account, selectedChains) { controller.approveDappConnectRequest(requestId, account, JSON.stringify(selectedChains)) - root.wcService.displayToastMessage(qsTr("Successfully authenticated %1").arg(root.dappInfo.url), false) + const { url, name, icon: iconUrl } = root.dappInfo; + root.wcService.displayToastMessage(qsTr("Successfully authenticated %1").arg(url), false); } rejectSession: function(requestId) { diff --git a/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml b/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml index ccc2269311..5638c7e2ae 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml @@ -32,11 +32,26 @@ QObject { required property DAppsStore store required property var walletRootStore - readonly property alias dappsModel: dappsProvider.dappsModel + readonly property var dappsModel: ConcatModel { + markerRoleName: "source" + + sources: [ + SourceModel { + model: dappsProvider.dappsModel + markerRoleValue: "walletConnect" + }, + SourceModel { + model: connectorDAppsProvider.dappsModel + markerRoleValue: "connector" + } + ] + } readonly property alias requestHandler: requestHandler readonly property bool isServiceAvailableForAddressSelection: dappsProvider.supportedAccountsModel.ModelCount.count + readonly property alias connectorDAppsProvider: connectorDAppsProvider + readonly property var validAccounts: SortFilterProxyModel { sourceModel: d.supportedAccountsModel proxyRoles: [ @@ -127,6 +142,8 @@ QObject { } } }); + + root.revokeSession(url) } function getDApp(dAppUrl) { @@ -141,6 +158,8 @@ QObject { // and WalletConnectService.approvePair errors signal pairingValidated(int validationState) + signal revokeSession(string dAppUrl) + readonly property Connections sdkConnections: Connections { target: wcSDK @@ -293,6 +312,10 @@ QObject { selectedAddress: root.walletRootStore.selectedAddress } + ConnectorDAppsListProvider { + id: connectorDAppsProvider + } + // Timeout for the corner case where the URL was already dismissed and the SDK doesn't respond with an error nor advances with the proposal Timer { id: timeoutTimer diff --git a/ui/app/AppLayouts/Wallet/services/dapps/qmldir b/ui/app/AppLayouts/Wallet/services/dapps/qmldir index 9391c09251..b6ff39a232 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/qmldir +++ b/ui/app/AppLayouts/Wallet/services/dapps/qmldir @@ -5,5 +5,6 @@ DappsConnectorSDK 1.0 DappsConnectorSDK.qml DAppsListProvider 1.0 DAppsListProvider.qml DAppsRequestHandler 1.0 DAppsRequestHandler.qml +ConnectorDAppsListProvider 1.0 ConnectorDAppsListProvider.qml DAppsHelpers 1.0 helpers.js diff --git a/vendor/status-go b/vendor/status-go index cf1a6631f8..d5a78e784a 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit cf1a6631f879726b57247c73ebc4309b601ace7b +Subproject commit d5a78e784aa1b8852efc3a82de1cf9a0a1c10eda