From d99b6e9d62d3de47601077adb6b3b14fbe92f8a9 Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Fri, 21 Dec 2018 08:30:01 +0100 Subject: [PATCH] Finance: add data fallback for known tokens (#616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some known tokens don’t strictly follow ERC-20 and it would be difficult to adapt to every situation. The data listed in this map is used as a fallback if either the name or the symbol can’t be determined from the contract only. --- .../app/src/components/NewTransfer/Deposit.js | 39 +++++++++++++------ apps/finance/app/src/lib/token-utils.js | 30 ++++++++++++++ apps/finance/app/src/script.js | 36 ++++++++++------- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/apps/finance/app/src/components/NewTransfer/Deposit.js b/apps/finance/app/src/components/NewTransfer/Deposit.js index 076cc19295..4b69842972 100644 --- a/apps/finance/app/src/components/NewTransfer/Deposit.js +++ b/apps/finance/app/src/components/NewTransfer/Deposit.js @@ -18,7 +18,10 @@ import tokenDecimalsAbi from '../../abi/token-decimals.json' import tokenSymbolAbi from '../../abi/token-symbol.json' import { fromDecimals, toDecimals } from '../../lib/math-utils' import provideNetwork from '../../lib/provideNetwork' -import { ETHER_TOKEN_FAKE_ADDRESS } from '../../lib/token-utils' +import { + ETHER_TOKEN_FAKE_ADDRESS, + tokenDataFallback, +} from '../../lib/token-utils' import { addressesEqual, isAddress } from '../../lib/web3-utils' import { combineLatest } from '../../rxjs' import ToggleContent from '../ToggleContent' @@ -121,7 +124,7 @@ class Deposit extends React.Component { return selectedToken.value && !selectedToken.data.loading } loadTokenData(address) { - const { app, userAccount } = this.props + const { app, network, userAccount } = this.props // ETH if (addressesEqual(address, ETHER_TOKEN_FAKE_ADDRESS)) { @@ -145,24 +148,38 @@ class Deposit extends React.Component { // Tokens const token = app.external(address, tokenAbi) - return new Promise((resolve, reject) => - combineLatest( - token.symbol(), - token.decimals(), - token.balanceOf(userAccount) - ) + return new Promise(async (resolve, reject) => { + const userBalance = await token + .balanceOf(userAccount) + .first() + .toPromise() + + const decimalsFallback = + tokenDataFallback(address, 'decimals', network.type) || '0' + const symbolFallback = + tokenDataFallback(address, 'symbol', network.type) || '' + + combineLatest(token.decimals(), token.symbol()) .first() .subscribe( - ([symbol, decimals, userBalance]) => + ([decimals = decimalsFallback, symbol = symbolFallback]) => resolve({ symbol, userBalance, decimals: parseInt(decimals, 10), loading: false, }), - reject + () => { + // Decimals and symbols are optional + resolve({ + userBalance, + decimals: parseInt(decimalsFallback, 10), + loading: false, + symbol: symbolFallback, + }) + } ) - ) + }) } validateInputs({ amount, selectedToken } = {}) { amount = amount || this.state.amount diff --git a/apps/finance/app/src/lib/token-utils.js b/apps/finance/app/src/lib/token-utils.js index b45db89641..a5024ebf41 100644 --- a/apps/finance/app/src/lib/token-utils.js +++ b/apps/finance/app/src/lib/token-utils.js @@ -1,5 +1,21 @@ import { ETHER_TOKEN_VERIFIED_ADDRESSES } from './verified-tokens' +// Some known tokens don’t strictly follow ERC-20 and it would be difficult to +// adapt to every situation. The data listed in this map is used as a fallback +// if either some part of their interface doesn't conform to a standard we +// support. +const KNOWN_TOKENS_FALLBACK = new Map([ + [ + 'main', + new Map([ + [ + '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + { symbol: 'DAI', name: 'Dai Stablecoin v1.0', decimals: '18' }, + ], + ]), + ], +]) + export const ETHER_TOKEN_FAKE_ADDRESS = '0x0000000000000000000000000000000000000000' @@ -8,3 +24,17 @@ export const isTokenVerified = (tokenAddress, networkType) => networkType === 'main' ? ETHER_TOKEN_VERIFIED_ADDRESSES.has(tokenAddress.toLowerCase()) : true + +export const tokenDataFallback = (tokenAddress, fieldName, networkType) => { + // The fallback list is without checksums + const addressWithoutChecksum = tokenAddress.toLowerCase() + + const fallbacksForNetwork = KNOWN_TOKENS_FALLBACK.get(networkType) + if ( + fallbacksForNetwork == null || + !fallbacksForNetwork.has(addressWithoutChecksum) + ) { + return null + } + return fallbacksForNetwork.get(addressWithoutChecksum)[fieldName] || null +} diff --git a/apps/finance/app/src/script.js b/apps/finance/app/src/script.js index e367b795dc..88ad719b92 100644 --- a/apps/finance/app/src/script.js +++ b/apps/finance/app/src/script.js @@ -1,7 +1,11 @@ import Aragon from '@aragon/client' import { of } from './rxjs' import { getTestTokenAddresses } from './testnet' -import { ETHER_TOKEN_FAKE_ADDRESS, isTokenVerified } from './lib/token-utils' +import { + ETHER_TOKEN_FAKE_ADDRESS, + isTokenVerified, + tokenDataFallback, +} from './lib/token-utils' import { addressesEqual } from './lib/web3-utils' import tokenDecimalsAbi from './abi/token-decimals.json' import tokenNameAbi from './abi/token-name.json' @@ -252,9 +256,9 @@ function updateTransactions({ transactions = [] }, transactionDetails) { async function newBalanceEntry(tokenContract, tokenAddress, settings) { const [balance, decimals, name, symbol] = await Promise.all([ loadTokenBalance(tokenAddress, settings), - loadTokenDecimals(tokenContract), - loadTokenName(tokenContract), - loadTokenSymbol(tokenContract), + loadTokenDecimals(tokenContract, tokenAddress, settings), + loadTokenName(tokenContract, tokenAddress, settings), + loadTokenSymbol(tokenContract, tokenAddress, settings), ]) return { @@ -285,66 +289,72 @@ function loadTokenBalance(tokenAddress, { vault }) { }) } -function loadTokenDecimals(tokenContract) { +function loadTokenDecimals(tokenContract, tokenAddress, { network }) { return new Promise((resolve, reject) => { if (tokenDecimals.has(tokenContract)) { resolve(tokenDecimals.get(tokenContract)) } else { + const fallback = + tokenDataFallback(tokenAddress, 'decimals', network.type) || '0' tokenContract .decimals() .first() .subscribe( - decimals => { + (decimals = fallback) => { tokenDecimals.set(tokenContract, decimals) resolve(decimals) }, () => { // Decimals is optional - resolve('0') + resolve(fallback) } ) } }) } -function loadTokenName(tokenContract) { +function loadTokenName(tokenContract, tokenAddress, { network }) { return new Promise((resolve, reject) => { if (tokenName.has(tokenContract)) { resolve(tokenName.get(tokenContract)) } else { + const fallback = + tokenDataFallback(tokenAddress, 'name', network.type) || '' tokenContract .name() .first() .subscribe( - name => { + (name = fallback) => { tokenName.set(tokenContract, name) resolve(name) }, () => { // Name is optional - resolve('') + resolve(fallback) } ) } }) } -function loadTokenSymbol(tokenContract) { +function loadTokenSymbol(tokenContract, tokenAddress, { network }) { return new Promise((resolve, reject) => { if (tokenSymbols.has(tokenContract)) { resolve(tokenSymbols.get(tokenContract)) } else { + const fallback = + tokenDataFallback(tokenAddress, 'symbol', network.type) || '' tokenContract .symbol() .first() .subscribe( - symbol => { + (symbol = fallback) => { tokenSymbols.set(tokenContract, symbol) resolve(symbol) }, () => { // Symbol is optional - resolve('') + resolve(fallback) } ) }