The ERC-721 standard, introduced in 2017, had advanced Ethereum's blockchain experience, enabling developers to develop NFTs with ownership of an exclusive digital asset. ERC-6551 is the latest standard developed by Ethereum, that uses ERC-721 as the base standard. ERC-6551 is an Ethereum Improvement Proposal (EIP) that introduces the capability for NFTs to function as self-contained wallets.
In this blog, we will demonstrate how you can create an ERC-6551 Registry Smart Contract with the ERC-6551 account smart contract. Before jumping on to writing the smart contracts, it is highly recommended to know more about ERC-6551 in detail in our previous blog - ERC-6551: How NFTs Can Be Used as Backpack Wallets?
In a nutshell, ERC-6551 allows NFTs (ERC-721) to have their own Token-bound Accounts, essentially giving them the ability to have their own smart contract wallets. These accounts can store other NFTs, which can be ERC-721 or ERC-1155, ETH, or even ERC-20 tokens.
We are going to use Remix IDE for creating and deploying smart contracts on one of the provided EVMs. The very first step in Remix is to create a new workspace.
Let us call the workspace ERC6551. In this workspace, we will create 3 smart contracts:
Along with these contracts, two interfaces are added:
The folder structure should look as follows:
Before moving forward, kindly make sure you use the compiler version 0.8.19 for Solidity.
First, let’s start by adding the NewNFT.sol contract, which is a basic ERC-721 NFT contract having only one function safeMint().
The second contract that we are going to make is Registry.sol. This is a simple implementation of the ERC-6551 Registry smart contract, which can be found in the official documentation with a little tweaking. The Registry is responsible for creating TBAs for the ERC-721 NFT. It is the single point of source for all the TBA queries. It has two major functions:
function createAccount( address implementation, uint256 chainId, address tokenContract, uint256 tokenId, uint256 salt, bytes calldata initData ) external returns (address);
function account( address implementation, uint256 chainId, address tokenContract, uint256 tokenId, uint256 salt ) external view returns (address);
Both the functions take the following arguments:
The next and final contract we are going to make is the Account.sol. The address on the chain of this smart contract is provided as the implementation address to the Registry contracts createAccount() and account () methods. The major functions of this smart contract are:
The next step is to compile and deploy all three contracts. Select the smart contract to deploy from the file explorer section. Go to the Compile section and click on the Compile button. You can also enable the Auto Compile option to automatically compile the contracts. Next, go to the Deployment section and select one of the addresses from the drop-down. Make sure to select the correct contract to be deployed and click on the Deploy Button.
Repeat this step for all three contracts.
Now, once you have deployed all the contracts, it's time to mint the NFT. Select another address from the dropdown and copy it. Next, select the owner address and open the deployed NewNFT.sol contract from the Deployed Contarcts section. Expand the safeMint() function and mint tokenId 1 for the copied address.
Moving on, we now need to compute an address for the minted NFT. Expand the account() method under the Registry smart contract and enter the following arguments to call the method.
This function will compute an account address for the provided NFT and return it as the output. In my case, 0x5F1F70140851Fc9Cd6AbbA5f06999a38dF27F5aA is the computed address which in turn is the address of our TBA.
Now we have our computed address, however, it is yet to be created. To create this address, call the createAccount() method with the same arguments keeping initData as  (empty array).
This will create a new TBA for the provided NFT. Now to verify, you can go into the transaction details and tally the decoded output. It should be the same as the computed address.
Now copy the newly created address and paste it into the At Address input field and select the Account address from the drop-down menu. Make sure that it is the selected contract, and click on the At Address button. This should now add a new contract under the deployed contract section. To verify that this address belongs to the owner of the original NFT, you can call the owner() method.
Now, we will execute a call through this newly created Token-bound Address. To make a call from the contract, select the owner's address from the drop-down. We will send 1 ETH from the owner's address to another address using the TBA. Select ETH from the Value type drop-down, and enter 1 as the value. From the address drop-down, select and copy another address. Now expand the executeCall() method under your TBA contract, and enter the copied address as the input for to argument. Enter the value as 1000000000000000000 (1 ETH) and keep the bytes as . Now click on the the transact button. After the successful execution, you will see that the balance of the receiver address will be increased by 1.
If you were to do this transaction from another address, not the owner's address, you would get an error, and the transaction would fail.
There you have it. You have successfully created an ERC-6551 Registry for creating Token Bound Accounts using a deployed Account smart contract for a given ERC-721 and verified that it is able to sign transactions on your behalf.
ERC-6551 intends to accelerate the evolution of NFTs. Using the Token Bound Accounts, ERC-721 gets the complete capabilities of an Ethereum account along with increased dynamics and interaction. Now individual ERC-721 NFTs can behave as a separate account holding other NFTs which can be transacted with other accounts or ERC-721 tokens. It opens plenty of opportunities for developers to create new use cases, especially in the gaming industry.