feat(dapps) authenticate user for session request

Trigger authentication with identity of the request
Track the authentication using the identity and allow only once
Add tests for the new functionality
Minor improvements around the code

Closes #15018
This commit is contained in:
Stefan 2024-06-04 23:45:03 +03:00 committed by Stefan Dunca
parent 3b2a7b8f08
commit 4d080e12aa
15 changed files with 423 additions and 91 deletions

View File

@ -14,7 +14,6 @@ import app_service/service/collectible/service as collectible_service
import app_service/service/currency/service as currency_service
import app_service/service/transaction/service as transaction_service
import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/wallet_connect/service as wallet_connect_service
import app_service/service/bookmarks/service as bookmark_service
import app_service/service/dapp_permissions/service as dapp_permissions_service
import app_service/service/privacy/service as privacy_service
@ -81,7 +80,6 @@ type
currencyService: currency_service.Service
transactionService: transaction_service.Service
walletAccountService: wallet_account_service.Service
walletConnectService: wallet_connect_service.Service
bookmarkService: bookmark_service.Service
dappPermissionsService: dapp_permissions_service.Service
providerService: provider_service.Service
@ -192,7 +190,6 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.accountsService,
result.tokenService, result.networkService, result.currencyService
)
result.walletConnectService = wallet_connect_service.newService(statusFoundation.events, statusFoundation.threadpool, result.settingsService)
result.messageService = message_service.newService(
statusFoundation.events,
statusFoundation.threadpool,
@ -328,7 +325,6 @@ proc delete*(self: AppController) =
self.tokenService.delete
self.transactionService.delete
self.walletAccountService.delete
self.walletConnectService.delete
self.aboutService.delete
self.networkService.delete
self.activityCenterService.delete
@ -456,7 +452,6 @@ proc load(self: AppController) =
self.collectibleService.init()
self.currencyService.init()
self.walletAccountService.init()
self.walletConnectService.init()
# Apply runtime log level settings
if not main_constants.runtimeLogLevelSet():

View File

@ -168,7 +168,7 @@ proc newModule*(
result.filter = initFilter(result.controller)
result.walletConnectService = wc_service.newService(result.events, result.threadpool, settingsService)
result.walletConnectController = wc_controller.newController(result.walletConnectService)
result.walletConnectController = wc_controller.newController(result.walletConnectService, walletAccountService)
result.view = newView(result, result.activityController, result.tmpActivityControllers, result.activityDetailsController, result.collectibleDetailsController, result.walletConnectController)
@ -341,6 +341,7 @@ method load*(self: Module) =
self.overviewModule.load()
self.sendModule.load()
self.networksModule.load()
self.walletConnectService.init()
method isLoaded*(self: Module): bool =
return self.moduleLoaded

View File

@ -2,6 +2,7 @@ import NimQml
import chronicles
import app_service/service/wallet_connect/service as wallet_connect_service
import app_service/service/wallet_account/service as wallet_account_service
logScope:
topics = "wallet-connect-controller"
@ -10,14 +11,16 @@ QtObject:
type
Controller* = ref object of QObject
service: wallet_connect_service.Service
walletAccountService: wallet_account_service.Service
proc delete*(self: Controller) =
self.QObject.delete
proc newController*(service: wallet_connect_service.Service): Controller =
proc newController*(service: wallet_connect_service.Service, walletAccountService: wallet_account_service.Service): Controller =
new(result, delete)
result.service = service
result.walletAccountService = walletAccountService
result.QObject.setup
@ -34,3 +37,15 @@ QtObject:
else:
self.dappsListReceived(res)
return true
proc userAuthenticationResult*(self: Controller, topic: string, id: string, error: bool) {.signal.}
# Beware, it will fail if an authentication is already in progress
proc authenticateUser*(self: Controller, topic: string, id: string, address: string): bool {.slot.} =
let acc = self.walletAccountService.getAccountByAddress(address)
if acc.keyUid == "":
return false
return self.service.authenticateUser(acc.keyUid, proc(success: bool) =
self.userAuthenticationResult(topic, id, success)
)

View File

@ -10,17 +10,26 @@ import app/core/eventemitter
import app/core/signals/types
import app/core/tasks/[threadpool]
import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module
logScope:
topics = "wallet-connect-service"
# include async_tasks
const UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER* = "WalletSection-WCModule"
type
AuthenticationResponseFn* = proc(success: bool)
QtObject:
type Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
settingsService: settings_service.Service
authenticationCallback: AuthenticationResponseFn
proc delete*(self: Service) =
self.QObject.delete
@ -31,12 +40,29 @@ QtObject:
): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.settingsService = settings_service
proc init*(self: Service) =
discard
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args):
let args = SharedKeycarModuleArgs(e)
if args.uniqueIdentifier != UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER:
return
if self.authenticationCallback == nil:
error "unexpected user authenticated event; no callback set"
return
defer:
self.authenticationCallback = nil
if args.password == "" and args.pin == "":
info "fail to authenticate user"
self.authenticationCallback(false)
return
self.authenticationCallback(true)
proc addSession*(self: Service, session_json: string): bool =
# TODO #14588: call it async
@ -46,4 +72,17 @@ QtObject:
let validAtEpoch = now().toTime().toUnix()
let testChains = self.settingsService.areTestNetworksEnabled()
# TODO #14588: call it async
return status_go.getDapps(validAtEpoch, testChains)
return status_go.getDapps(validAtEpoch, testChains)
# Will fail if another authentication is in progress
proc authenticateUser*(self: Service, keyUid: string, callback: AuthenticationResponseFn): bool =
if self.authenticationCallback != nil:
return false
self.authenticationCallback = callback
let data = SharedKeycarModuleAuthenticationArgs(
uniqueIdentifier: UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER,
keyUid: keyUid)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
return true

View File

@ -4,6 +4,7 @@ import QtQuick.Layouts 1.15
import QtQml 2.15
import Qt.labs.settings 1.0
import QtTest 1.15
import QtQml.Models 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
@ -30,7 +31,6 @@ import shared.stores 1.0
Item {
id: root
// qml Splitter
SplitView {
anchors.fill: parent
@ -213,6 +213,42 @@ Item {
}
}
StatusDialog {
id: authMockDialog
title: "Authenticate user"
visible: false
property string topic: ""
property string id: ""
ColumnLayout {
RowLayout {
StatusBaseText { text: "Topic" }
StatusBaseText { text: authMockDialog.topic }
StatusBaseText { text: "ID" }
StatusBaseText { text: authMockDialog.id }
}
}
footer: StatusDialogFooter {
rightButtons: ObjectModel {
StatusButton {
text: qsTr("Reject")
onClicked: {
walletConnectService.store.userAuthenticationFailed(authMockDialog.topic, authMockDialog.id)
authMockDialog.close()
}
}
StatusButton {
text: qsTr("Authenticate")
onClicked: {
walletConnectService.store.userAuthenticated(authMockDialog.topic, authMockDialog.id)
authMockDialog.close()
}
}
}
}
}
WalletConnectService {
id: walletConnectService
@ -224,6 +260,9 @@ Item {
store: DAppsStore {
signal dappsListReceived(string dappsJson)
signal userAuthenticated(string topic, string id)
signal userAuthenticationFailed(string topic, string id)
signal sessionRequestExecuted(var payload, bool success)
function addWalletConnectSession(sessionJson) {
console.info("Persist Session", sessionJson)
@ -243,6 +282,13 @@ Item {
this.dappsListReceived(JSON.stringify(d.persistedDapps))
return true
}
function authenticateUser(topic, id, address) {
authMockDialog.topic = topic
authMockDialog.id = id
authMockDialog.open()
return true
}
}
walletStore: WalletStore {
@ -253,6 +299,8 @@ Item {
property var accounts: customAccountsModel.count > 0 ? customAccountsModel : defaultAccountsModel
readonly property ListModel ownAccounts: accounts
}
onDisplayToastMessage: (message, error) => console.info("Storybook - toast message: ", message, !!error ? "; error: " + error: "")
}

View File

@ -22,8 +22,8 @@ import utils 1.0
Item {
id: root
// width: 600
// height: 400
width: 600
height: 400
// Component {
// id: sdkComponent
@ -79,6 +79,9 @@ Item {
// DAppsStore {
// signal dappsListReceived(string dappsJson)
// signal userAuthenticated(string topic, string id)
// signal userAuthenticationFailed(string topic, string id)
// signal sessionRequestExecuted(var payload, bool success)
// // By default, return no dapps in store
// function getDapps() {
@ -90,6 +93,16 @@ Item {
// function addWalletConnectSession(sessionJson) {
// addWalletConnectSessionCalls.push({sessionJson})
// }
// property var authenticateUserCalls: []
// function authenticateUser(topic, id, address) {
// authenticateUserCalls.push({topic, id, address})
// }
// property var signMessageCalls: []
// function signMessage(message) {
// signMessageCalls.push({message})
// }
// }
// }
@ -120,6 +133,52 @@ Item {
// }
// }
// Component {
// id: dappsRequestHandlerComponent
// DAppsRequestHandler {
// }
// }
// TestCase {
// id: requestHandlerTest
// name: "DAppsRequestHandler"
// property DAppsRequestHandler handler: null
// SignalSpy {
// id: displayToastMessageSpy
// target: requestHandlerTest.handler
// signalName: "onDisplayToastMessage"
// }
// function init() {
// let walletStore = createTemporaryObject(walletStoreComponent, root)
// verify(!!walletStore)
// let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab" })
// verify(!!sdk)
// let store = createTemporaryObject(dappsStoreComponent, root)
// verify(!!store)
// handler = createTemporaryObject(dappsRequestHandlerComponent, root, {sdk: sdk, store: store, walletStore: walletStore})
// verify(!!handler)
// }
// function cleanup() {
// displayToastMessageSpy.clear()
// }
// function test_TestAuthentication() {
// let td = mockSessionRequestEvent(this, handler.sdk, handler.walletStore)
// handler.authenticate(td.request)
// compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser")
// let store = handler.store
// store.userAuthenticated(td.topic, td.request.id)
// compare(store.signMessageCalls.length, 1, "expected a call to store.signMessage")
// compare(store.signMessageCalls[0].message, td.request.data)
// }
// }
// TestCase {
// id: walletConnectServiceTest
// name: "WalletConnectService"
@ -263,6 +322,10 @@ Item {
// verify(!!request.data, "expected data to be set")
// compare(request.data.message, message, "expected message to be set")
// }
// // TODO #14757: add tests with multiple session requests coming in; validate that authentication is serialized and in order
// // function tst_SessionRequestQueueMultiple() {
// // }
// }
// Component {
@ -394,7 +457,7 @@ Item {
// waitForRendering(controlUnderTest)
// compare(dappsListReadySpy.count, 0, "expected NO dappsListReady signal to be emitted")
// mouseClick(controlUnderTest, Qt.LeftButton)
// mouseClick(controlUnderTest)
// waitForRendering(controlUnderTest)
// compare(dappsListReadySpy.count, 1, "expected dappsListReady signal to be emitted")
@ -402,7 +465,7 @@ Item {
// verify(!!popup)
// verify(popup.opened)
// mouseClick(Overlay.overlay, Qt.LeftButton)
// popup.close()
// waitForRendering(controlUnderTest)
// verify(!popup.opened)
@ -411,7 +474,7 @@ Item {
// function test_OpenPairModal() {
// waitForRendering(controlUnderTest)
// mouseClick(controlUnderTest, Qt.LeftButton)
// mouseClick(controlUnderTest)
// waitForRendering(controlUnderTest)
// let popup = findChild(controlUnderTest, "dappsPopup")
@ -422,7 +485,7 @@ Item {
// verify(!!connectButton)
// verify(pairWCReadySpy.count === 0, "expected NO pairWCReady signal to be emitted")
// mouseClick(connectButton, Qt.LeftButton)
// mouseClick(connectButton)
// waitForRendering(controlUnderTest)
// verify(pairWCReadySpy.count === 1, "expected pairWCReady signal to be emitted")
@ -437,42 +500,11 @@ Item {
// }
// }
// function mockSessionRequestEvent() {
// let service = controlUnderTest.wcService
// let account = service.walletStore.accounts.get(1)
// let network = service.walletStore.flatNetworks.get(1)
// let method = "personal_sign"
// let message = "hello world"
// let params = [Helpers.strToHex(message), account.address]
// let topic = "b536a"
// let requestEvent = JSON.parse(Testing.formatSessionRequest(network.chainId, method, params, topic))
// let request = createTemporaryObject(sessionRequestComponent, root, {
// event: requestEvent,
// topic,
// id: requestEvent.id,
// method: Constants.personal_sign,
// account,
// network,
// data: message
// })
// // All calls to SDK are expected as events to be made by the wallet connect SDK
// let sdk = service.wcSDK
// // Expect to have calls to getActiveSessions from service initialization
// let prevRequests = sdk.getActiveSessionsCallbacks.length
// sdk.sessionRequestEvent(requestEvent)
// // Service will trigger a sessionRequest event following the getActiveSessions call
// let callback = sdk.getActiveSessionsCallbacks[prevRequests].callback
// let session = JSON.parse(Testing.formatApproveSessionResponse([network.chainId, 7], [account.address]))
// callback({"b536a": session})
// return {sdk, session, account, network, topic, id: request.id}
// }
// function test_OpenDappRequestModal() {
// waitForRendering(controlUnderTest)
// let td = mockSessionRequestEvent()
// let service = controlUnderTest.wcService
// let td = mockSessionRequestEvent(this, service.wcSDK, service.walletStore)
// waitForRendering(controlUnderTest)
// let popup = findChild(controlUnderTest, "dappsRequestModal")
@ -494,7 +526,8 @@ Item {
// function test_RejectDappRequestModal() {
// waitForRendering(controlUnderTest)
// let td = mockSessionRequestEvent()
// let service = controlUnderTest.wcService
// let td = mockSessionRequestEvent(this, service.wcSDK, service.walletStore)
// waitForRendering(controlUnderTest)
// let popup = findChild(controlUnderTest, "dappsRequestModal")
@ -502,11 +535,11 @@ Item {
// let rejectButton = findChild(popup, "rejectButton")
// mouseClick(rejectButton, Qt.LeftButton)
// mouseClick(rejectButton)
// compare(td.sdk.rejectSessionRequestCalls.length, 1, "expected a call to service.rejectSessionRequest")
// let args = td.sdk.rejectSessionRequestCalls[0]
// compare(args.topic, td.topic, "expected topic to be set")
// compare(args.id, td.id, "expected id to be set")
// compare(args.id, td.request.id, "expected id to be set")
// compare(args.error, false, "expected no error; it was user rejected")
// waitForRendering(controlUnderTest)
@ -514,4 +547,32 @@ Item {
// verify(!popup.visible)
// }
// }
// function mockSessionRequestEvent(tc, sdk, walletStore) {
// let account = walletStore.accounts.get(1)
// let network = walletStore.flatNetworks.get(1)
// let method = "personal_sign"
// let message = "hello world"
// let params = [Helpers.strToHex(message), account.address]
// let topic = "b536a"
// let requestEvent = JSON.parse(Testing.formatSessionRequest(network.chainId, method, params, topic))
// let request = tc.createTemporaryObject(sessionRequestComponent, root, {
// event: requestEvent,
// topic,
// id: requestEvent.id,
// method: Constants.personal_sign,
// account,
// network,
// data: message
// })
// // Expect to have calls to getActiveSessions from service initialization
// let prevRequests = sdk.getActiveSessionsCallbacks.length
// sdk.sessionRequestEvent(requestEvent)
// // Service might trigger a sessionRequest event following the getActiveSessions call
// let callback = sdk.getActiveSessionsCallbacks[prevRequests].callback
// let session = JSON.parse(Testing.formatApproveSessionResponse([network.chainId, 7], [account.address]))
// callback({"b536a": session})
// return {sdk, session, account, network, topic, request}
// }
}

View File

@ -61,12 +61,13 @@ ConnectedDappsButton {
sourceComponent: DAppsListPopup {
visible: true
model: wcService.dappsModel
model: root.wcService.dappsModel
onPairWCDapp: {
pairWCLoader.active = true
this.close()
}
onOpened: {
this.x = root.width - this.contentWidth - 2 * this.padding
this.y = root.height + 4
@ -91,8 +92,8 @@ ConnectedDappsButton {
visible: true
onClosed: connectDappLoader.active = false
accounts: wcService.validAccounts
flatNetworks: wcService.flatNetworks
accounts: root.wcService.validAccounts
flatNetworks: root.wcService.flatNetworks
onConnect: {
root.wcService.approvePairSession(sessionProposal, dappChains, selectedAccount)
@ -136,17 +137,30 @@ ConnectedDappsButton {
onClosed: sessionRequestLoader.active = false
onSign: {
console.debug("@dd TODO sign session request")
if (!request) {
console.error("Error signing: request is null")
return
}
root.wcService.requestHandler.authenticate(request)
}
onReject: {
let userRejected = true
wcService.requestHandler.rejectSessionRequest(request, userRejected)
root.wcService.requestHandler.rejectSessionRequest(request, userRejected)
close()
}
}
}
Connections {
target: root.wcService ? root.wcService.requestHandler : null
function onSessionRequestResult(payload, isSuccess) {
// TODO #14927 handle this properly
sessionRequestLoader.active = false
}
}
Connections {
target: root.wcService

View File

@ -23,16 +23,32 @@ QObject {
sdk.rejectSessionRequest(request.topic, request.id, error)
}
/// Beware, it will fail if called multiple times before getting an answer
function authenticate(request) {
return store.authenticateUser(request.topic, request.id, request.account.address)
}
signal sessionRequest(SessionRequestResolved request)
signal displayToastMessage(string message, bool error)
signal sessionRequestResult(var payload, bool isSuccess)
/// Supported methods
property QtObject methods: QtObject {
readonly property string personalSign: Constants.personal_sign
readonly property string sendTransaction: "eth_sendTransaction"
readonly property QtObject personalSign: QtObject {
readonly property string name: Constants.personal_sign
readonly property string userString: qsTr("sign")
}
readonly property QtObject sendTransaction: QtObject {
readonly property string name: "eth_sendTransaction"
readonly property string userString: qsTr("send transaction")
}
readonly property var all: [personalSign, sendTransaction]
}
function getSupportedMethods() {
return [root.methods.personalSign, root.methods.sendTransaction]
return methods.all.map(function(method) {
return method.name
})
}
Connections {
@ -47,6 +63,65 @@ QObject {
}
requests.enqueue(obj)
}
function onSessionRequestUserAnswerResult(topic, id, accept, error) {
var request = requests.findRequest(topic, id)
if (request === null) {
console.error("Error finding event for topic", topic, "id", id)
return
}
let methodStr = d.methodToUserString(request.method)
if (!methodStr) {
console.error("Error finding user string for method", request.method)
return
}
d.lookupSession(topic, function(session) {
if (session === null)
return
if (error) {
root.displayToastMessage(qsTr("Fail to %1 from %2").arg(methodStr).arg(session.peer.metadata.url), true)
// TODO #14757 handle SDK error on user accept/reject
console.error(`Error accepting session request for topic: ${topic}, id: ${id}, accept: ${accept}, error: ${error}`)
return
}
let actionStr = accept ? qsTr("accepted") : qsTr("rejected")
root.displayToastMessage("%1 %2 %3".arg(session.peer.metadata.url).arg(methodStr).arg(actionStr), false)
root.sessionRequestApprovalResult()
})
}
}
Connections {
target: root.store
function onUserAuthenticated(topic, id) {
var request = requests.findRequest(topic, id)
if (request === null) {
console.error("Error finding event for topic", topic, "id", id)
return
}
d.executeSessionRequest(request)
}
function onUserAuthenticationFailed(topic, id) {
var request = requests.findRequest(topic, id)
let methodStr = d.methodToUserString(request.method)
if (request === null || !methodStr) {
return
}
d.lookupSession(topic, function(session) {
if (session === null)
return
root.displayToastMessage(qsTr("Failed to authenticate %1 from %2").arg(methodStr).arg(session.peer.metadata.url), true)
})
}
function onSessionRequestExecuted(payload, isSuccess) {
// TODO #14927 handle this properly
root.sessionRequestResult(payload, isSuccess)
}
}
QObject {
@ -74,19 +149,18 @@ QObject {
// Check later to have a valid request object
if (!getSupportedMethods().includes(method)
// TODO #14927: support method eth_sendTransaction
|| method == "eth_sendTransaction") {
|| method == root.methods.sendTransaction.name) {
console.error("Unsupported method", method)
return null
}
sdk.getActiveSessions((res) => {
Object.keys(res).forEach((topic) => {
if (topic === obj.topic) {
let session = res[topic]
obj.resolveDappInfoFromSession(session)
root.sessionRequest(obj)
}
})
d.lookupSession(obj.topic, function(session) {
if (session === null) {
console.error("DAppsRequestHandler.lookupSession: error finding session for topic", obj.topic)
return
}
obj.resolveDappInfoFromSession(session)
root.sessionRequest(obj)
})
return obj
@ -94,7 +168,7 @@ QObject {
/// Returns null if the account is not found
function lookupAccountFromEvent(event, method) {
if (method === root.methods.personalSign) {
if (method === root.methods.personalSign.name) {
if (event.params.request.params.length < 2) {
return null
}
@ -111,7 +185,7 @@ QObject {
/// Returns null if the network is not found
function lookupNetworkFromEvent(event, method) {
if (method === root.methods.personalSign) {
if (method === root.methods.personalSign.name) {
let chainId = Helpers.chainIdFromEip155(event.params.chainId)
for (let i = 0; i < walletStore.flatNetworks.count; i++) {
let network = ModelUtils.get(walletStore.flatNetworks, i)
@ -124,7 +198,7 @@ QObject {
}
function extractMethodData(event, method) {
if (method === root.methods.personalSign) {
if (method === root.methods.personalSign.name) {
if (event.params.request.params.length == 0) {
return null
}
@ -134,6 +208,35 @@ QObject {
}
}
}
function methodToUserString(method) {
for (let i = 0; i < methods.all.length; i++) {
if (methods.all[i].name === method) {
return methods.all[i].userString
}
}
return ""
}
function lookupSession(topicToLookup, callback) {
sdk.getActiveSessions((res) => {
Object.keys(res).forEach((topic) => {
if (topic === topicToLookup) {
let session = res[topic]
callback(session)
}
})
})
}
function executeSessionRequest(request) {
if (request.method === root.methods.personalSign.name) {
store.signMessage(request.data.message)
console.debug("TODO #14927 sign message: ", request.data.message)
} else {
console.error("Unsupported method to execute: ", request.method)
}
}
}
/// The queue is used to ensure that the events are processed in the order they are received but they could be

View File

@ -206,10 +206,10 @@ WalletConnectSDKBase {
d.engine.runJavaScript(`
wc.respondSessionRequest("${topic}", ${id}, "${signature}")
.then((value) => {
wc.statusObject.onRespondSessionRequestResponse("")
wc.statusObject.onAcceptSessionRequestResponse("${topic}", ${id}, "")
})
.catch((e) => {
wc.statusObject.onRespondSessionRequestResponse(e.message)
wc.statusObject.onAcceptSessionRequestResponse("${topic}", ${id}, e.message)
})
`
)
@ -221,10 +221,10 @@ WalletConnectSDKBase {
d.engine.runJavaScript(`
wc.rejectSessionRequest("${topic}", ${id}, "${error}")
.then((value) => {
wc.statusObject.onRejectSessionRequestResponse("")
wc.statusObject.onRejectSessionRequestResponse("${topic}", ${id}, "")
})
.catch((e) => {
wc.statusObject.onRejectSessionRequestResponse(e.message)
wc.statusObject.onRejectSessionRequestResponse("${topic}", ${id}, e.message)
})
`
)
@ -387,14 +387,16 @@ WalletConnectSDKBase {
root.rejectSessionResult(error)
}
function onRespondSessionRequestResponse(error) {
console.debug(`WC WalletConnectSDK.onRespondSessionRequestResponse; error: ${error}`)
root.sessionRequestUserAnswerResult(true, error)
function onAcceptSessionRequestResponse(topic, id, error) {
console.debug(`WC WalletConnectSDK.onAcceptSessionRequestResponse; topic: ${topic}, id: ${id} error: ${error}`)
let responseToAccept = true
root.sessionRequestUserAnswerResult(topic, id, responseToAccept, error)
}
function onRejectSessionRequestResponse(error) {
console.debug(`WC WalletConnectSDK.onRejectSessionRequestResponse; error: ${error}`)
root.sessionRequestUserAnswerResult(false, error)
function onRejectSessionRequestResponse(topic, id, error) {
console.debug(`WC WalletConnectSDK.onRejectSessionRequestResponse; topic: ${topic}, id: ${id}, error: ${error}`)
let responseToAccept = false
root.sessionRequestUserAnswerResult(topic, id, responseToAccept, error)
}
function onSessionProposal(details) {

View File

@ -13,7 +13,7 @@ Item {
signal approveSessionResult(var approvedNamespaces, string error)
signal rejectSessionResult(string error)
signal sessionRequestEvent(var sessionRequest)
signal sessionRequestUserAnswerResult(bool accept, string error)
signal sessionRequestUserAnswerResult(string topic, string id, bool accept /* not reject */, string error)
signal authRequest(var request)
signal authMessageFormated(string formatedMessage, string address)

View File

@ -164,6 +164,9 @@ QObject {
onSessionRequest: (request) => {
root.sessionRequest(request)
}
onDisplayToastMessage: (message, error) => {
root.displayToastMessage(message, error)
}
}
DAppsListProvider {

View File

@ -4,8 +4,8 @@ import QtQuick 2.15
ListModel {
id: root
function enqueue(event) {
root.append(event);
function enqueue(request) {
root.append(request);
}
function dequeue() {
@ -16,4 +16,15 @@ ListModel {
}
return null;
}
/// returns null if not found
function findRequest(topic, id) {
for (var i = 0; i < root.count; i++) {
let entry = root.get(i)
if (entry.topic === topic && entry.id === id) {
return entry;
}
}
return null;
}
}

View File

@ -2052,6 +2052,11 @@ Item {
Component.onCompleted: {
Global.walletConnectService = walletConnectService
}
onDisplayToastMessage: (message, isErr) => {
Global.displayToastMessage(message, "", isErr ? "warning" : "checkmark-circle", false,
isErr ? Constants.ephemeralNotificationType.danger : Constants.ephemeralNotificationType.success, "")
}
}
}
}

View File

@ -196,11 +196,14 @@ StatusDialog {
id: footer
leftButtons: ObjectModel {
MaxFeesDisplay {}
MaxFeesDisplay {
}
Item {
width: 20
}
EstimatedTimeDisplay {}
EstimatedTimeDisplay {
visible: !!root.estimatedTimeText
}
}
rightButtons: ObjectModel {
@ -218,8 +221,6 @@ StatusDialog {
height: 44
text: qsTr("Sign")
enabled: false
onClicked: {
root.sign()
}
@ -228,21 +229,32 @@ StatusDialog {
}
component MaxFeesDisplay: ColumnLayout {
visible: root.maxFeesText
StatusBaseText {
text: qsTr("Max fees:")
font.pixelSize: 12
color: Theme.palette.directColor1
}
StatusBaseText {
id: maxFeesDisplay
text: root.maxFeesText
visible: !!root.maxFeesText
font.pixelSize: 16
font.weight: Font.DemiBold
}
StatusBaseText {
text: qsTr("No fees")
visible: !maxFeesDisplay.visible
font.pixelSize: maxFeesDisplay.font.pixelSize
font.weight: maxFeesDisplay.font.weight
}
}
component EstimatedTimeDisplay: ColumnLayout {
visible: root.estimatedTimeText
StatusBaseText {
text: qsTr("Est. time:")
font.pixelSize: 12

View File

@ -9,11 +9,26 @@ QObject {
/// \c dappsJson serialized from status-go.wallet.GetDapps
signal dappsListReceived(string dappsJson)
signal userAuthenticated(string topic, string id)
signal userAuthenticationFailed(string topic, string id)
signal sessionRequestExecuted(var payload, bool success)
function addWalletConnectSession(sessionJson) {
controller.addWalletConnectSession(sessionJson)
}
function authenticateUser(topic, id, address) {
let ok = controller.authenticateUser(topic, id, address)
if(!ok) {
root.userAuthenticationFailed()
}
}
function signMessage(message) {
// TODO #14927 implement me
root.sessionRequestExecuted(message, true)
}
/// \c getDapps triggers an async response to \c dappsListReceived
function getDapps() {
return controller.getDapps()
@ -26,5 +41,13 @@ QObject {
function onDappsListReceived(dappsJson) {
root.dappsListReceived(dappsJson)
}
function onUserAuthenticationResult(topic, id, success) {
if (success) {
root.userAuthenticated(topic, id)
} else {
root.userAuthenticationFailed(topic, id)
}
}
}
}