Getting Started with Smart Wallet

Learn what a smart wallet is, what its use cases are, and how you can use them to create powerful wallet experiences for users.

Getting Started with Smart Wallet

In this guide, we will go through how to create smart wallets (smart contract wallets) for your users that are fully ERC-4337 compliant with paymaster support. We will then learn how to integrate smart wallets into both a front-end React project and a Node back-end script easily.

We will be covering the most simple implementation of smart wallets. We will use a local wallet as the personal wallet key for a smart wallet account. Using the smart wallet, we will claim an NFT from an Edition Drop contract – completely gasless and transactionless!

By the end of this guide, you should be able to create web3 apps and node scripts that implement smart accounts to enable a gasless and transactionless experience for your users!

Why use Smart Wallets?

The user experience (UX) of traditional web3 that involves wallets can be quite cumbersome. It requires installing a wallet, remembering a seed phrase, acquiring funds through a faucet, and more. This process can be incredibly daunting for someone new to the web3 space. It often leads to questions like, "Why do I need to install a wallet to claim my ticket?". This valid concern highlights the need for a massive improvement in web3 UX.

Smart wallets address this issue by allowing developers to create app-linked smart wallets for users and incorporate additional features such as spending limits, multi-signature functionality, permissions, account recovery, and more. These capabilities are not available using EOAs (externally owned accounts). Smart wallets are essentially smart contracts with customizable logic. The best part is that users don't need to know that a smart contract manages everything behind the scenes!

For more information on what a smart wallet is and how it is, visit the portal!

How does a Smart Wallet work?

For every app, there needs to be a factory contract that deploys multiple smart wallets for your users on-demand. You can deploy this factory contract through our Explore page.

Each wallet can unlock one smart account per account factory. The user's smart wallet contract is deployed once a transaction is initiated from the smart wallet. The smart wallet address is deterministic, meaning that the address of the smart wallet is known prior to deployment.

Due to this feature, we are able to provide users with their smart wallet address without having to deploy their smart wallet contract until they perform their first transaction. This is beneficial because it removes potentially unnecessary costs of deploying a smart contract on-chain until you're sure the user needs to perform an on-chain action.

For each smart wallet, there needs to be a personal wallet that acts as a "key" for the smart wallet. This personal wallet is able to perform transactions on the smart wallet, such as adding additional signers to the account. To create a seamless experience, you can use local wallet (a wallet created and stored on a device) or a Paper wallet which allows your users to log in with email (or Google) and create non-custodial wallets that you can use as a key for your user's smart wallets.

How to deploy the Account Factory

Head to the AccountFactory contract page, fill in the contract parameters, Select your desired network and click on Deploy Now:

Entering deployment details for AccountFactory
Entering deployment details for AccountFactory

If you have a customized the Account contract, replace the Entrypoint with the address of a deployed instance of your customized account. If you wish to proceed with the defaults, leave the Entrypoint as it is.

Once the AccountFactory contract is deployed, you will be redirected to the deployed contracts page. Copy the address for this contract – we will need it when we write code!

Creating a thirdweb API key

To create an API key, head to the API keys section of the dashboard and sign in with your wallet:

Creating a new thirdweb API key
Creating a new thirdweb API key

Deploying an Edition Drop contract

💡
This step is optional and is only for this tutorial. Feel free to use any contract with your script or app.

Now, head to the Edition Drop page on the Explore page and deploy the contract. Lazy mint an NFT and set an initial claim phase. We recommend using a public free mint for this tutorial as we will be creating smart wallets in the Node.js script.

Example claim conditions for an NFT in the Edition Drop contract
Example claim conditions for an NFT in the Edition Drop contract

Using Smart Wallets in Node scripts

💡
You can find the code for the example in the associated GitHub repository.

Start by creating a node project by running the following command:

# npm
npm init -y

# yarn 
yarn init -y

Then, run the following commands in your project to install the required dependencies:

# npm
npm install @thirdweb-dev/sdk @thirdweb-dev/wallets @thirdweb-dev/chains ethers@5

# yarn
yarn add @thirdweb-dev/sdk @thirdweb-dev/wallets @thirdweb-dev/chains ethers@5

This should install all the dependencies required to implement smart wallets in our node script. Add the following property in package.json file to enable importing files using import:

"type": "module",

Now, create a new folder called const and create yourDetails.js inside this folder. In this file, import the chain that your smart wallet contract exists on. For this guide, we will be using Goerli testnet which you can import from the @thirdweb-dev/chains package. This file will contain all of the variables required by our script.

import { Goerli } from "@thirdweb-dev/chains";

export const TWFactoryAddress = "0xe448A5878866dD47F61C6654Ee297631eEb98966";
export const TWApiKey = "";
export const activeChain = Goerli;

export const editionDropAddress = "0x8D9919db3CD6aF84e8A12CedC3c5A694Bf026aB8";
export const editionDropTokenId = "0";

Make sure you replace the value of TWApiKey with your thirdweb API key in case you wish to make the experience gasless. Now, let's proceed with writing the script:

(async () => {
	
})();

This is an empty Promise function script that allows us to await promise resolutions within our script.

Now, let's create a local wallet that we will use as our personal wallet, which acts as a 'key' to unlock our smart wallet account.

💡
As mentioned previously, a personal wallet (or an EOA) is required to deploy a smart contract, as this wallet acts as a key to the contract.
(async () => {
  // Create a local wallet to be a key for smart wallet
  const localWallet = new LocalWallet();

  await localWallet.generate();

  const localWalletAddress = await localWallet.getAddress();
  console.log(`✨ Local wallet address: ${localWalletAddress}`);
})();

In the above snippet, we are using LocalWallet (imported from @thirdweb-dev/wallets) to create a personal wallet for this script, you can use any other wallet from the package if you wish to do so, for example MetaMask. If you run the above script, a new wallet address will be printed in your terminal for each run.

Now, let's use this newly-generated wallet as a key for our smart wallet:

// Create a smart wallet using the local wallet as the key
const smartWallet = new SmartWallet({
  chain: activeChain,
  factoryAddress: TWFactoryAddress,
  thirdwebApiKey: TWApiKey,
  gasless: true,
});

await smartWallet.connect({
  personalWallet: localWallet,
});

const smartWalletAddress = await smartWallet.getAddress();
console.log(`✨ Smart wallet address: ${smartWalletAddress}`);

In the above snippet, we are using SmartWallet (imported from @thirdweb-dev/wallets) to create a new smart wallet. While initializing, we provide the details we added to the yourDetails.js file as well as the local wallet we created in the previous step to use as a key.

Now, we can instantiate thirdweb SDK with the smart wallet to perform any actions. Or, you could instantiate the SDK fromSigner by getting the wallet signer by using smartWallet.getSigner().

// Instantiate thirdweb SDK with the smart wallet
// (or you can get signer using smartWallet.getSigner())
const sdk = await ThirdwebSDK.fromWallet(smartWallet);

try {
  // Claiming access NFT
  const contract = await sdk.getContract(editionDropAddress);
  const claimTxn = await contract.erc1155.claim(editionDropTokenId, 1);
  console.log(
      `🪄 Access NFT claimed! Txn hash: ${claimTxn.receipt.transactionHash}`
  );
} catch (error) {
  console.error(`❌ Error claiming NFT: ${error.message}`);
}

In the above snippet, we are initializing the thirdweb SDK with the smart wallet using the fromWallet() function from ThirdwebSDK. Then, we retrieve the Edition Drop contract deployed in the previous steps using the getContract() function. Now, we can use the claim() function to claim the NFT.  Finally, we log the transaction hash for the NFT claim. We also catch any errors in case something goes wrong while claiming.

Run your script by using the following command:

node index.js

This will do the following:

  • Create a local wallet.
  • Create a smart wallet by using the previously created local wallet as a key.
  • Initialize the thirdweb SDK and get the contract.
  • Claim the NFT in the smart wallet.

The following ise an example output for the script:

✨ Local wallet address: 0x7186d208Cb3F0DCe91A9e3C455f253244DF0D89C
✨ Smart wallet address: 0x3A7B11e40D17E01dA53c4ef69E1B8Fa03a60697a
AccountAPI - Creating account via factory
Cost to create account:  {
  ether: '0.000001431776012568',
  wei: BigNumber { _hex: '0x014d5c802918', _isBigNumber: true }
}
🪄 Access NFT claimed! Txn hash: 0xd5750e1c36b0da52ad71bbbf2ef1da65f61c88d373a96cf986408d65069c49f4

If you check the Events tab for your Edition Drop contract, you can see the NFT being claimed by the smart wallet. The best part is that the entire script is gasless because we set the gasless property to true while creating the smart wallet. This means the local wallet doesn't need funds to deploy the smart wallet.

Now, let's see how you can use smart wallets in React.

Using Smart Wallets in React apps

💡
We are using Vite for this tutorial. However, feel free to use any other React framework. You can find the code for the example in the associated GitHub repository.

If you don't already have a React app set up, you can use the following CLI command to create one:

npx thirdweb create app

Feel free to choose your own configuration. For this tutorial, we will be using Vite with JavaScript:

thirdweb CLI creating a web3-enabled dApp
thirdweb CLI creating a web3-enabled dApp

When the app has been created, as before, create a new file yourDetails.js under a new folder named const. You can use the same file for the node script, as we will use similar details for the React app.

Now, in main.jsx (assuming you're using Vite), you should see the ThirdwebProvider component. Update the file to look like this:

import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import {
  ThirdwebProvider,
  localWallet,
  metamaskWallet,
  smartWallet,
} from "@thirdweb-dev/react";
import "./styles/globals.css";
import { TWApiKey, TWFactoryAddress, activeChain } from "../const/yourDetails";

const container = document.getElementById("root");
const root = createRoot(container);
root.render(
  <React.StrictMode>
    <ThirdwebProvider
      activeChain={activeChain}
      supportedWallets={[
        smartWallet({
          factoryAddress: TWFactoryAddress,
          thirdwebApiKey: TWApiKey,
          gasless: true,
          personalWallets: [metamaskWallet(), localWallet({ persist: true })],
        }),
      ]}
    >
      <App />
    </ThirdwebProvider>
  </React.StrictMode>
);

In the above code snippet, we are importing activeChain from the yourDetails.js file and adding it to the activeChain prop of ThirdwebProvider. We are also adding smartWallet to the supportedWallets prop with the relevant smart wallet details, indicating that our dApp will use smart wallets. We also provide a list of personalWallets in the smart wallet indicating the wallets that can be used as personal wallets for the smart accounts. Now, you should be able to use everything else as usual throughout the app.

Now in App.jsx file, use the following:

import {
  ConnectWallet,
  Web3Button,
  useAddress,
  useContract,
  useNFT,
  useOwnedNFTs,
} from "@thirdweb-dev/react";
import "./styles/Home.css";
import { editionDropAddress, editionDropTokenId } from "../const/yourDetails";

export default function Home() {
  const address = useAddress();

  const { contract: editionDropContract } = useContract(
    editionDropAddress,
  );
  const { data: nft, isLoading: isNftLoading } = useNFT(
    editionDropContract,
    editionDropTokenId
  );
  const { data: ownedNfts, refetch: refetchOwnedNfts } = useOwnedNFTs(
    editionDropContract,
    address
  );

  return (
    <div className="container">
      <main className="main">
        <h1 className="title">
          Welcome to <a href="https://thirdweb.com/">thirdweb</a>!
        </h1>

        <p className="description">
          Claim your test access pass by creating an account!
        </p>

        <div className="connect">
          <ConnectWallet
            dropdownPosition={{
              align: "center",
              side: "bottom",
            }}
            btnTitle="Login"
          />
        </div>

        {isNftLoading ? (
          "Loading..."
        ) : (
          <div className="card">
            <img
              className="nftImage"
              src={nft.metadata.image}
              alt={nft.metadata.description}
            />
            {address ? (
              <>
                <p>You own {ownedNfts?.[0]?.quantityOwned || "0"}</p>
                <Web3Button
                  contractAddress={editionDropAddress}
                  action={(contract) =>
                    contract.erc1155.claim(editionDropTokenId, 1)
                  }
                  onSuccess={async () => {
                    await refetchOwnedNfts();
                    alert("Claim successful!");
                  }}
                  style={{ width: "100%", marginTop: "10px" }}
                >
                  Claim!
                </Web3Button>
              </>
            ) : (
              <p>Login to claim!</p>
            )}
          </div>
        )}
      </main>
    </div>
  );
}

In the above code, we are doing the following:

  • Getting the previously deployed Edition Drop contract using the useContract() hook.
  • Getting the NFT metadata using the token ID from the yourDetails.js file and rendering the NFT image on the screen.
  • Creating a Connect Wallet button that now supports connecting t0 smart wallet (as we configured it in ThirdwebProvider).
  • Creating a Web3Button to claim the NFT.

Now, use the following command to run the development server:

# npm
npm run dev

# yarn
yarn dev

Now, once you visit the local deployment, you should be able to see a connect wallet button, which after clicking, you should be prompted to choose a wallet or continue as guest:

Connection options for smart wallet
Connection options for smart wallet

By choosing Continue as guest, a local wallet will be used and you would be prompted to choose a password for that wallet. After you have connected your personal wallet, your smart wallet should get connected:

Smart wallet connected to dApp
Smart wallet connected to dApp

Now, you can use the claim button to claim the NFT in a completely gasless manner! You can copy the smart wallet address from the connect wallet button and cross-check with the Events tab of your Edition Drop contract.

Wrapping up

In this tutorial, we demonstrated how to use smart wallets in both Node.js and React environments to enable smart wallet functionality in any app you desire.

Smart wallets offer a more accessible and seamless web3 experience and are poised to play a significant role in the future.

For more information on smart wallets and our wallet SDK, visit the portal, and for the full source code, visit the GitHub repo.

Join our community on Discord if you wish to connect with the team or if you want to ask any questions. For any feedback, please leave a comment on our feedback board!