2024-05-31 09:58:47 +00:00
|
|
|
import QtQuick 2.15
|
|
|
|
|
|
|
|
import AppLayouts.Wallet.services.dapps 1.0
|
|
|
|
|
|
|
|
import StatusQ.Core.Utils 0.1
|
|
|
|
|
|
|
|
import shared.stores 1.0
|
|
|
|
import utils 1.0
|
|
|
|
|
|
|
|
import "types"
|
|
|
|
|
|
|
|
QObject {
|
|
|
|
id: root
|
|
|
|
|
|
|
|
required property WalletConnectSDKBase sdk
|
|
|
|
required property var walletStore
|
|
|
|
required property DAppsStore store
|
|
|
|
|
|
|
|
property alias requestsModel: requests
|
|
|
|
|
|
|
|
function rejectSessionRequest(request, userRejected) {
|
|
|
|
let error = userRejected ? false : true
|
|
|
|
sdk.rejectSessionRequest(request.topic, request.id, error)
|
|
|
|
}
|
|
|
|
|
2024-06-04 20:45:03 +00:00
|
|
|
/// 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)
|
|
|
|
}
|
|
|
|
|
2024-05-31 09:58:47 +00:00
|
|
|
signal sessionRequest(SessionRequestResolved request)
|
2024-06-04 20:45:03 +00:00
|
|
|
signal displayToastMessage(string message, bool error)
|
2024-06-07 16:54:19 +00:00
|
|
|
signal sessionRequestResult(/*model entry of SessionRequestResolved*/ var request, var payload, bool isSuccess)
|
2024-05-31 09:58:47 +00:00
|
|
|
|
|
|
|
/// Supported methods
|
|
|
|
property QtObject methods: QtObject {
|
2024-06-04 20:45:03 +00:00
|
|
|
readonly property QtObject personalSign: QtObject {
|
|
|
|
readonly property string name: Constants.personal_sign
|
|
|
|
readonly property string userString: qsTr("sign")
|
|
|
|
}
|
2024-06-07 16:54:19 +00:00
|
|
|
readonly property QtObject signTypedData_v4: QtObject {
|
|
|
|
readonly property string name: "eth_signTypedData_v4"
|
|
|
|
readonly property string userString: qsTr("sign typed data")
|
|
|
|
}
|
|
|
|
|
2024-06-04 20:45:03 +00:00
|
|
|
readonly property QtObject sendTransaction: QtObject {
|
|
|
|
readonly property string name: "eth_sendTransaction"
|
|
|
|
readonly property string userString: qsTr("send transaction")
|
|
|
|
}
|
2024-06-07 16:54:19 +00:00
|
|
|
readonly property var all: [personalSign, signTypedData_v4, sendTransaction]
|
2024-05-31 09:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getSupportedMethods() {
|
2024-06-04 20:45:03 +00:00
|
|
|
return methods.all.map(function(method) {
|
|
|
|
return method.name
|
|
|
|
})
|
2024-05-31 09:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Connections {
|
|
|
|
target: sdk
|
|
|
|
|
|
|
|
function onSessionRequestEvent(event) {
|
|
|
|
let obj = d.resolveAsync(event)
|
|
|
|
if (obj === null) {
|
|
|
|
let error = true
|
|
|
|
sdk.rejectSessionRequest(event.topic, event.id, error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
requests.enqueue(obj)
|
|
|
|
}
|
2024-06-04 20:45:03 +00:00
|
|
|
|
|
|
|
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)
|
2024-06-07 16:54:19 +00:00
|
|
|
|
|
|
|
root.sessionRequestResult(request, "", false /*isSuccessful*/)
|
|
|
|
|
2024-06-04 20:45:03 +00:00
|
|
|
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)
|
2024-06-07 16:54:19 +00:00
|
|
|
|
|
|
|
root.sessionRequestResult(request, "", true /*isSuccessful*/)
|
2024-06-04 20:45:03 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Connections {
|
|
|
|
target: root.store
|
|
|
|
|
2024-06-07 16:54:19 +00:00
|
|
|
function onUserAuthenticated(topic, id, password, pin) {
|
2024-06-04 20:45:03 +00:00
|
|
|
var request = requests.findRequest(topic, id)
|
|
|
|
if (request === null) {
|
|
|
|
console.error("Error finding event for topic", topic, "id", id)
|
|
|
|
return
|
|
|
|
}
|
2024-06-07 16:54:19 +00:00
|
|
|
d.executeSessionRequest(request, password, pin)
|
2024-06-04 20:45:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
2024-05-31 09:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QObject {
|
|
|
|
id: d
|
|
|
|
|
|
|
|
function resolveAsync(event) {
|
|
|
|
let method = event.params.request.method
|
|
|
|
let account = lookupAccountFromEvent(event, method)
|
|
|
|
let network = lookupNetworkFromEvent(event, method)
|
|
|
|
let data = extractMethodData(event, method)
|
|
|
|
let obj = sessionRequestComponent.createObject(null, {
|
|
|
|
event,
|
|
|
|
topic: event.topic,
|
|
|
|
id: event.id,
|
|
|
|
method,
|
|
|
|
account,
|
|
|
|
network,
|
|
|
|
data
|
|
|
|
})
|
|
|
|
if (obj === null) {
|
|
|
|
console.error("Error creating SessionRequestResolved for event")
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check later to have a valid request object
|
|
|
|
if (!getSupportedMethods().includes(method)
|
|
|
|
// TODO #14927: support method eth_sendTransaction
|
2024-06-04 20:45:03 +00:00
|
|
|
|| method == root.methods.sendTransaction.name) {
|
2024-05-31 09:58:47 +00:00
|
|
|
console.error("Unsupported method", method)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2024-06-04 20:45:03 +00:00
|
|
|
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)
|
2024-05-31 09:58:47 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns null if the account is not found
|
|
|
|
function lookupAccountFromEvent(event, method) {
|
2024-06-07 16:54:19 +00:00
|
|
|
var address = ""
|
2024-06-04 20:45:03 +00:00
|
|
|
if (method === root.methods.personalSign.name) {
|
2024-05-31 09:58:47 +00:00
|
|
|
if (event.params.request.params.length < 2) {
|
|
|
|
return null
|
|
|
|
}
|
2024-06-07 16:54:19 +00:00
|
|
|
address = event.params.request.params[1]
|
|
|
|
} else if(method === root.methods.signTypedData_v4.name) {
|
|
|
|
if (event.params.request.params.length < 2) {
|
|
|
|
return null
|
2024-05-31 09:58:47 +00:00
|
|
|
}
|
2024-06-07 16:54:19 +00:00
|
|
|
address = event.params.request.params[0]
|
2024-05-31 09:58:47 +00:00
|
|
|
}
|
2024-06-07 16:54:19 +00:00
|
|
|
return ModelUtils.getByKey(walletStore.ownAccounts, "address", address)
|
2024-05-31 09:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns null if the network is not found
|
|
|
|
function lookupNetworkFromEvent(event, method) {
|
2024-06-07 16:54:19 +00:00
|
|
|
if (method === root.methods.personalSign.name || method === root.methods.signTypedData_v4.name) {
|
2024-05-31 09:58:47 +00:00
|
|
|
let chainId = Helpers.chainIdFromEip155(event.params.chainId)
|
|
|
|
for (let i = 0; i < walletStore.flatNetworks.count; i++) {
|
|
|
|
let network = ModelUtils.get(walletStore.flatNetworks, i)
|
|
|
|
if (network.chainId === chainId) {
|
|
|
|
return network
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
function extractMethodData(event, method) {
|
2024-06-04 20:45:03 +00:00
|
|
|
if (method === root.methods.personalSign.name) {
|
2024-05-31 09:58:47 +00:00
|
|
|
if (event.params.request.params.length == 0) {
|
|
|
|
return null
|
|
|
|
}
|
2024-06-07 16:54:19 +00:00
|
|
|
var message = ""
|
|
|
|
let messageParam = event.params.request.params[0]
|
|
|
|
// There is no standard on how data is encoded. Therefore we support hex or utf8
|
|
|
|
if (Helpers.isHex(messageParam)) {
|
|
|
|
message = Helpers.hexToString(messageParam)
|
|
|
|
} else {
|
|
|
|
message = messageParam
|
|
|
|
}
|
|
|
|
return {message}
|
|
|
|
} else if (method === root.methods.signTypedData_v4.name) {
|
|
|
|
if (event.params.request.params.length < 2) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
let jsonMessage = event.params.request.params[1]
|
2024-05-31 09:58:47 +00:00
|
|
|
return {
|
2024-06-07 16:54:19 +00:00
|
|
|
message: jsonMessage
|
2024-05-31 09:58:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-04 20:45:03 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-07 16:54:19 +00:00
|
|
|
function executeSessionRequest(request, password, pin) {
|
|
|
|
if (request.method === root.methods.personalSign.name || request.method === root.methods.signTypedData_v4.name) {
|
|
|
|
if (password !== "") {
|
|
|
|
//let originalMessage = request.data.message
|
|
|
|
// TODO #14756: clarify why prefixing the message fails the test app https://react-app.walletconnect.com/
|
|
|
|
//let finalMessage = "\x19Ethereum Signed Message:\n" + originalMessage.length + originalMessage
|
|
|
|
let finalMessage = request.data.message
|
|
|
|
var signedMessage = ""
|
|
|
|
if (request.method === root.methods.personalSign.name) {
|
|
|
|
signedMessage = store.signMessage(request.topic, request.id,
|
|
|
|
request.account.address, password, finalMessage)
|
|
|
|
} else if (request.method === root.methods.signTypedData_v4.name) {
|
|
|
|
signedMessage = store.signTypedDataV4(request.topic, request.id,
|
|
|
|
request.account.address, password, finalMessage)
|
|
|
|
}
|
|
|
|
let isSuccessful = signedMessage != ""
|
|
|
|
if (isSuccessful) {
|
|
|
|
// acceptSessionRequest will trigger an sdk.sessionRequestUserAnswerResult signal
|
|
|
|
sdk.acceptSessionRequest(request.topic, request.id, signedMessage)
|
|
|
|
} else {
|
|
|
|
root.sessionRequestResult(request, request.data.message, isSuccessful)
|
|
|
|
}
|
|
|
|
} else if (pin !== "") {
|
|
|
|
console.debug("TODO #14927 sign message using keycard: ", request.data.message)
|
|
|
|
} else {
|
|
|
|
console.error("No password or pin provided to sign message")
|
|
|
|
}
|
2024-06-04 20:45:03 +00:00
|
|
|
} else {
|
|
|
|
console.error("Unsupported method to execute: ", request.method)
|
|
|
|
}
|
|
|
|
}
|
2024-05-31 09:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The queue is used to ensure that the events are processed in the order they are received but they could be
|
|
|
|
/// processed handled randomly on user intervention through activity center
|
|
|
|
SessionRequestsModel {
|
|
|
|
id: requests
|
|
|
|
}
|
|
|
|
|
|
|
|
Component {
|
|
|
|
id: sessionRequestComponent
|
|
|
|
|
|
|
|
SessionRequestResolved {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|