From fe78377baec368055ee4e2bfe8a782e81192f59a Mon Sep 17 00:00:00 2001 From: Amira Nasri Date: Tue, 30 Sep 2025 17:27:20 +0100 Subject: [PATCH 1/4] convert counter-example to ts --- frontend/jsconfig.json | 7 - frontend/next-env.d.ts | 6 + frontend/package.json | 41 ++--- .../{Navigation.jsx => Navigation.tsx} | 26 ++- frontend/src/{config.js => config.ts} | 0 frontend/src/global.d.ts | 6 + frontend/src/pages/_app.js | 46 ----- frontend/src/pages/_app.tsx | 53 ++++++ frontend/src/pages/index.js | 140 --------------- frontend/src/pages/index.tsx | 166 ++++++++++++++++++ .../wallets/{web3modal.js => web3modal.tsx} | 0 frontend/tsconfig.json | 27 +++ 12 files changed, 297 insertions(+), 221 deletions(-) delete mode 100644 frontend/jsconfig.json create mode 100644 frontend/next-env.d.ts rename frontend/src/components/{Navigation.jsx => Navigation.tsx} (59%) rename frontend/src/{config.js => config.ts} (100%) create mode 100644 frontend/src/global.d.ts delete mode 100644 frontend/src/pages/_app.js create mode 100644 frontend/src/pages/_app.tsx delete mode 100644 frontend/src/pages/index.js create mode 100644 frontend/src/pages/index.tsx rename frontend/src/wallets/{web3modal.js => web3modal.tsx} (100%) create mode 100644 frontend/tsconfig.json diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json deleted file mode 100644 index b8d6842..0000000 --- a/frontend/jsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "@/*": ["./src/*"] - } - } -} diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts new file mode 100644 index 0000000..00a8785 --- /dev/null +++ b/frontend/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/frontend/package.json b/frontend/package.json index 61c2073..5594aff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,29 +13,30 @@ }, "dependencies": { "@near-js/providers": "^1.0.1", - "@near-wallet-selector/bitte-wallet": "^8.10.0", - "@near-wallet-selector/core": "^8.10.0", - "@near-wallet-selector/ethereum-wallets": "^8.10.0", - "@near-wallet-selector/here-wallet": "^8.10.0", - "@near-wallet-selector/hot-wallet": "^8.10.0", - "@near-wallet-selector/ledger": "^8.10.0", - "@near-wallet-selector/meteor-wallet": "^8.10.0", - "@near-wallet-selector/meteor-wallet-app": "^8.10.0", - "@near-wallet-selector/modal-ui": "^8.10.0", - "@near-wallet-selector/my-near-wallet": "^8.10.0", - "@near-wallet-selector/near-mobile-wallet": "^8.10.0", - "@near-wallet-selector/nightly": "^8.10.0", - "@near-wallet-selector/react-hook": "^8.9.15", - "@near-wallet-selector/sender": "^8.10.0", - "@near-wallet-selector/welldone-wallet": "^8.10.0", - "@reown/appkit": "^1.6.9", - "@reown/appkit-adapter-wagmi": "^1.6.9", + "@near-wallet-selector/bitte-wallet": "^9.0.2", + "@near-wallet-selector/core": "^9.0.2", + "@near-wallet-selector/ethereum-wallets": "^9.0.2", + "@near-wallet-selector/here-wallet": "^9.0.2", + "@near-wallet-selector/hot-wallet": "^9.0.2", + "@near-wallet-selector/ledger": "^9.0.2", + "@near-wallet-selector/meteor-wallet": "^9.0.2", + "@near-wallet-selector/meteor-wallet-app": "^9.0.2", + "@near-wallet-selector/modal-ui": "^9.0.2", + "@near-wallet-selector/my-near-wallet": "^9.0.2", + "@near-wallet-selector/near-mobile-wallet": "^9.0.2", + "@near-wallet-selector/nightly": "^9.0.2", + "@near-wallet-selector/react-hook": "^9.0.2", + "@near-wallet-selector/sender": "^9.0.2", + "@near-wallet-selector/welldone-wallet": "^9.0.2", + "@reown/appkit": "^1.7.7", + "@reown/appkit-adapter-wagmi": "^1.7.7", + "@wagmi/core": "^2.17.2", "bootstrap": "^5", "bootstrap-icons": "^1.11.3", - "near-api-js": "^5.0.1", - "next": "15.2.1", + "next": "^15", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "viem": "^2.30.5" }, "devDependencies": { "encoding": "^0.1.13", diff --git a/frontend/src/components/Navigation.jsx b/frontend/src/components/Navigation.tsx similarity index 59% rename from frontend/src/components/Navigation.jsx rename to frontend/src/components/Navigation.tsx index 1efc3d5..c8b4608 100644 --- a/frontend/src/components/Navigation.jsx +++ b/frontend/src/components/Navigation.tsx @@ -3,12 +3,13 @@ import Link from 'next/link'; import { useEffect, useState } from 'react'; import { useWalletSelector } from '@near-wallet-selector/react-hook'; -import NearLogo from '/public/near-logo.svg'; export const Navigation = () => { const { signedAccountId, signIn, signOut } = useWalletSelector(); - const [action, setAction] = useState(() => { }); - const [label, setLabel] = useState('Loading...'); + + // Typing the action as a function returning void + const [action, setAction] = useState<() => void>(() => () => {}); + const [label, setLabel] = useState('Loading...'); useEffect(() => { if (signedAccountId) { @@ -18,18 +19,27 @@ export const Navigation = () => { setAction(() => signIn); setLabel('Login'); } - }, [signedAccountId]); + }, [signedAccountId, signIn, signOut]); return ( ); -}; \ No newline at end of file +}; diff --git a/frontend/src/config.js b/frontend/src/config.ts similarity index 100% rename from frontend/src/config.js rename to frontend/src/config.ts diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts new file mode 100644 index 0000000..14c22bb --- /dev/null +++ b/frontend/src/global.d.ts @@ -0,0 +1,6 @@ +declare module "*.png" { + const value: string; + export default value; + +} +declare module "*.module.css"; diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js deleted file mode 100644 index 4280920..0000000 --- a/frontend/src/pages/_app.js +++ /dev/null @@ -1,46 +0,0 @@ -import '@/styles/globals.css'; -import '@near-wallet-selector/modal-ui/styles.css'; - -import { setupMyNearWallet } from '@near-wallet-selector/my-near-wallet'; -import { setupMeteorWallet } from '@near-wallet-selector/meteor-wallet'; -import { setupMeteorWalletApp } from '@near-wallet-selector/meteor-wallet-app'; -import { setupBitteWallet } from '@near-wallet-selector/bitte-wallet'; -import { setupEthereumWallets } from '@near-wallet-selector/ethereum-wallets'; -import { setupHotWallet } from '@near-wallet-selector/hot-wallet'; -import { setupLedger } from '@near-wallet-selector/ledger'; -import { setupSender } from '@near-wallet-selector/sender'; -import { setupHereWallet } from '@near-wallet-selector/here-wallet'; -import { setupNearMobileWallet } from '@near-wallet-selector/near-mobile-wallet'; -import { setupWelldoneWallet } from '@near-wallet-selector/welldone-wallet'; -import { WalletSelectorProvider } from '@near-wallet-selector/react-hook'; -import { wagmiAdapter, web3Modal } from '@/wallets/web3modal'; -import { Navigation } from '@/components/Navigation'; -import { NetworkId, CounterContract } from '@/config'; - -const walletSelectorConfig = { - network: NetworkId, - createAccessKeyFor: CounterContract, - modules: [ - setupEthereumWallets({ wagmiConfig: wagmiAdapter.wagmiConfig, web3Modal }), - setupBitteWallet(), - setupMeteorWallet(), - setupMeteorWalletApp({contractId: CounterContract}), - setupHotWallet(), - setupLedger(), - setupSender(), - setupHereWallet(), - setupNearMobileWallet(), - setupWelldoneWallet(), - setupMyNearWallet(), - ], -} - -export default function App({ Component, pageProps }) { - - return ( - - - - - ); -} \ No newline at end of file diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx new file mode 100644 index 0000000..d4b26c3 --- /dev/null +++ b/frontend/src/pages/_app.tsx @@ -0,0 +1,53 @@ +import "@/styles/globals.css"; +import "@near-wallet-selector/modal-ui/styles.css"; + +import type { AppProps } from "next/app"; +import { WalletSelectorProvider } from "@near-wallet-selector/react-hook"; +import { Navigation } from "@/components/Navigation"; +import { CounterContract, NetworkId } from "@/config"; + +// Wallet setups +import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; +import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; +import { setupMeteorWalletApp } from "@near-wallet-selector/meteor-wallet-app"; +import { setupBitteWallet } from "@near-wallet-selector/bitte-wallet"; +import { setupEthereumWallets } from "@near-wallet-selector/ethereum-wallets"; +import { setupHotWallet } from "@near-wallet-selector/hot-wallet"; +import { setupLedger } from "@near-wallet-selector/ledger"; +import { setupSender } from "@near-wallet-selector/sender"; +import { setupHereWallet } from "@near-wallet-selector/here-wallet"; +import { setupNearMobileWallet } from "@near-wallet-selector/near-mobile-wallet"; +import { setupWelldoneWallet } from "@near-wallet-selector/welldone-wallet"; + +// Ethereum adapters +import { wagmiAdapter, web3Modal } from "@/wallets/web3modal"; +import type { WalletModuleFactory } from "@near-wallet-selector/core"; + +const walletSelectorConfig: { + network: typeof NetworkId; + modules: WalletModuleFactory[]; +} = { + network: NetworkId, + modules: [ + setupEthereumWallets({ wagmiConfig: wagmiAdapter.wagmiConfig as any, web3Modal }), + setupBitteWallet(), + setupMeteorWallet(), + setupMeteorWalletApp({ contractId: CounterContract }), + setupHotWallet(), + setupLedger(), + setupSender(), + setupHereWallet(), + setupNearMobileWallet(), + setupWelldoneWallet(), + setupMyNearWallet(), + ] as WalletModuleFactory[], +}; + +export default function App({ Component, pageProps }: AppProps) { + return ( + + + + + ); +} \ No newline at end of file diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js deleted file mode 100644 index 387b599..0000000 --- a/frontend/src/pages/index.js +++ /dev/null @@ -1,140 +0,0 @@ -import { useEffect, useState } from 'react'; - -import styles from '@/styles/app.module.css'; -import { useWalletSelector } from '@near-wallet-selector/react-hook'; -import { CounterContract } from '@/config'; - - -export default function Home() { - const { signedAccountId, callFunction, viewFunction } = useWalletSelector(); - const [number, setNumber] = useState(0); - const [numberIncrement, setNumberIncrement] = useState(0); - - const [leftEyeVisible, setLeftEyeVisible] = useState(true); - const [rightEyeVisible, setRightEyeVisible] = useState(true); - const [tongueVisible, setTongueVisible] = useState(false); - const [dotOn, setDotOn] = useState(true); - - const [globalInterval, setGlobalInterval] = useState(null); - - useEffect(() => { - fetchNumber(); - - // Fetch the number every two seconds - let interval = setInterval(fetchNumber, 1500); - setGlobalInterval(interval); - - return () => clearInterval(interval); - }, []) - - useEffect(() => { - // interrupt the constant fetching of the number - clearInterval(globalInterval); - - // Debounce the increment call until the user stops clicking - const getData = setTimeout(() => { - if (numberIncrement === 0) return; - - setNumberIncrement(0); - - // Try to increment the counter, fetch the number afterwords - callFunction({ contractId: CounterContract, method: 'increment', args: { number: numberIncrement } }) - .finally(() => { - fetchNumber(); - let interval = setInterval(fetchNumber, 1500) - setGlobalInterval(interval); - }) - - }, 500) - - return () => clearTimeout(getData); - }, [numberIncrement]) - - const fetchNumber = async () => { - setDotOn(true); - console.log("fetching number") - const num = await viewFunction({ contractId: CounterContract, method: "get_num" }); - setNumber(num); - setDotOn(false); - } - - const call = (method) => async () => { - const methodToState = { - increment: () => { - setNumberIncrement(numberIncrement + 1) - setNumber(number + 1) - }, - decrement: () => { - setNumberIncrement(numberIncrement - 1) - setNumber(number - 1) - }, - reset: async () => { - setNumberIncrement(0) - setNumber(0) - callFunction({ contractId: CounterContract, method: 'reset' }).then(async () => { - await fetchNumber(); - }) - }, - } - - methodToState[method]?.(); - } - - return ( -
-

This global counter lives in the NEAR blockchain!

- {!signedAccountId &&
-

You'll need to sign in to interact with the counter:

-
} -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
= 0 ? 'smile' : 'cry'}`}>
-
-
-
-
{number}
-
-
-
- - -
-
-
- - - - -
-
-
-
-
- -
-
- ); -} \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx new file mode 100644 index 0000000..251052d --- /dev/null +++ b/frontend/src/pages/index.tsx @@ -0,0 +1,166 @@ +import { useEffect, useState } from 'react'; + +import styles from '@/styles/app.module.css'; +import { useWalletSelector } from '@near-wallet-selector/react-hook'; +import { CounterContract } from '@/config'; + +export default function Home() { + const { signedAccountId, callFunction, viewFunction } = useWalletSelector(); + + const [number, setNumber] = useState(0); + const [numberIncrement, setNumberIncrement] = useState(0); + + const [leftEyeVisible, setLeftEyeVisible] = useState(true); + const [rightEyeVisible, setRightEyeVisible] = useState(true); + const [tongueVisible, setTongueVisible] = useState(false); + const [dotOn, setDotOn] = useState(true); + + const [globalInterval, setGlobalInterval] = useState(null); + + // Fetch number initially and set interval + useEffect(() => { + fetchNumber(); + + const interval = setInterval(fetchNumber, 1500); + setGlobalInterval(interval); + + return () => { + if (interval) clearInterval(interval); + }; + }, []); + + // Handle debounced increment + useEffect(() => { + if (globalInterval) clearInterval(globalInterval); + + const timeout = setTimeout(() => { + if (numberIncrement === 0) return; + + const incrementValue = numberIncrement; + setNumberIncrement(0); + + callFunction({ + contractId: CounterContract, + method: 'increment', + args: { number: incrementValue }, + }).finally(() => { + fetchNumber(); + const interval = setInterval(fetchNumber, 1500); + setGlobalInterval(interval); + }); + }, 500); + + return () => clearTimeout(timeout); + }, [numberIncrement]); + + const fetchNumber = async () => { + setDotOn(true); + const num = await viewFunction({ contractId: CounterContract, method: 'get_num' }); + setNumber(num as number); + setDotOn(false); + }; + + type Method = 'increment' | 'decrement' | 'reset'; + const call = (method: Method) => async () => { + switch (method) { + case 'increment': + setNumberIncrement(prev => prev + 1); + setNumber(prev => prev + 1); + break; + case 'decrement': + setNumberIncrement(prev => prev - 1); + setNumber(prev => prev - 1); + break; + case 'reset': + setNumberIncrement(0); + setNumber(0); + await callFunction({ contractId: CounterContract, method: 'reset' }); + await fetchNumber(); + break; + } + }; + + return ( +
+

This global counter lives in the NEAR blockchain!

+ + {!signedAccountId && ( +
+

You'll need to sign in to interact with the counter:

+
+ )} + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
= 0 ? 'smile' : 'cry'}`}>
+
+
+
+
{number}
+
+ +
+
+ + +
+ +
+
+ + + + +
+
+
+ +
+
+
+
+ ); +} diff --git a/frontend/src/wallets/web3modal.js b/frontend/src/wallets/web3modal.tsx similarity index 100% rename from frontend/src/wallets/web3modal.js rename to frontend/src/wallets/web3modal.tsx diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..2b628ff --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "paths": { + "@/*": ["*"] + }, + "jsx": "preserve", + "strict": true, + "moduleResolution": "node", + "skipLibCheck": true, + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "noEmit": true, + "incremental": true, + "module": "esnext", + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "global.d.ts" + ], + "exclude": ["node_modules"] +} From 3e9a06c961b2eb7400dffba0e4d468e993e139bf Mon Sep 17 00:00:00 2001 From: Amira Nasri Date: Sun, 12 Oct 2025 23:40:08 +0100 Subject: [PATCH 2/4] feat/migrate-to-near-selector --- frontend/package.json | 32 +++---- frontend/src/components/Navigation.tsx | 22 +++-- frontend/src/hooks/useNear.tsx | 119 +++++++++++++++++++++++++ frontend/src/pages/_app.tsx | 44 +-------- frontend/src/pages/index.tsx | 6 +- 5 files changed, 145 insertions(+), 78 deletions(-) create mode 100644 frontend/src/hooks/useNear.tsx diff --git a/frontend/package.json b/frontend/package.json index 5594aff..55c2764 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,33 +12,23 @@ "lint": "next lint" }, "dependencies": { - "@near-js/providers": "^1.0.1", - "@near-wallet-selector/bitte-wallet": "^9.0.2", - "@near-wallet-selector/core": "^9.0.2", - "@near-wallet-selector/ethereum-wallets": "^9.0.2", - "@near-wallet-selector/here-wallet": "^9.0.2", - "@near-wallet-selector/hot-wallet": "^9.0.2", - "@near-wallet-selector/ledger": "^9.0.2", - "@near-wallet-selector/meteor-wallet": "^9.0.2", - "@near-wallet-selector/meteor-wallet-app": "^9.0.2", - "@near-wallet-selector/modal-ui": "^9.0.2", - "@near-wallet-selector/my-near-wallet": "^9.0.2", - "@near-wallet-selector/near-mobile-wallet": "^9.0.2", - "@near-wallet-selector/nightly": "^9.0.2", - "@near-wallet-selector/react-hook": "^9.0.2", - "@near-wallet-selector/sender": "^9.0.2", - "@near-wallet-selector/welldone-wallet": "^9.0.2", - "@reown/appkit": "^1.7.7", - "@reown/appkit-adapter-wagmi": "^1.7.7", - "@wagmi/core": "^2.17.2", + "@hot-labs/near-connect": "^0.6.2", + "@near-js/crypto": "^2.3.1", + "@near-js/providers": "^2.3.1", + "@near-js/transactions": "^2.3.1", + "@near-js/utils": "^2.3.1", + "@walletconnect/sign-client": "^2.21.9", "bootstrap": "^5", "bootstrap-icons": "^1.11.3", + "near-api-js": "^6.3.0", "next": "^15", "react": "^18", - "react-dom": "^18", - "viem": "^2.30.5" + "react-dom": "^18" }, "devDependencies": { + "@types/node": "24.7.2", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.1", "encoding": "^0.1.13", "eslint": "^9.16.0", "eslint-config-next": "15.0.3" diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx index c8b4608..e4c6146 100644 --- a/frontend/src/components/Navigation.tsx +++ b/frontend/src/components/Navigation.tsx @@ -1,15 +1,13 @@ -import Image from 'next/image'; -import Link from 'next/link'; -import { useEffect, useState } from 'react'; -import { useWalletSelector } from '@near-wallet-selector/react-hook'; - +import Image from "next/image"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useNear } from "@/hooks/useNear"; +import NearLogo from "../../public/near-logo.svg"; export const Navigation = () => { - const { signedAccountId, signIn, signOut } = useWalletSelector(); - - // Typing the action as a function returning void const [action, setAction] = useState<() => void>(() => () => {}); - const [label, setLabel] = useState('Loading...'); + const [label, setLabel] = useState("Loading..."); + const { signedAccountId, signIn, signOut } = useNear(); useEffect(() => { if (signedAccountId) { @@ -17,7 +15,7 @@ export const Navigation = () => { setLabel(`Logout ${signedAccountId}`); } else { setAction(() => signIn); - setLabel('Login'); + setLabel("Login"); } }, [signedAccountId, signIn, signOut]); @@ -27,7 +25,7 @@ export const Navigation = () => { NEAR { ); -}; +}; \ No newline at end of file diff --git a/frontend/src/hooks/useNear.tsx b/frontend/src/hooks/useNear.tsx new file mode 100644 index 0000000..f200a81 --- /dev/null +++ b/frontend/src/hooks/useNear.tsx @@ -0,0 +1,119 @@ +import { useCallback, useEffect, useState } from "react"; +import { JsonRpcProvider } from "@near-js/providers"; +import { NearConnector, NearWallet } from "@hot-labs/near-connect"; + +interface ConnectedWallet { + wallet: NearWallet; + accounts: { accountId: string }[]; +} + +interface FunctionCallParams { + contractId: string; + method: string; + args?: Record; + gas?: string; + deposit?: string; +} + +interface ViewFunctionParams { + contractId: string; + method: string; + args?: Record; +} + +let connector: NearConnector | undefined; +const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }); + +if (typeof window !== "undefined") { + connector = new NearConnector({ network: "testnet" }); +} + +export function useNear() { + const [wallet, setWallet] = useState(undefined); + const [signedAccountId, setSignedAccountId] = useState(""); + const [loading, setLoading] = useState(true); + + const signIn = useCallback(async () => { + if (!connector) return; + const connectedWallet = await connector.connect(); + console.log("Connected wallet", connectedWallet); + }, []); + + const signOut = useCallback(async () => { + if (!wallet || !connector) return; + await connector.disconnect(wallet); + console.log("Disconnected wallet"); + setWallet(undefined); + setSignedAccountId(""); + }, [wallet]); + + useEffect(() => { + if (!connector) return; + + async function reload() { + try { + const { wallet, accounts } = (await connector!.getConnectedWallet()) as ConnectedWallet; + setWallet(wallet); + setSignedAccountId(accounts[0]?.accountId || ""); + } catch { + setWallet(undefined); + setSignedAccountId(""); + } finally { + setLoading(false); + } + } + + const onSignOut = () => { + setWallet(undefined); + setSignedAccountId(""); + }; + + const onSignIn = async (payload: { wallet: NearWallet }) => { + console.log("Signed in with payload", payload); + setWallet(payload.wallet); + const accountId = await payload.wallet.getAddress(); + setSignedAccountId(accountId); + }; + + connector.on("wallet:signOut", onSignOut); + connector.on("wallet:signIn", onSignIn); + + reload(); + + return () => { + connector?.off("wallet:signOut", onSignOut); + connector?.off("wallet:signIn", onSignIn); + }; + }, []); + + const viewFunction = useCallback(async ({ contractId, method, args = {} }: ViewFunctionParams) => { + return provider.callFunction(contractId, method, args); + }, []); + + const callFunction = useCallback( + async ({ contractId, method, args = {}, gas = "30000000000000", deposit = "0" }: FunctionCallParams) => { + if (!wallet) throw new Error("Wallet not connected"); + return wallet.signAndSendTransaction({ + receiverId: contractId, + actions: [ + { + type: "FunctionCall", + params: { methodName: method, args, gas, deposit }, + }, + ], + }); + }, + [wallet] + ); + + return { + signedAccountId, + wallet, + signIn, + signOut, + loading, + viewFunction, + callFunction, + provider, + }; +} \ No newline at end of file diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index d4b26c3..f788c20 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -1,53 +1,13 @@ import "@/styles/globals.css"; -import "@near-wallet-selector/modal-ui/styles.css"; import type { AppProps } from "next/app"; -import { WalletSelectorProvider } from "@near-wallet-selector/react-hook"; import { Navigation } from "@/components/Navigation"; -import { CounterContract, NetworkId } from "@/config"; - -// Wallet setups -import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; -import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; -import { setupMeteorWalletApp } from "@near-wallet-selector/meteor-wallet-app"; -import { setupBitteWallet } from "@near-wallet-selector/bitte-wallet"; -import { setupEthereumWallets } from "@near-wallet-selector/ethereum-wallets"; -import { setupHotWallet } from "@near-wallet-selector/hot-wallet"; -import { setupLedger } from "@near-wallet-selector/ledger"; -import { setupSender } from "@near-wallet-selector/sender"; -import { setupHereWallet } from "@near-wallet-selector/here-wallet"; -import { setupNearMobileWallet } from "@near-wallet-selector/near-mobile-wallet"; -import { setupWelldoneWallet } from "@near-wallet-selector/welldone-wallet"; - -// Ethereum adapters -import { wagmiAdapter, web3Modal } from "@/wallets/web3modal"; -import type { WalletModuleFactory } from "@near-wallet-selector/core"; - -const walletSelectorConfig: { - network: typeof NetworkId; - modules: WalletModuleFactory[]; -} = { - network: NetworkId, - modules: [ - setupEthereumWallets({ wagmiConfig: wagmiAdapter.wagmiConfig as any, web3Modal }), - setupBitteWallet(), - setupMeteorWallet(), - setupMeteorWalletApp({ contractId: CounterContract }), - setupHotWallet(), - setupLedger(), - setupSender(), - setupHereWallet(), - setupNearMobileWallet(), - setupWelldoneWallet(), - setupMyNearWallet(), - ] as WalletModuleFactory[], -}; export default function App({ Component, pageProps }: AppProps) { return ( - + <> - + ); } \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 251052d..6e103ac 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,11 +1,11 @@ import { useEffect, useState } from 'react'; import styles from '@/styles/app.module.css'; -import { useWalletSelector } from '@near-wallet-selector/react-hook'; +import { useNear } from '@/hooks/useNear'; import { CounterContract } from '@/config'; export default function Home() { - const { signedAccountId, callFunction, viewFunction } = useWalletSelector(); + const { signedAccountId, callFunction, viewFunction } = useNear(); const [number, setNumber] = useState(0); const [numberIncrement, setNumberIncrement] = useState(0); @@ -15,7 +15,7 @@ export default function Home() { const [tongueVisible, setTongueVisible] = useState(false); const [dotOn, setDotOn] = useState(true); - const [globalInterval, setGlobalInterval] = useState(null); + const [globalInterval, setGlobalInterval] = useState | null>(null); // Fetch number initially and set interval useEffect(() => { From cb12e13f19bae1068b1eca832f496614ab305322 Mon Sep 17 00:00:00 2001 From: Amira Nasri Date: Wed, 15 Oct 2025 23:36:33 +0100 Subject: [PATCH 3/4] remove web3modal --- frontend/src/wallets/web3modal.tsx | 46 ------------------------------ 1 file changed, 46 deletions(-) delete mode 100644 frontend/src/wallets/web3modal.tsx diff --git a/frontend/src/wallets/web3modal.tsx b/frontend/src/wallets/web3modal.tsx deleted file mode 100644 index 55f4ba0..0000000 --- a/frontend/src/wallets/web3modal.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { injected,walletConnect } from '@wagmi/connectors'; -import { createAppKit } from "@reown/appkit/react"; -import { reconnect } from "@wagmi/core"; -import { nearTestnet } from "@reown/appkit/networks"; -import { WagmiAdapter } from "@reown/appkit-adapter-wagmi"; - -// Get your projectId at https://cloud.reown.com -const projectId = '5bb0fe33763b3bea40b8d69e4269b4ae'; - -const connectors = [ - walletConnect({ - projectId, - metadata: { - name: "Counters", - description: "Examples demonstrating integrations with NEAR blockchain", - url: "https://near.github.io/wallet-selector", - icons: ["https://near.github.io/wallet-selector/favicon.ico"], - }, - showQrModal: false, // showQrModal must be false - }), - injected({ shimDisconnect: true }), -]; - -export const wagmiAdapter = new WagmiAdapter({ - projectId, - connectors, - networks: [nearTestnet], -}); - -reconnect(wagmiAdapter.wagmiConfig); - -export const web3Modal = createAppKit({ - adapters: [wagmiAdapter], - projectId, - networks: [nearTestnet], - defaultNetwork: nearTestnet, - enableWalletConnect: true, - features: { - analytics: true, - swaps: false, - onramp: false, - email: false, // Smart accounts (Safe contract) not available on NEAR Protocol, only EOA. - socials: false, // Smart accounts (Safe contract) not available on NEAR Protocol, only EOA. - }, - coinbasePreference: "eoaOnly", // Smart accounts (Safe contract) not available on NEAR Protocol, only EOA. -}); From 0dcc7940f3cd3646f3eb1403c57f3eae7fe65b60 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Wed, 5 Nov 2025 21:15:08 -0300 Subject: [PATCH 4/4] chore: update navigation and useNear --- frontend/package.json | 3 +- frontend/src/components/Navigation.tsx | 33 ++--- frontend/src/hooks/useNear.tsx | 160 +++++++++++++------------ 3 files changed, 104 insertions(+), 92 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 55c2764..ad2ad95 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,7 @@ "@types/react-dom": "^19.2.1", "encoding": "^0.1.13", "eslint": "^9.16.0", - "eslint-config-next": "15.0.3" + "eslint-config-next": "15.0.3", + "typescript": "5.9.3" } } diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx index e4c6146..f6bb5fe 100644 --- a/frontend/src/components/Navigation.tsx +++ b/frontend/src/components/Navigation.tsx @@ -1,43 +1,44 @@ import Image from "next/image"; import Link from "next/link"; -import { useEffect, useState } from "react"; import { useNear } from "@/hooks/useNear"; -import NearLogo from "../../public/near-logo.svg"; +import NearLogo from "/public/near-logo.svg"; export const Navigation = () => { - const [action, setAction] = useState<() => void>(() => () => {}); - const [label, setLabel] = useState("Loading..."); - const { signedAccountId, signIn, signOut } = useNear(); + const { signedAccountId, loading, signIn, signOut } = useNear(); - useEffect(() => { + const handleAction = () => { if (signedAccountId) { - setAction(() => signOut); - setLabel(`Logout ${signedAccountId}`); + signOut(); } else { - setAction(() => signIn); - setLabel("Login"); + signIn(); } - }, [signedAccountId, signIn, signOut]); + }; + + const label = loading + ? "Loading..." + : signedAccountId + ? `Logout ${signedAccountId}` + : "Login"; return ( ); -}; \ No newline at end of file +}; diff --git a/frontend/src/hooks/useNear.tsx b/frontend/src/hooks/useNear.tsx index f200a81..7ff052f 100644 --- a/frontend/src/hooks/useNear.tsx +++ b/frontend/src/hooks/useNear.tsx @@ -1,110 +1,120 @@ -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { JsonRpcProvider } from "@near-js/providers"; -import { NearConnector, NearWallet } from "@hot-labs/near-connect"; +import type { NearConnector, NearWalletBase } from "@hot-labs/near-connect"; -interface ConnectedWallet { - wallet: NearWallet; - accounts: { accountId: string }[]; -} - -interface FunctionCallParams { +interface ViewFunctionParams { contractId: string; method: string; args?: Record; - gas?: string; - deposit?: string; } -interface ViewFunctionParams { +interface FunctionCallParams { contractId: string; method: string; args?: Record; + gas?: string; + deposit?: string; } let connector: NearConnector | undefined; const provider = new JsonRpcProvider({ url: "https://test.rpc.fastnear.com" }); -if (typeof window !== "undefined") { - connector = new NearConnector({ network: "testnet" }); -} - export function useNear() { - const [wallet, setWallet] = useState(undefined); - const [signedAccountId, setSignedAccountId] = useState(""); - const [loading, setLoading] = useState(true); + const [wallet, setWallet] = useState(undefined); + const [signedAccountId, setSignedAccountId] = useState(""); + const [loading, setLoading] = useState(true); - const signIn = useCallback(async () => { - if (!connector) return; - const connectedWallet = await connector.connect(); - console.log("Connected wallet", connectedWallet); - }, []); + useEffect(() => { + if (typeof window === "undefined") return; - const signOut = useCallback(async () => { - if (!wallet || !connector) return; - await connector.disconnect(wallet); - console.log("Disconnected wallet"); - setWallet(undefined); - setSignedAccountId(""); - }, [wallet]); + async function initializeConnector() { + if (!connector) { + const { NearConnector } = await import("@hot-labs/near-connect"); + connector = new NearConnector({ network: "testnet" }); + } - useEffect(() => { - if (!connector) return; + async function reload() { + try { + const { wallet, accounts } = await connector!.getConnectedWallet(); + setWallet(wallet); + setSignedAccountId(accounts[0].accountId); + } catch { + setWallet(undefined); + setSignedAccountId(""); + } finally { + setLoading(false); + } + } - async function reload() { - try { - const { wallet, accounts } = (await connector!.getConnectedWallet()) as ConnectedWallet; - setWallet(wallet); - setSignedAccountId(accounts[0]?.accountId || ""); - } catch { + async function onSignOut() { setWallet(undefined); setSignedAccountId(""); - } finally { - setLoading(false); } - } - const onSignOut = () => { - setWallet(undefined); - setSignedAccountId(""); - }; + async function onSignIn(payload: { wallet: NearWalletBase }) { + console.log("Signed in with payload", payload); + setWallet(payload.wallet); + const accounts = await payload.wallet.getAccounts(); + setSignedAccountId(accounts[0]?.accountId || ""); + } - const onSignIn = async (payload: { wallet: NearWallet }) => { - console.log("Signed in with payload", payload); - setWallet(payload.wallet); - const accountId = await payload.wallet.getAddress(); - setSignedAccountId(accountId); - }; + connector.on("wallet:signOut", onSignOut); + connector.on("wallet:signIn", onSignIn); - connector.on("wallet:signOut", onSignOut); - connector.on("wallet:signIn", onSignIn); + await reload(); - reload(); + return () => { + if (!connector) return; + connector.off("wallet:signOut", onSignOut); + connector.off("wallet:signIn", onSignIn); + }; + } - return () => { - connector?.off("wallet:signOut", onSignOut); - connector?.off("wallet:signIn", onSignIn); - }; + initializeConnector(); }, []); - const viewFunction = useCallback(async ({ contractId, method, args = {} }: ViewFunctionParams) => { - return provider.callFunction(contractId, method, args); - }, []); + async function signIn() { + if (!connector) return; + const wallet = await connector.connect(); + console.log("Connected wallet", wallet); + } - const callFunction = useCallback( - async ({ contractId, method, args = {}, gas = "30000000000000", deposit = "0" }: FunctionCallParams) => { - if (!wallet) throw new Error("Wallet not connected"); - return wallet.signAndSendTransaction({ - receiverId: contractId, - actions: [ - { - type: "FunctionCall", - params: { methodName: method, args, gas, deposit }, + async function signOut() { + if (!connector || !wallet) return; + await connector.disconnect(wallet); + console.log("Disonnected wallet"); + } + + async function viewFunction({ contractId, method, args = {} }: ViewFunctionParams) { + const response = await provider.query({ + request_type: "call_function", + account_id: contractId, + method_name: method, + args_base64: Buffer.from(JSON.stringify(args)).toString("base64"), + finality: "final", + }); + // @ts-ignore - response type from provider + return JSON.parse(Buffer.from(response.result).toString()); + } + + async function callFunction({ contractId, method, args = {}, gas = "30000000000000", deposit = "0" }: FunctionCallParams) { + if (!wallet) throw new Error("Wallet not connected"); + + return wallet.signAndSendTransaction({ + receiverId: contractId, + actions: [ + { + type: "FunctionCall", + params: { + methodName: method, + args, + gas, + deposit, }, - ], - }); - }, - [wallet] - ); + }, + ], + }); + } return { signedAccountId,