Skip to content

Commit

Permalink
feat(tokens): Use new token metadata source/schema and fetch from blo…
Browse files Browse the repository at this point in the history
…ckchain-api (#4210)

### Description

For ACT-909. Updates the wallet to fetch the new format of token
metadata from the new REST endpoint on blockchain-api and user balances
for all available chains (if multi-chain balances are enabled via
statsig). As soon as the new blockchain-api REST endpoint is updated to
return non-Celo token metadata, multi-chain balances should work
out-of-the-box in-app. In short, there are two functional changes in
this PR:

* Token balances are fetched for all supported chains, provided an
override has been set in Statsig for the `fetch_multi_chain_balances`
feature gate.
* We no longer fetch token metadata directly from Firebase, but instead
the new blockchain-api endpoint.
* Internal (Redux) state has been updated to reflect the schema changes
in the token metadata.

The second bullet above required some rejiggering of types throughout
the repo, and a lot of mechanical changes to tests throughout the app.
Existing places in code that depend on a token's address being available
have been updated to use the "new" (but identical to the "old")
`WithAddress` suffixed types. New code which supports multi-chain
features should begin using the types that make address optional.
Currently, there are no places in code that take advantage of this new
format; we'll need to add a selector that returns the "new format" data
from Redux, typed correctly.

Note that this change requires a migration; I have specifically opted
_not_ to muck about with trying to migrate stored token information in
Redux (re: the trouble caused by attempting to do so with transfers),
and instead opted to wipe the stored token info. This means that users
will need to refresh their balances upon updating.

### Test plan

Unit and manual tested.

### Related issues

- Fixes ACT-909.

### Backwards compatibility

Yes.

---------

Co-authored-by: Charlie Andrews-Jubelt <[email protected]>
  • Loading branch information
jophish and cajubelt authored Sep 26, 2023
1 parent 0ce240f commit ec4623e
Show file tree
Hide file tree
Showing 43 changed files with 646 additions and 199 deletions.
62 changes: 46 additions & 16 deletions src/analytics/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { PincodeType } from 'src/account/reducer'
import { getCurrentUserTraits } from 'src/analytics/selectors'
import { getFeatureGate } from 'src/statsig'
import { getMockStoreData } from 'test/utils'
import { NetworkId } from 'src/transactions/types'

jest.mock('src/statsig')

Expand Down Expand Up @@ -30,8 +31,10 @@ describe('getCurrentUserTraits', () => {
},
tokens: {
tokenBalances: {
'0xcusd': {
'celo-alfajores:0xcusd': {
name: 'Celo Dollars',
tokenId: 'celo-alfajores:0xcusd',
networkId: NetworkId['celo-alfajores'],
address: '0xcusd',
symbol: 'cUSD',
decimals: 18,
Expand All @@ -41,8 +44,10 @@ describe('getCurrentUserTraits', () => {
priceFetchedAt: Date.now(),
isCoreToken: true,
},
'0xceur': {
'celo-alfajores:0xceur': {
name: 'Celo Euros',
tokenId: 'celo-alfajores:0xceur',
networkId: NetworkId['celo-alfajores'],
address: '0xceur',
symbol: 'cEUR',
decimals: 18,
Expand All @@ -52,19 +57,24 @@ describe('getCurrentUserTraits', () => {
priceFetchedAt: Date.now(),
isCoreToken: true,
},
'0xcelo': {
'celo-alfajores:native': {
name: 'Celo',
tokenId: 'celo-alfajores:native',
networkId: NetworkId['celo-alfajores'],
address: '0xcelo',
symbol: 'CELO',
decimals: 18,
imageUrl: '',
usdPrice: '5',
balance: '0',
isNative: true,
priceFetchedAt: Date.now(),
isCoreToken: true,
},
'0xa': {
'celo-alfajores:0xa': {
name: 'a',
tokenId: 'celo-alfajores:0xa',
networkId: NetworkId['celo-alfajores'],
address: '0xa',
symbol: 'A',
decimals: 18,
Expand All @@ -73,8 +83,10 @@ describe('getCurrentUserTraits', () => {
balance: '1',
priceFetchedAt: Date.now(),
},
'0xb': {
'celo-alfajores:0xb': {
name: 'b',
tokenId: 'celo-alfajores:0xb',
networkId: NetworkId['celo-alfajores'],
address: '0xb',
symbol: 'B',
decimals: 18,
Expand All @@ -83,8 +95,10 @@ describe('getCurrentUserTraits', () => {
balance: '3',
priceFetchedAt: Date.now(),
},
'0xc': {
'celo-alfajores:0xc': {
name: 'c',
tokenId: 'celo-alfajores:0xc',
networkId: NetworkId['celo-alfajores'],
address: '0xc',
symbol: 'C',
decimals: 18,
Expand All @@ -93,8 +107,10 @@ describe('getCurrentUserTraits', () => {
balance: '2',
priceFetchedAt: Date.now(),
},
'0xd': {
'celo-alfajores:0xd': {
name: 'd',
tokenId: 'celo-alfajores:0xd',
networkId: NetworkId['celo-alfajores'],
address: '0xd',
symbol: 'D',
decimals: 18,
Expand All @@ -103,8 +119,10 @@ describe('getCurrentUserTraits', () => {
balance: '0.01',
priceFetchedAt: Date.now(),
},
'0xe': {
'celo-alfajores:0xe': {
name: 'e',
tokenId: 'celo-alfajores:0xe',
networkId: NetworkId['celo-alfajores'],
address: '0xe',
symbol: 'E',
decimals: 18,
Expand All @@ -113,8 +131,10 @@ describe('getCurrentUserTraits', () => {
balance: '7',
priceFetchedAt: Date.now(),
},
'0xf': {
'celo-alfajores:0xf': {
name: 'f',
tokenId: 'celo-alfajores:0xf',
networkId: NetworkId['celo-alfajores'],
address: '0xf',
symbol: 'F',
decimals: 18,
Expand All @@ -123,8 +143,10 @@ describe('getCurrentUserTraits', () => {
balance: '6',
priceFetchedAt: Date.now(),
},
'0xg': {
'celo-alfajores:0xg': {
name: 'g',
tokenId: 'celo-alfajores:0xg',
networkId: NetworkId['celo-alfajores'],
address: '0xg',
symbol: 'G',
decimals: 18,
Expand All @@ -133,8 +155,10 @@ describe('getCurrentUserTraits', () => {
balance: '10',
priceFetchedAt: Date.now(),
},
'0xh': {
'celo-alfajores:0xh': {
name: 'h',
tokenId: 'celo-alfajores:0xh',
networkId: NetworkId['celo-alfajores'],
address: '0xh',
symbol: 'H',
decimals: 18,
Expand All @@ -143,8 +167,10 @@ describe('getCurrentUserTraits', () => {
balance: '9.123456789',
priceFetchedAt: Date.now(),
},
'0xi': {
'celo-alfajores:0xi': {
name: 'i',
tokenId: 'celo-alfajores:0xi',
networkId: NetworkId['celo-alfajores'],
address: '0xi',
symbol: 'I',
decimals: 18,
Expand All @@ -153,18 +179,22 @@ describe('getCurrentUserTraits', () => {
balance: '1000',
priceFetchedAt: Date.now(),
},
'0xj': {
'celo-alfajores:0xj': {
name: 'j',
address: '0xi',
tokenId: 'celo-alfajores:0xj',
networkId: NetworkId['celo-alfajores'],
address: '0xj',
symbol: '', // Empty on purpose, will end up using the address
decimals: 18,
imageUrl: '',
usdPrice: '5',
balance: '11.003',
priceFetchedAt: Date.now(),
},
'0xk': {
'celo-alfajores:0xk': {
name: 'k',
tokenId: 'celo-alfajores:0xk',
networkId: NetworkId['celo-alfajores'],
address: '0xk',
symbol: 'K',
decimals: 18,
Expand Down Expand Up @@ -248,7 +278,7 @@ describe('getCurrentUserTraits', () => {
language: 'es-419',
localCurrencyCode: 'PHP',
netWorthUsd: 5764.949123945,
otherTenTokens: 'I:1000,K:80,0xi:11.003,G:10,H:9.12345,E:7,F:6,B:3,C:2,A:1',
otherTenTokens: 'I:1000,K:80,0xj:11.003,G:10,H:9.12345,E:7,F:6,B:3,C:2,A:1',
phoneCountryCallingCode: '+33',
phoneCountryCodeAlpha2: 'FR',
pincodeType: 'CustomPin',
Expand Down
32 changes: 26 additions & 6 deletions src/components/TokenBottomSheet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,30 @@ import TokenBottomSheet, {
DEBOUCE_WAIT_TIME,
TokenPickerOrigin,
} from 'src/components/TokenBottomSheet'
import { TokenBalance } from 'src/tokens/slice'
import { TokenBalanceWithAddress } from 'src/tokens/slice'
import { createMockStore, getElementText } from 'test/utils'
import { mockCeurAddress, mockCusdAddress, mockTestTokenAddress } from 'test/values'
import {
mockCeurAddress,
mockCusdAddress,
mockTestTokenAddress,
mockCeurTokenId,
mockCusdTokenId,
mockTestTokenTokenId,
} from 'test/values'
import { NetworkId } from 'src/transactions/types'

jest.mock('src/components/useShowOrHideAnimation')
jest.mock('src/analytics/ValoraAnalytics')

const tokens: TokenBalance[] = [
const tokens: TokenBalanceWithAddress[] = [
{
balance: new BigNumber('10'),
usdPrice: new BigNumber('1'),
lastKnownUsdPrice: new BigNumber('1'),
symbol: 'cUSD',
address: mockCusdAddress,
tokenId: mockCusdTokenId,
networkId: NetworkId['celo-alfajores'],
isCoreToken: true,
priceFetchedAt: Date.now(),
decimals: 18,
Expand All @@ -34,6 +44,8 @@ const tokens: TokenBalance[] = [
lastKnownUsdPrice: new BigNumber('1.2'),
symbol: 'cEUR',
address: mockCeurAddress,
tokenId: mockCeurTokenId,
networkId: NetworkId['celo-alfajores'],
isCoreToken: true,
priceFetchedAt: Date.now(),
decimals: 18,
Expand All @@ -46,6 +58,8 @@ const tokens: TokenBalance[] = [
usdPrice: null,
lastKnownUsdPrice: new BigNumber('1'),
address: mockTestTokenAddress,
tokenId: mockTestTokenTokenId,
networkId: NetworkId['celo-alfajores'],
priceFetchedAt: Date.now(),
decimals: 18,
name: 'Test Token',
Expand All @@ -56,28 +70,34 @@ const tokens: TokenBalance[] = [
const mockStore = createMockStore({
tokens: {
tokenBalances: {
[mockCusdAddress]: {
[mockCusdTokenId]: {
balance: '10',
usdPrice: '1',
symbol: 'cUSD',
address: mockCusdAddress,
tokenId: mockCusdTokenId,
networkId: NetworkId['celo-alfajores'],
isCoreToken: true,
priceFetchedAt: Date.now(),
name: 'Celo Dollar',
},
[mockCeurAddress]: {
[mockCeurTokenId]: {
balance: '20',
usdPrice: '1.2',
symbol: 'cEUR',
address: mockCeurAddress,
tokenId: mockCeurTokenId,
networkId: NetworkId['celo-alfajores'],
isCoreToken: true,
priceFetchedAt: Date.now(),
name: 'Celo Euro',
},
[mockTestTokenAddress]: {
[mockTestTokenTokenId]: {
balance: '10',
symbol: 'TT',
address: mockTestTokenAddress,
tokenId: mockTestTokenTokenId,
networkId: NetworkId['celo-alfajores'],
priceFetchedAt: Date.now(),
name: 'Test Token',
},
Expand Down
12 changes: 9 additions & 3 deletions src/components/TokenBottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Times from 'src/icons/Times'
import colors, { Colors } from 'src/styles/colors'
import fontStyles from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import { TokenBalance } from 'src/tokens/slice'
import { TokenBalanceWithAddress } from 'src/tokens/slice'

export enum TokenPickerOrigin {
Send = 'Send',
Expand All @@ -29,12 +29,18 @@ interface Props {
origin: TokenPickerOrigin
onTokenSelected: (tokenAddress: string) => void
onClose: () => void
tokens: TokenBalance[]
tokens: TokenBalanceWithAddress[]
searchEnabled?: boolean
title: string
}

function TokenOption({ tokenInfo, onPress }: { tokenInfo: TokenBalance; onPress: () => void }) {
function TokenOption({
tokenInfo,
onPress,
}: {
tokenInfo: TokenBalanceWithAddress
onPress: () => void
}) {
return (
<Touchable onPress={onPress} testID={`${tokenInfo.symbol}Touchable`}>
<View style={styles.tokenOptionContainer}>
Expand Down
6 changes: 3 additions & 3 deletions src/components/TokenIcon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import React from 'react'
import { Provider } from 'react-redux'
import TokenIcon from 'src/components/TokenIcon'
import { createMockStore } from 'test/utils'
import { mockCeloAddress, mockCusdAddress, mockTokenBalances } from 'test/values'
import { mockCeloTokenId, mockCusdTokenId, mockTokenBalances } from 'test/values'

// Setting up the mock token balances with expected additional values
const CELO_TOKEN = mockTokenBalances[mockCeloAddress]
const CELO_TOKEN = mockTokenBalances[mockCeloTokenId]
const CUSD_TOKEN = {
...mockTokenBalances[mockCusdAddress],
...mockTokenBalances[mockCusdTokenId],
networkIconUrl:
'https://raw.githubusercontent.com/valora-inc/address-metadata/main/assets/tokens/CELO.png',
}
Expand Down
15 changes: 11 additions & 4 deletions src/components/TokenTotalLineItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import 'react-native'
import { Provider } from 'react-redux'
import TokenTotalLineItem from 'src/components/TokenTotalLineItem'
import { LocalCurrencyCode } from 'src/localCurrency/consts'
import { LocalAmount } from 'src/transactions/types'
import { NetworkId, LocalAmount } from 'src/transactions/types'
import { createMockStore, getElementText } from 'test/utils'
import { mockCusdAddress } from 'test/values'
import { mockCusdAddress, mockCusdTokenId } from 'test/values'

const mockBtcAddress = '0xbtc'
const mockBtcTokenId = `celo-alfajores:${mockBtcAddress}`

const defaultAmount = new BigNumber(10)
const defaultTokenAddress = mockCusdAddress
Expand Down Expand Up @@ -42,13 +43,19 @@ describe('TokenTotalLineItem', () => {
},
tokens: {
tokenBalances: {
[mockCusdAddress]: {
[mockCusdTokenId]: {
networkId: NetworkId['celo-alfajores'],
address: mockCusdAddress,
tokenId: mockCusdTokenId,
symbol: 'cUSD',
usdPrice: '1',
balance: '10',
priceFetchedAt: Date.now(),
},
[mockBtcAddress]: {
[mockBtcTokenId]: {
networkId: NetworkId['celo-alfajores'],
address: mockBtcAddress,
tokenId: mockBtcTokenId,
symbol: 'WBTC',
usdPrice: '65000',
balance: '0.5',
Expand Down
9 changes: 6 additions & 3 deletions src/consumerIncentives/ConsumerIncentivesHomeScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ import { Screens } from 'src/navigator/Screens'
import { RootState } from 'src/redux/reducers'
import { StoredTokenBalance } from 'src/tokens/slice'
import { createMockStore } from 'test/utils'
import { mockCeurAddress, mockCusdAddress } from 'test/values'
import { mockCeurAddress, mockCusdAddress, mockCusdTokenId } from 'test/values'
import { NetworkId } from 'src/transactions/types'

interface TokenBalances {
[address: string]: StoredTokenBalance
}

const CUSD_TOKEN_BALANCE = {
address: mockCusdAddress,
tokenId: mockCusdTokenId,
networkId: NetworkId['celo-alfajores'],
balance: '50',
usdPrice: '1',
symbol: 'cUSD',
Expand All @@ -36,10 +39,10 @@ const CUSD_TOKEN_BALANCE = {
}

const ONLY_CUSD_BALANCE: TokenBalances = {
[mockCusdAddress]: CUSD_TOKEN_BALANCE,
[mockCusdTokenId]: CUSD_TOKEN_BALANCE,
}
const NO_BALANCES: TokenBalances = {
[mockCusdAddress]: {
[mockCusdTokenId]: {
...CUSD_TOKEN_BALANCE,
balance: '5',
},
Expand Down
Loading

0 comments on commit ec4623e

Please sign in to comment.