feat(dapps) implement signing of messages
Implement infrastructure and integration with status-go to support general session requests Supported methods: - personal_sign - eth_signTypedData_v4 depends on status-go change that exposes the signing methods Also - support hex or utf8 encoding for personal_sign - format the typed data for display in the modal Tests are disabled for now, as they are crashing on CI Close: #14927
This commit is contained in:
parent
28a7b691cd
commit
758dbc55e5
|
@ -38,7 +38,7 @@ QtObject:
|
||||||
self.dappsListReceived(res)
|
self.dappsListReceived(res)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc userAuthenticationResult*(self: Controller, topic: string, id: string, error: bool) {.signal.}
|
proc userAuthenticationResult*(self: Controller, topic: string, id: string, error: bool, password: string, pin: string) {.signal.}
|
||||||
|
|
||||||
# Beware, it will fail if an authentication is already in progress
|
# Beware, it will fail if an authentication is already in progress
|
||||||
proc authenticateUser*(self: Controller, topic: string, id: string, address: string): bool {.slot.} =
|
proc authenticateUser*(self: Controller, topic: string, id: string, address: string): bool {.slot.} =
|
||||||
|
@ -46,6 +46,12 @@ QtObject:
|
||||||
if acc.keyUid == "":
|
if acc.keyUid == "":
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return self.service.authenticateUser(acc.keyUid, proc(success: bool) =
|
return self.service.authenticateUser(acc.keyUid, proc(password: string, pin: string, success: bool) =
|
||||||
self.userAuthenticationResult(topic, id, success)
|
self.userAuthenticationResult(topic, id, success, password, pin)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc signMessage*(self: Controller, address: string, password: string, message: string): string {.slot.} =
|
||||||
|
return self.service.signMessage(address, password, message)
|
||||||
|
|
||||||
|
proc signTypedDataV4*(self: Controller, address: string, password: string, typedDataJson: string): string {.slot.} =
|
||||||
|
return self.service.signTypedDataV4(address, password, typedDataJson)
|
||||||
|
|
|
@ -20,7 +20,7 @@ logScope:
|
||||||
const UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER* = "WalletSection-WCModule"
|
const UNIQUE_WALLET_CONNECT_MODULE_IDENTIFIER* = "WalletSection-WCModule"
|
||||||
|
|
||||||
type
|
type
|
||||||
AuthenticationResponseFn* = proc(success: bool)
|
AuthenticationResponseFn* = proc(password: string, pin: string, success: bool)
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type Service* = ref object of QObject
|
type Service* = ref object of QObject
|
||||||
|
@ -59,10 +59,10 @@ QtObject:
|
||||||
|
|
||||||
if args.password == "" and args.pin == "":
|
if args.password == "" and args.pin == "":
|
||||||
info "fail to authenticate user"
|
info "fail to authenticate user"
|
||||||
self.authenticationCallback(false)
|
self.authenticationCallback("", "", false)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.authenticationCallback(true)
|
self.authenticationCallback(args.password, args.pin, true)
|
||||||
|
|
||||||
proc addSession*(self: Service, session_json: string): bool =
|
proc addSession*(self: Service, session_json: string): bool =
|
||||||
# TODO #14588: call it async
|
# TODO #14588: call it async
|
||||||
|
@ -86,3 +86,9 @@ QtObject:
|
||||||
|
|
||||||
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
|
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
proc signMessage*(self: Service, address: string, password: string, message: string): string =
|
||||||
|
return status_go.signMessage(address, password, message)
|
||||||
|
|
||||||
|
proc signTypedDataV4*(self: Service, address: string, password: string, typedDataJson: string): string =
|
||||||
|
return status_go.signTypedData(address, password, typedDataJson)
|
||||||
|
|
|
@ -1,20 +1,32 @@
|
||||||
import options, logging
|
import options, logging
|
||||||
import json, json_serialization
|
import json, json_serialization
|
||||||
import core, response_type
|
import core, response_type
|
||||||
|
import strutils
|
||||||
|
|
||||||
from gen import rpc
|
from gen import rpc
|
||||||
import backend
|
import backend
|
||||||
|
|
||||||
|
import status_go
|
||||||
|
|
||||||
|
import app_service/service/community/dto/sign_params
|
||||||
|
|
||||||
|
import app_service/common/utils
|
||||||
|
|
||||||
rpc(addWalletConnectSession, "wallet"):
|
rpc(addWalletConnectSession, "wallet"):
|
||||||
sessionJson: string
|
sessionJson: string
|
||||||
|
|
||||||
proc isErrorResponse(rpcResponse: RpcResponse[JsonNode]): bool =
|
rpc(signTypedDataV4, "wallet"):
|
||||||
return not rpcResponse.error.isNil
|
typedJson: string
|
||||||
|
address: string
|
||||||
|
password: string
|
||||||
|
|
||||||
|
proc isSuccessResponse(rpcResponse: RpcResponse[JsonNode]): bool =
|
||||||
|
return rpcResponse.error.isNil
|
||||||
|
|
||||||
proc addSession*(sessionJson: string): bool =
|
proc addSession*(sessionJson: string): bool =
|
||||||
try:
|
try:
|
||||||
let rpcRes = addWalletConnectSession(sessionJson)
|
let rpcRes = addWalletConnectSession(sessionJson)
|
||||||
return isErrorResponse(rpcRes):
|
return isSuccessResponse(rpcRes):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
warn "AddWalletConnectSession failed: ", "msg", e.msg
|
warn "AddWalletConnectSession failed: ", "msg", e.msg
|
||||||
return false
|
return false
|
||||||
|
@ -33,3 +45,29 @@ proc getDapps*(validAtEpoch: int64, testChains: bool): string =
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
warn "GetWalletConnectDapps failed: ", "msg", e.msg
|
warn "GetWalletConnectDapps failed: ", "msg", e.msg
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
proc signMessage*(address: string, password: string, message: string): string =
|
||||||
|
try:
|
||||||
|
let signParams = SignParamsDto(address: address, password: hashPassword(password), data: "0x" & toHex(message))
|
||||||
|
let paramsStr = $toJson(signParams)
|
||||||
|
let rpcResRaw = status_go.signMessage(paramsStr)
|
||||||
|
|
||||||
|
let rpcRes = Json.decode(rpcResRaw, RpcResponse[JsonNode])
|
||||||
|
if(not rpcRes.error.isNil):
|
||||||
|
return ""
|
||||||
|
return rpcRes.result.getStr()
|
||||||
|
except Exception as e:
|
||||||
|
warn "status_go.signMessage failed: ", "msg", e.msg
|
||||||
|
return ""
|
||||||
|
|
||||||
|
proc signTypedData*(address: string, password: string, typedDataJson: string): string =
|
||||||
|
try:
|
||||||
|
let rpcRes = signTypedDataV4(typedDataJson, address, hashPassword(password))
|
||||||
|
|
||||||
|
if not isSuccessResponse(rpcRes):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return rpcRes.result.getStr()
|
||||||
|
except Exception as e:
|
||||||
|
warn "wallet_signTypedDataV4 failed: ", "msg", e.msg
|
||||||
|
return ""
|
||||||
|
|
|
@ -50,7 +50,8 @@ Item {
|
||||||
dappName: settings.dappName
|
dappName: settings.dappName
|
||||||
dappUrl: settings.dappUrl
|
dappUrl: settings.dappUrl
|
||||||
dappIcon: settings.dappIcon
|
dappIcon: settings.dappIcon
|
||||||
signContent: JSON.stringify(d.signTestContent, null, 2)
|
signContent: d.signTestContent
|
||||||
|
method: "eth_signTypedData_v4"
|
||||||
maxFeesText: "1.82 EUR"
|
maxFeesText: "1.82 EUR"
|
||||||
estimatedTimeText: "3-5 mins"
|
estimatedTimeText: "3-5 mins"
|
||||||
|
|
||||||
|
@ -128,36 +129,7 @@ Item {
|
||||||
|
|
||||||
readonly property var selectedNetwork: NetworksModel.flatNetworks.get(0)
|
readonly property var selectedNetwork: NetworksModel.flatNetworks.get(0)
|
||||||
|
|
||||||
readonly property var signTestContent: {
|
readonly property var signTestContent: "{\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallet\",\"type\":\"address\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person\"},{\"name\":\"contents\",\"type\":\"string\"}]},\"primaryType\":\"Mail\",\"domain\":{\"name\":\"Ether Mail\",\"version\":\"1\",\"chainId\":1,\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\"},\"message\":{\"from\":{\"name\":\"Cow\",\"wallet\":\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\"},\"to\":{\"name\":\"Bob\",\"wallet\":\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\"},\"contents\":\"Hello, Bob!\"}}"
|
||||||
"id": 1714038548266495,
|
|
||||||
"params": {
|
|
||||||
"chainld": "eip155:11155111",
|
|
||||||
"request": {
|
|
||||||
"expiryTimestamp": 1714038848,
|
|
||||||
"method": "eth_signTransaction",
|
|
||||||
"params": [
|
|
||||||
{
|
|
||||||
"data": "0x",
|
|
||||||
"from": "0xE2d622C817878dA5143bBE06866ca8E35273Ba8",
|
|
||||||
"gasLimit": "0x5208",
|
|
||||||
"gasPrice": "0xa677ef31",
|
|
||||||
"nonce": "0x27",
|
|
||||||
"to": "0xE2d622C817878dA5143bBE06866ca8E35273Ba8a",
|
|
||||||
"value": "0x00"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"topic": "a0f85b23a1f3a540d85760a523963165fb92169d57320c",
|
|
||||||
"verifyContext": {
|
|
||||||
"verified": {
|
|
||||||
"isScam": false,
|
|
||||||
"origin": "https://react-app.walletconnect.com/",
|
|
||||||
"validation": "VALID",
|
|
||||||
"verifyUrl": "https://verify.walletconnect.com/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -241,7 +241,7 @@ Item {
|
||||||
StatusButton {
|
StatusButton {
|
||||||
text: qsTr("Authenticate")
|
text: qsTr("Authenticate")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
walletConnectService.store.userAuthenticated(authMockDialog.topic, authMockDialog.id)
|
walletConnectService.store.userAuthenticated(authMockDialog.topic, authMockDialog.id, "0x1234567890", "123")
|
||||||
authMockDialog.close()
|
authMockDialog.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,9 +260,8 @@ Item {
|
||||||
|
|
||||||
store: DAppsStore {
|
store: DAppsStore {
|
||||||
signal dappsListReceived(string dappsJson)
|
signal dappsListReceived(string dappsJson)
|
||||||
signal userAuthenticated(string topic, string id)
|
signal userAuthenticated(string topic, string id, string password, string pin)
|
||||||
signal userAuthenticationFailed(string topic, string id)
|
signal userAuthenticationFailed(string topic, string id)
|
||||||
signal sessionRequestExecuted(var payload, bool success)
|
|
||||||
|
|
||||||
function addWalletConnectSession(sessionJson) {
|
function addWalletConnectSession(sessionJson) {
|
||||||
console.info("Persist Session", sessionJson)
|
console.info("Persist Session", sessionJson)
|
||||||
|
@ -276,6 +275,7 @@ Item {
|
||||||
"iconUrl": firstIconUrl
|
"iconUrl": firstIconUrl
|
||||||
}
|
}
|
||||||
d.persistedDapps.push(persistedDapp)
|
d.persistedDapps.push(persistedDapp)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDapps() {
|
function getDapps() {
|
||||||
|
@ -289,6 +289,16 @@ Item {
|
||||||
authMockDialog.open()
|
authMockDialog.open()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hardcoded for https://react-app.walletconnect.com/
|
||||||
|
function signMessage(topic, id, address, password, message) {
|
||||||
|
return "0x0b083acc1b3b612dd38e8e725b28ce9b2dd4936b4cf7922da4e4a3c6f44f7f4f6d3050ccb41455a2b85093f1bfadb10fc6a75d83bb590b2eb70e3447653459701c"
|
||||||
|
}
|
||||||
|
|
||||||
|
// hardcoded for https://react-app.walletconnect.com/
|
||||||
|
function signTypedDataV4(topic, id, address, password, typedDataJson) {
|
||||||
|
return "0xf8ceb3468319cc215523b67c24c4504b3addd9bf8de31c278038d7478c9b6de554f7d8a516cd5d6a066b7d48b81f03d9d6bb7d5d754513c08325674ebcc7efbc1b"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
walletStore: WalletStore {
|
walletStore: WalletStore {
|
||||||
|
|
|
@ -25,6 +25,7 @@ Item {
|
||||||
width: 600
|
width: 600
|
||||||
height: 400
|
height: 400
|
||||||
|
|
||||||
|
// TODO #15151 fix CI crash and re-enable tests
|
||||||
// Component {
|
// Component {
|
||||||
// id: sdkComponent
|
// id: sdkComponent
|
||||||
|
|
||||||
|
@ -79,9 +80,8 @@ Item {
|
||||||
|
|
||||||
// DAppsStore {
|
// DAppsStore {
|
||||||
// signal dappsListReceived(string dappsJson)
|
// signal dappsListReceived(string dappsJson)
|
||||||
// signal userAuthenticated(string topic, string id)
|
// signal userAuthenticated(string topic, string id, string password, string pin)
|
||||||
// signal userAuthenticationFailed(string topic, string id)
|
// signal userAuthenticationFailed(string topic, string id)
|
||||||
// signal sessionRequestExecuted(var payload, bool success)
|
|
||||||
|
|
||||||
// // By default, return no dapps in store
|
// // By default, return no dapps in store
|
||||||
// function getDapps() {
|
// function getDapps() {
|
||||||
|
@ -100,8 +100,12 @@ Item {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// property var signMessageCalls: []
|
// property var signMessageCalls: []
|
||||||
// function signMessage(message) {
|
// function signMessage(topic, id, address, password, message) {
|
||||||
// signMessageCalls.push({message})
|
// signMessageCalls.push({topic, id, address, password, message})
|
||||||
|
// }
|
||||||
|
// property var signTypedDataV4Calls: []
|
||||||
|
// function signTypedDataV4(topic, id, address, password, message) {
|
||||||
|
// signTypedDataV4Calls.push({topic, id, address, password, message})
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
@ -173,7 +177,7 @@ Item {
|
||||||
// compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser")
|
// compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser")
|
||||||
|
|
||||||
// let store = handler.store
|
// let store = handler.store
|
||||||
// store.userAuthenticated(td.topic, td.request.id)
|
// store.userAuthenticated(td.topic, td.request.id, "password", "")
|
||||||
// compare(store.signMessageCalls.length, 1, "expected a call to store.signMessage")
|
// compare(store.signMessageCalls.length, 1, "expected a call to store.signMessage")
|
||||||
// compare(store.signMessageCalls[0].message, td.request.data)
|
// compare(store.signMessageCalls[0].message, td.request.data)
|
||||||
// }
|
// }
|
||||||
|
@ -414,7 +418,7 @@ Item {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // Beware this TestCase should be last; I had it before ServiceHelpers and it was not run with `when: windowShown`
|
// TODO #15151: this TestCase if placed before ServiceHelpers was not run with `when: windowShown`. Check if related to the CI crash
|
||||||
// TestCase {
|
// TestCase {
|
||||||
// id: dappsWorkflowTest
|
// id: dappsWorkflowTest
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,7 @@ ConnectedDappsButton {
|
||||||
dappIcon: request.dappIcon
|
dappIcon: request.dappIcon
|
||||||
|
|
||||||
signContent: request.data.message
|
signContent: request.data.message
|
||||||
|
method: request.method
|
||||||
maxFeesText: request.maxFeesText
|
maxFeesText: request.maxFeesText
|
||||||
estimatedTimeText: request.estimatedTimeText
|
estimatedTimeText: request.estimatedTimeText
|
||||||
|
|
||||||
|
@ -155,9 +156,12 @@ ConnectedDappsButton {
|
||||||
Connections {
|
Connections {
|
||||||
target: root.wcService ? root.wcService.requestHandler : null
|
target: root.wcService ? root.wcService.requestHandler : null
|
||||||
|
|
||||||
function onSessionRequestResult(payload, isSuccess) {
|
function onSessionRequestResult(request, payload, isSuccess) {
|
||||||
// TODO #14927 handle this properly
|
if (isSuccess) {
|
||||||
sessionRequestLoader.active = false
|
sessionRequestLoader.active = false
|
||||||
|
} else {
|
||||||
|
// TODO #14762 handle the error case
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ QObject {
|
||||||
|
|
||||||
signal sessionRequest(SessionRequestResolved request)
|
signal sessionRequest(SessionRequestResolved request)
|
||||||
signal displayToastMessage(string message, bool error)
|
signal displayToastMessage(string message, bool error)
|
||||||
signal sessionRequestResult(var payload, bool isSuccess)
|
signal sessionRequestResult(/*model entry of SessionRequestResolved*/ var request, var payload, bool isSuccess)
|
||||||
|
|
||||||
/// Supported methods
|
/// Supported methods
|
||||||
property QtObject methods: QtObject {
|
property QtObject methods: QtObject {
|
||||||
|
@ -38,11 +38,16 @@ QObject {
|
||||||
readonly property string name: Constants.personal_sign
|
readonly property string name: Constants.personal_sign
|
||||||
readonly property string userString: qsTr("sign")
|
readonly property string userString: qsTr("sign")
|
||||||
}
|
}
|
||||||
|
readonly property QtObject signTypedData_v4: QtObject {
|
||||||
|
readonly property string name: "eth_signTypedData_v4"
|
||||||
|
readonly property string userString: qsTr("sign typed data")
|
||||||
|
}
|
||||||
|
|
||||||
readonly property QtObject sendTransaction: QtObject {
|
readonly property QtObject sendTransaction: QtObject {
|
||||||
readonly property string name: "eth_sendTransaction"
|
readonly property string name: "eth_sendTransaction"
|
||||||
readonly property string userString: qsTr("send transaction")
|
readonly property string userString: qsTr("send transaction")
|
||||||
}
|
}
|
||||||
readonly property var all: [personalSign, sendTransaction]
|
readonly property var all: [personalSign, signTypedData_v4, sendTransaction]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSupportedMethods() {
|
function getSupportedMethods() {
|
||||||
|
@ -81,14 +86,17 @@ QObject {
|
||||||
return
|
return
|
||||||
if (error) {
|
if (error) {
|
||||||
root.displayToastMessage(qsTr("Fail to %1 from %2").arg(methodStr).arg(session.peer.metadata.url), true)
|
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
|
|
||||||
|
root.sessionRequestResult(request, "", false /*isSuccessful*/)
|
||||||
|
|
||||||
console.error(`Error accepting session request for topic: ${topic}, id: ${id}, accept: ${accept}, error: ${error}`)
|
console.error(`Error accepting session request for topic: ${topic}, id: ${id}, accept: ${accept}, error: ${error}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let actionStr = accept ? qsTr("accepted") : qsTr("rejected")
|
let actionStr = accept ? qsTr("accepted") : qsTr("rejected")
|
||||||
root.displayToastMessage("%1 %2 %3".arg(session.peer.metadata.url).arg(methodStr).arg(actionStr), false)
|
root.displayToastMessage("%1 %2 %3".arg(session.peer.metadata.url).arg(methodStr).arg(actionStr), false)
|
||||||
root.sessionRequestApprovalResult()
|
|
||||||
|
root.sessionRequestResult(request, "", true /*isSuccessful*/)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,13 +104,13 @@ QObject {
|
||||||
Connections {
|
Connections {
|
||||||
target: root.store
|
target: root.store
|
||||||
|
|
||||||
function onUserAuthenticated(topic, id) {
|
function onUserAuthenticated(topic, id, password, pin) {
|
||||||
var request = requests.findRequest(topic, id)
|
var request = requests.findRequest(topic, id)
|
||||||
if (request === null) {
|
if (request === null) {
|
||||||
console.error("Error finding event for topic", topic, "id", id)
|
console.error("Error finding event for topic", topic, "id", id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.executeSessionRequest(request)
|
d.executeSessionRequest(request, password, pin)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUserAuthenticationFailed(topic, id) {
|
function onUserAuthenticationFailed(topic, id) {
|
||||||
|
@ -117,11 +125,6 @@ QObject {
|
||||||
root.displayToastMessage(qsTr("Failed to authenticate %1 from %2").arg(methodStr).arg(session.peer.metadata.url), true)
|
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 {
|
QObject {
|
||||||
|
@ -168,24 +171,24 @@ QObject {
|
||||||
|
|
||||||
/// Returns null if the account is not found
|
/// Returns null if the account is not found
|
||||||
function lookupAccountFromEvent(event, method) {
|
function lookupAccountFromEvent(event, method) {
|
||||||
|
var address = ""
|
||||||
if (method === root.methods.personalSign.name) {
|
if (method === root.methods.personalSign.name) {
|
||||||
if (event.params.request.params.length < 2) {
|
if (event.params.request.params.length < 2) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
var address = event.params.request.params[1]
|
address = event.params.request.params[1]
|
||||||
for (let i = 0; i < walletStore.ownAccounts.count; i++) {
|
} else if(method === root.methods.signTypedData_v4.name) {
|
||||||
let acc = ModelUtils.get(walletStore.ownAccounts, i)
|
if (event.params.request.params.length < 2) {
|
||||||
if (acc.address === address) {
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
address = event.params.request.params[0]
|
||||||
|
}
|
||||||
|
return ModelUtils.getByKey(walletStore.ownAccounts, "address", address)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns null if the network is not found
|
/// Returns null if the network is not found
|
||||||
function lookupNetworkFromEvent(event, method) {
|
function lookupNetworkFromEvent(event, method) {
|
||||||
if (method === root.methods.personalSign.name) {
|
if (method === root.methods.personalSign.name || method === root.methods.signTypedData_v4.name) {
|
||||||
let chainId = Helpers.chainIdFromEip155(event.params.chainId)
|
let chainId = Helpers.chainIdFromEip155(event.params.chainId)
|
||||||
for (let i = 0; i < walletStore.flatNetworks.count; i++) {
|
for (let i = 0; i < walletStore.flatNetworks.count; i++) {
|
||||||
let network = ModelUtils.get(walletStore.flatNetworks, i)
|
let network = ModelUtils.get(walletStore.flatNetworks, i)
|
||||||
|
@ -202,9 +205,22 @@ QObject {
|
||||||
if (event.params.request.params.length == 0) {
|
if (event.params.request.params.length == 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
let hexMessage = event.params.request.params[0]
|
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]
|
||||||
return {
|
return {
|
||||||
message: Helpers.hexToString(hexMessage)
|
message: jsonMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,10 +245,33 @@ QObject {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeSessionRequest(request) {
|
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) {
|
if (request.method === root.methods.personalSign.name) {
|
||||||
store.signMessage(request.data.message)
|
signedMessage = store.signMessage(request.topic, request.id,
|
||||||
console.debug("TODO #14927 sign message: ", request.data.message)
|
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")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error("Unsupported method to execute: ", request.method)
|
console.error("Unsupported method to execute: ", request.method)
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ WalletConnectSDKBase {
|
||||||
|
|
||||||
if (d.engine) {
|
if (d.engine) {
|
||||||
d.engine.runJavaScript(`wc.getActiveSessions()`, function(result) {
|
d.engine.runJavaScript(`wc.getActiveSessions()`, function(result) {
|
||||||
let allSessions = ""
|
var allSessions = ""
|
||||||
for (var key of Object.keys(result)) {
|
for (var key of Object.keys(result)) {
|
||||||
allSessions += `\nsessionTopic: ${key} relatedPairingTopic: ${result[key].pairingTopic}`;
|
allSessions += `\nsessionTopic: ${key} relatedPairingTopic: ${result[key].pairingTopic}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,9 @@ QObject {
|
||||||
root.displayToastMessage(qsTr("Connected to %1 via WalletConnect").arg(app_url), false)
|
root.displayToastMessage(qsTr("Connected to %1 via WalletConnect").arg(app_url), false)
|
||||||
|
|
||||||
// Persist session
|
// Persist session
|
||||||
store.addWalletConnectSession(JSON.stringify(session))
|
if(!store.addWalletConnectSession(JSON.stringify(session))) {
|
||||||
|
console.error("Failed to persist session")
|
||||||
|
}
|
||||||
|
|
||||||
// Notify client
|
// Notify client
|
||||||
root.approveSessionResult(session, err)
|
root.approveSessionResult(session, err)
|
||||||
|
|
|
@ -4,6 +4,10 @@ function chainIdFromEip155(chain) {
|
||||||
return parseInt(chain.split(':').pop().trim(), 10)
|
return parseInt(chain.split(':').pop().trim(), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isHex(str) {
|
||||||
|
return str.startsWith('0x') && str.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(str.slice(2))
|
||||||
|
}
|
||||||
|
|
||||||
function hexToString(hex) {
|
function hexToString(hex) {
|
||||||
if (hex.startsWith("0x")) {
|
if (hex.startsWith("0x")) {
|
||||||
hex = hex.substring(2);
|
hex = hex.substring(2);
|
||||||
|
|
|
@ -21,6 +21,7 @@ StatusDialog {
|
||||||
required property string dappName
|
required property string dappName
|
||||||
required property string dappUrl
|
required property string dappUrl
|
||||||
required property url dappIcon
|
required property url dappIcon
|
||||||
|
required property string method
|
||||||
required property string signContent
|
required property string signContent
|
||||||
required property string maxFeesText
|
required property string maxFeesText
|
||||||
required property string estimatedTimeText
|
required property string estimatedTimeText
|
||||||
|
@ -35,6 +36,10 @@ StatusDialog {
|
||||||
|
|
||||||
padding: 20
|
padding: 20
|
||||||
|
|
||||||
|
onSignContentChanged: d.updatePayloadToDisplay()
|
||||||
|
onMethodChanged: d.updatePayloadToDisplay()
|
||||||
|
Component.onCompleted: d.updatePayloadToDisplay()
|
||||||
|
|
||||||
contentItem: StatusScrollView {
|
contentItem: StatusScrollView {
|
||||||
id: scrollView
|
id: scrollView
|
||||||
padding: 0
|
padding: 0
|
||||||
|
@ -50,7 +55,6 @@ StatusDialog {
|
||||||
dappName: root.dappName
|
dappName: root.dappName
|
||||||
dappIcon: root.dappIcon
|
dappIcon: root.dappIcon
|
||||||
account: root.account
|
account: root.account
|
||||||
signContent: root.signContent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentPanel {
|
ContentPanel {
|
||||||
|
@ -273,7 +277,6 @@ StatusDialog {
|
||||||
required property string dappName
|
required property string dappName
|
||||||
required property url dappIcon
|
required property url dappIcon
|
||||||
required property var account
|
required property var account
|
||||||
required property string signContent
|
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
Item {
|
Item {
|
||||||
|
@ -405,10 +408,24 @@ StatusDialog {
|
||||||
|
|
||||||
width: contentScrollView.availableWidth
|
width: contentScrollView.availableWidth
|
||||||
|
|
||||||
text: signContent
|
text: d.payloadToDisplay
|
||||||
|
|
||||||
wrapMode: Text.WrapAnywhere
|
wrapMode: Text.WrapAnywhere
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
|
||||||
|
property string payloadToDisplay: ""
|
||||||
|
|
||||||
|
function updatePayloadToDisplay() {
|
||||||
|
if (root.method === "eth_signTypedData_v4" && root.signContent) {
|
||||||
|
payloadToDisplay = JSON.stringify(JSON.parse(root.signContent), null, 2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payloadToDisplay = root.signContent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,11 @@ QObject {
|
||||||
|
|
||||||
/// \c dappsJson serialized from status-go.wallet.GetDapps
|
/// \c dappsJson serialized from status-go.wallet.GetDapps
|
||||||
signal dappsListReceived(string dappsJson)
|
signal dappsListReceived(string dappsJson)
|
||||||
signal userAuthenticated(string topic, string id)
|
signal userAuthenticated(string topic, string id, string password, string pin)
|
||||||
signal userAuthenticationFailed(string topic, string id)
|
signal userAuthenticationFailed(string topic, string id)
|
||||||
signal sessionRequestExecuted(var payload, bool success)
|
|
||||||
|
|
||||||
function addWalletConnectSession(sessionJson) {
|
function addWalletConnectSession(sessionJson) {
|
||||||
controller.addWalletConnectSession(sessionJson)
|
return controller.addWalletConnectSession(sessionJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
function authenticateUser(topic, id, address) {
|
function authenticateUser(topic, id, address) {
|
||||||
|
@ -24,9 +23,14 @@ QObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function signMessage(message) {
|
// Returns the hex encoded signature of the message or empty string if error
|
||||||
// TODO #14927 implement me
|
function signMessage(topic, id, address, password, message) {
|
||||||
root.sessionRequestExecuted(message, true)
|
return controller.signMessage(address, password, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the hex encoded signature of the typedDataJson or empty string if error
|
||||||
|
function signTypedDataV4(topic, id, address, password, typedDataJson) {
|
||||||
|
return controller.signTypedDataV4(address, password, typedDataJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \c getDapps triggers an async response to \c dappsListReceived
|
/// \c getDapps triggers an async response to \c dappsListReceived
|
||||||
|
@ -42,9 +46,9 @@ QObject {
|
||||||
root.dappsListReceived(dappsJson)
|
root.dappsListReceived(dappsJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUserAuthenticationResult(topic, id, success) {
|
function onUserAuthenticationResult(topic, id, success, password, pin) {
|
||||||
if (success) {
|
if (success) {
|
||||||
root.userAuthenticated(topic, id)
|
root.userAuthenticated(topic, id, password, pin)
|
||||||
} else {
|
} else {
|
||||||
root.userAuthenticationFailed(topic, id)
|
root.userAuthenticationFailed(topic, id)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue