From 581e3b9ca00cee233af04f0aa2f27988f453a6ea Mon Sep 17 00:00:00 2001 From: jgresham Date: Mon, 7 Apr 2025 14:22:51 -0700 Subject: [PATCH] add coinbase sub account. write contract not working --- .env.production | 12 +- package.json | 1 + pnpm-lock.yaml | 80 +++++++++ src/app/footer.tsx | 6 +- src/app/games/[gameId]/game.tsx | 74 ++++++-- src/app/header.tsx | 70 ++++++++ src/app/layout.tsx | 28 +--- src/app/page.tsx | 13 +- src/components/NewGameSheet.tsx | 13 +- src/components/providers/providers.tsx | 21 ++- src/components/util/DisplayAddress.tsx | 29 +++- src/context/CoinbaseWalletContext.tsx | 224 +++++++++++++++++++++++++ wrangler.jsonc | 34 ++++ 13 files changed, 547 insertions(+), 58 deletions(-) create mode 100644 src/app/header.tsx create mode 100644 src/context/CoinbaseWalletContext.tsx diff --git a/.env.production b/.env.production index 15155e7..5aef306 100644 --- a/.env.production +++ b/.env.production @@ -1,10 +1,10 @@ # no trailing slash -NEXT_PUBLIC_WORKER_DOMAIN=chess-worker.johnsgresham.workers.dev -NEXT_PUBLIC_URL=https://basedchess.xyz -NEXT_PUBLIC_NEYNAR_API_KEY_FRNT=47FB9C28-6FFC-4086-84C3-ED9B88DBAF27 +# NEXT_PUBLIC_WORKER_DOMAIN=chess-worker.johnsgresham.workers.dev +# NEXT_PUBLIC_URL=https://basedchess.xyz +# NEXT_PUBLIC_NEYNAR_API_KEY_FRNT=47FB9C28-6FFC-4086-84C3-ED9B88DBAF27 # staging because passing env vars is broken in opennextjs-cloudflare # NEXT_PUBLIC_URL=https://based-chess-worker-nextjs-staging.johnsgresham.workers.dev -# NEXT_PUBLIC_WORKER_DOMAIN=chess-worker-staging.johnsgresham.workers.dev -# NEXT_PUBLIC_URL=https://staging.basedchess.xyz -# NEXT_PUBLIC_NEYNAR_API_KEY_FRNT=47FB9C28-6FFC-4086-84C3-ED9B88DBAF27 +NEXT_PUBLIC_WORKER_DOMAIN=chess-worker-staging.johnsgresham.workers.dev +NEXT_PUBLIC_URL=https://sub-accounts-demo.basedchess.xyz +NEXT_PUBLIC_NEYNAR_API_KEY_FRNT=47FB9C28-6FFC-4086-84C3-ED9B88DBAF27 diff --git a/package.json b/package.json index 40b3e92..5bbdffd 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts" }, "dependencies": { + "@coinbase/wallet-sdk": "4.4.0-canary.20250402", "@farcaster/frame-sdk": "^0.0.32", "@farcaster/frame-wagmi-connector": "^0.0.20", "@radix-ui/react-accordion": "^1.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6c9799..310502b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@coinbase/wallet-sdk': + specifier: 4.4.0-canary.20250402 + version: 4.4.0-canary.20250402(@types/react@19.0.12)(bufferutil@4.0.9)(react@19.1.0)(typescript@5.8.2)(use-sync-external-store@1.4.0(react@19.1.0))(utf-8-validate@5.0.10)(zod@3.24.2) '@farcaster/frame-sdk': specifier: ^0.0.32 version: 0.0.32(typescript@5.8.2)(zod@3.24.2) @@ -755,6 +758,9 @@ packages: '@coinbase/wallet-sdk@4.3.0': resolution: {integrity: sha512-T3+SNmiCw4HzDm4we9wCHCxlP0pqCiwKe4sOwPH3YAK2KSKjxPRydKu6UQJrdONFVLG7ujXvbd/6ZqmvJb8rkw==} + '@coinbase/wallet-sdk@4.4.0-canary.20250402': + resolution: {integrity: sha512-moQz9aI1+poE49lXFqtTqdg7HeJLEydGEYInSeDJPPV18La2RUUUSnwFEqqufPPE8edIVtbRt4FTy0qyawuMMg==} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -4502,6 +4508,9 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} + preact@10.24.2: + resolution: {integrity: sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==} + preact@10.26.4: resolution: {integrity: sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w==} @@ -5222,6 +5231,14 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + viem@2.22.17: + resolution: {integrity: sha512-eqNhlPGgRLR29XEVUT2uuaoEyMiaQZEKx63xT1py9OYsE+ZwlVgjnfrqbXad7Flg2iJ0Bs5Hh7o0FfRWUJGHvg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + viem@2.23.2: resolution: {integrity: sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==} peerDependencies: @@ -5433,6 +5450,24 @@ packages: use-sync-external-store: optional: true + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@adraffy/ens-normalize@1.11.0': {} @@ -7095,6 +7130,26 @@ snapshots: eventemitter3: 5.0.1 preact: 10.26.4 + '@coinbase/wallet-sdk@4.4.0-canary.20250402(@types/react@19.0.12)(bufferutil@4.0.9)(react@19.1.0)(typescript@5.8.2)(use-sync-external-store@1.4.0(react@19.1.0))(utf-8-validate@5.0.10)(zod@3.24.2)': + dependencies: + '@noble/hashes': 1.4.0 + clsx: 1.2.1 + eventemitter3: 5.0.1 + idb-keyval: 6.2.1 + ox: 0.6.9(typescript@5.8.2)(zod@3.24.2) + preact: 10.24.2 + viem: 2.22.17(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2) + zustand: 5.0.3(@types/react@19.0.12)(react@19.1.0)(use-sync-external-store@1.4.0(react@19.1.0)) + transitivePeerDependencies: + - '@types/react' + - bufferutil + - immer + - react + - typescript + - use-sync-external-store + - utf-8-validate + - zod + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -11702,6 +11757,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + preact@10.24.2: {} + preact@10.26.4: {} prelude-ls@1.2.1: {} @@ -12460,6 +12517,23 @@ snapshots: vary@1.1.2: {} + viem@2.22.17(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2): + dependencies: + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 + '@scure/bip32': 1.6.2 + '@scure/bip39': 1.5.4 + abitype: 1.0.8(typescript@5.8.2)(zod@3.24.2) + isows: 1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + ox: 0.6.7(typescript@5.8.2)(zod@3.24.2) + ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + viem@2.23.2(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2): dependencies: '@noble/curves': 1.8.1 @@ -12715,3 +12789,9 @@ snapshots: '@types/react': 19.0.12 react: 19.1.0 use-sync-external-store: 1.4.0(react@19.1.0) + + zustand@5.0.3(@types/react@19.0.12)(react@19.1.0)(use-sync-external-store@1.4.0(react@19.1.0)): + optionalDependencies: + '@types/react': 19.0.12 + react: 19.1.0 + use-sync-external-store: 1.4.0(react@19.1.0) diff --git a/src/app/footer.tsx b/src/app/footer.tsx index 7d65b00..86a669d 100644 --- a/src/app/footer.tsx +++ b/src/app/footer.tsx @@ -3,13 +3,15 @@ import { ArrowUpRight } from "lucide-react"; import { contracts, type SupportedChainId } from "../lib/contracts"; import { blockExplorers } from "../lib/contracts"; import { buttonVariants } from "../components/ui/button"; -import { useAccount } from "wagmi"; +// import { useAccount } from "wagmi"; import Link from "next/link"; import { DarkModeToggle } from "../components/DarkModeToggle"; import { DevModeToggle } from "../components/DevModeToggle"; +// import { useCoinbaseWallet } from "../context/CoinbaseWalletContext"; export const Footer = () => { - const { chainId } = useAccount(); + // const { chainId } = useAccount(); + const chainId: SupportedChainId = 84532; return (
diff --git a/src/app/games/[gameId]/game.tsx b/src/app/games/[gameId]/game.tsx index dd78b90..3e17e7e 100644 --- a/src/app/games/[gameId]/game.tsx +++ b/src/app/games/[gameId]/game.tsx @@ -40,6 +40,8 @@ import { MintGameWinNFTBtn, type MintStep } from "./MintGameWinNFTBtn"; import { useDevMode } from "../../../components/hooks/useLocalSettings"; import { useFarcasterUser } from "../../../components/hooks/useFarcasterUser"; import { FarcasterUser } from "../../../lib/neynar.server"; +import { useCoinbaseWallet } from "../../../context/CoinbaseWalletContext"; +import { recoverMessageAddress } from "viem"; export type WsMessage = { type: string; @@ -84,7 +86,7 @@ export default function Game() { const wsRef = useRef(null); const chessboardRef = useRef(null); const [game, setGame] = useState(); - const { address, isConnected } = useAccount(); + // const { address, isConnected } = useAccount(); const [awaitSigningMove, setAwaitSigningMove] = useState(false); const [boardOrientation, setBoardOrientation] = useState("white"); const params = useParams(); @@ -136,8 +138,8 @@ export default function Game() { >({}); const { showToast, Toast } = useToast(); - const chainId = useChainId(); - + // const chainId = useChainId(); + const chainId: SupportedChainId = 84532; const [winner, setWinner] = useState<`0x${string}` | undefined>(); const [isNFTMinted, setIsNFTMinted] = useState(false); const [isNFTReadyToMint, setIsNFTReadyToMint] = useState(false); @@ -177,6 +179,47 @@ export default function Game() { args: [contracts.gamesContract[chainId as SupportedChainId].address, contractGameId], }); + // [start] Coinbase Sub Account Wallet specifics + const { + isConnected, + connect, + disconnect, + address, + subAccount, + createSubAccount, + subAccountWalletClient, + provider, + } = useCoinbaseWallet(); + const [signature, setSignature] = useState(null); + + const signMessageSubAccount = useCallback( + async (message: string) => { + if (!subAccountWalletClient || !subAccount) { + // open create sub account for user + createSubAccount(); + console.error("Subaccount wallet client or subaccount not found"); + throw new Error("Subaccount wallet client or subaccount not found"); + } + + const signature = await subAccountWalletClient.signMessage({ + message, + account: subAccount, + }); + console.log("signature", signature); + // Error: Invalid yParityOrV value + // const addr = await recoverMessageAddress({ + // message, + // signature, + // }); + // console.log("recoverMessageAddress addr", addr); + + setSignature(signature); + return signature; + }, + [subAccountWalletClient, subAccount, createSubAccount], + ); + // [end] Coinbase Sub Account Wallet specifics + useEffect(() => { setAudioPlayerDropChessPiece(new Audio("/sounds/drop_piece.mp3")); setAudioPlayerLoseGame(new Audio("/sounds/lose_game.mp3")); @@ -678,17 +721,21 @@ export default function Game() { // sign move const message = game.pgn(); // const message = JSON.stringify(moveMove.lan) - const signature = await signMessage(frameWagmiConfig, { - message, - }); + // const signature = await signMessage(frameWagmiConfig, { + // message, + // }); + const signature = await signMessageSubAccount(message); + console.log("user move signature:", signature); - const verified = await verifyMessage(frameWagmiConfig, { - address, - message, - signature, - }); - console.log("user move signature verified:", verified); + // todo: verify the sub account signature + + // const verified = await verifyMessage(frameWagmiConfig, { + // address, + // message, + // signature, + // }); + // console.log("user move signature verified:", verified); setAwaitSigningMove(false); // update server @@ -718,6 +765,7 @@ export default function Game() { signature, message, address, + subAccount, }, }), ); @@ -744,6 +792,7 @@ export default function Game() { signature, message, address, + subAccount, }, }), ); @@ -770,6 +819,7 @@ export default function Game() { signature, message, address, + subAccount, }, }), ); diff --git a/src/app/header.tsx b/src/app/header.tsx new file mode 100644 index 0000000..5f4151a --- /dev/null +++ b/src/app/header.tsx @@ -0,0 +1,70 @@ +"use client"; +import { ArrowUpRight } from "lucide-react"; +import { contracts, type SupportedChainId } from "../lib/contracts"; +import { blockExplorers } from "../lib/contracts"; +import { Button, buttonVariants } from "../components/ui/button"; +import { useAccount } from "wagmi"; +import Link from "next/link"; +import { DarkModeToggle } from "../components/DarkModeToggle"; +import { DevModeToggle } from "../components/DevModeToggle"; +import Image from "next/image"; +import { useCoinbaseWallet } from "../context/CoinbaseWalletContext"; +import DisplayAddress, { truncateAddress } from "../components/util/DisplayAddress"; + +export const Header = () => { + const { isConnected, connect, disconnect, address, subAccount, createSubAccount } = + useCoinbaseWallet(); + + return ( +
+ +
+ Based Chess Logo +
Based Chess
+
+ + {process.env.NEXT_PUBLIC_WORKER_DOMAIN?.includes("localhost") && ( +
DEV
+ )} + {process.env.NEXT_PUBLIC_WORKER_DOMAIN?.includes("staging") && ( +
Sub Accounts Demo
+ )} +
+ {/* */} + {isConnected ? ( +
+ + Account: + + + {!subAccount && ( + + )} + + {subAccount && ( + + Sub Account: + + )} + + +
+ ) : ( + + )} +
+
+ ); +}; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4836852..f321769 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,14 +1,12 @@ import type { Metadata } from "next"; import localFont from "next/font/local"; -import { ConnectButton } from "@rainbow-me/rainbowkit"; import "./globals.css"; import Providers from "../components/providers/providers"; import "../../node_modules/@rainbow-me/rainbowkit/dist/index.css"; -import Link from "next/link"; -import Image from "next/image"; import { Footer } from "./footer"; import { useDevMode } from "../components/hooks/useLocalSettings"; import { ErudaEnabler } from "../components/util/ErudaEnabler"; +import { Header } from "./header"; const geistSans = localFont({ src: "./fonts/GeistVF.woff", variable: "--font-geist-sans", @@ -93,29 +91,7 @@ export default function RootLayout({ {/* // 98% to leave room for vertical scrollbar */}
-
- -
- Based Chess Logo -
Based Chess
-
- - {process.env.NEXT_PUBLIC_WORKER_DOMAIN?.includes("localhost") && ( -
DEV
- )} - {process.env.NEXT_PUBLIC_WORKER_DOMAIN?.includes("staging") && ( -
STAGING
- )} -
- -
-
+
{children} {/*
*/}