Adding Smart Accounts¶
Smart Accounts or, in our case, Safe Wallet ERC4337 enabled Accounts, are ERC4337 accounts which understand UserOps from an Entrypoint. If that sounds very confusing, then I can recommend the following tutorials on getting started with Smart Accounts:
Video Tutorial: Build an ERC4337 App
Let's get on with calculating our Smart Account Address.
Calculating the Smart Account¶
In our case, we will use the entrypoint v0.6 to calculate the Safe Wallet Account Address.
Basics of Account-Abstraction
If you have never worked with account abstraction erc4337, then the following part can be confusing. If you are interested in first learning the basics of account abstraction step by step for developers, there is an easy to follow video, which you can find here: https://youtu.be/v_V4Wqcn7vE.
But there are also plenty of other learning resources, for example you could run through the very first Demo of https://docs.candide.dev/wallet/guides/getting-started/ to get started quickly.
Let's import the SafeAccountV0_2_0 which uses Entrypoint v6 from the abstractionkit and then calculate and show the Smart Account Address:
import { ConnectButton } from '@rainbow-me/rainbowkit';
import type { NextPage } from 'next';
import Head from 'next/head';
import { useAccount } from 'wagmi';
import { Icons } from '../const/icons';
import { SafeAccountV0_2_0 as SafeAccount } from '@morpher-io/dd-abstractionkit';
import { useEffect, useState } from 'react';
const Home: NextPage = () => {
const { isConnected, address } = useAccount();
const [smartAccount, setSmartAccount] = useState<SafeAccount | undefined>();
useEffect(() => {
if (address) {
setSmartAccount(SafeAccount.initializeNewAccount([address]));
} else {
setSmartAccount(undefined);
}
}, [address]);
return (
<div className='container ms-auto text-center'>
...
{isConnected && <>
<div className='flex justify-center mb-6'>
<Icons.silverCoin className='w-20 h-20' />
</div>
<div className='flex flex-col gap-2 mb-6'>
{smartAccount &&
<div>
<p>Your Smart Account Address: {smartAccount.accountAddress}</p>
</div>
}
</div>
</>}
...
);
};
export default Home;
Access Safe Wallet
Once you have the account address and sent your first transaction, you can also access the safe wallet directly via https://safe.global - or directly via https://app.safe.global/home?safe=matic:0x123 where 0x123 is your account address.
With this, you should see your Smart Account address:
If we want to interact with our Silver NFT contract, we need to pay for the NFT and the data somehow. This payment comes from our Smart Account, so we need to know how much Gas Token (POL) are in there and somehow top it up if necessary.
Getting the Balance of the Smart Account¶
To get the balance for the Smart Account, we can simply query the balance. For this, we can use the useBalance hook from Wagmi. Extend the index.tsx file with the following content:
import { ConnectButton } from '@rainbow-me/rainbowkit';
import type { NextPage } from 'next';
import Head from 'next/head';
import { useAccount, useBalance } from 'wagmi';
import { Icons } from '../const/icons';
import { SafeAccountV0_2_0 as SafeAccount } from '@morpher-io/dd-abstractionkit';
import { useEffect, useState } from 'react';
import { formatUnits, parseEther } from 'viem';
const Home: NextPage = () => {
...
const smartAccountGasBalance = useBalance({ address: smartAccount?.accountAddress as `0x${string}` });
...
<div className='flex flex-col gap-2 mb-6'>
{smartAccount &&
<div>
<p>Your Smart Account Address: {smartAccount.accountAddress}</p>
{smartAccountGasBalance.isFetched && <>currently has {smartAccountGasBalance.data?.value !== undefined ? formatUnits(smartAccountGasBalance.data?.value, smartAccountGasBalance.data?.decimals) : '0'} POL
</>}
</div>
}
...
This should show us a balance in POL like so:
But how can we top up our Smart Account? That is relatively easy. We send a simple transaction to the address of the smart account. To do this, we can either copy the address of the smart account and use the UI of our wallet, or we add a button to our UI that starts a transaction. Let's add that button and give our smart account an approximate number of POL to pay for the silver NFT in POL (which is $35/$0.4 = 87.5 POL, let's give it 100 POL to be sure...)
Top Up the Smart Account¶
To start a simple transaction, we can use the useSendTransaction hook from Wagmi. Let's extend that index.tsx file to add the following parts:
import { ConnectButton } from '@rainbow-me/rainbowkit';
import type { NextPage } from 'next';
import Head from 'next/head';
import { useAccount, useBalance, useSendTransaction } from 'wagmi';
import { Icons } from '../const/icons';
import { SafeAccountV0_2_0 as SafeAccount } from '@morpher-io/dd-abstractionkit';
import { useEffect, useState } from 'react';
import { formatUnits, parseEther } from 'viem';
const Home: NextPage = () => {
const { isConnected, address } = useAccount();
const { sendTransaction } = useSendTransaction();
const [smartAccount, setSmartAccount] = useState<SafeAccount | undefined>();
const smartAccountGasBalance = useBalance({ address: smartAccount?.accountAddress as `0x${string}` });
...
{smartAccount &&
<div>
<p>Your Smart Account Address: {smartAccount.accountAddress}</p>
{smartAccountGasBalance.isFetched && <>currently has {smartAccountGasBalance.data?.value !== undefined ? formatUnits(smartAccountGasBalance.data?.value, smartAccountGasBalance.data?.decimals) : '0'} POL
<button
title="Top Up"
onClick={() => sendTransaction({
to: smartAccount.accountAddress as `0x${string}`,
value: parseEther("100")
})}
className='border p-1 rounded-md hover:bg-gray-100'>
<Icons.iconCash />
</button></>}
</div>
}
</div>
</>}
With this you get a tiny button that allows you to send a transaction directly with the connected wallet.
Getting the NFT Balance¶
Next we need to get the current balance of NFTs in the smart wallet. This is quite easy, because the NFT contract contains a function called balanceOf
. We can use the useReadContract
hook from wagmi for this. We also need the ABI and the contract address. Since our contracts are in a subfolder of the project, we can just import them.
First, let's add our Contract Address to the .env
file. Create a new .env file in the root of the nextjs project. Be careful that its not one layer up or deeper in the folder structure somewhere - it should be on the same level as the package.js file:
NEXT_PUBLIC_TOKEN_ADDRESS=0x8D56087058F75FB8Cb402890856962d9eF649188
Let's extend our index.tsx file:
import { useAccount, useBalance, useReadContract, useSendTransaction } from 'wagmi';
import Silvernft from "../../contracts/out/SilverNft.sol/SilverOunce.json";
const Home: NextPage = () => {
...
const coinBalance = useReadContract({
address: process.env.NEXT_PUBLIC_TOKEN_ADDRESS as `0x${string}`,
abi: Silvernft.abi,
functionName: 'balanceOf',
args: [smartAccount?.accountAddress as `0x${string}`]
})
return (
...
{isConnected && <>
...
<p>You got {coinBalance.isFetched && coinBalance.data?.toString() == "0" ? "no" : coinBalance.data?.toString()} Silver NFTs currently.</p>
</>}
...
Now that we have that, let's see how we can work with Oracle data...