bridge_ui: separate attest workflow
Change-Id: If6270d7ce0deb02a48b63b81ba2ef688c5f4af29
This commit is contained in:
parent
f53d180753
commit
1da690aa42
|
@ -1,4 +1,5 @@
|
|||
import { AppBar, makeStyles, Toolbar } from "@material-ui/core";
|
||||
import Attest from "./components/Attest";
|
||||
import Transfer from "./components/Transfer";
|
||||
import wormholeLogo from "./icons/wormhole.svg";
|
||||
|
||||
|
@ -40,6 +41,7 @@ function App() {
|
|||
</Toolbar>
|
||||
</AppBar>
|
||||
<div className={classes.content}>
|
||||
<Attest />
|
||||
<Transfer />
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import useAttestSignedVAA from "../../hooks/useAttestSignedVAA";
|
||||
import { setIsCreating } from "../../store/attestSlice";
|
||||
import {
|
||||
selectAttestIsCreating,
|
||||
selectAttestTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { CHAIN_ID_SOLANA } from "../../utils/consts";
|
||||
import createWrappedOn, {
|
||||
createWrappedOnSolana,
|
||||
} from "../../utils/createWrappedOn";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferButton: {
|
||||
marginTop: theme.spacing(2),
|
||||
textTransform: "none",
|
||||
width: "100%",
|
||||
},
|
||||
}));
|
||||
|
||||
function Create() {
|
||||
const dispatch = useDispatch();
|
||||
const classes = useStyles();
|
||||
const targetChain = useSelector(selectAttestTargetChain);
|
||||
const { wallet } = useSolanaWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const signedVAA = useAttestSignedVAA();
|
||||
const isCreating = useSelector(selectAttestIsCreating);
|
||||
const handleCreateClick = useCallback(() => {
|
||||
if (
|
||||
targetChain === CHAIN_ID_SOLANA &&
|
||||
createWrappedOn[targetChain] === createWrappedOnSolana &&
|
||||
signedVAA
|
||||
) {
|
||||
dispatch(setIsCreating(true));
|
||||
createWrappedOnSolana(wallet, solPK?.toString(), signedVAA);
|
||||
}
|
||||
}, [dispatch, targetChain, wallet, solPK, signedVAA]);
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.transferButton}
|
||||
disabled={isCreating}
|
||||
onClick={handleCreateClick}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
{isCreating ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "50%",
|
||||
marginLeft: -12,
|
||||
marginBottom: 6,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Create;
|
|
@ -0,0 +1,127 @@
|
|||
import { Button, CircularProgress, makeStyles } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import useWrappedAsset from "../../hooks/useWrappedAsset";
|
||||
import { setIsSending, setSignedVAAHex } from "../../store/attestSlice";
|
||||
import {
|
||||
selectAttestIsSendComplete,
|
||||
selectAttestIsSending,
|
||||
selectAttestIsTargetComplete,
|
||||
selectAttestSourceAsset,
|
||||
selectAttestSourceChain,
|
||||
selectAttestTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { uint8ArrayToHex } from "../../utils/array";
|
||||
import attestFrom, {
|
||||
attestFromEth,
|
||||
attestFromSolana,
|
||||
} from "../../utils/attestFrom";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../../utils/consts";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferButton: {
|
||||
marginTop: theme.spacing(2),
|
||||
textTransform: "none",
|
||||
width: "100%",
|
||||
},
|
||||
}));
|
||||
|
||||
// TODO: move attest to its own workflow
|
||||
|
||||
function Send() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectAttestSourceChain);
|
||||
const sourceAsset = useSelector(selectAttestSourceAsset);
|
||||
const targetChain = useSelector(selectAttestTargetChain);
|
||||
const isTargetComplete = useSelector(selectAttestIsTargetComplete);
|
||||
const isSending = useSelector(selectAttestIsSending);
|
||||
const isSendComplete = useSelector(selectAttestIsSendComplete);
|
||||
const { provider, signer } = useEthereumProvider();
|
||||
const { wallet } = useSolanaWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const {
|
||||
isLoading: isCheckingWrapped,
|
||||
// isWrapped,
|
||||
wrappedAsset,
|
||||
} = useWrappedAsset(targetChain, sourceChain, sourceAsset, provider);
|
||||
// TODO: check this and send to separate flow
|
||||
const isWrapped = true;
|
||||
console.log(isCheckingWrapped, isWrapped, wrappedAsset);
|
||||
// TODO: dynamically get "to" wallet
|
||||
const handleAttestClick = useCallback(() => {
|
||||
// TODO: more generic way of calling these
|
||||
if (attestFrom[sourceChain]) {
|
||||
if (
|
||||
sourceChain === CHAIN_ID_ETH &&
|
||||
attestFrom[sourceChain] === attestFromEth
|
||||
) {
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const vaaBytes = await attestFromEth(provider, signer, sourceAsset);
|
||||
console.log("bytes in attest", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
})();
|
||||
}
|
||||
if (
|
||||
sourceChain === CHAIN_ID_SOLANA &&
|
||||
attestFrom[sourceChain] === attestFromSolana
|
||||
) {
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const vaaBytes = await attestFromSolana(
|
||||
wallet,
|
||||
solPK?.toString(),
|
||||
sourceAsset
|
||||
);
|
||||
console.log("bytes in attest", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
dispatch(setIsSending(false));
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
}, [dispatch, sourceChain, provider, signer, wallet, solPK, sourceAsset]);
|
||||
return (
|
||||
<>
|
||||
<div style={{ position: "relative" }}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.transferButton}
|
||||
onClick={handleAttestClick}
|
||||
disabled={!isTargetComplete || isSending || isSendComplete}
|
||||
>
|
||||
Attest
|
||||
</Button>
|
||||
{isSending ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "50%",
|
||||
marginLeft: -12,
|
||||
marginBottom: 6,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Send;
|
|
@ -0,0 +1,85 @@
|
|||
import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
selectAttestIsSourceComplete,
|
||||
selectAttestShouldLockFields,
|
||||
selectAttestSourceAsset,
|
||||
selectAttestSourceChain,
|
||||
} from "../../store/selectors";
|
||||
import {
|
||||
incrementStep,
|
||||
setSourceAsset,
|
||||
setSourceChain,
|
||||
} from "../../store/attestSlice";
|
||||
import { CHAINS } from "../../utils/consts";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
transferField: {
|
||||
marginTop: theme.spacing(5),
|
||||
},
|
||||
}));
|
||||
|
||||
function Source() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectAttestSourceChain);
|
||||
const sourceAsset = useSelector(selectAttestSourceAsset);
|
||||
const isSourceComplete = useSelector(selectAttestIsSourceComplete);
|
||||
const shouldLockFields = useSelector(selectAttestShouldLockFields);
|
||||
const handleSourceChange = useCallback(
|
||||
(event) => {
|
||||
dispatch(setSourceChain(event.target.value));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleAssetChange = useCallback(
|
||||
(event) => {
|
||||
dispatch(setSourceAsset(event.target.value));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleNextClick = useCallback(
|
||||
(event) => {
|
||||
dispatch(incrementStep());
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
value={sourceChain}
|
||||
onChange={handleSourceChange}
|
||||
disabled={shouldLockFields}
|
||||
>
|
||||
{CHAINS.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<KeyAndBalance chainId={sourceChain} />
|
||||
<TextField
|
||||
placeholder="Asset"
|
||||
fullWidth
|
||||
className={classes.transferField}
|
||||
value={sourceAsset}
|
||||
onChange={handleAssetChange}
|
||||
disabled={shouldLockFields}
|
||||
/>
|
||||
<Button
|
||||
disabled={!isSourceComplete}
|
||||
onClick={handleNextClick}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Source;
|
|
@ -0,0 +1,65 @@
|
|||
import { Button, MenuItem, TextField } from "@material-ui/core";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
selectAttestIsTargetComplete,
|
||||
selectAttestShouldLockFields,
|
||||
selectAttestSourceChain,
|
||||
selectAttestTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { incrementStep, setTargetChain } from "../../store/attestSlice";
|
||||
import { CHAINS } from "../../utils/consts";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
||||
function Target() {
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectAttestSourceChain);
|
||||
const chains = useMemo(
|
||||
() => CHAINS.filter((c) => c.id !== sourceChain),
|
||||
[sourceChain]
|
||||
);
|
||||
const targetChain = useSelector(selectAttestTargetChain);
|
||||
const isTargetComplete = useSelector(selectAttestIsTargetComplete);
|
||||
const shouldLockFields = useSelector(selectAttestShouldLockFields);
|
||||
const handleTargetChange = useCallback(
|
||||
(event) => {
|
||||
dispatch(setTargetChain(event.target.value));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleNextClick = useCallback(
|
||||
(event) => {
|
||||
dispatch(incrementStep());
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
value={targetChain}
|
||||
onChange={handleTargetChange}
|
||||
disabled={shouldLockFields}
|
||||
>
|
||||
{chains.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{/* TODO: determine "to" token address */}
|
||||
<KeyAndBalance chainId={targetChain} />
|
||||
<Button
|
||||
disabled={!isTargetComplete}
|
||||
onClick={handleNextClick}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Target;
|
|
@ -0,0 +1,70 @@
|
|||
import {
|
||||
Container,
|
||||
Step,
|
||||
StepButton,
|
||||
StepContent,
|
||||
Stepper,
|
||||
} from "@material-ui/core";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import useGetBalanceEffect from "../../hooks/useGetBalanceEffect";
|
||||
import {
|
||||
selectAttestActiveStep,
|
||||
selectAttestSignedVAAHex,
|
||||
} from "../../store/selectors";
|
||||
import { setStep } from "../../store/attestSlice";
|
||||
import Create from "./Create";
|
||||
import Send from "./Send";
|
||||
import Source from "./Source";
|
||||
import Target from "./Target";
|
||||
|
||||
// TODO: ensure that both wallets are connected to the same known network
|
||||
|
||||
function Attest() {
|
||||
useGetBalanceEffect();
|
||||
const dispatch = useDispatch();
|
||||
const activeStep = useSelector(selectAttestActiveStep);
|
||||
const signedVAAHex = useSelector(selectAttestSignedVAAHex);
|
||||
return (
|
||||
<Container maxWidth="md">
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
<Step>
|
||||
<StepButton onClick={() => dispatch(setStep(0))}>
|
||||
Select a source
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Source />
|
||||
</StepContent>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepButton onClick={() => dispatch(setStep(1))}>
|
||||
Select a target
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Target />
|
||||
</StepContent>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepButton onClick={() => dispatch(setStep(2))}>
|
||||
Send attestation
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Send />
|
||||
</StepContent>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepButton
|
||||
onClick={() => dispatch(setStep(3))}
|
||||
disabled={!signedVAAHex}
|
||||
>
|
||||
Create wrapper
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Create />
|
||||
</StepContent>
|
||||
</Step>
|
||||
</Stepper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default Attest;
|
|
@ -3,7 +3,10 @@ import { useCallback } from "react";
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import useTransferSignedVAA from "../../hooks/useTransferSignedVAA";
|
||||
import { selectIsRedeeming, selectTargetChain } from "../../store/selectors";
|
||||
import {
|
||||
selectTransferIsRedeeming,
|
||||
selectTransferTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { setIsRedeeming } from "../../store/transferSlice";
|
||||
import { CHAIN_ID_ETH } from "../../utils/consts";
|
||||
import redeemOn, { redeemOnEth } from "../../utils/redeemOn";
|
||||
|
@ -19,10 +22,10 @@ const useStyles = makeStyles((theme) => ({
|
|||
function Redeem() {
|
||||
const dispatch = useDispatch();
|
||||
const classes = useStyles();
|
||||
const targetChain = useSelector(selectTargetChain);
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const { provider, signer } = useEthereumProvider();
|
||||
const signedVAA = useTransferSignedVAA();
|
||||
const isRedeeming = useSelector(selectIsRedeeming);
|
||||
const isRedeeming = useSelector(selectTransferIsRedeeming);
|
||||
const handleRedeemClick = useCallback(() => {
|
||||
if (
|
||||
targetChain === CHAIN_ID_ETH &&
|
||||
|
|
|
@ -5,14 +5,14 @@ import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
|||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import useWrappedAsset from "../../hooks/useWrappedAsset";
|
||||
import {
|
||||
selectAmount,
|
||||
selectIsSendComplete,
|
||||
selectIsSending,
|
||||
selectIsTargetComplete,
|
||||
selectSourceAsset,
|
||||
selectSourceChain,
|
||||
selectSourceParsedTokenAccount,
|
||||
selectTargetChain,
|
||||
selectTransferAmount,
|
||||
selectTransferIsSendComplete,
|
||||
selectTransferIsSending,
|
||||
selectTransferIsTargetComplete,
|
||||
selectTransferSourceAsset,
|
||||
selectTransferSourceChain,
|
||||
selectTransferSourceParsedTokenAccount,
|
||||
selectTransferTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { setIsSending, setSignedVAAHex } from "../../store/transferSlice";
|
||||
import { uint8ArrayToHex } from "../../utils/array";
|
||||
|
@ -35,17 +35,19 @@ const useStyles = makeStyles((theme) => ({
|
|||
function Send() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectSourceChain);
|
||||
const sourceAsset = useSelector(selectSourceAsset);
|
||||
const amount = useSelector(selectAmount);
|
||||
const targetChain = useSelector(selectTargetChain);
|
||||
const isTargetComplete = useSelector(selectIsTargetComplete);
|
||||
const isSending = useSelector(selectIsSending);
|
||||
const isSendComplete = useSelector(selectIsSendComplete);
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||
const amount = useSelector(selectTransferAmount);
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const isTargetComplete = useSelector(selectTransferIsTargetComplete);
|
||||
const isSending = useSelector(selectTransferIsSending);
|
||||
const isSendComplete = useSelector(selectTransferIsSendComplete);
|
||||
const { provider, signer, signerAddress } = useEthereumProvider();
|
||||
const { wallet } = useSolanaWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const sourceParsedTokenAccount = useSelector(selectSourceParsedTokenAccount);
|
||||
const sourceParsedTokenAccount = useSelector(
|
||||
selectTransferSourceParsedTokenAccount
|
||||
);
|
||||
const tokenPK = sourceParsedTokenAccount?.publicKey;
|
||||
const decimals = sourceParsedTokenAccount?.decimals;
|
||||
const {
|
||||
|
|
|
@ -2,12 +2,12 @@ import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core";
|
|||
import { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
selectAmount,
|
||||
selectIsSourceComplete,
|
||||
selectShouldLockFields,
|
||||
selectSourceAsset,
|
||||
selectSourceBalanceString,
|
||||
selectSourceChain,
|
||||
selectTransferAmount,
|
||||
selectTransferIsSourceComplete,
|
||||
selectTransferShouldLockFields,
|
||||
selectTransferSourceAsset,
|
||||
selectTransferSourceBalanceString,
|
||||
selectTransferSourceChain,
|
||||
} from "../../store/selectors";
|
||||
import {
|
||||
incrementStep,
|
||||
|
@ -27,12 +27,12 @@ const useStyles = makeStyles((theme) => ({
|
|||
function Source() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectSourceChain);
|
||||
const sourceAsset = useSelector(selectSourceAsset);
|
||||
const uiAmountString = useSelector(selectSourceBalanceString);
|
||||
const amount = useSelector(selectAmount);
|
||||
const isSourceComplete = useSelector(selectIsSourceComplete);
|
||||
const shouldLockFields = useSelector(selectShouldLockFields);
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||
const uiAmountString = useSelector(selectTransferSourceBalanceString);
|
||||
const amount = useSelector(selectTransferAmount);
|
||||
const isSourceComplete = useSelector(selectTransferIsSourceComplete);
|
||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||
const handleSourceChange = useCallback(
|
||||
(event) => {
|
||||
dispatch(setSourceChain(event.target.value));
|
||||
|
|
|
@ -2,10 +2,10 @@ import { Button, MenuItem, TextField } from "@material-ui/core";
|
|||
import { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
selectIsTargetComplete,
|
||||
selectShouldLockFields,
|
||||
selectSourceChain,
|
||||
selectTargetChain,
|
||||
selectTransferIsTargetComplete,
|
||||
selectTransferShouldLockFields,
|
||||
selectTransferSourceChain,
|
||||
selectTransferTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import { incrementStep, setTargetChain } from "../../store/transferSlice";
|
||||
import { CHAINS } from "../../utils/consts";
|
||||
|
@ -13,14 +13,14 @@ import KeyAndBalance from "../KeyAndBalance";
|
|||
|
||||
function Target() {
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectSourceChain);
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const chains = useMemo(
|
||||
() => CHAINS.filter((c) => c.id !== sourceChain),
|
||||
[sourceChain]
|
||||
);
|
||||
const targetChain = useSelector(selectTargetChain);
|
||||
const isTargetComplete = useSelector(selectIsTargetComplete);
|
||||
const shouldLockFields = useSelector(selectShouldLockFields);
|
||||
const targetChain = useSelector(selectTransferTargetChain);
|
||||
const isTargetComplete = useSelector(selectTransferIsTargetComplete);
|
||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||
const handleTargetChange = useCallback(
|
||||
(event) => {
|
||||
dispatch(setTargetChain(event.target.value));
|
||||
|
|
|
@ -7,7 +7,10 @@ import {
|
|||
} from "@material-ui/core";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import useGetBalanceEffect from "../../hooks/useGetBalanceEffect";
|
||||
import { selectActiveStep, selectSignedVAAHex } from "../../store/selectors";
|
||||
import {
|
||||
selectTransferActiveStep,
|
||||
selectTransferSignedVAAHex,
|
||||
} from "../../store/selectors";
|
||||
import { setStep } from "../../store/transferSlice";
|
||||
import Redeem from "./Redeem";
|
||||
import Send from "./Send";
|
||||
|
@ -22,8 +25,8 @@ import Target from "./Target";
|
|||
function Transfer() {
|
||||
useGetBalanceEffect();
|
||||
const dispatch = useDispatch();
|
||||
const activeStep = useSelector(selectActiveStep);
|
||||
const signedVAAHex = useSelector(selectSignedVAAHex);
|
||||
const activeStep = useSelector(selectTransferActiveStep);
|
||||
const signedVAAHex = useSelector(selectTransferSignedVAAHex);
|
||||
return (
|
||||
<Container maxWidth="md">
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { useMemo } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { selectAttestSignedVAAHex } from "../store/selectors";
|
||||
import { hexToUint8Array } from "../utils/array";
|
||||
|
||||
export default function useAttestSignedVAA() {
|
||||
const signedVAAHex = useSelector(selectAttestSignedVAAHex);
|
||||
const signedVAA = useMemo(
|
||||
() => (signedVAAHex ? hexToUint8Array(signedVAAHex) : undefined),
|
||||
[signedVAAHex]
|
||||
);
|
||||
return signedVAA;
|
||||
}
|
|
@ -5,7 +5,10 @@ import { useDispatch, useSelector } from "react-redux";
|
|||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
import { TokenImplementation__factory } from "../ethers-contracts";
|
||||
import { selectSourceAsset, selectSourceChain } from "../store/selectors";
|
||||
import {
|
||||
selectTransferSourceAsset,
|
||||
selectTransferSourceChain,
|
||||
} from "../store/selectors";
|
||||
import { setSourceParsedTokenAccount } from "../store/transferSlice";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA, SOLANA_HOST } from "../utils/consts";
|
||||
|
||||
|
@ -27,8 +30,8 @@ function createParsedTokenAccount(
|
|||
|
||||
function useGetBalanceEffect() {
|
||||
const dispatch = useDispatch();
|
||||
const sourceChain = useSelector(selectSourceChain);
|
||||
const sourceAsset = useSelector(selectSourceAsset);
|
||||
const sourceChain = useSelector(selectTransferSourceChain);
|
||||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||
const { wallet } = useSolanaWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const { provider, signerAddress } = useEthereumProvider();
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { useMemo } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { selectSignedVAAHex } from "../store/selectors";
|
||||
import { selectTransferSignedVAAHex } from "../store/selectors";
|
||||
import { hexToUint8Array } from "../utils/array";
|
||||
|
||||
export default function useTransferSignedVAA() {
|
||||
const signedVAAHex = useSelector(selectSignedVAAHex);
|
||||
const signedVAAHex = useSelector(selectTransferSignedVAAHex);
|
||||
const signedVAA = useMemo(
|
||||
() => (signedVAAHex ? hexToUint8Array(signedVAAHex) : undefined),
|
||||
[signedVAAHex]
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
ETH_TEST_TOKEN_ADDRESS,
|
||||
SOL_TEST_TOKEN_ADDRESS,
|
||||
} from "../utils/consts";
|
||||
|
||||
const LAST_STEP = 3;
|
||||
|
||||
type Steps = 0 | 1 | 2 | 3;
|
||||
|
||||
export interface AttestState {
|
||||
activeStep: Steps;
|
||||
sourceChain: ChainId;
|
||||
sourceAsset: string;
|
||||
targetChain: ChainId;
|
||||
signedVAAHex: string | undefined;
|
||||
isSending: boolean;
|
||||
isCreating: boolean;
|
||||
}
|
||||
|
||||
const initialState: AttestState = {
|
||||
activeStep: 0,
|
||||
sourceChain: CHAIN_ID_SOLANA,
|
||||
sourceAsset: SOL_TEST_TOKEN_ADDRESS,
|
||||
targetChain: CHAIN_ID_ETH,
|
||||
signedVAAHex: undefined,
|
||||
isSending: false,
|
||||
isCreating: false,
|
||||
};
|
||||
|
||||
export const attestSlice = createSlice({
|
||||
name: "attest",
|
||||
initialState,
|
||||
reducers: {
|
||||
incrementStep: (state) => {
|
||||
if (state.activeStep < LAST_STEP) state.activeStep++;
|
||||
},
|
||||
decrementStep: (state) => {
|
||||
if (state.activeStep > 0) state.activeStep--;
|
||||
},
|
||||
setStep: (state, action: PayloadAction<Steps>) => {
|
||||
state.activeStep = action.payload;
|
||||
},
|
||||
setSourceChain: (state, action: PayloadAction<ChainId>) => {
|
||||
const prevSourceChain = state.sourceChain;
|
||||
state.sourceChain = action.payload;
|
||||
// TODO: remove or check env - for testing purposes
|
||||
if (action.payload === CHAIN_ID_ETH) {
|
||||
state.sourceAsset = ETH_TEST_TOKEN_ADDRESS;
|
||||
}
|
||||
if (action.payload === CHAIN_ID_SOLANA) {
|
||||
state.sourceAsset = SOL_TEST_TOKEN_ADDRESS;
|
||||
}
|
||||
if (state.targetChain === action.payload) {
|
||||
state.targetChain = prevSourceChain;
|
||||
}
|
||||
},
|
||||
setSourceAsset: (state, action: PayloadAction<string>) => {
|
||||
state.sourceAsset = action.payload;
|
||||
},
|
||||
setTargetChain: (state, action: PayloadAction<ChainId>) => {
|
||||
const prevTargetChain = state.targetChain;
|
||||
state.targetChain = action.payload;
|
||||
if (state.sourceChain === action.payload) {
|
||||
state.sourceChain = prevTargetChain;
|
||||
state.activeStep = 0;
|
||||
// TODO: remove or check env - for testing purposes
|
||||
if (state.targetChain === CHAIN_ID_ETH) {
|
||||
state.sourceAsset = ETH_TEST_TOKEN_ADDRESS;
|
||||
}
|
||||
if (state.targetChain === CHAIN_ID_SOLANA) {
|
||||
state.sourceAsset = SOL_TEST_TOKEN_ADDRESS;
|
||||
}
|
||||
}
|
||||
},
|
||||
setSignedVAAHex: (state, action: PayloadAction<string>) => {
|
||||
state.signedVAAHex = action.payload;
|
||||
state.isSending = false;
|
||||
state.activeStep = 3;
|
||||
},
|
||||
setIsSending: (state, action: PayloadAction<boolean>) => {
|
||||
state.isSending = action.payload;
|
||||
},
|
||||
setIsCreating: (state, action: PayloadAction<boolean>) => {
|
||||
state.isCreating = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
incrementStep,
|
||||
decrementStep,
|
||||
setStep,
|
||||
setSourceChain,
|
||||
setSourceAsset,
|
||||
setTargetChain,
|
||||
setSignedVAAHex,
|
||||
setIsSending,
|
||||
setIsCreating,
|
||||
} = attestSlice.actions;
|
||||
|
||||
export default attestSlice.reducer;
|
|
@ -1,8 +1,10 @@
|
|||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import attestReducer from "./attestSlice";
|
||||
import transferReducer from "./transferSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
attest: attestReducer,
|
||||
transfer: transferReducer,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,27 +2,64 @@ import { parseUnits } from "ethers/lib/utils";
|
|||
import { RootState } from ".";
|
||||
import { CHAIN_ID_SOLANA } from "../utils/consts";
|
||||
|
||||
export const selectActiveStep = (state: RootState) => state.transfer.activeStep;
|
||||
export const selectSourceChain = (state: RootState) =>
|
||||
/*
|
||||
* Attest
|
||||
*/
|
||||
|
||||
export const selectAttestActiveStep = (state: RootState) =>
|
||||
state.attest.activeStep;
|
||||
export const selectAttestSourceChain = (state: RootState) =>
|
||||
state.attest.sourceChain;
|
||||
export const selectAttestSourceAsset = (state: RootState) =>
|
||||
state.attest.sourceAsset;
|
||||
export const selectAttestTargetChain = (state: RootState) =>
|
||||
state.attest.targetChain;
|
||||
export const selectAttestSignedVAAHex = (state: RootState) =>
|
||||
state.attest.signedVAAHex;
|
||||
export const selectAttestIsSending = (state: RootState) =>
|
||||
state.attest.isSending;
|
||||
export const selectAttestIsCreating = (state: RootState) =>
|
||||
state.attest.isCreating;
|
||||
|
||||
// safety checks
|
||||
// TODO: could make this return a string with a user informative message
|
||||
export const selectAttestIsSourceComplete = (state: RootState) =>
|
||||
!!state.attest.sourceChain && !!state.attest.sourceAsset;
|
||||
// TODO: check wrapped asset exists or is native attest
|
||||
export const selectAttestIsTargetComplete = (state: RootState) =>
|
||||
selectAttestIsSourceComplete(state) && !!state.attest.targetChain;
|
||||
export const selectAttestIsSendComplete = (state: RootState) =>
|
||||
!!selectAttestSignedVAAHex(state);
|
||||
export const selectAttestShouldLockFields = (state: RootState) =>
|
||||
selectAttestIsSending(state) || selectAttestIsSendComplete(state);
|
||||
|
||||
/*
|
||||
* Transfer
|
||||
*/
|
||||
|
||||
export const selectTransferActiveStep = (state: RootState) =>
|
||||
state.transfer.activeStep;
|
||||
export const selectTransferSourceChain = (state: RootState) =>
|
||||
state.transfer.sourceChain;
|
||||
export const selectSourceAsset = (state: RootState) =>
|
||||
export const selectTransferSourceAsset = (state: RootState) =>
|
||||
state.transfer.sourceAsset;
|
||||
export const selectSourceParsedTokenAccount = (state: RootState) =>
|
||||
export const selectTransferSourceParsedTokenAccount = (state: RootState) =>
|
||||
state.transfer.sourceParsedTokenAccount;
|
||||
export const selectSourceBalanceString = (state: RootState) =>
|
||||
export const selectTransferSourceBalanceString = (state: RootState) =>
|
||||
state.transfer.sourceParsedTokenAccount?.uiAmountString || "";
|
||||
export const selectAmount = (state: RootState) => state.transfer.amount;
|
||||
export const selectTargetChain = (state: RootState) =>
|
||||
export const selectTransferAmount = (state: RootState) => state.transfer.amount;
|
||||
export const selectTransferTargetChain = (state: RootState) =>
|
||||
state.transfer.targetChain;
|
||||
export const selectSignedVAAHex = (state: RootState) =>
|
||||
export const selectTransferSignedVAAHex = (state: RootState) =>
|
||||
state.transfer.signedVAAHex;
|
||||
export const selectIsSending = (state: RootState) => state.transfer.isSending;
|
||||
export const selectIsRedeeming = (state: RootState) =>
|
||||
export const selectTransferIsSending = (state: RootState) =>
|
||||
state.transfer.isSending;
|
||||
export const selectTransferIsRedeeming = (state: RootState) =>
|
||||
state.transfer.isRedeeming;
|
||||
|
||||
// safety checks
|
||||
// TODO: could make this return a string with a user informative message
|
||||
export const selectIsSourceComplete = (state: RootState) =>
|
||||
export const selectTransferIsSourceComplete = (state: RootState) =>
|
||||
!!state.transfer.sourceChain &&
|
||||
!!state.transfer.sourceAsset &&
|
||||
!!state.transfer.sourceParsedTokenAccount &&
|
||||
|
@ -41,9 +78,9 @@ export const selectIsSourceComplete = (state: RootState) =>
|
|||
)
|
||||
);
|
||||
// TODO: check wrapped asset exists or is native transfer
|
||||
export const selectIsTargetComplete = (state: RootState) =>
|
||||
selectIsSourceComplete(state) && !!state.transfer.targetChain;
|
||||
export const selectIsSendComplete = (state: RootState) =>
|
||||
!!selectSignedVAAHex(state);
|
||||
export const selectShouldLockFields = (state: RootState) =>
|
||||
selectIsSending(state) || selectIsSendComplete(state);
|
||||
export const selectTransferIsTargetComplete = (state: RootState) =>
|
||||
selectTransferIsSourceComplete(state) && !!state.transfer.targetChain;
|
||||
export const selectTransferIsSendComplete = (state: RootState) =>
|
||||
!!selectTransferSignedVAAHex(state);
|
||||
export const selectTransferShouldLockFields = (state: RootState) =>
|
||||
selectTransferIsSending(state) || selectTransferIsSendComplete(state);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||
import {
|
||||
Connection, Keypair,
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
|
@ -66,8 +67,7 @@ export async function attestFromEth(
|
|||
export async function attestFromSolana(
|
||||
wallet: Wallet | undefined,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
mintAddress: string,
|
||||
decimals: number
|
||||
mintAddress: string
|
||||
) {
|
||||
if (!wallet || !wallet.publicKey || !payerAddress) return;
|
||||
const nonceConst = Math.random() * 100000;
|
||||
|
@ -106,8 +106,8 @@ export async function attestFromSolana(
|
|||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
messageKey.publicKey.toString(),
|
||||
mintAddress, // TODO: mint_metadata: what address is this supposed to be?
|
||||
nonce,
|
||||
mintAddress,
|
||||
nonce
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(transferIx, ix);
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { ixFromRust } from "../sdk";
|
||||
import {
|
||||
CHAIN_ID_SOLANA,
|
||||
SOLANA_HOST,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
|
||||
export async function createWrappedOnSolana(
|
||||
wallet: Wallet | undefined,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
if (!wallet || !wallet.publicKey || !payerAddress) return;
|
||||
console.log("creating wrapped");
|
||||
console.log("PROGRAM:", SOL_TOKEN_BRIDGE_ADDRESS);
|
||||
console.log("BRIDGE:", SOL_BRIDGE_ADDRESS);
|
||||
console.log("PAYER:", payerAddress);
|
||||
console.log("VAA:", signedVAA);
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const { create_wrapped_ix } = await import("token-bridge");
|
||||
const ix = ixFromRust(
|
||||
create_wrapped_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
// Sign transaction, broadcast, and confirm
|
||||
const signed = await wallet.signTransaction(transaction);
|
||||
console.log("SIGNED", signed);
|
||||
const txid = await connection.sendRawTransaction(signed.serialize());
|
||||
console.log("SENT", txid);
|
||||
const conf = await connection.confirmTransaction(txid);
|
||||
console.log("CONFIRMED", conf);
|
||||
const info = await connection.getTransaction(txid);
|
||||
console.log("INFO", info);
|
||||
}
|
||||
|
||||
const createWrappedOn = {
|
||||
[CHAIN_ID_SOLANA]: createWrappedOnSolana,
|
||||
};
|
||||
|
||||
export default createWrappedOn;
|
Loading…
Reference in New Issue