diff --git a/dapps/universal-provider-tezos/src/App.tsx b/dapps/universal-provider-tezos/src/App.tsx index 380d4eec3..47c96a8ff 100644 --- a/dapps/universal-provider-tezos/src/App.tsx +++ b/dapps/universal-provider-tezos/src/App.tsx @@ -1,371 +1,262 @@ import UniversalProvider from "@walletconnect/universal-provider"; import { WalletConnectModal } from "@walletconnect/modal"; -import { useState } from "react"; -import { signMessage, sendTransaction, TezosChainData, apiGetContractAddress, getAccounts, getChainId, apiGetTezosAccountBalance, formatTezosBalance } from "./utils/helpers"; +import { useState, useEffect, useCallback, useMemo } from "react"; +import { + signMessage, + sendTransaction, + TezosChainData, + apiGetTezosAccountBalance, + formatTezosBalance, + getAccounts, + getChainId, + apiGetContractAddress +} from "./utils/helpers"; import { SAMPLE_KINDS, SAMPLES } from "./utils/samples"; const projectId = import.meta.env.VITE_PROJECT_ID; -const events: string[] = []; -// const events = ["display_uri", "chainChanged", "accountsChanged", "disconnect"]; const rpcMap = { "tezos:mainnet": "https://rpc.tzbeta.net", "tezos:testnet": "https://rpc.ghostnet.teztnets.com" -} +}; -// 1. select chains (tezos) const chains = ["tezos:mainnet", "tezos:testnet"]; - -// 2. select methods (tezos) const methods = ["tezos_getAccounts", "tezos_sign", "tezos_send"]; -// 3. create modal instance -const modal = new WalletConnectModal({ - projectId, - chains, -}); - -// 4. create provider instance -const provider = await UniversalProvider.init({ - logger: "debug", - // relayUrl: "wss://relay.walletconnect.com", - projectId: projectId, - metadata: { - name: "WalletConnect @ Tezos", - description: "Tezos integration with WalletConnect's Universal Provider", - url: "https://walletconnect.com/", - icons: ["https://avatars.githubusercontent.com/u/37784886"], - }, - // client: undefined, // optional instance of @walletconnect/sign-client -}); - const App = () => { + const [provider, setProvider] = useState(null); const [isConnected, setIsConnected] = useState(false); const [lastKind, setLastKind] = useState(null); const [result, setResult] = useState(null); const [description, setDescription] = useState(null); - const [contractAddress, setContractAddress] = useState(""); const [balance, setBalance] = useState(""); + const [address, setAddress] = useState(""); + const [contractAddress, setContractAddress] = useState("[click Origination to get contract address]"); + + const modal = useMemo( + () => + new WalletConnectModal({ + projectId, + chains, + }), + [] + ); - // 5. get address once loaded - const address = - provider.session?.namespaces.tezos?.accounts[0].split(":")[2]; - - // 6. handle display_uri event and open modal - provider.on("display_uri", async (uri: string) => { - console.log("event display_uri", uri); - await modal.openModal({ - uri, - }); - }); - - provider.on("session_ping", ({ id, topic }: { id: string; topic: string }) => { - console.log("Session Ping:", id, topic); - }); - - provider.on("session_event", ({ event, chainId }: { event: any; chainId: string }) => { - console.log("Session Event:", event, chainId); - }); - - provider.on("session_update", ({ topic, params }: { topic: string; params: any }) => { - console.log("Session Update:", topic, params); - }); - - provider.on("session_delete", ({ id, topic }: { id: string; topic: string }) => { - console.log("Session Delete:", id, topic); - }); - - // 7. handle connect event - const connect = async () => { - try { - await provider.connect({ - namespaces: { - tezos: { - methods, - chains, - events:[], - }, + // Initialize provider once when the component mounts + useEffect(() => { + const initializeProvider = async () => { + const newProvider = await UniversalProvider.init({ + logger: "debug", + projectId, + metadata: { + name: "WalletConnect @ Tezos", + description: "Tezos integration with WalletConnect's Universal Provider", + url: "https://walletconnect.com/", + icons: ["https://avatars.githubusercontent.com/u/37784886"], }, - skipPairing: false, }); - setIsConnected(true); - console.log("Connected successfully. Provider", provider); - console.log("Connected successfully. Session", provider.session); - const chainId = await getChainId("tezos:testnet"); - provider.setDefaultChain(chainId, rpcMap["tezos:testnet"]); - console.log("Connected to chain:", chainId); - await getBalance(); - } catch (error) { - console.error("Connection error:", error); - } - modal.closeModal(); - }; - - // 8. handle disconnect event - const disconnect = async () => { - await provider.disconnect(); - setIsConnected(false); - setResult(null); // Clear result on disconnect - }; - - // 9. handle operations - const handleGetAccounts = async () => { - return await getAccounts( - TezosChainData["testnet"].id, - provider, - address! - ); - }; - const handleSign = async () => { - return await signMessage( - TezosChainData["testnet"].id, - provider, - address! - ); - }; - - const handleSendTransaction = async () => { - return await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - SAMPLES[SAMPLE_KINDS.SEND_TRANSACTION], - ); - }; - - const handleSendDelegation = async () => { - return await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - SAMPLES[SAMPLE_KINDS.SEND_DELEGATION], - ); - }; + // Event handlers for provider + newProvider.on("display_uri", async (uri: string) => { + console.log("event display_uri", uri); + await modal.openModal({ uri }); + }); - const handleSendUndelegation = async () => { - return await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - SAMPLES[SAMPLE_KINDS.SEND_UNDELEGATION], - ); - }; + newProvider.on("session_ping", ({ id, topic }: { id: string; topic: string }) => { + console.log("Session Ping:", id, topic); + }); - const handleSendOriginate = async () => { - const res = await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - SAMPLES[SAMPLE_KINDS.SEND_ORGINATION], - ); - console.log("TezosRpc origination result: ", res); - const contractAddressList = await apiGetContractAddress(TezosChainData["testnet"].id, res.hash); - if (contractAddressList.length > 0) { - setContractAddress(contractAddressList[0]); - console.log("TezosRpc stored contract: ", contractAddressList[0]); - } else { - console.error("TezosRpc could not find contract address in origination operation."); - } - return res; - }; + newProvider.on("session_event", ({ event, chainId }: { event: any; chainId: string }) => { + console.log("Session Event:", event, chainId); + }); - const handleSendContractCall = async () => { - return await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - {...SAMPLES[SAMPLE_KINDS.SEND_CONTRACT_CALL], destination: contractAddress}, - ); - }; + newProvider.on("session_delete", ({ id, topic }: { id: string; topic: string }) => { + console.log("Session Delete:", id, topic); + setIsConnected(false); + setProvider(null); + }); - const handleSendStake = async () => { - if (!address) { - throw new Error("Address is not set"); - } - return await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - {...SAMPLES[SAMPLE_KINDS.SEND_STAKE], destination: address}, - ); - }; + setProvider(newProvider); + }; - const handleSendUnstake = async () => { - if (!address) { - throw new Error("Address is not set"); - } - return await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - {...SAMPLES[SAMPLE_KINDS.SEND_UNSTAKE], destination: address}, - ); - }; + initializeProvider(); + }, [modal]); - const handleSendFinalize = async () => { - if (!address) { - throw new Error("Address is not set"); + // Fetch address once provider is set + useEffect(() => { + if (provider && provider.session) { + const addr = provider.session.namespaces.tezos?.accounts[0]?.split(":")[2]; + setAddress(addr); } - return await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - {...SAMPLES[SAMPLE_KINDS.SEND_FINALIZE], destination: address}, - ); - }; + }, [provider]); - const handleSendIncreasePaidStorage = async () => { - return await sendTransaction( - TezosChainData["testnet"].id, - provider, - address!, - {...SAMPLES[SAMPLE_KINDS.SEND_INCREASE_PAID_STORAGE], destination: contractAddress}, - ); - }; + const connect = useCallback(async () => { + try { + if (provider) { + await provider.connect({ + namespaces: { + tezos: { + methods, + chains, + events: [], + }, + }, + skipPairing: false, + }); - const handleOp = async (kind: SAMPLE_KINDS) => { - if (!provider) return; + setIsConnected(true); + console.log("Connected successfully. Provider", provider); - setLastKind(kind); - setResult("Waiting for response from the Wallet..."); + const chainId = await getChainId("tezos:testnet"); + provider.setDefaultChain(chainId, rpcMap["tezos:testnet"]); - try { - let res = null; - switch (kind) { - case SAMPLE_KINDS.GET_ACCOUNTS: - res = await handleGetAccounts(); - break; - case SAMPLE_KINDS.SIGN: - res = await handleSign(); - break; - case SAMPLE_KINDS.SEND_TRANSACTION: - res = await handleSendTransaction(); - break; - case SAMPLE_KINDS.SEND_ORGINATION: - res = await handleSendOriginate(); - break; - case SAMPLE_KINDS.SEND_CONTRACT_CALL: - res = await handleSendContractCall(); - break; - case SAMPLE_KINDS.SEND_DELEGATION: - res = await handleSendDelegation(); - break; - case SAMPLE_KINDS.SEND_UNDELEGATION: - res = await handleSendUndelegation(); - break; - case SAMPLE_KINDS.SEND_STAKE: - res = await handleSendStake(); - break; - case SAMPLE_KINDS.SEND_UNSTAKE: - res = await handleSendUnstake(); - break; - case SAMPLE_KINDS.SEND_FINALIZE: - res = await handleSendFinalize(); - break; - case SAMPLE_KINDS.SEND_INCREASE_PAID_STORAGE: - res = await handleSendIncreasePaidStorage(); - break; - - default: - throw new Error(`Unsupported method ${kind}`); + await getBalance(); } - setResult(res); - console.log(res); - await getBalance(); } catch (error) { - console.error("Error sending ${method}:", error); - setResult(error); + console.error("Connection error:", error); + } finally { + modal.closeModal(); + } + }, [provider, modal]); + + const disconnect = useCallback(async () => { + if (provider) { + await provider.disconnect(); + setIsConnected(false); + setResult(null); // Clear result on disconnect } - }; + }, [provider]); + + const handleOp = useCallback( + async (kind: SAMPLE_KINDS) => { + if (!provider) return; + + setLastKind(kind); + setResult("Waiting for response from the Wallet..."); + + try { + let res = null; + switch (kind) { + case SAMPLE_KINDS.GET_ACCOUNTS: + res = await getAccounts(TezosChainData["testnet"].id, provider, address); + break; + case SAMPLE_KINDS.SIGN: + res = await signMessage(TezosChainData["testnet"].id, provider, address); + break; + case SAMPLE_KINDS.SEND_TRANSACTION: + case SAMPLE_KINDS.SEND_DELEGATION: + case SAMPLE_KINDS.SEND_UNDELEGATION: + res = await sendTransaction(TezosChainData["testnet"].id, provider, address, SAMPLES[kind]); + break; + case SAMPLE_KINDS.SEND_ORGINATION: + res = await sendTransaction(TezosChainData["testnet"].id, provider, address, SAMPLES[kind]); + console.log("TezosRpc origination result: ", res); + for (let attempt = 0; attempt < 5; attempt++) { + const contractAddressList = await apiGetContractAddress(TezosChainData["testnet"].id, res.hash); + if (contractAddressList.length > 0) { + setContractAddress(contractAddressList[0]); + console.log("TezosRpc stored contract:", contractAddressList[0]); + break; + } + console.log("Waiting for contract address..."); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + if (!contractAddress) { + setResult(`Indexer cannot find contract for op hash ${res.hash}`); + return; + } + break; + case SAMPLE_KINDS.SEND_CONTRACT_CALL: + case SAMPLE_KINDS.SEND_INCREASE_PAID_STORAGE: + res = await sendTransaction( + TezosChainData["testnet"].id, provider, address, + {...SAMPLES[kind], destination: contractAddress}); + break; + case SAMPLE_KINDS.SEND_STAKE: + case SAMPLE_KINDS.SEND_UNSTAKE: + case SAMPLE_KINDS.SEND_FINALIZE: + res = await sendTransaction( + TezosChainData["testnet"].id, provider, address, + {...SAMPLES[kind], destination: address}); + break; + + default: + throw new Error(`Unsupported method ${kind}`); + } + setResult(res); + console.log(res); + await getBalance(); + } catch (error) { + console.error(`Error sending ${kind}:`, error); + setResult(error); + } + }, + [provider, address] + ); - const getBalance = async () => { - const balance = await apiGetTezosAccountBalance(address!, "testnet"); - setBalance(formatTezosBalance(balance)); - }; + const getBalance = useCallback(async () => { + if (address) { + const balance = await apiGetTezosAccountBalance(address, "testnet"); + setBalance(formatTezosBalance(balance)); + } + }, [address]); - const describe = (kind: SAMPLE_KINDS) => { + const describe = useCallback((kind: SAMPLE_KINDS) => { switch (kind) { case SAMPLE_KINDS.SEND_TRANSACTION: - setDescription(SAMPLES[kind]); - break; case SAMPLE_KINDS.SEND_DELEGATION: - setDescription(SAMPLES[kind]); - break; case SAMPLE_KINDS.SEND_UNDELEGATION: - setDescription(SAMPLES[kind]); - break; case SAMPLE_KINDS.SEND_ORGINATION: setDescription(SAMPLES[kind]); break; case SAMPLE_KINDS.SEND_CONTRACT_CALL: - setDescription({ - ...SAMPLES[kind], - destination: contractAddress ? contractAddress : "[click Origination to get contract address]", - }); + case SAMPLE_KINDS.SEND_INCREASE_PAID_STORAGE: + setDescription({...SAMPLES[kind], destination: contractAddress}); break; case SAMPLE_KINDS.SEND_STAKE: - setDescription({...SAMPLES[kind], destination: address}); - break; case SAMPLE_KINDS.SEND_UNSTAKE: - setDescription({...SAMPLES[kind], destination: address}); - break; case SAMPLE_KINDS.SEND_FINALIZE: setDescription({...SAMPLES[kind], destination: address}); break; - case SAMPLE_KINDS.SEND_INCREASE_PAID_STORAGE: - setDescription({ - ...SAMPLES[kind], - destination: contractAddress ? contractAddress : "[click Origination to get contract address]", - }); - break; default: setDescription("No description available"); } - }; + }, []); - const describeClear = () => { + const describeClear = useCallback(() => { setDescription(undefined); - }; + }, []); return (

UniversalProvider

WalletConnect for Tezos

-

- dApp prototype integrating WalletConnect's Tezos Universal Provider. -

+

dApp prototype integrating WalletConnect's Tezos Universal Provider.

- {(!projectId || projectId === "YOUR_PROJECT_ID") ? ( + {!projectId || projectId === "YOUR_PROJECT_ID" ? (

The project is not initialized

Set your project ID in the .env file

) : isConnected ? ( <> -

- Public Key: - {address ?? "No account connected"} -

-

- Balance: - {balance} -

+

Public Key: {address ?? "No account connected"}

+

Balance: {balance}

- - - - - - - - - - - - -
+ + + + + + + + + + + + +
{result && ( <> @@ -374,7 +265,7 @@ const App = () => {
{JSON.stringify(result, null, 2)}
)} - {description && ( + {description && ( <>

Operation:

{JSON.stringify(description, null, 2)}