feat: list, toggle and remove custom tokens

This commit is contained in:
Richard Ramos 2020-09-08 11:52:09 -04:00 committed by Iuri Matias
parent f3f27a5e59
commit 27abf30fc8
9 changed files with 231 additions and 98 deletions

View File

@ -17,6 +17,7 @@ QtObject:
focusedAccount: AccountItemView
currentTransactions: TransactionList
defaultTokenList: TokenList
customTokenList: TokenList
status: Status
totalFiatBalance: string
etherscanLink: string
@ -35,6 +36,7 @@ QtObject:
self.focusedAccount.delete
self.currentTransactions.delete
self.defaultTokenList.delete
self.customTokenList.delete
self.QAbstractListModel.delete
proc setup(self: WalletView) =
@ -50,6 +52,7 @@ QtObject:
result.currentTransactions = newTransactionList()
result.currentCollectiblesLists = newCollectiblesList()
result.defaultTokenList = newTokenList()
result.customTokenList = newTokenList()
result.totalFiatBalance = ""
result.etherscanLink = ""
result.safeLowGasPrice = "0"
@ -295,6 +298,20 @@ QtObject:
self.accountListChanged()
self.currentAccountChanged()
proc removeCustomToken*(self: WalletView, tokenAddress: string) {.slot.} =
let t = getTokenByAddress(getCustomTokens(), tokenAddress)
if t.kind == JNull: return
self.status.wallet.hideAsset(t["symbol"].getStr)
removeCustomToken(tokenAddress)
self.customTokenList.loadCustomTokens()
for account in self.status.wallet.accounts:
if account.address == self.currentAccount.address:
self.currentAccount.setAccountItem(account)
else:
self.accounts.updateAssetsInList(account.address, account.assetList)
self.accountListChanged()
self.currentAccountChanged()
proc updateView*(self: WalletView) =
self.totalFiatBalanceChanged()
self.currentAccountChanged()
@ -456,11 +473,21 @@ QtObject:
result = $status_wallet.getWalletAccounts()[0].address
proc getDefaultTokenList(self: WalletView): QVariant {.slot.} =
self.defaultTokenList.setupTokens()
self.defaultTokenList.loadDefaultTokens()
result = newQVariant(self.defaultTokenList)
QtProperty[QVariant] defaultTokenList:
read = getDefaultTokenList
proc loadCustomTokens(self: WalletView) {.slot.} =
self.customTokenList.loadCustomTokens()
proc getCustomTokenList(self: WalletView): QVariant {.slot.} =
result = newQVariant(self.customTokenList)
QtProperty[QVariant] customTokenList:
read = getCustomTokenList
proc historyWasFetched*(self: WalletView) {.signal.}
proc setHistoryFetchState*(self: WalletView, accounts: seq[string], isFetching: bool) =

View File

@ -1,11 +1,13 @@
import NimQml, tables, json
import ../../../status/libstatus/default_tokens
import ../../../status/libstatus/tokens
type
TokenRoles {.pure.} = enum
Name = UserRole + 1,
Symbol = UserRole + 2,
HasIcon = UserRole + 3
HasIcon = UserRole + 3,
Address = UserRole + 4
QtObject:
type TokenList* = ref object of QAbstractListModel
@ -18,10 +20,16 @@ QtObject:
self.tokens = @[]
self.QAbstractListModel.delete
proc setupTokens*(self:TokenList) =
proc loadDefaultTokens*(self:TokenList) =
if self.tokens.len == 0:
self.tokens = getDefaultTokens().getElems()
proc loadCustomTokens*(self: TokenList) =
self.beginResetModel()
self.tokens = getCustomTokens().getElems()
echo $self.tokens
self.endResetModel()
proc newTokenList*(): TokenList =
new(result, delete)
result.tokens = @[]
@ -40,13 +48,12 @@ QtObject:
case tokenRole:
of TokenRoles.Name: result = newQVariant(token["name"].getStr)
of TokenRoles.Symbol: result = newQVariant(token["symbol"].getStr)
of TokenRoles.HasIcon: result = newQVariant(token["hasIcon"].getBool)
of TokenRoles.HasIcon: result = newQVariant(token{"hasIcon"}.getBool)
of TokenRoles.Address: result = newQVariant(token["address"].getStr)
method roleNames(self: TokenList): Table[int, string] =
{TokenRoles.Name.int:"name",
TokenRoles.Symbol.int:"symbol",
TokenRoles.HasIcon.int:"hasIcon"}.toTable
TokenRoles.HasIcon.int:"hasIcon",
TokenRoles.Address.int:"address"}.toTable
proc forceUpdate*(self: TokenList) =
self.beginResetModel()
self.endResetModel()

View File

@ -1152,15 +1152,3 @@ proc getDefaultTokens*(): JsonNode =
"hasIcon": true
})
proc getTokenBySymbol*(symbol: string): JsonNode =
for defToken in getDefaultTokens().getElems():
if defToken["symbol"].getStr == symbol:
return defToken
return newJNull()
proc getTokenByAddress*(address: string): JsonNode =
for defToken in getDefaultTokens().getElems():
if defToken["address"].getStr == address:
return defToken
return newJNull()

View File

@ -7,22 +7,45 @@ import settings
from types import Setting, Network
import default_tokens
import strutils
import locks
logScope:
topics = "wallet"
proc getCustomTokens*(): JsonNode =
let payload = %* []
let response = callPrivateRPC("wallet_getCustomTokens", payload).parseJson
if response["result"].kind == JNull:
return %* []
return response["result"]
var customTokensLock: Lock
initLock(customTokensLock)
var customTokens {.guard: customTokensLock.} = %*{}
var dirty {.guard: customTokensLock.} = true
proc getCustomTokens*(useCached: bool = true): JsonNode =
{.gcsafe.}:
withLock customTokensLock:
if useCached and not dirty:
result = customTokens
else:
let payload = %* []
result = callPrivateRPC("wallet_getCustomTokens", payload).parseJSON()["result"]
if result.kind == JNull: result = %* []
dirty = false
customTokens = result
proc getTokenBySymbol*(tokenList: JsonNode, symbol: string): JsonNode =
for defToken in tokenList.getElems():
if defToken["symbol"].getStr == symbol:
return defToken
return newJNull()
proc getTokenByAddress*(tokenList: JsonNode, address: string): JsonNode =
for defToken in tokenList.getElems():
if defToken["address"].getStr == address:
return defToken
return newJNull()
proc visibleTokensSNTDefault(): JsonNode =
let currentNetwork = getSetting[string](Setting.Networks_CurrentNetwork)
let SNT = if getCurrentNetwork() == Network.Testnet: "STT" else: "SNT"
let response = getSetting[string](Setting.VisibleTokens, "{\"" & currentNetwork & "\": [\"" & SNT & "\"]}")
echo response
result = response.parseJson
proc toggleAsset*(symbol: string) =
@ -38,6 +61,17 @@ proc toggleAsset*(symbol: string) =
visibleTokens[currentNetwork] = %* visibleTokenList
discard saveSetting(Setting.VisibleTokens, $visibleTokens)
proc hideAsset*(symbol: string) =
let currentNetwork = getSetting[string](Setting.Networks_CurrentNetwork)
let visibleTokens = visibleTokensSNTDefault()
var visibleTokenList = visibleTokens[currentNetwork].to(seq[string])
var symbolIdx = visibleTokenList.find(symbol)
if symbolIdx > -1:
visibleTokenList.del(symbolIdx)
visibleTokens[currentNetwork] = newJArray()
visibleTokens[currentNetwork] = %* visibleTokenList
discard saveSetting(Setting.VisibleTokens, $visibleTokens)
proc getVisibleTokens*(): JsonNode =
let currentNetwork = getSetting[string](Setting.Networks_CurrentNetwork)
let visibleTokens = visibleTokensSNTDefault()
@ -45,24 +79,23 @@ proc getVisibleTokens*(): JsonNode =
let customTokens = getCustomTokens()
result = newJArray()
for v in visibleTokenList:
let t = getTokenBySymbol(v)
let t = getTokenBySymbol(getDefaultTokens(), v)
if t.kind != JNull: result.elems.add(t)
for custToken in customTokens.getElems():
for v in visibleTokenList:
if custToken["symbol"].getStr == v:
result.elems.add(custToken)
break
let ct = getTokenBySymbol(getCustomTokens(), v)
if ct.kind != JNull: result.elems.add(ct)
proc addCustomToken*(address: string, name: string, symbol: string, decimals: int, color: string) =
let payload = %* [{"address": address, "name": name, "symbol": symbol, "decimals": decimals, "color": color}]
discard callPrivateRPC("wallet_addCustomToken", payload)
withLock customTokensLock:
dirty = true
proc removeCustomToken*(address: string) =
let payload = %* [address]
discard callPrivateRPC("wallet_deleteCustomToken", payload)
echo callPrivateRPC("wallet_deleteCustomToken", payload)
withLock customTokensLock:
dirty = true
proc getTokensBalances*(accounts: openArray[string], tokens: openArray[string]): JsonNode =
let payload = %* [accounts, tokens]
@ -80,9 +113,14 @@ proc getTokenBalance*(tokenAddress: string, account: string): string =
let response = callPrivateRPC("eth_call", payload)
let balance = response.parseJson["result"].getStr
let t = getTokenByAddress(tokenAddress)
var decimals = 18
if t.kind != JNull: decimals = t["decimals"].getInt
let t = getTokenByAddress(getDefaultTokens(), tokenAddress)
let ct = getTokenByAddress(getCustomTokens(), tokenAddress)
if t.kind != JNull:
decimals = t["decimals"].getInt
elif ct.kind != JNull:
decimals = ct["decimals"].getInt
result = $hex2Token(balance, decimals)
proc getSNTAddress*(): string =
@ -92,10 +130,3 @@ proc getSNTAddress*(): string =
proc getSNTBalance*(account: string): string =
let snt = contracts.getContract("snt")
result = getTokenBalance("0x" & $snt.address, account)
proc addOrRemoveToken*(enable: bool, address: string, name: string, symbol: string, decimals: int, color: string): JsonNode =
if enable:
addCustomToken(address, name, symbol, decimals, color)
else:
removeCustomToken(address)
getCustomTokens()

View File

@ -198,6 +198,7 @@ proc addWatchOnlyAccount*(self: WalletModel, address: string, accountName: strin
return self.addNewGeneratedAccount(account, "", accountName, color, constants.WATCH, false)
proc hasAsset*(self: WalletModel, account: string, symbol: string): bool =
self.tokens = status_tokens.getVisibleTokens()
self.tokens.anyIt(it["symbol"].getStr == symbol)
proc changeAccountSettings*(self: WalletModel, address: string, accountName: string, color: string): string =
@ -224,6 +225,14 @@ proc toggleAsset*(self: WalletModel, symbol: string) =
updateBalance(account, self.getDefaultCurrency())
self.events.emit("assetChanged", Args())
proc hideAsset*(self: WalletModel, symbol: string) =
status_tokens.hideAsset(symbol)
self.tokens = status_tokens.getVisibleTokens()
for account in self.accounts:
account.assetList = self.generateAccountConfiguredAssets(account.address)
updateBalance(account, self.getDefaultCurrency())
self.events.emit("assetChanged", Args())
proc addCustomToken*(self: WalletModel, symbol: string, enable: bool, address: string, name: string, decimals: int, color: string) =
addCustomToken(address, name, symbol, decimals, color)

View File

@ -75,6 +75,8 @@ ModalPopup {
changeError.open()
return
}
walletModel.loadCustomTokens()
popup.close();
}
}

View File

@ -20,10 +20,7 @@ ModalPopup {
//% "Add custom token"
label: qsTrId("add-custom-token")
anchors.top: parent.top
onClicked: {
popup.close()
addCustomTokenModal.open()
}
onClicked: addCustomTokenModal.open()
}
}

View File

@ -158,6 +158,7 @@ Item {
icon.source: "../../img/add_remove_token.svg"
onTriggered: {
tokenSettingsModal.open()
walletModel.loadCustomTokens()
}
}
Action {

View File

@ -1,5 +1,7 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../../../../imports"
import "../../../../shared"
import "../../Chat/ContactsColumn"
@ -16,70 +18,139 @@ Item {
anchors.top: modalBody.top
}
ListView {
id: tokenListView
spacing: 0
Component {
id: tokenComponent
Item {
id: tokenContainer
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
width: 300
property bool isVisible: symbol && (searchBox.text == "" || name.toLowerCase().includes(searchBox.text.toLowerCase()) || symbol.toLowerCase().includes(searchBox.text.toLowerCase()))
visible: isVisible
height: isVisible ? 40 + Style.current.smallPadding : 0
Image {
id: assetInfoImage
width: 36
height: tokenContainer.isVisible !== "" ? 36 : 0
anchors.top: parent.top
anchors.topMargin: 0
source: hasIcon ? "../../../img/tokens/" + symbol + ".png" : "../../../img/tokens/0-native.png"
anchors.left: parent.left
anchors.leftMargin: 0
}
StyledText {
id: assetSymbol
text: symbol
anchors.left: assetInfoImage.right
anchors.leftMargin: Style.current.smallPadding
anchors.top: assetInfoImage.top
anchors.topMargin: 0
font.pixelSize: 15
}
StyledText {
id: assetFullTokenName
text: name || ""
anchors.bottom: assetInfoImage.bottom
anchors.bottomMargin: 0
anchors.left: assetInfoImage.right
anchors.leftMargin: Style.current.smallPadding
color: Style.current.darkGrey
font.pixelSize: 15
width: 330
}
CheckBox {
id: assetCheck
checked: walletModel.hasAsset("0x123", symbol)
anchors.left: assetFullTokenName.right
anchors.leftMargin: Style.current.smallPadding
onClicked: walletModel.toggleAsset(symbol)
}
MouseArea {
acceptedButtons: Qt.RightButton
anchors.fill: parent
onClicked: contextMenu.popup(assetSymbol.x - 100, assetSymbol.y + 25)
PopupMenu {
id: contextMenu
Action {
icon.source: "../../../img/make-admin.svg"
text: qsTr("Token details")
onTriggered: {
console.log("TODO")
}
}
Action {
icon.source: "../../../img/remove-from-group.svg"
icon.color: Style.current.red
text: qsTr("Remove token")
onTriggered: walletModel.removeCustomToken(address)
}
}
}
}
}
ScrollView {
id: sview
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
contentHeight: tokenList.height
anchors.top: searchBox.bottom
anchors.topMargin: Style.current.smallPadding
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
model: walletModel.defaultTokenList
ScrollBar.vertical: ScrollBar { active: true }
delegate: Component {
id: component
Item {
id: tokenContainer
anchors.right: parent.right
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
width: parent.width
property bool isVisible: symbol && (searchBox.text == "" || name.toLowerCase().includes(searchBox.text.toLowerCase()) || symbol.toLowerCase().includes(searchBox.text.toLowerCase()))
visible: isVisible
height: isVisible ? 40 + Style.current.smallPadding : 0
Item {
id: tokenList
height: childrenRect.height
Column {
id: customTokens
Image {
id: assetInfoImage
width: 36
height: tokenContainer.isVisible !== "" ? 36 : 0
anchors.top: parent.top
anchors.topMargin: 0
source: hasIcon ? "../../../img/tokens/" + symbol + ".png" : "../../../img/tokens/0-native.png"
anchors.left: parent.left
anchors.leftMargin: 0
}
StyledText {
id: assetSymbol
text: symbol
anchors.left: assetInfoImage.right
anchors.leftMargin: Style.current.smallPadding
anchors.top: assetInfoImage.top
anchors.topMargin: 0
font.pixelSize: 15
id: customLbl
text: qsTr("Custom")
font.pixelSize: 13
color: Style.current.secondaryText
height: 20
}
Repeater {
model: walletModel.customTokenList
delegate: tokenComponent
anchors.top: customLbl.bottom
anchors.topMargin: Style.current.smallPadding
}
}
Column {
anchors.top: customTokens.bottom
anchors.topMargin: Style.current.smallPadding
id: defaultTokens
StyledText {
id: assetFullTokenName
text: name || ""
anchors.bottom: assetInfoImage.bottom
anchors.bottomMargin: 0
anchors.left: assetInfoImage.right
anchors.leftMargin: Style.current.smallPadding
color: Style.current.darkGrey
font.pixelSize: 15
id: defaultLbl
text: qsTr("Default")
font.pixelSize: 13
color: Style.current.secondaryText
height: 20
}
CheckBox {
id: assetCheck
checked: walletModel.hasAsset("0x123", symbol)
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
onClicked: walletModel.toggleAsset(symbol)
Repeater {
model: walletModel.defaultTokenList
delegate: tokenComponent
anchors.top: defaultLbl.bottom
anchors.topMargin: Style.current.smallPadding
}
}
}
highlightFollowsCurrentItem: true
}
}