diff --git a/examples/nextjs-reown/.env.example b/examples/nextjs-reown/.env.example new file mode 100644 index 0000000..445dbea --- /dev/null +++ b/examples/nextjs-reown/.env.example @@ -0,0 +1,3 @@ +NEXT_PUBLIC_PROJECT_ID=your_reown_project_id_here +NEXT_PUBLIC_JAW_API_KEY=your_jaw_api_key_here + diff --git a/examples/nextjs-reown/.gitignore b/examples/nextjs-reown/.gitignore new file mode 100644 index 0000000..e72b4d6 --- /dev/null +++ b/examples/nextjs-reown/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/nextjs-reown/README.md b/examples/nextjs-reown/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/examples/nextjs-reown/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/examples/nextjs-reown/app/favicon.ico b/examples/nextjs-reown/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/examples/nextjs-reown/app/favicon.ico differ diff --git a/examples/nextjs-reown/app/globals.css b/examples/nextjs-reown/app/globals.css new file mode 100644 index 0000000..a2dc41e --- /dev/null +++ b/examples/nextjs-reown/app/globals.css @@ -0,0 +1,26 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/examples/nextjs-reown/app/layout.tsx b/examples/nextjs-reown/app/layout.tsx new file mode 100644 index 0000000..7a2614b --- /dev/null +++ b/examples/nextjs-reown/app/layout.tsx @@ -0,0 +1,40 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import { headers } from "next/headers"; +import ContextProvider from "@/context"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "JAW + Reown AppKit", + description: "JAW Smart Accounts with Reown AppKit and wagmi", +}; + +export default async function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const headersObj = await headers(); + const cookies = headersObj.get("cookie"); + + return ( + + + {children} + + + ); +} diff --git a/examples/nextjs-reown/app/page.tsx b/examples/nextjs-reown/app/page.tsx new file mode 100644 index 0000000..acbf0b5 --- /dev/null +++ b/examples/nextjs-reown/app/page.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { useAccount } from "wagmi"; +import { useEffect, useState } from "react"; +import { JawConnectButton } from "@/components/connect-button"; +import { SignMessage } from "@/components/sign-message"; +import { SignTypedData } from "@/components/sign-typed-data"; +import { SendTransaction } from "@/components/send-transaction"; + +export default function Home() { + const { isConnected } = useAccount(); + const [mounted, setMounted] = useState(false); + + useEffect(() => setMounted(true), []); + + return ( +
+
+
+

+ JAW + Reown AppKit +

+

+ Connect with a JAW passkey smart account or any wallet via Reown. +

+
+ +
+
+

+ Passkey Smart Account +

+ +
+ +
+
+ or +
+
+ +
+

+ Any Wallet +

+ +
+
+ + {mounted && isConnected && ( +
+
+
+ + Actions + +
+
+ + + + +
+ )} +
+
+ ); +} diff --git a/examples/nextjs-reown/appkit.d.ts b/examples/nextjs-reown/appkit.d.ts new file mode 100644 index 0000000..8b38294 --- /dev/null +++ b/examples/nextjs-reown/appkit.d.ts @@ -0,0 +1,10 @@ +import "react"; + +declare namespace JSX { + interface IntrinsicElements { + "appkit-button": React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + >; + } +} diff --git a/examples/nextjs-reown/components/connect-button.tsx b/examples/nextjs-reown/components/connect-button.tsx new file mode 100644 index 0000000..fa62340 --- /dev/null +++ b/examples/nextjs-reown/components/connect-button.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useAccount } from "wagmi"; +import { useConnect, useDisconnect } from "@jaw.id/wagmi"; +import { config } from "@/config"; + +export function JawConnectButton() { + const { address, isConnected } = useAccount(); + const { mutate: connect, isPending: isConnecting } = useConnect(); + const { mutate: disconnect, isPending: isDisconnecting } = useDisconnect(); + const [mounted, setMounted] = useState(false); + + useEffect(() => setMounted(true), []); + + if (!mounted) return null; + + if (isConnected) { + return ( +
+
+

+ Connected as +

+

+ {address} +

+
+ +
+ ); + } + + return ( + + ); +} diff --git a/examples/nextjs-reown/components/send-transaction.tsx b/examples/nextjs-reown/components/send-transaction.tsx new file mode 100644 index 0000000..effeed5 --- /dev/null +++ b/examples/nextjs-reown/components/send-transaction.tsx @@ -0,0 +1,281 @@ +"use client"; + +import { useState } from "react"; +import { useSendCalls } from "wagmi"; +import { parseEther, encodeFunctionData } from "viem"; +import { baseSepolia } from "wagmi/chains"; + +const erc20Abi = [ + { + name: "transfer", + type: "function", + stateMutability: "nonpayable", + inputs: [ + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + }, +] as const; + +export function SendTransaction() { + return ( +
+ + + +
+ ); +} + +function SendEth() { + const [to, setTo] = useState(""); + const [amount, setAmount] = useState(""); + const { sendCalls, isPending, data, error } = useSendCalls(); + + function handleSend() { + if (!to || !amount) return; + sendCalls({ + chainId: baseSepolia.id, + calls: [ + { + to: to as `0x${string}`, + value: parseEther(amount), + }, + ], + }); + } + + return ( +
+

+ Send ETH +

+

+ Send a single ETH transfer to any address. +

+ +
+ setTo(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> + setAmount(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> + +
+ + +
+ ); +} + +function BatchSend() { + const [toA, setToA] = useState(""); + const [amountA, setAmountA] = useState(""); + const [toB, setToB] = useState(""); + const [amountB, setAmountB] = useState(""); + const { sendCalls, isPending, data, error } = useSendCalls(); + + function handleBatch() { + if (!toA || !amountA || !toB || !amountB) return; + sendCalls({ + chainId: baseSepolia.id, + calls: [ + { + to: toA as `0x${string}`, + value: parseEther(amountA), + }, + { + to: toB as `0x${string}`, + value: parseEther(amountB), + }, + ], + }); + } + + return ( +
+

+ Batch Send +

+

+ Send ETH to two addresses in a single batched call. +

+ +
+
+

+ Recipient A +

+ setToA(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> + setAmountA(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> +
+ +
+

+ Recipient B +

+ setToB(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> + setAmountB(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> +
+ + +
+ + +
+ ); +} + +function Erc20Transfer() { + const [token, setToken] = useState(""); + const [to, setTo] = useState(""); + const [amount, setAmount] = useState(""); + const { sendCalls, isPending, data, error } = useSendCalls(); + + function handleTransfer() { + if (!token || !to || !amount) return; + + const callData = encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [to as `0x${string}`, BigInt(amount)], + }); + + sendCalls({ + chainId: baseSepolia.id, + calls: [ + { + to: token as `0x${string}`, + data: callData, + }, + ], + }); + } + + return ( +
+

+ ERC-20 Transfer +

+

+ Transfer ERC-20 tokens using{" "} + + encodeFunctionData + {" "} + from viem. +

+ +
+ setToken(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> + setTo(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> + setAmount(e.target.value)} + className="rounded-lg border border-zinc-200 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder-zinc-600 dark:focus:border-blue-600" + /> + +
+ + +
+ ); +} + +function TxStatus({ id, error }: { id?: string; error: Error | null }) { + if (error) { + return ( +
+

+ Transaction failed +

+

+ {error.message} +

+
+ ); + } + + if (id) { + return ( +
+

+ Transaction submitted +

+

+ User Op ID +

+

+ {id} +

+
+ ); + } + + return null; +} diff --git a/examples/nextjs-reown/components/sign-message.tsx b/examples/nextjs-reown/components/sign-message.tsx new file mode 100644 index 0000000..a0d09ae --- /dev/null +++ b/examples/nextjs-reown/components/sign-message.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { useState } from "react"; +import { useSignMessage } from "wagmi"; + +export function SignMessage() { + const [message, setMessage] = useState("Hello from JAW + Reown!"); + const { + signMessage, + data: signature, + isPending, + error, + reset, + } = useSignMessage(); + + return ( +
+

+ Personal Sign +

+

+ Sign a plaintext message using personal_sign (EIP-191) +

+ +