bridge_ui: separate attest workflow

Change-Id: If6270d7ce0deb02a48b63b81ba2ef688c5f4af29
This commit is contained in:
Evan Gray 2021-08-10 21:55:06 -04:00 committed by Hendrik Hofstadt
parent f53d180753
commit 1da690aa42
19 changed files with 708 additions and 69 deletions

View File

@ -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>
</>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 &&

View File

@ -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 {

View File

@ -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));

View File

@ -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));

View File

@ -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">

View File

@ -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;
}

View File

@ -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();

View File

@ -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]

View File

@ -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;

View File

@ -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,
},
});

View File

@ -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);

View File

@ -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);

View File

@ -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;