feat(@desktop/wallet): Custom routing

fixes #8237
This commit is contained in:
Khushboo Mehta 2022-11-25 10:13:02 +01:00 committed by Khushboo-dev-cpp
parent 265b0b5a8f
commit f84404c956
20 changed files with 273 additions and 202 deletions

View File

@ -157,25 +157,25 @@ QtObject:
proc getNetworkIconUrl*(self: Model, shortName: string): string {.slot.} =
for item in self.items:
if(item.getShortName() == toLowerAscii(shortName)):
if cmpIgnoreCase(item.getShortName(), shortName) == 0:
return item.getIconURL()
return ""
proc getNetworkName*(self: Model, shortName: string): string {.slot.} =
for item in self.items:
if(item.getShortName() == toLowerAscii(shortName)):
if cmpIgnoreCase(item.getShortName(), shortName) == 0:
return item.getChainName()
return ""
proc getNetworkColor*(self: Model, shortName: string): string {.slot.} =
for item in self.items:
if(item.getShortName() == toLowerAscii(shortName)):
if cmpIgnoreCase(item.getShortName(), shortName) == 0:
return item.getChainColor()
return ""
proc getNetworkChainId*(self: Model, shortName: string): int {.slot.} =
for item in self.items:
if(item.getShortName() == toLowerAscii(shortName)):
if cmpIgnoreCase(item.getShortName(), shortName) == 0:
return item.getChainId()
return 0

View File

@ -114,8 +114,8 @@ proc suggestedFees*(self: Controller, chainId: int): string =
let suggestedFees = self.transactionService.suggestedFees(chainId)
return suggestedFees.toJson()
proc suggestedRoutes*(self: Controller, account: string, amount: Uint256, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int): string =
let suggestedRoutes = self.transactionService.suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType)
proc suggestedRoutes*(self: Controller, account: string, amount: Uint256, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int, lockedInAmounts: string): string =
let suggestedRoutes = self.transactionService.suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts)
return suggestedRoutes.toJson()
proc getChainIdForChat*(self: Controller): int =

View File

@ -53,7 +53,7 @@ method transactionWasSent*(self: AccessInterface, result: string) {.base.} =
method suggestedFees*(self: AccessInterface, chainId: int): string {.base.} =
raise newException(ValueError, "No implementation available")
method suggestedRoutes*(self: AccessInterface, account: string, amount: UInt256, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int): string {.base.} =
method suggestedRoutes*(self: AccessInterface, account: string, amount: UInt256, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int, lockedInAmounts: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method getChainIdForChat*(self: AccessInterface): int =

View File

@ -149,8 +149,8 @@ method transactionWasSent*(self: Module, result: string) =
method suggestedFees*(self: Module, chainId: int): string =
return self.controller.suggestedFees(chainId)
method suggestedRoutes*(self: Module, account: string, amount: UInt256, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int): string =
return self.controller.suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType)
method suggestedRoutes*(self: Module, account: string, amount: UInt256, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int, lockedInAmounts: string): string =
return self.controller.suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts)
method getChainIdForChat*(self: Module): int =
return self.controller.getChainIdForChat()

View File

@ -120,7 +120,7 @@ QtObject:
proc suggestedFees*(self: View, chainId: int): string {.slot.} =
return self.delegate.suggestedFees(chainId)
proc suggestedRoutes*(self: View, account: string, amount: string, token: string, disabledFromChainIDs: string, disabledToChainIDs: string, preferredChainIDs: string, sendType: int): string {.slot.} =
proc suggestedRoutes*(self: View, account: string, amount: string, token: string, disabledFromChainIDs: string, disabledToChainIDs: string, preferredChainIDs: string, sendType: int, lockedInAmounts: string): string {.slot.} =
var parsedAmount = stint.u256("0")
var seqPreferredChainIDs = seq[uint64] : @[]
var seqDisabledFromChainIDs = seq[uint64] : @[]
@ -149,7 +149,7 @@ QtObject:
except Exception as e:
discard
return self.delegate.suggestedRoutes(account, parsedAmount, token, seqDisabledFromChainIDs, seqDisabledToChainIDs, seqPreferredChainIDs, sendType)
return self.delegate.suggestedRoutes(account, parsedAmount, token, seqDisabledFromChainIDs, seqDisabledToChainIDs, seqPreferredChainIDs, sendType, lockedInAmounts)
proc getChainIdForChat*(self: View): int {.slot.} =
return self.delegate.getChainIdForChat()

View File

@ -62,7 +62,7 @@ var NETWORKS* = %* [
"blockExplorerUrl": "https://goerli.etherscan.io/",
"iconUrl": "network/Network=Testnet",
"chainColor": "#939BA1",
"shortName": "goeEth",
"shortName": "goEth",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -92,7 +92,7 @@ var NETWORKS* = %* [
"blockExplorerUrl": "https://goerli-optimism.etherscan.io/",
"iconUrl": "network/Network=Testnet",
"chainColor": "#939BA1",
"shortName": "goerOpt",
"shortName": "goOpt",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -122,7 +122,7 @@ var NETWORKS* = %* [
"blockExplorerUrl": "https://goerli.arbiscan.io/",
"iconUrl": "network/Network=Testnet",
"chainColor": "#939BA1",
"shortName": "rinArb",
"shortName": "goArb",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -162,7 +162,7 @@ if GANACHE_NETWORK_RPC_URL != "":
"blockExplorerUrl": "https://goerli.etherscan.io/",
"iconUrl": "network/Network=Testnet",
"chainColor": "#939BA1",
"shortName": "goeEth",
"shortName": "goEth",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -198,7 +198,7 @@ if GANACHE_NETWORK_RPC_URL != "":
"blockExplorerUrl": "https://goerli-optimism.etherscan.io/",
"iconUrl": "network/Network=Testnet",
"chainColor": "#939BA1",
"shortName": "goerOpt",
"shortName": "goOpt",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -228,7 +228,7 @@ if GANACHE_NETWORK_RPC_URL != "":
"blockExplorerUrl": "https://goerli.arbiscan.io/",
"iconUrl": "network/Network=Testnet",
"chainColor": "#939BA1",
"shortName": "rinArb",
"shortName": "goArb",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,

View File

@ -40,6 +40,7 @@ type
disabledToChainIDs: seq[uint64]
preferredChainIDs: seq[uint64]
sendType: int
lockedInAmounts: string
proc getGasEthValue*(gweiValue: float, gasLimit: uint64): float =
let weiValue = service_conversion.gwei2Wei(gweiValue) * u256(gasLimit)
@ -66,13 +67,20 @@ const getSuggestedRoutesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall
try:
let amountAsHex = "0x" & eth_utils.stripLeadingZeros(arg.amount.toHex)
let response = eth.suggestedRoutes(arg.account, amountAsHex, arg.token, arg.disabledFromChainIDs, arg.disabledToChainIDs, arg.preferredChainIDs, arg.sendType).result
var lockedInAmounts = Table[string, string] : initTable[string, string]()
try:
for lockedAmount in parseJson(arg.lockedInAmounts):
lockedInAmounts[$lockedAmount["chainID"].getInt] = "0x" & lockedAmount["value"].getStr
except:
discard
let response = eth.suggestedRoutes(arg.account, amountAsHex, arg.token, arg.disabledFromChainIDs, arg.disabledToChainIDs, arg.preferredChainIDs, arg.sendType, lockedInAmounts).result
var bestPaths = response["Best"].getElems().map(x => x.toTransactionPathDto())
# retry along with unpreferred chains incase no route is possible with preferred chains
if(bestPaths.len == 0 and arg.preferredChainIDs.len > 0):
let response = eth.suggestedRoutes(arg.account, amountAsHex, arg.token, arg.disabledFromChainIDs, arg.disabledToChainIDs, @[], arg.sendType).result
let response = eth.suggestedRoutes(arg.account, amountAsHex, arg.token, arg.disabledFromChainIDs, arg.disabledToChainIDs, @[], arg.sendType, lockedInAmounts).result
bestPaths = response["Best"].getElems().map(x => x.toTransactionPathDto())
bestPaths.sort(sortAsc[TransactionPathDto])

View File

@ -163,6 +163,8 @@ type
bonderFees*: string
cost*: float
estimatedTime*: int
amountInLocked*: bool
proc `$`*(self: TransactionPathDto): string =
return fmt"""TransactionPath(
@ -176,7 +178,8 @@ proc `$`*(self: TransactionPathDto): string =
tokenFees:{self.tokenFees},
bonderFees:{self.bonderFees},
cost:{self.cost},
estimatedTime:{self.estimatedTime}
estimatedTime:{self.estimatedTime},
amountInLocked:{self.amountInLocked}
)"""
proc toTransactionPathDto*(jsonObj: JsonNode): TransactionPathDto =
@ -193,6 +196,7 @@ proc toTransactionPathDto*(jsonObj: JsonNode): TransactionPathDto =
result.amountOut = stint.fromHex(UInt256, jsonObj{"AmountOut"}.getStr)
result.estimatedTime = jsonObj{"EstimatedTime"}.getInt
discard jsonObj.getProp("GasAmount", result.gasAmount)
discard jsonObj.getProp("AmountInLocked", result.amountInLocked)
proc convertToTransactionPathDto*(jsonObj: JsonNode): TransactionPathDto =
result = TransactionPathDto()

View File

@ -379,7 +379,7 @@ QtObject:
proc suggestedRoutesReady*(self: Service, suggestedRoutes: string) {.slot.} =
self.events.emit(SIGNAL_SUGGESTED_ROUTES_READY, SuggestedRoutesArgs(suggestedRoutes: suggestedRoutes))
proc suggestedRoutes*(self: Service, account: string, amount: Uint256, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int): SuggestedRoutesDto =
proc suggestedRoutes*(self: Service, account: string, amount: Uint256, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int, lockedInAmounts: string): SuggestedRoutesDto =
let arg = GetSuggestedRoutesTaskArg(
tptr: cast[ByteAddress](getSuggestedRoutesTask),
vptr: cast[ByteAddress](self.vptr),
@ -390,7 +390,8 @@ QtObject:
disabledFromChainIDs: disabledFromChainIDs,
disabledToChainIDs: disabledToChainIDs,
preferredChainIDs: preferredChainIDs,
sendType: sendType
sendType: sendType,
lockedInAmounts: lockedInAmounts
)
self.threadpool.start(arg)

View File

@ -1,4 +1,4 @@
import json, stint
import json, stint, tables
import ./core, ./response_type
export response_type
@ -28,6 +28,6 @@ proc suggestedFees*(chainId: int): RpcResponse[JsonNode] {.raises: [Exception].}
let payload = %* [chainId]
return core.callPrivateRPC("wallet_getSuggestedFees", payload)
proc suggestedRoutes*(account: string, amount: string, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [sendType, account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, 1]
proc suggestedRoutes*(account: string, amount: string, token: string, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs: seq[uint64], sendType: int, lockedInAmounts: var Table[string, string]): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [sendType, account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, 1 , lockedInAmounts]
return core.callPrivateRPC("wallet_getSuggestedRoutes", payload)

View File

@ -62,7 +62,16 @@ Rectangle {
advanced mode before it locked for any new changes
*/
property int lockTimeout: 1500
/*!
\qmlproperty int StatusCard::locked
This property holds if the custom amount entered by user is locked
*/
property bool locked: false
/*!
\qmlproperty int StatusCard::preCalculatedAdvancedText
This property is the amounts calculated by the routing algorithm
*/
property string preCalculatedAdvancedText
/*!
\qmlproperty alias StatusCard::primaryText
Used to set Primary text in the StatusCard
@ -136,6 +145,12 @@ Rectangle {
*/
signal clicked()
/*!
\qmlsignal StatusCard::cardLocked
This signal is emitted when the card is locked or unlocked
*/
signal cardLocked(bool isLocked)
/*!
\qmlproperty string StatusCard::state
This property holds the states of the StatusCard.
@ -200,7 +215,7 @@ Rectangle {
StatusInput {
id: advancedInput
property bool locked: false
property bool tempLock: false
implicitWidth: 80
maximumHeight: 32
topPadding: 0
@ -208,7 +223,7 @@ Rectangle {
leftPadding: 8
rightPadding: 5
input.edit.font.pixelSize: 13
input.edit.readOnly: locked || disabled
input.edit.readOnly: disabled
input.rightComponent: Row {
width: implicitWidth
spacing: 4
@ -216,15 +231,15 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 12
height: 12
icon.name: advancedInput.locked ? "lock" : "unlock"
icon.name: root.locked && advancedInput.tempLock ? "lock" : "unlock"
icon.width: 12
icon.height: 12
icon.color: advancedInput.locked ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
icon.color: root.locked && advancedInput.tempLock? Theme.palette.primaryColor1 : Theme.palette.baseColor1
type: StatusFlatRoundButton.Type.Secondary
enabled: !disabled
onClicked: {
advancedInput.locked = !advancedInput.locked
waitTimer.restart()
advancedInput.tempLock = !advancedInput.tempLock
root.cardLocked(advancedInput.tempLock)
}
}
StatusFlatRoundButton {
@ -235,19 +250,22 @@ Rectangle {
icon.height: 14
icon.color: Theme.palette.baseColor1
type: StatusFlatRoundButton.Type.Secondary
onClicked: advancedInput.edit.clear()
onClicked: advancedInput.input.edit.clear()
}
}
text: root.preCalculatedAdvancedText
onTextChanged: {
locked = false
advancedInput.tempLock = false
waitTimer.restart()
}
Timer {
id: waitTimer
interval: lockTimeout
onTriggered: {
if(advancedInput.text)
advancedInput.locked = true
advancedInput.tempLock = true
if(!!advancedInput.text && root.preCalculatedAdvancedText !== advancedInput.text) {
root.cardLocked(advancedInput.tempLock)
}
}
}
}
@ -320,7 +338,7 @@ Rectangle {
}
PropertyChanges {
target: advancedInput
input.color: Theme.palette.directColor1
input.edit.color: Theme.palette.directColor1
}
PropertyChanges {
target: basicInput
@ -438,7 +456,7 @@ Rectangle {
}
PropertyChanges {
target: advancedInput
input.color: Theme.palette.directColor1
input.edit.color: Theme.palette.directColor1
}
PropertyChanges {
target: basicInput
@ -497,7 +515,7 @@ Rectangle {
}
PropertyChanges {
target: advancedInput
input.color: Theme.palette.directColor1
input.edit.color: Theme.palette.directColor1
}
PropertyChanges {
target: basicInput

View File

@ -542,8 +542,8 @@ QtObject {
return JSON.parse(walletSectionTransactions.suggestedFees(chainId))
}
function suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIds, sendType) {
walletSectionTransactions.suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIds, sendType)
function suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIds, sendType, lockedInAmounts) {
walletSectionTransactions.suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIds, sendType, lockedInAmounts)
}
function resolveENS(value) {

View File

@ -17,7 +17,7 @@ ColumnLayout {
property double amountToSend: 0
property bool isLoading: true
visible: !balancedExceededError.transferPossible && balancedExceededError.amountToSend > 0 || isLoading
visible: !balancedExceededError.transferPossible || isLoading
StatusIcon {
Layout.preferredHeight: 20
@ -40,7 +40,7 @@ ColumnLayout {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: Theme.palette.dangerColor1
text: isLoading ? qsTr("Calculating fees"): balancedExceededError.amountToSend > 0 ? qsTr("Balance exceeded"): qsTr("No networks available")
text: isLoading ? qsTr("Calculating fees") : qsTr("Balance exceeded")
wrapMode: Text.WordWrap
}
}

View File

@ -72,7 +72,7 @@ StatusDialog {
let amount = parseFloat(amountToSendInput.text) * Math.pow(10, assetSelector.selectedAsset.decimals)
popup.store.suggestedRoutes(popup.selectedAccount.address, amount.toString(16), assetSelector.selectedAsset.symbol,
store.disabledChainIdsFromList, store.disabledChainIdsToList,
store.preferredChainIds, popup.sendType)
store.preferredChainIds, popup.sendType, store.lockedInAmounts)
}
})
@ -84,9 +84,9 @@ StatusDialog {
amountToSendInput.validate()
}
readonly property bool isReady: amountToSendInput.valid && !amountToSendInput.pending && recipientReady
readonly property bool errorMode: (networkSelector.bestRoutes && networkSelector.bestRoutes.length <= 0) || networkSelector.errorMode || isNaN(amountToSendInput.text)
readonly property bool errorMode: popup.isLoading || !isReady ? false : (networkSelector.bestRoutes && networkSelector.bestRoutes.length <= 0) || networkSelector.errorMode || isNaN(amountToSendInput.text)
readonly property bool recipientReady: (isAddressValid || isENSValid) && !recipientSelector.isPending
property bool isAddressValid: false
property bool isAddressValid: Utils.isValidAddress(popup.addressText)
property bool isENSValid: false
readonly property var resolveENS: Backpressure.debounce(popup, 500, function (ensName) {
store.resolveENS(ensName)
@ -94,33 +94,16 @@ StatusDialog {
property string resolvedENSAddress
readonly property string uuid: Utils.uuid()
property bool isPendingTx: false
property var preferredChainIds: []
property string totalTimeEstimate
property string totalFeesInEth
property string totalFeesInFiat
property Timer waitTimer: Timer {
interval: 1000
interval: 1500
onTriggered: {
d.isAddressValid = false
let splitWords = popup.store.plainText(recipientSelector.input.text).split(':')
let editedText = ""
for(var i=0; i<splitWords.length; i++) {
if(splitWords[i].startsWith("0x")) {
d.isAddressValid = Utils.isValidAddress(splitWords[i])
popup.addressText = splitWords[i]
editedText += splitWords[i]
} else {
let chainColor = popup.store.allNetworks.getNetworkColor(splitWords[i])
if(!!chainColor) {
if(!isBridgeTx)
store.addPreferredChain(popup.store.allNetworks.getNetworkChainId(splitWords[i]))
editedText += `<span style='color: %1'>%2</span>`.arg(chainColor).arg(splitWords[i])+':'
}
}
}
editedText +="</a></p>"
recipientSelector.input.text = editedText
let result = store.splitAndFormatAddressPrefix(recipientSelector.input.text, isBridgeTx, networkSelector.showUnpreferredNetworks)
popup.addressText = result.address
recipientSelector.input.text = result.formattedText
popup.recalculateRoutesAndFees()
}
}
@ -138,8 +121,7 @@ StatusDialog {
onOpened: {
if(!isBridgeTx) {
store.addPreferredChain(popup.store.getMainnetChainId())
store.addUnpreferredChainsToDisabledChains()
store.setDefaultPreferredDisabledChains()
}
amountToSendInput.input.edit.forceActiveFocus()
@ -163,10 +145,7 @@ StatusDialog {
}
}
onClosed: {
popup.store.disabledChainIdsFromList = []
popup.store.disabledChainIdsToList = []
}
onClosed: popup.store.resetTxStoreProperties()
header: AccountsModalHeader {
anchors.top: parent.top
@ -394,12 +373,12 @@ StatusDialog {
multiline: false
input.edit.textFormat: TextEdit.RichText
input.rightComponent: RowLayout {
StatusButton {
visible: recipientSelector.text === ""
borderColor: Theme.palette.primaryColor1
size: StatusBaseButton.Size.Tiny
text: qsTr("Paste")
onClicked: recipientSelector.input.edit.paste()
StatusIcon {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
icon: "tiny/checkmark"
color: Theme.palette.primaryColor1
visible: d.recipientReady
}
StatusFlatRoundButton {
visible: recipientSelector.text !== ""

View File

@ -93,8 +93,8 @@ QtObject {
return walletSectionTransactions.getChainIdForBrowser()
}
function suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIds, sendType) {
walletSectionTransactions.suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIds, sendType)
function suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIds, sendType, lockedInAmounts) {
walletSectionTransactions.suggestedRoutes(account, amount, token, disabledFromChainIDs, disabledToChainIDs, preferredChainIds, sendType, JSON.stringify(lockedInAmounts))
}
function hex2Eth(value) {
@ -124,32 +124,89 @@ QtObject {
return globalUtils.plainText(text)
}
property var preferredChainIds: []
function addPreferredChain(chainID) {
if(!chainID)
return
if(preferredChainIds.includes(chainID))
return
preferredChainIds.push(chainID)
function setDefaultPreferredDisabledChains() {
let mainnetChainId = getMainnetChainId()
preferredChainIds.push(mainnetChainId)
addUnpreferredChainsToDisabledChains()
}
function resetTxStoreProperties() {
disabledChainIdsFromList = []
disabledChainIdsToList = []
preferredChainIds = []
lockedInAmounts = []
}
property var preferredChainIds: []
function getMainnetChainId() {
return networksModule.getMainnetChainId()
}
function addPreferredChains(preferredchains, showUnpreferredNetworks) {
for(const chain of preferredchains) {
if(!preferredChainIds.includes(chain)) {
preferredChainIds.push(chain)
// remove from disabled accounts as it was added as preferred
addRemoveDisabledToChain(chain, false)
}
}
// here we are trying to remove chains that are not preferred from the list and
// also disable them incase the showUnpreferredNetworks toggle is turned off
for(var i = 0; i < preferredChainIds.length; i++) {
if(!preferredchains.includes(preferredChainIds[i])) {
if(!showUnpreferredNetworks)
addRemoveDisabledToChain(preferredChainIds[i], true)
preferredChainIds.splice(i, 1)
}
}
}
function addUnpreferredChainsToDisabledChains() {
let mainnetChainId = getMainnetChainId()
for(var i = 0; i < allNetworks.count; i++) {
let chainId = allNetworks.rowData(i, "chainId") * 1
if(mainnetChainId !== chainId) {
if(!preferredChainIds.includes(chainId)) {
addRemoveDisabledToChain(chainId, true)
}
}
}
function splitAndFormatAddressPrefix(text, isBridgeTx, showUnpreferredNetworks) {
let address = ""
let tempPreferredChains = []
let chainFound = false
let splitWords = plainText(text).split(':')
let editedText = ""
for(var i=0; i<splitWords.length; i++) {
const word = splitWords[i]
if(word.startsWith("0x")) {
address = word
editedText += word
} else {
let chainColor = allNetworks.getNetworkColor(word)
if(!!chainColor) {
chainFound = true
if(!isBridgeTx)
tempPreferredChains.push(allNetworks.getNetworkChainId(word))
editedText += `<span style='color: %1'>%2</span>`.arg(chainColor).arg(word)+':'
}
}
}
if(!chainFound && !isBridgeTx)
addPreferredChains([getMainnetChainId()], showUnpreferredNetworks)
else
addPreferredChains(tempPreferredChains, showUnpreferredNetworks)
editedText +="</a></p>"
return {
formattedText: editedText,
address: address
}
}
enum EstimatedTime {
Unknown = 0,
LessThanOneMin,
@ -172,4 +229,21 @@ QtObject {
return qsTr("> 5 minutes")
}
}
property var lockedInAmounts: []
function addLockedInAmount(chainID, value, decimals, locked) {
let amount = Number.fromLocaleString(Qt.locale(), value) * Math.pow(10, decimals)
let index = lockedInAmounts.findIndex(lockedItem => lockedItem !== undefined && lockedItem.chainID === chainID)
if(index === -1) {
lockedInAmounts.push({"chainID": chainID, "value": amount.toString(16)})
}
else {
if(locked) {
lockedInAmounts[index].value = amount.toString(16)
} else {
lockedInAmounts.splice(index,1)
}
}
}
}

View File

@ -21,25 +21,15 @@ Item {
property bool customMode: false
property double amountToSend: 0
property double requiredGasInEth: 0
property bool errorMode: {
if(customMode) {
return (d.customAmountToSend > amountToSend) || (d.customAmountToSend < amountToSend) ||
(d.customAmountToReceive > amountToSend) || (d.customAmountToReceive < amountToSend)
}
else {
return !d.thereIsApossibleRoute
}
}
property bool errorMode: !d.thereIsApossibleRoute || d.customAmountToSend > root.amountToSend
property bool interactive: true
property bool showPreferredChains: false
property var weiToEth: function(wei) {}
property var reCalculateSuggestedRoute: function() {}
QtObject {
id: d
property double customAmountToSend: 0
property double customAmountToReceive: 0
property bool thereIsApossibleRoute: false
function resetAllSetValues() {
@ -51,6 +41,16 @@ Item {
}
}
function calculateCustomAmounts() {
d.customAmountToSend = 0
for(var i = 0; i<fromNetworksRepeater.count; i++) {
if(fromNetworksRepeater.itemAt(i).locked) {
let amountEntered = parseFloat(fromNetworksRepeater.itemAt(i).advancedInputText)
d.customAmountToSend += isNaN(amountEntered) ? 0 : amountEntered
}
}
}
function draw() {
canvas.clear()
canvas.requestPaint()
@ -59,6 +59,7 @@ Item {
onVisibleChanged: if(visible) d.draw()
onBestRoutesChanged: d.draw()
onErrorModeChanged: if(errorMode) d.draw()
width: 410
height: visible ? networkCardsLayout.height : 0
@ -85,15 +86,23 @@ Item {
property int routeOnNetwork: 0
property string tokenBalanceOnChain: selectedAccount && selectedAccount!== undefined && selectedAsset!== undefined ? selectedAccount.getTokenBalanceOnChain(model.chainId, selectedAsset.symbol) : ""
property var hasGas: selectedAccount.hasGas(model.chainId, model.nativeCurrencySymbol, requiredGasInEth)
primaryText: model.chainName
secondaryText: (parseFloat(tokenBalanceOnChain) === 0 && root.amountToSend !== 0) ?
qsTr("No Balance") : !hasGas ? qsTr("No Gas") : LocaleUtils.numberToLocaleString(fromNetwork.amountToSend)
qsTr("No Balance") : !hasGas ? qsTr("No Gas") : advancedInputText
tertiaryText: qsTr("BALANCE: ") + LocaleUtils.numberToLocaleString(parseFloat(tokenBalanceOnChain))
locked: store.lockedInAmounts.findIndex(lockedItem => lockedItem !== undefined && lockedItem.chainID === model.chainId) !== -1
preCalculatedAdvancedText: {
let index = store.lockedInAmounts.findIndex(lockedItem => lockedItem!== undefined && lockedItem.chainID === model.chainId)
if(locked && index !== -1) {
return root.weiToEth(parseInt(store.lockedInAmounts[index].value, 16))
}
else return LocaleUtils.numberToLocaleString(fromNetwork.amountToSend)
}
state: tokenBalanceOnChain === 0 || !hasGas ? "unavailable" : root.errorMode ? "error" : "default"
cardIcon.source: Style.svg(model.iconUrl)
disabledText: qsTr("Disabled")
advancedMode: root.customMode
advancedInputText: LocaleUtils.numberToLocaleString(fromNetwork.amountToSend)
disabled: store.disabledChainIdsFromList.includes(model.chainId)
clickable: root.interactive
onClicked: {
@ -106,73 +115,70 @@ Item {
if(visible)
disabled = store.disabledChainIdsFromList.includes(model.chainId)
}
// To-do needed for custom view
// onAdvancedInputTextChanged: {
// if(selectedNetwork && selectedNetwork.chainName === model.chainName) {
// d.customAmountToSend = isNaN(parseFloat(advancedInputText)) ? 0 : parseFloat(advancedInputText)
// }
// }
onCardLocked: {
store.addLockedInAmount(model.chainId, advancedInputText, root.selectedAsset.decimals, isLocked)
locked = store.lockedInAmounts.length > 0 && store.lockedInAmounts.findIndex(lockedItem => lockedItem !== undefined && lockedItem.chainID === model.chainId) !== -1
d.calculateCustomAmounts()
if(!locked || d.customAmountToSend <= root.amountToSend)
root.reCalculateSuggestedRoute()
}
}
}
}
ColumnLayout {
id: toNetworksLayout
id: toMainNetworksLayout
Layout.alignment: Qt.AlignRight | Qt.AlignTop
spacing: 12
StatusBaseText {
Layout.alignment: Qt.AlignRight
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.maximumWidth: 100
font.pixelSize: 10
color: Theme.palette.baseColor1
text: StatusQUtils.Utils.elideText(selectedAccount.address, 6, 4).toUpperCase()
elide: Text.ElideMiddle
}
Repeater {
id: toNetworksRepeater
model: root.allNetworks
StatusCard {
id: toCard
objectName: model.chainId
property int routeOnNetwork: 0
property double amountToReceive: 0
property bool preferred: store.preferredChainIds.includes(model.chainId)
primaryText: model.chainName
secondaryText: LocaleUtils.numberToLocaleString(amountToReceive)
tertiaryText: state === "unpreferred" ? qsTr("UNPREFERRED") : ""
state: root.errorMode ? "error" : !preferred ? "unpreferred" : "default"
opacity: preferred || showPreferredChains ? 1 : 0
cardIcon.source: Style.svg(model.iconUrl)
disabledText: qsTr("Disabled")
advancedMode: root.customMode
advancedInputText: LocaleUtils.numberToLocaleString(amountToReceive)
disabled: store.disabledChainIdsToList.includes(model.chainId)
clickable: root.interactive
onClicked: {
store.addRemoveDisabledToChain(model.chainId, disabled)
// only recalculate if the a best route was disabled
if(root.bestRoutes.length === 0 || routeOnNetwork !== 0 || !disabled)
root.reCalculateSuggestedRoute()
}
onVisibleChanged: {
if(visible) {
disabled = store.disabledChainIdsToList.includes(model.chainId)
preferred = store.preferredChainIds.includes(model.chainId)
}
}
onOpacityChanged: {
if(opacity === 1) {
disabled = store.disabledChainIdsToList.includes(model.chainId)
} else {
if(opacity === 0 && routeOnNetwork > 0)
Column {
id: toNetworksLayout
spacing: root.customMode ? 26 : 12
Repeater {
id: toNetworksRepeater
model: root.allNetworks
StatusCard {
id: toCard
objectName: model.chainId
property int routeOnNetwork: 0
property double amountToReceive: 0
property bool preferred: store.preferredChainIds.includes(model.chainId)
primaryText: model.chainName
secondaryText: LocaleUtils.numberToLocaleString(amountToReceive)
tertiaryText: state === "unpreferred" ? qsTr("UNPREFERRED") : ""
state: root.errorMode ? "error" : !preferred ? "unpreferred" : "default"
opacity: preferred || showPreferredChains ? 1 : 0
cardIcon.source: Style.svg(model.iconUrl)
disabledText: qsTr("Disabled")
disabled: store.disabledChainIdsToList.includes(model.chainId)
clickable: root.interactive
onClicked: {
store.addRemoveDisabledToChain(model.chainId, disabled)
// only recalculate if the a best route was disabled
if(root.bestRoutes.length === 0 || routeOnNetwork !== 0 || !disabled)
root.reCalculateSuggestedRoute()
}
onVisibleChanged: {
if(visible) {
disabled = store.disabledChainIdsToList.includes(model.chainId)
preferred = store.preferredChainIds.includes(model.chainId)
}
}
onOpacityChanged: {
if(opacity === 1) {
disabled = store.disabledChainIdsToList.includes(model.chainId)
} else {
if(opacity === 0 && routeOnNetwork > 0)
root.reCalculateSuggestedRoute()
}
}
}
// To-do needed for custom view
// onAdvancedInputTextChanged: {
// if(selectedNetwork && selectedNetwork.chainName === model.chainName)
// d.customAmountToReceive = isNaN(parseFloat(advancedInputText)) ? 0 : parseFloat(advancedInputText)
// }
}
}
}
@ -200,7 +206,7 @@ Item {
if(bestRoutes === undefined)
return
// in case you are drwaing multiple routes we need an offset so that the lines dont overlap
// in case you are drawing multiple routes we need an offset so that the lines dont overlap
let yOffsetFrom = 0
let yOffsetTo = 0
let xOffset = 0
@ -232,14 +238,15 @@ Item {
fromN.routeOnNetwork += 1
toN.routeOnNetwork += 1
d.thereIsApossibleRoute = true
let routeColor = toN.preferred ? '#627EEA' : Theme.palette.pinColor1
let routeColor = root.errorMode ? Theme.palette.dangerColor1 : toN.preferred ? '#627EEA' : Theme.palette.pinColor1
StatusQUtils.Utils.drawArrow(ctx, fromN.x + fromN.width,
fromN.y + fromN.cardIconPosition + yOffsetFrom,
toNetworksLayout.x + toN.x,
toN.y + toN.cardIconPosition + yOffsetTo,
toMainNetworksLayout.x + toN.x,
toNetworksLayout.y + toN.y + toN.cardIconPosition + yOffsetTo,
routeColor, xOffset)
}
}
d.calculateCustomAmounts()
}
}
}

View File

@ -24,12 +24,10 @@ Item {
property var bestRoutes
property bool isLoading: false
property bool advancedOrCustomMode: (tabBar.currentIndex === 1) || (tabBar.currentIndex === 2)
property bool errorMode: (tabBar.currentIndex === 1) ?
advancedNetworkRoutingPage.errorMode :
(tabBar.currentIndex === 2) ?
customNetworkRoutingPage.errorMode: false
property bool errorMode: advancedNetworkRoutingPage.errorMode
property bool interactive: true
property bool isBridgeTx: false
property bool showUnpreferredNetworks: advancedNetworkRoutingPage.showUnpreferredNetworks
signal reCalculateSuggestedRoute()
@ -49,10 +47,9 @@ Item {
StatusSwitchTabButton {
text: qsTr("Advanced")
}
// To-do Implementaion is not ready yet
// StatusSwitchTabButton {
// text: qsTr("Custom")
// }
StatusSwitchTabButton {
text: qsTr("Custom")
}
}
StackLayout {
@ -60,10 +57,9 @@ Item {
anchors.top: tabBar.bottom
anchors.topMargin: Style.current.bigPadding
height: currentIndex == 0 ? networksSimpleRoutingPage.height + networksSimpleRoutingPage.anchors.margins + Style.current.bigPadding:
currentIndex == 1 ? advancedNetworkRoutingPage.height + advancedNetworkRoutingPage.anchors.margins + Style.current.bigPadding:
customNetworkRoutingPage.height + customNetworkRoutingPage.anchors.margins + Style.current.bigPadding
advancedNetworkRoutingPage.height + advancedNetworkRoutingPage.anchors.margins + Style.current.bigPadding
width: parent.width
currentIndex: tabBar.currentIndex
currentIndex: tabBar.currentIndex === 0 ? 0 : 1
Rectangle {
id: simple
@ -82,6 +78,7 @@ Item {
store: root.store
selectedAsset: root.selectedAsset
selectedAccount: root.selectedAccount
errorMode: root.errorMode
weiToEth: function(wei) {
return "%1 %2".arg(LocaleUtils.numberToLocaleString(parseFloat(store.getWei2Eth(wei, selectedAsset.decimals)))).arg(selectedAsset.symbol)
}
@ -101,32 +98,7 @@ Item {
anchors.left: parent.left
anchors.margins: Style.current.padding
store: root.store
selectedAccount: root.selectedAccount
amountToSend: root.amountToSend
requiredGasInEth: root.requiredGasInEth
selectedAsset: root.selectedAsset
onReCalculateSuggestedRoute: root.reCalculateSuggestedRoute()
bestRoutes: root.bestRoutes
isLoading: root.isLoading
interactive: root.interactive
isBridgeTx: root.isBridgeTx
weiToEth: function(wei) {
return parseFloat(store.getWei2Eth(wei, selectedAsset.decimals))
}
}
}
Rectangle {
id: custom
radius: d.backgroundRectRadius
color: d.backgroundRectColor
NetworksAdvancedCustomRoutingView {
id: customNetworkRoutingPage
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: Style.current.padding
customMode: true
store: root.store
customMode: tabBar.currentIndex === 2
selectedAccount: root.selectedAccount
amountToSend: root.amountToSend
requiredGasInEth: root.requiredGasInEth

View File

@ -26,6 +26,7 @@ ColumnLayout {
property var weiToEth: function(wei) {}
property bool interactive: true
property bool isBridgeTx: false
property bool showUnpreferredNetworks: preferredToggleButton.checked
signal reCalculateSuggestedRoute()

View File

@ -24,7 +24,7 @@ RowLayout {
property var selectedAccount
property var weiToEth: function(wei) {}
property var reCalculateSuggestedRoute: function() {}
property bool errorMode: false
spacing: 10
StatusRoundIcon {
@ -94,8 +94,15 @@ RowLayout {
rightPadding: 5
implicitWidth: 126
title: modelData.toNetwork.chainName
subTitle: root.weiToEth(modelData.amountIn)
statusListItemSubTitle.color: Theme.palette.primaryColor1
subTitle: {
let index = store.lockedInAmounts.findIndex(lockedItem => lockedItem !== undefined && lockedItem.chainID === modelData.toNetwork.chainId)
if(!root.errorMode || index === -1)
return root.weiToEth(modelData.amountIn)
else {
return root.weiToEth(parseInt(store.lockedInAmounts[index].value, 16))
}
}
statusListItemSubTitle.color: root.errorMode ? Theme.palette.dangerColor1 : Theme.palette.primaryColor1
asset.width: 32
asset.height: 32
asset.name: Style.svg("tiny/" + modelData.toNetwork.iconUrl)

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit b4bdfd3df6cf5fb91ab2d0e9f3b38e8d1b9703e5
Subproject commit 6abbe98cd20b639d0c7298091f00262e876f3e17