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
This commit is contained in:
Godfrain Jacques 2024-08-06 13:20:08 -07:00 committed by GitHub
parent 4ba3cea925
commit bef66612c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 251 additions and 9 deletions

View File

@ -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()

View File

@ -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 =

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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))
return isSuccessResponse(sendTransactionRejected(args))
proc recallDAppPermissionFinishedRpc*(dAppUrl: string): bool =
return isSuccessResponse(recallDAppPermission(dAppUrl))

View File

@ -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
}
}
}

View File

@ -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) {

View File

@ -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

View File

@ -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

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit cf1a6631f879726b57247c73ebc4309b601ace7b
Subproject commit d5a78e784aa1b8852efc3a82de1cf9a0a1c10eda