Skip to content

Commit

Permalink
improved UI for CA demo
Browse files Browse the repository at this point in the history
Added input filed for amount and recipient address
  • Loading branch information
KannuSingh committed Nov 5, 2024
1 parent 6f2ff22 commit 40d0d06
Show file tree
Hide file tree
Showing 8 changed files with 814 additions and 502 deletions.
5 changes: 2 additions & 3 deletions advanced/dapps/chain-abstraction-demo/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const initialState = cookieToInitialState(config, headers().get("cookie"));

const cookies = headers().get('cookie')
return (
<html lang="en" suppressHydrationWarning>
<head />
Expand All @@ -35,7 +34,7 @@ export default function RootLayout({
fontSans.variable
)}
>
<AppKitProvider initialState={initialState}>
<AppKitProvider cookies={cookies}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
Expand Down
5 changes: 3 additions & 2 deletions advanced/dapps/chain-abstraction-demo/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
'use client'
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import Transfer from "./transfer";

export default function Home() {
return (
<main>
<div>
<Card>
<Card >
<CardHeader>
<w3m-button />
</CardHeader>
<CardContent>
<Transfer></Transfer>
<Transfer/>
</CardContent>
</Card>
</div>
Expand Down
141 changes: 125 additions & 16 deletions advanced/dapps/chain-abstraction-demo/app/transfer.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,88 @@
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import useSendUsdc from "./hooks/useSendUsdc";
import { useAccount } from "wagmi";
import { useAccount, useReadContract } from "wagmi";
import { useState } from "react";
import { useToast } from "@/hooks/use-toast";
import { Loader2 } from "lucide-react";
import { isAddress } from "viem";
import { tokenAddresses } from "@/consts/tokens";

const sendToAddress = "0x81D8C68Be5EcDC5f927eF020Da834AA57cc3Bd24";
const sendAmount = 6000000;
interface BalanceDisplayProps {
address: `0x${string}`;
chainId: number;
}

const BalanceDisplay: React.FC<BalanceDisplayProps> = ({ address, chainId }) => {
const {
data: usdcBalance,
isLoading: usdcBalanceLoading,
} = useReadContract({
abi: [
{
inputs: [{ internalType: "address", name: "owner", type: "address" }],
name: "balanceOf",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
],
address: tokenAddresses[chainId],
functionName: 'balanceOf',
args: [address],
});

// Convert the balance from the smallest unit to USDC
const formattedBalance = usdcBalanceLoading
? 'Loading...'
: usdcBalance
? (parseFloat(usdcBalance.toString()) / 1e6).toFixed(2) // Convert to USDC and format to 2 decimal places
: '0.00';

return (
<p className="text-gray-800 text-lg font-semibold">USDC Balance: {formattedBalance} USDC</p>
);
};

export default function Transfer() {
const { sendUsdcAsync } = useSendUsdc();
const { isConnected, chain } = useAccount();
const { isConnected, chain, address } = useAccount();
const [isLoading, setIsLoading] = useState(false);
const { toast } = useToast();
const [sendToAddress, setSendToAddress] = useState<string>("");
const [sendAmount, setSendAmount] = useState<number | string>("");

if (!chain || !address) {
return null;
}

const onButtonClick = async () => {
try {
if (!isAddress(sendToAddress)) {
toast({
variant: "destructive",
title: "Invalid Address",
description: "Please enter a valid Ethereum address.",
});
return;
}

const amount = Number(sendAmount);
if (amount <= 0) {
toast({
variant: "destructive",
title: "Invalid Amount",
description: "Please enter an amount greater than zero.",
});
return;
}

// Convert the amount to smallest denomination (e.g., 1 USDC = 1,000,000 in smallest unit)
const amountInSmallestDenomination = amount * 1e6;

setIsLoading(true);
const res = await sendUsdcAsync(sendToAddress, sendAmount);
const res = await sendUsdcAsync(sendToAddress, amountInSmallestDenomination);
console.log("Transaction completed", res);
toast({
title: "Transaction completed",
Expand All @@ -39,16 +103,61 @@ export default function Transfer() {
return (
<>
{isConnected ? (
<Button onClick={onButtonClick} disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Sending...
</>
) : (
<>Perform action with USDC on {chain?.name}</>
)}
</Button>
) : null}
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md space-y-4">
<BalanceDisplay address={address} chainId={chain.id} />
<form
onSubmit={(e) => {
e.preventDefault();
onButtonClick();
}}
className="space-y-4"
>
<div>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">
Recipient Address
</label>
<Input
type="text"
id="address"
value={sendToAddress}
onChange={(e) => setSendToAddress(e.target.value)}
className="mt-1 w-[340px] block rounded-md text-black border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm p-2"
required
placeholder="Enter recipient address"
/>
</div>
<div>
<label htmlFor="amount" className="block text-sm font-medium text-gray-700">
Amount (in USDC)
</label>
<Input
type="number"
id="amount"
value={sendAmount}
onChange={(e) => setSendAmount(e.target.value)}
className="mt-1 block w-[340px] text-black rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm p-2"
required
placeholder="Enter amount in USDC (e.g., 2 for 2 USDC)"
/>
</div>
<Button
type="submit"
disabled={isLoading || !sendToAddress || !sendAmount}
className={`w-full ${isLoading ? 'bg-gray-400' : 'bg-blue-600 hover:bg-blue-700'} transition duration-200`}
>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Sending...
</>
) : (
<>Send USDC on {chain?.name}</>
)}
</Button>
</form>
</div>
) : (
<p className="text-center text-gray-500">Please connect your account to proceed.</p>
)}
</>
);
}
}
25 changes: 25 additions & 0 deletions advanced/dapps/chain-abstraction-demo/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from "react"

import { cn } from "@/lib/utils"

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"

export { Input }
41 changes: 22 additions & 19 deletions advanced/dapps/chain-abstraction-demo/config/index.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import { defaultWagmiConfig } from "@web3modal/wagmi/react/config";
import { cookieStorage, createStorage, http } from '@wagmi/core'
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
import { arbitrum, optimism, base } from '@reown/appkit/networks'

import { cookieStorage, createStorage } from "wagmi";
import { arbitrum, base, optimism } from "wagmi/chains";
export const projectId = process.env.NEXT_PUBLIC_PROJECT_ID

// Get projectId from https://cloud.walletconnect.com
export const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
if (!projectId) {
throw new Error('Project ID is not defined')
}

export const networks = [base, optimism, arbitrum]

export const wagmiAdapter = new WagmiAdapter({
storage: createStorage({
storage: cookieStorage
}),
ssr: true,
projectId,
networks
})

if (!projectId) throw new Error("Project ID is not defined");

export const metadata = {
name: "AppKit",
description: "AppKit Example",
name: "Chain Abstraction Demo",
description: "A demo of Chain Abstraction",
url: "https://web3modal.com", // origin must match your domain & subdomain
icons: ["https://avatars.githubusercontent.com/u/37784886"],
};

// Create wagmiConfig
const chains = [base, optimism, arbitrum] as const;
export const config = defaultWagmiConfig({
chains,
projectId,
metadata,
ssr: true,
storage: createStorage({
storage: cookieStorage,
}),
});

export const config = wagmiAdapter.wagmiConfig
48 changes: 23 additions & 25 deletions advanced/dapps/chain-abstraction-demo/context/index.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,35 @@
"use client";
'use client'

import React, { ReactNode } from "react";
import { config, projectId, metadata } from "@/config";
import { wagmiAdapter, projectId, metadata } from '@/config'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { createAppKit } from '@reown/appkit/react'
import { arbitrum, base, optimism } from '@reown/appkit/networks'
import React, { type ReactNode } from 'react'
import { cookieToInitialState, WagmiProvider, type Config } from 'wagmi'

import { createWeb3Modal } from "@web3modal/wagmi/react";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import { State, WagmiProvider } from "wagmi";

// Setup queryClient
const queryClient = new QueryClient();

if (!projectId) throw new Error("Project ID is not defined");

// Create modal
createWeb3Modal({
metadata,
wagmiConfig: config,
const modal = createAppKit({
adapters: [wagmiAdapter],
projectId,
enableAnalytics: true, // Optional - defaults to your Cloud configuration
});
networks: [base, optimism, arbitrum],
defaultNetwork: base,
metadata: metadata,
features: {
analytics: true
}
})

function AppKitProvider({ children, cookies }: { children: ReactNode; cookies: string | null }) {
const initialState = cookieToInitialState(wagmiAdapter.wagmiConfig as Config, cookies)

export default function AppKitProvider({
children,
initialState,
}: {
children: ReactNode;
initialState?: State;
}) {
return (
<WagmiProvider config={config} initialState={initialState}>
<WagmiProvider config={wagmiAdapter.wagmiConfig } initialState={initialState}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</WagmiProvider>
);
)
}

export default AppKitProvider
36 changes: 20 additions & 16 deletions advanced/dapps/chain-abstraction-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,35 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@tanstack/react-query": "^5.54.1",
"@web3modal/wagmi": "^5.1.6",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.439.0",
"@radix-ui/react-icons": "1.3.1",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-toast": "1.2.2",
"@reown/appkit": "1.2.1",
"@reown/appkit-adapter-wagmi": "1.2.1",
"@tanstack/react-query": "5.59.19",
"@web3modal/wagmi": "5.1.11",
"class-variance-authority": "0.7.0",
"clsx": "2.1.1",
"lucide-react": "0.439.0",
"next": "14.2.7",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"viem": "^2.21.1",
"wagmi": "^2.12.8"
"next-themes": "0.3.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"tailwind-merge": "2.5.2",
"tailwindcss-animate": "1.0.7",
"viem": "2.21.40",
"wagmi": "2.12.25"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"encoding": "0.1.13",
"eslint": "^8",
"eslint-config-next": "14.2.7",
"pino-pretty": "^11.3.0",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
}
Loading

0 comments on commit 40d0d06

Please sign in to comment.