From a0f32132e07e2f1d54e0b2b939ae25a3a085ae52 Mon Sep 17 00:00:00 2001 From: Godswill Idolor Date: Sun, 24 Aug 2025 18:18:40 +0100 Subject: [PATCH] feat: integrate depositAsset bridge functionality for ETH and ERC20 tokens - Add depositAsset contract integration in Ethereum provider - Implement automatic ERC20 token approval handling - Support ETH deposits with msg.value parameter - Add bridge contract configuration for Sepolia testnet - Capture and store commitment hash for L2 operations - Update deposit and claim-burn components with new functionality - Add comprehensive error handling and user feedback - Store transaction details in localStorage for cross-chain tracking Closes #148 --- app/components/Ethereum-provider.tsx | 99 +++++++++++++++++++++++ app/components/claim-burn.tsx | 28 +++++-- app/components/deposit.tsx | 117 ++++++++++++++++++--------- app/config.ts | 13 ++- 4 files changed, 210 insertions(+), 47 deletions(-) diff --git a/app/components/Ethereum-provider.tsx b/app/components/Ethereum-provider.tsx index bf9e3f3..c72768b 100644 --- a/app/components/Ethereum-provider.tsx +++ b/app/components/Ethereum-provider.tsx @@ -2,6 +2,14 @@ import { createContext, useContext, useEffect, useState } from "react"; import { ethers } from "ethers"; +import { BRIDGE_CONTRACTS } from "../config"; + +interface DepositResult { + success: boolean; + commitmentHash?: string; + transactionHash?: string; + error?: string; +} interface EthereumContextType { provider: ethers.BrowserProvider | null; @@ -9,6 +17,7 @@ interface EthereumContextType { isConnected: boolean; connectWallet: () => Promise; address: string | null; + depositAsset: (assetType: number, tokenAddress: string, amount: string) => Promise; } const EthereumContext = createContext({ @@ -17,6 +26,7 @@ const EthereumContext = createContext({ isConnected: false, connectWallet: async () => {}, address: null, + depositAsset: async () => ({ success: false, error: "Not implemented" }), }); export const EthereumProvider = ({ children }: { children: React.ReactNode }) => { @@ -45,6 +55,94 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) => } }; + const depositAsset = async ( + assetType: number, + tokenAddress: string, + amount: string + ): Promise => { + try { + if (!signer || !provider || !address) { + return { success: false, error: "Wallet not connected" }; + } + + const network = await provider.getNetwork(); + const bridgeAddress = BRIDGE_CONTRACTS[Number(network.chainId)]; + + if (!bridgeAddress) { + return { success: false, error: `Bridge not supported on network ${network.chainId}` }; + } + + // Bridge contract ABI (simplified - includes only depositAsset function) + const bridgeABI = [ + "function depositAsset(uint256 assetType, address tokenAddress, uint256 amount, address user) external payable returns (bytes32)" + ]; + + const bridgeContract = new ethers.Contract(bridgeAddress, bridgeABI, signer); + const amountInWei = ethers.parseEther(amount); + + let tx; + + if (assetType === 0) { + // ETH deposit + tx = await bridgeContract.depositAsset( + assetType, + tokenAddress, + amountInWei, + address, + { value: amountInWei } + ); + } else { + // ERC20 deposit - first check and handle approval + const erc20ABI = [ + "function allowance(address owner, address spender) view returns (uint256)", + "function approve(address spender, uint256 amount) returns (bool)" + ]; + + const tokenContract = new ethers.Contract(tokenAddress, erc20ABI, signer); + const allowance = await tokenContract.allowance(address, bridgeAddress); + + if (allowance < amountInWei) { + const approvalTx = await tokenContract.approve(bridgeAddress, amountInWei); + await approvalTx.wait(); + } + + tx = await bridgeContract.depositAsset(assetType, tokenAddress, amountInWei, address); + } + + const receipt = await tx.wait(); + + // Extract commitment hash from logs + let commitmentHash: string | undefined; + for (const log of receipt.logs) { + try { + const parsedLog = bridgeContract.interface.parseLog({ + topics: log.topics, + data: log.data, + }); + + if (parsedLog && parsedLog.args && parsedLog.args.length > 0) { + commitmentHash = parsedLog.args[0]; + break; + } + } catch (error) { + console.warn("Could not parse log:", error); + } + } + + return { + success: true, + commitmentHash, + transactionHash: receipt.hash, + }; + } catch (error) { + console.error("Deposit error:", error); + return { + success: false, + error: error instanceof Error ? error.message : "Unknown error occurred", + }; + } + }; + useEffect(() => { const checkConnection = async () => { if (typeof window.ethereum !== "undefined") { @@ -87,6 +185,7 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) => isConnected, connectWallet, address, + depositAsset, }} > {children} diff --git a/app/components/claim-burn.tsx b/app/components/claim-burn.tsx index 50ecd1a..97b917c 100644 --- a/app/components/claim-burn.tsx +++ b/app/components/claim-burn.tsx @@ -73,7 +73,7 @@ const XZBInterface: React.FC = ({ const { isConnected: isEthereumConnected, // connect: connectEthereum, - // depositAsset, + depositAsset, // claimTokens, address: ethereumAddress, provider @@ -214,13 +214,27 @@ const XZBInterface: React.FC = ({ }); // Call depositAsset function - // const txHash = await depositAsset( - // assetType, - // tokenAddress, - // amount - // ); + const result = await depositAsset( + assetType, + tokenAddress, + amount + ); + + if (!result.success) { + throw new Error(result.error || "Deposit failed"); + } + + console.log("Transaction successful:", { + transactionHash: result.transactionHash, + commitmentHash: result.commitmentHash + }); + + // Store commitment hash for L2 use + if (result.commitmentHash) { + localStorage.setItem('latestCommitmentHash', result.commitmentHash); + localStorage.setItem('latestDepositTx', result.transactionHash || ''); + } - // console.log("Transaction hash:", txHash); setHasBurned(true); onBurn(amount, assetId); } catch (error) { diff --git a/app/components/deposit.tsx b/app/components/deposit.tsx index 990ab40..c9687ea 100644 --- a/app/components/deposit.tsx +++ b/app/components/deposit.tsx @@ -8,6 +8,7 @@ import { // useSendTransaction } from "@starknet-react/core"; import { useTheme } from "../ThemeContext"; +import { useEthereum } from "./Ethereum-provider"; // import { uint256 } from "starknet"; interface DepositProps { @@ -18,10 +19,14 @@ interface DepositProps { const Deposit: React.FC = ({ token, onClose }) => { const [amount, setAmount] = useState(""); const [error, setError] = useState(""); + const [isProcessing, setIsProcessing] = useState(false); const { isDarkMode } = useTheme(); // Starknet React Hooks const { account } = useAccount(); + + // Ethereum hooks + const { isConnected: isEthereumConnected, depositAsset, address: ethereumAddress } = useEthereum(); // // Get contract instance // const { contract } = useContract({ // address: `0x${contractAddress}`, @@ -45,41 +50,77 @@ const Deposit: React.FC = ({ token, onClose }) => { // ], // }); -// const handleDeposit = async () => { -// if (!contract || !account) { -// setError("Please connect your wallet"); -// return; -// } + const handleDeposit = async () => { + try { + setError(""); + setIsProcessing(true); -// try { -// setError(""); + if (!isEthereumConnected || !ethereumAddress) { + setError("Please connect your Ethereum wallet"); + return; + } + + if (!amount || parseFloat(amount) <= 0) { + setError("Please enter a valid amount"); + return; + } + + // Determine asset type and token address + const assetType = token === "ETH" ? 0 : 1; + let tokenAddress; + + if (assetType === 0) { + tokenAddress = "0x0000000000000000000000000000000000000000"; + } else { + // For ERC20 tokens, you'd need to map token symbols to addresses + // This is a placeholder - in production, you'd have a token registry + const tokenAddresses: Record = { + USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + USDT: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + }; + tokenAddress = tokenAddresses[token]; + + if (!tokenAddress) { + setError(`Token ${token} not supported`); + return; + } + } + + console.log("Initiating deposit:", { + assetType, + tokenAddress, + amount, + userAddress: ethereumAddress + }); + + const result = await depositAsset(assetType, tokenAddress, amount); -// // Convert amount to uint256 -// const amountInWei = uint256.bnToUint256( -// BigInt(parseFloat(amount) * 10 ** 18) -// ); - -// // Update calldata with the correct values -// const tx = send([ -// { -// contractAddress: contractAddress, -// entrypoint: "deposit", -// calldata: [ -// account.address, -// amountInWei.low, -// amountInWei.high, -// ], -// }, -// ]); -// console.log('transaction result', tx); -// // Clear input and close modal -// setAmount(""); -// onClose(); - -// } catch (err) { -// setError(err instanceof Error ? err.message : "Failed to deposit"); -// } -// }; + if (!result.success) { + throw new Error(result.error || "Deposit failed"); + } + + console.log("Deposit successful:", { + transactionHash: result.transactionHash, + commitmentHash: result.commitmentHash + }); + + // Store commitment hash for L2 use + if (result.commitmentHash) { + localStorage.setItem('latestCommitmentHash', result.commitmentHash); + localStorage.setItem('latestDepositTx', result.transactionHash || ''); + } + + // Clear input and close modal on success + setAmount(""); + onClose(); + + } catch (err) { + console.error("Deposit error:", err); + setError(err instanceof Error ? err.message : "Failed to deposit"); + } finally { + setIsProcessing(false); + } + }; // Format balance for display const formattedBalance = balance @@ -153,13 +194,15 @@ const Deposit: React.FC = ({ token, onClose }) => { )} diff --git a/app/config.ts b/app/config.ts index 2804055..bc8b80c 100644 --- a/app/config.ts +++ b/app/config.ts @@ -1,11 +1,11 @@ import { http, createConfig } from 'wagmi' -import { base, mainnet} from 'wagmi/chains' +import { base, mainnet, sepolia} from 'wagmi/chains' import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors' const projectId = '' export const config = createConfig({ - chains: [mainnet, base], + chains: [mainnet, base, sepolia], connectors: [ injected(), walletConnect({ projectId }), @@ -15,5 +15,12 @@ export const config = createConfig({ transports: { [mainnet.id]: http(), [base.id]: http(), + [sepolia.id]: http(), }, -}) \ No newline at end of file +}) + +// Bridge contract addresses +export const BRIDGE_CONTRACTS: Record = { + [sepolia.id]: '0x8F25bFe32269632dfd8D223D51FF145414d8107b', + // Add other networks as needed +} \ No newline at end of file