fix: 1:1 chat command transactions "intrinsic gas too low"

I noticed that the 1:1 chat commands were not able to send token transactions due to "intrinsic gas too low" error. I quickly realised there there were a few components missing, which have been fixed.

*feat: update the 1:1 chat commands transaction modal to allow editing of the from account and network fee*

The TransactionStackGroup was updated slightly to allow manual control of back/next actions.

Fixes #870.

*fix: Create distinct modal transaction actions*

Previously, adding `Connection`s for the  `walletModel.transactionWasSent` signal in different dialogs would cause the signal to be handled in the wrong dialog. The solution was to pass a `uuid` from the requesting dialog, and include the `uuid` in the response, so that only requests that were requested from the dialog would be handled.

*fix: update 1:1 translations*
All the translations were not being translated for me. I noticed that they did not exist in the `.ts` translation files either.
This commit is contained in:
emizzle 2020-10-07 13:47:21 +11:00 committed by Pascal Precht
parent 0a13940742
commit 423882df89
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
13 changed files with 252 additions and 127 deletions

View File

@ -280,18 +280,18 @@ QtObject:
proc transactionSent(self: WalletView, txResult: string) {.slot.} =
self.transactionWasSent(txResult)
proc sendTransaction*(self: WalletView, from_addr: string, to: string, assetAddress: string, value: string, gas: string, gasPrice: string, password: string) {.slot.} =
proc sendTransaction*(self: WalletView, from_addr: string, to: string, assetAddress: string, value: string, gas: string, gasPrice: string, password: string, uuid: string) {.slot.} =
let wallet = self.status.wallet
if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace:
spawnAndSend(self, "transactionSent") do:
var success: bool
let response = wallet.sendTokenTransaction(from_addr, to, assetAddress, value, gas, gasPrice, password, success)
$(%* { "result": %response, "success": %success })
$(%* { "result": %response, "success": %success, "uuid": %uuid })
else:
spawnAndSend(self, "transactionSent") do:
var success: bool
let response = wallet.sendTransaction(from_addr, to, value, gas, gasPrice, password, success)
$(%* { "result": %response, "success": %success })
$(%* { "result": %response, "success": %success, "uuid": %uuid })
proc getDefaultAccount*(self: WalletView): string {.slot.} =
self.currentAccount.address

View File

@ -44,7 +44,11 @@ ModalPopup {
currency: walletModel.defaultCurrency
width: stack.width
//% "From account"
label: qsTrId("from-account")
label: {
return root.isRequested ?
qsTr("Receive on account") :
qsTrId("from-account")
}
reset: function() {
accounts = Qt.binding(function() { return walletModel.accounts })
selectedAccount = Qt.binding(function() { return walletModel.currentAccount })
@ -54,13 +58,13 @@ ModalPopup {
id: separator
anchors.top: selectFromAccount.bottom
anchors.topMargin: 19
icon.rotation: root.isRequested ? -90 : 90
}
RecipientSelector {
id: selectRecipient
accounts: walletModel.accounts
contacts: profileModel.addedContacts
//% "Recipient"
label: qsTrId("recipient")
label: qsTr("From")
readOnly: true
anchors.top: separator.bottom
anchors.topMargin: 10
@ -114,6 +118,7 @@ ModalPopup {
SVGImage {
width: 16
height: 16
visible: warningText.visible
source: "../../../../img/warning.svg"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: warningText.top
@ -122,6 +127,7 @@ ModalPopup {
StyledText {
id: warningText
visible: !root.isRequested
//% "You need to request the recipients address first.\nAssets wont be sent yet."
text: qsTrId("you-need-to-request-the-recipient-s-address-first--nassets-won-t-be-sent-yet-")
color: Style.current.danger
@ -139,29 +145,14 @@ ModalPopup {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
StyledButton {
StatusRoundButton {
id: btnBack
anchors.left: parent.left
width: 44
height: 44
visible: !stack.isFirstGroup
label: ""
background: Rectangle {
anchors.fill: parent
border.width: 0
radius: width / 2
color: btnBack.hovered ? Qt.darker(btnBack.btnColor, 1.1) : btnBack.btnColor
SVGImage {
width: 20.42
height: 15.75
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../../../../img/arrow-right.svg"
rotation: 180
}
}
icon.name: "arrow-right"
icon.width: 20
icon.height: 16
rotation: 180
onClicked: {
stack.back()
}

View File

@ -24,7 +24,8 @@ ModalPopup {
root.selectedAmount,
selectedGasLimit,
selectedGasPrice,
enteredPassword)
enteredPassword,
stack.uuid)
let response = JSON.parse(responseStr)
@ -67,13 +68,14 @@ ModalPopup {
anchors.fill: parent
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
initialItem: groupPreview
isLastGroup: stack.currentGroup === groupSignTx
onGroupActivated: {
root.title = group.headerText
btnNext.text = group.footerText
}
TransactionFormGroup {
id: group1
//% "Send"
id: groupSelectAcct
headerText: {
if(trxData.startsWith("0x095ea7b3")){
const approveData = JSON.parse(walletModel.decodeTokenApproval(selectedRecipient.address, trxData))
@ -82,9 +84,54 @@ ModalPopup {
}
return qsTrId("command-button-send");
}
footerText: qsTr("Continue")
showNextBtn: false
onBackClicked: function() {
if(validate()) {
stack.pop()
}
}
AccountSelector {
id: selectFromAccount
accounts: walletModel.accounts
selectedAccount: root.selectedAccount
currency: walletModel.defaultCurrency
width: stack.width
//% "Choose account"
label: qsTrId("choose-account")
showBalanceForAssetSymbol: root.selectedAsset.symbol
minRequiredAssetBalance: parseFloat(root.selectedAmount)
reset: function() {
accounts = Qt.binding(function() { return walletModel.accounts })
selectedAccount = Qt.binding(function() { return root.selectedAccount })
showBalanceForAssetSymbol = Qt.binding(function() { return root.selectedAsset.symbol })
minRequiredAssetBalance = Qt.binding(function() { return parseFloat(root.selectedAmount) })
}
onSelectedAccountChanged: if (isValid) { gasSelector.estimateGas() }
}
RecipientSelector {
id: selectRecipient
visible: false
accounts: walletModel.accounts
contacts: profileModel.addedContacts
selectedRecipient: root.selectedRecipient
readOnly: true
reset: function() {
accounts = Qt.binding(function() { return walletModel.accounts })
contacts = Qt.binding(function() { return profileModel.addedContacts })
selectedRecipient = Qt.binding(function() { return root.selectedRecipient })
}
}
}
TransactionFormGroup {
id: groupSelectGas
headerText: qsTr("Network fee")
//% "Preview"
footerText: qsTrId("preview")
showNextBtn: false
onBackClicked: function() {
stack.pop()
}
GasSelector {
id: gasSelector
anchors.topMargin: Style.current.bigPadding
@ -98,17 +145,19 @@ ModalPopup {
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
}
function estimateGas() {
console.log("CALLING ESTIMATE GAS")
if (!(root.selectedAccount && root.selectedAccount.address &&
root.selectedRecipient && root.selectedRecipient.address &&
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address &&
selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address &&
root.selectedAsset && root.selectedAsset.address &&
root.selectedAmount)) return
root.selectedAmount)) {
selectedGasLimit = 250000
return
}
let gasEstimate = JSON.parse(walletModel.estimateGas(
root.selectedAccount.address,
root.selectedRecipient.address,
selectFromAccount.selectedAccount.address,
selectRecipient.selectedRecipient.address,
root.selectedAsset.address,
root.selectedAmount,
trxData))
@ -119,7 +168,7 @@ ModalPopup {
return
}
selectedGasLimit = gasEstimate.result
}
})
}
GasValidator {
id: gasValidator
@ -130,16 +179,24 @@ ModalPopup {
selectedAsset: root.selectedAsset
selectedGasEthValue: gasSelector.selectedGasEthValue
reset: function() {
selectedAccount = Qt.binding(function() { return root.selectedAccount })
selectedAmount = Qt.binding(function() { return parseFloat(root.selectedAmount) })
selectedAsset = Qt.binding(function() { return root.selectedAsset })
selectedGasEthValue = Qt.binding(function() { return gasSelector.selectedGasEthValue })
}
}
}
TransactionFormGroup {
id: group2
id: groupPreview
//% "Transaction preview"
headerText: qsTrId("transaction-preview")
//% "Sign with password"
footerText: qsTrId("sign-with-password")
showBackBtn: false
onNextClicked: function() {
stack.push(groupSignTx, StackView.Immediate)
}
TransactionPreview {
id: pvwTransaction
@ -155,6 +212,7 @@ ModalPopup {
amount: { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount }
currency: walletModel.defaultCurrency
reset: function() {
fromAccount = Qt.binding(function() { return root.selectedAccount })
gas = Qt.binding(function() {
return {
"value": gasSelector.selectedGasEthValue,
@ -162,15 +220,23 @@ ModalPopup {
"fiatValue": gasSelector.selectedGasFiatValue
}
})
toAccount = Qt.binding(function() { return root.selectedRecipient })
asset = Qt.binding(function() { return root.selectedAsset })
amount = Qt.binding(function() { return { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount } })
}
onFromClicked: stack.push(groupSelectAcct, StackView.Immediate)
onGasClicked: stack.push(groupSelectGas, StackView.Immediate)
}
}
TransactionFormGroup {
id: group3
id: groupSignTx
//% "Sign with password"
headerText: qsTrId("sign-with-password")
//% "Send %1 %2"
footerText: qsTrId("send--1--2").arg(root.selectedAmount).arg(!!root.selectedAsset ? root.selectedAsset.symbol : "")
onBackClicked: function() {
stack.pop()
}
TransactionSigner {
id: transactionSigner
@ -187,30 +253,19 @@ ModalPopup {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
StyledButton {
StatusRoundButton {
id: btnBack
anchors.left: parent.left
width: 44
height: 44
visible: !stack.isFirstGroup
label: ""
background: Rectangle {
anchors.fill: parent
border.width: 0
radius: width / 2
color: btnBack.hovered ? Qt.darker(btnBack.btnColor, 1.1) : btnBack.btnColor
SVGImage {
width: 20.42
height: 15.75
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../../../../img/arrow-right.svg"
rotation: 180
}
}
icon.name: "arrow-right"
icon.width: 20
icon.height: 16
rotation: 180
visible: stack.currentGroup.showBackBtn
enabled: stack.currentGroup.isValid || stack.isLastGroup
onClicked: {
if (typeof stack.currentGroup.onBackClicked === "function") {
return stack.currentGroup.onBackClicked()
}
stack.back()
}
}
@ -220,6 +275,7 @@ ModalPopup {
//% "Next"
text: qsTrId("next")
enabled: stack.currentGroup.isValid && !stack.currentGroup.isPending
visible: stack.currentGroup.showNextBtn
onClicked: {
const validity = stack.currentGroup.validate()
if (validity.isValid && !validity.isPending) {
@ -228,10 +284,47 @@ ModalPopup {
gasSelector.selectedGasPrice,
transactionSigner.enteredPassword)
}
if (typeof stack.currentGroup.onNextClicked === "function") {
return stack.currentGroup.onNextClicked()
}
stack.next()
}
}
}
Connections {
target: walletModel
onTransactionWasSent: {
try {
let response = JSON.parse(txResult)
if (response.uuid !== stack.uuid) return
stack.currentGroup.isPending = false
if (!response.success) {
if (response.result.includes("could not decrypt key with given password")){
//% "Wrong password"
transactionSigner.validationError = qsTrId("wrong-password")
return
}
sendingError.text = response.result
return sendingError.open()
}
//% "Transaction pending..."
toastMessage.title = qsTrId("ens-transaction-pending")
toastMessage.source = "../../img/loading.svg"
toastMessage.iconColor = Style.current.primary
toastMessage.iconRotates = true
toastMessage.link = `${walletModel.etherscanLink}/${response.result}`
toastMessage.open()
root.close()
} catch (e) {
console.error('Error parsing the response', e)
}
}
}
}
}

View File

@ -14,8 +14,7 @@ Item {
StyledText {
id: signText
color: Style.current.blue
//% "Sign and send"
text: qsTrId("sign-and-send")
text: qsTr("Sign and send")
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
font.weight: Font.Medium

View File

@ -55,26 +55,18 @@ Rectangle {
}
text: {
switch (root.state) {
//% "Pending"
case Constants.pending: return qsTrId("pending")
//% "Confirmed"
case Constants.confirmed: return qsTrId("status-confirmed")
//% "Unknown token"
case Constants.unknown: return qsTrId("unknown-token")
//% "Address requested"
case Constants.addressRequested: return qsTrId("address-requested")
//% "Waiting to accept"
case Constants.transactionRequested: return qsTrId("waiting-to-accept")
//% "Address shared"
//% "Address received"
case Constants.addressReceived: return (!root.outgoing ? qsTrId("address-shared") : qsTrId("address-received"))
case Constants.pending: return qsTr("Pending")
case Constants.confirmed: return qsTr("Confirmed")
case Constants.unknown: return qsTr("Unknown token")
case Constants.addressRequested: return qsTr("Address requested")
case Constants.transactionRequested: return qsTr("Waiting to accept")
case Constants.addressReceived: return (!root.outgoing ?
qsTr("Address shared") :
qsTr("Address received"))
case Constants.transactionDeclined:
//% "Transaction declined"
case Constants.declined: return qsTrId("transaction-declined")
//% "Failure"
case Constants.failure: return qsTrId("failure")
//% "Unknown state"
default: return qsTrId("unknown-state")
case Constants.declined: return qsTr("Transaction declined")
case Constants.failure: return qsTr("failure")
default: return qsTr("Unknown state")
}
}
font.weight: Font.Medium

View File

@ -81,7 +81,7 @@ ModalPopup {
showBalanceForAssetSymbol = Qt.binding(function() { return root.asset.symbol })
minRequiredAssetBalance = Qt.binding(function() { return root.ensPrice })
}
onSelectedAccountChanged: gasSelector.estimateGas()
onSelectedAccountChanged: if (isValid) { gasSelector.estimateGas() }
}
RecipientSelector {
id: selectRecipient
@ -90,7 +90,7 @@ ModalPopup {
contacts: profileModel.addedContacts
selectedRecipient: { "address": utilsModel.ensRegisterAddress, "type": RecipientSelector.Type.Address }
readOnly: true
onSelectedRecipientChanged: gasSelector.estimateGas()
onSelectedRecipientChanged: if (isValid) { gasSelector.estimateGas() }
}
GasSelector {
id: gasSelector

View File

@ -85,7 +85,7 @@ ModalPopup {
showBalanceForAssetSymbol = Qt.binding(function() { return "ETH" })
minRequiredAssetBalance = Qt.binding(function() { return 0 })
}
onSelectedAccountChanged: gasSelector.estimateGas()
onSelectedAccountChanged: if (isValid) { gasSelector.estimateGas() }
}
RecipientSelector {
id: selectRecipient
@ -94,7 +94,7 @@ ModalPopup {
contacts: profileModel.addedContacts
selectedRecipient: { "address": utilsModel.ensRegisterAddress, "type": RecipientSelector.Type.Address }
readOnly: true
onSelectedRecipientChanged: gasSelector.estimateGas()
onSelectedRecipientChanged: if (isValid) { gasSelector.estimateGas() }
}
GasSelector {
id: gasSelector

View File

@ -35,7 +35,8 @@ ModalPopup {
txtAmount.selectedAmount,
gasSelector.selectedGasLimit,
gasSelector.selectedGasPrice,
transactionSigner.enteredPassword)
transactionSigner.enteredPassword,
stack.uuid)
}
TransactionStackView {
@ -66,7 +67,7 @@ ModalPopup {
accounts = Qt.binding(function() { return walletModel.accounts })
selectedAccount = Qt.binding(function() { return walletModel.currentAccount })
}
onSelectedAccountChanged: gasSelector.estimateGas()
onSelectedAccountChanged: if (isValid) { gasSelector.estimateGas() }
}
SeparatorWithIcon {
id: separator
@ -87,7 +88,7 @@ ModalPopup {
contacts = Qt.binding(function() { return profileModel.addedContacts })
selectedRecipient = {}
}
onSelectedRecipientChanged: gasSelector.estimateGas()
onSelectedRecipientChanged: if (isValid) { gasSelector.estimateGas() }
}
}
TransactionFormGroup {
@ -107,8 +108,8 @@ ModalPopup {
reset: function() {
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
}
onSelectedAssetChanged: gasSelector.estimateGas()
onSelectedAmountChanged: gasSelector.estimateGas()
onSelectedAssetChanged: if (isValid) { gasSelector.estimateGas() }
onSelectedAmountChanged: if (isValid) { gasSelector.estimateGas() }
}
GasSelector {
id: gasSelector
@ -225,30 +226,14 @@ ModalPopup {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
StyledButton {
StatusRoundButton {
id: btnBack
anchors.left: parent.left
width: 44
height: 44
visible: !stack.isFirstGroup
label: ""
background: Rectangle {
anchors.fill: parent
border.width: 0
radius: width / 2
color: btnBack.disabled ? Style.current.grey :
btnBack.hovered ? Qt.darker(btnBack.btnColor, 1.1) : btnBack.btnColor
SVGImage {
width: 20.42
height: 15.75
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../../img/arrow-right.svg"
rotation: 180
}
}
icon.name: "arrow-right"
icon.width: 20
icon.height: 16
rotation: 180
onClicked: {
stack.back()
}
@ -275,9 +260,12 @@ ModalPopup {
target: walletModel
onTransactionWasSent: {
try {
stack.currentGroup.isPending = false
let response = JSON.parse(txResult)
if (response.uuid !== stack.uuid) return
stack.currentGroup.isPending = false
if (!response.success) {
if (response.result.includes("could not decrypt key with given password")){
//% "Wrong password"

View File

@ -167,4 +167,8 @@ QtObject {
let wordCount = countWords(text);
return qsTr("%1%2 words").arg(getTick(wordCount)).arg(wordCount.toString());
}
function uuid() {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 5)
}
}

View File

@ -6,4 +6,8 @@ FormGroup {
id: root
property string headerText
property string footerText
property bool showBackBtn: true
property bool showNextBtn: true
property var onBackClicked
property var onNextClicked
}

View File

@ -15,6 +15,8 @@ Item {
property var gas
height: content.height
property var reset: function() {}
signal fromClicked
signal gasClicked
function resetInternal() {
fromAccount = undefined
@ -53,15 +55,39 @@ Item {
id: imgFromWallet
sourceSize.height: 18
sourceSize.width: 18
anchors.right: parent.right
anchors.right: fromArrow.visible ? fromArrow.left : parent.right
anchors.rightMargin: fromArrow.visible ? Style.current.padding : 0
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/walletIcon.svg"
ColorOverlay {
anchors.fill: parent
source: parent
color: root.fromAccount ? root.fromAccount.iconColor : Style.current.blue
}
}
ColorOverlay {
anchors.fill: imgFromWallet
source: imgFromWallet
color: root.fromAccount ? root.fromAccount.iconColor : Style.current.blue
SVGImage {
id: fromArrow
width: 13
visible: typeof root.fromClicked === "function"
anchors.right: parent.right
anchors.rightMargin: 7
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/caret.svg"
rotation: 270
ColorOverlay {
anchors.fill: parent
visible: parent.visible
source: parent
color: Style.current.secondaryText
}
}
MouseArea {
anchors.fill: parent
visible: fromArrow.visible
cursorShape: Qt.PointingHandCursor
onClicked: root.fromClicked()
}
}
}
@ -316,6 +342,7 @@ Item {
id: itmNetworkFee
//% "Network fee"
label: qsTrId("network-fee")
visible: !!root.gas
value: Item {
id: networkFeeRoot
anchors.fill: parent
@ -362,11 +389,35 @@ Item {
height: 22
text: root.currency.toUpperCase()
color: Style.current.secondaryText
anchors.right: parent.right
anchors.right: gasArrow.visible ? gasArrow.left : parent.right
anchors.rightMargin: gasArrow.visible ? Style.current.padding : 0
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
}
SVGImage {
id: gasArrow
width: 13
visible: typeof root.gasClicked === "function"
anchors.right: parent.right
anchors.rightMargin: 7
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/caret.svg"
rotation: 270
ColorOverlay {
anchors.fill: parent
visible: parent.visible
source: parent
color: Style.current.secondaryText
}
}
MouseArea {
anchors.fill: parent
visible: gasArrow.visible
cursorShape: Qt.PointingHandCursor
onClicked: root.gasClicked()
}
}
}
}

View File

@ -10,13 +10,14 @@ StackView {
property bool isFirstGroup: currentIdx === 0
signal groupActivated(Item group)
property alias currentGroup: root.currentItem
readonly property string uuid: Utils.uuid()
property var next: function() {
if (groups && groups.length <= currentIdx + 1) {
return
}
const group = groups[++currentIdx]
this.push(group, StackView.Immediate)
}
property var back: function() {
if (currentIdx <= 0) {

View File

@ -85,7 +85,7 @@ ModalPopup {
showBalanceForAssetSymbol = Qt.binding(function() { return root.asset.symbol })
minRequiredAssetBalance = Qt.binding(function() { return root.packPrice })
}
onSelectedAccountChanged: gasSelector.estimateGas()
onSelectedAccountChanged: if (isValid) { gasSelector.estimateGas() }
}
RecipientSelector {
id: selectRecipient
@ -94,7 +94,7 @@ ModalPopup {
contacts: profileModel.addedContacts
selectedRecipient: { "address": utilsModel.stickerMarketAddress, "type": RecipientSelector.Type.Address }
readOnly: true
onSelectedRecipientChanged: gasSelector.estimateGas()
onSelectedRecipientChanged: if (isValid) { gasSelector.estimateGas() }
}
GasSelector {
id: gasSelector
@ -196,11 +196,13 @@ ModalPopup {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
StyledButton {
StatusRoundButton {
id: btnBack
anchors.left: parent.left
//% "Back"
label: qsTrId("back")
icon.name: "arrow-right"
icon.width: 20
icon.height: 16
rotation: 180
onClicked: {
if (stack.isFirstGroup) {
return root.close()