From 9428abaf7cc7c9c0e047ba1b36e73df2391f9e3f Mon Sep 17 00:00:00 2001 From: Amira Nasri Date: Fri, 26 Sep 2025 10:33:28 +0100 Subject: [PATCH 1/4] migrate hello-web3auth example from JSX to TSX --- eslint.config.js | 8 +- package.json | 3 + src/App.jsx | 27 ----- src/App.tsx | 26 ++++ src/components/{cards.jsx => cards.tsx} | 7 +- src/components/navigation.jsx | 71 ----------- src/components/navigation.tsx | 86 ++++++++++++++ src/{config.js => config.ts} | 0 src/context/provider.jsx | 118 ------------------- src/context/provider.tsx | 117 ++++++++++++++++++ src/context/signer.js | 29 ----- src/context/signer.ts | 59 ++++++++++ src/context/useNear.jsx | 29 ----- src/context/useNear.tsx | 23 ++++ src/main.jsx | 10 -- src/main.tsx | 14 +++ src/pages/{hello_near.jsx => hello_near.tsx} | 72 ++++++----- src/pages/{home.jsx => home.tsx} | 0 tsconfig.json | 16 +++ 19 files changed, 399 insertions(+), 316 deletions(-) delete mode 100644 src/App.jsx create mode 100644 src/App.tsx rename src/components/{cards.jsx => cards.tsx} (79%) delete mode 100644 src/components/navigation.jsx create mode 100644 src/components/navigation.tsx rename src/{config.js => config.ts} (100%) delete mode 100644 src/context/provider.jsx create mode 100644 src/context/provider.tsx delete mode 100644 src/context/signer.js create mode 100644 src/context/signer.ts delete mode 100644 src/context/useNear.jsx create mode 100644 src/context/useNear.tsx delete mode 100644 src/main.jsx create mode 100644 src/main.tsx rename src/pages/{hello_near.jsx => hello_near.tsx} (54%) rename src/pages/{home.jsx => home.tsx} (100%) create mode 100644 tsconfig.json diff --git a/eslint.config.js b/eslint.config.js index 238d2e4..c023055 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,20 +1,24 @@ +// eslint.config.js import js from '@eslint/js' import globals from 'globals' import react from 'eslint-plugin-react' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' export default [ { ignores: ['dist'] }, { - files: ['**/*.{js,jsx}'], + files: ['**/*.{js,jsx,ts,tsx}'], languageOptions: { ecmaVersion: 2020, globals: globals.browser, + parser: tseslint.parser, // 👈 add parser for TS parserOptions: { ecmaVersion: 'latest', ecmaFeatures: { jsx: true }, sourceType: 'module', + project: './tsconfig.json', }, }, settings: { react: { version: '18.3' } }, @@ -22,12 +26,14 @@ export default [ react, 'react-hooks': reactHooks, 'react-refresh': reactRefresh, + '@typescript-eslint': tseslint.plugin, // 👈 add TS plugin }, rules: { ...js.configs.recommended.rules, ...react.configs.recommended.rules, ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, + ...tseslint.configs.recommended.rules, // 👈 TS recommended rules 'react/jsx-no-target-blank': 'off', 'react-refresh/only-export-components': [ 'warn', diff --git a/package.json b/package.json index e37cac8..258b0fa 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "@eslint/js": "^9.17.0", "@types/react": "^18", "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^8.44.1", + "@typescript-eslint/parser": "^8.44.1", "@vitejs/plugin-react": "^4.3.4", "eslint": "^9.17.0", "eslint-plugin-react": "^7.37.2", @@ -38,6 +40,7 @@ "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", "prettier": "^3.6.2", + "typescript": "^5.9.2", "vite": "^6.3.5", "vite-plugin-node-polyfills": "^0.24.0" } diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index a138d56..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Navigation } from './components/navigation' -import Home from './pages/home' - -import HelloNear from './pages/hello_near' -import { BrowserRouter, HashRouter, Routes, Route } from 'react-router' - -import { NEARxWeb3Auth } from './context/provider' - -function App() { - // Use HashRouter when deployed under a subpath (GitHub Pages) - const useHashRouter = import.meta.env.BASE_URL !== '/' - const Router = useHashRouter ? HashRouter : BrowserRouter - - return ( - - - - - } /> - } /> - - - - ) -} - -export default App diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..72b80ca --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,26 @@ +import React, { ReactElement } from 'react'; +import { Navigation } from './components/navigation'; +import Home from './pages/home'; +import HelloNear from './pages/hello_near'; +import { BrowserRouter, HashRouter, Routes, Route } from 'react-router-dom'; +import { NEARxWeb3Auth } from './context/provider'; + +function App(): ReactElement { + // Use HashRouter when deployed under a subpath (GitHub Pages) + const useHashRouter: boolean = import.meta.env.BASE_URL !== '/'; + const Router = useHashRouter ? HashRouter : BrowserRouter; + + return ( + + + + + } /> + } /> + + + + ); +} + +export default App; diff --git a/src/components/cards.jsx b/src/components/cards.tsx similarity index 79% rename from src/components/cards.jsx rename to src/components/cards.tsx index b4ef121..88dc8b1 100644 --- a/src/components/cards.jsx +++ b/src/components/cards.tsx @@ -1,7 +1,8 @@ +import React from 'react' import styles from '@/styles/app.module.css' -import { Link } from 'react-router' +import { Link } from 'react-router-dom' -export const Cards = () => { +export const Cards: React.FC = () => { return (
{

Learn how this application works, and what you can build on Near.

- +

Near Integration ->

diff --git a/src/components/navigation.jsx b/src/components/navigation.jsx deleted file mode 100644 index 42d734f..0000000 --- a/src/components/navigation.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useEffect, useState } from 'react' - -import NearLogo from '@/assets/near-logo.svg' -import { Link } from 'react-router' -import styles from '@/styles/app.module.css' - -import { useNEARxWeb3Auth } from '../context/useNear' -import { NEAR } from '@near-js/tokens' - -export const Navigation = () => { - const [action, setAction] = useState(() => {}) - const [label, setLabel] = useState('Loading...') - const [balance, setBalance] = useState(0) - - const { walletId, nearAccount, web3AuthUser, login, logout, loading } = - useNEARxWeb3Auth() - - useEffect(() => { - if (loading) return setLabel('Loading...') - - if (walletId) { - const userId = web3AuthUser?.email || walletId - setAction(() => logout) - setLabel(`Logout ${userId}`) - } else { - setAction(() => login) - setLabel('Login') - setBalance(null) - } - }, [web3AuthUser, loading, login, logout, walletId]) - - useEffect(() => { - if (walletId) { - nearAccount - .getBalance() - .then((b) => setBalance(Number(NEAR.toDecimal(b)).toFixed(2))) - .catch(() => setBalance(0)) // non existing account - } - }, [walletId, nearAccount]) - - return ( - <> - - - ) -} diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx new file mode 100644 index 0000000..f138a16 --- /dev/null +++ b/src/components/navigation.tsx @@ -0,0 +1,86 @@ +import { useEffect, useState } from 'react' +import NearLogo from '@/assets/near-logo.svg' +import { Link } from 'react-router-dom' +import styles from '@/styles/app.module.css' + +import { useNEARxWeb3Auth } from '../context/useNear' +import { NEAR } from '@near-js/tokens' + +export const Navigation = (): JSX.Element => { + const [label, setLabel] = useState('Loading...') + const [balance, setBalance] = useState(null) + + const { walletId, nearAccount, web3AuthUser, login, logout, loading } = + useNEARxWeb3Auth() + + useEffect(() => { + if (loading) { + setLabel('Loading...') + return + } + + if (walletId) { + const userId = web3AuthUser?.email || walletId + setLabel(`Logout ${userId}`) + } else { + setLabel('Login') + setBalance(null) + } + }, [web3AuthUser, loading, walletId]) + + useEffect(() => { + if (walletId && nearAccount) { + nearAccount + .getBalance() + .then((b: bigint) => setBalance(Number(NEAR.toDecimal(b)).toFixed(2))) + .catch(() => setBalance('0')) // non-existing account + } + }, [walletId, nearAccount]) + + const handleClick = async () => { + console.log("handleClick fired, walletId:", walletId) + + try { + if (walletId) { + console.log("Logging out...") + await logout() + } else { + console.log("Logging in... opening modal") + await login() + } + } catch (err) { + console.error("Auth error:", err) + } + } + + return ( + + ) +} diff --git a/src/config.js b/src/config.ts similarity index 100% rename from src/config.js rename to src/config.ts diff --git a/src/context/provider.jsx b/src/context/provider.jsx deleted file mode 100644 index eaa944d..0000000 --- a/src/context/provider.jsx +++ /dev/null @@ -1,118 +0,0 @@ -// near api js -import { JsonRpcProvider } from '@near-js/providers' -import { Account } from '@near-js/accounts' - -// utils -import { base58 } from '@scure/base' - -// config -import { useEffect } from 'react' -import { useState } from 'react' -import { NearContext } from './useNear' -import { providerUrl } from '../config' -import { tssLib } from '@toruslabs/tss-frost-lib' -import { - Web3AuthMPCCoreKit, - WEB3AUTH_NETWORK, - COREKIT_STATUS, -} from '@web3auth/mpc-core-kit' -import { web3AuthSigner } from './signer' -import { bytesToHex } from '@noble/hashes/utils' - -// config -const web3AuthClientId = - 'BMzaHK4oZHNeZeF5tAAHvlznY5H0k1lNs5WynTzE1Uxvr_fVIemzk-90v_hmnRIwFOuU4wbyMqazIvqth60yRRA' // get from https://dashboard.web3auth.io -const verifier = 'web3auth-test-near' -const googleClientId = - '17426988624-32m2gh1o1n5qve6govq04ue91sioruk7.apps.googleusercontent.com' - - -// Provider -const provider = new JsonRpcProvider({ url: providerUrl }) - -console.log(`${window.location.origin}${window.location.pathname}`) - -const coreKitInstance = new Web3AuthMPCCoreKit({ - web3AuthClientId, - web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, - storage: window.localStorage, - manualSync: true, - tssLib, - uxMode: 'popup', - baseUrl: `${window.location.origin}${window.location.pathname}`, -}) - -// eslint-disable-next-line react/prop-types -export function NEARxWeb3Auth({ children }) { - const [coreKitStatus, setCoreKitStatus] = useState( - COREKIT_STATUS.NOT_INITIALIZED - ) - const [walletId, setWalletId] = useState(null) - const [nearAccount, setNearAccount] = useState(null) - const [web3AuthUser, setWeb3AuthUser] = useState(null) - const [loading, setLoading] = useState(true) - - useEffect(() => { - const init = async () => { - await coreKitInstance.init() - - if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) { - const signer = new web3AuthSigner(coreKitInstance) - const publicKey = signer - .getPublicKey() - .toString() - .replace('ed25519:', '') - const walletId = bytesToHex(base58.decode(publicKey)) - const acc = new Account(walletId, provider, signer) - setWalletId(acc.accountId) - setNearAccount(acc) - setWeb3AuthUser(coreKitInstance.getUserInfo()) - } else { - setWalletId(null) - setNearAccount(null) - setWeb3AuthUser(null) - } - - setLoading(false) - } - init() - }, [coreKitStatus]) - - const login = async () => { - setLoading(true) - - await coreKitInstance.loginWithOAuth({ - subVerifierDetails: { - typeOfLogin: 'google', - verifier, - clientId: googleClientId, - }, - }) - - if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) { - await coreKitInstance.commitChanges() // Needed for new accounts - } - setCoreKitStatus(coreKitInstance.status) - } - - const logout = async () => { - await coreKitInstance.logout() - setCoreKitStatus(coreKitInstance.status) - } - - return ( - - {children} - - ) -} diff --git a/src/context/provider.tsx b/src/context/provider.tsx new file mode 100644 index 0000000..79024ed --- /dev/null +++ b/src/context/provider.tsx @@ -0,0 +1,117 @@ +// src/context/provider.tsx +import { ReactNode, useEffect, useState } from 'react'; +import { JsonRpcProvider } from '@near-js/providers'; +import { Account } from '@near-js/accounts'; +import { NearContext } from './useNear'; +import { providerUrl } from '../config'; +import { tssLib } from '@toruslabs/tss-frost-lib'; +import { + Web3AuthMPCCoreKit, + WEB3AUTH_NETWORK, + COREKIT_STATUS, +} from '@web3auth/mpc-core-kit'; +import { Web3AuthSigner } from './signer'; +import { bytesToHex } from '@noble/hashes/utils'; + +const web3AuthClientId = + 'BMzaHK4oZHNeZeF5tAAHvlznY5H0k1lNs5WynTzE1Uxvr_fVIemzk-90v_hmnRIwFOuU4wbyMqazIvqth60yRRA'; +const verifier = 'web3auth-test-near'; +const googleClientId = + '17426988624-32m2gh1o1n5qve6govq04ue91sioruk7.apps.googleusercontent.com'; + +const provider = new JsonRpcProvider({ url: providerUrl }); + +const coreKitInstance = new Web3AuthMPCCoreKit({ + web3AuthClientId, + web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, + storage: window.localStorage, + manualSync: true, + tssLib, + uxMode: 'popup', + baseUrl: window.location.origin, +}); + +interface ProviderProps { + children: ReactNode; +} + +export const NEARxWeb3Auth = ({ children }: ProviderProps) => { + const [coreKitStatus, setCoreKitStatus] = useState( + COREKIT_STATUS.NOT_INITIALIZED + ); + const [walletId, setWalletId] = useState(null); + const [nearAccount, setNearAccount] = useState(null); + const [web3AuthUser, setWeb3AuthUser] = useState | null>(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const init = async () => { + try { + await coreKitInstance.init(); + + if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) { + const publicKeyRaw = coreKitInstance.getPubKeyEd25519(); // Uint8Array + const walletIdHex = bytesToHex(Buffer.from(publicKeyRaw)); + const signer = new Web3AuthSigner(coreKitInstance, walletIdHex); + const acc = new Account(walletIdHex, provider, signer as any); + + setWalletId(acc.accountId); + setNearAccount(acc); + setWeb3AuthUser(coreKitInstance.getUserInfo()); + } else { + setWalletId(null); + setNearAccount(null); + setWeb3AuthUser(null); + } + } catch (err) { + console.error('CoreKit init failed:', err); + } finally { + setLoading(false); + } + }; + + init(); + }, [coreKitStatus]); + + const login = async () => { + setLoading(true); + try { + await coreKitInstance.loginWithOAuth({ + subVerifierDetails: { typeOfLogin: 'google', verifier, clientId: googleClientId }, + }); + + if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) { + await coreKitInstance.commitChanges(); + } + + setCoreKitStatus(coreKitInstance.status); + } catch (err) { + console.error('Login failed:', err); + } finally { + setLoading(false); + } + }; + + const logout = async () => { + setLoading(true); + try { + await coreKitInstance.logout(); + setCoreKitStatus(coreKitInstance.status); + setWalletId(null); + setNearAccount(null); + setWeb3AuthUser(null); + } catch (err) { + console.error('Logout failed:', err); + } finally { + setLoading(false); + } + }; + + return ( + + {children} + + ); +}; diff --git a/src/context/signer.js b/src/context/signer.js deleted file mode 100644 index 9af23aa..0000000 --- a/src/context/signer.js +++ /dev/null @@ -1,29 +0,0 @@ -import { PublicKey } from "@near-js/crypto" -import { Signature, SignedTransaction } from "@near-js/transactions" -import { sha256 } from "@noble/hashes/sha2" -import { bytesToHex, hexToBytes } from "@noble/hashes/utils" - -// A Signer class that implements the `Signer` interface from near-api-js -export class web3AuthSigner { - constructor(coreKitInstance) { - this.coreKitInstance = coreKitInstance - } - - getPublicKey() { - let web3AuthKey = this.coreKitInstance.getPubKeyEd25519() - return new PublicKey({ keyType: 0, data: Uint8Array.from(web3AuthKey) }) - } - - async signTransaction(transaction) { - const encoded = transaction.encode() - const txHash = bytesToHex(sha256(encoded)) - const signatureRaw = await this.coreKitInstance.sign( - hexToBytes(txHash) - ) - const signature = new Signature({ - keyType: transaction.publicKey.keyType, - data: signatureRaw, - }) - return [[], new SignedTransaction({ transaction, signature })] - } -} \ No newline at end of file diff --git a/src/context/signer.ts b/src/context/signer.ts new file mode 100644 index 0000000..2739c91 --- /dev/null +++ b/src/context/signer.ts @@ -0,0 +1,59 @@ +import { PublicKey } from "@near-js/crypto"; +import { Signature, SignedTransaction, Transaction } from "@near-js/transactions"; +import { sha256 } from "@noble/hashes/sha2"; +import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; + +export interface SignedMessage { + accountId: string; + signature: Uint8Array; + publicKey: PublicKey; +} + +export class Web3AuthSigner { + coreKitInstance: any; + accountId: string; + + constructor(coreKitInstance: any, accountId: string) { + this.coreKitInstance = coreKitInstance; + this.accountId = accountId; + } + + async getPublicKey(): Promise { + const web3AuthKey = this.coreKitInstance.getPubKeyEd25519(); + return new PublicKey({ keyType: 0, data: Uint8Array.from(web3AuthKey) }); + } + + async signNep413Message( + message: string, + accountId: string, + recipient: string, + nonce: Uint8Array, + callbackUrl?: string + ): Promise { + const messageBytes = new TextEncoder().encode(message); + const hash = sha256(messageBytes); + const signatureRaw = await this.coreKitInstance.sign(hash); + const pubKey = await this.getPublicKey(); + + return { + accountId, + signature: new Uint8Array(signatureRaw), + publicKey: pubKey, + }; + } + + async signDelegateAction(_delegateAction: any): Promise { + return new Uint8Array(); + } + + async signTransaction(transaction: Transaction) { + const encoded = transaction.encode(); + const txHash = bytesToHex(sha256(encoded)); + const signatureRaw = await this.coreKitInstance.sign(hexToBytes(txHash)); + const signature = new Signature({ + keyType: transaction.publicKey.keyType, + data: signatureRaw, + }); + return [[], new SignedTransaction({ transaction, signature })]; + } +} diff --git a/src/context/useNear.jsx b/src/context/useNear.jsx deleted file mode 100644 index aeac0fb..0000000 --- a/src/context/useNear.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useContext, createContext } from 'react' - -/** - * @typedef {Object} NearContext - * @property {import('@near-js/accounts').Account | null} nearAccount The NEAR account instance - * @property {string} walletId The user's NEAR wallet address - * @property {import('@near-js/providers').JsonRpcProvider} provider A NEAR JSON RPC provider - */ - -/** @type {import('react').Context} */ -export const NearContext = createContext({ - walletId: null, - web3AuthUser: null, - nearAccount: null, - provider: undefined, - loading: Boolean, - login: async () => {}, - logout: async () => {}, -}) - -export function useNEARxWeb3Auth() { - const context = useContext(NearContext) - if (!context) { - throw new Error( - 'useNEARxWeb3Auth must be used within a provider' - ) - } - return context -} diff --git a/src/context/useNear.tsx b/src/context/useNear.tsx new file mode 100644 index 0000000..3dacb44 --- /dev/null +++ b/src/context/useNear.tsx @@ -0,0 +1,23 @@ +import { createContext, useContext } from 'react'; +import { Account } from '@near-js/accounts'; +import { JsonRpcProvider } from '@near-js/providers'; + +export interface NearContextType { + provider?: JsonRpcProvider; + nearAccount: Account | null; + walletId: string | null; + web3AuthUser: Record | null; + loading: boolean; + login: () => Promise; + logout: () => Promise; +} + +export const NearContext = createContext(undefined); + +export function useNEARxWeb3Auth(): NearContextType { + const context = useContext(NearContext); + if (!context) { + throw new Error('useNEARxWeb3Auth must be used within a provider'); + } + return context; +} diff --git a/src/main.jsx b/src/main.jsx deleted file mode 100644 index 54b3afd..0000000 --- a/src/main.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './styles/globals.css' -import App from './App.jsx' - -createRoot(document.getElementById('root')).render( - - - -) diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..0d7e847 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,14 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles/globals.css'; +import App from './App'; + +const rootElement = document.getElementById('root'); + +if (rootElement) { + createRoot(rootElement).render( + + + + ); +} diff --git a/src/pages/hello_near.jsx b/src/pages/hello_near.tsx similarity index 54% rename from src/pages/hello_near.jsx rename to src/pages/hello_near.tsx index 7072622..7cda3f6 100644 --- a/src/pages/hello_near.jsx +++ b/src/pages/hello_near.tsx @@ -1,47 +1,60 @@ -import { useEffect, useState } from 'react' - +import { useEffect, useState, useCallback } from 'react' import { Cards } from '@/components/cards' import styles from '@/styles/app.module.css' - import { HelloNearContract } from '@/config' import { useNEARxWeb3Auth } from '../context/useNear' -import { useCallback } from 'react' +import { JsonRpcProvider } from '@near-js/providers' +import { Account } from '@near-js/accounts' // Contract that the app will interact with const CONTRACT = HelloNearContract -export default function HelloNear() { - const [greeting, setGreeting] = useState('loading...') - const [newGreeting, setNewGreeting] = useState('loading...') - const [loggedIn, setLoggedIn] = useState(false) - const [showSpinner, setShowSpinner] = useState(false) +export default function HelloNear(): JSX.Element { + const [greeting, setGreeting] = useState('loading...') + const [newGreeting, setNewGreeting] = useState('loading...') + const [loggedIn, setLoggedIn] = useState(false) + const [showSpinner, setShowSpinner] = useState(false) - const { provider, nearAccount, walletId } = useNEARxWeb3Auth() + const { provider, nearAccount, walletId } = useNEARxWeb3Auth() as { + provider: JsonRpcProvider + nearAccount: Account | null + walletId: string | null + } const fetchGreeting = useCallback(async () => { - const greeting = await provider.callFunction(CONTRACT, 'get_greeting', {}) - setGreeting(greeting) + if (!provider) return + try { + const greeting = await provider.callFunction(CONTRACT, 'get_greeting', {}) + setGreeting(greeting as string) + } catch (e) { + console.error('Error fetching greeting:', e) + setGreeting('Error fetching greeting') + } }, [provider]) const saveGreeting = async () => { - nearAccount - .callFunction({ + if (!nearAccount) { + alert('No NEAR account connected') + return + } + + try { + await nearAccount.callFunction({ contractId: CONTRACT, methodName: 'set_greeting', args: { greeting: newGreeting }, }) - .catch((e) => { - alert( - `Error, did you deposit any NEAR Ⓝ? You can get some at https://dev.near.org/faucet` - ) - console.log(`Error saving greeting: ${e.message}`) - fetchGreeting() - }) - - setShowSpinner(true) - await new Promise((resolve) => setTimeout(resolve, 300)) - setGreeting(newGreeting) - setShowSpinner(false) + setShowSpinner(true) + await new Promise((resolve) => setTimeout(resolve, 300)) + setGreeting(newGreeting) + setShowSpinner(false) + } catch (e: any) { + alert( + `Error, did you deposit any NEAR Ⓝ? You can get some at https://dev.near.org/faucet` + ) + console.error('Error saving greeting:', e?.message ?? e) + fetchGreeting() + } } useEffect(() => { @@ -65,6 +78,7 @@ export default function HelloNear() {

The contract says: {greeting}

+