lp_ui: ethereum migration pool functionality
Change-Id: Ibdf24e1f90e711e5284016045c0c7d9d413be4ac
This commit is contained in:
parent
7e6123a3a8
commit
d8a8d5722a
|
@ -7,12 +7,12 @@
|
|||
"": {
|
||||
"name": "lp_ui",
|
||||
"version": "0.1.0",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "file:..\\sdk\\js",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@solana/spl-token": "^0.1.6",
|
||||
"@solana/spl-token-registry": "^0.2.216",
|
||||
"@solana/wallet-adapter-base": "^0.5.2",
|
||||
|
@ -25,6 +25,7 @@
|
|||
"@types/node": "^16.9.1",
|
||||
"@types/react": "^17.0.20",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"clsx": "^1.1.1",
|
||||
"ethers": "^5.4.6",
|
||||
"notistack": "^1.0.10",
|
||||
"react": "^17.0.2",
|
||||
|
@ -39,7 +40,7 @@
|
|||
},
|
||||
"../sdk/js": {
|
||||
"name": "@certusone/wormhole-sdk",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.5",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@improbable-eng/grpc-web": "^0.14.0",
|
||||
|
@ -3542,6 +3543,14 @@
|
|||
"react-dom": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@metamask/detect-provider": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@metamask/detect-provider/-/detect-provider-1.2.0.tgz",
|
||||
"integrity": "sha512-ocA76vt+8D0thgXZ7LxFPyqw3H7988qblgzddTDA6B8a/yU0uKV42QR/DhA+Jh11rJjxW0jKvwb5htA6krNZDQ==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -26318,6 +26327,11 @@
|
|||
"react-is": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"@metamask/detect-provider": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@metamask/detect-provider/-/detect-provider-1.2.0.tgz",
|
||||
"integrity": "sha512-ocA76vt+8D0thgXZ7LxFPyqw3H7988qblgzddTDA6B8a/yU0uKV42QR/DhA+Jh11rJjxW0jKvwb5htA6krNZDQ=="
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@solana/spl-token": "^0.1.6",
|
||||
"@solana/spl-token-registry": "^0.2.216",
|
||||
"@solana/wallet-adapter-base": "^0.5.2",
|
||||
|
@ -19,6 +20,7 @@
|
|||
"@types/node": "^16.9.1",
|
||||
"@types/react": "^17.0.20",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"clsx": "^1.1.1",
|
||||
"ethers": "^5.4.6",
|
||||
"notistack": "^1.0.10",
|
||||
"react": "^17.0.2",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Main from "./views/Main";
|
||||
import Home from "./views/Home";
|
||||
|
||||
function App() {
|
||||
return <Main />;
|
||||
return <Home />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { Typography } from "@material-ui/core";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import ToggleConnectedButton from "./ToggleConnectedButton";
|
||||
|
||||
const EthereumSignerKey = () => {
|
||||
const { connect, disconnect, signerAddress, providerError } =
|
||||
useEthereumProvider();
|
||||
return (
|
||||
<>
|
||||
<ToggleConnectedButton
|
||||
connect={connect}
|
||||
disconnect={disconnect}
|
||||
connected={!!signerAddress}
|
||||
pk={signerAddress || ""}
|
||||
/>
|
||||
{providerError ? (
|
||||
<Typography variant="body2" color="error">
|
||||
{providerError}
|
||||
</Typography>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EthereumSignerKey;
|
|
@ -1,13 +1,8 @@
|
|||
import { Button, Paper, Typography } from "@material-ui/core";
|
||||
import { useEffect } from "react";
|
||||
import { useLogger } from "../contexts/Logger";
|
||||
|
||||
function LogWatcher() {
|
||||
const { logs, clear, log } = useLogger();
|
||||
|
||||
useEffect(() => {
|
||||
log("Instantiated the logger.");
|
||||
}, [log]);
|
||||
const { logs, clear } = useLogger();
|
||||
|
||||
return (
|
||||
<Paper style={{ padding: "1rem", maxHeight: "600px", overflow: "auto" }}>
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Button, makeStyles, Tooltip, Typography } from "@material-ui/core";
|
||||
import { FileCopy, OpenInNew } from "@material-ui/icons";
|
||||
import { withStyles } from "@material-ui/styles";
|
||||
import clsx from "clsx";
|
||||
import useCopyToClipboard from "../hooks/useCopyToClipboard";
|
||||
import { CLUSTER } from "../utils/consts";
|
||||
import { shortenAddress } from "../utils/solana";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
mainTypog: {
|
||||
display: "inline-block",
|
||||
marginLeft: theme.spacing(1),
|
||||
marginRight: theme.spacing(1),
|
||||
textDecoration: "underline",
|
||||
textUnderlineOffset: "2px",
|
||||
},
|
||||
noGutter: {
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
},
|
||||
noUnderline: {
|
||||
textDecoration: "none",
|
||||
},
|
||||
buttons: {
|
||||
marginLeft: ".5rem",
|
||||
marginRight: ".5rem",
|
||||
},
|
||||
}));
|
||||
|
||||
const tooltipStyles = {
|
||||
tooltip: {
|
||||
minWidth: "max-content",
|
||||
textAlign: "center",
|
||||
"& > *": {
|
||||
margin: ".25rem",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
const StyledTooltip = withStyles(tooltipStyles)(Tooltip);
|
||||
|
||||
export default function SmartAddress({
|
||||
chainId,
|
||||
address,
|
||||
symbol,
|
||||
tokenName,
|
||||
variant,
|
||||
noGutter,
|
||||
noUnderline,
|
||||
}: {
|
||||
chainId: ChainId;
|
||||
address?: string;
|
||||
logo?: string;
|
||||
tokenName?: string;
|
||||
symbol?: string;
|
||||
variant?: any;
|
||||
noGutter?: boolean;
|
||||
noUnderline?: boolean;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const useableAddress = address || "";
|
||||
const useableSymbol = symbol || "";
|
||||
const isNative = false;
|
||||
const addressShort = shortenAddress(useableAddress) || "";
|
||||
|
||||
const useableName = tokenName || "";
|
||||
//TODO terra
|
||||
const explorerAddress = isNative
|
||||
? null
|
||||
: chainId === CHAIN_ID_ETH
|
||||
? `https://${
|
||||
CLUSTER === "testnet" ? "goerli." : ""
|
||||
}etherscan.io/address/${useableAddress}`
|
||||
: chainId === CHAIN_ID_SOLANA
|
||||
? `https://explorer.solana.com/address/${useableAddress}${
|
||||
CLUSTER === "testnet"
|
||||
? "?cluster=testnet"
|
||||
: CLUSTER === "devnet"
|
||||
? "?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899"
|
||||
: ""
|
||||
}`
|
||||
: undefined;
|
||||
const explorerName = chainId === CHAIN_ID_ETH ? "Etherscan" : "Explorer";
|
||||
|
||||
const copyToClipboard = useCopyToClipboard(useableAddress);
|
||||
|
||||
const explorerButton = !explorerAddress ? null : (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
endIcon={<OpenInNew />}
|
||||
className={classes.buttons}
|
||||
href={explorerAddress}
|
||||
target="_blank"
|
||||
>
|
||||
{"View on " + explorerName}
|
||||
</Button>
|
||||
);
|
||||
//TODO add icon here
|
||||
const copyButton = isNative ? null : (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
endIcon={<FileCopy />}
|
||||
onClick={copyToClipboard}
|
||||
className={classes.buttons}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
);
|
||||
|
||||
const tooltipContent = (
|
||||
<>
|
||||
{useableName && <Typography>{useableName}</Typography>}
|
||||
{useableSymbol && !isNative && (
|
||||
<Typography noWrap variant="body2">
|
||||
{addressShort}
|
||||
</Typography>
|
||||
)}
|
||||
<div>
|
||||
{explorerButton}
|
||||
{copyButton}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTooltip
|
||||
title={tooltipContent}
|
||||
interactive={true}
|
||||
className={classes.mainTypog}
|
||||
>
|
||||
<Typography
|
||||
variant={variant || "body1"}
|
||||
className={clsx(classes.mainTypog, {
|
||||
[classes.noGutter]: noGutter,
|
||||
[classes.noUnderline]: noUnderline,
|
||||
})}
|
||||
component="div"
|
||||
>
|
||||
{useableSymbol || addressShort}
|
||||
</Typography>
|
||||
</StyledTooltip>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { Button, makeStyles, Tooltip } from "@material-ui/core";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
button: {
|
||||
display: "block",
|
||||
margin: `${theme.spacing(1)}px auto`,
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
},
|
||||
}));
|
||||
|
||||
const ToggleConnectedButton = ({
|
||||
connect,
|
||||
disconnect,
|
||||
connected,
|
||||
pk,
|
||||
}: {
|
||||
connect(): any;
|
||||
disconnect(): any;
|
||||
connected: boolean;
|
||||
pk: string;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const is0x = pk.startsWith("0x");
|
||||
return connected ? (
|
||||
<Tooltip title={pk}>
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={disconnect}
|
||||
className={classes.button}
|
||||
>
|
||||
Disconnect {pk.substring(0, is0x ? 6 : 3)}...
|
||||
{pk.substr(pk.length - (is0x ? 4 : 3))}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={connect}
|
||||
className={classes.button}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleConnectedButton;
|
|
@ -0,0 +1,158 @@
|
|||
import detectEthereumProvider from "@metamask/detect-provider";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import React, {
|
||||
ReactChildren,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
export type Provider = ethers.providers.Web3Provider | undefined;
|
||||
export type Signer = ethers.Signer | undefined;
|
||||
|
||||
interface IEthereumProviderContext {
|
||||
connect(): void;
|
||||
disconnect(): void;
|
||||
provider: Provider;
|
||||
chainId: number | undefined;
|
||||
signer: Signer;
|
||||
signerAddress: string | undefined;
|
||||
providerError: string | null;
|
||||
}
|
||||
|
||||
const EthereumProviderContext = React.createContext<IEthereumProviderContext>({
|
||||
connect: () => {},
|
||||
disconnect: () => {},
|
||||
provider: undefined,
|
||||
chainId: undefined,
|
||||
signer: undefined,
|
||||
signerAddress: undefined,
|
||||
providerError: null,
|
||||
});
|
||||
export const EthereumProviderProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactChildren;
|
||||
}) => {
|
||||
const [providerError, setProviderError] = useState<string | null>(null);
|
||||
const [provider, setProvider] = useState<Provider>(undefined);
|
||||
const [chainId, setChainId] = useState<number | undefined>(undefined);
|
||||
const [signer, setSigner] = useState<Signer>(undefined);
|
||||
const [signerAddress, setSignerAddress] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const connect = useCallback(() => {
|
||||
setProviderError(null);
|
||||
detectEthereumProvider()
|
||||
.then((detectedProvider) => {
|
||||
if (detectedProvider) {
|
||||
const provider = new ethers.providers.Web3Provider(
|
||||
// @ts-ignore
|
||||
detectedProvider,
|
||||
"any"
|
||||
);
|
||||
provider
|
||||
.send("eth_requestAccounts", [])
|
||||
.then(() => {
|
||||
setProviderError(null);
|
||||
setProvider(provider);
|
||||
provider
|
||||
.getNetwork()
|
||||
.then((network) => {
|
||||
setChainId(network.chainId);
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while getting the network"
|
||||
);
|
||||
});
|
||||
const signer = provider.getSigner();
|
||||
setSigner(signer);
|
||||
signer
|
||||
.getAddress()
|
||||
.then((address) => {
|
||||
setSignerAddress(address);
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while getting the signer address"
|
||||
);
|
||||
});
|
||||
// TODO: try using ethers directly
|
||||
// @ts-ignore
|
||||
if (detectedProvider && detectedProvider.on) {
|
||||
// @ts-ignore
|
||||
detectedProvider.on("chainChanged", (chainId) => {
|
||||
try {
|
||||
setChainId(BigNumber.from(chainId).toNumber());
|
||||
} catch (e) {}
|
||||
});
|
||||
// @ts-ignore
|
||||
detectedProvider.on("accountsChanged", (accounts) => {
|
||||
try {
|
||||
const signer = provider.getSigner();
|
||||
setSigner(signer);
|
||||
signer
|
||||
.getAddress()
|
||||
.then((address) => {
|
||||
setSignerAddress(address);
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while getting the signer address"
|
||||
);
|
||||
});
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while requesting eth accounts"
|
||||
);
|
||||
});
|
||||
} else {
|
||||
setProviderError("Please install MetaMask");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError("Please install MetaMask");
|
||||
});
|
||||
}, []);
|
||||
const disconnect = useCallback(() => {
|
||||
setProviderError(null);
|
||||
setProvider(undefined);
|
||||
setChainId(undefined);
|
||||
setSigner(undefined);
|
||||
setSignerAddress(undefined);
|
||||
}, []);
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
connect,
|
||||
disconnect,
|
||||
provider,
|
||||
chainId,
|
||||
signer,
|
||||
signerAddress,
|
||||
providerError,
|
||||
}),
|
||||
[
|
||||
connect,
|
||||
disconnect,
|
||||
provider,
|
||||
chainId,
|
||||
signer,
|
||||
signerAddress,
|
||||
providerError,
|
||||
]
|
||||
);
|
||||
return (
|
||||
<EthereumProviderContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</EthereumProviderContext.Provider>
|
||||
);
|
||||
};
|
||||
export const useEthereumProvider = () => {
|
||||
return useContext(EthereumProviderContext);
|
||||
};
|
|
@ -20,7 +20,7 @@ const LoggerProviderContext = React.createContext<LoggerContext>({
|
|||
});
|
||||
|
||||
export const LoggerProvider = ({ children }: { children: ReactChildren }) => {
|
||||
const [logs, setLogs] = useState<string[]>([]);
|
||||
const [logs, setLogs] = useState<string[]>(["Instantiated the logger."]);
|
||||
const clear = useCallback(() => setLogs([]), [setLogs]);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { useSnackbar } from "notistack";
|
||||
import { useCallback } from "react";
|
||||
import pushToClipboard from "../utils/pushToClipboard";
|
||||
|
||||
export default function useCopyToClipboard(content: string) {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
return useCallback(() => {
|
||||
pushToClipboard(content)?.then(() => {
|
||||
enqueueSnackbar("Copied", { variant: "success" });
|
||||
});
|
||||
}, [content, enqueueSnackbar]);
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
import {
|
||||
Migrator,
|
||||
Migrator__factory,
|
||||
TokenImplementation,
|
||||
TokenImplementation__factory,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Signer } from "@ethersproject/abstract-signer";
|
||||
import { formatUnits } from "@ethersproject/units";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
export type EthMigrationInfo = {
|
||||
isLoading: boolean;
|
||||
error: string;
|
||||
data: RequisiteData | null;
|
||||
};
|
||||
|
||||
export type RequisiteData = {
|
||||
poolAddress: string;
|
||||
fromAddress: string;
|
||||
toAddress: string;
|
||||
fromToken: TokenImplementation;
|
||||
toToken: TokenImplementation;
|
||||
migrator: Migrator;
|
||||
fromSymbol: string;
|
||||
toSymbol: string;
|
||||
fromDecimals: number;
|
||||
toDecimals: number;
|
||||
sharesDecimals: number;
|
||||
fromWalletBalance: string;
|
||||
toWalletBalance: string;
|
||||
fromPoolBalance: string;
|
||||
toPoolBalance: string;
|
||||
walletSharesBalance: string;
|
||||
};
|
||||
|
||||
const getRequisiteData = async (
|
||||
migrator: Migrator,
|
||||
signer: Signer,
|
||||
signerAddress: string
|
||||
): Promise<RequisiteData> => {
|
||||
try {
|
||||
const poolAddress = migrator.address;
|
||||
const fromAddress = await migrator.fromAsset();
|
||||
const toAddress = await migrator.toAsset();
|
||||
|
||||
const fromToken = TokenImplementation__factory.connect(fromAddress, signer);
|
||||
const toToken = TokenImplementation__factory.connect(toAddress, signer);
|
||||
|
||||
const fromSymbol = await fromToken.symbol();
|
||||
const toSymbol = await toToken.symbol();
|
||||
|
||||
const fromDecimals = await (await migrator.fromDecimals()).toNumber();
|
||||
const toDecimals = await (await migrator.toDecimals()).toNumber();
|
||||
const sharesDecimals = await migrator.decimals();
|
||||
|
||||
const fromWalletBalance = formatUnits(
|
||||
await fromToken.balanceOf(signerAddress),
|
||||
fromDecimals
|
||||
);
|
||||
const toWalletBalance = formatUnits(
|
||||
await toToken.balanceOf(signerAddress),
|
||||
toDecimals
|
||||
);
|
||||
|
||||
const fromPoolBalance = formatUnits(
|
||||
await fromToken.balanceOf(poolAddress),
|
||||
fromDecimals
|
||||
);
|
||||
const toPoolBalance = formatUnits(
|
||||
await toToken.balanceOf(poolAddress),
|
||||
toDecimals
|
||||
);
|
||||
|
||||
const walletSharesBalance = formatUnits(
|
||||
await migrator.balanceOf(signerAddress),
|
||||
sharesDecimals
|
||||
);
|
||||
|
||||
return {
|
||||
poolAddress,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
fromToken,
|
||||
toToken,
|
||||
migrator,
|
||||
fromSymbol,
|
||||
toSymbol,
|
||||
fromDecimals,
|
||||
toDecimals,
|
||||
fromWalletBalance,
|
||||
toWalletBalance,
|
||||
fromPoolBalance,
|
||||
toPoolBalance,
|
||||
walletSharesBalance,
|
||||
sharesDecimals,
|
||||
};
|
||||
} catch (e) {
|
||||
return Promise.reject("Failed to retrieve required data.");
|
||||
}
|
||||
};
|
||||
|
||||
function useEthereumMigratorInformation(
|
||||
migratorAddress: string | undefined,
|
||||
signer: Signer | undefined,
|
||||
signerAddress: string | undefined,
|
||||
toggleRefresh: boolean
|
||||
): EthMigrationInfo {
|
||||
const migrator = useMemo(
|
||||
() =>
|
||||
migratorAddress &&
|
||||
signer &&
|
||||
Migrator__factory.connect(migratorAddress, signer),
|
||||
[migratorAddress, signer]
|
||||
);
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!signer || !migrator || !signerAddress) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setIsLoading(true);
|
||||
getRequisiteData(migrator, signer, signerAddress).then(
|
||||
(result) => {
|
||||
if (!cancelled) {
|
||||
setData(result);
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
if (!cancelled) {
|
||||
setIsLoading(false);
|
||||
setError("Failed to retrieve necessary data.");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
return;
|
||||
};
|
||||
}, [migrator, signer, signerAddress, toggleRefresh]);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!migratorAddress || !signer || !signerAddress) {
|
||||
return {
|
||||
isLoading: false,
|
||||
error:
|
||||
!signer || !signerAddress
|
||||
? "Wallet not connected"
|
||||
: !migratorAddress
|
||||
? "No contract address"
|
||||
: "Error",
|
||||
data: null,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
data,
|
||||
};
|
||||
}
|
||||
}, [isLoading, error, data, migratorAddress, signer, signerAddress]);
|
||||
}
|
||||
|
||||
export default useEthereumMigratorInformation;
|
|
@ -7,16 +7,19 @@ import { LoggerProvider } from "./contexts/Logger";
|
|||
import { SolanaWalletProvider } from "./contexts/SolanaWalletContext";
|
||||
import { theme } from "./muiTheme";
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import { EthereumProviderProvider } from "./contexts/EthereumProviderContext";
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SolanaWalletProvider>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<LoggerProvider>
|
||||
<App />
|
||||
</LoggerProvider>
|
||||
</SnackbarProvider>
|
||||
<EthereumProviderProvider>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<LoggerProvider>
|
||||
<App />
|
||||
</LoggerProvider>
|
||||
</SnackbarProvider>
|
||||
</EthereumProviderProvider>
|
||||
</SolanaWalletProvider>
|
||||
</ThemeProvider>
|
||||
</ErrorBoundary>,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export default function pushToClipboard(content: any) {
|
||||
if (!navigator.clipboard) {
|
||||
// Clipboard API not available
|
||||
return;
|
||||
}
|
||||
return navigator.clipboard.writeText(content);
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import { Migrator__factory } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
makeStyles,
|
||||
Paper,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { ethers } from "ethers";
|
||||
import { useState } from "react";
|
||||
import EthereumSignerKey from "../components/EthereumSignerKey";
|
||||
import LogWatcher from "../components/LogWatcher";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { useLogger } from "../contexts/Logger";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
rootContainer: {},
|
||||
mainPaper: {
|
||||
"& > *": {
|
||||
margin: "1rem",
|
||||
},
|
||||
padding: "2rem",
|
||||
},
|
||||
divider: {
|
||||
margin: "2rem",
|
||||
},
|
||||
spacer: {
|
||||
height: "1rem",
|
||||
},
|
||||
}));
|
||||
|
||||
function DeployNewEthereum() {
|
||||
const classes = useStyles();
|
||||
const { signer, provider } = useEthereumProvider();
|
||||
const { log } = useLogger();
|
||||
|
||||
const [migratorAddress, setMigratorAddress] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [fromAddress, setFromAddress] = useState<string | null>(null);
|
||||
const [toAddress, setToAddress] = useState<string | null>(null);
|
||||
|
||||
const errorMessage =
|
||||
error ||
|
||||
(!provider && "Wallet not connected") ||
|
||||
(!fromAddress && "No 'from' address") ||
|
||||
(!toAddress && "No 'to' address");
|
||||
|
||||
const deployPool = async () => {
|
||||
if (fromAddress && toAddress) {
|
||||
const contractInterface = Migrator__factory.createInterface();
|
||||
const bytecode = Migrator__factory.bytecode;
|
||||
const factory = new ethers.ContractFactory(
|
||||
contractInterface,
|
||||
bytecode,
|
||||
signer
|
||||
);
|
||||
const contract = await factory.deploy(fromAddress, toAddress);
|
||||
contract.deployed().then(
|
||||
(result) => {
|
||||
log("Successfully deployed contract at " + result.address);
|
||||
setMigratorAddress(result.address);
|
||||
},
|
||||
(error) => {
|
||||
log("Failed to deploy the contract");
|
||||
setError((error && error.toString()) || "Unable to create the pool.");
|
||||
}
|
||||
);
|
||||
} else {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container maxWidth="md" className={classes.rootContainer}>
|
||||
<Paper className={classes.mainPaper}>
|
||||
<Typography variant="h6">
|
||||
Create a new Ethereum Liquidity Pool
|
||||
</Typography>
|
||||
<EthereumSignerKey />
|
||||
<TextField
|
||||
value={fromAddress}
|
||||
onChange={(event) => setFromAddress(event.target.value)}
|
||||
label={"From Token"}
|
||||
fullWidth
|
||||
style={{ display: "block" }}
|
||||
/>
|
||||
<TextField
|
||||
value={toAddress}
|
||||
onChange={(event) => setToAddress(event.target.value)}
|
||||
label={"To Token"}
|
||||
fullWidth
|
||||
style={{ display: "block" }}
|
||||
/>
|
||||
<Button disabled={!!errorMessage} onClick={deployPool}>
|
||||
Create
|
||||
</Button>
|
||||
{errorMessage && <Typography>{errorMessage}</Typography>}
|
||||
{migratorAddress !== null && (
|
||||
<>
|
||||
<Typography>Successfully created a new pool at:</Typography>
|
||||
<Typography variant="h5">{migratorAddress}</Typography>
|
||||
<Typography>
|
||||
You may now populate the pool from the Ethereum pool management
|
||||
page.
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
<LogWatcher />
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DeployNewEthereum;
|
|
@ -0,0 +1,104 @@
|
|||
import { AppBar, Button, Divider, Typography } from "@material-ui/core";
|
||||
import { useCallback, useState } from "react";
|
||||
import { default as DeployNewEthereum } from "./DeployNewEthereum";
|
||||
import MigrateEthereum from "./MigrateEthereum";
|
||||
import Main from "./Main";
|
||||
import { CLUSTER } from "../utils/consts";
|
||||
|
||||
const ETH = "Interact with an existing Ethereum pool";
|
||||
const NEW_ETH = "Create a New Ethereum Pool";
|
||||
const SOL = "Manage Solana Liquidity pools.";
|
||||
|
||||
function Home() {
|
||||
const [displayedView, setDisplayedView] = useState<string | null>(null);
|
||||
|
||||
const setEth = useCallback(() => {
|
||||
setDisplayedView(ETH);
|
||||
}, []);
|
||||
|
||||
const setNewEth = useCallback(() => {
|
||||
setDisplayedView(NEW_ETH);
|
||||
}, []);
|
||||
|
||||
const setSol = useCallback(() => {
|
||||
setDisplayedView(SOL);
|
||||
}, []);
|
||||
|
||||
const clear = useCallback(() => {
|
||||
setDisplayedView(null);
|
||||
}, []);
|
||||
|
||||
const backHeader = (
|
||||
<>
|
||||
<div style={{ padding: ".5rem", textAlign: "center" }}>
|
||||
<Typography variant="h5">{displayedView}</Typography>
|
||||
<Button onClick={clear} variant="contained" color="default">
|
||||
Back
|
||||
</Button>
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
);
|
||||
|
||||
const content =
|
||||
displayedView === null ? (
|
||||
<div style={{ textAlign: "center", padding: "1rem" }}>
|
||||
<Typography variant="h5">
|
||||
Which action would you like to perform?
|
||||
</Typography>
|
||||
<div style={{ margin: "2rem" }}>
|
||||
<Button
|
||||
style={{ margin: ".5rem" }}
|
||||
variant="contained"
|
||||
onClick={setEth}
|
||||
>
|
||||
{ETH}
|
||||
</Button>
|
||||
<Button
|
||||
style={{ margin: ".5rem" }}
|
||||
variant="contained"
|
||||
onClick={setNewEth}
|
||||
>
|
||||
{NEW_ETH}
|
||||
</Button>
|
||||
<Button
|
||||
style={{ margin: ".5rem" }}
|
||||
variant="contained"
|
||||
onClick={setSol}
|
||||
>
|
||||
{SOL}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : displayedView === ETH ? (
|
||||
<>
|
||||
{backHeader}
|
||||
<MigrateEthereum />
|
||||
</>
|
||||
) : displayedView === NEW_ETH ? (
|
||||
<>
|
||||
{backHeader}
|
||||
<DeployNewEthereum />
|
||||
</>
|
||||
) : displayedView === SOL ? (
|
||||
<>
|
||||
{backHeader}
|
||||
<Main />
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{CLUSTER === "mainnet" ? null : (
|
||||
<AppBar position="static" color="secondary">
|
||||
<Typography style={{ textAlign: "center" }}>
|
||||
Caution! You are using the {CLUSTER} build of this app.
|
||||
</Typography>
|
||||
</AppBar>
|
||||
)}
|
||||
{content}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
|
@ -1,24 +1,23 @@
|
|||
import addLiquidityTx from "@certusone/wormhole-sdk/lib/migration/addLiquidity";
|
||||
import getAuthorityAddress from "@certusone/wormhole-sdk/lib/migration/authorityAddress";
|
||||
import claimSharesTx from "@certusone/wormhole-sdk/lib/migration/claimShares";
|
||||
import removeLiquidityTx from "@certusone/wormhole-sdk/lib/migration/removeLiquidity";
|
||||
import createPoolAccount from "@certusone/wormhole-sdk/lib/migration/createPool";
|
||||
import getFromCustodyAddress from "@certusone/wormhole-sdk/lib/migration/fromCustodyAddress";
|
||||
import migrateTokensTx from "@certusone/wormhole-sdk/lib/migration/migrateTokens";
|
||||
import parsePool from "@certusone/wormhole-sdk/lib/migration/parsePool";
|
||||
import getPoolAddress from "@certusone/wormhole-sdk/lib/migration/poolAddress";
|
||||
import removeLiquidityTx from "@certusone/wormhole-sdk/lib/migration/removeLiquidity";
|
||||
import getShareMintAddress from "@certusone/wormhole-sdk/lib/migration/shareMintAddress";
|
||||
import getToCustodyAddress from "@certusone/wormhole-sdk/lib/migration/toCustodyAddress";
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Container,
|
||||
Divider,
|
||||
makeStyles,
|
||||
Paper,
|
||||
TextField,
|
||||
Typography,
|
||||
CircularProgress,
|
||||
AppBar,
|
||||
} from "@material-ui/core";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
|
@ -36,11 +35,7 @@ import SolanaCreateAssociatedAddress, {
|
|||
import SolanaWalletKey from "../components/SolanaWalletKey";
|
||||
import { useLogger } from "../contexts/Logger";
|
||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
import {
|
||||
CLUSTER,
|
||||
MIGRATION_PROGRAM_ADDRESS,
|
||||
SOLANA_URL,
|
||||
} from "../utils/consts";
|
||||
import { MIGRATION_PROGRAM_ADDRESS, SOLANA_URL } from "../utils/consts";
|
||||
import { getMultipleAccounts, signSendAndConfirm } from "../utils/solana";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
|
@ -59,7 +54,7 @@ const useStyles = makeStyles(() => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const compareWithDecimalOffset = (
|
||||
export const compareWithDecimalOffset = (
|
||||
valueA: string,
|
||||
decimalsA: number,
|
||||
valueB: string,
|
||||
|
@ -1002,13 +997,6 @@ function Main() {
|
|||
|
||||
return (
|
||||
<>
|
||||
{CLUSTER === "mainnet" ? null : (
|
||||
<AppBar position="static" color="secondary">
|
||||
<Typography style={{ textAlign: "center" }}>
|
||||
Caution! You are using the {CLUSTER} build of this app.
|
||||
</Typography>
|
||||
</AppBar>
|
||||
)}
|
||||
<Container maxWidth="md" className={classes.rootContainer}>
|
||||
<Paper className={classes.mainPaper}>
|
||||
<SolanaWalletKey />
|
||||
|
|
|
@ -0,0 +1,431 @@
|
|||
import { CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Container,
|
||||
Divider,
|
||||
makeStyles,
|
||||
Paper,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
//import { pool_address } from "@certusone/wormhole-sdk/lib/solana/migration/wormhole_migration";
|
||||
import { parseUnits } from "ethers/lib/utils";
|
||||
import { useCallback, useState } from "react";
|
||||
import EthereumSignerKey from "../components/EthereumSignerKey";
|
||||
import LogWatcher from "../components/LogWatcher";
|
||||
import SmartAddress from "../components/SmartAddress";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { useLogger } from "../contexts/Logger";
|
||||
import useEthereumMigratorInformation from "../hooks/useEthereumMigratorInformation";
|
||||
import { compareWithDecimalOffset } from "./Main";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
rootContainer: {},
|
||||
mainPaper: {
|
||||
"& > *": {
|
||||
margin: "1rem",
|
||||
},
|
||||
padding: "2rem",
|
||||
},
|
||||
divider: {
|
||||
margin: "2rem",
|
||||
},
|
||||
spacer: {
|
||||
height: "1rem",
|
||||
},
|
||||
}));
|
||||
|
||||
function MigrateEthereum() {
|
||||
const classes = useStyles();
|
||||
const { signer, signerAddress } = useEthereumProvider();
|
||||
const { log } = useLogger();
|
||||
|
||||
const [migratorAddress, setMigratorAddress] = useState("");
|
||||
const [refresher, setRefresher] = useState(false);
|
||||
const forceRefresh = useCallback(() => {
|
||||
setRefresher((prevState) => !prevState);
|
||||
}, []);
|
||||
const poolInfo = useEthereumMigratorInformation(
|
||||
migratorAddress,
|
||||
signer,
|
||||
signerAddress,
|
||||
refresher
|
||||
);
|
||||
const info = poolInfo.data;
|
||||
|
||||
const [liquidityAmount, setLiquidityAmount] = useState("");
|
||||
const [removeLiquidityAmount, setRemoveLiquidityAmount] = useState("");
|
||||
const [migrationAmount, setMigrationAmount] = useState("");
|
||||
const [redeemAmount, setRedeemAmount] = useState("");
|
||||
|
||||
const [liquidityIsProcessing, setLiquidityIsProcessing] = useState(false);
|
||||
const [removeLiquidityIsProcessing, setRemoveLiquidityIsProcessing] =
|
||||
useState(false);
|
||||
const [migrationIsProcessing, setMigrationIsProcessing] = useState(false);
|
||||
const [redeemIsProcessing, setRedeemIsProcessing] = useState(false);
|
||||
|
||||
const addLiquidity = useCallback(async () => {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setLiquidityIsProcessing(true);
|
||||
await info.toToken.approve(
|
||||
info.migrator.address,
|
||||
parseUnits(liquidityAmount, info.toDecimals)
|
||||
);
|
||||
const transaction = await info.migrator.add(
|
||||
parseUnits(liquidityAmount, info.toDecimals)
|
||||
);
|
||||
await transaction.wait();
|
||||
forceRefresh();
|
||||
log(`Successfully added liquidity to the pool.`, "success");
|
||||
setLiquidityIsProcessing(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
log(`Could not add liquidity to the pool.`, "error");
|
||||
setLiquidityIsProcessing(false);
|
||||
}
|
||||
}, [info, liquidityAmount, log, forceRefresh]);
|
||||
|
||||
const removeLiquidity = useCallback(async () => {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setRemoveLiquidityIsProcessing(true);
|
||||
const transaction = await info.migrator.remove(
|
||||
parseUnits(removeLiquidityAmount, info.sharesDecimals)
|
||||
);
|
||||
await transaction.wait();
|
||||
forceRefresh();
|
||||
log(`Successfully removed liquidity from the pool.`, "success");
|
||||
setRemoveLiquidityIsProcessing(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
log(`Could not remove liquidity from the pool.`, "error");
|
||||
setRemoveLiquidityIsProcessing(false);
|
||||
}
|
||||
}, [info, removeLiquidityAmount, log, forceRefresh]);
|
||||
|
||||
const migrateTokens = useCallback(async () => {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setMigrationIsProcessing(true);
|
||||
await info.fromToken.approve(
|
||||
info.migrator.address,
|
||||
parseUnits(migrationAmount, info.fromDecimals)
|
||||
);
|
||||
const transaction = await info.migrator.migrate(
|
||||
parseUnits(migrationAmount, info.fromDecimals)
|
||||
);
|
||||
await transaction.wait();
|
||||
forceRefresh();
|
||||
log(`Successfully migrated tokens.`, "success");
|
||||
setMigrationIsProcessing(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
log(`Could not migrate the tokens.`, "error");
|
||||
setMigrationIsProcessing(false);
|
||||
}
|
||||
}, [info, migrationAmount, log, forceRefresh]);
|
||||
|
||||
const redeemShares = useCallback(async () => {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setRedeemIsProcessing(true);
|
||||
const transaction = await info.migrator.claim(
|
||||
parseUnits(redeemAmount, info.sharesDecimals)
|
||||
);
|
||||
await transaction.wait();
|
||||
forceRefresh();
|
||||
log(`Successfully redeemed shares.`, "success");
|
||||
setRedeemIsProcessing(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
log(`Could not redeem shares.`, "error");
|
||||
setRedeemIsProcessing(false);
|
||||
}
|
||||
}, [info, redeemAmount, log, forceRefresh]);
|
||||
|
||||
const addToTokensInWallet =
|
||||
info &&
|
||||
liquidityAmount &&
|
||||
compareWithDecimalOffset(
|
||||
liquidityAmount,
|
||||
info.toDecimals,
|
||||
info.toWalletBalance,
|
||||
info.toDecimals
|
||||
) !== 1;
|
||||
const addLiquidityIsReady = addToTokensInWallet;
|
||||
const addLiquidityUI = (
|
||||
<>
|
||||
<Typography variant="h4">Add Liquidity</Typography>
|
||||
<Typography variant="body1">
|
||||
This will remove 'To' tokens from your wallet, and give you an equal
|
||||
number of 'Share' tokens.
|
||||
</Typography>
|
||||
<TextField
|
||||
value={liquidityAmount}
|
||||
type="number"
|
||||
onChange={(event) => setLiquidityAmount(event.target.value)}
|
||||
label={"Amount to add"}
|
||||
></TextField>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={addLiquidity}
|
||||
disabled={liquidityIsProcessing || !addLiquidityIsReady}
|
||||
>
|
||||
Add Liquidity
|
||||
</Button>
|
||||
{liquidityIsProcessing ? <CircularProgress /> : null}
|
||||
</>
|
||||
);
|
||||
|
||||
const removeToTokensInPool =
|
||||
info &&
|
||||
removeLiquidityAmount &&
|
||||
compareWithDecimalOffset(
|
||||
removeLiquidityAmount,
|
||||
info.sharesDecimals,
|
||||
info.toPoolBalance,
|
||||
info.toDecimals
|
||||
) !== 1;
|
||||
const removeShareTokensInWallet =
|
||||
info &&
|
||||
removeLiquidityAmount &&
|
||||
compareWithDecimalOffset(
|
||||
removeLiquidityAmount,
|
||||
info.sharesDecimals,
|
||||
info.walletSharesBalance,
|
||||
info.sharesDecimals
|
||||
) !== 1;
|
||||
const removeLiquidityIsReady =
|
||||
removeShareTokensInWallet && removeToTokensInPool;
|
||||
const removeLiquidityUI = (
|
||||
<>
|
||||
<Typography variant="h4">Remove Liquidity</Typography>
|
||||
<Typography variant="body1">
|
||||
This will remove 'Share' tokens from your wallet, and give you an equal
|
||||
number of 'To' tokens.
|
||||
</Typography>
|
||||
<TextField
|
||||
value={removeLiquidityAmount}
|
||||
type="number"
|
||||
onChange={(event) => setRemoveLiquidityAmount(event.target.value)}
|
||||
label={"Amount to remove"}
|
||||
></TextField>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={removeLiquidity}
|
||||
disabled={removeLiquidityIsProcessing || !removeLiquidityIsReady}
|
||||
>
|
||||
Remove Liquidity
|
||||
</Button>
|
||||
{removeLiquidityIsProcessing ? <CircularProgress /> : null}
|
||||
</>
|
||||
);
|
||||
|
||||
const migrateToTokensInPool =
|
||||
info &&
|
||||
migrationAmount &&
|
||||
compareWithDecimalOffset(
|
||||
migrationAmount,
|
||||
info.fromDecimals,
|
||||
info.toPoolBalance,
|
||||
info.toDecimals
|
||||
) !== 1;
|
||||
const migrateFromTokensInWallet =
|
||||
info &&
|
||||
migrationAmount &&
|
||||
compareWithDecimalOffset(
|
||||
migrationAmount,
|
||||
info.fromDecimals,
|
||||
info.fromWalletBalance,
|
||||
info.fromDecimals
|
||||
) !== 1;
|
||||
const migrateIsReady = migrateFromTokensInWallet && migrateToTokensInPool;
|
||||
const migrateTokensUI = (
|
||||
<>
|
||||
<Typography variant="h4">Migrate Tokens</Typography>
|
||||
<Typography variant="body1">
|
||||
This will remove 'From' tokens from your wallet, and give you an equal
|
||||
number of 'To' tokens.
|
||||
</Typography>
|
||||
<TextField
|
||||
value={migrationAmount}
|
||||
type="number"
|
||||
onChange={(event) => setMigrationAmount(event.target.value)}
|
||||
label={"Amount to migrate"}
|
||||
></TextField>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={migrateTokens}
|
||||
disabled={migrationIsProcessing || !migrateIsReady}
|
||||
>
|
||||
Migrate Tokens
|
||||
</Button>
|
||||
{migrationIsProcessing ? <CircularProgress /> : null}
|
||||
</>
|
||||
);
|
||||
|
||||
const redeemSharesInWallet =
|
||||
info &&
|
||||
redeemAmount &&
|
||||
compareWithDecimalOffset(
|
||||
redeemAmount,
|
||||
info.sharesDecimals,
|
||||
info.walletSharesBalance,
|
||||
info.sharesDecimals
|
||||
) !== 1;
|
||||
const redeemFromTokensInPool =
|
||||
info &&
|
||||
redeemAmount &&
|
||||
compareWithDecimalOffset(
|
||||
redeemAmount,
|
||||
info.sharesDecimals,
|
||||
info.fromPoolBalance,
|
||||
info.fromDecimals
|
||||
) !== 1;
|
||||
const redeemIsReady = redeemSharesInWallet && redeemFromTokensInPool;
|
||||
const redeemSharesUI = (
|
||||
<>
|
||||
<Typography variant="h4">Redeem Shares</Typography>
|
||||
<Typography variant="body1">
|
||||
This will remove 'Share' tokens from your wallet, and give you an equal
|
||||
number of 'From' tokens.
|
||||
</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
value={redeemAmount}
|
||||
onChange={(event) => setRedeemAmount(event.target.value)}
|
||||
label={"Amount to redeem"}
|
||||
></TextField>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={redeemShares}
|
||||
disabled={redeemIsProcessing || !redeemIsReady}
|
||||
>
|
||||
Redeem Shares
|
||||
</Button>
|
||||
{redeemIsProcessing ? <CircularProgress /> : null}
|
||||
</>
|
||||
);
|
||||
|
||||
const topContent = (
|
||||
<>
|
||||
<Typography variant="h6">Manage an Ethereum Pool</Typography>
|
||||
<EthereumSignerKey />
|
||||
<TextField
|
||||
value={migratorAddress}
|
||||
onChange={(event) => setMigratorAddress(event.target.value)}
|
||||
label={"Migrator Address"}
|
||||
fullWidth
|
||||
style={{ display: "block" }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
const infoDisplay = poolInfo.isLoading ? (
|
||||
<CircularProgress />
|
||||
) : poolInfo.error ? (
|
||||
<Typography>{poolInfo.error}</Typography>
|
||||
) : !poolInfo.data ? null : (
|
||||
<>
|
||||
<div style={{ display: "flex" }}>
|
||||
<div>
|
||||
<Typography variant="h5">Pool Balances</Typography>
|
||||
<Typography>
|
||||
{`'From' Asset: `}
|
||||
{info?.fromPoolBalance}
|
||||
<SmartAddress
|
||||
chainId={CHAIN_ID_ETH}
|
||||
address={info?.fromAddress}
|
||||
symbol={info?.fromSymbol}
|
||||
/>
|
||||
</Typography>
|
||||
<Typography>
|
||||
{`'To' Asset: `}
|
||||
{info?.toPoolBalance}
|
||||
<SmartAddress
|
||||
chainId={CHAIN_ID_ETH}
|
||||
address={info?.toAddress}
|
||||
symbol={info?.toSymbol}
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
<div>
|
||||
<Typography variant="h5">Connected Wallet Balances</Typography>
|
||||
<Typography>
|
||||
{`'From' Asset: `}
|
||||
{info?.fromWalletBalance}
|
||||
<SmartAddress
|
||||
chainId={CHAIN_ID_ETH}
|
||||
address={info?.fromAddress}
|
||||
symbol={info?.fromSymbol}
|
||||
/>
|
||||
</Typography>
|
||||
<Typography>
|
||||
{`'To' Asset: `}
|
||||
{info?.toWalletBalance}
|
||||
<SmartAddress
|
||||
chainId={CHAIN_ID_ETH}
|
||||
address={info?.toAddress}
|
||||
symbol={info?.toSymbol}
|
||||
/>
|
||||
</Typography>
|
||||
<Typography>
|
||||
{`'Shares' Asset: `}
|
||||
{info?.walletSharesBalance}
|
||||
<SmartAddress chainId={CHAIN_ID_ETH} address={info?.poolAddress} />
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={forceRefresh} variant="contained" color="primary">
|
||||
Force Refresh
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
||||
const actionPanel = poolInfo.data ? (
|
||||
<>
|
||||
{addLiquidityUI}
|
||||
<Divider className={classes.divider} />
|
||||
{removeLiquidityUI}
|
||||
<Divider className={classes.divider} />
|
||||
{redeemSharesUI}
|
||||
<Divider className={classes.divider} />
|
||||
{migrateTokensUI}
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container maxWidth="md" className={classes.rootContainer}>
|
||||
<Paper className={classes.mainPaper}>
|
||||
{topContent}
|
||||
{infoDisplay && (
|
||||
<>
|
||||
<Divider className={classes.divider} />
|
||||
{infoDisplay}
|
||||
</>
|
||||
)}
|
||||
{actionPanel && (
|
||||
<>
|
||||
<Divider className={classes.divider} />
|
||||
{actionPanel}
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
<LogWatcher />
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MigrateEthereum;
|
Loading…
Reference in New Issue