Improve webinterface
This commit is contained in:
parent
6d3dea0884
commit
d1850e4c59
|
@ -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": ".",
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue