Account abstraction sets up an on-chain wallet that can store assets and act like a regular wallet. With this approach, a crypto wallet becomes a unique smart contract that can be programmed for various purposes.
A single contract with wallet functionality is deployed, controlled by a single owner. The owner is identified by checking the owner of the tokenId at the NFT contract address, both stored in the account during creation.
An example account contract is available on GitHub: ExampleERC6551Account.sol.
The registry is a helper contract that deploys account contracts and calculates their unique contract address using a tokenId & NFT contract address. It acts like a singleton factory that creates an account only once and returns the same address every time.
The NFT contract address and tokenId linked to the account are provided during account creation and stored in the account for future access verification.
An example registry contract is available on GitHub: ERC6551Registry.sol.
During NFT minting, the Registry can be instructed to create a new Account. The account will only be created once:
IERC6551Registry("0x_<Registry-Deployment>")
.createAccount(
"0x_<Account-Deployment>",
block.chainid,
"0x_<NFT-Deployment>",
tokenId,
salt,
""
);
The account creation can also be delayed, to only create it when required.
Using the account()
function, the same address will always be calculated, allowing you to know an address even before the wallet is deployed.
IERC6551Registry("0x_<Registry-Deployment>")
.account(
"0x_<Account-Deployment>",
block.chainid,
"0x_<NFT-Deployment>",
tokenId,
salt
);
Using the address and the account interface, the account can be instructed using the execute()
function:
ExampleERC6551Account("0x_<Account Address>")
.execute(
"0x_<recipient/to address>",
vetBalanceToTransferOr0,
encodedData,
0
)
For example, transferring VTHO and 0 VET:
TBA.execute(
// VTHO Address
"0x0000000000000000000000000000456e65726779",
// VET Value to transfer
0,
// Instructions on the recipient
vtho.interface.encodeFunctionData("transfer", [owner, vthoAmount]),
// must be 0 in the example contract
0
)
The NFT contract is a basic OpenZeppelin template. Its address and tokenId provide proof of ownership.
An example contract can be generated with OpenZeppelin as ERC721 on https://wizard.openzeppelin.com/#erc721.
Ownership in the example account is possible because, during account creation, the address of the NFT contract and the corresponding tokenId are stored within the account.
Using this information, the account will verify ownership using the ownerOf()
functionality on the NFT contract:
IERC721(tokenContract).ownerOf(tokenId);
An example project showing all elements connected into a single Hardhat project is available on GitHub at example-token-bound-accounts.
erDiagram
User ||--o{ Token : owns
User ||--o{ Registry : instructs
Registry ||--|{ Account : creates
User ||--o{ Account : instructs
Account ||--o{ Token : verifies_permission