Building a Decentralized Address Book DApp with Solana's Address Lookup Tables
A deep dive into use cases of Address Lookup Tables (ALTs)
Table of contents
Imagine you're working on a decentralized application (DApp) that involves a complex workflow with multiple steps, each handled by a different smart contract or program on the Solana blockchain. Some of these steps might not require cryptographic signatures for security validation, especially if they involve public or pre-approved data.
Example 1: Liquidity pools in DeFi application
To illustrate the complexity, let's consider a decentralized finance (DeFi) application where users can participate in liquidity pools. When a user decides to add liquidity to a pool, the transaction may involve several accounts:
User Wallet Account: The account of the user initiating the transaction.
Liquidity Pool Account: The pool where liquidity is being added.
Token Accounts: Accounts representing the tokens being added to the pool.
Configuration Account: An account specifying certain parameters or rules for the liquidity pool.
Audit Account: An account where audit or tracking information is stored.
In this example, the accounts related to the liquidity pool configuration, audit, or other non-sensitive data might not require cryptographic signatures. However, due to the legacy transaction size limitation, including a large number of these signature-free accounts in a single transaction becomes challenging.
Example 2:Decentralized Identity Verification Service
Let's take another example:
You are developing a decentralized identity verification service on the Solana blockchain. This service allows users to verify their identity by submitting various documents, and the verification process involves multiple steps.
Transaction Components:
User Wallet Account:
The account of the user initiating the identity verification process.
Identity Verification Service Accounts:
Accounts responsible for managing the overall identity verification process.
Document Submission Accounts:
Accounts representing the documents submitted by the user for verification.
Biometric Data Account:
An account storing biometric data for identity verification.
Verification Status Account:
An account tracking the status of the identity verification process.
Challenge with Legacy Transactions:
The identity verification process involves multiple documents and data types, each requiring a separate account.
Storing and managing these accounts in a legacy transaction becomes cumbersome due to the maximum size limitation and the need to include various types of accounts.
Example 3: Decentralized Autonomous Organization (DAO) Governance Proposal
Another one:
You are developing a decentralized autonomous organization (DAO) on the Solana blockchain. The DAO allows token holders to participate in governance by proposing and voting on changes to the organization.
Transaction Components:
User Wallet Account:
- The account of the user initiating a governance proposal.
Proposal Details Account:
- An account storing the details of the proposed change, including a description, rationale, and metadata.
Voting Power Accounts (Token Holders):
- Accounts representing the voting power of individual token holders. Each token holder has a dedicated account.
Configuration Account:
- An account specifying certain parameters or rules for the governance process.
Previous Proposals Accounts:
- Accounts storing information about previous governance proposals.
Challenge with Legacy Transactions:
In a complex DAO governance proposal, you may need to involve numerous token holders, each with a dedicated account for their voting power.
Considering the legacy transaction size limitation, including 40 voting power accounts and other necessary components might exceed the maximum allowed size.
Now the above examples were to give you an idea, of where there can be multiple accounts (with numerous unique addresses) required to complete a single transaction.
Problems with legacy transactions
The maximum allowed size of a transaction is 1232 bytes. The size of an account address is 32 bytes. Thus, a transaction can at the very best store 35 accounts, taking into account some space for headers, signatures, and other metadata.
This is problematic as there are several cases where developers need to include 100s of signature-free accounts in a single transaction (as described in the examples above). This is currently not possible with the legacy transaction model. The solution currently being used is to temporarily store state on-chain and consume it later in transactions. This workaround does not work when multiple programs need to be composed in a single transaction. Each program requires multiple accounts as input and hence we fall into the same problem as before.
This is where Address Lookup Tables (ALTs) or simply Lookup Tables(LUTs) come into the picture.
Address Lookup Tables (ALTs)
Purpose:
- LUTs are like tables or arrays on the blockchain where you can store a bunch of account addresses.
How It Works:
- Instead of directly putting the actual addresses in a transaction, you create a table. This table has a special address, and you refer to this address in your transaction.
Indexing Accounts:
- Each account inside the table gets a u8 index. Think of it like a list where each item has a number starting from 0, 1, 2, and so on.
Index Size:
- The u8 index is just a small number, taking only 1 byte of space. This means you can have up to 256 (2^8) different accounts referenced in the table.
Space Saving:
By using this table and referencing indexes, you save space in the transaction. You don't need to list all the actual addresses; you just point to them using these small u8 index numbers.
Adding Accounts:
- You can add new accounts to this table over time. This can be done either by a specific on-chain process (like a program that adds addresses) or directly by appending them to the table through an
extension
instruction.
- You can add new accounts to this table over time. This can be done either by a specific on-chain process (like a program that adds addresses) or directly by appending them to the table through an
Rent-Exempt Initialization:
- When you first create or set up this table (initialize it) or whenever you add a new address, you don't have to pay rent for the space it takes up. It's "rent-exempt" during these actions.
Metadata Storage:
You can also store additional information (metadata) along with the accounts in the table. This could be extra details related to each account.
Example:
- Think of Address Lookup Tables as having a phone book. Instead of writing out everyone's full address each time you want to call someone, you just write down their name and assign them a number. When you make a call (transaction), you use their number (index) to reach them.
A decentralized address book DApp using ALTs
We will now move to learn more about Address Lookup Tables by building an address book DApp.
We will create a decentralized application (DApp) that functions as a Solana Address Book using Address Lookup Tables. This project will allow users to sign in, store, and manage a collection of addresses efficiently on the Solana blockchain.
1. Setting Up the Solana Project:
Begin by setting up your Solana project. Ensure you have the necessary tools installed, and connect to a Solana cluster.
For simplicity, we'll use the Devnet cluster for development.
//connect to Devnet cluster
const web3 = require("@solana/web3.js");
const connection = new web3.Connection(web3.clusterApiUrl("devnet"));
const slot = await connection.getSlot();
2. Implementing User Authentication:
Implement a secure user authentication system using Solana accounts.
Allow users to create accounts, log in, and securely manage their address book.
To implement user authentication, you can follow either of the 4 methods:
You can use web3auth.io .
You can use quicknode.
You can do it using Phantom's Sign in with solana (SIWS).(launched recently)
You can also use moralis.io
I personally found Quick Node's to be the fastest. So I would be using that to build our dapp but you can use either of the 3 methods. It hardly makes a difference.
You can follow the user's authentication guide given here to implement the user authentication!
3. Utilizing Address Lookup Tables:
- Use Solana's Address lookup tables to efficiently store and manage addresses associated with each user.
// code for creating an address lookup table
// Assumption:
// `payer` is a valid `Keypair` with enough SOL to pay for the execution
const [lookupTableInst, lookupTableAddress] =
web3.AddressLookupTableProgram.createLookupTable({
authority: payer.publicKey,
payer: payer.publicKey,
recentSlot: slot,
});
console.log("Lookup table address:", lookupTableAddress.toBase58());
// To create the Address Lookup Table on chain:
// send the `lookupTableInst` instruction in a transaction
Explanation:
Method:
web3.AddressLookupTableProgram.createLookupTable
: Creates a new address lookup table.Parameters:
authority: payer.publicKey
: Specifies the authority (entity with permission).payer: payer.publicKey
: Designates the payer (entity covering transaction costs).recentSlot: slot
: Indicates the recent blockchain slot for transaction consistency.
Destructuring:
const [lookupTableInst, lookupTableAddress] = ...
: Extracts the instruction and address from the method result.
Benefits:
Reduced Transaction Size:
In legacy transactions, the maximum size constraint is 1232 bytes, limiting the number of accounts that can be included in a single atomic transaction.
LUTs address this limitation by allowing account addresses to be stored in a table-like data structure on-chain. This reduces the need to include full account addresses in the transaction message itself.
The LUT only requires a reference to the table's address and a 1-byte u8 index to point to individual accounts within the table.
Increased Transaction Throughput:
Since LUTs enable referencing multiple accounts with a single index, transactions can include more account interactions in a single block.
Developers can efficiently load more readonly and writable accounts within a single transaction, leading to improved transaction throughput.
Cost Savings:
With LUTs, the cost of transactions is reduced as the data size included in the transaction message is minimized.
Instead of including full account addresses, transactions only need to reference the LUT address and use compact indexes, resulting in lower transaction fees.
Versioned Transactions for Flexibility:
The introduction of Versioned Transactions allows for flexibility in transaction structures. Developers can choose between legacy transactions and versioned transactions depending on their requirements.
This flexibility enables developers to adapt to different use cases, ensuring that the transaction structure aligns with the specific needs of their applications.
4. Performing CRUD Operations:
- Enable users to perform CRUD (Create, Read, Update, Delete) operations on their address book entries.
//Code for CRUD operations with address lookup tables
const createEntryInst = web3.AddressLookupTableProgram.createEntry({
authority: payer.publicKey,
lookupTable: lookupTableAddress,
entry: { name: "John Doe", address: "ABC123" },
});
const updateEntryInst = web3.AddressLookupTableProgram.updateEntry({
authority: payer.publicKey,
lookupTable: lookupTableAddress,
entryIndex: 0,
updatedEntry: { name: "Jane Doe", address: "XYZ789" },
});
const deleteEntryInst = web3.AddressLookupTableProgram.deleteEntry({
authority: payer.publicKey,
lookupTable: lookupTableAddress,
entryIndex: 1,
});
Explanation:
createEntryInst
: This variable holds the instruction to create a new entry in the Address Lookup Table.web3.AddressLookupTableProgram.createEntry
: It is a function provided by the Solana web3.js library for creating an entry in the Address Lookup Table.authority
: The public key of the authority, typically the payer who has the permission to create entries.lookupTable
: The address of the Address Lookup Table where the entry will be added.entry
: An object containing the information of the new entry, in this case, the name and address.updateEntryInst
: This variable holds the instruction to update an entry in the Address Lookup Table.web3.AddressLookupTableProgram.updateEntry
: A function to update an entry in the Address Lookup Table.entryIndex
: The index of the entry in the table that needs to be updated.updatedEntry
: An object containing the updated information, in this case, the new name and address.deleteEntryInst
: This variable holds the instruction to delete an entry from the Address Lookup Table.web3.AddressLookupTableProgram.deleteEntry
: A function to delete an entry from the Address Lookup Table.
Benefits:
CRUD operations (Create, Read, Update, Delete) become more efficient with LUTs. The example provided above demonstrate how easy it is to create, update, and delete entries in the Address Lookup Table.
The compact array of indexes used in CRUD operations allows developers to perform these actions with minimal on-chain data, contributing to faster transaction processing.
5. Implementing Transaction History:
- Implement a transaction history feature that logs changes made to the address book.
// code for transaction history with ALTs
const transactionHistoryInst = web3.AddressLookupTableProgram.logTransactionHistory({
authority: payer.publicKey,
lookupTable: lookupTableAddress,
transactionInfo: "Updated address for John Doe",
});
logTransactionHistory
is a custom function or method belonging to theAddressLookupTableProgram
. It is designed to log or record transaction history information related to address changes or updates within the lookup table.
While implementing the transaction history, you will realize how easy, fast, and streamlined the process of storing the transaction becomes.
All the above the above code samples will help you create a Solana address book DApp. An example codebase utilizing all the above code examples can be found in Solana address book github repository .
Resources
To learn more about the Address Lookup Tables, you can refer the following resources:
If this article was helpful to you, a small comment, share or like would mean a lot.
Meet you in some other post.
Till then,
Signing off,
With ❤️,
Dhruv Varshney