Improve webinterface

This commit is contained in:
Hendrik Hofstadt 2020-08-20 16:53:25 +02:00
parent 6d3dea0884
commit d1850e4c59
4 changed files with 124 additions and 87 deletions

View File

@ -1,21 +1,11 @@
{ {
"short_name": "React App", "short_name": "Wormhole Bridge",
"name": "Create React App Sample", "name": "Solana token bridge",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16", "sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon" "type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
} }
], ],
"start_url": ".", "start_url": ".",

View File

@ -2,7 +2,7 @@ import {PublicKey} from "@solana/web3.js";
const BRIDGE_ADDRESS = "0xac3eB48829fFC3C37437ce4459cE63F1F4d4E0b4"; const BRIDGE_ADDRESS = "0xac3eB48829fFC3C37437ce4459cE63F1F4d4E0b4";
const SOLANA_BRIDGE_PROGRAM = new PublicKey("C2MtMo3upLePs5Sumkw61aBgcgpCw9Q84KyJMovLbXu6"); const SOLANA_BRIDGE_PROGRAM = new PublicKey("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o");
const TOKEN_PROGRAM = new PublicKey("TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"); const TOKEN_PROGRAM = new PublicKey("TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o");

View File

@ -2,19 +2,18 @@ import React, {useContext, useEffect, useState} from 'react';
import ClientContext from "../providers/ClientContext"; import ClientContext from "../providers/ClientContext";
import * as solanaWeb3 from '@solana/web3.js'; import * as solanaWeb3 from '@solana/web3.js';
import {Account, Connection, PublicKey, Transaction} from '@solana/web3.js'; import {Account, Connection, PublicKey, Transaction} from '@solana/web3.js';
import {Button, Col, Form, Input, InputNumber, message, Row, Select, Space} from "antd"; import {Button, Card, Col, Divider, Form, Input, InputNumber, List, message, Row, Select} from "antd";
import {ethers} from "ethers"; import {ethers} from "ethers";
import {Erc20Factory} from "../contracts/Erc20Factory"; import {Erc20Factory} from "../contracts/Erc20Factory";
import {Arrayish, BigNumber, BigNumberish} from "ethers/utils"; import {Arrayish, BigNumber, BigNumberish} from "ethers/utils";
import {WormholeFactory} from "../contracts/WormholeFactory"; import {WormholeFactory} from "../contracts/WormholeFactory";
import {WrappedAssetFactory} from "../contracts/WrappedAssetFactory"; import {WrappedAssetFactory} from "../contracts/WrappedAssetFactory";
import {BRIDGE_ADDRESS} from "../config"; import {BRIDGE_ADDRESS} from "../config";
import {SlotContext} from "../providers/SlotContext";
import {SolanaTokenContext} from "../providers/SolanaTokenContext"; import {SolanaTokenContext} from "../providers/SolanaTokenContext";
import {BridgeContext} from "../providers/BridgeContext"; import {BridgeContext} from "../providers/BridgeContext";
import SplBalances from "../components/SplBalances";
import {AssetMeta, SolanaBridge} from "../utils/bridge"; import {AssetMeta, SolanaBridge} from "../utils/bridge";
import KeyContext from "../providers/KeyContext"; import KeyContext from "../providers/KeyContext";
import {FormInstance} from "antd/lib/form";
// @ts-ignore // @ts-ignore
@ -79,7 +78,6 @@ async function createWrapped(c: Connection, b: SolanaBridge, key: Account, meta:
function Transfer() { function Transfer() {
let c = useContext<solanaWeb3.Connection>(ClientContext); let c = useContext<solanaWeb3.Connection>(ClientContext);
let tokenAccounts = useContext(SolanaTokenContext); let tokenAccounts = useContext(SolanaTokenContext);
let slot = useContext(SlotContext);
let bridge = useContext(BridgeContext); let bridge = useContext(BridgeContext);
let k = useContext(KeyContext); let k = useContext(KeyContext);
@ -91,20 +89,63 @@ function Transfer() {
chainID: 0, chainID: 0,
assetAddress: new Buffer("") assetAddress: new Buffer("")
}); });
let [amount, setAmount] = useState(0); let [amount, setAmount] = useState(new BigNumber(0));
let [amountValid, setAmountValid] = useState(false);
let [address, setAddress] = useState(""); let [address, setAddress] = useState("");
let [addressValid, setAddressValid] = useState(false) let [addressValid, setAddressValid] = useState(false)
let [solanaAccount, setSolanaAccount] = useState(false)
let [solanaAccount, setSolanaAccount] = useState({
valid: false,
message: ""
})
let [wrappedMint, setWrappedMint] = useState("") let [wrappedMint, setWrappedMint] = useState("")
let [recipient, setRecipient] = useState("")
let formRef = React.createRef<FormInstance>();
useEffect(() => { useEffect(() => {
let fetchBalance = async (token: string) => {
try {
let e = WrappedAssetFactory.connect(token, provider);
let addr = await signer.getAddress();
let balance = await e.balanceOf(addr);
let decimals = await e.decimals();
let allowance = await e.allowance(addr, BRIDGE_ADDRESS);
let info = {
balance: balance.div(new BigNumber(10).pow(decimals)),
allowance: allowance.div(new BigNumber(10).pow(decimals)),
decimals: decimals,
isWrapped: false,
chainID: 2,
assetAddress: new Buffer(token.slice(2), "hex")
}
let b = WormholeFactory.connect(BRIDGE_ADDRESS, provider);
let isWrapped = await b.isWrappedAsset(token)
if (isWrapped) {
info.chainID = await e.assetChain()
info.assetAddress = new Buffer((await e.assetAddress()).slice(2), "hex")
info.isWrapped = true
}
setCoinInfo(info)
setAddressValid(true)
} catch (e) {
setAddressValid(false)
}
}
fetchBalance(address) fetchBalance(address)
}, [address]) }, [address])
useEffect(() => { useEffect(() => {
if (!addressValid) { if (!addressValid) {
setWrappedMint("") setWrappedMint("")
setSolanaAccount(false) setSolanaAccount({
valid: false,
message: ""
})
return return
} }
@ -116,62 +157,39 @@ function Transfer() {
setWrappedMint(wrappedMint.toString()) setWrappedMint(wrappedMint.toString())
for (let account of tokenAccounts.balances) { for (let account of tokenAccounts.balances) {
if (account.mint == wrappedMint.toString()) { if (account.account.toString() == recipient) {
setSolanaAccount(true) setSolanaAccount({
valid: true,
message: ""
})
return; return;
} }
} }
setSolanaAccount(false) setSolanaAccount({
valid: false,
message: "Not a valid wrapped token account"
})
} }
getWrappedInfo(); getWrappedInfo();
}, [address, addressValid, tokenAccounts, bridge]) }, [address, addressValid, tokenAccounts, bridge])
async function fetchBalance(token: string) { useEffect(() => {
try { setAmountValid(amount.lte(coinInfo.balance))
let e = WrappedAssetFactory.connect(token, provider); }, [amount, coinInfo])
let addr = await signer.getAddress();
let balance = await e.balanceOf(addr);
let decimals = await e.decimals();
let allowance = await e.allowance(addr, BRIDGE_ADDRESS);
let info = {
balance: balance.div(new BigNumber(10).pow(decimals)),
allowance: allowance.div(new BigNumber(10).pow(decimals)),
decimals: decimals,
isWrapped: false,
chainID: 2,
assetAddress: new Buffer(token.slice(2), "hex")
}
let b = WormholeFactory.connect(BRIDGE_ADDRESS, provider);
let isWrapped = await b.isWrappedAsset(token)
if (isWrapped) {
info.chainID = await e.assetChain()
info.assetAddress = new Buffer((await e.assetAddress()).slice(2), "hex")
info.isWrapped = true
}
setCoinInfo(info)
setAddressValid(true)
} catch (e) {
setAddressValid(false)
}
}
return ( return (
<> <>
<p>Slot: {slot}</p> <Row gutter={12}>
<Row>
<Col span={12}> <Col span={12}>
<Form onFinish={(values) => { <Form onFinish={(values) => {
let recipient = new solanaWeb3.PublicKey(values["recipient"]).toBuffer() let recipient = new solanaWeb3.PublicKey(values["recipient"]).toBuffer()
let transferAmount = new BigNumber(values["amount"]).mul(new BigNumber(10).pow(coinInfo.decimals)); let transferAmount = new BigNumber(values["amount"]).mul(new BigNumber(10).pow(coinInfo.decimals));
if (coinInfo.allowance.toNumber() >= amount || coinInfo.isWrapped) { if (coinInfo.allowance.gte(amount) || coinInfo.isWrapped) {
lockAssets(values["address"], transferAmount, recipient, values["target_chain"]) lockAssets(values["address"], transferAmount, recipient, values["target_chain"])
} else { } else {
approveAssets(values["address"], transferAmount) approveAssets(values["address"], transferAmount)
} }
}} style={{width: "100%"}}> }} style={{width: "100%"}} ref={formRef}>
<Form.Item name="address" validateStatus={addressValid ? "success" : "error"}> <Form.Item name="address" validateStatus={addressValid ? "success" : "error"}>
<Input addonAfter={`Balance: ${coinInfo.balance}`} name="address" <Input addonAfter={`Balance: ${coinInfo.balance}`} name="address"
placeholder={"ERC20 address"} placeholder={"ERC20 address"}
@ -179,46 +197,73 @@ function Transfer() {
setAddress(v.target.value) setAddress(v.target.value)
}}/> }}/>
</Form.Item> </Form.Item>
<Form.Item name="amount" rules={[{ <Form.Item name="amount" validateStatus={amountValid ? "success" : "error"}>
required: true, validator: (rule, value, callback) => { <InputNumber name={"amount"} placeholder={"Amount"} type={"number"} min={0}
let big = new BigNumber(value); onChange={value => {
callback(big.lte(coinInfo.balance) ? undefined : "Amount exceeds balance") // @ts-ignore
} setAmount(new BigNumber(value) || 0)
}]}> }}/>
<InputNumber name={"amount"} placeholder={"Amount"} type={"number"} onChange={value => {
// @ts-ignore
setAmount(value || 0)
}}/>
</Form.Item> </Form.Item>
<Form.Item name="target_chain" <Form.Item name="target_chain"
rules={[{required: true, message: "Please choose a target chain"}]}> rules={[{required: true, message: "Please choose a target chain"}]}>
<Select placeholder="Target Chain"> <Select placeholder="Target Chain" defaultValue={1}>
<Select.Option value={1}> <Select.Option value={1}>
Solana Solana
</Select.Option> </Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item name="recipient" validateStatus={solanaAccount ? "success" : "error"}> <Form.Item name="recipient" validateStatus={solanaAccount.valid ? "success" : "error"}
<Input name="recipient" placeholder={"Address of the recipient"}/> help={recipient === "" ? undefined : solanaAccount.message}>
<Input name="recipient" placeholder={"Address of the recipient"}
onFocus={(v) => {
setRecipient(v.target.value)
}}/>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
{coinInfo.allowance.toNumber() >= amount || coinInfo.isWrapped ? "Transfer" : "Approve"} {coinInfo.allowance.gte(amount) || coinInfo.isWrapped ? "Transfer" : "Approve"}
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>
</Col> </Col>
<Col span={12}>
<Card>
<Row justify={"space-between"} align={"middle"}>
<Col>Token Accounts on Solana:</Col>
<Col><Button size={"small"}
disabled={wrappedMint === ""}
onClick={() => {
createWrapped(c, bridge, k, {
chain: coinInfo.chainID,
address: coinInfo.assetAddress
}, new PublicKey(wrappedMint))
}}>Create new</Button></Col>
</Row>
<Divider/>
<Row>
<Col span={24}>
<List>
{
tokenAccounts.balances
.filter(value => value.mint == wrappedMint)
.map(v => (
<List.Item
actions={[(<Button size={"small"} type={"dashed"} onClick={() => {
setRecipient(v.account.toString())
formRef.current?.setFieldsValue({
"recipient": v.account.toString()
})
}}>use</Button>)]}>
{v.account.toString()}
</List.Item>
))
}
</List>
</Col>
</Row>
</Card>
</Col>
</Row> </Row>
<Col>
<Space><Button disabled={!addressValid} onClick={() => {
createWrapped(c, bridge, k, {
chain: coinInfo.chainID,
address: coinInfo.assetAddress
}, new PublicKey(wrappedMint))
}}>Create Wrapped account</Button>
<p>Mint: {wrappedMint}</p></Space>
<SplBalances/>
</Col>
</> </>
); );
} }

View File

@ -3,7 +3,7 @@ import ClientContext from "../providers/ClientContext";
import * as solanaWeb3 from '@solana/web3.js'; import * as solanaWeb3 from '@solana/web3.js';
import {PublicKey, Transaction} from '@solana/web3.js'; import {PublicKey, Transaction} from '@solana/web3.js';
import * as spl from '@solana/spl-token'; import * as spl from '@solana/spl-token';
import {Button, Col, Form, Input, InputNumber, message, Row, Select} from "antd"; import {Button, Card, Col, Divider, Form, Input, InputNumber, List, message, Row, Select} from "antd";
import {BigNumber} from "ethers/utils"; import {BigNumber} from "ethers/utils";
import SplBalances from "../components/SplBalances"; import SplBalances from "../components/SplBalances";
import {SlotContext} from "../providers/SlotContext"; import {SlotContext} from "../providers/SlotContext";
@ -58,9 +58,9 @@ function TransferSolana() {
return ( return (
<> <>
<p>Slot: {slot}</p> <Row gutter={12}>
<Row>
<Col span={12}> <Col span={12}>
<p>Transfer from Solana:</p>
<Form onFinish={(values) => { <Form onFinish={(values) => {
let recipient = new Buffer(values["recipient"].slice(2), "hex"); let recipient = new Buffer(values["recipient"].slice(2), "hex");
@ -114,7 +114,7 @@ function TransferSolana() {
</Form.Item> </Form.Item>
<Form.Item name="target_chain" <Form.Item name="target_chain"
rules={[{required: true, message: "Please choose a target chain"}]}> rules={[{required: true, message: "Please choose a target chain"}]}>
<Select placeholder="Target Chain"> <Select placeholder="Target Chain" defaultValue={2}>
<Select.Option value={2}> <Select.Option value={2}>
Ethereum Ethereum
</Select.Option> </Select.Option>
@ -129,7 +129,7 @@ function TransferSolana() {
callback() callback()
} }
} }
},]}> }]}>
<Input name="recipient" placeholder={"Address of the recipient"}/> <Input name="recipient" placeholder={"Address of the recipient"}/>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
@ -139,6 +139,8 @@ function TransferSolana() {
</Form.Item> </Form.Item>
</Form> </Form>
</Col> </Col>
</Row>
<Row>
<Col> <Col>
<SplBalances/> <SplBalances/>
</Col> </Col>