Skip to content

Commit

Permalink
NDEV-830. Upgraded Wormhole portal for Neon transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
oable committed Feb 28, 2023
1 parent 42ecd2b commit b5acf3e
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 55 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# dependencies
/node_modules
.idea
/.pnp
.pnp.js

Expand All @@ -24,4 +25,4 @@ yarn-error.log*

# ethereum contracts
/contracts
/src/ethers-contracts
/src/ethers-contracts
131 changes: 86 additions & 45 deletions src/components/FeeMethodSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
CHAIN_ID_ACALA,
CHAIN_ID_KARURA,
CHAIN_ID_NEON,
CHAIN_ID_TERRA,
hexToNativeAssetString,
isEVMChain,
Expand Down Expand Up @@ -32,6 +33,7 @@ import {
} from "../store/selectors";
import { setRelayerFee, setUseRelayer } from "../store/transferSlice";
import { CHAINS_BY_ID, getDefaultNativeCurrencySymbol } from "../utils/consts";
import { useNeonRelayerInfo } from "../hooks/useNeonRelayerInfo";

const useStyles = makeStyles((theme) => ({
feeSelectorContainer: {
Expand Down Expand Up @@ -107,6 +109,11 @@ function FeeMethodSelector() {
vaaNormalizedAmount,
originChain ? hexToNativeAssetString(originAsset, originChain) : undefined
);
const neonRelayerInfo = useNeonRelayerInfo(
targetChain,
vaaNormalizedAmount,
originChain ? hexToNativeAssetString(originAsset, originChain) : undefined
);
const sourceChain = useSelector(selectTransferSourceChain);
const dispatch = useDispatch();
const relayerSelected = !!useSelector(selectTransferUseRelayer);
Expand All @@ -123,13 +130,23 @@ function FeeMethodSelector() {
targetChain === CHAIN_ID_ACALA || targetChain === CHAIN_ID_KARURA;
const acalaRelayerEligible = acalaRelayerInfo.data?.shouldRelay;

const targetIsNeon = targetChain === CHAIN_ID_NEON;
const neonRelayerEligible = neonRelayerInfo.data?.shouldRelay;

const chooseAcalaRelayer = useCallback(() => {
if (targetIsAcala && acalaRelayerEligible) {
dispatch(setUseRelayer(true));
dispatch(setRelayerFee(undefined));
}
}, [dispatch, targetIsAcala, acalaRelayerEligible]);

const chooseNeonRelayer = useCallback(() => {
if (targetIsNeon && neonRelayerEligible) {
dispatch(setUseRelayer(true));
dispatch(setRelayerFee(undefined));
}
}, [dispatch, targetIsNeon, neonRelayerEligible]);

const chooseRelayer = useCallback(() => {
if (relayerEligible) {
dispatch(setUseRelayer(true));
Expand All @@ -149,6 +166,12 @@ function FeeMethodSelector() {
} else {
chooseManual();
}
} else if (targetIsNeon) {
if (neonRelayerEligible) {
chooseNeonRelayer();
} else {
chooseManual();
}
} else if (relayerInfo.data?.isRelayable === true) {
chooseRelayer();
} else if (relayerInfo.data?.isRelayable === false) {
Expand All @@ -162,53 +185,67 @@ function FeeMethodSelector() {
targetIsAcala,
acalaRelayerEligible,
chooseAcalaRelayer,
targetIsNeon,
neonRelayerEligible,
chooseNeonRelayer,
]);

const acalaRelayerContent = (
<Card
className={
classes.optionCardBase +
" " +
(relayerSelected ? classes.optionCardSelected : "") +
" " +
(acalaRelayerEligible ? classes.optionCardSelectable : "")
}
onClick={chooseAcalaRelayer}
>
<div className={classes.alignCenterContainer}>
<Checkbox
checked={relayerSelected}
disabled={!acalaRelayerEligible}
onClick={chooseAcalaRelayer}
className={classes.inlineBlock}
/>
<div className={clsx(classes.inlineBlock, classes.alignLeft)}>
{acalaRelayerEligible ? (
<div>
<Typography variant="body1">
{CHAINS_BY_ID[targetChain].name}
</Typography>
<Typography variant="body2" color="textSecondary">
{CHAINS_BY_ID[targetChain].name} pays gas for you &#127881;
</Typography>
</div>
) : (
<>
<Typography color="textSecondary" variant="body2">
{"Automatic redeem is unavailable for this token."}
</Typography>
<div />
</>
)}
const relayerContentFactory = (relayerEligible: any, chooseRelayer: any) => {
return (
<Card
className={
classes.optionCardBase +
" " +
(relayerSelected ? classes.optionCardSelected : "") +
" " +
(relayerEligible ? classes.optionCardSelectable : "")
}
onClick={chooseRelayer}
>
<div className={classes.alignCenterContainer}>
<Checkbox
checked={relayerSelected}
disabled={!relayerEligible}
onClick={chooseRelayer}
className={classes.inlineBlock}
/>
<div className={clsx(classes.inlineBlock, classes.alignLeft)}>
{relayerEligible ? (
<>
<Typography variant="body1">
{CHAINS_BY_ID[targetChain].name}
</Typography>
<Typography variant="body2" color="textSecondary">
{CHAINS_BY_ID[targetChain].name} pays gas for you &#127881;
</Typography>
</>
) : (
<>
<Typography color="textSecondary" variant="body2">
{"Automatic redeem is unavailable for this token."}
</Typography>
<div />
</>
)}
</div>
</div>
</div>
{acalaRelayerEligible ? (
<>
<div></div>
<div></div>
</>
) : null}
</Card>
{relayerEligible ? (
<>
<div></div>
<div></div>
</>
) : null}
</Card>
);
};

const acalaRelayerContent = relayerContentFactory(
acalaRelayerEligible,
chooseAcalaRelayer
);
const neonRelayerContent = relayerContentFactory(
neonRelayerEligible,
chooseNeonRelayer
);

const relayerContent = (
Expand Down Expand Up @@ -323,7 +360,11 @@ function FeeMethodSelector() {
>
How would you like to pay the target chain fees?
</Typography>
{targetIsAcala ? acalaRelayerContent : relayerContent}
{targetIsAcala
? acalaRelayerContent
: targetIsNeon
? neonRelayerContent
: relayerContent}
{manualRedeemContent}
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions src/components/Transfer/Redeem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ function Redeem() {
}, [useRelayer]);
const targetChain = useSelector(selectTransferTargetChain);
const targetIsAcala =
targetChain === CHAIN_ID_ACALA || targetChain === CHAIN_ID_KARURA;
targetChain === CHAIN_ID_ACALA ||
targetChain === CHAIN_ID_KARURA ||
targetChain === CHAIN_ID_NEON;
const targetAsset = useSelector(selectTransferTargetAsset);
const isRecovery = useSelector(selectTransferIsRecovery);
const { isTransferCompletedLoading, isTransferCompleted } =
Expand Down Expand Up @@ -273,7 +275,6 @@ function Redeem() {
</ButtonWithLoader>
<WaitingForWalletMessage />
</>

{useRelayer && !isTransferCompleted ? (
<div className={classes.centered}>
<Button
Expand Down
17 changes: 14 additions & 3 deletions src/hooks/useHandleRedeem.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {
ChainId,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
CHAIN_ID_INJECTIVE,
CHAIN_ID_KLAYTN,
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
CHAIN_ID_XPLA,
ChainId,
CHAINS,
isEVMChain,
isTerraChain,
postVaaSolanaWithRetry,
Expand Down Expand Up @@ -68,9 +69,10 @@ import {
getTokenBridgeAddressForChain,
MAX_VAA_UPLOAD_RETRIES_SOLANA,
NEAR_TOKEN_BRIDGE_ACCOUNT,
SOLANA_HOST,
NEON_RELAY_URL,
SOL_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS,
SOLANA_HOST,
} from "../utils/consts";
import { broadcastInjectiveTx } from "../utils/injective";
import {
Expand Down Expand Up @@ -530,13 +532,22 @@ export function useHandleRedeem() {
injAddress,
]);

const getUrl = (targetChain: ChainId): string => {
switch (targetChain) {
case CHAINS.neon:
return NEON_RELAY_URL;
default:
return ACALA_RELAY_URL;
}
};

const handleAcalaRelayerRedeemClick = useCallback(async () => {
if (!signedVAA) return;

dispatch(setIsRedeeming(true));

try {
const res = await axios.post(ACALA_RELAY_URL, {
const res = await axios.post(getUrl(targetChain), {
targetChain,
signedVAA: uint8ArrayToHex(signedVAA),
});
Expand Down
88 changes: 88 additions & 0 deletions src/hooks/useNeonRelayerInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { ChainId } from "@certusone/wormhole-sdk";
import { CHAIN_ID_NEON } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
import axios from "axios";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
DataWrapper,
errorDataWrapper,
fetchDataWrapper,
getEmptyDataWrapper,
receiveDataWrapper,
} from "../store/helpers";
import { selectNeonRelayerInfo } from "../store/selectors";
import {
errorNeonRelayerInfo,
fetchNeonRelayerInfo,
receiveNeonRelayerInfo,
setNeonRelayerInfo,
} from "../store/transferSlice";
import { NEON_RELAYER_URL, NEON_SHOULD_RELAY_URL } from "../utils/consts";

export interface NeonRelayerInfo {
shouldRelay: boolean;
msg: string;
}

export const useNeonRelayerInfo = (
targetChain: ChainId,
vaaNormalizedAmount: string | undefined,
originAsset: string | undefined,
useStore: boolean = true
) => {
// within flow, update the store
const dispatch = useDispatch();
// within recover, use internal state
const [state, setState] = useState<DataWrapper<NeonRelayerInfo>>(
getEmptyDataWrapper()
);
useEffect(() => {
let cancelled = false;
if (
!NEON_RELAYER_URL ||
!targetChain ||
targetChain !== CHAIN_ID_NEON ||
!vaaNormalizedAmount ||
!originAsset
) {
useStore
? dispatch(setNeonRelayerInfo())
: setState(getEmptyDataWrapper());
return;
}
useStore ? dispatch(fetchNeonRelayerInfo()) : setState(fetchDataWrapper());
(async () => {
try {
const result = await axios.get(NEON_SHOULD_RELAY_URL, {
params: {
targetChain,
originAsset,
amount: vaaNormalizedAmount,
},
});
if (!cancelled) {
useStore
? dispatch(receiveNeonRelayerInfo(result.data))
: setState(receiveDataWrapper(result.data));
}
} catch (e) {
if (!cancelled) {
useStore
? dispatch(
errorNeonRelayerInfo(
"Failed to retrieve the Neon relayer info."
)
)
: setState(
errorDataWrapper("Failed to retrieve the Neon relayer info.")
);
}
}
})();
return () => {
cancelled = true;
};
}, [targetChain, vaaNormalizedAmount, originAsset, dispatch, useStore]);
const neonRelayerInfoFromStore = useSelector(selectNeonRelayerInfo);
return useStore ? neonRelayerInfoFromStore : state;
};
9 changes: 7 additions & 2 deletions src/store/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
CHAIN_ID_ACALA,
CHAIN_ID_KARURA,
CHAIN_ID_NEON,
CHAIN_ID_SOLANA,
isEVMChain,
} from "@certusone/wormhole-sdk";
Expand Down Expand Up @@ -296,14 +297,16 @@ export const selectTransferTargetError = (state: RootState) => {
state.transfer.relayerFee === undefined &&
// Acala offers relaying without a fee for qualified tokens
state.transfer.targetChain !== CHAIN_ID_ACALA &&
state.transfer.targetChain !== CHAIN_ID_KARURA
state.transfer.targetChain !== CHAIN_ID_KARURA &&
state.transfer.targetChain !== CHAIN_ID_NEON
) {
return "Invalid relayer fee.";
}
if (
state.transfer.useRelayer &&
(state.transfer.targetChain === CHAIN_ID_ACALA ||
state.transfer.targetChain === CHAIN_ID_KARURA) &&
state.transfer.targetChain === CHAIN_ID_KARURA ||
state.transfer.targetChain === CHAIN_ID_NEON) &&
!state.transfer.acalaRelayerInfo.data?.shouldRelay
) {
return "Token is ineligible for relay.";
Expand Down Expand Up @@ -357,6 +360,8 @@ export const selectTransferRelayerFee = (state: RootState) =>
state.transfer.relayerFee;
export const selectAcalaRelayerInfo = (state: RootState) =>
state.transfer.acalaRelayerInfo;
export const selectNeonRelayerInfo = (state: RootState) =>
state.transfer.neonRelayerInfo;
export const selectSolanaTokenMap = (state: RootState) => {
return state.tokens.solanaTokenMap;
};
Expand Down
Loading

0 comments on commit b5acf3e

Please sign in to comment.