From 6d7d4e6837ae5cd56b7f168318730436c439704d Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Tue, 14 Jan 2025 13:20:41 -0500 Subject: [PATCH] Refactor nextapp for wasm --- examples/next/next.config.js | 41 +++- examples/next/package.json | 8 +- examples/next/src/app/layout.tsx | 20 +- examples/next/src/app/not-found.tsx | 11 + examples/next/src/app/page.tsx | 28 ++- examples/next/src/app/token/page.tsx | 134 ----------- .../next/src/components/ConnectWallet.tsx | 110 --------- examples/next/src/components/Header.tsx | 17 +- .../components/providers/StarknetProvider.tsx | 48 ++-- .../next/src/components/providers/index.tsx | 11 +- examples/next/tsconfig.json | 25 +- packages/connector/package.json | 7 +- packages/connector/src/controller.ts | 2 +- packages/connector/src/index.ts | 2 - packages/connector/src/session.ts | 2 +- packages/controller/package.json | 6 +- packages/controller/src/controller.ts | 1 + packages/controller/src/session/provider.ts | 18 +- .../src/components/session/MessageCard.tsx | 6 +- packages/keychain/src/hooks/connection.ts | 1 + pnpm-lock.yaml | 219 ++++++++++++------ 21 files changed, 313 insertions(+), 404 deletions(-) create mode 100644 examples/next/src/app/not-found.tsx delete mode 100644 examples/next/src/app/token/page.tsx delete mode 100644 examples/next/src/components/ConnectWallet.tsx diff --git a/examples/next/next.config.js b/examples/next/next.config.js index 7c3edd0f0..5e48a2319 100644 --- a/examples/next/next.config.js +++ b/examples/next/next.config.js @@ -6,29 +6,46 @@ const nextConfig = { externalDir: true, }, webpack: (config, { isServer, dev }) => { - // Use the client static directory in the server bundle and prod mode - // Fixes `Error occurred prerendering page "/"` - config.output.webassemblyModuleFilename = - isServer && !dev - ? "../static/wasm/[modulehash].wasm" - : "static/wasm/[modulehash].wasm"; + config.output.environment = { + ...config.output.environment, + asyncFunction: true, + }; // Since Webpack 5 doesn't enable WebAssembly by default, we should do it manually config.experiments = { ...config.experiments, asyncWebAssembly: true, - layers: true, + topLevelAwait: true, }; - // Add WASM file handling - config.module.rules.push({ - test: /\.wasm$/, - type: "webassembly/async", - }); + // https://github.com/vercel/next.js/issues/29362#issuecomment-971377869 + if (!dev && isServer) { + config.output.webassemblyModuleFilename = "chunks/[id].wasm"; + config.plugins.push(new WasmChunksFixPlugin()); + } return config; }, output: "standalone", }; +class WasmChunksFixPlugin { + apply(compiler) { + compiler.hooks.thisCompilation.tap("WasmChunksFixPlugin", (compilation) => { + compilation.hooks.processAssets.tap( + { name: "WasmChunksFixPlugin" }, + (assets) => + Object.entries(assets).forEach(([pathname, source]) => { + if (!pathname.match(/\.wasm$/)) return; + compilation.deleteAsset(pathname); + + const name = pathname.split("/")[1]; + const info = compilation.assetsInfo.get(pathname); + compilation.emitAsset(name, source, info); + }), + ); + }); + } +} + module.exports = nextConfig; diff --git a/examples/next/package.json b/examples/next/package.json index b5768d0c9..bdcf03792 100644 --- a/examples/next/package.json +++ b/examples/next/package.json @@ -4,7 +4,7 @@ "version": "0.5.8", "scripts": { "dev": "next dev -p 3002", - "build": "NEXT_TELEMETRY_DISABLED=1 next build", + "build": "next build", "e2e": "playwright test", "e2e:ui": "playwright test --ui", "start": "next start -p 3002", @@ -16,9 +16,9 @@ "@cartridge/connector": "workspace:*", "@cartridge/controller": "workspace:*", "@cartridge/ui-next": "workspace:*", - "@starknet-react/chains": "^0.1.3", + "@starknet-react/chains": "^3.0.2", "@starknet-react/core": "^3.0.2", - "next": "^14.2.15", + "next": "^14.0.0", "next-themes": "^0.3.0", "prettier": "^2.7.1", "react": "^18.3.1", @@ -29,7 +29,7 @@ "@cartridge/tsconfig": "workspace:*", "@playwright/test": "^1.46.0", "@types/node": "^20.6.0", - "@types/react": "^18.3.12", + "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", "autoprefixer": "^10.4.18", "eslint": "^8.23.0", diff --git a/examples/next/src/app/layout.tsx b/examples/next/src/app/layout.tsx index 7a7e47858..3087993e5 100644 --- a/examples/next/src/app/layout.tsx +++ b/examples/next/src/app/layout.tsx @@ -1,22 +1,20 @@ -import { Providers } from "components/providers"; +import { ReactNode } from "react"; import { Metadata } from "next"; -import { PropsWithChildren } from "react"; import "./globals.css"; +import { Providers } from "components/providers"; + +export const metadata: Metadata = { + title: "Cartridge Controller", + description: "Cartridge Controller Example", +}; -export default function RootLayout({ children }: PropsWithChildren) { +export default function RootLayout({ children }: { children: ReactNode }) { return ( - + {children} ); } - -export const metadata: Metadata = { - title: "Cartridge Controller - Example (Next.js)", - icons: { - icon: "favicon.ico", - }, -}; diff --git a/examples/next/src/app/not-found.tsx b/examples/next/src/app/not-found.tsx new file mode 100644 index 000000000..03a723868 --- /dev/null +++ b/examples/next/src/app/not-found.tsx @@ -0,0 +1,11 @@ +import { FC } from "react"; + +const NotFound: FC = () => { + return ( +
+

404 - Page Not Found

+
+ ); +}; + +export default NotFound; diff --git a/examples/next/src/app/page.tsx b/examples/next/src/app/page.tsx index adb934ec9..9e2a00b05 100644 --- a/examples/next/src/app/page.tsx +++ b/examples/next/src/app/page.tsx @@ -1,16 +1,20 @@ -import { Transfer } from "components/Transfer"; -import { ManualTransferEth } from "components/ManualTransferEth"; -import { InvalidTxn } from "components/InvalidTxn"; -import { SignMessage } from "components/SignMessage"; -import { DelegateAccount } from "components/DelegateAccount"; +"use client"; + +import { FC } from "react"; + import { ColorModeToggle } from "components/ColorModeToggle"; -import { Profile } from "components/Profile"; -import { LookupControllers } from "components/LookupControllers"; import Header from "components/Header"; +import { DelegateAccount } from "components/DelegateAccount"; +import { InvalidTxn } from "components/InvalidTxn"; +import { LookupControllers } from "components/LookupControllers"; +import { ManualTransferEth } from "components/ManualTransferEth"; +import { Profile } from "components/Profile"; +import { SignMessage } from "components/SignMessage"; +import { Transfer } from "components/Transfer"; -export default function Home() { +const Home: FC = () => { return ( -
+

Controller Example (Next.js) @@ -25,6 +29,8 @@ export default function Home() { -

+
); -} +}; + +export default Home; diff --git a/examples/next/src/app/token/page.tsx b/examples/next/src/app/token/page.tsx deleted file mode 100644 index b1df03c5b..000000000 --- a/examples/next/src/app/token/page.tsx +++ /dev/null @@ -1,134 +0,0 @@ -"use client"; - -import { - useAccount, - useReadContract, - // useSendTransaction, -} from "@starknet-react/core"; -import { useMemo } from "react"; -import { cairo, uint256 } from "starknet"; -import { ConnectWallet } from "components/ConnectWallet"; -// import { useTokenContract } from "hooks/token"; -import { Abi } from "starknet"; -import Erc20Abi from "abi/erc20.json"; -// import { Button, Input } from "@cartridge/ui-next"; - -function UserBalance() { - const { account } = useAccount(); - - const { data, isLoading, error } = useReadContract({ - abi: Erc20Abi as Abi, - address: - "0x07394cbe418daa16e42b87ba67372d4ab4a5df0b05c6e554d158458ce245bc10", - functionName: "balanceOf", - args: account ? [account] : undefined, - }); - - const content = useMemo(() => { - if (isLoading || !(data as [])?.length) { - return
Loading balance
; - } - - if (error) { - console.error(error); - return
Error!
; - } - - const balance = uint256.uint256ToBN(cairo.uint256(data[0])); - return
{balance.toString(10)}
; - }, [data, isLoading, error]); - - return ( -
-

User balance

- {content} -
- ); -} - -// function MintToken() { -// const { account, address } = useAccount(); -// const [amount, setAmount] = useState(""); -// const [amountError, setAmountError] = useState(); - -// const { contract } = useTokenContract(); - -// const calls = useMemo(() => { -// if (!address || !contract) return []; - -// const amountBn = cairo.uint256(amount); -// return [contract.populateTransaction["mint"](address, [address, amountBn])]; -// }, [address, contract, amount]); - -// const { sendAsync, isPending, error, reset } = useSendTransaction({ -// calls, -// }); - -// const updateAmount = useCallback( -// (newAmount: string) => { -// // soft-validate amount -// setAmount(newAmount); -// try { -// BigInt(newAmount); -// setAmountError(undefined); -// } catch (err) { -// console.error(err); -// setAmountError("Please input a valid number"); -// } -// }, -// [setAmount], -// ); - -// const onMint = useCallback(() => { -// reset(); -// if (account && !amountError) { -// sendAsync(); -// } -// }, [account, amountError, reset, sendAsync]); - -// const mintButtonDisabled = useMemo(() => { -// if (isPending) return true; -// return !account || !!amountError; -// }, [isPending, account, amountError]); - -// return ( -//
-//

Mint token

-//

-// Amount: -// updateAmount(evt.target.value)} -// /> -//

-// -// {error && ( -//

-// <>Error: {error} -//

-// )} -//
-// ); -// } - -export default function TokenPage() { - const { address } = useAccount(); - - if (!address) { - return ( -
-

Connect Wallet

- -
- ); - } - return ( -
-

Connected: {address}

- - {/* */} -
- ); -} diff --git a/examples/next/src/components/ConnectWallet.tsx b/examples/next/src/components/ConnectWallet.tsx deleted file mode 100644 index af4fea030..000000000 --- a/examples/next/src/components/ConnectWallet.tsx +++ /dev/null @@ -1,110 +0,0 @@ -"use client"; - -import { useAccount, useConnect, useDisconnect } from "@starknet-react/core"; -import ControllerConnector from "@cartridge/connector/controller"; -import React, { useEffect, useState, useRef } from "react"; -import { Button } from "@cartridge/ui-next"; - -export function ConnectWallet() { - const { connect, connectors } = useConnect(); - const { disconnect } = useDisconnect(); - const { address } = useAccount(); - - const controller = connectors[0] as ControllerConnector; - const [isDragging, setIsDragging] = useState(false); - const [position, setPosition] = useState({ x: -1000, y: -1000 }); - const dragRef = useRef<{ startX: number; startY: number }>(); - - useEffect(() => { - setPosition({ - x: window.innerWidth - 220, - y: window.innerHeight - 220, - }); - }, []); - - const [username, setUsername] = useState(); - useEffect(() => { - if (!address) return; - controller.username()?.then((n) => setUsername(n)); - }, [address, controller]); - - const handleMouseDown = (e: React.MouseEvent) => { - setIsDragging(true); - dragRef.current = { - startX: e.pageX - position.x, - startY: e.pageY - position.y, - }; - }; - - const handleMouseMove = (e: React.MouseEvent) => { - if (!isDragging || !dragRef.current) return; - - setPosition({ - x: e.pageX - dragRef.current.startX, - y: e.pageY - dragRef.current.startY, - }); - }; - - const handleMouseUp = () => { - setIsDragging(false); - dragRef.current = undefined; - }; - - return ( -
- {address && ( - <> -

Account: {address}

- {username &&

Username: {username}

} - - )} - {address ? ( - - ) : ( -
- -
- )} - - {/* Draggable test overlay */} -
-
- Drag me around -
- (Clickjacking Test) -
-
-
- ); -} diff --git a/examples/next/src/components/Header.tsx b/examples/next/src/components/Header.tsx index 86e77b147..feb28abe5 100644 --- a/examples/next/src/components/Header.tsx +++ b/examples/next/src/components/Header.tsx @@ -9,16 +9,16 @@ import { useNetwork, useSwitchChain, } from "@starknet-react/core"; -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useMemo } from "react"; import { constants, num } from "starknet"; import { Chain } from "@starknet-react/chains"; +import SessionConnector from "@cartridge/connector/session"; const Header = () => { const { connect, connectors } = useConnect(); const { disconnect } = useDisconnect(); const { chain, chains } = useNetwork(); - const { address, connector, status } = useAccount(); - const controllerConnector = connector as never as ControllerConnector; + const { address, status } = useAccount(); const [networkOpen, setNetworkOpen] = useState(false); const [profileOpen, setProfileOpen] = useState(false); const { switchChain } = useSwitchChain({ @@ -28,9 +28,14 @@ const Header = () => { }); const networkRef = useRef(null); const profileRef = useRef(null); + const controllerConnector = useMemo( + () => ControllerConnector.fromConnectors(connectors), + [connectors], + ); - const sessionConnector = connectors.find( - (c) => c.id === "controller_session", + const sessionConnector = useMemo( + () => SessionConnector.fromConnectors(connectors), + [connectors], ); useEffect(() => { @@ -139,7 +144,7 @@ const Header = () => {