Skip to content

Commit

Permalink
Finance: MCD DAI migration (aragon#1048)
Browse files Browse the repository at this point in the history
* Finance, Tokens: update verified tokens
* Finance: upgrade to MCD DAI and use tokenIconUrl() for token icons
* Finance: only show token icons on test networks for known test tokens
  • Loading branch information
sohkai authored Nov 23, 2019
1 parent e9e5bce commit bd9afff
Show file tree
Hide file tree
Showing 10 changed files with 643 additions and 245 deletions.
8 changes: 6 additions & 2 deletions apps/finance/app/src/components/BalanceToken.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react'
import { GU, textStyle, useTheme } from '@aragon/ui'
import { useNetwork } from '@aragon/api-react'
import { tokenIconUrl } from '../lib/icon-utils'
import { formatTokenAmount } from '../lib/utils'

const splitAmount = amount => {
Expand All @@ -21,13 +23,15 @@ const splitAmount = amount => {
}

const BalanceToken = ({
address,
amount,
compact,
symbol,
verified,
convertedAmount = -1,
}) => {
const theme = useTheme()
const network = useNetwork()

return (
<div css="display: inline-block">
Expand All @@ -41,12 +45,12 @@ const BalanceToken = ({
text-transform: uppercase;
`}
>
{verified && symbol && (
{verified && address && (
<img
alt=""
width="20"
height="20"
src={`https://chasing-coins.com/coin/logo/${symbol}`}
src={tokenIconUrl(address, symbol, network && network.type)}
css={`
margin-right: ${0.75 * GU}px;
`}
Expand Down
1 change: 1 addition & 0 deletions apps/finance/app/src/components/Balances.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class Balances extends React.Component {
`}
>
<BalanceToken
address={address}
amount={amount}
compact={compactMode}
convertedAmount={convertedAmount}
Expand Down
46 changes: 21 additions & 25 deletions apps/finance/app/src/components/NewTransfer/Deposit.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import tokenSymbolAbi from '../../abi/token-symbol.json'
import { fromDecimals, toDecimals } from '../../lib/math-utils'
import {
ETHER_TOKEN_FAKE_ADDRESS,
tokenDataFallback,
tokenDataOverride,
getTokenSymbol,
} from '../../lib/token-utils'
import { addressesEqual, isAddress } from '../../lib/web3-utils'
Expand Down Expand Up @@ -161,36 +161,32 @@ class Deposit extends React.Component {
.toPromise()
.catch(() => '-1')

const decimalsFallback =
tokenDataFallback(address, 'decimals', network.type) || '0'
const symbolFallback =
tokenDataFallback(address, 'symbol', network.type) || ''

const tokenData = {
userBalance,
decimals: parseInt(decimalsFallback, 10),
loading: false,
symbol: symbolFallback,
const fetchSymbol = async () => {
const override = tokenDataOverride(address, 'symbol', network.type)
return override || getTokenSymbol(api, address).catch(() => '')
}
const fetchDecimals = async () => {
const override = tokenDataOverride(address, 'decimals', network.type)
const decimals =
override ||
(await token
.decimals()
.toPromise()
.catch(() => '0'))
return parseInt(decimals, 10)
}

const [tokenSymbol, tokenDecimals] = await Promise.all([
getTokenSymbol(api, address).catch(() => ''),
token
.decimals()
.toPromise()
.then(decimals => parseInt(decimals, 10))
.catch(() => ''),
fetchSymbol(),
fetchDecimals(),
])

// If symbol or decimals are resolved, overwrite the fallbacks
if (tokenSymbol) {
tokenData.symbol = tokenSymbol
}
if (tokenDecimals) {
tokenData.decimals = tokenDecimals
return {
userBalance,
decimals: tokenDecimals,
loading: false,
symbol: tokenSymbol,
}

return tokenData
}
validateInputs({ amount, selectedToken } = {}) {
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class TokenSelector extends React.Component {
// Use the verified token address if provided a symbol and it matches
// The symbols in the verified map are all capitalized
const resolvedAddress =
!isAddress(value) && network.type === 'main'
!isAddress(value) && network && network.type === 'main'
? ETHER_TOKEN_VERIFIED_BY_SYMBOL.get(value.toUpperCase()) || ''
: value

Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,65 @@
import React from 'react'
import styled from 'styled-components'
import { GU } from '@aragon/ui'
import { useNetwork } from '@aragon/api-react'
import LocalIdentityBadge from '../LocalIdentityBadge/LocalIdentityBadge'
import { tokenIconUrl } from '../../lib/icon-utils'
import { ETHER_TOKEN_FAKE_ADDRESS } from '../../lib/token-utils'
import { addressesEqual } from '../../lib/web3-utils'

class TokenSelectorInstance extends React.PureComponent {
render() {
const { address, name, symbol, showIcon = true } = this.props
return (
<Main>
{showIcon ? (
<Icon src={`https://chasing-coins.com/coin/logo/${symbol}`} />
) : (
<IconSpacer />
)}
{symbol && <TokenSymbol>{symbol}</TokenSymbol>}
{name && <TokenName>({name})</TokenName>}
{!addressesEqual(address, ETHER_TOKEN_FAKE_ADDRESS) && (
<LocalIdentityBadge badgeOnly compact entity={address} />
)}
</Main>
)
}
}

const Main = styled.div`
display: flex;
align-items: center;
`
const TokenSelectorInstance = React.memo(function TokenSelectorInstance({
address,
name,
symbol,
showIcon = true,
}) {
const network = useNetwork()
return (
<div
css={`
display: flex;
align-items: center;
`}
>
{showIcon ? (
<Icon src={tokenIconUrl(address, symbol, network && network.type)} />
) : (
<div
css={`
width: ${3 * GU}px;
`}
/>
)}
{symbol && (
<span
css={`
margin-right: ${1 * GU}px;
`}
>
{symbol}
</span>
)}
{name && (
<span
css={`
max-width: 110px;
margin-right: ${1 * GU}px;
overflow: hidden;
text-overflow: ellipsis;
`}
>
({name})
</span>
)}
{!addressesEqual(address, ETHER_TOKEN_FAKE_ADDRESS) && (
<LocalIdentityBadge badgeOnly compact entity={address} />
)}
</div>
)
})

const Icon = styled.img.attrs({ alt: '', width: '16', height: '16' })`
margin-right: ${1 * GU}px;
`

const IconSpacer = styled.div`
width: ${3 * GU}px;
`

const TokenName = styled.span`
max-width: 110px;
margin-right: ${1 * GU}px;
overflow: hidden;
text-overflow: ellipsis;
`

const TokenSymbol = styled.span`
margin-right: ${1 * GU}px;
`

export default TokenSelectorInstance
export default React.memo(TokenSelectorInstance)
19 changes: 19 additions & 0 deletions apps/finance/app/src/lib/icon-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { tokenIconUrl as _tokenIconUrl } from '@aragon/ui'
import { getTestTokenAddresses } from '../testnet'
import { ETHER_TOKEN_VERIFIED_BY_SYMBOL } from './verified-tokens'

// Small shim on top of @aragon/ui's tokenIconUrl, to handle our testnet tokens
export const tokenIconUrl = (tokenAddress, tokenSymbol, networkType) => {
if (networkType === 'main') {
return _tokenIconUrl(tokenAddress)
}

// On other networks, only pretend known test tokens are legit
const testTokens = new Set(getTestTokenAddresses(networkType))
if (testTokens.has(tokenAddress.toLowerCase())) {
const mainnetEquivalent = ETHER_TOKEN_VERIFIED_BY_SYMBOL.get(tokenSymbol)
return mainnetEquivalent ? _tokenIconUrl(mainnetEquivalent) : ''
}

return ''
}
26 changes: 14 additions & 12 deletions apps/finance/app/src/lib/token-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import tokenSymbolBytesAbi from '../abi/token-symbol-bytes.json'
import tokenNameAbi from '../abi/token-name.json'
import tokenNameBytesAbi from '../abi/token-name-bytes.json'

const ANT_MAINNET_TOKEN_ADDRESS = '0x960b236A07cf122663c4303350609A66A7B288C0'
const DAI_MAINNET_TOKEN_ADDRESS = '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359'
const ANT_MAINNET_TOKEN_ADDRESS = '0x960b236a07cf122663c4303350609a66a7b288c0'
const DAI_MAINNET_TOKEN_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f'
const SAI_MAINNET_TOKEN_ADDRESS = '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359'
export const ETHER_TOKEN_FAKE_ADDRESS =
'0x0000000000000000000000000000000000000000'

Expand All @@ -18,21 +19,22 @@ const PRESET_TOKENS = new Map([
ETHER_TOKEN_FAKE_ADDRESS,
ANT_MAINNET_TOKEN_ADDRESS,
DAI_MAINNET_TOKEN_ADDRESS,
SAI_MAINNET_TOKEN_ADDRESS,
],
],
])

// 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
// adapt to every situation. The data listed in this map is used as an override
// if either some part of their interface doesn't conform to a standard we
// support.
const KNOWN_TOKENS_FALLBACK = new Map([
const KNOWN_TOKENS_OVERRIDE = new Map([
[
'main',
new Map([
[
DAI_MAINNET_TOKEN_ADDRESS,
{ symbol: 'DAI', name: 'Dai Stablecoin v1.0', decimals: '18' },
SAI_MAINNET_TOKEN_ADDRESS,
{ symbol: 'SAI', name: 'Sai Stablecoin v1.0', decimals: '18' },
],
]),
],
Expand All @@ -44,18 +46,18 @@ export const isTokenVerified = (tokenAddress, networkType) =>
? ETHER_TOKEN_VERIFIED_ADDRESSES.has(tokenAddress.toLowerCase())
: true

export const tokenDataFallback = (tokenAddress, fieldName, networkType) => {
// The fallback list is without checksums
export const tokenDataOverride = (tokenAddress, fieldName, networkType) => {
// The override list is without checksums
const addressWithoutChecksum = tokenAddress.toLowerCase()

const fallbacksForNetwork = KNOWN_TOKENS_FALLBACK.get(networkType)
const overridesForNetwork = KNOWN_TOKENS_OVERRIDE.get(networkType)
if (
fallbacksForNetwork == null ||
!fallbacksForNetwork.has(addressWithoutChecksum)
overridesForNetwork == null ||
!overridesForNetwork.has(addressWithoutChecksum)
) {
return null
}
return fallbacksForNetwork.get(addressWithoutChecksum)[fieldName] || null
return overridesForNetwork.get(addressWithoutChecksum)[fieldName] || null
}

export async function getTokenSymbol(app, address) {
Expand Down
Loading

0 comments on commit bd9afff

Please sign in to comment.