diff --git a/apps/web/.env b/apps/web/.env index dedd618d..92c39a72 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -1 +1 @@ -VITE_WALLET_CONNECT_ID=2719448e2ce94fdd269a3c8587123bcc +VITE_WALLET_CONNECT_PROJECT_ID=d155df487dbbf1140fa0418387f85fe1 diff --git a/apps/web/package.json b/apps/web/package.json index e7147501..3e7b6ecc 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,9 +20,11 @@ "@apollo/client": "^3.10.3", "@floating-ui/react": "^0.26.14", "@helixbridge/helixconf": "1.1.15", - "@rainbow-me/rainbowkit": "^1.3.7", + "@reown/appkit": "^1.0.7", + "@reown/appkit-adapter-wagmi": "^1.0.7", "@sentry/react": "^8.30.0", "@sentry/vite-plugin": "^2.22.4", + "@tanstack/react-query": "^5.59.0", "date-fns": "^3.6.0", "date-fns-tz": "^3.1.3", "graphql": "^16.8.1", @@ -35,8 +37,8 @@ "react-transition-group": "^4.4.5", "rxjs": "^7.8.1", "sort-by": "^0.0.2", - "viem": "^1.21.4", - "wagmi": "^1.4.13" + "viem": "^2.21.19", + "wagmi": "^2.12.17" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/apps/web/src/bridges/base.ts b/apps/web/src/bridges/base.ts index 4d93c985..b9e883b6 100644 --- a/apps/web/src/bridges/base.ts +++ b/apps/web/src/bridges/base.ts @@ -12,8 +12,15 @@ import { TransferOptions, } from "../types"; import { getBalance } from "../utils"; -import { Address, PublicClient as ViemPublicClient, TransactionReceipt, createPublicClient, http } from "viem"; -import { PublicClient as WagmiPublicClient, WalletClient } from "wagmi"; +import { + Address, + PublicClient as ViemPublicClient, + TransactionReceipt, + createPublicClient, + http, + PublicClient, + WalletClient, +} from "viem"; import { CONFIRMATION_BLOCKS } from "../config"; export abstract class BaseBridge { @@ -35,7 +42,7 @@ export abstract class BaseBridge { protected readonly sourcePublicClient: ViemPublicClient | undefined; protected readonly targetPublicClient: ViemPublicClient | undefined; - protected readonly publicClient?: WagmiPublicClient; // The public client to which the wallet is connected + protected readonly publicClient?: PublicClient; // The public client to which the wallet is connected protected readonly walletClient?: WalletClient | null; constructor(args: BridgeConstructorArgs) { diff --git a/apps/web/src/bridges/lnbridge-v3.ts b/apps/web/src/bridges/lnbridge-v3.ts index 60aec03f..983be5bc 100644 --- a/apps/web/src/bridges/lnbridge-v3.ts +++ b/apps/web/src/bridges/lnbridge-v3.ts @@ -54,7 +54,8 @@ export class LnBridgeV3 extends LnBridgeBase { if (askEstimateGas) { return this.sourcePublicClient.estimateContractGas(defaultParams); } else if (this.walletClient) { - const hash = await this.walletClient.writeContract(defaultParams); + const { request } = await this.sourcePublicClient.simulateContract(defaultParams); + const hash = await this.walletClient.writeContract(request); return this.sourcePublicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } @@ -102,8 +103,10 @@ export class LnBridgeV3 extends LnBridgeBase { async registerLnProvider(baseFee: bigint, feeRate: number, transferLimit: bigint) { await this.validateNetwork("source"); + const account = await this.getSigner(); if ( + account && this.contract && this.publicClient && this.walletClient && @@ -111,7 +114,7 @@ export class LnBridgeV3 extends LnBridgeBase { this.sourceToken && this.targetToken ) { - const hash = await this.walletClient.writeContract({ + const { request } = await this.publicClient.simulateContract({ address: this.contract.sourceAddress, abi: (await import("../abi/lnbridge-v3")).default, functionName: "registerLnProvider", @@ -124,37 +127,46 @@ export class LnBridgeV3 extends LnBridgeBase { transferLimit, ], gas: this.getTxGasLimit(), + account, }); + const hash = await this.walletClient.writeContract(request); return this.publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } async depositPenaltyReserve(amount: bigint) { await this.validateNetwork("source"); + const account = await this.getSigner(); - if (this.contract && this.publicClient && this.walletClient && this.sourceToken) { - const hash = await this.walletClient.writeContract({ + if (account && this.contract && this.publicClient && this.walletClient && this.sourceToken) { + const { request } = await this.publicClient.simulateContract({ address: this.contract.sourceAddress, abi: (await import("../abi/lnbridge-v3")).default, functionName: "depositPenaltyReserve", args: [this.sourceToken.address, amount], value: this.sourceToken.type === "native" ? amount : undefined, gas: this.getTxGasLimit(), + account, }); + const hash = await this.walletClient.writeContract(request); return this.publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } async withdrawPenaltyReserve(amount: bigint) { await this.validateNetwork("source"); + const account = await this.getSigner(); - if (this.contract && this.sourceToken && this.publicClient && this.walletClient) { - const hash = await this.walletClient.writeContract({ + if (account && this.contract && this.sourceToken && this.publicClient && this.walletClient) { + const { request } = await this.publicClient.simulateContract({ address: this.contract.sourceAddress, abi: (await import("../abi/lnbridge-v3")).default, functionName: "withdrawPenaltyReserve", args: [this.sourceToken.address, amount], + gas: this.getTxGasLimit(), + account, }); + const hash = await this.walletClient.writeContract(request); return this.publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } @@ -197,18 +209,21 @@ export class LnBridgeV3 extends LnBridgeBase { async requestWithdrawLiquidity(relayer: Address, transferIds: Hex[], messageFee: bigint, extParams: Hex) { await this.validateNetwork("target"); + const account = await this.getSigner(); - if (this.contract && this.sourceChain && this.publicClient && this.walletClient) { + if (account && this.contract && this.sourceChain && this.publicClient && this.walletClient) { const remoteChainId = BigInt(this.sourceChain.id); - const hash = await this.walletClient.writeContract({ + const { request } = await this.publicClient.simulateContract({ address: this.contract.targetAddress, abi: (await import("../abi/lnbridge-v3")).default, functionName: "requestWithdrawLiquidity", args: [remoteChainId, transferIds, relayer, extParams], value: messageFee, gas: this.getTxGasLimit(), + account, }); + const hash = await this.walletClient.writeContract(request); return this.publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } diff --git a/apps/web/src/bridges/lnv2-default.ts b/apps/web/src/bridges/lnv2-default.ts index 9fd4760d..7981ca08 100644 --- a/apps/web/src/bridges/lnv2-default.ts +++ b/apps/web/src/bridges/lnv2-default.ts @@ -53,7 +53,8 @@ export class LnBridgeV2Default extends LnBridgeBase { if (askEstimateGas) { return this.sourcePublicClient.estimateContractGas(defaultParams); } else if (this.walletClient) { - const hash = await this.walletClient.writeContract(defaultParams); + const { request } = await this.sourcePublicClient.simulateContract(defaultParams); + const hash = await this.walletClient.writeContract(request); return this.sourcePublicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } @@ -61,8 +62,10 @@ export class LnBridgeV2Default extends LnBridgeBase { async depositMargin(margin: bigint) { await this.validateNetwork("target"); + const account = await this.getSigner(); if ( + account && this.contract && this.sourceChain && this.sourceToken && @@ -70,22 +73,26 @@ export class LnBridgeV2Default extends LnBridgeBase { this.publicClient && this.walletClient ) { - const hash = await this.walletClient.writeContract({ + const { request } = await this.publicClient.simulateContract({ address: this.contract.targetAddress, abi: (await import(`../abi/lnv2-default`)).default, functionName: "depositProviderMargin", args: [BigInt(this.sourceChain.id), this.sourceToken.address, this.targetToken.address, margin], value: this.targetToken.type === "native" ? margin : undefined, gas: this.getTxGasLimit(), + account, }); + const hash = await this.walletClient.writeContract(request); return this.publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } async setFeeAndRate(baseFee: bigint, feeRate: number) { await this.validateNetwork("source"); + const account = await this.getSigner(); if ( + account && this.contract && this.targetChain && this.sourceToken && @@ -93,21 +100,25 @@ export class LnBridgeV2Default extends LnBridgeBase { this.publicClient && this.walletClient ) { - const hash = await this.walletClient.writeContract({ + const { request } = await this.publicClient.simulateContract({ address: this.contract.sourceAddress, abi: (await import(`../abi/lnv2-default`)).default, functionName: "setProviderFee", args: [BigInt(this.targetChain.id), this.sourceToken.address, this.targetToken.address, baseFee, feeRate], gas: this.getTxGasLimit(), + account, }); + const hash = await this.walletClient.writeContract(request); return this.publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } async withdrawMargin(recipientOrParams: Address | Hex, amount: bigint, fee: bigint) { await this.validateNetwork("source"); + const account = await this.getSigner(); if ( + account && this.contract && this.sourceToken && this.targetToken && @@ -117,14 +128,16 @@ export class LnBridgeV2Default extends LnBridgeBase { ) { const remoteChainId = BigInt(this.targetChain.id); - const hash = await this.walletClient.writeContract({ + const { request } = await this.publicClient.simulateContract({ address: this.contract.sourceAddress, abi: (await import(`../abi/lnv2-default`)).default, functionName: "requestWithdrawMargin", args: [remoteChainId, this.sourceToken.address, this.targetToken.address, amount, recipientOrParams], gas: this.getTxGasLimit(), value: fee, + account, }); + const hash = await this.walletClient.writeContract(request); return this.publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } diff --git a/apps/web/src/bridges/lnv2-opposite.ts b/apps/web/src/bridges/lnv2-opposite.ts index 913c466d..9de440b9 100644 --- a/apps/web/src/bridges/lnv2-opposite.ts +++ b/apps/web/src/bridges/lnv2-opposite.ts @@ -53,7 +53,8 @@ export class LnBridgeV2Opposite extends LnBridgeBase { if (askEstimateGas) { return this.sourcePublicClient.estimateContractGas(defaultParams); } else if (this.walletClient) { - const hash = await this.walletClient.writeContract(defaultParams); + const { request } = await this.sourcePublicClient.simulateContract(defaultParams); + const hash = await this.walletClient.writeContract(request); return this.sourcePublicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } @@ -61,8 +62,10 @@ export class LnBridgeV2Opposite extends LnBridgeBase { async updateFeeAndMargin(margin: bigint, baseFee: bigint, feeRate: number) { await this.validateNetwork("source"); + const account = await this.getSigner(); if ( + account && this.contract && this.targetChain && this.sourceToken && @@ -70,7 +73,7 @@ export class LnBridgeV2Opposite extends LnBridgeBase { this.publicClient && this.walletClient ) { - const hash = await this.walletClient.writeContract({ + const { request } = await this.publicClient.simulateContract({ address: this.contract.sourceAddress, abi: (await import(`../abi/lnv2-opposite`)).default, functionName: "updateProviderFeeAndMargin", @@ -84,7 +87,9 @@ export class LnBridgeV2Opposite extends LnBridgeBase { ], value: this.sourceToken.type === "native" ? margin : undefined, gas: this.getTxGasLimit(), + account, }); + const hash = await this.walletClient.writeContract(request); return this.publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); } } diff --git a/apps/web/src/components/chain-switch.tsx b/apps/web/src/components/chain-switch.tsx index 7d92fdaf..9667cac7 100644 --- a/apps/web/src/components/chain-switch.tsx +++ b/apps/web/src/components/chain-switch.tsx @@ -10,7 +10,7 @@ import { useTransitionStyles, } from "@floating-ui/react"; import { useMemo, useState } from "react"; -import { useAccount, useNetwork, useSwitchNetwork } from "wagmi"; +import { useAccount, useSwitchChain } from "wagmi"; const chainOptions = getChainConfigs(); @@ -32,9 +32,8 @@ export default function ChainSwitch({ placement }: { placement?: Placement }) { const click = useClick(context); const dismiss = useDismiss(context); const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]); - const { switchNetwork } = useSwitchNetwork(); - const { chain } = useNetwork(); - const activeChain = useMemo(() => getChainConfig(chain?.id), [chain?.id]); + const { switchChain } = useSwitchChain(); + const activeChain = useMemo(() => getChainConfig(account.chainId), [account.chainId]); return account.address ? ( <> @@ -80,9 +79,9 @@ export default function ChainSwitch({ placement }: { placement?: Placement }) { {chainOptions.map((option) => ( diff --git a/apps/web/src/hooks/use-allowance.ts b/apps/web/src/hooks/use-allowance.ts index 709c4b3a..2e5cdacd 100644 --- a/apps/web/src/hooks/use-allowance.ts +++ b/apps/web/src/hooks/use-allowance.ts @@ -48,15 +48,17 @@ export function useAllowance( const approve = useCallback( async (amount: bigint) => { - if (owner && spender && walletClient) { + if (owner && spender && walletClient && publicClient) { setBusy(true); try { - const hash = await walletClient.writeContract({ + const { request } = await publicClient.simulateContract({ address: token.address, abi, functionName: "approve", args: [spender, amount], + account: owner, }); + const hash = await walletClient.writeContract(request); const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: CONFIRMATION_BLOCKS }); if (receipt.status === "success") { diff --git a/apps/web/src/hooks/use-balance-all.ts b/apps/web/src/hooks/use-balance-all.ts index 50c942c8..253e0a4d 100644 --- a/apps/web/src/hooks/use-balance-all.ts +++ b/apps/web/src/hooks/use-balance-all.ts @@ -1,6 +1,6 @@ import { getChainConfigs } from "../utils"; import { useCallback, useEffect, useRef, useState } from "react"; -import { createPublicClient, getContract, http } from "viem"; +import { createPublicClient, http } from "viem"; import { forkJoin, map, of, merge, mergeAll } from "rxjs"; import abi from "../abi/erc20"; import { ChainConfig, ChainID, Token } from "../types"; @@ -30,8 +30,12 @@ export function useBalanceAll() { if (token.type === "native") { return publicClient.getBalance({ address }); } else { - const contract = getContract({ address: token.address, abi, publicClient }); - return contract.read.balanceOf([address]); + return publicClient.readContract({ + address: token.address, + abi, + functionName: "balanceOf", + args: [address], + }); } }); return tokensObs.length diff --git a/apps/web/src/providers/rainbow-provider.tsx b/apps/web/src/providers/rainbow-provider.tsx deleted file mode 100644 index 54ec02af..00000000 --- a/apps/web/src/providers/rainbow-provider.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import "@rainbow-me/rainbowkit/styles.css"; -import { RainbowKitProvider, connectorsForWallets, getDefaultWallets, darkTheme } from "@rainbow-me/rainbowkit"; -import { okxWallet, rabbyWallet, safeWallet, talismanWallet } from "@rainbow-me/rainbowkit/wallets"; -import { PropsWithChildren } from "react"; -import { WagmiConfig, configureChains, createConfig } from "wagmi"; -import { publicProvider } from "wagmi/providers/public"; -import { getChainConfigs } from "../utils"; - -const projectId = import.meta.env.VITE_WALLET_CONNECT_ID || ""; -const appName = "Helix Bridge"; - -const { chains, publicClient } = configureChains( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getChainConfigs().map(({ tokens, ...chain }) => chain), - [publicProvider()], -); - -const { wallets } = getDefaultWallets({ appName, projectId, chains }); - -const connectors = connectorsForWallets([ - ...wallets, - { - groupName: "More", - wallets: [ - okxWallet({ chains, projectId }), - rabbyWallet({ chains }), - talismanWallet({ chains }), - safeWallet({ chains }), - ], - }, -]); - -const wagmiConfig = createConfig({ - autoConnect: true, - connectors, - publicClient, -}); - -export default function RainbowProvider({ children }: PropsWithChildren) { - return ( - - - {children} - - - ); -} diff --git a/apps/web/src/providers/walletconnect-provider.tsx b/apps/web/src/providers/walletconnect-provider.tsx new file mode 100644 index 00000000..d0d05598 --- /dev/null +++ b/apps/web/src/providers/walletconnect-provider.tsx @@ -0,0 +1,66 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { PropsWithChildren } from "react"; +import { getChainConfigs } from "../utils"; +import { WagmiAdapter } from "@reown/appkit-adapter-wagmi"; +import { CaipNetwork, createAppKit } from "@reown/appkit/react"; +import { WagmiProvider } from "wagmi"; + +// 0. Setup queryClient +const queryClient = new QueryClient(); + +// 1. Get projectId from https://cloud.reown.com +const projectId = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID || ""; + +// 2. Create a metadata object - optional +const metadata = { + name: "Helix Bridge", + description: "Secure, fast, and low-cost cross-chain crypto transfers", + url: "https://helixbridge.app", // origin must match your domain & subdomain + icons: ["https://helixbridge.app/pwa-512x512.png"], +}; + +// 3. Set the networks +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const networks: CaipNetwork[] = getChainConfigs().map(({ tokens, ...chain }) => ({ + id: `eip155:${chain.id}`, + chainId: chain.id, + chainNamespace: "eip155", + name: chain.name, + currency: chain.nativeCurrency.symbol, + explorerUrl: chain.blockExplorers?.default?.url || "", + rpcUrl: chain.rpcUrls.default.http[0], + imageUrl: chain.logo, +})); + +// 4. Create Wagmi Adapter +const wagmiAdapter = new WagmiAdapter({ + networks, + projectId, + ssr: false, +}); + +// 5. Create modal +createAppKit({ + adapters: [wagmiAdapter], + networks, + projectId, + metadata, + features: { + socials: false, + swaps: false, + onramp: false, + analytics: true, // Optional - defaults to your Cloud configuration + }, + themeMode: "dark", + themeVariables: { + "--w3m-accent": "#0085FF", + }, +}); + +export default function WalletConnectProvider({ children }: PropsWithChildren) { + return ( + + {children} + + ); +} diff --git a/apps/web/src/routes/root.tsx b/apps/web/src/routes/root.tsx index c0a04d7e..c745474d 100644 --- a/apps/web/src/routes/root.tsx +++ b/apps/web/src/routes/root.tsx @@ -2,19 +2,19 @@ import Footer from "../components/footer"; import Header from "../components/header"; import AppProvider from "../providers/app-provider"; import GraphqlProvider from "../providers/graphql-provider"; -import RainbowProvider from "../providers/rainbow-provider"; import { Outlet } from "react-router-dom"; +import WalletConnectProvider from "../providers/walletconnect-provider"; export default function Root() { return ( - +