Skip to content

Commit 67ffbfa

Browse files
committed
NDEV-830. Upgraded Wormhole portal for Neon transfer
1 parent 42ecd2b commit 67ffbfa

File tree

8 files changed

+241
-58
lines changed

8 files changed

+241
-58
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# dependencies
44
/node_modules
5+
.idea
56
/.pnp
67
.pnp.js
78

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

2526
# ethereum contracts
2627
/contracts
27-
/src/ethers-contracts
28+
/src/ethers-contracts

src/components/FeeMethodSelector.tsx

Lines changed: 87 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
CHAIN_ID_ACALA,
33
CHAIN_ID_KARURA,
4+
CHAIN_ID_NEON,
45
CHAIN_ID_TERRA,
56
hexToNativeAssetString,
67
isEVMChain,
@@ -32,6 +33,7 @@ import {
3233
} from "../store/selectors";
3334
import { setRelayerFee, setUseRelayer } from "../store/transferSlice";
3435
import { CHAINS_BY_ID, getDefaultNativeCurrencySymbol } from "../utils/consts";
36+
import { useNeonRelayerInfo } from "../hooks/useNeonRelayerInfo";
3537

3638
const useStyles = makeStyles((theme) => ({
3739
feeSelectorContainer: {
@@ -107,6 +109,11 @@ function FeeMethodSelector() {
107109
vaaNormalizedAmount,
108110
originChain ? hexToNativeAssetString(originAsset, originChain) : undefined
109111
);
112+
const neonRelayerInfo = useNeonRelayerInfo(
113+
targetChain,
114+
vaaNormalizedAmount,
115+
originChain ? hexToNativeAssetString(originAsset, originChain) : undefined
116+
);
110117
const sourceChain = useSelector(selectTransferSourceChain);
111118
const dispatch = useDispatch();
112119
const relayerSelected = !!useSelector(selectTransferUseRelayer);
@@ -123,13 +130,23 @@ function FeeMethodSelector() {
123130
targetChain === CHAIN_ID_ACALA || targetChain === CHAIN_ID_KARURA;
124131
const acalaRelayerEligible = acalaRelayerInfo.data?.shouldRelay;
125132

133+
const targetIsNeon = targetChain === CHAIN_ID_NEON;
134+
const neonRelayerEligible = neonRelayerInfo.data?.shouldRelay;
135+
126136
const chooseAcalaRelayer = useCallback(() => {
127137
if (targetIsAcala && acalaRelayerEligible) {
128138
dispatch(setUseRelayer(true));
129139
dispatch(setRelayerFee(undefined));
130140
}
131141
}, [dispatch, targetIsAcala, acalaRelayerEligible]);
132142

143+
const chooseNeonRelayer = useCallback(() => {
144+
if (targetIsNeon && neonRelayerEligible) {
145+
dispatch(setUseRelayer(true));
146+
dispatch(setRelayerFee(undefined));
147+
}
148+
}, [dispatch, targetIsNeon, neonRelayerEligible]);
149+
133150
const chooseRelayer = useCallback(() => {
134151
if (relayerEligible) {
135152
dispatch(setUseRelayer(true));
@@ -149,66 +166,84 @@ function FeeMethodSelector() {
149166
} else {
150167
chooseManual();
151168
}
169+
} else if (targetIsNeon) {
170+
if (neonRelayerEligible) {
171+
chooseNeonRelayer();
172+
} else {
173+
chooseManual();
174+
}
152175
} else if (relayerInfo.data?.isRelayable === true) {
153176
chooseRelayer();
154177
} else if (relayerInfo.data?.isRelayable === false) {
155178
chooseManual();
156179
}
157180
//If it's undefined / null it's still loading, so no action is taken.
158-
}, [
159-
relayerInfo,
181+
}, [relayerInfo,
160182
chooseRelayer,
161183
chooseManual,
162184
targetIsAcala,
163185
acalaRelayerEligible,
164186
chooseAcalaRelayer,
165-
]);
187+
targetIsNeon,
188+
neonRelayerEligible,
189+
chooseNeonRelayer]);
166190

167-
const acalaRelayerContent = (
168-
<Card
169-
className={
170-
classes.optionCardBase +
171-
" " +
172-
(relayerSelected ? classes.optionCardSelected : "") +
173-
" " +
174-
(acalaRelayerEligible ? classes.optionCardSelectable : "")
175-
}
176-
onClick={chooseAcalaRelayer}
177-
>
178-
<div className={classes.alignCenterContainer}>
179-
<Checkbox
180-
checked={relayerSelected}
181-
disabled={!acalaRelayerEligible}
182-
onClick={chooseAcalaRelayer}
183-
className={classes.inlineBlock}
184-
/>
185-
<div className={clsx(classes.inlineBlock, classes.alignLeft)}>
186-
{acalaRelayerEligible ? (
187-
<div>
188-
<Typography variant="body1">
189-
{CHAINS_BY_ID[targetChain].name}
190-
</Typography>
191-
<Typography variant="body2" color="textSecondary">
192-
{CHAINS_BY_ID[targetChain].name} pays gas for you &#127881;
193-
</Typography>
194-
</div>
195-
) : (
196-
<>
197-
<Typography color="textSecondary" variant="body2">
198-
{"Automatic redeem is unavailable for this token."}
199-
</Typography>
200-
<div />
201-
</>
202-
)}
191+
const relayerContentFactory = (relayerEligible: any, chooseRelayer: any) => {
192+
return (
193+
<Card
194+
className={
195+
classes.optionCardBase +
196+
" " +
197+
(relayerSelected ? classes.optionCardSelected : "") +
198+
" " +
199+
(relayerEligible ? classes.optionCardSelectable : "")
200+
}
201+
onClick={chooseRelayer}
202+
>
203+
<div className={classes.alignCenterContainer}>
204+
<Checkbox
205+
checked={relayerSelected}
206+
disabled={!relayerEligible}
207+
onClick={chooseRelayer}
208+
className={classes.inlineBlock}
209+
/>
210+
<div className={clsx(classes.inlineBlock, classes.alignLeft)}>
211+
{relayerEligible ? (
212+
<>
213+
<Typography variant="body1">
214+
{CHAINS_BY_ID[targetChain].name}
215+
</Typography>
216+
<Typography variant="body2" color="textSecondary">
217+
{CHAINS_BY_ID[targetChain].name} pays gas for you &#127881;
218+
</Typography>
219+
</>
220+
) : (
221+
<>
222+
<Typography color="textSecondary" variant="body2">
223+
{"Automatic redeem is unavailable for this token."}
224+
</Typography>
225+
<div />
226+
</>
227+
)}
228+
</div>
203229
</div>
204-
</div>
205-
{acalaRelayerEligible ? (
206-
<>
207-
<div></div>
208-
<div></div>
209-
</>
210-
) : null}
211-
</Card>
230+
{relayerEligible ? (
231+
<>
232+
<div></div>
233+
<div></div>
234+
</>
235+
) : null}
236+
</Card>
237+
);
238+
};
239+
240+
const acalaRelayerContent = relayerContentFactory(
241+
acalaRelayerEligible,
242+
chooseAcalaRelayer
243+
);
244+
const neonRelayerContent = relayerContentFactory(
245+
neonRelayerEligible,
246+
chooseNeonRelayer
212247
);
213248

214249
const relayerContent = (
@@ -323,7 +358,11 @@ function FeeMethodSelector() {
323358
>
324359
How would you like to pay the target chain fees?
325360
</Typography>
326-
{targetIsAcala ? acalaRelayerContent : relayerContent}
361+
{targetIsAcala
362+
? acalaRelayerContent
363+
: targetIsNeon
364+
? neonRelayerContent
365+
: relayerContent}
327366
{manualRedeemContent}
328367
</div>
329368
);

src/components/Transfer/Redeem.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ function Redeem() {
9292
}, [useRelayer]);
9393
const targetChain = useSelector(selectTransferTargetChain);
9494
const targetIsAcala =
95-
targetChain === CHAIN_ID_ACALA || targetChain === CHAIN_ID_KARURA;
95+
targetChain === CHAIN_ID_ACALA ||
96+
targetChain === CHAIN_ID_KARURA ||
97+
targetChain === CHAIN_ID_NEON;
9698
const targetAsset = useSelector(selectTransferTargetAsset);
9799
const isRecovery = useSelector(selectTransferIsRecovery);
98100
const { isTransferCompletedLoading, isTransferCompleted } =
@@ -273,7 +275,6 @@ function Redeem() {
273275
</ButtonWithLoader>
274276
<WaitingForWalletMessage />
275277
</>
276-
277278
{useRelayer && !isTransferCompleted ? (
278279
<div className={classes.centered}>
279280
<Button

src/hooks/useHandleRedeem.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {
2-
ChainId,
32
CHAIN_ID_ALGORAND,
43
CHAIN_ID_APTOS,
54
CHAIN_ID_INJECTIVE,
65
CHAIN_ID_KLAYTN,
76
CHAIN_ID_NEAR,
87
CHAIN_ID_SOLANA,
98
CHAIN_ID_XPLA,
9+
ChainId,
10+
CHAINS,
1011
isEVMChain,
1112
isTerraChain,
1213
postVaaSolanaWithRetry,
@@ -68,9 +69,10 @@ import {
6869
getTokenBridgeAddressForChain,
6970
MAX_VAA_UPLOAD_RETRIES_SOLANA,
7071
NEAR_TOKEN_BRIDGE_ACCOUNT,
71-
SOLANA_HOST,
72+
NEON_RELAY_URL,
7273
SOL_BRIDGE_ADDRESS,
7374
SOL_TOKEN_BRIDGE_ADDRESS,
75+
SOLANA_HOST,
7476
} from "../utils/consts";
7577
import { broadcastInjectiveTx } from "../utils/injective";
7678
import {
@@ -530,13 +532,22 @@ export function useHandleRedeem() {
530532
injAddress,
531533
]);
532534

535+
const getUrl = (targetChain: ChainId): string => {
536+
switch (targetChain) {
537+
case CHAINS.neon:
538+
return NEON_RELAY_URL;
539+
default:
540+
return ACALA_RELAY_URL;
541+
}
542+
};
543+
533544
const handleAcalaRelayerRedeemClick = useCallback(async () => {
534545
if (!signedVAA) return;
535546

536547
dispatch(setIsRedeeming(true));
537548

538549
try {
539-
const res = await axios.post(ACALA_RELAY_URL, {
550+
const res = await axios.post(getUrl(targetChain), {
540551
targetChain,
541552
signedVAA: uint8ArrayToHex(signedVAA),
542553
});

src/hooks/useNeonRelayerInfo.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { ChainId } from "@certusone/wormhole-sdk";
2+
import { CHAIN_ID_NEON } from "@certusone/wormhole-sdk/lib/cjs/utils/consts";
3+
import axios from "axios";
4+
import { useEffect, useState } from "react";
5+
import { useDispatch, useSelector } from "react-redux";
6+
import {
7+
DataWrapper,
8+
errorDataWrapper,
9+
fetchDataWrapper,
10+
getEmptyDataWrapper,
11+
receiveDataWrapper,
12+
} from "../store/helpers";
13+
import { selectNeonRelayerInfo } from "../store/selectors";
14+
import {
15+
errorNeonRelayerInfo,
16+
fetchNeonRelayerInfo,
17+
receiveNeonRelayerInfo,
18+
setNeonRelayerInfo,
19+
} from "../store/transferSlice";
20+
import { NEON_RELAYER_URL, NEON_SHOULD_RELAY_URL } from "../utils/consts";
21+
22+
export interface NeonRelayerInfo {
23+
shouldRelay: boolean;
24+
msg: string;
25+
}
26+
27+
export const useNeonRelayerInfo = (
28+
targetChain: ChainId,
29+
vaaNormalizedAmount: string | undefined,
30+
originAsset: string | undefined,
31+
useStore: boolean = true
32+
) => {
33+
// within flow, update the store
34+
const dispatch = useDispatch();
35+
// within recover, use internal state
36+
const [state, setState] = useState<DataWrapper<NeonRelayerInfo>>(
37+
getEmptyDataWrapper()
38+
);
39+
useEffect(() => {
40+
let cancelled = false;
41+
if (
42+
!NEON_RELAYER_URL ||
43+
!targetChain ||
44+
targetChain !== CHAIN_ID_NEON ||
45+
!vaaNormalizedAmount ||
46+
!originAsset
47+
) {
48+
useStore
49+
? dispatch(setNeonRelayerInfo())
50+
: setState(getEmptyDataWrapper());
51+
return;
52+
}
53+
useStore ? dispatch(fetchNeonRelayerInfo()) : setState(fetchDataWrapper());
54+
(async () => {
55+
try {
56+
const result = await axios.get(NEON_SHOULD_RELAY_URL, {
57+
params: {
58+
targetChain,
59+
originAsset,
60+
amount: vaaNormalizedAmount,
61+
},
62+
});
63+
if (!cancelled) {
64+
useStore
65+
? dispatch(receiveNeonRelayerInfo(result.data))
66+
: setState(receiveDataWrapper(result.data));
67+
}
68+
} catch (e) {
69+
if (!cancelled) {
70+
useStore
71+
? dispatch(
72+
errorNeonRelayerInfo(
73+
"Failed to retrieve the Neon relayer info."
74+
)
75+
)
76+
: setState(
77+
errorDataWrapper("Failed to retrieve the Neon relayer info.")
78+
);
79+
}
80+
}
81+
})();
82+
return () => {
83+
cancelled = true;
84+
};
85+
}, [targetChain, vaaNormalizedAmount, originAsset, dispatch, useStore]);
86+
const neonRelayerInfoFromStore = useSelector(selectNeonRelayerInfo);
87+
return useStore ? neonRelayerInfoFromStore : state;
88+
};

0 commit comments

Comments
 (0)