diff --git a/src/app/wallet/views/token_list.nim b/src/app/wallet/views/token_list.nim index e35dd894ab..e52060cb5f 100644 --- a/src/app/wallet/views/token_list.nim +++ b/src/app/wallet/views/token_list.nim @@ -1,7 +1,7 @@ -import NimQml, tables -import ../../../status/libstatus/tokens -import ../../../status/libstatus/eth/contracts +import NimQml, tables, json +import ../../../status/libstatus/[tokens, settings, utils, eth/contracts] from web3/conversions import `$` +import ../../../status/threads type TokenRoles {.pure.} = enum @@ -81,3 +81,21 @@ QtObject: TokenRoles.Decimals.int:"decimals", TokenRoles.IsCustom.int:"isCustom"}.toTable + # Resolving a ENS name + proc getTokenDetails*(self: TokenList, address: string) {.slot.} = + spawnAndSend(self, "tokenDetailsResolved") do: + let tkn = newErc20Contract("", getCurrentNetwork(), address.parseAddress, "", 18, false) + + let decimals = tkn.tokenDecimals() + + $ (%* { + "address": address, + "name": tkn.tokenName(), + "symbol": tkn.tokenSymbol(), + "decimals": (if decimals == 0: "" else: $decimals) + }) + + proc tokenDetailsWereResolved*(self: TokenList, tokenDetails: string) {.signal.} + + proc tokenDetailsResolved(self: TokenList, tokenDetails: string) {.slot.} = + self.tokenDetailsWereResolved(tokenDetails) diff --git a/src/status/libstatus/tokens.nim b/src/status/libstatus/tokens.nim index f84816d18d..1ff38394c1 100644 --- a/src/status/libstatus/tokens.nim +++ b/src/status/libstatus/tokens.nim @@ -1,4 +1,4 @@ -import json, chronicles, strformat, stint, strutils, sequtils +import json, chronicles, strformat, stint, strutils, sequtils, tables import core, wallet import ./eth/contracts import web3/[ethtypes, conversions] @@ -131,3 +131,37 @@ proc getSNTAddress*(): string = proc getSNTBalance*(account: string): string = let snt = contracts.getSntContract() result = getTokenBalance($snt.address, account) + +proc getTokenString*(contract: Contract, methodName: string): string = + let payload = %* [{ + "to": $contract.address, + "data": contract.methods[methodName].encodeAbi() + }, "latest"] + + let responseStr = callPrivateRPC("eth_call", payload) + let response = Json.decode(responseStr, RpcResponse) + if not response.error.isNil: + raise newException(RpcException, "Error getting token string - " & methodName & ": " & response.error.message) + if response.result == "0x": + return "" + + let size = fromHex(Stuint[256], response.result[66..129]).toInt + result = response.result[130..129+size*2].parseHexStr + +proc tokenName*(contract: Contract): string = getTokenString(contract, "name") + +proc tokenSymbol*(contract: Contract): string = getTokenString(contract, "symbol") + +proc tokenDecimals*(contract: Contract): int = + let payload = %* [{ + "to": $contract.address, + "data": contract.methods["decimals"].encodeAbi() + }, "latest"] + + let responseStr = callPrivateRPC("eth_call", payload) + let response = Json.decode(responseStr, RpcResponse) + if not response.error.isNil: + raise newException(RpcException, "Error getting token decimals: " & response.error.message) + if response.result == "0x": + return 0 + result = parseHexInt(response.result) diff --git a/ui/app/AppLayouts/Wallet/AddCustomTokenModal.qml b/ui/app/AppLayouts/Wallet/AddCustomTokenModal.qml index c2cf6d6964..7c113dff9b 100644 --- a/ui/app/AppLayouts/Wallet/AddCustomTokenModal.qml +++ b/ui/app/AppLayouts/Wallet/AddCustomTokenModal.qml @@ -9,6 +9,7 @@ ModalPopup { property bool editable: true property int marginBetweenInputs: 35 + property string validationError: "" title: editable ? //% "Add custom token" @@ -39,13 +40,57 @@ ModalPopup { open(); } + + function validate() { + if (addressInput.text !== "" && !Utils.isAddress(addressInput.text)) { + validationError = qsTr("This needs to be a valid address"); + } + return validationError === "" + } + + property var getTokenDetails: Backpressure.debounce(popup, 500, function (tokenAddress){ + walletModel.customTokenList.getTokenDetails(tokenAddress) + }); + + function onKeyReleased(){ + validationError = ""; + + if (!validate() || addressInput.text === "") { + return; + } + Qt.callLater(getTokenDetails, addressInput.text) + } + + Item { + Connections { + target: walletModel.customTokenList + onTokenDetailsWereResolved: { + const jsonObj = JSON.parse(tokenDetails) + + if(jsonObj.address === ""){ + validationError = qsTr("Invalid ERC20 address") + return; + } + + if(addressInput.text.toLowerCase() === jsonObj.address.toLowerCase()){ + symbolInput.text = jsonObj.symbol; + decimalsInput.text = jsonObj.decimals; + nameInput.text = jsonObj.name; + } + } + } + } + Input { id: addressInput readOnly: !editable + textField.maximumLength: 42 //% "Enter contract address..." placeholderText: qsTrId("enter-contract-address...") //% "Contract address" label: qsTrId("contract-address") + validationError: popup.validationError + Keys.onReleased: onKeyReleased() } Input { @@ -98,7 +143,7 @@ ModalPopup { //% "Add" label: qsTrId("add") - disabled: addressInput.text === "" || nameInput.text === "" || symbolInput.text === "" || decimalsInput.text === "" + disabled: validationError !== "" && addressInput.text === "" || nameInput.text === "" || symbolInput.text === "" || decimalsInput.text === "" onClicked : { const error = walletModel.addCustomToken(addressInput.text, nameInput.text, symbolInput.text, decimalsInput.text);