Creare un'interfaccia utente per il tuo contratto
Hai trovato una funzionalità di cui abbiamo bisogno nell'ecosistema di Ethereum. Hai scritto i contratti intelligenti per implementarla, e forse anche del codice correlato che viene eseguito fuori catena. È fantastico! Sfortunatamente, senza un'interfaccia utente non avrai alcun utente, e l'ultima volta che hai scritto un sito web le persone usavano modem dial-up e JavaScript era una novità.
Questo articolo fa per te. Presumo che tu conosca la programmazione, e forse un po' di JavaScript e HTML, ma che le tue competenze in materia di interfacce utente siano arrugginite e obsolete. Insieme esamineremo una semplice applicazione moderna in modo che tu possa vedere come si fa al giorno d'oggi.
Perché è importante
In teoria, potresti semplicemente far usare alle persone Etherscan (opens in a new tab) o Blockscout (opens in a new tab) per interagire con i tuoi contratti. Questo è ottimo per gli Ethereani esperti. Ma stiamo cercando di servire un altro miliardo di persone (opens in a new tab). Questo non accadrà senza un'ottima esperienza utente, e un'interfaccia utente amichevole ne è una parte importante.
Applicazione Greeter
C'è molta teoria dietro al funzionamento delle moderne interfacce utente, e molti ottimi siti (opens in a new tab) che la spiegano (opens in a new tab). Invece di ripetere l'ottimo lavoro svolto da quei siti, presumerò che tu preferisca imparare facendo e iniziare con un'applicazione con cui puoi giocare. Hai comunque bisogno della teoria per fare le cose, e ci arriveremo: esamineremo semplicemente file sorgente per file sorgente e discuteremo le cose man mano che le incontriamo.
Installazione
-
L'applicazione utilizza la rete di test Sepolia (opens in a new tab). Se necessario, ottieni ETH di test di Sepolia e aggiungi Sepolia al tuo portafoglio (opens in a new tab).
-
Clona il repository GitHub e installa i pacchetti necessari.
1git clone https://github.com/qbzzt/260301-modern-ui-web3.git2cd 260301-modern-ui-web33npm install
1
23. L'applicazione utilizza punti di accesso gratuiti, che presentano limitazioni di prestazioni. Se desideri utilizzare un fornitore di [Nodi come servizio](/developers/docs/nodes-and-clients/nodes-as-a-service/), sostituisci gli URL in [`src/wagmi.ts`](#wagmi-ts).3
44. Avvia l'applicazione.5
6 ```sh7 npm run dev-
Naviga all'URL mostrato dall'applicazione. Nella maggior parte dei casi, è http://localhost:5173/ (opens in a new tab).
-
Puoi vedere il codice sorgente del contratto, una versione modificata del Greeter di Hardhat, su un esploratore di blocchi (opens in a new tab).
Analisi dei file
index.html
Questo file è un boilerplate HTML standard, ad eccezione di questa riga, che importa il file di script.
1<script type="module" src="/src/main.tsx"></script>src/main.tsx
L'estensione del file indica che si tratta di un componente React (opens in a new tab) scritto in TypeScript (opens in a new tab), un'estensione di JavaScript che supporta il controllo dei tipi (opens in a new tab). TypeScript viene compilato in JavaScript, quindi possiamo usarlo lato client.
Questo file è spiegato principalmente nel caso in cui tu sia interessato. Di solito non si modifica questo file, ma src/App.tsx e i file che importa.
1import { QueryClient, QueryClientProvider } from '@tanstack/react-query'2import React from 'react'3import ReactDOM from 'react-dom/client'4import { WagmiProvider } from 'wagmi'Importa il codice della libreria di cui abbiamo bisogno.
1import App from './App.tsx'Importa il componente React che implementa l'applicazione (vedi sotto).
1import { config } from './wagmi.ts'Importa la configurazione di wagmi (opens in a new tab), che include la configurazione della blockchain.
1const queryClient = new QueryClient()Crea una nuova istanza del gestore della cache di React Query (opens in a new tab). Questo oggetto memorizzerà:
- Chiamate RPC memorizzate nella cache
- Letture del contratto
- Stato di recupero in background
Abbiamo bisogno del gestore della cache perché wagmi v3 utilizza React Query internamente.
1ReactDOM.createRoot(document.getElementById('root')!).render(Crea il componente React radice. Il parametro per render è JSX (opens in a new tab), un linguaggio di estensione che utilizza sia HTML che JavaScript/TypeScript. Il punto esclamativo qui dice al componente TypeScript: "non sai che document.getElementById('root') sarà un parametro valido per ReactDOM.createRoot, ma non preoccuparti: sono lo sviluppatore e ti dico che ci sarà".
1 <React.StrictMode>L'applicazione andrà all'interno di un componente React.StrictMode (opens in a new tab). Questo componente dice alla libreria React di inserire controlli di debug aggiuntivi, il che è utile durante lo sviluppo.
1 <WagmiProvider config={config}>L'applicazione è anche all'interno di un componente WagmiProvider (opens in a new tab). La libreria wagmi (we are going to make it) (opens in a new tab) collega le definizioni dell'interfaccia utente React con la libreria viem (opens in a new tab) per scrivere un'applicazione decentralizzata su Ethereum.
1 <QueryClientProvider client={queryClient}>E infine, aggiungi un provider React Query in modo che qualsiasi componente dell'applicazione possa utilizzare le query memorizzate nella cache.
1 <App />Ora possiamo avere il componente per l'applicazione, che implementa effettivamente l'interfaccia utente. Il /> alla fine del componente dice a React che questo componente non ha alcuna definizione al suo interno, secondo lo standard XML.
1 </QueryClientProvider>2 </WagmiProvider>3 </React.StrictMode>,4)Naturalmente, dobbiamo chiudere gli altri componenti.
src/App.tsx
1import {2 useConnect,3 useConnection,4 useDisconnect,5 useSwitchChain6} from 'wagmi'7
8import { useEffect } from 'react'9import { Greeter } from './Greeter'Importa le librerie di cui abbiamo bisogno, così come il componente Greeter.
1const SEPOLIA_CHAIN_ID = 11155111L'ID della catena Sepolia.
1function App() {Questo è il modo standard per creare un componente React: definire una funzione che viene chiamata ogni volta che deve essere renderizzata. Questa funzione contiene in genere codice TypeScript o JavaScript, seguito da un'istruzione return che restituisce il codice JSX.
1 const connection = useConnection()Usa useConnection (opens in a new tab) per ottenere informazioni relative alla connessione corrente, come l'indirizzo e il chainId.
Per convenzione, in React le funzioni chiamate use... sono hook (opens in a new tab). Queste funzioni non si limitano a restituire dati al componente; assicurano anche che venga renderizzato di nuovo (la funzione del componente viene eseguita di nuovo e il suo output sostituisce quello precedente nell'HTML) quando quei dati cambiano.
1 const { connectors, connect, status, error } = useConnect()Usa useConnect (opens in a new tab) per ottenere informazioni sulla connessione del portafoglio.
1 const { disconnect } = useDisconnect()Questo hook (opens in a new tab) ci fornisce la funzione per disconnetterci dal portafoglio.
1 const { switchChain } = useSwitchChain()Questo hook (opens in a new tab) ci permette di cambiare catena.
1 useEffect(() => {L'hook di React useEffect (opens in a new tab) ti consente di eseguire una funzione ogni volta che il valore di una variabile cambia per sincronizzare un sistema esterno.
1 if (connection.status === 'connected' &&2 connection.chainId !== SEPOLIA_CHAIN_ID3 ) {4 switchChain({ chainId: SEPOLIA_CHAIN_ID })5 }Se siamo connessi, ma non alla blockchain di Sepolia, passa a Sepolia.
1 }, [connection.status, connection.chainId])Esegui di nuovo la funzione ogni volta che lo stato della connessione o il chainId della connessione cambiano.
1 return (2 <>Il JSX di un componente React deve restituire un singolo componente HTML. Quando abbiamo più componenti e non abbiamo bisogno di un contenitore per avvolgerli tutti, usiamo un componente vuoto (<> ... </>) per combinarli in un singolo componente.
1 <h2>Connection</h2>2 <div>3 status: {connection.status}4 <br />5 addresses: {JSON.stringify(connection.addresses)}6 <br />7 chainId: {connection.chainId}8</div>Fornisci informazioni sulla connessione corrente. All'interno di JSX, {<expression>} significa valutare l'espressione come JavaScript.
1 {connection.status === 'connected' && (La sintassi {<condition> && <value>} significa "se la condizione è true, valuta il valore; se non lo è, valuta false`".
Questo è il modo standard per inserire istruzioni if all'interno di JSX.
1 <div>2 <Greeter />3 <hr />JSX segue lo standard XML, che è più rigoroso dell'HTML. Se un tag non ha un tag di chiusura corrispondente, deve avere una barra (/) alla fine per terminarlo.
Qui abbiamo due di questi tag, <Greeter /> (che in realtà contiene il codice HTML che comunica con il contratto) e <hr /> per una linea orizzontale (opens in a new tab).
1 <button type="button" onClick={disconnect}>2 Disconnect3 </button>4</div>5 )}Se l'utente fa clic su questo pulsante, chiama la funzione disconnect.
1 {connection.status !== 'connected' && (Se non siamo connessi, mostra le opzioni necessarie per connettersi al portafoglio.
1 <div>2 <h2>Connect</h2>3 {connectors.map((connector) => (In connectors abbiamo un elenco di connettori. Usiamo map (opens in a new tab) per trasformarlo in un elenco di pulsanti JSX da visualizzare.
1 <button2 key={connector.uid}In JSX è necessario che i tag "fratelli" (tag che discendono dallo stesso genitore) abbiano identificatori diversi.
1 onClick={() => connect({ connector })}2 type="button"3 >4 {connector.name}5 </button>6 ))}I pulsanti del connettore.
1 <div>{status}</div>2 <div>{error?.message}</div>3 4</div>5 )}Fornisci informazioni aggiuntive. La sintassi dell'espressione <variable>?.<field> dice a JavaScript che se la variabile è definita, valuta quel campo. Se la variabile non è definita, allora questa espressione valuta undefined.
L'espressione error.message, quando non ci sono errori, solleverebbe un'eccezione. L'uso di error?.message ci consente di evitare questo problema.
src/Greeter.tsx
Questo file contiene la maggior parte delle funzionalità dell'interfaccia utente. Include definizioni che normalmente si troverebbero in più file, ma poiché si tratta di un tutorial, il programma è ottimizzato per essere facile da comprendere la prima volta, piuttosto che per le prestazioni o la facilità di manutenzione.
1import {2 useState,3 useEffect,4 } from 'react'5import { useChainId,6 useAccount,7 useReadContract,8 useWriteContract,9 useWatchContractEvent,10 useSimulateContract11 } from 'wagmi'Usiamo queste funzioni di libreria. Anche in questo caso, sono spiegate di seguito dove vengono utilizzate.
1import { AddressType } from 'abitype'La libreria abitype (opens in a new tab) ci fornisce definizioni TypeScript per vari tipi di dati di Ethereum, come AddressType (opens in a new tab).
1let greeterABI = [2 { "type": "function", "name": "greet", ... },3 { "type": "function", "name": "setGreeting", ... },4 { "type": "event", "name": "SetGreeting", ... },5] as const // greeterABIL'ABI per il contratto Greeter.
Se stai sviluppando i contratti e l'interfaccia utente contemporaneamente, normalmente li metteresti nello stesso repository e useresti l'ABI generata dal compilatore Solidity come file nella tua applicazione. Tuttavia, questo non è necessario qui perché il contratto è già sviluppato e non cambierà.
Usiamo as const (opens in a new tab) per dire a TypeScript che questa è una vera costante. Normalmente, quando specifichi in JavaScript const x = {"a": 1}, puoi cambiare il valore in x, semplicemente non puoi assegnargli un nuovo valore.
1type AddressPerBlockchainType = {2 [key: number]: AddressType3}TypeScript è fortemente tipizzato. Usiamo questa definizione per specificare l'indirizzo in cui il contratto Greeter è distribuito su diverse catene. La chiave è un numero (il chainId) e il valore è un AddressType (un indirizzo).
1const contractAddrs : AddressPerBlockchainType = {2 // Sepolia3 11155111: '0xC87506C66c7896366b9E988FE0aA5B6dDE77CFfA'4}L'indirizzo del contratto su Sepolia (opens in a new tab).
Componente Timer
Il componente Timer mostra il numero di secondi trascorsi da un determinato momento. Questo è importante ai fini dell'usabilità. Quando gli utenti fanno qualcosa, si aspettano una reazione immediata. Nelle blockchain, questo è spesso impossibile perché non succede nulla finché una transazione non viene inserita in un blocco. Una soluzione è mostrare quanto tempo è trascorso da quando l'utente ha eseguito l'azione, in modo che l'utente possa decidere se il tempo richiesto è ragionevole.
1type TimerProps = {2 lastUpdate: Date3}Il componente Timer accetta un parametro, lastUpdate, che è l'ora dell'ultima azione.
1const Timer = ({ lastUpdate }: TimerProps) => {2 const [_, setNow] = useState(new Date())Dobbiamo avere uno stato (una variabile legata al componente) e aggiornarlo affinché il componente funzioni correttamente. Ma non abbiamo mai bisogno di leggerlo, quindi non preoccuparti di creare una variabile.
1 useEffect(() => {2 const id = setInterval(() => setNow(new Date()), 1000)3 return () => clearInterval(id)4 }, [])La funzione setInterval (opens in a new tab) ci consente di programmare l'esecuzione periodica di una funzione. In questo caso, ogni secondo. La funzione chiama setNow per aggiornare lo stato, in modo che il componente Timer venga renderizzato di nuovo. Avvolgiamo questo all'interno di useEffect (opens in a new tab) con un elenco di dipendenze vuoto in modo che avvenga solo una volta, piuttosto che ogni volta che il componente viene renderizzato.
1 const secondsSinceUpdate = Math.floor(2 (Date.now() - lastUpdate.getTime()) / 10003 )4
5 return (6 <span>{secondsSinceUpdate} seconds ago</span>7 )8}Calcola il numero di secondi dall'ultimo aggiornamento e restituiscilo.
Componente Greeter
1const Greeter = () => {Infine, arriviamo a definire il componente.
1 const chainId = useChainId()2 const account = useAccount()Informazioni sulla catena e sull'account che stiamo utilizzando, per gentile concessione di wagmi (opens in a new tab). Poiché si tratta di un hook (use...), il componente viene renderizzato di nuovo ogni volta che queste informazioni cambiano.
1 const greeterAddr = chainId && contractAddrs[chainId] L'indirizzo del contratto Greeter, che è undefined se non abbiamo informazioni sulla catena, o se siamo su una catena senza quel contratto.
1 const readResults = useReadContract({2 address: greeterAddr,3 abi: greeterABI,4 functionName: "greet", // Nessun argomento5 })L'hook useReadContract (opens in a new tab) chiama la funzione greet del contratto (opens in a new tab).
1 const [ currentGreeting, setCurrentGreeting ] = 2 useState("Please wait while we fetch the greeting from the blockchain...")3 const [ newGreeting, setNewGreeting ] = useState("")L'hook useState (opens in a new tab) di React ci consente di specificare una variabile di stato, il cui valore persiste da un rendering del componente all'altro. Il valore iniziale è il parametro, in questo caso la stringa vuota.
L'hook useState restituisce un elenco con due valori:
- Il valore corrente della variabile di stato.
- Una funzione per modificare la variabile di stato quando necessario. Poiché si tratta di un hook, ogni volta che viene chiamato il componente viene renderizzato di nuovo.
In questo caso, stiamo utilizzando una variabile di stato per il nuovo saluto che l'utente desidera impostare.
1 const [ lastSetterAddress, setLastSetterAddress ] = useState("")Se più utenti utilizzano lo stesso contratto contemporaneamente, potrebbero sovrascrivere i saluti degli altri. Questo sembrerebbe agli utenti come se l'applicazione non funzionasse correttamente. Se l'applicazione mostra chi ha impostato il saluto per ultimo, l'utente saprà che è stato qualcun altro e che l'applicazione funziona correttamente.
1 const [ status, setStatus ] = useState("")2 const [ statusTime, setStatusTime ] = useState(new Date())Agli utenti piace vedere che le loro azioni hanno un effetto immediato. Tuttavia, su una blockchain, non è così. Queste variabili di stato ci consentono almeno di visualizzare qualcosa agli utenti in modo che sappiano che la loro azione è in corso.
1 useEffect(() => {2 if (readResults.data) {3 setCurrentGreeting(readResults.data)4 setStatus("Greeting fetched from blockchain")5 }6 }, [readResults.data])Se readResults sopra modifica i dati e non è impostato su un valore falso (ad esempio undefined), aggiorna il saluto corrente a quello letto dalla blockchain. Inoltre, aggiorna lo stato.
1 useWatchContractEvent({2 address: greeterAddr,3 abi: greeterABI,4 eventName: 'SetGreeting',5 chainId,Ascolta gli eventi SetGreeting.
1 enabled: !!greeterAddr,!!<value> significa che se il valore è false, o un valore che viene valutato come falso, come undefined, 0 o una stringa vuota, l'espressione nel complesso è false. Per qualsiasi altro valore, è true. È un modo per convertire i valori in booleani, perché se non c'è greeterAddr, non vogliamo ascoltare gli eventi.
1 onLogs: logs => {2 const greetingFromContract = logs[0].args.greeting3 setCurrentGreeting(greetingFromContract)4 setLastSetterAddress(logs[0].args.sender)5 updateStatus("Greeting updated by event")6 },7 })Quando vediamo i log (il che accade quando vediamo un nuovo evento), significa che il saluto è stato modificato. In tal caso, possiamo aggiornare currentGreeting e lastSetterAddress ai nuovi valori. Inoltre, vogliamo aggiornare la visualizzazione dello stato.
1 const updateStatus = (newStatus: string) => {2 setStatus(newStatus)3 setStatusTime(new Date())4 }Quando aggiorniamo lo stato vogliamo fare due cose:
- Aggiornare la stringa di stato (
status) - Aggiornare l'ora dell'ultimo aggiornamento di stato (
statusTime) ad adesso.
1 const greetingChange = (evt) =>2 setNewGreeting(evt.target.value)Questo è il gestore di eventi per le modifiche al nuovo campo di input del saluto. Potremmo specificare il tipo del parametro evt, ma TypeScript è un linguaggio con tipi opzionali. Poiché questa funzione viene chiamata solo una volta, in un gestore di eventi HTML, non credo sia necessario.
1 const { writeContractAsync } = useWriteContract()La funzione per scrivere su un contratto. È simile a writeContracts (opens in a new tab), ma consente migliori aggiornamenti di stato.
1 const simulation = useSimulateContract({2 address: greeterAddr,3 abi: greeterABI,4 functionName: 'setGreeting',5 args: [newGreeting],6 account: account.address 7 })Questo è il processo per inviare una transazione blockchain dalla prospettiva del client:
- Invia la transazione a un nodo nella blockchain utilizzando
eth_estimateGas(opens in a new tab). - Attendi una risposta dal nodo.
- Quando viene ricevuta la risposta, chiedi all'utente di firmare la transazione tramite il portafoglio. Questo passaggio deve avvenire dopo aver ricevuto la risposta del nodo perché all'utente viene mostrato il costo del gas della transazione prima di firmarla.
- Attendi l'approvazione dell'utente.
- Invia di nuovo la transazione, questa volta utilizzando
eth_sendRawTransaction(opens in a new tab).
È probabile che il passaggio 2 richieda una quantità di tempo percettibile, durante la quale gli utenti potrebbero chiedersi se il loro comando sia stato ricevuto dall'interfaccia utente e perché non venga ancora chiesto loro di firmare la transazione. Ciò crea una scarsa esperienza utente (UX).
Una soluzione è inviare eth_estimateGas ogni volta che un parametro cambia. Quindi, quando l'utente desidera effettivamente inviare la transazione (in questo caso premendo Update greeting), il costo del gas è noto e l'utente può vedere immediatamente la pagina del portafoglio.
1 return (Ora possiamo finalmente creare l'HTML effettivo da restituire.
1 <>2 <h2>Greeter</h2>3 {currentGreeting}Mostra il saluto corrente.
1 {lastSetterAddress && (2 <p>Last updated by {3 lastSetterAddress === account.address ? "you" : lastSetterAddress4 }</p>5 )}Se sappiamo chi ha impostato il saluto per ultimo, visualizza tali informazioni. Greeter non tiene traccia di queste informazioni e non vogliamo guardare indietro per gli eventi SetGreeting, quindi le otteniamo solo una volta che il saluto viene modificato mentre siamo in esecuzione.
1 <hr /> 2 <input type="text"3 value={newGreeting}4 onChange={greetingChange}5 /> 6 <br />Questo è il campo di testo di input in cui l'utente può impostare un nuovo saluto. Ogni volta che l'utente preme un tasto, chiamiamo greetingChange, che chiama setNewGreeting. Poiché setNewGreeting proviene da useState, fa sì che il componente Greeter venga renderizzato di nuovo. Questo significa che:
- Dobbiamo specificare
valueper mantenere il valore del nuovo saluto, perché altrimenti tornerebbe al valore predefinito, la stringa vuota. - Anche
simulationviene aggiornato ogni volta chenewGreetingcambia, il che significa che otterremo una simulazione con il saluto corretto. Questo potrebbe essere rilevante perché il costo del gas dipende dalla dimensione dei dati della chiamata, che dipende dalla lunghezza della stringa.
1 <button disabled={!simulation.data}Abilita il pulsante solo quando abbiamo le informazioni necessarie per inviare la transazione.
1 onClick={async () => {2 updateStatus("Please confirm in wallet...")Aggiorna lo stato. A questo punto, l'utente deve confermare nel portafoglio.
1 await writeContractAsync(simulation.data.request)2 updateStatus("Transaction sent, waiting for greeting to change...")3 }}4 >5 Update greeting6 </button>7
writeContractAsync restituisce un valore solo dopo che la transazione è stata effettivamente inviata. Questo ci consente di mostrare all'utente da quanto tempo la transazione è in attesa di essere inclusa nella blockchain.
1 <h4>Status: {status}</h4>2 <p>Updated <Timer lastUpdate={statusTime} /> </p>3 </>4 )5}Mostra lo stato e da quanto tempo è stato aggiornato.
1export {Greeter}Esporta il componente.
src/wagmi.ts
Infine, varie definizioni relative a wagmi si trovano in src/wagmi.ts. Non spiegherò tutto qui, perché la maggior parte è boilerplate che difficilmente avrai bisogno di cambiare.
1import { http, webSocket, createConfig, fallback } from 'wagmi'2import { sepolia } from 'wagmi/chains'3import { injected } from 'wagmi/connectors'4
5export const config = createConfig({6 chains: [sepolia],La configurazione di wagmi include le catene supportate da questa applicazione. Puoi vedere l'elenco delle catene disponibili (opens in a new tab).
1 connectors: [2 injected(),3 ],Questo connettore (opens in a new tab) ci consente di comunicare con un portafoglio installato nel browser.
1 transports: {2 [sepolia.id]: http()L'endpoint HTTP predefinito fornito con Viem è sufficiente. Se vogliamo un URL diverso, possiamo usare http("https:// hostname ") o webSocket("wss:// hostname ").
1 },2 multiInjectedProviderDiscovery: false,3})Aggiungere un'altra blockchain
Al giorno d'oggi ci sono molte soluzioni di scalabilità di livello 2 (opens in a new tab), e potresti voler supportarne alcune che viem non supporta ancora. Per farlo, modifica src/wagmi.ts. Queste istruzioni spiegano come aggiungere Optimism Sepolia (opens in a new tab).
-
Modifica
src/wagmi.tsA. Importa il tipo
defineChainda viem.1import { defineChain } from 'viem'
1
2 B. Aggiungi la definizione della rete. Non hai davvero bisogno di farlo per Optimism Sepolia, [è già in `viem`](https://github.com/wevm/viem/blob/main/src/chains/definitions/optimismSepolia.ts), ma in questo modo impari come aggiungere una blockchain che non è in `viem`.3
4 ```ts5 const optimismSepolia = defineChain({6 id: 11_155_420,7 name: 'OP Sepolia',8 nativeCurrency: { name: 'Sepolia Ether', symbol: 'ETH', decimals: 18 },9 rpcUrls: {10 default: {11 http: ['https://sepolia.optimism.io'],12 webSocket: ['wss://optimism-sepolia.drpc.org'],13 },14 },15 blockExplorers: {16 default: {17 name: 'Blockscout',18 url: 'https://optimism-sepolia.blockscout.com',19 apiUrl: 'https://optimism-sepolia.blockscout.com/api',20 }21 },22 })C. Aggiungi la nuova catena alla chiamata createConfig.
1export const config = createConfig({2 chains: [sepolia, optimismSepolia],3 connectors: [4 injected(),5 ],6 transports: {7 [optimismSepolia.id]: http(),8 [sepolia.id]: http()9 },10 multiInjectedProviderDiscovery: false,11})-
Modifica
src/App.tsxper commentare il passaggio automatico a Sepolia. Su un sistema di produzione, probabilmente mostreresti pulsanti con collegamenti a ciascuna delle blockchain che supporti.1/* useEffect(() => {2 if (connection.status === 'connected' &&3 connection.chainId !== SEPOLIA_CHAIN_ID4 ) {5 switchChain({ chainId: SEPOLIA_CHAIN_ID })6 }7}, [connection.status, connection.chainId]) */89101112131415
1
23. Modifica `src/Greeter.tsx` per assicurarti che l'applicazione conosca l'indirizzo dei tuoi contratti sulla nuova rete.3
4 ```ts5 const contractAddrs: AddressPerBlockchainType = {6 // Optimism Sepolia7 11155420: "0x4dd85791923E9294E934271522f63875EAe5806f",8
9 // Sepolia10 11155111: "0x7143d5c190F048C8d19fe325b748b081903E3BF0",11 }-
Nel tuo browser.
A. Naviga su ChainList (opens in a new tab) e fai clic su uno dei pulsanti sul lato destro della tabella per aggiungere la catena al tuo portafoglio.
B. Nell'applicazione, fai clic su Disconnect e poi riconnettiti per cambiare la blockchain. Ci sono modi più eleganti per gestirlo, ma richiederebbero modifiche all'applicazione.
Conclusione
Naturalmente, non ti interessa davvero fornire un'interfaccia utente per Greeter. Vuoi creare un'interfaccia utente per i tuoi contratti. Per creare la tua applicazione, esegui questi passaggi:
-
Specifica di creare un'applicazione wagmi.
1npm create wagmi
1
22. Digita `y` per procedere.3
43. Dai un nome all'applicazione.5
64. Seleziona il framework **React**.7
85. Seleziona la variante **Vite**.9
10Ora vai e rendi i tuoi contratti utilizzabili per il mondo intero.11
12[Vedi qui per altri miei lavori](https://cryptodocguy.pro/).Ultimo aggiornamento della pagina: 3 marzo 2026