Skip to content

Commit

Permalink
User can add custom ERC20 token for the stream.
Browse files Browse the repository at this point in the history
  • Loading branch information
moiz-lakkadkutta committed Feb 12, 2022
1 parent 0dc9dbd commit 3bf29ba
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
151 changes: 151 additions & 0 deletions packages/react-app/src/components/TokenSelect.jsx
Original file line number Diff line number Diff line change
@@ -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 {};
}
};

/*
<TokenSelect
chainId={1}
onChange={setToAddress}
localProvider={localProvider}
nativeToken={{ name: 'Native token', symbol: 'ETH' }}
/>
*/
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 => (
<Select.Option key={i.address} style={{ paddingTop: "5px", paddingBottom: "5px" }} value={i.address}>
<div style={{ display: "flex", alignItems: "center" }}>
{i.logoURI && (
<div style={{ marginRight: "5px" }}>
<img src={i.logoURI} alt={`${i.name} (${i.symbol})`} />
</div>
)}
{i.name} - {i.symbol} {i.address?.substr(0, 5) + "..." + i.address?.substr(-4)}{" "}
{i.unlisted && <span style={{ fontStyle: "italic", fontSize: "12px", marginLeft: "3px" }}> (unlisted) </span>}
</div>
</Select.Option>
));
}, [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 (
<div>
<Select
showSearch
size="large"
showArrow={false}
defaultActiveFirstOption={false}
onSearch={handleSearch}
filterOption={false}
labelInValue={true}
id="0xERC20TokenSelect" // name it something other than address for auto fill doxxing
name="0xERC20TokenSelect" // name it something other than address for auto fill doxxing
placeholder={props.placeholder ? props.placeholder : "Token search... Eg: GTC"}
value={value}
onChange={handleOnChange}
notFoundContent={null}
style={{ width: "100%" }}
>
{children}
</Select>
</div>
);
}
1 change: 1 addition & 0 deletions packages/react-app/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
48 changes: 42 additions & 6 deletions packages/react-app/src/views/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -75,16 +88,14 @@ export default function Home({
"604800"
);
const _startFull = startFull === 1;
const GTCContractAddress = readContracts && readContracts.GTC.address;

const result = tx(
writeContracts &&
writeContracts.StreamFactory.createStreamFor(
userAddress,
capFormatted,
frequencyFormatted,
_startFull,
GTCContractAddress
tokenAddress
),
async (update) => {
console.log("📡 Transaction Update:", update);
Expand Down Expand Up @@ -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 = (<p>Default Token: GTC</p>);

return (
<div
style={{
Expand Down Expand Up @@ -152,12 +175,25 @@ export default function Home({
onChange={(a) => setUserAddress(a)}
/>
<div style={{ marginBottom: 25 }} />
<div style={{ marginBottom: 5 }}>
Token:
<Popover placement="bottomLeft" content={content} arrowPointAtCenter>
<InfoCircleOutlined style={{ marginLeft: 5 }} />
</Popover>
</div>
<TokenSelect
chainId={1}
onChange={setToAddress}
localProvider={mainnetProvider}
nativeToken={{ name: 'Native token', symbol: 'ETH' }}
/>
<div style={{ marginBottom: 25 }} />
<div style={{ display: "flex", flex: 1, flexDirection: "row" }}>
<div style={{ flex: 1, flexDirection: "column" }}>
<div style={{ marginBottom: 5 }}>GTC Amount:</div>
<div style={{ marginBottom: 5 }}>Amount:</div>
<InputNumber
placeholder="Amount"
min={1}
min={minimumAmount()}
value={amount}
onChange={(v) => setAmount(v)}
style={{ width: "100%" }}
Expand Down

0 comments on commit 3bf29ba

Please sign in to comment.