feat(@desktop/general): move token balance retrieval to status go

Fixes #4677
This commit is contained in:
Sale Djenic 2022-02-07 12:54:29 +01:00 committed by saledjenic
parent e7f1106f79
commit e3b349fe51
5 changed files with 28 additions and 172 deletions

View File

@ -4,11 +4,13 @@ from sugar import `=>`
import web3/ethtypes
from web3/conversions import `$`
import ../../../backend/custom_tokens as custom_tokens
import ../../../backend/tokens as token_backend
import ../settings/service_interface as settings_service
import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
import ./dto, ./static_token
import ./dto
export dto
@ -76,18 +78,19 @@ QtObject:
if activeTokenSymbols.len == 0:
activeTokenSymbols = self.getDefaultVisibleSymbols()
let static_tokens = static_token.all().map(
proc(x: TokenDto): TokenDto =
x.isVisible = activeTokenSymbols.contains(x.symbol)
return x
let chainId = self.settingsService.getCurrentNetworkId()
let responseTokens = token_backend.getTokens(chainId)
let default_tokens = map(
responseTokens.result.getElems(),
proc(x: JsonNode): TokenDto = x.toTokenDto(activeTokenSymbols, hasIcon=true, isCustom=false)
)
let response = custom_tokens.getCustomTokens()
let responseCustomTokens = custom_tokens.getCustomTokens()
self.tokens = concat(
static_tokens,
map(response.result.getElems(), proc(x: JsonNode): TokenDto = x.toTokenDto(activeTokenSymbols))
default_tokens,
map(responseCustomTokens.result.getElems(), proc(x: JsonNode): TokenDto = x.toTokenDto(activeTokenSymbols))
).filter(
proc(x: TokenDto): bool = x.chainId == self.settingsService.getCurrentNetworkId()
proc(x: TokenDto): bool = x.chainId == chainId
)
except Exception as e:

View File

@ -1,126 +0,0 @@
import web3/ethtypes
import ../network/types
import ./dto
proc all*(): seq[TokenDto] =
return @[
newDto("Status Network Token", Mainnet, fromHex(Address, "0x744d70fdbe2ba4cf95131626614a1763df805b9e"), "SNT", 18, true),
newDto("Dai Stablecoin", Mainnet, fromHex(Address, "0x6b175474e89094c44da98b954eedeac495271d0f"), "DAI", 18, true),
newDto("Sai Stablecoin v1.0", Mainnet, fromHex(Address, "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"), "SAI", 18, true),
newDto("MKR", Mainnet, fromHex(Address, "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2"), "MKR", 18, true),
newDto("EOS", Mainnet, fromHex(Address, "0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0"), "EOS", 18, true),
newDto("OMGToken", Mainnet, fromHex(Address, "0xd26114cd6ee289accf82350c8d8487fedb8a0c07"), "OMG", 18, true),
newDto("Populous Platform", Mainnet, fromHex(Address, "0xd4fa1460f537bb9085d22c7bccb5dd450ef28e3a"), "PPT", 8, true),
newDto("Reputation", Mainnet, fromHex(Address, "0x1985365e9f78359a9b6ad760e32412f4a445e862"), "REP", 18, true),
newDto("PowerLedger", Mainnet, fromHex(Address, "0x595832f8fc6bf59c85c527fec3740a1b7a361269"), "POWR", 6, true),
newDto("TenX Pay Token", Mainnet, fromHex(Address, "0xb97048628db6b661d4c2aa833e95dbe1a905b280"), "PAY", 18, true),
newDto("Veros", Mainnet, fromHex(Address, "0x92e78dae1315067a8819efd6dca432de9dcde2e9"), "VRS", 6, false),
newDto("Golem Network Token", Mainnet, fromHex(Address, "0xa74476443119a942de498590fe1f2454d7d4ac0d"), "GNT", 18, true),
newDto("Salt", Mainnet, fromHex(Address, "0x4156d3342d5c385a87d264f90653733592000581"), "SALT", 8, true),
newDto("BNB", Mainnet, fromHex(Address, "0xb8c77482e45f1f44de1745f52c74426c631bdd52"), "BNB", 18, true),
newDto("Basic Attention Token", Mainnet, fromHex(Address, "0x0d8775f648430679a709e98d2b0cb6250d2887ef"), "BAT", 18, true),
newDto("Kyber Network Crystal", Mainnet, fromHex(Address, "0xdd974d5c2e2928dea5f71b9825b8b646686bd200"), "KNC", 18, true),
newDto("BTU Protocol", Mainnet, fromHex(Address, "0xb683D83a532e2Cb7DFa5275eED3698436371cc9f"), "BTU", 18, true),
newDto("Digix DAO", Mainnet, fromHex(Address, "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a"), "DGD", 9, true),
newDto("Aeternity", Mainnet, fromHex(Address, "0x5ca9a71b1d01849c0a95490cc00559717fcf0d1d"), "AE", 18, true),
newDto("Tronix", Mainnet, fromHex(Address, "0xf230b790e05390fc8295f4d3f60332c93bed42e2"), "TRX", 6, true),
newDto("Ethos", Mainnet, fromHex(Address, "0x5af2be193a6abca9c8817001f45744777db30756"), "ETHOS", 8, true),
newDto("Raiden Token", Mainnet, fromHex(Address, "0x255aa6df07540cb5d3d297f0d0d4d84cb52bc8e6"), "RDN", 18, true),
newDto("SingularDTV", Mainnet, fromHex(Address, "0xaec2e87e0a235266d9c5adc9deb4b2e29b54d009"), "SNGLS", 0, true),
newDto("Gnosis Token", Mainnet, fromHex(Address, "0x6810e776880c02933d47db1b9fc05908e5386b96"), "GNO", 18, true),
newDto("StorjToken", Mainnet, fromHex(Address, "0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac"), "STORJ", 8, true),
newDto("AdEx", Mainnet, fromHex(Address, "0x4470bb87d77b963a013db939be332f927f2b992e"), "ADX", 4, false),
newDto("FunFair", Mainnet, fromHex(Address, "0x419d0d8bdd9af5e606ae2232ed285aff190e711b"), "FUN", 8, true),
newDto("Civic", Mainnet, fromHex(Address, "0x41e5560054824ea6b0732e656e3ad64e20e94e45"), "CVC", 8, true),
newDto("ICONOMI", Mainnet, fromHex(Address, "0x888666ca69e0f178ded6d75b5726cee99a87d698"), "ICN", 18, true),
newDto("Walton Token", Mainnet, fromHex(Address, "0xb7cb1c96db6b22b0d3d9536e0108d062bd488f74"), "WTC", 18, true),
newDto("Bytom", Mainnet, fromHex(Address, "0xcb97e65f07da24d46bcdd078ebebd7c6e6e3d750"), "BTM", 8, true),
newDto("0x Protocol Token", Mainnet, fromHex(Address, "0xe41d2489571d322189246dafa5ebde1f4699f498"), "ZRX", 18, true),
newDto("Bancor Network Token", Mainnet, fromHex(Address, "0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c"), "BNT", 18, true),
newDto("Metal", Mainnet, fromHex(Address, "0xf433089366899d83a9f26a773d59ec7ecf30355e"), "MTL", 8, false),
newDto("PayPie", Mainnet, fromHex(Address, "0xc42209accc14029c1012fb5680d95fbd6036e2a0"), "PPP", 18, true),
newDto("ChainLink Token", Mainnet, fromHex(Address, "0x514910771af9ca656af840dff83e8264ecf986ca"), "LINK", 18, true),
newDto("Kin", Mainnet, fromHex(Address, "0x818fc6c2ec5986bc6e2cbf00939d90556ab12ce5"), "KIN", 18, true),
newDto("Aragon Network Token", Mainnet, fromHex(Address, "0x960b236a07cf122663c4303350609a66a7b288c0"), "ANT", 18, true),
newDto("MobileGo Token", Mainnet, fromHex(Address, "0x40395044ac3c0c57051906da938b54bd6557f212"), "MGO", 8, true),
newDto("Monaco", Mainnet, fromHex(Address, "0xb63b606ac810a52cca15e44bb630fd42d8d1d83d"), "MCO", 8, true),
newDto("loopring", Mainnet, fromHex(Address, "0xef68e7c694f40c8202821edf525de3782458639f"), "LRC", 18, true),
newDto("Zeus Shield Coin", Mainnet, fromHex(Address, "0x7a41e0517a5eca4fdbc7fbeba4d4c47b9ff6dc63"), "ZSC", 18, true),
newDto("Streamr DATAcoin", Mainnet, fromHex(Address, "0x0cf0ee63788a0849fe5297f3407f701e122cc023"), "DATA", 18, true),
newDto("Ripio Credit Network Token", Mainnet, fromHex(Address, "0xf970b8e36e23f7fc3fd752eea86f8be8d83375a6"), "RCN", 18, true),
newDto("WINGS", Mainnet, fromHex(Address, "0x667088b212ce3d06a1b553a7221e1fd19000d9af"), "WINGS", 18, true),
newDto("Edgeless", Mainnet, fromHex(Address, "0x08711d3b02c8758f2fb3ab4e80228418a7f8e39c"), "EDG", 0, true),
newDto("Melon Token", Mainnet, fromHex(Address, "0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1"), "MLN", 18, true),
newDto("Moeda Loyalty Points", Mainnet, fromHex(Address, "0x51db5ad35c671a87207d88fc11d593ac0c8415bd"), "MDA", 18, true),
newDto("PILLAR", Mainnet, fromHex(Address, "0xe3818504c1b32bf1557b16c238b2e01fd3149c17"), "PLR", 18, true),
newDto("QRL", Mainnet, fromHex(Address, "0x697beac28b09e122c4332d163985e8a73121b97f"), "QRL", 8, true),
newDto("Modum Token", Mainnet, fromHex(Address, "0x957c30ab0426e0c93cd8241e2c60392d08c6ac8e"), "MOD", 0, true),
newDto("Token-as-a-Service", Mainnet, fromHex(Address, "0xe7775a6e9bcf904eb39da2b68c5efb4f9360e08c"), "TAAS", 6, true),
newDto("GRID Token", Mainnet, fromHex(Address, "0x12b19d3e2ccc14da04fae33e63652ce469b3f2fd"), "GRID", 12, true),
newDto("SANtiment network token", Mainnet, fromHex(Address, "0x7c5a0ce9267ed19b22f8cae653f198e3e8daf098"), "SAN", 18, true),
newDto("SONM Token", Mainnet, fromHex(Address, "0x983f6d60db79ea8ca4eb9968c6aff8cfa04b3c63"), "SNM", 18, true),
newDto("Request Token", Mainnet, fromHex(Address, "0x8f8221afbb33998d8584a2b05749ba73c37a938a"), "REQ", 18, true),
newDto("Substratum", Mainnet, fromHex(Address, "0x12480e24eb5bec1a9d4369cab6a80cad3c0a377a"), "SUB", 2, true),
newDto("Decentraland MANA", Mainnet, fromHex(Address, "0x0f5d2fb29fb7d3cfee444a200298f468908cc942"), "MANA", 18, true),
newDto("AirSwap Token", Mainnet, fromHex(Address, "0x27054b13b1b798b345b591a4d22e6562d47ea75a"), "AST", 4, true),
newDto("R token", Mainnet, fromHex(Address, "0x48f775efbe4f5ece6e0df2f7b5932df56823b990"), "R", 0, true),
newDto("FirstBlood Token", Mainnet, fromHex(Address, "0xaf30d2a7e90d7dc361c8c4585e9bb7d2f6f15bc7"), "1ST", 18, true),
newDto("Cofoundit", Mainnet, fromHex(Address, "0x12fef5e57bf45873cd9b62e9dbd7bfb99e32d73e"), "CFI", 18, true),
newDto("Enigma", Mainnet, fromHex(Address, "0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4"), "ENG", 8, true),
newDto("Amber Token", Mainnet, fromHex(Address, "0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce"), "AMB", 18, true),
newDto("XPlay Token", Mainnet, fromHex(Address, "0x90528aeb3a2b736b780fd1b6c478bb7e1d643170"), "XPA", 18, true),
newDto("Open Trading Network", Mainnet, fromHex(Address, "0x881ef48211982d01e2cb7092c915e647cd40d85c"), "OTN", 18, true),
newDto("Trustcoin", Mainnet, fromHex(Address, "0xcb94be6f13a1182e4a4b6140cb7bf2025d28e41b"), "TRST", 6, true),
newDto("Monolith TKN", Mainnet, fromHex(Address, "0xaaaf91d9b90df800df4f55c205fd6989c977e73a"), "TKN", 8, true),
newDto("RHOC", Mainnet, fromHex(Address, "0x168296bb09e24a88805cb9c33356536b980d3fc5"), "RHOC", 8, true),
newDto("Target Coin", Mainnet, fromHex(Address, "0xac3da587eac229c9896d919abc235ca4fd7f72c1"), "TGT", 1, false),
newDto("Everex", Mainnet, fromHex(Address, "0xf3db5fa2c66b7af3eb0c0b782510816cbe4813b8"), "EVX", 4, true),
newDto("ICOS", Mainnet, fromHex(Address, "0x014b50466590340d41307cc54dcee990c8d58aa8"), "ICOS", 6, true),
newDto("district0x Network Token", Mainnet, fromHex(Address, "0x0abdace70d3790235af448c88547603b945604ea"), "DNT", 18, true),
newDto("Dentacoin", Mainnet, fromHex(Address, "0x08d32b0da63e2c3bcf8019c9c5d849d7a9d791e6"), "٨", 0, false),
newDto("Eidoo Token", Mainnet, fromHex(Address, "0xced4e93198734ddaff8492d525bd258d49eb388e"), "EDO", 18, true),
newDto("BitDice", Mainnet, fromHex(Address, "0x29d75277ac7f0335b2165d0895e8725cbf658d73"), "CSNO", 8, false),
newDto("Cobinhood Token", Mainnet, fromHex(Address, "0xb2f7eb1f2c37645be61d73953035360e768d81e6"), "COB", 18, true),
newDto("Enjin Coin", Mainnet, fromHex(Address, "0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c"), "ENJ", 18, false),
newDto("AVENTUS", Mainnet, fromHex(Address, "0x0d88ed6e74bbfd96b831231638b66c05571e824f"), "AVT", 18, false),
newDto("Chronobank TIME", Mainnet, fromHex(Address, "0x6531f133e6deebe7f2dce5a0441aa7ef330b4e53"), "TIME", 8, false),
newDto("Cindicator Token", Mainnet, fromHex(Address, "0xd4c435f5b09f855c3317c8524cb1f586e42795fa"), "CND", 18, true),
newDto("Stox", Mainnet, fromHex(Address, "0x006bea43baa3f7a6f765f14f10a1a1b08334ef45"), "STX", 18, true),
newDto("Xaurum", Mainnet, fromHex(Address, "0x4df812f6064def1e5e029f1ca858777cc98d2d81"), "XAUR", 8, true),
newDto("Vibe", Mainnet, fromHex(Address, "0x2c974b2d0ba1716e644c1fc59982a89ddd2ff724"), "VIB", 18, true),
newDto("PRG", Mainnet, fromHex(Address, "0x7728dfef5abd468669eb7f9b48a7f70a501ed29d"), "PRG", 6, false),
newDto("Delphy Token", Mainnet, fromHex(Address, "0x6c2adc2073994fb2ccc5032cc2906fa221e9b391"), "DPY", 18, true),
newDto("CoinDash Token", Mainnet, fromHex(Address, "0x2fe6ab85ebbf7776fee46d191ee4cea322cecf51"), "CDT", 18, true),
newDto("Tierion Network Token", Mainnet, fromHex(Address, "0x08f5a9235b08173b7569f83645d2c7fb55e8ccd8"), "TNT", 8, true),
newDto("DomRaiderToken", Mainnet, fromHex(Address, "0x9af4f26941677c706cfecf6d3379ff01bb85d5ab"), "DRT", 8, true),
newDto("SPANK", Mainnet, fromHex(Address, "0x42d6622dece394b54999fbd73d108123806f6a18"), "SPANK", 18, true),
newDto("Berlin Coin", Mainnet, fromHex(Address, "0x80046305aaab08f6033b56a360c184391165dc2d"), "BRLN", 18, true),
newDto("USD//C", Mainnet, fromHex(Address, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), "USDC", 6, true),
newDto("Livepeer Token", Mainnet, fromHex(Address, "0x58b6a8a3302369daec383334672404ee733ab239"), "LPT", 18, true),
newDto("Simple Token", Mainnet, fromHex(Address, "0x2c4e8f2d746113d0696ce89b35f0d8bf88e0aeca"), "ST", 18, true),
newDto("Wrapped BTC", Mainnet, fromHex(Address, "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"), "WBTC", 8, true),
newDto("Bloom Token", Mainnet, fromHex(Address, "0x107c4504cd79c5d2696ea0030a8dd4e92601b82e"), "BLT", 18, true),
newDto("Unisocks", Mainnet, fromHex(Address, "0x23b608675a2b2fb1890d3abbd85c5775c51691d5"), "SOCKS", 18, true),
newDto("Hermez Network Token", Mainnet, fromHex(Address, "0xEEF9f339514298C6A857EfCfC1A762aF84438dEE"), "HEZ", 18, true),
newDto("Uniswap", Mainnet, fromHex(Address, "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"), "UNI", 18, true),
newDto("Compound", Mainnet, fromHex(Address, "0xc00e94cb662c3520282e6f5717214004a7f26888"), "COMP", 18, true),
newDto("Balancer", Mainnet, fromHex(Address, "0xba100000625a3754423978a60c9317c58a424e3d"), "BAL", 18, true),
newDto("Akropolis", Mainnet, fromHex(Address, "0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7"), "AKRO", 18, true),
newDto("Orchid", Mainnet, fromHex(Address, "0x4575f41308EC1483f3d399aa9a2826d74Da13Deb"), "OXT", 18, false),
newDto("Status Test Token", Ropsten, fromHex(Address, "0xc55cf4b03948d7ebc8b9e8bad92643703811d162"), "STT", 18, true),
newDto("Handy Test Token", Ropsten, fromHex(Address, "0xdee43a267e8726efd60c2e7d5b81552dcd4fa35c"), "HND", 0, false),
newDto("Lucky Test Token", Ropsten, fromHex(Address, "0x703d7dc0bc8e314d65436adf985dda51e09ad43b"), "LXS", 2, false),
newDto("Adi Test Token", Ropsten, fromHex(Address, "0xe639e24346d646e927f323558e6e0031bfc93581"), "ADI", 7, false),
newDto("Wagner Test Token", Ropsten, fromHex(Address, "0x2e7cd05f437eb256f363417fd8f920e2efa77540"), "WGN", 10, false),
newDto("Modest Test Token", Ropsten, fromHex(Address, "0x57cc9b83730e6d22b224e9dc3e370967b44a2de0"), "MDS", 18, false),
# Rinkeby contracts
newDto("Moksha Coin", Rinkeby, fromHex(Address, "0x6ba7dc8dd10880ab83041e60c4ede52bb607864b"), "MOKSHA", 18, false),
newDto("WIBB", Rinkeby, fromHex(Address, "0x7d4ccf6af2f0fdad48ee7958bcc28bdef7b732c7"), "WIBB", 18, false),
newDto("Status Test Token", Rinkeby, fromHex(Address, "0x43d5adc3b49130a575ae6e4b00dfa4bc55c71621"), "STT", 18, false),
# xDai contracts
newDto("buffiDai", XDai, fromHex(Address, "0x3e50bf6703fc132a94e4baff068db2055655f11b"), "BUFF", 18, false),
]

View File

@ -10,6 +10,7 @@ import ./service_interface, ./dto
import ../../../app/core/eventemitter
import ../../../backend/accounts as status_go_accounts
import ../../../backend/tokens as status_go_tokens
import ../../../backend/eth as status_go_eth
export service_interface
@ -66,20 +67,6 @@ proc fetchAccounts(): seq[WalletAccountDto] =
x => x.toWalletAccountDto()
).filter(a => not a.isChat)
proc fetchTokenBalance(tokenAddress, accountAddress: string, decimals: int): float64 =
let key = tokenAddress & accountAddress
if balanceCache.hasKey(key):
return balanceCache[key]
try:
let tokenBalanceResponse = status_go_eth.getTokenBalance(tokenAddress, accountAddress)
result = parsefloat(hex2Balance(tokenBalanceResponse.result.getStr, decimals))
balanceCache[key] = result
except Exception as e:
error "Error getting token balance", msg = e.msg
proc fetchEthBalance(accountAddress: string): float64 =
let key = "0x0" & accountAddress
if balanceCache.hasKey(key):
@ -132,7 +119,8 @@ method getVisibleTokens(self: Service): seq[TokenDto] =
method buildTokens(
self: Service,
account: WalletAccountDto,
prices: Table[string, float64]
prices: Table[string, float64],
balances: JsonNode
): seq[WalletTokenDto] =
let balance = fetchEthBalance(account.address)
result = @[WalletTokenDto(
@ -148,7 +136,7 @@ method buildTokens(
)]
for token in self.getVisibleTokens():
let balance = fetchTokenBalance($token.address, account.address, token.decimals)
let balance = parsefloat(hex2Balance(balances{token.addressAsString()}.getStr, token.decimals))
result.add(
WalletTokenDto(
name: token.name,
@ -174,11 +162,19 @@ method fetchPrices(self: Service): Table[string, float64] =
return prices
method fetchBalances(self: Service, accounts: seq[string]): JsonNode =
let chainId = self.settingsService.getCurrentNetworkId()
let tokens = self.getVisibleTokens().map(t => t.addressAsString())
return status_go_tokens.getBalances(chainId, accounts, tokens).result
method refreshBalances(self: Service) =
let prices = self.fetchPrices()
let accounts = toSeq(self.accounts.keys)
let balances = self.fetchBalances(accounts)
for account in toSeq(self.accounts.values):
account.tokens = self.buildTokens(account, prices)
account.tokens = self.buildTokens(account, prices, balances{account.address})
method init*(self: Service) =
try:
@ -243,7 +239,9 @@ method saveAccount(
newAccount = account
break
newAccount.tokens = self.buildTokens(newAccount, prices)
let balances = self.fetchBalances(@[newAccount.address])
newAccount.tokens = self.buildTokens(newAccount, prices, balances{newAccount.address})
self.accounts[newAccount.address] = newAccount
self.events.emit("walletAccount/accountSaved", AccountSaved(account: newAccount))
except Exception as e:

View File

@ -14,14 +14,6 @@ proc getEthBalance*(address: string): RpcResponse[JsonNode] {.raises: [Exception
let payload = %* [address, "latest"]
return core.callPrivateRPC("eth_getBalance", payload)
proc getTokenBalance*(tokenAddress: string, accountAddress: string): RpcResponse[JsonNode] {.raises: [Exception].} =
var postfixedAccount: string = accountAddress
postfixedAccount.removePrefix("0x")
let payload = %* [{
"to": tokenAddress, "from": accountAddress, "data": fmt"0x70a08231000000000000000000000000{postfixedAccount}"
}, "latest"]
return core.callPrivateRPC("eth_call", payload)
proc sendTransaction*(transactionData: string, password: string): RpcResponse[JsonNode] {.raises: [Exception].} =
core.sendTransaction(transactionData, password)

View File

@ -9,14 +9,3 @@ proc getTokens*(chainId: int): RpcResponse[JsonNode] {.raises: [Exception].} =
proc getBalances*(chainId: int, accounts: seq[string], tokens: seq[string]): RpcResponse[JsonNode] {.raises: [Exception].} =
return callPrivateRPC("wallet_getTokensBalancesForChainIDs", %* [@[chainId], accounts, tokens])
proc getCustomTokens*(): RpcResponse[JsonNode] {.raises: [Exception].} =
return callPrivateRPC("wallet_getCustomTokens", %* [])
proc addCustomToken*(address: string, name: string, symbol: string, decimals: int, color: string) {.raises: [Exception].} =
discard callPrivateRPC("wallet_addCustomToken", %* [
{"address": address, "name": name, "symbol": symbol, "decimals": decimals, "color": color}
])
proc removeCustomToken*(address: string) {.raises: [Exception].} =
discard callPrivateRPC("wallet_deleteCustomToken", %* [address])