Making NFT’s act as Wallets

How to Use ERC-6551 to Create Non-Fungible Token Bound Accounts

Javier Calderon Jr

--

Introduction

Welcome to the world of non-fungible tokens (NFTs) where unique digital assets are taking the blockchain by storm. With the rise of NFTs, we have seen a massive expansion in the possibilities of what can be achieved on blockchain networks. One exciting development is the concept of an NFT acting as a wallet or an account, a powerful idea that unlocks a plethora of new use cases and applications.

ERC-721 is the Ethereum standard for NFTs, and while it has served the community well, its limitations have given rise to new extensions and concepts. One such extension is ERC-6551, which brings forth the idea of Token Bound Accounts. This proposal, still in draft status at the time of writing, aims to give every ERC-721 token its own smart contract account, allowing NFTs to own assets and interact with applications without any change to existing ERC-721 smart contracts or infrastructure.

How it works

This guide introduced you to the process of turning an NFT into a wallet or account using the ERC-6551 proposal. We started by outlining the fundamental principles behind ERC-6551, which provides a standard interface and a permissionless registry to create token bound accounts. Then, we delved into the specific function implementations, including createAccount and account in the Registry, and the IERC6551Account interface.

We then explored how to implement an IERC6551Account in Solidity, creating a smart contract where an NFT acts as a wallet. This contract has the ability to receive, hold, and send ERC-20 tokens, essentially acting as a standalone wallet for each NFT. We also shared a simplified example of a smart contract implementing this functionality, which should provide you a solid starting point.

Do remember, the code we’ve shared is for illustrative purposes, and if you’re planning to implement this in a production environment, it is crucial to have the code audited and tested thoroughly.

The ability to turn NFTs into wallets or accounts opens up new dimensions of possibilities in the blockchain world. It paves the way for an NFT to become a unique digital identity capable of owning and managing assets on-chain. Whether it’s a digital avatar accumulating in-game assets, an art piece holding its royalties, or a membership card recording interactions, the potential use cases are truly fascinating.

The Implementation

Blockchain technology has revolutionized the way we approach asset ownership and transactions. Among these advancements, Non-Fungible Tokens (NFTs) — unique assets represented on the blockchain — have captured significant attention. Yet, despite their individuality, traditional NFTs carry a significant limitation: they can’t own assets or interact with applications independently. Enter ERC-6551, a novel approach to extending the functionalities of NFTs by equipping them with their unique accounts, essentially turning NFTs into wallets or accounts. This article delves into the implementation of ERC-6551, showing how it can make NFTs more versatile, and providing code snippets and best practices along the way.

Understanding ERC-6551

The Ethereum Improvement Proposal (EIP) ERC-6551 is a recently introduced standard that extends the capabilities of ERC-721 tokens (NFTs). It assigns each NFT a unique, deterministic smart contract account, thereby allowing NFTs to own assets and interact with applications without requiring changes to existing ERC-721 smart contracts or infrastructure​1​.

The ERC-6551 system consists of two main components: a permissionless registry for deploying token-bound accounts and a standard interface for token-bound account implementations​1​. The registry deploys each token-bound account as an ERC-1167 minimal proxy with immutable constant data appended to the bytecode​1​. The address of each account is deterministic and derived from a unique combination of the implementation address, token contract address, token ID, EIP-155 chain ID, and an optional salt​1​.

How to Implement ERC-6551

ERC-6551 is implemented through a registry contract, which is permissionless, immutable, and has no owner. The registry serves as a single entry point for projects wishing to utilize token-bound accounts​1​. The registry contract has two functions:

  • createAccount: Deploys a token-bound account for an ERC-721 token given an implementation address.
  • account: A read-only function that computes the token-bound account address for an ERC-721 token given an implementation address.

Here is the interface for the registry contract:

interface IERC6551Registry {
event AccountCreated(
address account,
address implementation,
uint256 chainId,
address tokenContract,
uint256 tokenId,
uint256 salt
);

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
);
}

The createAccount function can be used to deploy a new token-bound account. If the account has already been created, it returns the account address without calling create2. If initData is not empty and the account has not yet been created, it calls the account with the provided initData after creation. The account function returns the computed address of a token-bound account.

Building the Account Bound Token

To further develop the smart contract, we can start by extending the functionalities of our IERC6551Account implementation.

Firstly, let’s add the functionality to handle ERC-20 tokens which might be received by the NFT. We’ll add a function that allows the NFT to approve another address to spend a certain amount of a specific ERC-20 token:

interface IERC20 {
function approve(address spender, uint256 amount) external returns (bool);
}

contract MyERC6551Account is IERC6551Account {
// Other contract code...

function approveERC20(address tokenAddress, address spender, uint256 amount) external onlyOwner {
IERC20 token = IERC20(tokenAddress);
token.approve(spender, amount);
}
}

Here, onlyOwner is a modifier ensuring that only the owner of the NFT can call this function. IERC20 is a simplified interface for interacting with ERC-20 tokens.

Next, let’s add the ability for our NFT to interact with a hypothetical DeFi protocol, such as a lending platform. For simplicity, we’ll add a function that allows the NFT to deposit a certain amount of an ERC-20 token to the platform:

interface ILendingPlatform {
function deposit(address tokenAddress, uint256 amount) external;
}

contract MyERC6551Account is IERC6551Account {
// Other contract code...

function depositToLendingPlatform(address platformAddress, address tokenAddress, uint256 amount) external onlyOwner {
// Transfer the specified amount of tokens to this contract
IERC20 token = IERC20(tokenAddress);
require(token.transferFrom(msg.sender, address(this), amount), "Token transfer failed");

// Approve the lending platform to spend the tokens
token.approve(platformAddress, amount);

// Deposit the tokens to the lending platform
ILendingPlatform platform = ILendingPlatform(platformAddress);
platform.deposit(tokenAddress, amount);
}
}

in this code snippet, ILendingPlatform is a simplified interface for interacting with the hypothetical lending platform. We first transfer the specified amount of tokens from the NFT owner to this contract, then approve the lending platform to spend these tokens, and finally deposit the tokens to the lending platform.

These are just some examples of what can be achieved with ERC-6551. The possibilities are truly endless, and depend on the specific needs of your project. Remember to thoroughly test any new functionality before deploying it to mainnet to ensure it works as expected and doesn’t have any security vulnerabilities.

Turning an NFT into a Wallet

Creating a smart contract where an NFT acts as a wallet or account, you would first need to deploy an ERC-721 contract. This contract would handle the minting and transferring of your NFTs.

Next, you would implement an IERC6551Account contract that interacts with the ERC-721 contract. This contract would have the capability to receive, hold, and send ERC-20 tokens, essentially making each NFT a standalone wallet.

Here is a simplified example of how the IERC6551Account contract might look:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract NFTWallet is ERC721, ReentrancyGuard, Ownable {
mapping(uint256 => address) private _wallets;

constructor(string memory name, string memory symbol) ERC721(name, symbol) {}

function walletOfToken(uint256 tokenId) public view returns (address) {
return _wallets[tokenId];
}

function mint(address to) public onlyOwner {
uint256 newTokenId = totalSupply() + 1;
_mint(to, newTokenId);
_wallets[newTokenId] = to;
}

function transferERC20(uint256 tokenId, address token, address to, uint256 amount) public nonReentrant {
require(_wallets[tokenId] == msg.sender, "Only wallet owner can transfer");
IERC20(token).transferFrom(address(this), to, amount);
}

function depositERC20(uint256 tokenId, address token, uint256 amount) public nonReentrant {
require(_wallets[tokenId] == msg.sender, "Only wallet owner can deposit");
IERC20(token).transferFrom(msg.sender, address(this), amount);
}

function balanceOfERC20(uint256 tokenId, address token) public view returns (uint256) {
require(_wallets[tokenId] == msg.sender || ownerOf(tokenId) == msg.sender, "Not wallet owner");
return IERC20(token).balanceOf(address(this));
}
}

In this contract, each NFT (identified by its tokenId) is associated with a wallet address (_wallets[tokenId]). The mint function mints a new NFT and sets its wallet address to the address provided. The transferERC20 function allows the owner of the NFT to transfer ERC-20 tokens from the NFT's associated wallet to another address. The depositERC20 function allows the owner of the NFT to deposit ERC-20 tokens from their personal address into the NFT's associated wallet. The balanceOfERC20 function allows the owner of the NFT to check the balance of ERC-20 tokens in the NFT's associated wallet.

Please note that this contract is simplified for the sake of clarity and does not include the full functionality of the ERC-6551 standard. This code may not be fully secure or optimized, and it should be thoroughly tested and audited before being used in a production environment.

Finally, remember that the actual deployment of these smart contracts would require gas fees, which are paid in Ether. The amount of gas needed will depend on the complexity of your contract and the current gas price on the Ethereum network.

Best Practices

When implementing ERC-6551, it is crucial to follow best practices to ensure safety and efficiency:

  • Ensure Compatibility: The ERC-6551 standard is designed to be backward compatible with existing ERC-721 tokens. Always test your implementation with existing ERC-721 contracts to ensure compatibility.
  • Security: As with any smart contract, security is paramount. Ensure that your implementation is secure and resistant to common attack
  • Security (continued): vectors. Consider conducting a security audit before deployment. Always keep the owner’s control over their ERC-721 tokens and their associated accounts paramount.
  • Gas Efficiency: Ensure your implementation is gas-efficient. Remember, each operation on the Ethereum blockchain consumes gas, and inefficient contracts can lead to unnecessarily high costs.
  • Use the Correct Addresses: Ensure that the correct implementation address, chain ID, token contract address, and token ID are used when creating a token-bound account. Using incorrect or invalid addresses could lead to unexpected behavior or loss of control over the account.
  • Utilize the Salt: The optional salt value in the account creation function allows you to create unique addresses for each token-bound account, even if other parameters are identical. Use this feature to increase the uniqueness and security of each account.

Conclusion

ERC-6551 is an exciting advancement in the Ethereum ecosystem, extending the capabilities of ERC-721 tokens by giving them their unique accounts. This development opens up new possibilities, including NFTs owning assets and interacting with applications independently. This guide has provided an introduction to ERC-6551 and its implementation. However, like any new technology, it should be used with caution, and best practices should be followed to ensure the security and efficiency of your contracts.

--

--