diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f580a04..1a2551d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import { Provider, Web3Provider } from '@ethersproject/providers' import { Symfoni } from "./hardhat/SymfoniContext"; import { Greeter } from './components/Greeter'; import { Bridge } from './components/Bridge'; +import { AdminBridge } from './components/AdminBridge'; import { getAndSetProvider } from './utils/network'; import { ERC20 } from './types/ERC20'; import { Bridge as IBridge } from './types/Bridge'; @@ -26,6 +27,7 @@ function App() { const [sntAvalanche, setSntAvalanche] = useState(); const [ethereumBridge, setEthereumBridge] = useState(); const [avalancheBridge, setAvalancheBridge] = useState(); + const [isRelayer, setIsRelayer] = useState(); useEffect(() => { if (!provider) getAndSetProvider(setProvider); @@ -44,10 +46,11 @@ function App() { }, [provider]) useEffect(() => { - //TODO use ethereum provider if (!provider) return - //TODO implement ava bridge const avalancheBridge: IBridge = getBridge(fujiAddress, fujiProvider); + avalancheBridge.isRelayer(account).then(isRelayer => { + setIsRelayer(isRelayer) + }); const ethereumBridge: IBridge = getBridge(ethereumAddress, provider); setEthereumBridge(ethereumBridge); setAvalancheBridge(avalancheBridge); @@ -77,6 +80,12 @@ function App() { sntAvalanche={sntAvalanche} ethereumBridge={ethereumBridge} />} + {!!isRelayer && } diff --git a/frontend/src/components/AdminBridge.tsx b/frontend/src/components/AdminBridge.tsx new file mode 100644 index 0000000..eaf2cf0 --- /dev/null +++ b/frontend/src/components/AdminBridge.tsx @@ -0,0 +1,155 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { BridgeContext, SymfoniBridge } from "./../hardhat/SymfoniContext"; +import { Wallet, providers, Contract, VoidSigner, BigNumberish } from "ethers"; +import Typography from '@material-ui/core/Typography'; +import { Bridge as IBridge } from "../types/Bridge" +import { SNT_ADDRESS } from "../constants/goerliAddress"; +import { Formik, FormikProps } from 'formik'; +import StatusTextField from './base/TextField'; +import useStyles from '../styles/adminBridge'; +import StatusButton from './base/Button'; +import { ETHEREUM_CHAIN_ID, AVA_CHAIN_ID } from '../constants/networks'; +import { createResourceID, createERCDepositData, toWei } from '../utils/helpers'; +import { Web3Provider } from '@ethersproject/providers'; +import { ERC20 } from "../types/ERC20"; +import { fromWei } from "../utils/helpers" +import { getSetBalance } from "../utils/contracts"; +import { ethereumSNTHandlerAddress } from "../constants/bridges"; +import { getDepositEvents, EnrichedEvent } from "../utils/events"; + +type IBridgeInfo = { + amount: string, + account: string, +} +const wallet = Wallet.createRandom(); + +interface Props { + account: string, + provider: Web3Provider | undefined, + sntEthereum: ERC20 | undefined, + sntAvalanche: ERC20 | undefined, + ethereumBridge: IBridge | undefined, +} +const FUJI_BRIDGE = '0xE57Eb49689bCAE4dE61D326F7E79Bd14aB527f0f'; +const GOERLI_BRIDGE = '0xD0E461b1Dc56503fC72565FA964C28E274146D44'; +const fujiProvider = new providers.JsonRpcProvider("https://api.avax-test.network/ext/bc/C/rpc"); +const goerliProvider = new providers.InfuraProvider("goerli"); +const fujiSigner = new Wallet(wallet.privateKey, fujiProvider); +const fujiVoidSigner = new VoidSigner(wallet.address, fujiProvider); +const goerliVoidsigner = new VoidSigner(wallet.address, goerliProvider); + +export const AdminBridge: React.FC = ({ account, provider, sntEthereum, sntAvalanche, ethereumBridge }) => { + const classes: any = useStyles() + const bridge: SymfoniBridge = useContext(BridgeContext); + const [message, setMessage] = useState(""); + const [inputGreeting, setInputGreeting] = useState(""); + const [goerliBridge, setGoerliBridge] = useState(); + const [fujiBridge, setFujiBridge] = useState(); + const [sntEthereumBalance, setSntEthereumBalance] = useState(); + const [sntAvalancheBalance, setSntAvalancheBalance] = useState(); + const [deposits, setDeposits] = useState(); + const { fieldWidth } = classes; + useEffect(() => { + getDepositEvents(ethereumBridge, setDeposits) + const doAsync = async () => { + if (!bridge.instance) return + console.log("Bridge is deployed at ", bridge.instance.address) + let gBridge = new Contract( + GOERLI_BRIDGE, + bridge.instance.interface, + goerliVoidsigner + ) as IBridge; + setGoerliBridge(gBridge); + let fBridge = new Contract( + FUJI_BRIDGE, + bridge.instance.interface, + fujiVoidSigner + ) as IBridge; + setFujiBridge(fBridge); + }; + doAsync(); + }, [bridge]) + + useEffect(() => { + getSetBalance(sntEthereum, account, setSntEthereumBalance); + }, [account]) + + useEffect(() => { + getSetBalance(sntAvalanche, account, setSntAvalancheBalance); + }, [account]) + + return ( + { + const { amount, account } = values; + const weiAmount = toWei(amount); + const resourceId = createResourceID(SNT_ADDRESS, ETHEREUM_CHAIN_ID); + const encodedData = createERCDepositData(toWei(amount), account); + if (!provider) return; + const signer = provider.getSigner() + const sntActiveProvider = sntEthereum?.connect(signer); + const activeBridge = ethereumBridge?.connect(signer); + if(!bridge || !bridge.instance) return + const approved = await sntActiveProvider?.allowance(account, ethereumSNTHandlerAddress); + if (approved?.lt(weiAmount)) { + const amt = approved.eq(0) ? weiAmount : toWei('0'); + //TODO approve handler not bridge + await sntActiveProvider?.approve(ethereumSNTHandlerAddress, amt); + } else { + console.log({AVA_CHAIN_ID, resourceId, encodedData}); + const deposit = await activeBridge?.deposit( + AVA_CHAIN_ID, + resourceId, + encodedData + ); + if (deposit) { + const receipt = await deposit.wait(1); + console.log({receipt}); + } + } + }} + > + {({ + values, + errors, + handleSubmit, + handleChange, + handleBlur, + setFieldValue + }: FormikProps) => { + return ( +
+ + + + + )} + } +
+ + ) +} diff --git a/frontend/src/styles/adminBridge.js b/frontend/src/styles/adminBridge.js new file mode 100644 index 0000000..77f031c --- /dev/null +++ b/frontend/src/styles/adminBridge.js @@ -0,0 +1,34 @@ +import { makeStyles } from '@material-ui/core/styles' + +const useStyles = makeStyles(theme => ({ + root: { + display: 'grid', + gridTemplateColumns: 'repeat(48, [col] 1fr)', + gridColumn: '3 / 45', + [theme.breakpoints.up('md')]: { + gridTemplateRows: '3rem 6rem 6rem 5rem', + gridColumn: '8 / 42', + }, + marginTop: '3rem' + }, + adornmentText: { + cursor: 'pointer', + color: '#4360DF' + }, + balanceText: { + color: '#025ea2', + gridColumn: '3 / 49', + fontSize: '1.5rem' + }, + title: { + gridColumn: '3 / 49' + }, + fieldWidth: { + gridColumn: '3 / 49' + }, + datePicker: { + borderRadius: '25px' + } +})) + +export default useStyles diff --git a/frontend/src/styles/app.js b/frontend/src/styles/app.js index f183a1b..5551eb6 100644 --- a/frontend/src/styles/app.js +++ b/frontend/src/styles/app.js @@ -6,7 +6,7 @@ const useStyles = makeStyles(theme => ({ gridTemplateColumns: 'repeat(48, [col] 1fr)', gridTemplateRows: '3rem 5rem auto auto', [theme.breakpoints.up('md')]: { - gridTemplateRows: '4rem 4rem auto auto 6rem' + gridTemplateRows: '3rem 25rem' } } })); diff --git a/frontend/src/utils/events.ts b/frontend/src/utils/events.ts new file mode 100644 index 0000000..fed5daa --- /dev/null +++ b/frontend/src/utils/events.ts @@ -0,0 +1,21 @@ +import { Bridge as IBridge } from "../types/Bridge"; +import { Event } from "ethers"; + +export interface EnrichedEvent extends Event { + decoded?: Array +} + +export const getDepositEvents = async (ethereumBridge: IBridge|undefined, setState: Function) => { + if(!ethereumBridge) return + const events = ethereumBridge.filters.Deposit(null,null,null) + const deposits: Event[] = await ethereumBridge.queryFilter(events) + const enriched = deposits.map(d => { + const { data, topics, decode } = d + if (!decode) return + const decoded = decode(data, topics) + const newD: EnrichedEvent = d + newD.decoded = decoded + return newD + }) + setState(enriched) +}