Feature/bridged token indicator (#138)

* Add script to retrieve bridget tokens list; add bridged token indicator to HRC20, HRC721, HRC1155 tokens list

* Set isBridged flag on erc tokens updates
pull/141/head
Artem 3 years ago committed by GitHub
parent a4f22389a9
commit ac53dd3cfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      package.json
  2. 19
      scripts/updateBridgeTokensList.js
  3. 6
      src/components/ERC1155_Pool.tsx
  4. 6
      src/components/ERC20_Pool.tsx
  5. 6
      src/components/ERC721_Pool.tsx
  6. 1
      src/config/bridgeTokensMap.json
  7. 4
      src/config/index.ts
  8. 9
      src/hooks/ERC1155_Pool.ts
  9. 7
      src/hooks/ERC20_Pool.ts
  10. 7
      src/hooks/ERC721_Pool.ts
  11. 12
      src/pages/ERC1155List/ERC1155Table.tsx
  12. 7
      src/pages/ERC1155List/index.tsx
  13. 12
      src/pages/ERC20List/ERC20Table.tsx
  14. 7
      src/pages/ERC20List/index.tsx
  15. 12
      src/pages/ERC721List/ERC721Table.tsx
  16. 7
      src/pages/ERC721List/index.tsx
  17. 3
      src/utils/utils.ts

@ -30,6 +30,7 @@
"build:testnet": "env-cmd -f .env.testnet react-scripts build", "build:testnet": "env-cmd -f .env.testnet react-scripts build",
"build:devnet": "env-cmd -f .env.devnet react-scripts build", "build:devnet": "env-cmd -f .env.devnet react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"update-bridged": "node scripts/updateBridgeTokensList.js",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
@ -65,6 +66,7 @@
"@types/react-window": "^1.8.3", "@types/react-window": "^1.8.3",
"@types/styled-components": "^5.1.9", "@types/styled-components": "^5.1.9",
"js-sha3": "^0.8.0", "js-sha3": "^0.8.0",
"node-fetch": "2",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"react-error-overlay": "6.0.9", "react-error-overlay": "6.0.9",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",

@ -0,0 +1,19 @@
const fs = require('fs');
const fetch = require('node-fetch');
const outputFileName = 'src/config/bridgeTokensMap.json';
(async function updateTokens () {
try {
console.log('Getting tokens list using bridge API...')
const response = await fetch('https://be4.bridge.hmny.io/tokens/?page=0&size=1000');
const { content: tokensList } = await response.json();
const tokensMap = {}
tokensList.forEach(item => {
tokensMap[item.hrc20Address.toLowerCase()] = item.symbol
})
await fs.writeFileSync(outputFileName, JSON.stringify(tokensMap))
console.log(`${tokensList.length} bridge tokens successfully written to '${outputFileName}'`)
} catch (err) {
console.error(err)
}
})()

@ -2,6 +2,7 @@ import React, { useEffect } from "react";
import { getAllERC1155 } from "src/api/client"; import { getAllERC1155 } from "src/api/client";
import { setERC1155Pool, ERC1155 } from "src/hooks/ERC1155_Pool"; import { setERC1155Pool, ERC1155 } from "src/hooks/ERC1155_Pool";
import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB"; import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB";
import { isTokenBridged } from "../utils";
export function ERC1155_Pool() { export function ERC1155_Pool() {
useEffect(() => { useEffect(() => {
@ -12,7 +13,10 @@ export function ERC1155_Pool() {
const erc1155Map = {} as Record<string, ERC1155>; const erc1155Map = {} as Record<string, ERC1155>;
let erc1155: ERC1155[] = await getAllERC1155(); let erc1155: ERC1155[] = await getAllERC1155();
erc1155 = erc1155.map(item => { erc1155 = erc1155.map(item => {
erc1155Map[item.address] = item; erc1155Map[item.address] = {
...item,
isBridged: isTokenBridged(item.address)
};
return { return {
[IndexedDbKeyPath]: item.address, [IndexedDbKeyPath]: item.address,
...item ...item

@ -2,6 +2,7 @@ import React, { useEffect } from "react";
import { getAllERC20 } from "src/api/client"; import { getAllERC20 } from "src/api/client";
import { setERC20Pool, Erc20 } from "src/hooks/ERC20_Pool"; import { setERC20Pool, Erc20 } from "src/hooks/ERC20_Pool";
import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB"; import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB";
import { isTokenBridged } from "../utils";
export function ERC20_Pool() { export function ERC20_Pool() {
useEffect(() => { useEffect(() => {
@ -12,7 +13,10 @@ export function ERC20_Pool() {
let erc20: Erc20[] = await getAllERC20(); let erc20: Erc20[] = await getAllERC20();
const erc20Map = {} as Record<string, Erc20>; const erc20Map = {} as Record<string, Erc20>;
erc20 = erc20.map((item) => { erc20 = erc20.map((item) => {
erc20Map[item.address] = item; erc20Map[item.address] = {
...item,
isBridged: isTokenBridged(item.address)
};
return { return {
[IndexedDbKeyPath]: item.address, [IndexedDbKeyPath]: item.address,
...item ...item

@ -2,6 +2,7 @@ import React, { useEffect } from "react";
import { getAllERC721 } from "src/api/client"; import { getAllERC721 } from "src/api/client";
import { setERC721Pool, ERC721 } from "src/hooks/ERC721_Pool"; import { setERC721Pool, ERC721 } from "src/hooks/ERC721_Pool";
import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB"; import { IndexedDbKeyPath, IndexedDbStore, saveToIndexedDB } from "../utils/indexedDB";
import { isTokenBridged } from "../utils";
export function ERC721_Pool() { export function ERC721_Pool() {
useEffect(() => { useEffect(() => {
@ -12,7 +13,10 @@ export function ERC721_Pool() {
let erc721: ERC721[] = await getAllERC721(); let erc721: ERC721[] = await getAllERC721();
const erc721Map = {} as Record<string, ERC721>; const erc721Map = {} as Record<string, ERC721>;
erc721 = erc721.map((item) => { erc721 = erc721.map((item) => {
erc721Map[item.address] = item; erc721Map[item.address] = {
...item,
isBridged: isTokenBridged(item.address)
};
return { return {
[IndexedDbKeyPath]: item.address, [IndexedDbKeyPath]: item.address,
...item ...item

File diff suppressed because one or more lines are too long

@ -1 +1,5 @@
import { default as bridgeTokens } from "src/config/bridgeTokensMap.json";
export const config = {} export const config = {}
export const bridgeTokensMap: Record<string, string> = bridgeTokens || {}

@ -1,6 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { singletonHook } from "react-singleton-hook"; import { singletonHook } from "react-singleton-hook";
import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB"; import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB";
import { isTokenBridged } from "../utils";
const initValue: ERC1155_Pool = {}; const initValue: ERC1155_Pool = {};
@ -14,7 +15,10 @@ export const useERC1155Pool = singletonHook(initValue, () => {
const erc1155 = await loadFromIndexedDB(IndexedDbStore.ERC1155Pool) const erc1155 = await loadFromIndexedDB(IndexedDbStore.ERC1155Pool)
const erc1155Map = {} as Record<string, ERC1155>; const erc1155Map = {} as Record<string, ERC1155>;
erc1155.forEach(item => { erc1155.forEach(item => {
erc1155Map[item.address] = item; erc1155Map[item.address] = {
...item,
isBridged: isTokenBridged(item.address)
};
}) })
setMode(erc1155Map) setMode(erc1155Map)
} catch (e) { } catch (e) {
@ -44,7 +48,8 @@ export interface ERC1155 {
holders: string; holders: string;
decimals: number; decimals: number;
symbol: string; symbol: string;
meta?: any meta?: any;
isBridged: boolean;
} }
export type ERC1155_Pool = Record<string, ERC1155>; export type ERC1155_Pool = Record<string, ERC1155>;

@ -1,6 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { singletonHook } from "react-singleton-hook"; import { singletonHook } from "react-singleton-hook";
import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB"; import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB";
import { isTokenBridged } from "../utils";
const initValue: ERC20_Pool = {}; const initValue: ERC20_Pool = {};
@ -14,7 +15,10 @@ export const useERC20Pool = singletonHook(initValue, () => {
const erc20 = await loadFromIndexedDB(IndexedDbStore.ERC20Pool) const erc20 = await loadFromIndexedDB(IndexedDbStore.ERC20Pool)
const erc20Map = {} as Record<string, Erc20>; const erc20Map = {} as Record<string, Erc20>;
erc20.forEach(item => { erc20.forEach(item => {
erc20Map[item.address] = item; erc20Map[item.address] = {
...item,
isBridged: isTokenBridged(item.address)
};
}) })
setMode(erc20Map) setMode(erc20Map)
} catch (e) { } catch (e) {
@ -50,6 +54,7 @@ export interface Erc20 {
name?: string name?: string
image?: string; image?: string;
}; };
isBridged: boolean;
} }
export type ERC20_Pool = Record<string, Erc20>; export type ERC20_Pool = Record<string, Erc20>;

@ -1,6 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { singletonHook } from "react-singleton-hook"; import { singletonHook } from "react-singleton-hook";
import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB"; import { IndexedDbStore, loadFromIndexedDB } from "../utils/indexedDB";
import { isTokenBridged } from "../utils";
const initValue: ERC721_Pool = {}; const initValue: ERC721_Pool = {};
@ -14,7 +15,10 @@ export const useERC721Pool = singletonHook(initValue, () => {
const erc721 = await loadFromIndexedDB(IndexedDbStore.ERC721Pool) const erc721 = await loadFromIndexedDB(IndexedDbStore.ERC721Pool)
const erc721Map = {} as Record<string, ERC721>; const erc721Map = {} as Record<string, ERC721>;
erc721.forEach(item => { erc721.forEach(item => {
erc721Map[item.address] = item; erc721Map[item.address] = {
...item,
isBridged: isTokenBridged(item.address)
};
}) })
setMode(erc721Map) setMode(erc721Map)
} catch (e) { } catch (e) {
@ -44,6 +48,7 @@ export interface ERC721 {
holders: string; holders: string;
decimals: number; decimals: number;
symbol: string; symbol: string;
isBridged: boolean;
} }
export type ERC721_Pool = Record<string, ERC721>; export type ERC721_Pool = Record<string, ERC721>;

@ -14,7 +14,8 @@ import {
} from "src/components/ui"; } from "src/components/ui";
import { Erc20 } from "../../hooks/ERC20_Pool"; import { Erc20 } from "../../hooks/ERC20_Pool";
import { ERC1155Icon } from "src/components/ui/ERC1155Icon"; import { ERC1155Icon } from "src/components/ui/ERC1155Icon";
import { CircleQuestion } from "grommet-icons"; import { CircleQuestion, StatusGood } from "grommet-icons";
import { isTokenBridged } from "../../utils";
interface TransactionTableProps { interface TransactionTableProps {
data: any[]; data: any[];
@ -142,7 +143,14 @@ function getColumns(props: any) {
Name Name
</Text> </Text>
), ),
render: (data: Erc20) => <Text size="small">{data.name}</Text>, render: (data: Erc20) => <Box direction={'row'} style={{ display: 'flex', alignItems: 'center' }}>
<Text size={'small'}>{data.name}</Text>
{data.isBridged && <div style={{ marginLeft: "4px", height: '14px', cursor: 'pointer' }}>
<Tip content={'Token is available on Harmony Bridge'}>
<StatusGood size={'14px'} color={'successText'} />
</Tip>
</div>}
</Box>,
}, },
{ {
property: "symbol", property: "symbol",

@ -25,15 +25,16 @@ export const ERC1155List = () => {
const erc1155 = useERC1155Pool(); const erc1155 = useERC1155Pool();
const themeMode = useThemeMode(); const themeMode = useThemeMode();
const erc1155Tokens = Object.values(erc1155); const erc1155Tokens = Object.values(erc1155);
const searchableFields = ["name", "symbol", "address"] as Array<keyof ERC1155>
const searchedTokenLength = erc1155Tokens.filter( const searchedTokenLength = erc1155Tokens.filter(
filterWithFields(["name", "symbol"], search) filterWithFields(searchableFields, search)
).length; ).length;
useEffect(() => { useEffect(() => {
setData( setData(
erc1155Tokens erc1155Tokens
.filter(filterWithFields(["name", "symbol"], search)) .filter(filterWithFields(searchableFields, search))
.sort(sortWithHolders) .sort(sortWithHolders)
//@ts-ignore //@ts-ignore
.slice(filter.offset, filter.offset + filter.limit) .slice(filter.offset, filter.offset + filter.limit)
@ -90,7 +91,7 @@ export const ERC1155List = () => {
backgroundColor: themeMode === "light" ? "white" : "transparent", backgroundColor: themeMode === "light" ? "white" : "transparent",
fontWeight: 500, fontWeight: 500,
}} }}
placeholder="Search by Name / Symbol" placeholder="Search by Name / Symbol / Address"
/> />
</Box> </Box>
{!erc1155Tokens.length && !search && ( {!erc1155Tokens.length && !search && (

@ -13,7 +13,8 @@ import {
TPaginationAction, TPaginationAction,
} from "src/components/ui"; } from "src/components/ui";
import { Erc20 } from "../../hooks/ERC20_Pool"; import { Erc20 } from "../../hooks/ERC20_Pool";
import { CircleQuestion } from "grommet-icons"; import { CircleQuestion, StatusGood } from "grommet-icons";
import { isTokenBridged } from "../../utils";
interface TransactionTableProps { interface TransactionTableProps {
data: any[]; data: any[];
@ -128,7 +129,14 @@ function getColumns(props: any) {
Name Name
</Text> </Text>
), ),
render: (data: Erc20) => <Text size="small">{data.name}</Text>, render: (data: Erc20) => <Box direction={'row'} style={{ display: 'flex', alignItems: 'center' }}>
<Text size={'small'}>{data.name}</Text>
{data.isBridged && <div style={{ marginLeft: "4px", height: '14px', cursor: 'pointer' }}>
<Tip content={'Token is available on Harmony Bridge'}>
<StatusGood size={'14px'} color={'successText'} />
</Tip>
</div>}
</Box>,
}, },
{ {
property: "symbol", property: "symbol",

@ -25,15 +25,16 @@ export const ERC20List = () => {
const erc20 = useERC20Pool(); const erc20 = useERC20Pool();
const themeMode = useThemeMode(); const themeMode = useThemeMode();
const erc20Tokens = Object.values(erc20); const erc20Tokens = Object.values(erc20);
const searchableFields = ["name", "symbol", "address"] as Array<keyof Erc20>
const searchedTokenLength = erc20Tokens.filter( const searchedTokenLength = erc20Tokens.filter(
filterWithFields(["name", "symbol"], search) filterWithFields(searchableFields, search)
).length; ).length;
useEffect(() => { useEffect(() => {
setData( setData(
erc20Tokens erc20Tokens
.filter(filterWithFields(["name", "symbol"], search)) .filter(filterWithFields(searchableFields, search))
.sort(sortWithHolders) .sort(sortWithHolders)
//@ts-ignore //@ts-ignore
.slice(filter.offset, filter.offset + filter.limit) .slice(filter.offset, filter.offset + filter.limit)
@ -91,7 +92,7 @@ export const ERC20List = () => {
backgroundColor: themeMode === "light" ? "white" : "transparent", backgroundColor: themeMode === "light" ? "white" : "transparent",
fontWeight: 500, fontWeight: 500,
}} }}
placeholder="Search by Name / Symbol" placeholder="Search by Name / Symbol / Address"
/> />
</Box> </Box>
{!erc20Tokens.length && !search && ( {!erc20Tokens.length && !search && (

@ -12,7 +12,8 @@ import {
TPaginationAction, TPaginationAction,
} from "src/components/ui"; } from "src/components/ui";
import { Erc20 } from "../../hooks/ERC20_Pool"; import { Erc20 } from "../../hooks/ERC20_Pool";
import { CircleQuestion } from "grommet-icons"; import { CircleQuestion, StatusGood } from "grommet-icons";
import { isTokenBridged } from "../../utils";
interface TransactionTableProps { interface TransactionTableProps {
data: any[]; data: any[];
@ -127,7 +128,14 @@ function getColumns(props: any) {
Name Name
</Text> </Text>
), ),
render: (data: Erc20) => <Text size="small">{data.name}</Text>, render: (data: Erc20) => <Box direction={'row'} style={{ display: 'flex', alignItems: 'center' }}>
<Text size={'small'}>{data.name}</Text>
{data.isBridged && <div style={{ marginLeft: "4px", height: '14px', cursor: 'pointer' }}>
<Tip content={'Token is available on Harmony Bridge'}>
<StatusGood size={'14px'} color={'successText'} />
</Tip>
</div>}
</Box>,
}, },
{ {
property: "symbol", property: "symbol",

@ -25,15 +25,16 @@ export const ERC721List = () => {
const erc721 = useERC721Pool(); const erc721 = useERC721Pool();
const themeMode = useThemeMode(); const themeMode = useThemeMode();
const erc721Tokens = Object.values(erc721); const erc721Tokens = Object.values(erc721);
const searchableFields = ["name", "symbol", "address"] as Array<keyof ERC721>
const searchedTokenLength = erc721Tokens.filter( const searchedTokenLength = erc721Tokens.filter(
filterWithFields(["name", "symbol"], search) filterWithFields(searchableFields, search)
).length; ).length;
useEffect(() => { useEffect(() => {
setData( setData(
erc721Tokens erc721Tokens
.filter(filterWithFields(["name", "symbol"], search)) .filter(filterWithFields(searchableFields, search))
.sort(sortWithHolders) .sort(sortWithHolders)
//@ts-ignore //@ts-ignore
.slice(filter.offset, filter.offset + filter.limit) .slice(filter.offset, filter.offset + filter.limit)
@ -90,7 +91,7 @@ export const ERC721List = () => {
backgroundColor: themeMode === "light" ? "white" : "transparent", backgroundColor: themeMode === "light" ? "white" : "transparent",
fontWeight: 500, fontWeight: 500,
}} }}
placeholder="Search by Name / Symbol" placeholder="Search by Name / Symbol / Address"
/> />
</Box> </Box>
{!erc721Tokens.length && !search && ( {!erc721Tokens.length && !search && (

@ -1,6 +1,7 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { RelatedTransaction, RelatedTransactionType, RPCTransactionHarmony } from "../types"; import { RelatedTransaction, RelatedTransactionType, RPCTransactionHarmony } from "../types";
import { getAddress } from "./getAddress/GetAddress"; import { getAddress } from "./getAddress/GetAddress";
import { bridgeTokensMap } from "src/config";
export const getQueryVariable = (variable: string, query: string) => { export const getQueryVariable = (variable: string, query: string) => {
const vars = query.split("&"); const vars = query.split("&");
@ -34,3 +35,5 @@ export const mapBlockchainTxToRelated = (
} }
return resultedTx return resultedTx
} }
export const isTokenBridged = (address: string) => !!bridgeTokensMap[address]

Loading…
Cancel
Save