2021-07-30 11:13:53 -07:00
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
Grid,
|
|
|
|
makeStyles,
|
|
|
|
MenuItem,
|
|
|
|
TextField,
|
|
|
|
Typography,
|
|
|
|
} from "@material-ui/core";
|
|
|
|
import { useCallback, useState } from "react";
|
|
|
|
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
|
|
|
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
|
|
|
import useEthereumBalance from "../hooks/useEthereumBalance";
|
2021-08-01 20:21:28 -07:00
|
|
|
import useSolanaBalance from "../hooks/useSolanaBalance";
|
2021-07-30 11:13:53 -07:00
|
|
|
import {
|
|
|
|
ChainId,
|
|
|
|
CHAINS,
|
2021-08-01 20:21:28 -07:00
|
|
|
CHAINS_BY_ID,
|
2021-07-30 11:13:53 -07:00
|
|
|
CHAIN_ID_ETH,
|
|
|
|
CHAIN_ID_SOLANA,
|
|
|
|
ETH_TEST_TOKEN_ADDRESS,
|
2021-08-01 20:21:28 -07:00
|
|
|
SOL_TEST_TOKEN_ADDRESS,
|
2021-07-30 11:13:53 -07:00
|
|
|
} from "../utils/consts";
|
2021-08-01 20:21:28 -07:00
|
|
|
import transferFrom, {
|
|
|
|
transferFromEth,
|
|
|
|
transferFromSolana,
|
|
|
|
} from "../utils/transferFrom";
|
|
|
|
import KeyAndBalance from "./KeyAndBalance";
|
2021-07-30 11:13:53 -07:00
|
|
|
|
|
|
|
const useStyles = makeStyles((theme) => ({
|
|
|
|
transferBox: {
|
|
|
|
width: 540,
|
|
|
|
margin: "auto",
|
|
|
|
border: `.5px solid ${theme.palette.divider}`,
|
|
|
|
padding: theme.spacing(5.5, 12),
|
|
|
|
},
|
|
|
|
arrow: {
|
|
|
|
display: "flex",
|
|
|
|
alignItems: "center",
|
|
|
|
justifyContent: "center",
|
|
|
|
},
|
|
|
|
transferField: {
|
|
|
|
marginTop: theme.spacing(5),
|
|
|
|
},
|
|
|
|
transferButton: {
|
|
|
|
marginTop: theme.spacing(7.5),
|
|
|
|
textTransform: "none",
|
|
|
|
width: "100%",
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
|
|
|
// TODO: loaders and such, navigation block?
|
|
|
|
// TODO: refresh displayed token amount after transfer somehow, could be resolved by having different components appear
|
|
|
|
// TODO: warn if amount exceeds balance
|
|
|
|
|
|
|
|
function Transfer() {
|
|
|
|
const classes = useStyles();
|
|
|
|
//TODO: don't attempt to connect to any wallets until the user clicks a connect button
|
|
|
|
const [fromChain, setFromChain] = useState<ChainId>(CHAIN_ID_ETH);
|
|
|
|
const [toChain, setToChain] = useState<ChainId>(CHAIN_ID_SOLANA);
|
|
|
|
const [assetAddress, setAssetAddress] = useState(ETH_TEST_TOKEN_ADDRESS);
|
|
|
|
const [amount, setAmount] = useState("");
|
|
|
|
const handleFromChange = useCallback(
|
|
|
|
(event) => {
|
|
|
|
setFromChain(event.target.value);
|
2021-08-01 20:21:28 -07:00
|
|
|
// TODO: remove or check env - for testing purposes
|
|
|
|
if (event.target.value === CHAIN_ID_ETH) {
|
|
|
|
setAssetAddress(ETH_TEST_TOKEN_ADDRESS);
|
|
|
|
}
|
|
|
|
if (event.target.value === CHAIN_ID_SOLANA) {
|
|
|
|
setAssetAddress(SOL_TEST_TOKEN_ADDRESS);
|
|
|
|
}
|
2021-07-30 11:13:53 -07:00
|
|
|
if (toChain === event.target.value) {
|
|
|
|
setToChain(fromChain);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[fromChain, toChain]
|
|
|
|
);
|
|
|
|
const handleToChange = useCallback(
|
|
|
|
(event) => {
|
|
|
|
setToChain(event.target.value);
|
|
|
|
if (fromChain === event.target.value) {
|
|
|
|
setFromChain(toChain);
|
2021-08-01 20:21:28 -07:00
|
|
|
// TODO: remove or check env - for testing purposes
|
|
|
|
if (toChain === CHAIN_ID_ETH) {
|
|
|
|
setAssetAddress(ETH_TEST_TOKEN_ADDRESS);
|
|
|
|
}
|
|
|
|
if (toChain === CHAIN_ID_SOLANA) {
|
|
|
|
setAssetAddress(SOL_TEST_TOKEN_ADDRESS);
|
|
|
|
}
|
2021-07-30 11:13:53 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
[fromChain, toChain]
|
|
|
|
);
|
|
|
|
const handleAssetChange = useCallback((event) => {
|
|
|
|
setAssetAddress(event.target.value);
|
|
|
|
}, []);
|
|
|
|
const handleAmountChange = useCallback((event) => {
|
|
|
|
setAmount(event.target.value);
|
|
|
|
}, []);
|
|
|
|
const provider = useEthereumProvider();
|
|
|
|
const { wallet } = useSolanaWallet();
|
2021-08-01 20:21:28 -07:00
|
|
|
const solPK = wallet?.publicKey;
|
2021-08-02 12:23:37 -07:00
|
|
|
const {
|
|
|
|
tokenAccount: solTokenPK,
|
|
|
|
decimals: solDecimals,
|
|
|
|
uiAmount: solBalance,
|
|
|
|
} = useSolanaBalance(assetAddress, solPK, fromChain === CHAIN_ID_SOLANA);
|
2021-07-30 11:13:53 -07:00
|
|
|
// TODO: dynamically get "to" wallet
|
|
|
|
const handleClick = useCallback(() => {
|
2021-08-01 20:21:28 -07:00
|
|
|
// TODO: more generic way of calling these
|
2021-07-30 11:13:53 -07:00
|
|
|
if (transferFrom[fromChain]) {
|
2021-08-01 20:21:28 -07:00
|
|
|
if (
|
|
|
|
fromChain === CHAIN_ID_ETH &&
|
|
|
|
transferFrom[fromChain] === transferFromEth
|
|
|
|
) {
|
|
|
|
transferFromEth(
|
|
|
|
provider,
|
|
|
|
assetAddress,
|
|
|
|
amount,
|
|
|
|
toChain,
|
|
|
|
solPK?.toBytes()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
fromChain === CHAIN_ID_SOLANA &&
|
|
|
|
transferFrom[fromChain] === transferFromSolana
|
|
|
|
) {
|
|
|
|
transferFromSolana(
|
2021-08-02 12:23:37 -07:00
|
|
|
wallet,
|
2021-08-01 20:21:28 -07:00
|
|
|
solPK?.toString(),
|
2021-08-02 12:23:37 -07:00
|
|
|
solTokenPK?.toString(),
|
2021-08-01 20:21:28 -07:00
|
|
|
assetAddress,
|
|
|
|
amount,
|
2021-08-02 12:23:37 -07:00
|
|
|
solDecimals,
|
2021-08-01 20:21:28 -07:00
|
|
|
provider,
|
|
|
|
toChain
|
|
|
|
);
|
|
|
|
}
|
2021-07-30 11:13:53 -07:00
|
|
|
}
|
2021-08-02 12:23:37 -07:00
|
|
|
}, [
|
|
|
|
fromChain,
|
|
|
|
provider,
|
|
|
|
wallet,
|
|
|
|
solPK,
|
|
|
|
solTokenPK,
|
|
|
|
assetAddress,
|
|
|
|
amount,
|
|
|
|
solDecimals,
|
|
|
|
toChain,
|
|
|
|
]);
|
2021-07-30 11:13:53 -07:00
|
|
|
// update this as we develop, just setting expectations with the button state
|
2021-08-01 20:21:28 -07:00
|
|
|
const ethBalance = useEthereumBalance(
|
|
|
|
assetAddress,
|
|
|
|
provider,
|
|
|
|
fromChain === CHAIN_ID_ETH
|
|
|
|
);
|
2021-08-02 12:23:37 -07:00
|
|
|
const balance = Number(ethBalance) || solBalance;
|
2021-07-30 11:13:53 -07:00
|
|
|
const isTransferImplemented = !!transferFrom[fromChain];
|
|
|
|
const isProviderConnected = !!provider;
|
|
|
|
const isRecipientAvailable = !!solPK;
|
|
|
|
const isAddressDefined = !!assetAddress;
|
|
|
|
const isAmountPositive = Number(amount) > 0; // TODO: this needs per-chain, bn parsing
|
2021-08-01 20:21:28 -07:00
|
|
|
const isBalanceAtLeastAmount = balance >= Number(amount); // TODO: ditto
|
2021-07-30 11:13:53 -07:00
|
|
|
const canAttemptTransfer =
|
|
|
|
isTransferImplemented &&
|
|
|
|
isProviderConnected &&
|
|
|
|
isRecipientAvailable &&
|
|
|
|
isAddressDefined &&
|
|
|
|
isAmountPositive &&
|
|
|
|
isBalanceAtLeastAmount;
|
|
|
|
return (
|
|
|
|
<div className={classes.transferBox}>
|
|
|
|
<Grid container>
|
|
|
|
<Grid item xs={4}>
|
2021-08-01 20:21:28 -07:00
|
|
|
<Typography>From</Typography>
|
2021-07-30 11:13:53 -07:00
|
|
|
<TextField
|
|
|
|
select
|
|
|
|
fullWidth
|
|
|
|
value={fromChain}
|
|
|
|
onChange={handleFromChange}
|
|
|
|
>
|
|
|
|
{CHAINS.map(({ id, name }) => (
|
|
|
|
<MenuItem key={id} value={id}>
|
|
|
|
{name}
|
|
|
|
</MenuItem>
|
|
|
|
))}
|
|
|
|
</TextField>
|
2021-08-01 20:21:28 -07:00
|
|
|
<KeyAndBalance chainId={fromChain} tokenAddress={assetAddress} />
|
2021-07-30 11:13:53 -07:00
|
|
|
</Grid>
|
|
|
|
<Grid item xs={4} className={classes.arrow}>
|
|
|
|
→
|
|
|
|
</Grid>
|
|
|
|
<Grid item xs={4}>
|
2021-08-01 20:21:28 -07:00
|
|
|
<Typography>To</Typography>
|
2021-07-30 11:13:53 -07:00
|
|
|
<TextField select fullWidth value={toChain} onChange={handleToChange}>
|
|
|
|
{CHAINS.map(({ id, name }) => (
|
|
|
|
<MenuItem key={id} value={id}>
|
|
|
|
{name}
|
|
|
|
</MenuItem>
|
|
|
|
))}
|
|
|
|
</TextField>
|
2021-08-01 20:21:28 -07:00
|
|
|
{/* TODO: determine "to" token address */}
|
|
|
|
<KeyAndBalance chainId={toChain} />
|
2021-07-30 11:13:53 -07:00
|
|
|
</Grid>
|
|
|
|
</Grid>
|
|
|
|
<TextField
|
|
|
|
placeholder="Asset"
|
|
|
|
fullWidth
|
|
|
|
className={classes.transferField}
|
|
|
|
value={assetAddress}
|
|
|
|
onChange={handleAssetChange}
|
|
|
|
/>
|
|
|
|
<TextField
|
|
|
|
placeholder="Amount"
|
|
|
|
type="number"
|
|
|
|
fullWidth
|
|
|
|
className={classes.transferField}
|
|
|
|
value={amount}
|
|
|
|
onChange={handleAmountChange}
|
|
|
|
/>
|
|
|
|
<Button
|
|
|
|
color="primary"
|
|
|
|
variant="contained"
|
|
|
|
className={classes.transferButton}
|
|
|
|
onClick={handleClick}
|
|
|
|
disabled={!canAttemptTransfer}
|
|
|
|
>
|
|
|
|
Transfer
|
|
|
|
</Button>
|
|
|
|
{canAttemptTransfer ? null : (
|
|
|
|
<Typography variant="body2" color="error">
|
|
|
|
{!isTransferImplemented
|
2021-08-01 20:21:28 -07:00
|
|
|
? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}`
|
2021-07-30 11:13:53 -07:00
|
|
|
: !isProviderConnected
|
|
|
|
? "The source wallet is not connected"
|
|
|
|
: !isRecipientAvailable
|
|
|
|
? "The receiving wallet is not connected"
|
|
|
|
: !isAddressDefined
|
|
|
|
? "Please provide an asset address"
|
|
|
|
: !isAmountPositive
|
|
|
|
? "The amount must be positive"
|
|
|
|
: !isBalanceAtLeastAmount
|
|
|
|
? "The amount may not be greater than the balance"
|
|
|
|
: ""}
|
|
|
|
</Typography>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default Transfer;
|