From 1ab186f8352f9666703b14bcba804ba70293e1a8 Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Fri, 10 Nov 2023 14:59:44 +0800 Subject: [PATCH 01/10] LnBridge-default withdraw margin api --- packages/apps/src/abi/lnaccess-controller.ts | 182 ++++++++++++++++++ packages/apps/src/bridges/lnbridge-default.ts | 42 +++- 2 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 packages/apps/src/abi/lnaccess-controller.ts diff --git a/packages/apps/src/abi/lnaccess-controller.ts b/packages/apps/src/abi/lnaccess-controller.ts new file mode 100644 index 000000000..542430417 --- /dev/null +++ b/packages/apps/src/abi/lnaccess-controller.ts @@ -0,0 +1,182 @@ +const abi = [ + { + inputs: [ + { internalType: "address", name: "_dao", type: "address" }, + { internalType: "address", name: "_endpoint", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "uint16", name: "lzRemoteChainId", type: "uint16" }, + { indexed: false, internalType: "bytes", name: "srcAddress", type: "bytes" }, + { indexed: false, internalType: "bool", name: "successed", type: "bool" }, + ], + name: "CallResult", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "uint16", name: "lzRemoteChainId", type: "uint16" }, + { indexed: false, internalType: "bytes", name: "srcAddress", type: "bytes" }, + { indexed: false, internalType: "address", name: "remoteAppAddress", type: "address" }, + ], + name: "CallerUnMatched", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "appAddress", type: "address" }, + { internalType: "bool", name: "enable", type: "bool" }, + ], + name: "authoriseAppCaller", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "callerWhiteList", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dao", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "endpoint", + outputs: [{ internalType: "contract ILayerZeroEndpoint", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_remoteChainId", type: "uint256" }, + { internalType: "bytes", name: "_message", type: "bytes" }, + ], + name: "fee", + outputs: [ + { internalType: "uint256", name: "nativeFee", type: "uint256" }, + { internalType: "uint256", name: "zroFee", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint16", name: "_srcChainId", type: "uint16" }, + { internalType: "bytes", name: "_srcAddress", type: "bytes" }, + { internalType: "uint64", name: "", type: "uint64" }, + { internalType: "bytes", name: "_payload", type: "bytes" }, + ], + name: "lzReceive", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "operator", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_remoteChainId", type: "uint256" }, + { internalType: "address", name: "_remoteBridge", type: "address" }, + ], + name: "registerRemoteReceiver", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_remoteChainId", type: "uint256" }, + { internalType: "address", name: "_remoteBridge", type: "address" }, + ], + name: "registerRemoteSender", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + name: "remoteAppReceivers", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + name: "remoteAppSenders", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "remoteMessagers", + outputs: [ + { internalType: "uint16", name: "lzRemoteChainId", type: "uint16" }, + { internalType: "address", name: "messager", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_remoteChainId", type: "uint256" }, + { internalType: "bytes", name: "_message", type: "bytes" }, + { internalType: "bytes", name: "_params", type: "bytes" }, + ], + name: "sendMessage", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_operator", type: "address" }], + name: "setOperator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_appRemoteChainId", type: "uint256" }, + { internalType: "uint16", name: "_lzRemoteChainId", type: "uint16" }, + { internalType: "address", name: "_remoteMessager", type: "address" }, + ], + name: "setRemoteMessager", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_dao", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint16", name: "", type: "uint16" }], + name: "trustedRemotes", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, +] as const; + +export default abi; diff --git a/packages/apps/src/bridges/lnbridge-default.ts b/packages/apps/src/bridges/lnbridge-default.ts index 12bcca403..3ab399e08 100644 --- a/packages/apps/src/bridges/lnbridge-default.ts +++ b/packages/apps/src/bridges/lnbridge-default.ts @@ -1,4 +1,4 @@ -import { Address, TransactionReceipt } from "viem"; +import { Address, TransactionReceipt, bytesToHex } from "viem"; import { LnBridgeBase } from "./lnbridge-base"; import { ChainConfig, ChainID } from "@/types/chain"; import { Token } from "@/types/token"; @@ -165,4 +165,44 @@ export class LnBridgeDefault extends LnBridgeBase { return this.publicClient.waitForTransactionReceipt({ hash }); } } + + async withdrawMargin(recipient: Address, amount: bigint) { + await this.validateNetwork("source"); + + if ( + this.contract && + this.sourceToken && + this.targetToken && + this.targetChain && + this.publicClient && + this.walletClient + ) { + const bridgeAbi = (await import(`../abi/lnbridgev20-default`)).default; + const accessAbi = (await import(`../abi/lnaccess-controller`)).default; + const remoteChainId = BigInt(this.targetChain.id); + + const [sendService, _receiveService] = await this.publicClient.readContract({ + address: this.contract.sourceAddress, + abi: bridgeAbi, + functionName: "messagers", + args: [remoteChainId], + }); + const [nativeFee, _zroFee] = await this.publicClient.readContract({ + address: sendService, + abi: accessAbi, + functionName: "fee", + args: [remoteChainId, bytesToHex(Uint8Array.from([123]), { size: 500 })], + }); + + const hash = await this.walletClient.writeContract({ + address: this.contract.sourceAddress, + abi: bridgeAbi, + functionName: "requestWithdrawMargin", + args: [remoteChainId, this.sourceToken.address, this.targetToken.address, amount, recipient], + gas: this.getTxGasLimit(), + value: nativeFee, + }); + return this.publicClient.waitForTransactionReceipt({ hash }); + } + } } From 61aba5c1097fedf7a174016a0c71351de324a616 Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Fri, 10 Nov 2023 16:08:56 +0800 Subject: [PATCH 02/10] Indexer messageChannel --- packages/apps/src/config/gql.ts | 1 + packages/apps/src/types/graphql.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/apps/src/config/gql.ts b/packages/apps/src/config/gql.ts index 9451f340a..2f7c6613e 100644 --- a/packages/apps/src/config/gql.ts +++ b/packages/apps/src/config/gql.ts @@ -129,6 +129,7 @@ export const QUERY_LNRELAYERS = gql` cost profit heartbeatTimestamp + messageChannel } } } diff --git a/packages/apps/src/types/graphql.ts b/packages/apps/src/types/graphql.ts index 63a6d95a2..368c59441 100644 --- a/packages/apps/src/types/graphql.ts +++ b/packages/apps/src/types/graphql.ts @@ -67,6 +67,8 @@ export interface RecordVariables { id: string; } +export type MessageChannel = "layerzero"; + interface Lnv20RelayInfo { id: string; nonce: string; @@ -88,6 +90,7 @@ interface Lnv20RelayInfo { cost?: string | null; profit?: string | null; heartbeatTimestamp?: number | null; + messageChannel?: MessageChannel | null; } export interface RelayersResponseData { @@ -123,6 +126,7 @@ export type LnRelayerInfo = Pick< | "cost" | "profit" | "heartbeatTimestamp" + | "messageChannel" >; export interface LnRelayersResponseData { From 9b8f318834078ead5e79e728ac3cf3e5800f0821 Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Fri, 10 Nov 2023 16:09:23 +0800 Subject: [PATCH 03/10] Relayer provider withdrawMargin --- .../apps/src/providers/relayer-provider.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/apps/src/providers/relayer-provider.tsx b/packages/apps/src/providers/relayer-provider.tsx index f6539d713..73e787206 100644 --- a/packages/apps/src/providers/relayer-provider.tsx +++ b/packages/apps/src/providers/relayer-provider.tsx @@ -50,6 +50,7 @@ interface RelayerCtx { depositMargin: (margin: bigint) => Promise; updateFeeAndMargin: (margin: bigint, baseFee: bigint, feeRate: number) => Promise; setFeeAndRate: (baseFee: bigint, feeRate: number) => Promise; + withdrawMargin: (amount: bigint) => Promise; } const defaultValue: RelayerCtx = { @@ -79,6 +80,7 @@ const defaultValue: RelayerCtx = { depositMargin: async () => undefined, updateFeeAndMargin: async () => undefined, setFeeAndRate: async () => undefined, + withdrawMargin: async () => undefined, }; export const RelayerContext = createContext(defaultValue); @@ -229,13 +231,29 @@ export default function RelayerProvider({ children }: PropsWithChildren return receipt; } catch (err) { console.error(err); - notification.error({ title: "Transfer failed", description: (err as Error).message }); + notification.error({ title: "Update failed", description: (err as Error).message }); } } }, [address, oppositeBridge, sourceChain], ); + const withdrawMargin = useCallback( + async (amount: bigint) => { + if (address && defaultBridge) { + try { + const receipt = await defaultBridge.withdrawMargin(address, amount); + notifyTransaction(receipt, sourceChain); + return receipt; + } catch (err) { + console.error(err); + notification.error({ title: "Withdraw failed", description: (err as Error).message }); + } + } + }, + [address, defaultBridge, sourceChain], + ); + useEffect(() => { let sub$$: Subscription | undefined; @@ -299,6 +317,7 @@ export default function RelayerProvider({ children }: PropsWithChildren depositMargin, updateFeeAndMargin, setFeeAndRate, + withdrawMargin, }} > {children} From 74485ed2eb54f5d0c331f2467b2419073a50638c Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Fri, 10 Nov 2023 16:09:55 +0800 Subject: [PATCH 04/10] Support layerzero withdraw margin --- .../src/components/relayer-manage-modal.tsx | 77 +++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/packages/apps/src/components/relayer-manage-modal.tsx b/packages/apps/src/components/relayer-manage-modal.tsx index b87ef1b47..42ff21125 100644 --- a/packages/apps/src/components/relayer-manage-modal.tsx +++ b/packages/apps/src/components/relayer-manage-modal.tsx @@ -11,6 +11,7 @@ import { formatBalance } from "@/utils/balance"; import { useRelayer } from "@/hooks/use-relayer"; import dynamic from "next/dynamic"; import { formatFeeRate, isValidFeeRate } from "@/utils/misc"; +import { TransactionReceipt } from "viem"; type TabKey = "update" | "deposit" | "withdraw"; const Modal = dynamic(() => import("@/ui/modal"), { ssr: false }); @@ -45,12 +46,21 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc setFeeAndRate, depositMargin, updateFeeAndMargin, + withdrawMargin, sourceApprove, targetApprove, } = useRelayer(); const [activeKey, setActiveKey] = useState["activeKey"]>("update"); const [height, setHeight] = useState(); const [busy, setBusy] = useState(false); + const [depositAmount, setDepositAmount] = useState<{ formatted: bigint; value: string }>({ + formatted: 0n, + value: "", + }); + const [withdrawAmount, setWithdrawAmount] = useState<{ formatted: bigint; value: string }>({ + formatted: 0n, + value: "", + }); const { chain } = useNetwork(); const { switchNetwork } = useSwitchNetwork(); @@ -114,13 +124,13 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc if (bridgeCategory === "lnbridgev20-default") { if (chain?.id !== targetChain?.id) { text = "Switch Network"; - } else if (sourceToken?.type !== "native" && margin.formatted > (targetAllowance?.value || 0n)) { + } else if (sourceToken?.type !== "native" && depositAmount.formatted > (targetAllowance?.value || 0n)) { text = "Approve"; } } else if (bridgeCategory === "lnbridgev20-opposite") { if (chain?.id !== sourceChain?.id) { text = "Switch Network"; - } else if (sourceToken?.type !== "native" && margin.formatted > (sourceAllowance?.value || 0n)) { + } else if (sourceToken?.type !== "native" && depositAmount.formatted > (sourceAllowance?.value || 0n)) { text = "Approve"; } } @@ -130,7 +140,7 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc return text; }, [ chain, - margin, + depositAmount, activeKey, bridgeCategory, sourceChain, @@ -148,60 +158,56 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc isOpen={isOpen} onClose={onClose} onOk={async () => { + let receipt: TransactionReceipt | undefined; try { if (activeKey === "update") { if (chain?.id !== sourceChain?.id) { switchNetwork?.(sourceChain?.id); } else if (bridgeCategory === "lnbridgev20-default") { setBusy(true); - const receipt = await setFeeAndRate(baseFee.formatted, feeRate.formatted); - if (receipt?.status === "success") { - onSuccess(); - onClose(); - } + receipt = await setFeeAndRate(baseFee.formatted, feeRate.formatted); } else if (bridgeCategory === "lnbridgev20-opposite") { setBusy(true); - const receipt = await updateFeeAndMargin(margin.formatted, baseFee.formatted, feeRate.formatted); - if (receipt?.status === "success") { - onSuccess(); - onClose(); - } + receipt = await updateFeeAndMargin(margin.formatted, baseFee.formatted, feeRate.formatted); } } else if (activeKey === "deposit") { if (bridgeCategory === "lnbridgev20-default") { if (chain?.id !== targetChain?.id) { switchNetwork?.(targetChain?.id); - } else if (sourceToken?.type !== "native" && margin.formatted > (targetAllowance?.value || 0n)) { + } else if (sourceToken?.type !== "native" && depositAmount.formatted > (targetAllowance?.value || 0n)) { setBusy(true); - await targetApprove(margin.formatted); + await targetApprove(depositAmount.formatted); } else { setBusy(true); - const receipt = await depositMargin(margin.formatted); - if (receipt?.status === "success") { - onSuccess(); - onClose(); - } + receipt = await depositMargin(depositAmount.formatted); } } else if (bridgeCategory === "lnbridgev20-opposite") { if (chain?.id !== sourceChain?.id) { switchNetwork?.(sourceChain?.id); - } else if (sourceToken?.type !== "native" && margin.formatted > (sourceAllowance?.value || 0n)) { + } else if (sourceToken?.type !== "native" && depositAmount.formatted > (sourceAllowance?.value || 0n)) { setBusy(true); - await sourceApprove(margin.formatted); + await sourceApprove(depositAmount.formatted); } else { setBusy(true); - const receipt = await updateFeeAndMargin(margin.formatted, baseFee.formatted, feeRate.formatted); - if (receipt?.status === "success") { - onSuccess(); - onClose(); - } + receipt = await updateFeeAndMargin(depositAmount.formatted, baseFee.formatted, feeRate.formatted); } } + } else if (activeKey === "withdraw") { + if (chain?.id !== sourceChain?.id) { + switchNetwork?.(sourceChain?.id); + } else { + setBusy(true); + receipt = await withdrawMargin(withdrawAmount.formatted); + } } } catch (err) { console.error(err); } finally { setBusy(false); + if (receipt?.status === "success") { + onSuccess(); + onClose(); + } } }} busy={busy} @@ -209,7 +215,9 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc disabledOk={ activeKey === "update" && !(baseFee.value && feeRate.value && isValidFeeRate(feeRate.formatted)) ? true - : activeKey === "deposit" && margin.formatted === 0n + : activeKey === "deposit" && depositAmount.formatted === 0n + ? true + : activeKey === "withdraw" && withdrawAmount.formatted === 0n ? true : false } @@ -262,7 +270,8 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc : undefined } suffix="symbol" - onChange={setMargin} + value={depositAmount} + onChange={setDepositAmount} /> ), @@ -288,10 +297,16 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc ), children: ( - + ), - disabled: true, + disabled: relayerInfo?.messageChannel !== "layerzero", }, ]} activeKey={activeKey} From 865c783a38c84294b0e61396c6c8ef90b066ac2b Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Fri, 10 Nov 2023 16:24:46 +0800 Subject: [PATCH 05/10] Relayer manage modal state reset --- packages/apps/src/components/relayer-manage-modal.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/apps/src/components/relayer-manage-modal.tsx b/packages/apps/src/components/relayer-manage-modal.tsx index 42ff21125..a39c5ded4 100644 --- a/packages/apps/src/components/relayer-manage-modal.tsx +++ b/packages/apps/src/components/relayer-manage-modal.tsx @@ -107,6 +107,9 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc setSourceChain(_sourceChain); setTargetChain(_targetChain); setSourceToken(_sourceToken); + setWithdrawAmount({ formatted: 0n, value: "" }); + setDepositAmount({ formatted: 0n, value: "" }); + setActiveKey("update"); }, [ relayerInfo, setBaseFee, From 143dd190461bebc09dc4102ee3b32fa2af596bfa Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Fri, 10 Nov 2023 17:42:18 +0800 Subject: [PATCH 06/10] Show withdraw fee and powered-by --- packages/apps/src/bridges/lnbridge-default.ts | 35 ++++++---- .../src/components/relayer-manage-modal.tsx | 65 +++++++++++++++---- .../apps/src/providers/relayer-provider.tsx | 6 +- packages/apps/src/ui/modal.tsx | 7 +- 4 files changed, 84 insertions(+), 29 deletions(-) diff --git a/packages/apps/src/bridges/lnbridge-default.ts b/packages/apps/src/bridges/lnbridge-default.ts index 3ab399e08..fc3a8710e 100644 --- a/packages/apps/src/bridges/lnbridge-default.ts +++ b/packages/apps/src/bridges/lnbridge-default.ts @@ -166,17 +166,8 @@ export class LnBridgeDefault extends LnBridgeBase { } } - async withdrawMargin(recipient: Address, amount: bigint) { - await this.validateNetwork("source"); - - if ( - this.contract && - this.sourceToken && - this.targetToken && - this.targetChain && - this.publicClient && - this.walletClient - ) { + async getWithdrawFee() { + if (this.contract && this.sourceNativeToken && this.targetChain && this.publicClient) { const bridgeAbi = (await import(`../abi/lnbridgev20-default`)).default; const accessAbi = (await import(`../abi/lnaccess-controller`)).default; const remoteChainId = BigInt(this.targetChain.id); @@ -194,13 +185,31 @@ export class LnBridgeDefault extends LnBridgeBase { args: [remoteChainId, bytesToHex(Uint8Array.from([123]), { size: 500 })], }); + return { fee: nativeFee, token: this.sourceNativeToken }; + } + } + + async withdrawMargin(recipient: Address, amount: bigint, fee: bigint) { + await this.validateNetwork("source"); + + if ( + this.contract && + this.sourceToken && + this.targetToken && + this.targetChain && + this.publicClient && + this.walletClient + ) { + const abi = (await import(`../abi/lnbridgev20-default`)).default; + const remoteChainId = BigInt(this.targetChain.id); + const hash = await this.walletClient.writeContract({ address: this.contract.sourceAddress, - abi: bridgeAbi, + abi, functionName: "requestWithdrawMargin", args: [remoteChainId, this.sourceToken.address, this.targetToken.address, amount, recipient], gas: this.getTxGasLimit(), - value: nativeFee, + value: fee, }); return this.publicClient.waitForTransactionReceipt({ hash }); } diff --git a/packages/apps/src/components/relayer-manage-modal.tsx b/packages/apps/src/components/relayer-manage-modal.tsx index a39c5ded4..ddb694ec2 100644 --- a/packages/apps/src/components/relayer-manage-modal.tsx +++ b/packages/apps/src/components/relayer-manage-modal.tsx @@ -12,6 +12,8 @@ import { useRelayer } from "@/hooks/use-relayer"; import dynamic from "next/dynamic"; import { formatFeeRate, isValidFeeRate } from "@/utils/misc"; import { TransactionReceipt } from "viem"; +import { Token } from "@/types/token"; +import { Subscription, from } from "rxjs"; type TabKey = "update" | "deposit" | "withdraw"; const Modal = dynamic(() => import("@/ui/modal"), { ssr: false }); @@ -36,6 +38,7 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc margin, baseFee, feeRate, + defaultBridge, setMargin, setBaseFee, setFeeRate, @@ -61,6 +64,7 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc formatted: 0n, value: "", }); + const [withdrawFee, setWithdrawFee] = useState<{ fee: bigint; token: Token }>(); const { chain } = useNetwork(); const { switchNetwork } = useSwitchNetwork(); @@ -121,6 +125,22 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc setBridgeCategory, ]); + useEffect(() => { + let sub$$: Subscription | undefined; + if (defaultBridge && relayerInfo?.messageChannel === "layerzero") { + sub$$ = from(defaultBridge.getWithdrawFee()).subscribe({ + next: setWithdrawFee, + error: (err) => { + console.error(err); + setWithdrawFee(undefined); + }, + }); + } else { + setWithdrawFee(undefined); + } + return () => sub$$?.unsubscribe(); + }, [defaultBridge, relayerInfo]); + const okText = useMemo(() => { let text = "Confirm"; if (activeKey === "deposit") { @@ -200,7 +220,7 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc switchNetwork?.(sourceChain?.id); } else { setBusy(true); - receipt = await withdrawMargin(withdrawAmount.formatted); + receipt = await withdrawMargin(withdrawAmount.formatted, withdrawFee?.fee ?? 0n); } } } catch (err) { @@ -220,10 +240,19 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc ? true : activeKey === "deposit" && depositAmount.formatted === 0n ? true - : activeKey === "withdraw" && withdrawAmount.formatted === 0n + : activeKey === "withdraw" && withdrawAmount.formatted === 0n && !withdrawFee ? true : false } + extra={ + activeKey === "withdraw" ? ( +
+ Powered by LayerZero & Helix +
+ ) : ( +
+ ) + } onCancel={onClose} > ), children: ( -
setHeight((prev) => node?.clientHeight || prev)}> +
@@ -299,15 +328,27 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc
), children: ( - - - +
setHeight((prev) => node?.clientHeight || prev)}> + + + + +
+ {withdrawFee ? ( + <> + {formatBalance(withdrawFee.fee, withdrawFee.token.decimals, { precision: 6 })} + {withdrawFee.token.symbol} + + ) : null} +
+
+
), disabled: relayerInfo?.messageChannel !== "layerzero", }, diff --git a/packages/apps/src/providers/relayer-provider.tsx b/packages/apps/src/providers/relayer-provider.tsx index 73e787206..8dbf8b119 100644 --- a/packages/apps/src/providers/relayer-provider.tsx +++ b/packages/apps/src/providers/relayer-provider.tsx @@ -50,7 +50,7 @@ interface RelayerCtx { depositMargin: (margin: bigint) => Promise; updateFeeAndMargin: (margin: bigint, baseFee: bigint, feeRate: number) => Promise; setFeeAndRate: (baseFee: bigint, feeRate: number) => Promise; - withdrawMargin: (amount: bigint) => Promise; + withdrawMargin: (amount: bigint, fee: bigint) => Promise; } const defaultValue: RelayerCtx = { @@ -239,10 +239,10 @@ export default function RelayerProvider({ children }: PropsWithChildren ); const withdrawMargin = useCallback( - async (amount: bigint) => { + async (amount: bigint, fee: bigint) => { if (address && defaultBridge) { try { - const receipt = await defaultBridge.withdrawMargin(address, amount); + const receipt = await defaultBridge.withdrawMargin(address, amount, fee); notifyTransaction(receipt, sourceChain); return receipt; } catch (err) { diff --git a/packages/apps/src/ui/modal.tsx b/packages/apps/src/ui/modal.tsx index f3922d063..e21499b91 100644 --- a/packages/apps/src/ui/modal.tsx +++ b/packages/apps/src/ui/modal.tsx @@ -15,6 +15,7 @@ interface Props { disabledCancel?: boolean; disabledOk?: boolean; busy?: boolean; + extra?: ReactElement | null; onClose?: () => void; onCancel?: () => void; onOk?: () => void; @@ -32,6 +33,7 @@ export default function Modal({ disabledCancel, disabledOk, busy, + extra, onClose = () => undefined, onCancel, onOk, @@ -91,7 +93,10 @@ export default function Modal({ {/* footer */} {(onCancel || onOk) && ( <> -
+
+ {extra} +
+
{onCancel && ( From 931e1522908b0a894ad7c766d779549f8de4a176 Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Tue, 14 Nov 2023 15:34:00 +0800 Subject: [PATCH 07/10] Improve withdraw interaction --- packages/apps/src/bridges/lnbridge-default.ts | 6 +++--- .../apps/src/components/relayer-manage-modal.tsx | 14 +++++++++++--- packages/apps/src/config/chains/mantle.ts | 9 +++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/apps/src/bridges/lnbridge-default.ts b/packages/apps/src/bridges/lnbridge-default.ts index fc3a8710e..253f9fd4b 100644 --- a/packages/apps/src/bridges/lnbridge-default.ts +++ b/packages/apps/src/bridges/lnbridge-default.ts @@ -167,18 +167,18 @@ export class LnBridgeDefault extends LnBridgeBase { } async getWithdrawFee() { - if (this.contract && this.sourceNativeToken && this.targetChain && this.publicClient) { + if (this.contract && this.sourceNativeToken && this.targetChain && this.sourcePublicClient) { const bridgeAbi = (await import(`../abi/lnbridgev20-default`)).default; const accessAbi = (await import(`../abi/lnaccess-controller`)).default; const remoteChainId = BigInt(this.targetChain.id); - const [sendService, _receiveService] = await this.publicClient.readContract({ + const [sendService, _receiveService] = await this.sourcePublicClient.readContract({ address: this.contract.sourceAddress, abi: bridgeAbi, functionName: "messagers", args: [remoteChainId], }); - const [nativeFee, _zroFee] = await this.publicClient.readContract({ + const [nativeFee, _zroFee] = await this.sourcePublicClient.readContract({ address: sendService, abi: accessAbi, functionName: "fee", diff --git a/packages/apps/src/components/relayer-manage-modal.tsx b/packages/apps/src/components/relayer-manage-modal.tsx index ddb694ec2..d36290635 100644 --- a/packages/apps/src/components/relayer-manage-modal.tsx +++ b/packages/apps/src/components/relayer-manage-modal.tsx @@ -240,7 +240,7 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc ? true : activeKey === "deposit" && depositAmount.formatted === 0n ? true - : activeKey === "withdraw" && withdrawAmount.formatted === 0n && !withdrawFee + : activeKey === "withdraw" && (withdrawAmount.formatted === 0n || !withdrawFee) ? true : false } @@ -339,13 +339,21 @@ export default function RelayerManageModal({ relayerInfo, isOpen, onClose, onSuc /> -
+
{withdrawFee ? ( <> {formatBalance(withdrawFee.fee, withdrawFee.token.decimals, { precision: 6 })} {withdrawFee.token.symbol} - ) : null} + ) : ( + + * Failed to get fee, withdraw is temporarily unavailable + + )}
diff --git a/packages/apps/src/config/chains/mantle.ts b/packages/apps/src/config/chains/mantle.ts index 2df4b154f..6490795ea 100644 --- a/packages/apps/src/config/chains/mantle.ts +++ b/packages/apps/src/config/chains/mantle.ts @@ -27,6 +27,15 @@ export const mantleChain: ChainConfig = { }, }, tokens: [ + { + decimals: 18, + symbol: "MNT", + name: "MNT", + type: "native", + address: "0x0000000000000000000000000000000000000000", + logo: "mnt.svg", + cross: [], + }, { decimals: 6, symbol: "USDT", From ca2ccffe482a7de939eca4d6616b784e79342395 Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Tue, 14 Nov 2023 15:53:07 +0800 Subject: [PATCH 08/10] Native token config test --- .../apps/src/__tests__/native-token-config.spec.ts | 10 ++++++++++ packages/apps/src/config/chains/pangolin.ts | 12 +----------- 2 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 packages/apps/src/__tests__/native-token-config.spec.ts diff --git a/packages/apps/src/__tests__/native-token-config.spec.ts b/packages/apps/src/__tests__/native-token-config.spec.ts new file mode 100644 index 000000000..01bd19b18 --- /dev/null +++ b/packages/apps/src/__tests__/native-token-config.spec.ts @@ -0,0 +1,10 @@ +import { getChainsConfig } from "../utils/chain"; +import type { ChainConfig } from "../types/chain"; + +describe.each(getChainsConfig() as ChainConfig[])("Should configure native token", ({ network, tokens }) => { + it(`${network}`, () => { + if (tokens.length) { + expect(tokens.some((t) => t.type === "native")).not.toBeFalsy(); + } + }); +}); diff --git a/packages/apps/src/config/chains/pangolin.ts b/packages/apps/src/config/chains/pangolin.ts index f8f18d7d8..013d99cce 100644 --- a/packages/apps/src/config/chains/pangolin.ts +++ b/packages/apps/src/config/chains/pangolin.ts @@ -27,15 +27,5 @@ export const pangolinChain: ChainConfig = { }, }, testnet: true, - tokens: [ - { - decimals: 18, - symbol: "PRING", - name: "PRING", - type: "erc20", - address: "0x3F3eDBda6124462a09E071c5D90e072E0d5d4ed4", - logo: "ring.svg", - cross: [], - }, - ], + tokens: [], }; From 9be4a6bc9446e167bad49c7cf229481bd5038ed0 Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Tue, 14 Nov 2023 16:11:38 +0800 Subject: [PATCH 09/10] Improve unit test --- .../apps/src/__tests__/cross-config.spec.ts | 18 ++++++++++++++++-- .../src/__tests__/native-token-config.spec.ts | 2 +- packages/apps/src/config/chains/polygon.ts | 9 +++++++++ packages/apps/src/config/chains/scroll.ts | 9 +++++++++ packages/apps/src/config/chains/zksync.ts | 9 +++++++++ packages/apps/src/utils/chain.ts | 6 ++++-- 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/packages/apps/src/__tests__/cross-config.spec.ts b/packages/apps/src/__tests__/cross-config.spec.ts index 6246f8acb..0fee6c2c2 100644 --- a/packages/apps/src/__tests__/cross-config.spec.ts +++ b/packages/apps/src/__tests__/cross-config.spec.ts @@ -1,7 +1,7 @@ -import { getChainsConfig } from "../utils/chain"; +import { getChainsConfig, getChainConfig } from "../utils/chain"; import type { ChainConfig } from "../types/chain"; -describe.each(getChainsConfig() as ChainConfig[])( +describe.each(getChainsConfig("all") as ChainConfig[])( "Should configure price for HelixLpBridge to cross native token", ({ network, tokens }) => { if (tokens.length) { @@ -17,3 +17,17 @@ describe.each(getChainsConfig() as ChainConfig[])( } }, ); + +describe.each(getChainsConfig("all") as ChainConfig[])("Should configure target token", ({ network, tokens }) => { + if (tokens.length) { + describe.each(tokens)(`Cross $symbol from ${network}`, (token) => { + if (token.cross.length) { + it.each(token.cross)(`to $target.network ($target.symbol)`, (cross) => { + const targetChain = getChainConfig(cross.target.network) as ChainConfig | undefined; + const targetToken = targetChain?.tokens.find((t) => t.symbol === cross.target.symbol); + expect(targetToken).not.toBeUndefined(); + }); + } + }); + } +}); diff --git a/packages/apps/src/__tests__/native-token-config.spec.ts b/packages/apps/src/__tests__/native-token-config.spec.ts index 01bd19b18..cbd216ac9 100644 --- a/packages/apps/src/__tests__/native-token-config.spec.ts +++ b/packages/apps/src/__tests__/native-token-config.spec.ts @@ -1,7 +1,7 @@ import { getChainsConfig } from "../utils/chain"; import type { ChainConfig } from "../types/chain"; -describe.each(getChainsConfig() as ChainConfig[])("Should configure native token", ({ network, tokens }) => { +describe.each(getChainsConfig("all") as ChainConfig[])("Should configure native token", ({ network, tokens }) => { it(`${network}`, () => { if (tokens.length) { expect(tokens.some((t) => t.type === "native")).not.toBeFalsy(); diff --git a/packages/apps/src/config/chains/polygon.ts b/packages/apps/src/config/chains/polygon.ts index beb92a162..28d4b04e9 100644 --- a/packages/apps/src/config/chains/polygon.ts +++ b/packages/apps/src/config/chains/polygon.ts @@ -27,6 +27,15 @@ export const polygonChain: ChainConfig = { }, }, tokens: [ + { + decimals: 18, + symbol: "MATIC", + name: "MATIC", + type: "native", + address: "0x0000000000000000000000000000000000000000", + logo: "matic.svg", + cross: [], + }, { decimals: 18, symbol: "RING", diff --git a/packages/apps/src/config/chains/scroll.ts b/packages/apps/src/config/chains/scroll.ts index 45675654d..167860040 100644 --- a/packages/apps/src/config/chains/scroll.ts +++ b/packages/apps/src/config/chains/scroll.ts @@ -27,6 +27,15 @@ export const scrollChain: ChainConfig = { }, }, tokens: [ + { + decimals: 18, + symbol: "ETH", + name: "ETH", + type: "native", + address: "0x0000000000000000000000000000000000000000", + logo: "eth.svg", + cross: [], + }, { decimals: 6, symbol: "USDT", diff --git a/packages/apps/src/config/chains/zksync.ts b/packages/apps/src/config/chains/zksync.ts index 551bea41c..c23059a83 100644 --- a/packages/apps/src/config/chains/zksync.ts +++ b/packages/apps/src/config/chains/zksync.ts @@ -27,6 +27,15 @@ export const zksyncChain: ChainConfig = { }, }, tokens: [ + { + decimals: 18, + symbol: "ETH", + name: "ETH", + type: "native", + address: "0x0000000000000000000000000000000000000000", + logo: "eth.svg", + cross: [], + }, { decimals: 6, symbol: "USDT", diff --git a/packages/apps/src/utils/chain.ts b/packages/apps/src/utils/chain.ts index 0e9a0270d..aeaecfd8c 100644 --- a/packages/apps/src/utils/chain.ts +++ b/packages/apps/src/utils/chain.ts @@ -76,7 +76,7 @@ export function getChainConfig(chainIdOrNetwork?: ChainID | Network | null): Cha } } -export function getChainsConfig() { +export function getChainsConfig(env?: "all") { const all = [ arbitrumChain, arbitrumGoerliChain, @@ -97,7 +97,9 @@ export function getChainsConfig() { scrollChain, ]; - if (isProduction()) { + if (env === "all") { + return all; + } else if (isProduction()) { return all.filter((c) => !c.hidden && !c.testnet); } else { return all.filter((c) => !c.hidden && !!c.testnet); From 9e47b5210647970bf269aedfc3e4b875feb4ecee Mon Sep 17 00:00:00 2001 From: JayJay1024 Date: Tue, 14 Nov 2023 16:22:31 +0800 Subject: [PATCH 10/10] Fix ci failed --- packages/apps/src/config/chains/base-goerli.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/apps/src/config/chains/base-goerli.ts b/packages/apps/src/config/chains/base-goerli.ts index e151e1f46..7827484ff 100644 --- a/packages/apps/src/config/chains/base-goerli.ts +++ b/packages/apps/src/config/chains/base-goerli.ts @@ -28,6 +28,15 @@ export const baseGoerliChain: ChainConfig = { }, testnet: true, tokens: [ + { + decimals: 18, + symbol: "ETH", + name: "ETH", + type: "native", + address: "0x0000000000000000000000000000000000000000", + logo: "eth.svg", + cross: [], + }, { decimals: 18, symbol: "USDT",