From 92d042044957e3a19e95ed3f04465ba42add3ced Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 18 Jun 2024 22:45:56 +0300 Subject: [PATCH] feat(dApps) implement sign transaction for wallet connect Uses status-go's endpoints: - `wallet_buildTransactions` to format the transaction - `wallet_signMessage` to sign the transaction - `wallet_buildRawTransaction` to format the final signed transaction Updates #15126 --- .../wallet_section/activity/controller.nim | 2 + .../wallet_connect/controller.nim | 3 ++ .../service/wallet_connect/service.nim | 39 ++++++++++++++++++- storybook/pages/DAppsWorkflowPage.qml | 8 +++- .../Wallet/panels/DAppsWorkflow.qml | 11 ++++++ .../services/dapps/DAppsRequestHandler.qml | 16 ++++++-- .../services/dapps/types/SessionRequest.qml | 2 +- .../popups/walletconnect/DAppRequestModal.qml | 2 +- ui/imports/shared/stores/DAppsStore.qml | 21 ++++++++++ 9 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/app/modules/main/wallet_section/activity/controller.nim b/src/app/modules/main/wallet_section/activity/controller.nim index e2962c97b9..0347e8cace 100644 --- a/src/app/modules/main/wallet_section/activity/controller.nim +++ b/src/app/modules/main/wallet_section/activity/controller.nim @@ -249,10 +249,12 @@ QtObject: # setup other event handlers self.eventsHandler.onFilteringDone(proc (jsonObj: JsonNode) = + echo "@dd eventsHandler.onFilteringDone: ", $jsonObj self.processResponse(jsonObj) ) self.eventsHandler.onFilteringUpdateDone(proc (jn: JsonNode) = + echo "@dd eventsHandler.onFilteringUpdateDone: ", $jn if jn.kind != JArray: error "expected an array" diff --git a/src/app/modules/shared_modules/wallet_connect/controller.nim b/src/app/modules/shared_modules/wallet_connect/controller.nim index 828478f823..e552785b72 100644 --- a/src/app/modules/shared_modules/wallet_connect/controller.nim +++ b/src/app/modules/shared_modules/wallet_connect/controller.nim @@ -55,3 +55,6 @@ QtObject: proc signTypedDataV4*(self: Controller, address: string, password: string, typedDataJson: string): string {.slot.} = return self.service.signTypedDataV4(address, password, typedDataJson) + + proc signTransaction*(self: Controller, address: string, chainId: int, password: string, txJson: string): string {.slot.} = + return self.service.signTransaction(address, chainId, password, txJson) diff --git a/src/app_service/service/wallet_connect/service.nim b/src/app_service/service/wallet_connect/service.nim index 2f460a290e..68d44956c1 100644 --- a/src/app_service/service/wallet_connect/service.nim +++ b/src/app_service/service/wallet_connect/service.nim @@ -1,8 +1,10 @@ -import NimQml, chronicles, times +import NimQml, chronicles, times, json import backend/wallet_connect as status_go +import backend/wallet import app_service/service/settings/service as settings_service +import app_service/common/wallet_constants import app/global/global_singleton @@ -92,3 +94,38 @@ QtObject: proc signTypedDataV4*(self: Service, address: string, password: string, typedDataJson: string): string = return status_go.signTypedData(address, password, typedDataJson) + + proc signTransaction*(self: Service, address: string, chainId: int, password: string, txJson: string): string = + var buildTxResponse: JsonNode + var err = wallet.buildTransaction(buildTxResponse, chainId, txJson) + if err.len > 0: + error "status-go - wallet_buildTransaction failed", err=err + return "" + if buildTxResponse.isNil or buildTxResponse.kind != JsonNodeKind.JObject or + not buildTxResponse.hasKey("txArgs") or not buildTxResponse.hasKey("messageToSign"): + error "unexpected buildTransaction response" + return "" + var txToBeSigned = buildTxResponse["messageToSign"].getStr + if txToBeSigned.len != wallet_constants.TX_HASH_LEN_WITH_PREFIX: + error "unexpected tx hash length" + return "" + + var signMsgRes: JsonNode + err = wallet.signMessage(signMsgRes, + txToBeSigned, + address, + hashPassword(password)) + if err.len > 0: + error "status-go - wallet_signMessage failed", err=err + let signature = singletonInstance.utils.removeHexPrefix(signMsgRes.getStr) + + var txResponse: JsonNode + err = wallet.buildRawTransaction(txResponse, chainId, $buildTxResponse["txArgs"], signature) + if err.len > 0: + error "status-go - wallet_buildRawTransaction failed", err=err + return "" + if txResponse.isNil or txResponse.kind != JsonNodeKind.JObject or not txResponse.hasKey("rawTx"): + error "unexpected buildRawTransaction response" + return "" + + return txResponse["rawTx"].getStr diff --git a/storybook/pages/DAppsWorkflowPage.qml b/storybook/pages/DAppsWorkflowPage.qml index 49cf2b8c45..7f6b259d3b 100644 --- a/storybook/pages/DAppsWorkflowPage.qml +++ b/storybook/pages/DAppsWorkflowPage.qml @@ -284,16 +284,20 @@ Item { // hardcoded for https://react-app.walletconnect.com/ function signMessage(topic, id, address, password, message) { + console.info(`calling mocked DAppsStore.signMessage(${topic}, ${id}, ${address}, ${password}, ${message})`) return "0x0b083acc1b3b612dd38e8e725b28ce9b2dd4936b4cf7922da4e4a3c6f44f7f4f6d3050ccb41455a2b85093f1bfadb10fc6a75d83bb590b2eb70e3447653459701c" } // hardcoded for https://react-app.walletconnect.com/ function signTypedDataV4(topic, id, address, password, typedDataJson) { + console.info(`calling mocked DAppsStore.signTypedDataV4(${topic}, ${id}, ${address}, ${password}, ${typedDataJson})`) return "0xf8ceb3468319cc215523b67c24c4504b3addd9bf8de31c278038d7478c9b6de554f7d8a516cd5d6a066b7d48b81f03d9d6bb7d5d754513c08325674ebcc7efbc1b" } - function signTransaction(topic, id, address, password, tx) { - return "0xf8ceb3468319cc215523b67c24c4504b3addd9bf8de31c278038d7478c9b6de554f7d8a516cd5d6a066b7d48b81f03d9d6bb7d5d754513c08325674ebcc7efbc1b" + // hardcoded for https://react-app.walletconnect.com/ + function signTransaction(topic, id, address, chainId, password, tx) { + console.info(`calling mocked DAppsStore.signTransaction(${topic}, ${id}, ${address}, ${chainId}, ${password}, ${tx})`) + return "0xf8672a8402fb7acf82520894e2d622c817878da5143bbe06866ca8e35273ba8a80808401546d71a04fc89c2f007c3b27d0fcff07d3e69c29f940967fab4caf525f9af72dadb48befa00c5312a3cb6f50328889ad361a0c88bb9d1b1a4fc510f6783b287930b4e187b5" } } diff --git a/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml b/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml index f497441785..e5aa2373eb 100644 --- a/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml +++ b/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml @@ -149,6 +149,17 @@ ConnectedDappsButton { root.wcService.requestHandler.rejectSessionRequest(request, userRejected) close() } + + Connections { + target: root.wcService.requestHandler + + function onMaxFeesUpdated(maxFees, symbol) { + maxFeesText = `${maxFees.toFixed(2)} ${symbol}` + } + function onEstimatedTimeUpdated(minMinutes, maxMinutes) { + estimatedTimeText = qsTr("%1-%2mins").arg(minMinutes).arg(maxMinutes) + } + } } } diff --git a/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml b/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml index 8befdf57c9..5848b700a4 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/DAppsRequestHandler.qml @@ -32,6 +32,8 @@ QObject { signal sessionRequest(SessionRequestResolved request) signal displayToastMessage(string message, bool error) signal sessionRequestResult(/*model entry of SessionRequestResolved*/ var request, bool isSuccess) + signal maxFeesUpdated(real maxFees, string symbol) + signal estimatedTimeUpdated(int minMinutes, int maxMinutes) Connections { target: sdk @@ -119,7 +121,9 @@ QObject { method, account, network, - data + data, + maxFeesText: "-", + estimatedTimeText: "-" }) if (obj === null) { console.error("Error creating SessionRequestResolved for event") @@ -141,6 +145,12 @@ QObject { } 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)); + root.maxFeesUpdated((gasLimit * gasPrice)/1000000000, "Gwei") + // TODO #15192: update estimatedTime + root.estimatedTimeUpdated(3, 12) }) return obj @@ -244,9 +254,9 @@ QObject { 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) signedMessage = store.signTransaction(request.topic, request.id, - request.account.address, password, - SessionRequest.methods.signTransaction.getTxFromData(request.data)) + request.account.address, request.network.chainId, password, txObj) } let isSuccessful = (signedMessage != "") if (isSuccessful) { diff --git a/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequest.qml b/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequest.qml index 8613c9b1fc..892356f85d 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequest.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/types/SessionRequest.qml @@ -30,7 +30,7 @@ QtObject { readonly property string requestDisplay: qsTr("sign this transaction") function buildDataObject(tx) { return {tx} } - function getTxFromData(data) { return data.tx } + function getTxObjFromData(data) { return data.tx } } readonly property QtObject sendTransaction: QtObject { diff --git a/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml b/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml index 57443cc185..ecc607f3a8 100644 --- a/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml +++ b/ui/imports/shared/popups/walletconnect/DAppRequestModal.qml @@ -443,7 +443,7 @@ StatusDialog { break } case SessionRequest.methods.signTransaction.name: { - let tx = SessionRequest.methods.signTransaction.getTxFromData(root.payloadData) + let tx = SessionRequest.methods.signTransaction.getTxObjFromData(root.payloadData) payloadToDisplay = JSON.stringify(tx, null, 2) userDisplayNaming = SessionRequest.methods.signTransaction.requestDisplay break diff --git a/ui/imports/shared/stores/DAppsStore.qml b/ui/imports/shared/stores/DAppsStore.qml index dac0ad9586..97e68c2971 100644 --- a/ui/imports/shared/stores/DAppsStore.qml +++ b/ui/imports/shared/stores/DAppsStore.qml @@ -33,6 +33,27 @@ QObject { return controller.signTypedDataV4(address, password, typedDataJson) } + // Remove leading zeros from hex number as expected by status-go + function stripLeadingZeros(hexNumber) { + let fixed = hexNumber.replace(/^0x0*/, '0x') + return fixed == '0x' ? '0x0' : fixed; + } + + // Returns the hex encoded signature of the transaction or empty string if error + function signTransaction(topic, id, address, chainId, password, txObj) { + // Strip leading zeros from numbers as expected by status-go + let tx = { + data: txObj.data, + from: txObj.from, + gasLimit: stripLeadingZeros(txObj.gasLimit), + gasPrice: stripLeadingZeros(txObj.gasPrice), + nonce: stripLeadingZeros(txObj.nonce), + to: txObj.to, + value: stripLeadingZeros(txObj.value) + } + return controller.signTransaction(address, chainId, password, JSON.stringify(tx)) + } + /// \c getDapps triggers an async response to \c dappsListReceived function getDapps() { return controller.getDapps()