diff --git a/advanced/dapps/chain-abstraction-demo/app/hooks/useGiftDonut.tsx b/advanced/dapps/chain-abstraction-demo/app/hooks/useGiftDonut.tsx
index 85da06be0..22b696774 100644
--- a/advanced/dapps/chain-abstraction-demo/app/hooks/useGiftDonut.tsx
+++ b/advanced/dapps/chain-abstraction-demo/app/hooks/useGiftDonut.tsx
@@ -2,13 +2,15 @@ import { config } from "@/config";
import { tokenAddresses } from "@/consts/tokens";
import { Network, Token } from "@/data/EIP155Data";
import { toast } from "sonner";
-import { erc20Abi, Hex, PublicClient } from "viem";
+import { erc20Abi, Hex, parseEther, PublicClient } from "viem";
import { getAccount, getWalletClient, getPublicClient } from "wagmi/actions";
import { useState } from "react";
import { TransactionToast } from "@/components/TransactionToast";
type TransactionStatus = "waiting-approval" | "pending" | "success" | "error";
+const ETH_USD_RATE = 0.0005; // 1 USD = 0.0005 ETH
+
export default function useGiftDonut() {
const [isPending, setIsPending] = useState(false);
@@ -36,7 +38,7 @@ export default function useGiftDonut() {
);
};
- const validateTransaction = async (network: Network) => {
+ const getClients = async (network: Network) => {
const client = await getWalletClient(config, { chainId: network.chainId });
const publicClient = getPublicClient(config);
if (!publicClient) throw new Error("Failed to get public client");
@@ -64,6 +66,12 @@ export default function useGiftDonut() {
return contract;
};
+ const convertDonutCountToEth = (donutCount: number) => {
+ // consider 1 donut = 1 USD, 1 USD = 0.0005 ETH
+ return donutCount * ETH_USD_RATE;
+ }
+
+
const giftDonutAsync = async (
to: Hex,
donutCount: number,
@@ -77,14 +85,9 @@ export default function useGiftDonut() {
try {
// Validate chain and get clients
- const { client, publicClient } = await validateTransaction(network);
+ const { client, publicClient } = await getClients(network);
const chainId = getAccount(config).chain?.id!;
- // Get token contract
- const contract = getTokenContract(token, chainId);
-
- // Calculate token amount using token's decimals
- const tokenAmount = donutCount * 10 ** token.decimals;
// Start tracking elapsed time
updateInterval = setInterval(() => {
updateToast(toastId, "waiting-approval", {
@@ -92,13 +95,28 @@ export default function useGiftDonut() {
});
}, 1000);
- // Send transaction
- const tx = await client.writeContract({
- abi: erc20Abi,
- address: contract,
- functionName: "transfer",
- args: [to, BigInt(tokenAmount)],
- });
+
+ let tx: Hex;
+ if (token.name === "ETH") {
+ // Calculate ETH amount using the conversion rate
+ const ethAmount = convertDonutCountToEth(donutCount);
+ tx = await client.sendTransaction({
+ to,
+ value: parseEther(ethAmount.toString()),
+ chainId,
+ });
+ } else {
+ // Get token contract
+ const contract = getTokenContract(token, chainId);
+ const tokenAmount = donutCount * 10 ** token.decimals;
+ // Send transaction
+ tx = await client.writeContract({
+ abi: erc20Abi,
+ address: contract,
+ functionName: "transfer",
+ args: [to, BigInt(tokenAmount)],
+ });
+ }
// Update to pending status
updateToast(toastId, "pending", { hash: tx, networkName: network.name });
diff --git a/advanced/dapps/chain-abstraction-demo/components/gift-donut-modal-views/CheckoutView.tsx b/advanced/dapps/chain-abstraction-demo/components/gift-donut-modal-views/CheckoutView.tsx
index 6c715bf3f..4cd95eca7 100644
--- a/advanced/dapps/chain-abstraction-demo/components/gift-donut-modal-views/CheckoutView.tsx
+++ b/advanced/dapps/chain-abstraction-demo/components/gift-donut-modal-views/CheckoutView.tsx
@@ -238,7 +238,7 @@ function GiftDonutForm({
type="button"
variant="secondary"
className="flex flex-1 gap-1"
- disabled={!giftDonutModalManager.isTokenNetworkCompatible()}
+ disabled={!giftDonutModalManager.isTokenNetworkCompatible() || count === 0}
>
setSelectedToken(tokenItem)}
/>
{availableTokens.length - 1 !== index && }
@@ -122,7 +122,7 @@ function TokenItem({
- Balance: ${formattedBalance}
+ Balance: ${formattedBalance} { token.type === "native" && token.name === "ETH" &&'(0.0005 ETH = $1)'}
{selected && }
diff --git a/advanced/dapps/chain-abstraction-demo/consts/tokens.ts b/advanced/dapps/chain-abstraction-demo/consts/tokens.ts
index 4f827d18c..898a4cef2 100644
--- a/advanced/dapps/chain-abstraction-demo/consts/tokens.ts
+++ b/advanced/dapps/chain-abstraction-demo/consts/tokens.ts
@@ -17,10 +17,17 @@ export const usdsTokenAddresses: Record = {
8453: "0x820c137fa70c8691f0e44dc420a5e53c168921dc", // Base
};
+export const ethTokenAddresses: Record = {
+ 42161: "0x0000000000000000000000000000000000000000", // Arbitrum
+ 10: "0x0000000000000000000000000000000000000000", // Optimism
+ 8453: "0x0000000000000000000000000000000000000000", // Base
+};
+
export const tokenAddresses: Record> = {
USDC: usdcTokenAddresses,
USDT: usdtTokenAddresses,
USDS: usdsTokenAddresses,
+ ETH: ethTokenAddresses,
};
export const getSupportedNetworks = (token: string): number[] => {
diff --git a/advanced/dapps/chain-abstraction-demo/controllers/GiftDonutModalManager.ts b/advanced/dapps/chain-abstraction-demo/controllers/GiftDonutModalManager.ts
index 77bb13752..ff70032a4 100644
--- a/advanced/dapps/chain-abstraction-demo/controllers/GiftDonutModalManager.ts
+++ b/advanced/dapps/chain-abstraction-demo/controllers/GiftDonutModalManager.ts
@@ -168,6 +168,14 @@ class GiftDonutModalManager {
}
getBalanceBySymbol(symbol: string): string {
+ if(symbol === "ETH") {
+ const ethBalance = this.state.state.balances.find((b) => b.symbol === "ETH")?.balance || "0.00";
+ // Convert ETH balance to USD equivalent (0.0005 ETH = 1 USD)
+ const usdEquivalent = parseFloat(ethBalance) / 0.0005;
+
+ return usdEquivalent.toFixed(2);
+ }
+
const balance = this.state.state.balances.find((b) => b.symbol === symbol);
return balance?.balance || "0.00";
}
diff --git a/advanced/dapps/chain-abstraction-demo/data/EIP155Data.ts b/advanced/dapps/chain-abstraction-demo/data/EIP155Data.ts
index 2d04941c8..ac2382359 100644
--- a/advanced/dapps/chain-abstraction-demo/data/EIP155Data.ts
+++ b/advanced/dapps/chain-abstraction-demo/data/EIP155Data.ts
@@ -13,9 +13,10 @@ export interface Network {
}
export interface Token {
+ id: string; // Unique identifier for the token
+ type: "erc20" | "native"
name: string;
icon: string;
- address: string;
supportedChainIds: number[];
decimals: number;
}
@@ -43,26 +44,37 @@ export const supportedNetworks: Network[] = [
export const supportedTokens: Token[] = [
{
+ id: "USDC",
+ type: "erc20",
name: "USDC",
icon: "/token-images/USDC.png",
- address: "0x1",
supportedChainIds: [arbitrum.id, base.id, optimism.id],
- decimals: 6
+ decimals: 6,
},
{
+ id: "USDT",
+ type: "erc20",
name: "USDT",
icon: "/token-images/USDT.png",
- address: "0x2",
supportedChainIds: [arbitrum.id, optimism.id],
- decimals: 6
+ decimals: 6,
},
{
+ id: "USDS",
+ type: "erc20",
name: "USDS",
icon: "/token-images/USDS(DAI).png",
- address: "0x3",
supportedChainIds: [base.id],
- decimals: 18
+ decimals: 18,
},
+ {
+ id: "ETH",
+ type: "native",
+ name: "ETH",
+ icon: "/token-images/ETH.png",
+ supportedChainIds: [arbitrum.id, base.id, optimism.id],
+ decimals: 18,
+ }
];
export function isTokenSupportedOnNetwork(
diff --git a/advanced/dapps/chain-abstraction-demo/public/token-images/ETH.png b/advanced/dapps/chain-abstraction-demo/public/token-images/ETH.png
new file mode 100644
index 000000000..db6015497
Binary files /dev/null and b/advanced/dapps/chain-abstraction-demo/public/token-images/ETH.png differ
diff --git a/advanced/dapps/chain-abstraction-demo/utils/BalanceFetcherUtil.ts b/advanced/dapps/chain-abstraction-demo/utils/BalanceFetcherUtil.ts
index f69107454..7ef2513fe 100644
--- a/advanced/dapps/chain-abstraction-demo/utils/BalanceFetcherUtil.ts
+++ b/advanced/dapps/chain-abstraction-demo/utils/BalanceFetcherUtil.ts
@@ -1,4 +1,5 @@
-import { usdcTokenAddresses, usdtTokenAddresses, usdsTokenAddresses } from "@/consts/tokens";
+import { supportedTokens } from "@/data/EIP155Data";
+import { tokenAddresses } from "@/consts/tokens";
import { createPublicClient, erc20Abi, Hex, http, PublicClient } from "viem";
import { formatBalance } from "@/utils/FormatterUtil";
import { getChain } from "@/utils/NetworksUtil";
@@ -25,7 +26,11 @@ async function fetchTokenBalance({
}: {
publicClient: PublicClient;
userAddress: Hex;
- tokenConfig: TokenConfig;
+ tokenConfig: {
+ symbol: string;
+ decimals: number;
+ address: Hex;
+ };
chainId: number;
}): Promise {
try {
@@ -44,15 +49,16 @@ async function fetchTokenBalance({
};
} catch (error) {
console.error(`Error fetching ${tokenConfig.symbol} balance:`, error);
-
return null;
}
}
+
function getTransport({ chainId }: { chainId: number }) {
return http(
`https://rpc.walletconnect.org/v1/?chainId=eip155:${chainId}&projectId=${process.env["NEXT_PUBLIC_PROJECT_ID"]}`,
);
}
+
export async function fetchFallbackBalances(
userAddress: Hex,
currentChainIdAsHex: Hex,
@@ -63,7 +69,6 @@ export async function fetchFallbackBalances(
const chain = getChain(currentChainId);
if (!chain) {
console.error(`Chain not found for ID: ${currentChainId}`);
-
return [];
}
@@ -74,70 +79,56 @@ export async function fetchFallbackBalances(
}) as PublicClient;
const balances: TokenBalance[] = [];
+ const tokenBalancePromises: Promise[] = [];
- // Fetch native token balance
- try {
- const nativeBalance = await publicClient.getBalance({
- address: userAddress,
- });
-
- balances.push({
- symbol: chain.nativeCurrency.symbol,
- balance: formatBalance(nativeBalance, chain.nativeCurrency.decimals),
- address: "0x" as Hex,
- chainId: currentChainId,
- });
- } catch (error) {
- console.error(`Error fetching native balance:`, error);
- }
+ // Filter tokens supported on the current chain
+ const tokensForChain = supportedTokens.filter(token =>
+ token.supportedChainIds.includes(currentChainId)
+ );
- // Get supported tokens for current chain
- const supportedTokens: TokenConfig[] = [];
-
- // Add USDC if supported
- const usdcAddress = usdcTokenAddresses[currentChainId];
- if (usdcAddress) {
- supportedTokens.push({
- symbol: "USDC",
- decimals: 6,
- address: usdcAddress,
- });
+ const nativeTokens = tokensForChain.filter(token => token.type === "native");
+ for (const nativeToken of nativeTokens) {
+ try {
+ const nativeBalance = await publicClient.getBalance({
+ address: userAddress,
+ });
+
+ balances.push({
+ symbol: nativeToken.name,
+ balance: formatBalance(nativeBalance, nativeToken.decimals),
+ address: "0x" as Hex,
+ chainId: currentChainId,
+ });
+ } catch (error) {
+ console.error(`Error fetching native ${nativeToken.name} balance:`, error);
+ }
}
- // Add USDT if supported
- const usdtAddress = usdtTokenAddresses[currentChainId];
- if (usdtAddress) {
- supportedTokens.push({
- symbol: "USDT",
- decimals: 6,
- address: usdtAddress,
- });
- }
+ const erc20Tokens = tokensForChain.filter(token => token.type === "erc20");
+ for (const erc20Token of erc20Tokens) {
+ const tokenAddressMap = tokenAddresses[erc20Token.id];
+ if (!tokenAddressMap) continue;
- // Add USDS if supported
- const usdsAddress = usdsTokenAddresses[currentChainId];
- if (usdsAddress) {
- supportedTokens.push({
- symbol: "USDS",
- decimals: 18,
- address: usdsAddress,
- });
- }
+ const tokenAddress = tokenAddressMap[currentChainId];
+ if (!tokenAddress) continue;
- // Fetch token balances
- const tokenResults = await Promise.all(
- supportedTokens.map((token) =>
+ tokenBalancePromises.push(
fetchTokenBalance({
publicClient,
userAddress,
- tokenConfig: token,
+ tokenConfig: {
+ symbol: erc20Token.name,
+ decimals: erc20Token.decimals,
+ address: tokenAddress,
+ },
chainId: currentChainId,
- }),
- ),
- );
+ })
+ );
+ }
- // Add successful token balances
- tokenResults.forEach((result) => {
+ const tokenResults = await Promise.all(tokenBalancePromises);
+
+ tokenResults.forEach(result => {
if (result) {
balances.push(result);
}
@@ -146,7 +137,6 @@ export async function fetchFallbackBalances(
return balances;
} catch (error) {
console.error("Error in fetchFallbackBalances:", error);
-
return [];
}
}