How To Tezos Wallet Connect Button to a Website with Taquito: A Step-by-Step dApp Tutorial
What is Tezos?
Tezos is an open-source blockchain that can execute peer-to-peer transactions and serve as a platform for deploying smart contracts. The native cryptocurrency for the Tezos blockchain is the tez (ISO 4217: XTZ; sign: ꜩ).
The Tezos network achieves consensus using proof-of-stake. Tezos uses an on-chain governance model that enables the protocol to be amended when upgrade proposals receive a favorable vote from the community.
What is Tezos Taquito?
Taquito is a fast and lightweight TypeScript library to accelerate DApp development on the Tezos blockchain. With it, you can easily interact with Smart Contracts deployed to Tezos.
It is distributed as a suite of individual npm
packages, to reduce bloat and improve application startup times.
Follow this guide to learn how to install Taquito and start interacting with your smart contracts on the Tezos blockchain.
Step 1:
Before we start writing scripts for our Tezos wallet connect button, we would first do the following:
- Set up our development environment
- Create a new Tezos project
- Initialize node in the project
- Create a react app using Vite
- Install the Taquito Library
Don’t know how to create a react app using Vite; Here’s a helpful resource. we will be using Typescript variant so select that instead of Javascrip when setting up your react app.
mkdir tezos-token-tutorial
cd tezos-token-tutorial
npm init -y
npm create vite@latest
npm install @taquito/taquito
Step 2: Simple Calculator Contract
We would be writing a simple calculator smart contract using SmartPy Online IDE
SmartPy is a comprehensive solution for developing, testing and deploying smart contracts on the Tezos blockchain.
With its easy-to-use Python syntax, developers can create contracts in a familiar and intuitive way, while SmartPy’s type inference provides added safety.
Copy and paste the smart contract code below to your SmartPy Online IDE to follow along
import smartpy as sp
@sp.module
def main():
class Calculator(sp.Contract):
def __init__(self):
self.data.result = 0
@sp.entrypoint
def multiply(self, x, y):
self.data.result = x * y
@sp.entrypoint
def add(self, x, y):
self.data.result = x + y
@sp.entrypoint
def square(self, x):
self.data.result = x * x
@sp.entrypoint
def squareRoot(self, x):
assert x >= 0
y = x
while y * y > x:
y = (x / y + y) / 2
assert y * y <= x and x < (y + 1) * (y + 1)
self.data.result = y
@sp.entrypoint
def factorial(self, x):
self.data.result = 1
for y in range(1, x + 1):
self.data.result *= y
@sp.entrypoint
def log2(self, x):
self.data.result = 0
y = x
while y > 1:
self.data.result += 1
y /= 2
It will look like this on SmartPy Online IDE
Step 3: Testing The Simple Calculator Contract
The subsequent crucial step is to create test cases for each operation mentioned above within the simple calculator contract.
Writing tests are essential as they help ensure the contract’s functionality is correct and without issues before deploying it to the testnet.
Robust testing provides confidence that the smart contract will perform as expected and can catch potential bugs or errors early in the development process.
Copy and paste the code for test cases below to your SmartPy Online IDE to follow along
if "templates" not in __name__:
@sp.add_test(name="Calculator")
def test():
c1 = main.Calculator()
scenario = sp.test_scenario(main)
scenario.h1("Calculator")
scenario += c1
c1.multiply(x=2, y=5)
c1.add(x=2, y=5)
c1.add(x=2, y=5)
c1.square(12)
c1.squareRoot(0)
c1.squareRoot(1234)
c1.factorial(100)
c1.log2(c1.data.result)
scenario.verify(c1.data.result == 524)
It will look like this on SmartPy Online IDE
Step 4: Compiling the Simple Calculator Contract
Run the contract code using ctrl + enter on Windows or cmd + enter on Mac or simply click the play icon on the SmartPy Online IDE.
You will also see the test results for the test cases we wrote in the previous step and this is to ensure that the simple calculator contract is performing as expected.
Step 5: Deploying the Simple Calculator Contract
Navigate to the Deploy Michelson Contract and click on the Deploy Michelson Contract
button as shown below.
- Select the network on which you want to deploy your contract and for this tutorial, we will be using the Ghostnet Tezos Testnet.
2. Connect your Temple wallet.
Temple Wallet is an easy-to-use cryptocurrency wallet for the Tezos blockchain: open-source, decentralized and secure.
Make sure you have the Tezos testnet token as this deployment will require a gas fee.
Click here to get test tokens from the Tezos Faucet.
3. After successfully connecting your Temple wallet, click on the VALIDATE
button as seen in the image below.
4. To avoid running into an error, just click on the ESTIMATE COST FROM RPC
to refresh the fees, gas limit, and storage limit.
5. Click on the DEPLOY CONTRACT
button and deploy the simple calculator contract that we created from step 2.
6. Click on the Accept button and click on SIgn when your Temple Wallet pops up.
If you followed this tutorial, step by step; scroll down and see that the simple calculator contract has been deployed successfully.
You can see the contract address and you can view it on the ghostnet explorer.
Step 6: Create the Connect and Disconnect Button using Taquito
- Open the react vite app folder from Step 1.
- Inside the src folder, create another folder and title it
components.
- Create a file inside the
components
folder and name itConnectButton.tsx
. - Paste the code below into the file you just created.
I will add comments to explain the purpose and functionality of each line of code. This will provide a clear understanding of the code’s logic and ensure that you can comprehend it without simply copying and pasting it.
// Import necessary modules from React and @taquito/taquito
import React, { Dispatch, SetStateAction, useState, useEffect } from "react";
import { TezosToolkit } from "@taquito/taquito";
import { BeaconWallet } from "@taquito/beacon-wallet";
import {
NetworkType,
BeaconEvent,
defaultEventCallbacks,
} from "@airgap/beacon-dapp";
// Define the props interface for the ConnectButton component
type ButtonProps = {
Tezos: TezosToolkit; // TezosToolkit instance for interacting with the Tezos blockchain
setContract: Dispatch<SetStateAction<any>>; // Function to update contract state
setWallet: Dispatch<SetStateAction<any>>; // Function to update wallet state
setUserAddress: Dispatch<SetStateAction<string>>; // Function to update user address state
setUserBalance: Dispatch<SetStateAction<number>>; // Function to update user balance state
setStorage: Dispatch<SetStateAction<number>>; // Function to update contract storage state
contractAddress: string; // Address of the smart contract
setBeaconConnection: Dispatch<SetStateAction<boolean>>; // Function to update beacon connection state
setPublicToken: Dispatch<SetStateAction<string | null>>; // Function to update public token state
wallet: BeaconWallet; // BeaconWallet instance for wallet connectivity
};
// ConnectButton functional component taking in the props defined in the interface
const ConnectButton = ({
Tezos,
setContract,
setWallet,
setUserAddress,
setUserBalance,
setStorage,
contractAddress,
setBeaconConnection,
setPublicToken,
wallet,
}: ButtonProps): JSX.Element => {
// Function to perform setup tasks when the wallet is connected
const setup = async (userAddress: string): Promise<void> => {
setUserAddress(userAddress); // Set the user address state to the connected wallet's address
// Update user balance state by fetching balance from the blockchain
const balance = await Tezos.tz.getBalance(userAddress);
setUserBalance(balance.toNumber());
// Create a contract instance for the smart contract at the specified address
const contract = await Tezos.wallet.at(contractAddress);
const storage: any = await contract.storage(); // Get the contract storage
setContract(contract); // Set the contract state to the contract instance
setStorage(storage.toNumber()); // Update the contract storage state
};
// Function to handle wallet connection
const connectWallet = async (): Promise<void> => {
try {
// Request permissions from the wallet to connect to the specified network
await wallet.requestPermissions({
network: {
type: NetworkType.GHOSTNET,
rpcUrl: "https://ghostnet.ecadinfra.com",
},
});
// Get the user's address from the connected wallet
const userAddress = await wallet.getPKH();
await setup(userAddress); // Perform setup tasks with the user's address
setBeaconConnection(true); // Set beacon connection state to true
} catch (error) {
console.log(error); // Log any errors that occurred during the connection process
}
};
// useEffect hook to execute when the component mounts
useEffect(() => {
// This function will be called when the component mounts
// It sets up the wallet instance and checks if the wallet was connected before
(async () => {
// Create a new wallet instance for BeaconWallet with specified options
const wallet = new BeaconWallet({
name: "Simple Calculator dApp",
preferredNetwork: NetworkType.GHOSTNET,
disableDefaultEvents: false, // Disable all events when true/UI. This also disables the pairing alert.
});
Tezos.setWalletProvider(wallet); // Set the wallet provider for TezosToolkit
setWallet(wallet); // Set the wallet state to the created instance
// Check if there is an active account (wallet was connected before)
const activeAccount = await wallet.client.getActiveAccount();
if (activeAccount) {
const userAddress = await wallet.getPKH(); // Get the user's address from the connected wallet
await setup(userAddress); // Perform setup tasks with the user's address
setBeaconConnection(true); // Set beacon connection state to true
}
})();
}, []);
// Return the JSX element to display the Connect Wallet button
return (
<div className="buttons">
<button className="button" onClick={connectWallet}>
<span>
<i className="fas fa-wallet"></i> Connect wallet
</span>
</button>
</div>
);
};
export default ConnectButton;
5. Create another file inside the components
folder and name it DisconnectButton.tsx
.
6. Paste the code below into the file you just created.
// Import necessary modules from React, @taquito/beacon-wallet, and @taquito/taquito
import React, { Dispatch, SetStateAction } from "react";
import { BeaconWallet } from "@taquito/beacon-wallet";
import { TezosToolkit } from "@taquito/taquito";
// Define the props interface for the DisconnectButton component
interface ButtonProps {
wallet: BeaconWallet | null; // Wallet instance or null if not connected
setPublicToken: Dispatch<SetStateAction<string | null>>; // Function to update public token state
setUserAddress: Dispatch<SetStateAction<string>>; // Function to update user address state
setUserBalance: Dispatch<SetStateAction<number>>; // Function to update user balance state
setWallet: Dispatch<SetStateAction<any>>; // Function to update wallet state
setTezos: Dispatch<SetStateAction<TezosToolkit>>; // Function to update TezosToolkit state
setBeaconConnection: Dispatch<SetStateAction<boolean>>; // Function to update beacon connection state
}
// DisconnectButton functional component taking in the props defined in the interface
const DisconnectButton = ({
wallet,
setPublicToken,
setUserAddress,
setUserBalance,
setWallet,
setTezos,
setBeaconConnection
}: ButtonProps): JSX.Element => {
// Function to handle wallet disconnection
const disconnectWallet = async (): Promise<void> => {
if (wallet) {
// Clear the active account in the wallet to disconnect
await wallet.clearActiveAccount();
}
// Reset user-related states to initial values when the wallet is disconnected
setUserAddress(""); // Set user address to an empty string
setUserBalance(0); // Set user balance to 0
setWallet(null); // Set wallet state to null
// Create a new TezosToolkit instance pointing to the Ghostnet URL
const tezosTK = new TezosToolkit("https://ghostnet.ecadinfra.com");
setTezos(tezosTK); // Set the TezosToolkit state to the new instance
setBeaconConnection(false); // Set beacon connection state to false
setPublicToken(null); // Set public token state to null
};
// Return JSX element to display the Disconnect Wallet button
return (
<div className="buttons">
<button className="button" onClick={disconnectWallet}>
<i className="fas fa-times"></i> Disconnect wallet
</button>
</div>
);
};
export default DisconnectButton;
Step 7: Create the Calculator Script to Interact With our Tezos Smart Contract
For this tutorial, we are going to focus on implementing the add
and multiply
function from the simple calculator contract we just created.
However, I would encourage you to try and implement the remaining functions and interact with them directly on the front end using the knowledge you have acquired from this tutorial.
- Create another file inside the
components
folder and name itCalculator.tsx
. - Paste the code below into the file you just created.
// Import necessary modules from React and @taquito/taquito
import React, { useState, Dispatch, SetStateAction, useEffect } from "react";
import { TezosToolkit, WalletContract } from "@taquito/taquito";
// Define the props interface for the CalculatorContract component
interface CalculatorContractProps {
contract: WalletContract | any; // Smart contract instance or any type (can be any object)
setUserBalance: Dispatch<SetStateAction<any>>; // Function to update user balance state
Tezos: TezosToolkit; // TezosToolkit instance for interacting with the Tezos blockchain
userAddress: string; // User's Tezos wallet address
setStorage: Dispatch<SetStateAction<number>>; // Function to update contract storage state
}
// CalculatorContract functional component taking in the props defined in the interface
const CalculatorContract = ({ contract, setUserBalance, Tezos, userAddress, setStorage }: CalculatorContractProps) => {
// State variables to manage loading state and user input values
const [loadingMultiply, setLoadingMultiply] = useState<boolean>(false); // State for multiply button loading state
const [loadingAdd, setLoadingAdd] = useState<boolean>(false); // State for add button loading state
const [x, setX] = useState<number>(0); // State for user input value x
const [y, setY] = useState<number>(0); // State for user input value y
// Function to handle the multiply operation
const multiply = async (): Promise<void> => {
setLoadingMultiply(true); // Set loading state to true to indicate the operation is in progress
try {
// Call the multiply entry point of the smart contract with user input values
const op = await contract.methods.multiply(x, y).send();
await op.confirmation(); // Wait for the operation to be confirmed on the blockchain
const newStorage: any = await contract.storage(); // Get the updated contract storage
if (newStorage) setStorage(newStorage.toNumber()); // Update the contract storage state
setUserBalance((await Tezos.tz.getBalance(userAddress)).toString()); // Update the user balance state by fetching it from the blockchain
} catch (error) {
console.log(error); // Log any errors that occurred during the operation
} finally {
setLoadingMultiply(false); // Set loading state back to false once the operation is completed (either successful or failed)
}
};
// Function to handle the add operation
const add = async (): Promise<void> => {
setLoadingAdd(true); // Set loading state to true to indicate the operation is in progress
try {
// Call the add entry point of the smart contract with user input values
const op = await contract.methods.add(x, y).send();
await op.confirmation(); // Wait for the operation to be confirmed on the blockchain
const newStorage: any = await contract.storage(); // Get the updated contract storage
if (newStorage) setStorage(newStorage.toNumber()); // Update the contract storage state
setUserBalance((await Tezos.tz.getBalance(userAddress)).toString()); // Update the user balance state by fetching it from the blockchain
} catch (error) {
console.log(error); // Log any errors that occurred during the operation
} finally {
setLoadingAdd(false); // Set loading state back to false once the operation is completed (either successful or failed)
}
};
// Implement similar functions for square, squareRoot and factorial etc
// Conditional rendering based on contract and userAddress
if (!contract && !userAddress) return <div> </div>; // Return an empty div if contract and userAddress are not available
// Return the JSX elements to display the calculator buttons and input fields
return (
<div className="buttons">
{/* ... (rest of your code) */}
<div id="transfer-inputs">
<label>Enter a Number (B):</label>
<input type="text"
placeholder="Enter a number"
value={y} onChange={(e) => setY(Number(e.target.value))} />
</div>
<div id="transfer-inputs">
<label>Enter a Number (A):</label>
<input type="text"
placeholder="Enter another number"
value={x} onChange={(e) => setX(Number(e.target.value))} />
</div>
</div>
);
};
export default CalculatorContract;
Step 8: Bring it All Together
With the connect and disconnect wallet buttons, along with the scripts to interact with our smart contract, now prepared, the next crucial step is to integrate them all into our react dApp and create our very own simple calculator.
- Open the
app.tsx
file still inside thesrc
folder and paste the code below.
import React, { useState } from "react";
import { TezosToolkit } from "@taquito/taquito";
import "./App.css";
import ConnectButton from "./components/ConnectWallet";
import DisconnectButton from "./components/DisconnectWallet";
import qrcode from "qrcode-generator";
import CalculatorContract from "./components/Calculator";
const App = () => {
// State variables for Tezos connection and contract information
const [Tezos, setTezos] = useState<TezosToolkit>(
new TezosToolkit("https://ghostnet.ecadinfra.com")
);
const [contract, setContract] = useState<any>(undefined);
const [publicToken, setPublicToken] = useState<string | null>(null);
const [wallet, setWallet] = useState<any>(null);
const [userAddress, setUserAddress] = useState<string>("");
const [userBalance, setUserBalance] = useState<number>(0);
const [storage, setStorage] = useState<number>(0);
const [copiedPublicToken, setCopiedPublicToken] = useState<boolean>(false);
const [beaconConnection, setBeaconConnection] = useState<boolean>(false);
// Contract address for the calculator contract
const contractAddress: string = "KT1FWGvZMxeB1SRaGi27EsJVqA9pfu61E861";
// Function to generate a QR code for the public token
const generateQrCode = (): { __html: string } => {
const qr = qrcode(0, "L");
qr.addData(publicToken || "");
qr.make();
return { __html: qr.createImgTag(4) };
};
// Conditional rendering based on the wallet status and user data
if (publicToken && (!userAddress || isNaN(userBalance))) {
// If the public token is available, but user address or balance is missing, display the wallet connection dialog
return (
<div className="main-box">
<h1>Taquito React template</h1>
<div id="dialog">
<header>Try the Taquito React template!</header>
<div id="content">
<p className="text-align-center">
<i className="fas fa-broadcast-tower"></i> Connecting to
your wallet
</p>
<div
dangerouslySetInnerHTML={generateQrCode()}
className="text-align-center"
></div>
<p id="public-token">
{copiedPublicToken ? (
<span id="public-token-copy__copied">
<i className="far fa-thumbs-up"></i>
</span>
) : (
<span
id="public-token-copy"
onClick={() => {
if (publicToken) {
navigator.clipboard.writeText(publicToken);
setCopiedPublicToken(true);
setTimeout(() => setCopiedPublicToken(false), 2000);
}
}}
>
<i className="far fa-copy"></i>
</span>
)}
<span>
Public token: <span>{publicToken}</span>
</span>
</p>
<p className="text-align-center">
Status: {beaconConnection ? "Connected" : "Disconnected"}
</p>
</div>
</div>
<div id="footer">
<img src="built-with-taquito.png" alt="Built with Taquito" />
</div>
</div>
);
} else if (userAddress && !isNaN(userBalance)) {
// If user address and balance are available, display the calculator UI
return (
<div className="main-box">
<h1>My Tezos Calculator</h1>
<div id="dialog">
<div id="content">
<div id="increment-decrement">
<div id="contract-functions">
{/* Render the CalculatorContract component with relevant props */}
<CalculatorContract
contract={contract}
setUserBalance={setUserBalance}
Tezos={Tezos}
userAddress={userAddress}
setStorage={setStorage}
/>
<h3 className="text-align-center">
Tezos Result: <span>{storage}</span>
</h3>
</div>
</div>
<p>
<i className="far fa-file-code"></i>
<a
href={`https://better-call.dev/ghostnet/${contractAddress}/operations`}
target="_blank"
rel="noopener noreferrer"
>
{contractAddress}
</a>
</p>
<p>
<i className="far fa-address-card"></i>
<a
href={`https://ghostnet.tzkt.io/${userAddress}/operations/`}
target="_blank"
rel="noopener noreferrer"
>
{userAddress}
</a>
</p>
<p>
<i className="fas fa-piggy-bank"></i>
{(userBalance / 1000000).toLocaleString("en-US")} ꜩ
</p>
</div>
{/* Render the DisconnectButton component with relevant props */}
<DisconnectButton
wallet={wallet}
setPublicToken={setPublicToken}
setUserAddress={setUserAddress}
setUserBalance={setUserBalance}
setWallet={setWallet}
setTezos={setTezos}
setBeaconConnection={setBeaconConnection}
/>
</div>
</div>
);
} else if (!publicToken && !userAddress && !userBalance) {
// If public token, user address, and user balance are missing, display the welcome message and connection button
return (
<div className="main-box">
<div id="dialog">
<header>Welcome to Tezos Calculator dApp!</header>
<div id="content">
<p>Hello!</p>
<p>
This is a Tezos dApp built using Taquito. It helps users carry out simple
arithmetic calculations.
<br />
</p>
<p>Go forth and Tezos!</p>
</div>
{/* Render the ConnectButton component with relevant props */}
<ConnectButton
Tezos={Tezos}
setContract={setContract}
setPublicToken={setPublicToken}
setWallet={setWallet}
setUserAddress={setUserAddress}
setUserBalance={setUserBalance}
setStorage={setStorage}
contractAddress={contractAddress}
setBeaconConnection={setBeaconConnection}
wallet={wallet}
/>
</div>
</div>
);
} else {
// If an error occurs, display an error message
return <div>An error has occurred</div>;
}
};
export default App;
If you’re carefully following along, run npm run dev
in your terminal to power up your local host http://localhost:5173.
You should see a screen like the one below. Go ahead and click on the connect wallet button to connect your Temple wallet, and start making calculations with your dApp.
After connecting your Temple wallet, you should see the main section of the fully functional dApp as shown in the image below.
You can go ahead now and deploy your website to Vercel or Netlify and share the link with your friends
Let them see the cool stuff you’re building on one of the most amazing blockchains out there.
Helpful Resources:
- How to deploy your website using Vercel: https://dev.to/therickedge/how-to-deploy-your-website-using-vercel-4499
- Step by Step deploying your website on Netlify: https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/
- Link to the project GitHub repository: https://github.com/amdonatusprince/integrating-a-simple-dApp-on-tezos-blockchain
- Link to the Deployed Website on Vercel: https://mytezoscalculator.vercel.app/
- Tezos website: https://tezos.com/
- Tezos documentation: https://tezos.gitlab.io/
- OpenTezos website : https://opentezos.com/
- SmartPy website: https://smartpy.io/
- Taquito.js website : https://tezostaquito.io/
Congratulations on accomplishing the following!!!
- You have now successfully written, tested, and deployed a smart contract on the Tezos Blockchain using SmartPy.
- You built a functional Frontend allowing users to interact with your smart contract
- You deployed your website on Vercel/Netlify, allowing users to interact with your dApp.