diff --git a/packages/react-app/package.json b/packages/react-app/package.json index fe83752..e981a38 100644 --- a/packages/react-app/package.json +++ b/packages/react-app/package.json @@ -54,6 +54,7 @@ "react-qr-reader": "^2.2.1", "react-router-dom": "^5.2.0", "react-scripts": "4.0.0", + "searchico": "^1.2.1", "walletlink": "^2.1.5", "web3modal": "^1.9.1" }, diff --git a/packages/react-app/src/components/TokenSelect.jsx b/packages/react-app/src/components/TokenSelect.jsx new file mode 100644 index 0000000..64161f8 --- /dev/null +++ b/packages/react-app/src/components/TokenSelect.jsx @@ -0,0 +1,151 @@ +import { Select } from "antd"; +import { useState, useMemo, useEffect } from "react"; +import { ethers } from "ethers"; +import axios from "axios"; +import searchico from "searchico"; + +// helpers to load token name and symbol for unlisted tokens +const ERC20ABI = ["function symbol() view returns (string)", "function name() view returns (string)"]; + +const loadERC20 = async (address, p) => { + try { + // load token information here + const r = new ethers.Contract(address, ERC20ABI, p); + const name = await r.name?.(); + const symbol = await r.symbol?.(); + + return { name, symbol }; + } catch (error) { + return {}; + } +}; + +/* + +*/ +export default function TokenSelect({ onChange, chainId = 1, nativeToken = {}, localProvider, ...props }) { + const [value, setValue] = useState(null); + const [list, setList] = useState([]); + const [searchResults, setSearchResults] = useState([]); + + const listCollection = useMemo(() => { + return searchico(list, { keys: ["address", "name", "symbol"] }); + }, [list.length]); + + const children = useMemo(() => { + if (searchResults.length < 1) { + return []; + } + + // use search result to format children + return searchResults.map(i => ( + +
+ {i.logoURI && ( +
+ {`${i.name} +
+ )} + {i.name} - {i.symbol} {i.address?.substr(0, 5) + "..." + i.address?.substr(-4)}{" "} + {i.unlisted && (unlisted) } +
+
+ )); + }, [JSON.stringify(searchResults)]); + + const handleSearch = async val => { + let collectionResult = []; + + if (val.length > 0) { + // TODO : Do all search & filtering here + collectionResult = (listCollection?.find(val) || []).filter(i => i.chainId === chainId); + + if (collectionResult.length < 1) { + const nativeTokenObj = { + chainId: chainId, + decimals: 18, + name: "Default Token", + symbol: "GTC", + address: "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f", + logoURI: "https://assets.coingecko.com/coins/images/15810/thumb/gitcoin.png?1621992929", + ...nativeToken, + }; + + collectionResult.push(nativeTokenObj); + + try { + const checksumAddress = ethers.utils.getAddress(val); + // load contract and try to get name and symbol if there's a provider given + const tokenInfo = localProvider ? await loadERC20(checksumAddress, localProvider) : {}; + collectionResult = [ + { + chainId: chainId, + name: null, + unlisted: true, + symbol: null, + address: checksumAddress, + logoURI: "", + ...tokenInfo, + }, + ]; + } catch (error) { + console.log(`Could not identify this token`); + } + } + } + + setSearchResults(collectionResult); + }; + + const handleOnChange = async e => { + setSearchResults([]); + + // TODO : check if it's an address that's not on list & Add as unlisted + + setValue(e); + + if (typeof onChange === "function") { + onChange(e.value); + } + }; + + const loadList = async () => { + // https://tokens.coingecko.com/uniswap/all.json + const res = await axios.get("https://tokens.coingecko.com/uniswap/all.json"); + const { tokens } = res.data; + + setList(tokens); + }; + + useEffect(() => { + loadList(); + }, []); + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/packages/react-app/src/components/index.js b/packages/react-app/src/components/index.js index 3306839..68a2f17 100644 --- a/packages/react-app/src/components/index.js +++ b/packages/react-app/src/components/index.js @@ -15,6 +15,7 @@ export { default as Swap } from "./Swap"; export { default as ThemeSwitch } from "./ThemeSwitch"; export { default as Timeline } from "./Timeline"; export { default as TokenBalance } from "./TokenBalance"; +export { default as TokenSelect } from "./TokenSelect"; export { default as Wallet } from "./Wallet"; export { default as L2Bridge } from "./L2Bridge"; export { default as QRBlockie } from "./QRBlockie"; diff --git a/packages/react-app/src/views/Home.jsx b/packages/react-app/src/views/Home.jsx index 78965bd..5f9e3e3 100644 --- a/packages/react-app/src/views/Home.jsx +++ b/packages/react-app/src/views/Home.jsx @@ -11,11 +11,14 @@ import { Col, Progress, Spin, + Popover, } from "antd"; -import { AddressInput, Address, Balance } from "../components"; +import { AddressInput, Address, Balance, TokenSelect } from "../components"; +import { InfoCircleOutlined } from "@ant-design/icons"; import { SimpleStreamABI } from "../contracts/external_ABI"; import { useHistory } from "react-router"; import { Link } from "react-router-dom"; +import axios from "axios"; export default function Home({ mainnetProvider, @@ -35,10 +38,20 @@ export default function Home({ const [sData, setData] = useState([]); + const [tokenAddress, setTokenAddress] = useState("0xde30da39c46104798bb5aa3fe8b9e0e1f348163f"); + const [tokenPrice, setTokenPrice] = useState(0); + let copy = JSON.parse(JSON.stringify(streams)); + const getTokenPrice = async (address) => { + const res = await axios.get(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${address}&vs_currencies=usd`); + const price = res.data[address]['usd']; + setTokenPrice(price); + } + useEffect(async () => { // Get an instance for each Stream contract + getTokenPrice(tokenAddress); for (let b in streams) { if (streams) var contract = new ethers.Contract( @@ -75,8 +88,6 @@ export default function Home({ "604800" ); const _startFull = startFull === 1; - const GTCContractAddress = readContracts && readContracts.GTC.address; - const result = tx( writeContracts && writeContracts.StreamFactory.createStreamFor( @@ -84,7 +95,7 @@ export default function Home({ capFormatted, frequencyFormatted, _startFull, - GTCContractAddress + tokenAddress ), async (update) => { console.log("📡 Transaction Update:", update); @@ -121,6 +132,18 @@ export default function Home({ console.log(await result); }; + const setToAddress = (address) => { + setTokenAddress(address); + getTokenPrice(address); + } + + const minimumAmount = () => { + // Minimum 10 USD for a stream + return parseFloat(10/tokenPrice).toFixed(2); + } + + const content = (

Default Token: GTC

); + return (
setUserAddress(a)} />
+
+ Token: + + + +
+ +
-
GTC Amount:
+
Amount:
setAmount(v)} style={{ width: "100%" }}