mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-18 09:37:59 +00:00
feat: enable sending an ETH transaction from the browser
This commit is contained in:
parent
3bc2c07042
commit
56d6ece3e9
@ -1,5 +1,5 @@
|
|||||||
import NimQml
|
import NimQml
|
||||||
import ../../status/[status, ens, chat/stickers]
|
import ../../status/[status, ens, chat/stickers, wallet]
|
||||||
import ../../status/libstatus/types
|
import ../../status/libstatus/types
|
||||||
import ../../status/libstatus/core
|
import ../../status/libstatus/core
|
||||||
import ../../status/libstatus/settings as status_settings
|
import ../../status/libstatus/settings as status_settings
|
||||||
@ -109,13 +109,37 @@ QtObject:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if SIGN_METHODS.contains(data.payload.rpcMethod):
|
if SIGN_METHODS.contains(data.payload.rpcMethod):
|
||||||
return $ %* { # TODO: send transaction, return transaction hash, etc etc. Disabled in the meantime
|
try:
|
||||||
"type": ResponseTypes.Web3SendAsyncCallback,
|
let request = data.request.parseJson
|
||||||
"messageId": data.messageId,
|
let fromAddress = request["params"][0]["from"].getStr()
|
||||||
"error": {
|
let to = request["params"][0]["to"].getStr()
|
||||||
"code": 4100
|
let value = request["params"][0]["value"].getStr()
|
||||||
|
let password = request["password"].getStr()
|
||||||
|
let selectedGasLimit = request["selectedGasLimit"].getStr()
|
||||||
|
let selectedGasPrice = request["selectedGasPrice"].getStr()
|
||||||
|
|
||||||
|
var success: bool
|
||||||
|
# TODO make this async
|
||||||
|
let response = status.wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, password, success)
|
||||||
|
debug "Response", response, success
|
||||||
|
return $ %* {
|
||||||
|
"type": ResponseTypes.Web3SendAsyncCallback,
|
||||||
|
"messageId": data.messageId,
|
||||||
|
# TODO do we get an error code?
|
||||||
|
"error": (if response == "" or not success: newJString("web3-response-error") else: newJNull()),
|
||||||
|
"result": response
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
error "Error sending the transaction", msg = e.msg
|
||||||
|
return $ %* {
|
||||||
|
"type": ResponseTypes.Web3SendAsyncCallback,
|
||||||
|
"messageId": data.messageId,
|
||||||
|
"error": {
|
||||||
|
# TODO where does the code come from?
|
||||||
|
"code": 4100,
|
||||||
|
"message": e.msg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ACC_METHODS.contains(data.payload.rpcMethod):
|
if ACC_METHODS.contains(data.payload.rpcMethod):
|
||||||
let dappAddress = status_settings.getSetting[string](Setting.DappsAddress)
|
let dappAddress = status_settings.getSetting[string](Setting.DappsAddress)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import NimQml, os, strformat, strutils, parseUtils
|
import NimQml, os, strformat, strutils, parseUtils, chronicles
|
||||||
import stint
|
import stint
|
||||||
import ../../status/status
|
import ../../status/status
|
||||||
import ../../status/stickers
|
import ../../status/stickers
|
||||||
@ -46,7 +46,10 @@ QtObject:
|
|||||||
return uintValue.toString()
|
return uintValue.toString()
|
||||||
|
|
||||||
proc wei2Token*(self: UtilsView, wei: string, decimals: int): string {.slot.} =
|
proc wei2Token*(self: UtilsView, wei: string, decimals: int): string {.slot.} =
|
||||||
return status_utils.wei2Token(wei, decimals)
|
var weiValue = wei
|
||||||
|
if(weiValue.startsWith("0x")):
|
||||||
|
weiValue = fromHex(Stuint[256], weiValue).toString()
|
||||||
|
return status_utils.wei2Token(weiValue, decimals)
|
||||||
|
|
||||||
proc getStickerMarketAddress(self: UtilsView): string {.slot.} =
|
proc getStickerMarketAddress(self: UtilsView): string {.slot.} =
|
||||||
$self.status.stickers.getStickerMarketAddress
|
$self.status.stickers.getStickerMarketAddress
|
||||||
|
@ -122,22 +122,31 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
property Component sendTransactionModalComponent: SendTransactionModal {}
|
||||||
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: provider
|
id: provider
|
||||||
WebChannel.id: "backend"
|
WebChannel.id: "backend"
|
||||||
|
|
||||||
signal web3Response(string data);
|
signal web3Response(string data);
|
||||||
|
|
||||||
function postMessage(data){
|
function postMessage(data) {
|
||||||
var request = JSON.parse(data)
|
var request;
|
||||||
|
try {
|
||||||
|
request = JSON.parse(data)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing the message data", e)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var ensAddr = urlENSDictionary[request.hostname];
|
var ensAddr = urlENSDictionary[request.hostname];
|
||||||
if(ensAddr){
|
if (ensAddr) {
|
||||||
request.hostname = ensAddr;
|
request.hostname = ensAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(request.type === Constants.api_request){
|
if (request.type === Constants.api_request) {
|
||||||
if(!web3Provider.hasPermission(request.hostname, request.permission)){
|
if (!web3Provider.hasPermission(request.hostname, request.permission)) {
|
||||||
var dialog = accessDialogComponent.createObject(browserWindow);
|
var dialog = accessDialogComponent.createObject(browserWindow);
|
||||||
dialog.request = request;
|
dialog.request = request;
|
||||||
dialog.open();
|
dialog.open();
|
||||||
@ -145,6 +154,12 @@ Item {
|
|||||||
request.isAllowed = true;
|
request.isAllowed = true;
|
||||||
web3Response(web3Provider.postMessage(JSON.stringify(request)));
|
web3Response(web3Provider.postMessage(JSON.stringify(request)));
|
||||||
}
|
}
|
||||||
|
} else if (request.type === Constants.web3SendAsyncReadOnly &&
|
||||||
|
request.payload.method === "eth_sendTransaction") {
|
||||||
|
const sendDialog = sendTransactionModalComponent.createObject(browserWindow);
|
||||||
|
sendDialog.request = request;
|
||||||
|
sendDialog.open();
|
||||||
|
walletModel.getGasPricePredictions()
|
||||||
} else {
|
} else {
|
||||||
web3Response(web3Provider.postMessage(data));
|
web3Response(web3Provider.postMessage(data));
|
||||||
}
|
}
|
||||||
|
187
ui/app/AppLayouts/Browser/SendTransactionModal.qml
Normal file
187
ui/app/AppLayouts/Browser/SendTransactionModal.qml
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import QtQuick 2.13
|
||||||
|
import "../../../shared"
|
||||||
|
import "../../../imports"
|
||||||
|
|
||||||
|
ModalPopup {
|
||||||
|
id: popup
|
||||||
|
|
||||||
|
property var request: ({
|
||||||
|
"type": "web3-send-async-read-only",
|
||||||
|
"messageId": 13,
|
||||||
|
"payload": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 19,
|
||||||
|
"method": "eth_sendTransaction",
|
||||||
|
"params": [{
|
||||||
|
"to": "0x2127edab5d08b1e11adf7ae4bae16c2b33fdf74a",
|
||||||
|
"value": "0x9184e72a000",
|
||||||
|
"from": "0x2dcb8515ea98701614919cb82d30876780936a76"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"hostname": "ciqhsxa6udhk6tho3smjn4kloo5tb2ly4scv5yrbxgsx6wutijucqdq.infura.status.im",
|
||||||
|
"title": "DAPP"
|
||||||
|
})
|
||||||
|
property string fromAccount: request.payload.params[0].from
|
||||||
|
property string toAccount: request.payload.params[0].to
|
||||||
|
property string value: {
|
||||||
|
// TODO get decimals
|
||||||
|
let val = utilsModel.wei2Token(request.payload.params[0].value, 18)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
function postMessage(isAllowed) {
|
||||||
|
request.isAllowed = isAllowed;
|
||||||
|
provider.web3Response(web3Provider.postMessage(JSON.stringify(request)));
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
popup.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
title: qsTr("Confirm transaction")
|
||||||
|
height: 600
|
||||||
|
|
||||||
|
property string passwordValidationError: ""
|
||||||
|
property bool loading: false
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
if (passwordInput.text === "") {
|
||||||
|
//% "You need to enter a password"
|
||||||
|
passwordValidationError = qsTrId("you-need-to-enter-a-password")
|
||||||
|
} else if (passwordInput.text.length < 4) {
|
||||||
|
//% "Password needs to be 4 characters or more"
|
||||||
|
passwordValidationError = qsTrId("password-needs-to-be-4-characters-or-more")
|
||||||
|
} else {
|
||||||
|
passwordValidationError = ""
|
||||||
|
}
|
||||||
|
return passwordValidationError === ""
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
passwordInput.text = ""
|
||||||
|
passwordInput.forceActiveFocus(Qt.MouseFocusReason)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Style.current.smallPadding
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
TextWithLabel {
|
||||||
|
label: qsTr("From")
|
||||||
|
text: fromAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
TextWithLabel {
|
||||||
|
label: qsTr("To")
|
||||||
|
text: toAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
TextWithLabel {
|
||||||
|
label: qsTr("Value")
|
||||||
|
text: popup.value
|
||||||
|
}
|
||||||
|
|
||||||
|
Input {
|
||||||
|
id: passwordInput
|
||||||
|
//% "Enter your password…"
|
||||||
|
placeholderText: qsTrId("enter-your-password…")
|
||||||
|
//% "Password"
|
||||||
|
label: qsTrId("password")
|
||||||
|
textField.echoMode: TextInput.Password
|
||||||
|
validationError: popup.passwordValidationError
|
||||||
|
}
|
||||||
|
|
||||||
|
GasSelector {
|
||||||
|
id: gasSelector
|
||||||
|
slowestGasPrice: parseFloat(walletModel.safeLowGasPrice)
|
||||||
|
fastestGasPrice: parseFloat(walletModel.fastestGasPrice)
|
||||||
|
getGasEthValue: walletModel.getGasEthValue
|
||||||
|
getFiatValue: walletModel.getFiatValue
|
||||||
|
defaultCurrency: walletModel.defaultCurrency
|
||||||
|
width: parent.width
|
||||||
|
reset: function() {
|
||||||
|
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
|
||||||
|
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
|
||||||
|
}
|
||||||
|
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
|
||||||
|
if (!(fromAccount &&
|
||||||
|
toAccount &&
|
||||||
|
// TODO support asset
|
||||||
|
// txtAmount.selectedAsset && txtAmount.selectedAsset.address &&
|
||||||
|
popup.value)) return
|
||||||
|
|
||||||
|
let gasEstimate = JSON.parse(walletModel.estimateGas(
|
||||||
|
fromAccount,
|
||||||
|
toAccount,
|
||||||
|
// TODO support other assets
|
||||||
|
Constants.zeroAddress,
|
||||||
|
popup.value))
|
||||||
|
|
||||||
|
if (!gasEstimate.success) {
|
||||||
|
//% "Error estimating gas: %1"
|
||||||
|
console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedGasLimit = gasEstimate.result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// TODO find where to get the assets
|
||||||
|
// GasValidator {
|
||||||
|
// id: gasValidator
|
||||||
|
// selectedAccount: request.payload.params[0].from
|
||||||
|
// selectedAmount: parseFloat(txtAmount.selectedAmount)
|
||||||
|
// selectedAsset: txtAmount.selectedAsset
|
||||||
|
// selectedGasEthValue: gasSelector.selectedGasEthValue
|
||||||
|
// reset: function() {
|
||||||
|
// selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
|
||||||
|
// selectedAmount = Qt.binding(function() { return parseFloat(txtAmount.selectedAmount) })
|
||||||
|
// selectedAsset = Qt.binding(function() { return txtAmount.selectedAsset })
|
||||||
|
// selectedGasEthValue = Qt.binding(function() { return gasSelector.selectedGasEthValue })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
StyledButton {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Style.current.padding
|
||||||
|
label: qsTr("Cancel")
|
||||||
|
disabled: loading
|
||||||
|
onClicked: {
|
||||||
|
// Do we need to send back an error?
|
||||||
|
popup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledButton {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Style.current.padding
|
||||||
|
label: loading ?
|
||||||
|
//% "Loading..."
|
||||||
|
qsTrId("loading") :
|
||||||
|
qsTr("Confirm")
|
||||||
|
|
||||||
|
disabled: loading || passwordInput.text === ""
|
||||||
|
|
||||||
|
onClicked : {
|
||||||
|
loading = true
|
||||||
|
if (!validate()) {
|
||||||
|
return loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
request.payload.selectedGasLimit = gasSelector.selectedGasLimit
|
||||||
|
request.payload.selectedGasPrice = gasSelector.selectedGasPrice
|
||||||
|
request.payload.password = passwordInput.text
|
||||||
|
request.payload.params[0].value = popup.value
|
||||||
|
provider.web3Response(web3Provider.postMessage(JSON.stringify(request)));
|
||||||
|
loading = false
|
||||||
|
|
||||||
|
popup.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -60,6 +60,7 @@ QtObject {
|
|||||||
|
|
||||||
|
|
||||||
readonly property string api_request: "api-request"
|
readonly property string api_request: "api-request"
|
||||||
|
readonly property string web3SendAsyncReadOnly: "web3-send-async-read-only"
|
||||||
|
|
||||||
readonly property string permission_web3: "web3"
|
readonly property string permission_web3: "web3"
|
||||||
readonly property string permission_contactCode: "contact-code"
|
readonly property string permission_contactCode: "contact-code"
|
||||||
|
@ -121,6 +121,7 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
|
|||||||
!isEmpty(target.path): INSTALLS += target
|
!isEmpty(target.path): INSTALLS += target
|
||||||
|
|
||||||
DISTFILES += \
|
DISTFILES += \
|
||||||
|
app/AppLayouts/Browser/SendTransactionModal.qml \
|
||||||
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandButton.qml \
|
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandButton.qml \
|
||||||
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandModal.qml \
|
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandModal.qml \
|
||||||
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandsPopup.qml \
|
app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandsPopup.qml \
|
||||||
@ -201,6 +202,9 @@ DISTFILES += \
|
|||||||
fonts/InterStatus/InterStatus-ThinItalic.otf \
|
fonts/InterStatus/InterStatus-ThinItalic.otf \
|
||||||
Theme.qml \
|
Theme.qml \
|
||||||
app/AppLayouts/Browser/BrowserLayout.qml \
|
app/AppLayouts/Browser/BrowserLayout.qml \
|
||||||
|
app/AppLayouts/Browser/BrowserDialog.qml \
|
||||||
|
app/AppLayouts/Browser/DownloadView.qml \
|
||||||
|
app/AppLayouts/Browser/FindBar.qml \
|
||||||
app/AppLayouts/Chat/ChatColumn.qml \
|
app/AppLayouts/Chat/ChatColumn.qml \
|
||||||
app/AppLayouts/Chat/ChatColumn/samples/MessagesData.qml \
|
app/AppLayouts/Chat/ChatColumn/samples/MessagesData.qml \
|
||||||
app/AppLayouts/Chat/ChatColumn/samples/StickerData.qml \
|
app/AppLayouts/Chat/ChatColumn/samples/StickerData.qml \
|
||||||
|
Loading…
x
Reference in New Issue
Block a user