feat(dapps) make wallet connect request data human readable
Notify user if he doesn't hold enough funds to make the transaction Also check fees funds including the amount to be sent Closes: #15192
This commit is contained in:
parent
c89203f371
commit
e57f7552d9
|
@ -222,8 +222,13 @@ QtObject:
|
||||||
else:
|
else:
|
||||||
maxFeePerGas = chainFees.maxFeePerGasL
|
maxFeePerGas = chainFees.maxFeePerGasL
|
||||||
else:
|
else:
|
||||||
let maxFeePerGasInt = parseHexInt(maxFeePerGasHex)
|
try:
|
||||||
maxFeePerGas = maxFeePerGasInt.float
|
let maxFeePerGasInt = parseHexInt(maxFeePerGasHex)
|
||||||
|
maxFeePerGas = maxFeePerGasInt.float
|
||||||
|
except ValueError:
|
||||||
|
error "failed to parse maxFeePerGasHex", maxFeePerGasHex
|
||||||
|
return EstimatedTime.Unknown
|
||||||
|
|
||||||
return self.transactions.getEstimatedTime(chainId, $(maxFeePerGas))
|
return self.transactions.getEstimatedTime(chainId, $(maxFeePerGas))
|
||||||
|
|
||||||
proc getSuggestedFees*(self: Service, chainId: int): SuggestedFeesDto =
|
proc getSuggestedFees*(self: Service, chainId: int): SuggestedFeesDto =
|
||||||
|
|
|
@ -134,9 +134,17 @@ Item {
|
||||||
l1GasFee: 0.0,
|
l1GasFee: 0.0,
|
||||||
eip1559Enabled: true
|
eip1559Enabled: true
|
||||||
})
|
})
|
||||||
|
|
||||||
function getSuggestedFees() {
|
function getSuggestedFees() {
|
||||||
return mockedSuggestedFees
|
return mockedSuggestedFees
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hexToDec(hex) {
|
||||||
|
if (hex.length > "0xfffffffffffff".length) {
|
||||||
|
console.warn(`Beware of possible loss of precision converting ${hex}`)
|
||||||
|
}
|
||||||
|
return parseInt(hex, 16).toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -804,9 +812,9 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockSessionRequestEvent(tc, sdk, accountsModel, networksMdodel) {
|
function mockSessionRequestEvent(tc, sdk, accountsModel, networksModel) {
|
||||||
let account = accountsModel.get(1)
|
let account = accountsModel.get(1)
|
||||||
let network = networksMdodel.get(1)
|
let network = networksModel.get(1)
|
||||||
let method = "personal_sign"
|
let method = "personal_sign"
|
||||||
let message = "hello world"
|
let message = "hello world"
|
||||||
let params = [`"${Helpers.strToHex(message)}"`, `"${account.address}"`]
|
let params = [`"${Helpers.strToHex(message)}"`, `"${account.address}"`]
|
||||||
|
@ -819,7 +827,8 @@ Item {
|
||||||
method: Constants.personal_sign,
|
method: Constants.personal_sign,
|
||||||
account,
|
account,
|
||||||
network,
|
network,
|
||||||
data: message
|
data: message,
|
||||||
|
preparedData: message
|
||||||
})
|
})
|
||||||
// Expect to have calls to getActiveSessions from service initialization
|
// Expect to have calls to getActiveSessions from service initialization
|
||||||
let prevRequests = sdk.getActiveSessionsCallbacks.length
|
let prevRequests = sdk.getActiveSessionsCallbacks.length
|
||||||
|
|
|
@ -140,6 +140,16 @@ QtObject {
|
||||||
return amount1.plus(amount2)
|
return amount1.plus(amount2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlmethod AmountsArithmetic::sub(amount1, amount2)
|
||||||
|
\brief Returns a Big number whose value is the subtraction of amount2 from amount1.
|
||||||
|
*/
|
||||||
|
function sub(amount1, amount2) {
|
||||||
|
console.assert(amount1 instanceof Big.Big)
|
||||||
|
console.assert(amount2 instanceof Big.Big || Number.isInteger(amount2))
|
||||||
|
return amount1.minus(amount2)
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod AmountsArithmetic::cmp(amount1, amount2)
|
\qmlmethod AmountsArithmetic::cmp(amount1, amount2)
|
||||||
\brief Compares two amounts.
|
\brief Compares two amounts.
|
||||||
|
|
|
@ -183,33 +183,9 @@ DappsComboBox {
|
||||||
enoughFundsForTransaction: request.enoughFunds
|
enoughFundsForTransaction: request.enoughFunds
|
||||||
enoughFundsForFees: request.enoughFunds
|
enoughFundsForFees: request.enoughFunds
|
||||||
|
|
||||||
signingTransaction: !!request.method && (request.method === SessionRequest.methods.signTransaction.name || request.method === SessionRequest.methods.sendTransaction.name)
|
signingTransaction: !!request.method && (request.method === SessionRequest.methods.signTransaction.name
|
||||||
requestPayload: {
|
|| request.method === SessionRequest.methods.sendTransaction.name)
|
||||||
switch(request.method) {
|
requestPayload: request.preparedData
|
||||||
case SessionRequest.methods.personalSign.name:
|
|
||||||
return SessionRequest.methods.personalSign.getMessageFromData(request.data)
|
|
||||||
case SessionRequest.methods.sign.name: {
|
|
||||||
return SessionRequest.methods.sign.getMessageFromData(request.data)
|
|
||||||
}
|
|
||||||
case SessionRequest.methods.signTypedData_v4.name: {
|
|
||||||
const stringPayload = SessionRequest.methods.signTypedData_v4.getMessageFromData(request.data)
|
|
||||||
return JSON.stringify(JSON.parse(stringPayload), null, 2)
|
|
||||||
}
|
|
||||||
case SessionRequest.methods.signTypedData.name: {
|
|
||||||
const stringPayload = SessionRequest.methods.signTypedData.getMessageFromData(root.payloadData)
|
|
||||||
return JSON.stringify(JSON.parse(stringPayload), null, 2)
|
|
||||||
}
|
|
||||||
case SessionRequest.methods.signTransaction.name: {
|
|
||||||
const jsonPayload = SessionRequest.methods.signTransaction.getTxObjFromData(request.data)
|
|
||||||
return JSON.stringify(jsonPayload, null, 2)
|
|
||||||
}
|
|
||||||
case SessionRequest.methods.sendTransaction.name: {
|
|
||||||
const jsonPayload = SessionRequest.methods.sendTransaction.getTxObjFromData(request.data)
|
|
||||||
return JSON.stringify(jsonPayload, null, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClosed: {
|
onClosed: {
|
||||||
Qt.callLater( () => {
|
Qt.callLater( () => {
|
||||||
rejectRequest()
|
rejectRequest()
|
||||||
|
|
|
@ -132,6 +132,9 @@ SQUtils.QObject {
|
||||||
console.error("Error in event data lookup", JSON.stringify(event))
|
console.error("Error in event data lookup", JSON.stringify(event))
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const interpreted = d.prepareData(method, data)
|
||||||
|
|
||||||
let enoughFunds = !d.isTransactionMethod(method)
|
let enoughFunds = !d.isTransactionMethod(method)
|
||||||
let obj = sessionRequestComponent.createObject(null, {
|
let obj = sessionRequestComponent.createObject(null, {
|
||||||
event,
|
event,
|
||||||
|
@ -141,6 +144,7 @@ SQUtils.QObject {
|
||||||
account,
|
account,
|
||||||
network,
|
network,
|
||||||
data,
|
data,
|
||||||
|
preparedData: interpreted.preparedData,
|
||||||
maxFeesText: "?",
|
maxFeesText: "?",
|
||||||
maxFeesEthText: "?",
|
maxFeesEthText: "?",
|
||||||
enoughFunds: enoughFunds,
|
enoughFunds: enoughFunds,
|
||||||
|
@ -180,7 +184,7 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
let st = getEstimatedFeesStatus(data, method, obj.network.chainId, mainChainId)
|
let st = getEstimatedFeesStatus(data, method, obj.network.chainId, mainChainId)
|
||||||
|
|
||||||
let fundsStatus = checkFundsStatus(st.feesInfo.maxFees, st.feesInfo.l1GasFee, account.address, obj.network.chainId, mainNet.chainId)
|
let fundsStatus = checkFundsStatus(st.feesInfo.maxFees, st.feesInfo.l1GasFee, account.address, obj.network.chainId, mainNet.chainId, interpreted.value)
|
||||||
|
|
||||||
root.maxFeesUpdated(st.fiatMaxFees.toNumber(), st.maxFeesEth, fundsStatus.haveEnoughFunds,
|
root.maxFeesUpdated(st.fiatMaxFees.toNumber(), st.maxFeesEth, fundsStatus.haveEnoughFunds,
|
||||||
fundsStatus.haveEnoughForFees, st.symbol, st.feesInfo)
|
fundsStatus.haveEnoughForFees, st.symbol, st.feesInfo)
|
||||||
|
@ -436,7 +440,7 @@ SQUtils.QObject {
|
||||||
let feesInfo = getEstimatedMaxFees(data, method, chainId, mainNetChainId)
|
let feesInfo = getEstimatedMaxFees(data, method, chainId, mainNetChainId)
|
||||||
|
|
||||||
let totalMaxFees = Math.sum(feesInfo.maxFees, feesInfo.l1GasFee)
|
let totalMaxFees = Math.sum(feesInfo.maxFees, feesInfo.l1GasFee)
|
||||||
let maxFeesEth = Math.div(totalMaxFees, Math.fromString("1000000000"))
|
let maxFeesEth = Math.div(totalMaxFees, Math.fromNumber(1, 9))
|
||||||
|
|
||||||
let maxFeesEthStr = maxFeesEth.toString()
|
let maxFeesEthStr = maxFeesEth.toString()
|
||||||
let fiatMaxFeesStr = root.currenciesStore.getFiatValue(maxFeesEthStr, Constants.ethToken)
|
let fiatMaxFeesStr = root.currenciesStore.getFiatValue(maxFeesEthStr, Constants.ethToken)
|
||||||
|
@ -446,27 +450,24 @@ SQUtils.QObject {
|
||||||
return {fiatMaxFees, maxFeesEth, symbol, feesInfo}
|
return {fiatMaxFees, maxFeesEth, symbol, feesInfo}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkBalanceForChain(balances, address, chainId, fees) {
|
function getBalanceInEth(balances, address, chainId) {
|
||||||
let Math = SQUtils.AmountsArithmetic
|
const Math = SQUtils.AmountsArithmetic
|
||||||
let accEth = SQUtils.ModelUtils.getFirstModelEntryIf(balances, (balance) => {
|
let accEth = SQUtils.ModelUtils.getFirstModelEntryIf(balances, (balance) => {
|
||||||
return balance.account.toLowerCase() === address.toLowerCase() && balance.chainId === chainId
|
return balance.account.toLowerCase() === address.toLowerCase() && balance.chainId === chainId
|
||||||
})
|
})
|
||||||
if (!accEth) {
|
if (!accEth) {
|
||||||
console.error("Error balance lookup for account ", address, " on chain ", chainId)
|
console.error("Error balance lookup for account ", address, " on chain ", chainId)
|
||||||
return {haveEnoughForFees, haveEnoughFunds}
|
return null
|
||||||
}
|
}
|
||||||
let accountFundsWei = Math.fromString(accEth.balance)
|
let accountFundsWei = Math.fromString(accEth.balance)
|
||||||
let accountFundsEth = Math.div(accountFundsWei, Math.fromString("1000000000000000000"))
|
return Math.div(accountFundsWei, Math.fromNumber(1, 18))
|
||||||
|
|
||||||
let feesEth = Math.div(fees, Math.fromString("1000000000"))
|
|
||||||
return Math.cmp(accountFundsEth, feesEth) >= 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFundsStatus(maxFees, l1GasFee, address, chainId, mainNetChainId) {
|
// Returns {haveEnoughForFees, haveEnoughFunds} and true in case of error not to block request
|
||||||
|
function checkFundsStatus(maxFees, l1GasFee, address, chainId, mainNetChainId, valueEth) {
|
||||||
let Math = SQUtils.AmountsArithmetic
|
let Math = SQUtils.AmountsArithmetic
|
||||||
|
|
||||||
let haveEnoughForFees = false
|
let haveEnoughForFees = true
|
||||||
// TODO #15192: extract funds from transaction and check against it
|
|
||||||
let haveEnoughFunds = true
|
let haveEnoughFunds = true
|
||||||
|
|
||||||
let token = SQUtils.ModelUtils.getByKey(root.assetsStore.groupedAccountAssetsModel, "tokensKey", Constants.ethToken)
|
let token = SQUtils.ModelUtils.getByKey(root.assetsStore.groupedAccountAssetsModel, "tokensKey", Constants.ethToken)
|
||||||
|
@ -475,13 +476,35 @@ SQUtils.QObject {
|
||||||
return {haveEnoughForFees, haveEnoughFunds}
|
return {haveEnoughForFees, haveEnoughFunds}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chainId == mainNetChainId) {
|
let chainBalance = getBalanceInEth(token.balances, address, chainId)
|
||||||
const finalFees = Math.sum(maxFees, l1GasFee)
|
if (!chainBalance) {
|
||||||
haveEnoughForFees = checkBalanceForChain(token.balances, address, chainId, finalFees)
|
console.error("Error fetching chain balance")
|
||||||
|
return {haveEnoughForFees, haveEnoughFunds}
|
||||||
|
}
|
||||||
|
haveEnoughFunds = Math.cmp(chainBalance, valueEth) >= 0
|
||||||
|
if (haveEnoughFunds) {
|
||||||
|
chainBalance = Math.sub(chainBalance, valueEth)
|
||||||
|
|
||||||
|
if (chainId == mainNetChainId) {
|
||||||
|
const finalFees = Math.sum(maxFees, l1GasFee)
|
||||||
|
let feesEth = Math.div(finalFees, Math.fromNumber(1, 9))
|
||||||
|
haveEnoughForFees = Math.cmp(chainBalance, feesEth) >= 0
|
||||||
|
} else {
|
||||||
|
const feesChain = Math.div(maxFees, Math.fromNumber(1, 9))
|
||||||
|
const haveEnoughOnChain = Math.cmp(chainBalance, feesChain) >= 0
|
||||||
|
|
||||||
|
const mainBalance = getBalanceInEth(token.balances, address, mainNetChainId)
|
||||||
|
if (!mainBalance) {
|
||||||
|
console.error("Error fetching mainnet balance")
|
||||||
|
return {haveEnoughForFees, haveEnoughFunds}
|
||||||
|
}
|
||||||
|
const feesMain = Math.div(l1GasFee, Math.fromNumber(1, 9))
|
||||||
|
const haveEnoughOnMain = Math.cmp(mainBalance, feesMain) >= 0
|
||||||
|
|
||||||
|
haveEnoughForFees = haveEnoughOnChain && haveEnoughOnMain
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const haveEnoughOnChain = checkBalanceForChain(token.balances, address, chainId, maxFees)
|
haveEnoughForFees = false
|
||||||
const haveEnoughOnMain = checkBalanceForChain(token.balances, address, mainNetChainId, l1GasFee)
|
|
||||||
haveEnoughForFees = haveEnoughOnChain && haveEnoughOnMain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {haveEnoughForFees, haveEnoughFunds}
|
return {haveEnoughForFees, haveEnoughFunds}
|
||||||
|
@ -503,6 +526,87 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns {
|
||||||
|
// preparedData,
|
||||||
|
// value // null or ETH Big number
|
||||||
|
// }
|
||||||
|
function prepareData(method, data) {
|
||||||
|
let payload = null
|
||||||
|
switch(method) {
|
||||||
|
case SessionRequest.methods.personalSign.name: {
|
||||||
|
payload = SessionRequest.methods.personalSign.getMessageFromData(data)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case SessionRequest.methods.sign.name: {
|
||||||
|
payload = SessionRequest.methods.sign.getMessageFromData(data)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case SessionRequest.methods.signTypedData_v4.name: {
|
||||||
|
const stringPayload = SessionRequest.methods.signTypedData_v4.getMessageFromData(data)
|
||||||
|
payload = JSON.stringify(JSON.parse(stringPayload), null, 2)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case SessionRequest.methods.signTypedData.name: {
|
||||||
|
const stringPayload = SessionRequest.methods.signTypedData.getMessageFromData(data)
|
||||||
|
payload = JSON.stringify(JSON.parse(stringPayload), null, 2)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// For transaction we process the data in a different way
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = SQUtils.AmountsArithmetic.fromNumber(0)
|
||||||
|
if (d.isTransactionMethod(method)) {
|
||||||
|
let txObj = d.getTxObject(method, data)
|
||||||
|
let tx = Object.assign({}, txObj)
|
||||||
|
if (tx.value) {
|
||||||
|
value = hexToEth(tx.value)
|
||||||
|
tx.value = value.toString()
|
||||||
|
}
|
||||||
|
if (tx.maxFeePerGas) {
|
||||||
|
tx.maxFeePerGas = hexToGwei(tx.maxFeePerGas).toString()
|
||||||
|
}
|
||||||
|
if (tx.maxPriorityFeePerGas) {
|
||||||
|
tx.maxPriorityFeePerGas = hexToGwei(tx.maxPriorityFeePerGas).toString()
|
||||||
|
}
|
||||||
|
if (tx.gasPrice) {
|
||||||
|
tx.gasPrice = hexToGwei(tx.gasPrice)
|
||||||
|
}
|
||||||
|
if (tx.gasLimit) {
|
||||||
|
tx.gasLimit = parseInt(root.store.hexToDec(tx.gasLimit))
|
||||||
|
}
|
||||||
|
if (tx.nonce) {
|
||||||
|
tx.nonce = parseInt(root.store.hexToDec(tx.nonce))
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = JSON.stringify(tx, null, 2)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
preparedData: payload,
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToEth(value) {
|
||||||
|
return hexToEthDenomination(value, "eth")
|
||||||
|
}
|
||||||
|
function hexToGwei(value) {
|
||||||
|
return hexToEthDenomination(value, "gwei")
|
||||||
|
}
|
||||||
|
function hexToEthDenomination(value, ethUnit) {
|
||||||
|
let unitMapping = {
|
||||||
|
"gwei": 9,
|
||||||
|
"eth": 18
|
||||||
|
}
|
||||||
|
let Math = SQUtils.AmountsArithmetic
|
||||||
|
let decValue = root.store.hexToDec(value)
|
||||||
|
if (!!decValue) {
|
||||||
|
return Math.div(Math.fromNumber(decValue), Math.fromNumber(1, unitMapping[ethUnit]))
|
||||||
|
}
|
||||||
|
return Math.fromNumber(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The queue is used to ensure that the events are processed in the order they are received but they could be
|
/// The queue is used to ensure that the events are processed in the order they are received but they could be
|
||||||
|
|
|
@ -23,6 +23,8 @@ QObject {
|
||||||
required property var network
|
required property var network
|
||||||
|
|
||||||
required property var data
|
required property var data
|
||||||
|
// Data prepared for display in a human readable format
|
||||||
|
required property var preparedData
|
||||||
|
|
||||||
readonly property alias dappName: d.dappName
|
readonly property alias dappName: d.dappName
|
||||||
readonly property alias dappUrl: d.dappUrl
|
readonly property alias dappUrl: d.dappUrl
|
||||||
|
|
|
@ -53,17 +53,28 @@ QObject {
|
||||||
|
|
||||||
// Strip leading zeros from numbers as expected by status-go
|
// Strip leading zeros from numbers as expected by status-go
|
||||||
function prepareTxForStatusGo(txObj) {
|
function prepareTxForStatusGo(txObj) {
|
||||||
let tx = {}
|
let tx = Object.assign({}, txObj)
|
||||||
if (txObj.data) { tx.data = txObj.data }
|
if (txObj.gasLimit) {
|
||||||
if (txObj.from) { tx.from = txObj.from }
|
tx.gasLimit = stripLeadingZeros(txObj.gasLimit)
|
||||||
if (txObj.gasLimit) { tx.gasLimit = stripLeadingZeros(txObj.gasLimit) }
|
}
|
||||||
if (txObj.gas) { tx.gas = stripLeadingZeros(txObj.gas) }
|
if (txObj.gas) {
|
||||||
if (txObj.gasPrice) { tx.gasPrice = stripLeadingZeros(txObj.gasPrice) }
|
tx.gas = stripLeadingZeros(txObj.gas)
|
||||||
if (txObj.nonce) { tx.nonce = stripLeadingZeros(txObj.nonce) }
|
}
|
||||||
if (txObj.maxFeePerGas) { tx.maxFeePerGas = stripLeadingZeros(txObj.maxFeePerGas) }
|
if (txObj.gasPrice) {
|
||||||
if (txObj.maxPriorityFeePerGas) { tx.maxPriorityFeePerGas = stripLeadingZeros(txObj.maxPriorityFeePerGas) }
|
tx.gasPrice = stripLeadingZeros(txObj.gasPrice)
|
||||||
if (txObj.to) { tx.to = txObj.to }
|
}
|
||||||
if (txObj.value) { tx.value = stripLeadingZeros(txObj.value) }
|
if (txObj.nonce) {
|
||||||
|
tx.nonce = stripLeadingZeros(txObj.nonce)
|
||||||
|
}
|
||||||
|
if (txObj.maxFeePerGas) {
|
||||||
|
tx.maxFeePerGas = stripLeadingZeros(txObj.maxFeePerGas)
|
||||||
|
}
|
||||||
|
if (txObj.maxPriorityFeePerGas) {
|
||||||
|
tx.maxPriorityFeePerGas = stripLeadingZeros(txObj.maxPriorityFeePerGas)
|
||||||
|
}
|
||||||
|
if (txObj.value) {
|
||||||
|
tx.value = stripLeadingZeros(txObj.value)
|
||||||
|
}
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue