status-desktop/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml

314 lines
13 KiB
QML
Raw Normal View History

import QtQuick 2.15
import AppLayouts.Wallet.services.dapps 1.0
import AppLayouts.Wallet.services.dapps.types 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)
}
/// 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(/*model entry of SessionRequestResolved*/ var request, bool isSuccess)
signal maxFeesUpdated(real maxFees, int maxFeesWei, bool haveEnoughFunds, string symbol)
signal estimatedTimeUpdated(int minMinutes, int maxMinutes)
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)
}
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 = SessionRequest.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)
root.sessionRequestResult(request, false /*isSuccessful*/)
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.sessionRequestResult(request, true /*isSuccessful*/)
})
}
}
Connections {
target: root.store
function onUserAuthenticated(topic, id, password, pin) {
var request = requests.findRequest(topic, id)
if (request === null) {
console.error("Error finding event for topic", topic, "id", id)
return
}
d.executeSessionRequest(request, password, pin)
}
function onUserAuthenticationFailed(topic, id) {
var request = requests.findRequest(topic, id)
let methodStr = SessionRequest.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)
})
}
}
QObject {
id: d
function resolveAsync(event) {
let method = event.params.request.method
let account = lookupAccountFromEvent(event, method)
if(!account) {
console.error("Error finding account for event", JSON.stringify(event))
return null
}
let network = lookupNetworkFromEvent(event, method)
if(!network) {
console.error("Error finding network for event", JSON.stringify(event))
return null
}
let data = extractMethodData(event, method)
if(!data) {
console.error("Error in event data lookup", JSON.stringify(event))
return null
}
let obj = sessionRequestComponent.createObject(null, {
event,
topic: event.topic,
id: event.id,
method,
account,
network,
data,
maxFeesText: "?",
maxFeesEthText: "?",
enoughFunds: false,
estimatedTimeText: "?"
})
if (obj === null) {
console.error("Error creating SessionRequestResolved for event")
return null
}
// Check later to have a valid request object
if (!SessionRequest.getSupportedMethods().includes(method)) {
console.error("Unsupported method", method)
return null
}
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)
// TODO #15192: update maxFees
let gasLimit = parseFloat(parseInt(event.params.request.params[0].gasLimit, 16));
let gasPrice = parseFloat(parseInt(event.params.request.params[0].gasPrice, 16));
let maxFees = gasLimit * gasPrice
root.maxFeesUpdated(maxFees/1000000000, maxFees, true, "Gwei")
// TODO #15192: update estimatedTime
root.estimatedTimeUpdated(1, 12)
})
return obj
}
/// Returns null if the account is not found
function lookupAccountFromEvent(event, method) {
var address = ""
if (method === SessionRequest.methods.personalSign.name) {
if (event.params.request.params.length < 2) {
return null
}
address = event.params.request.params[1]
} else if(method === SessionRequest.methods.signTypedData_v4.name) {
if (event.params.request.params.length < 2) {
return null
}
address = event.params.request.params[0]
} else if (method === SessionRequest.methods.signTransaction.name
|| method === SessionRequest.methods.sendTransaction.name) {
if (event.params.request.params.length == 0) {
return null
}
address = event.params.request.params[0].from
}
return ModelUtils.getFirstModelEntryIf(walletStore.ownAccounts, (account) => {
return account.address.toLowerCase() === address.toLowerCase()
})
}
/// Returns null if the network is not found
function lookupNetworkFromEvent(event, method) {
if (SessionRequest.getSupportedMethods().includes(method) === false) {
return null
}
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) {
if (method === SessionRequest.methods.personalSign.name) {
if (event.params.request.params.length == 0) {
return null
}
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 SessionRequest.methods.personalSign.buildDataObject(message)
} else if (method === SessionRequest.methods.signTypedData_v4.name) {
if (event.params.request.params.length < 2) {
return null
}
let jsonMessage = event.params.request.params[1]
return SessionRequest.methods.signTypedData_v4.buildDataObject(jsonMessage)
} else if (method === SessionRequest.methods.signTransaction.name) {
if (event.params.request.params.length == 0) {
return null
}
let tx = event.params.request.params[0]
return SessionRequest.methods.signTransaction.buildDataObject(tx)
} else if (method === SessionRequest.methods.sendTransaction.name) {
if (event.params.request.params.length == 0) {
return null
}
let tx = event.params.request.params[0]
return SessionRequest.methods.sendTransaction.buildDataObject(tx)
} else {
return null
}
}
function lookupSession(topicToLookup, callback) {
sdk.getActiveSessions((res) => {
Object.keys(res).forEach((topic) => {
if (topic === topicToLookup) {
let session = res[topic]
callback(session)
}
})
})
}
function executeSessionRequest(request, password, pin) {
if (!SessionRequest.getSupportedMethods().includes(request.method)) {
console.error("Unsupported method to execute: ", request.method)
return
}
if (password !== "") {
var actionResult = ""
if (request.method === SessionRequest.methods.personalSign.name) {
// 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
actionResult = store.signMessage(request.topic, request.id,
request.account.address, password,
SessionRequest.methods.personalSign.getMessageFromData(request.data))
} else if (request.method === SessionRequest.methods.signTypedData_v4.name) {
actionResult = store.signTypedDataV4(request.topic, request.id,
request.account.address, password,
SessionRequest.methods.signTypedData_v4.getMessageFromData(request.data))
} else if (request.method === SessionRequest.methods.signTransaction.name) {
let txObj = SessionRequest.methods.signTransaction.getTxObjFromData(request.data)
actionResult = store.signTransaction(request.topic, request.id,
request.account.address, request.network.chainId, password, txObj)
} else if (request.method === SessionRequest.methods.sendTransaction.name) {
let txObj = SessionRequest.methods.sendTransaction.getTxObjFromData(request.data)
actionResult = store.sendTransaction(request.topic, request.id,
request.account.address, request.network.chainId, password, txObj)
}
let isSuccessful = (actionResult != "")
if (isSuccessful) {
// acceptSessionRequest will trigger an sdk.sessionRequestUserAnswerResult signal
sdk.acceptSessionRequest(request.topic, request.id, actionResult)
} else {
root.sessionRequestResult(request, isSuccessful)
}
} else if (pin !== "") {
console.debug("TODO #15097 sign message using keycard: ", request.data)
} else {
console.error("No password or pin provided to sign message")
}
}
}
/// 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 {
}
}
}