Building on Aptos: A Guide for Solana Developers #1
Open Github Page
Solana to Aptos Cheat Sheet
Similarities
Differences
To-Do List Example
Tips for Building on Aptos
Video Review
Aptos Developer Docs
Data Structures and Storage
Define Your Own Data Structures
Both Solana and Aptos let developers define custom data structures easily. Take the todo list app as an example.
On Solana, we define the data structure as:
use anchor_lang::prelude::*;#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace)]pub struct Todo { #[max_len(200)] // Reserve space for up to 50 characters pub content: String, pub completed: bool,}// Each todo list is stored in its own account#[account]#[derive(InitSpace)]pub struct TodoList { pub owner: Pubkey, #[max_len(10)] // Reserve space for up to 10 todos pub todos: Vec<Todo>,}
On Aptos, we define the data structure as:
struct Todo has store, drop, copy { content: String, completed: bool,}// Each todo list is stored as a resourcestruct TodoList has key { owner: address, todos: vector<Todo>,}
Where to Store the Data
On Solana, TodoList
is stored under an account owned by the todo list program. But on Aptos, there are several options to store the TodoList
resource.
- Under the todo list owner: This is the simplest approach and makes it easy to retrieve the
TodoList
resource. But it's not recommended for production apps, because each address can have at most one resource of a certain type (e.g.TodoList
). If we place theTodoList
resource under the owner's address, the owner can only have one todo list.
// error codes/// Try to create another todo list, but each user can only have one todo listconst E_EACH_USER_CAN_ONLY_HAVE_ONE_TODO_LIST: u64 = 1;public entry fun create_todo_list(sender: &signer) { let sender_address = signer::address_of(sender); assert!( !exists<TodoList>(sender_address), E_EACH_USER_CAN_ONLY_HAVE_ONE_TODO_LIST ); let todo_list = TodoList { owner: sender_address, todos: vector::empty(), }; // store the TodoList resource directly under the sender move_to(sender, todo_list);}#[view]public fun get_todo_list(sender: address): (address, u64) acquires TodoList { // borrow the TodoList resource from the sender let todo_list = borrow_global<TodoList>(sender); (todo_list.owner, vector::length(&todo_list.todos))}
- Under an Object: This is the most scalable approach, by storing the
TodoList
resource under an object, each user can have multiple todo lists by owning multiple objects. We can usecreate_named_object
to deterministically generate the address of an object based on the user address and a seed (e.g. how many todo lists the user has). Named object address is similar to PDA on Solana.
use std::bcs;use std::signer;use aptos_framework::object;use aptos_framework::object::Object;struct UserTodoListCounter has key { counter: u64,}public entry fun create_todo_list(sender: &signer) acquires UserTodoListCounter { let sender_address = signer::address_of(sender); let counter = if (exists<UserTodoListCounter>(sender_address)) { let counter = borrow_global<UserTodoListCounter>(sender_address); counter.counter } else { let counter = UserTodoListCounter { counter: 0 }; // store the UserTodoListCounter resource directly under the sender move_to(sender, counter); 0 }; // create a new object to hold the todo list, use the counter as seed let obj_holds_todo_list = object::create_named_object(sender, bcs::to_bytes(&counter)); let obj_signer = object::generate_signer(&obj_holds_todo_list); let todo_list = TodoList { owner: sender_address, todos: vector::empty(), }; // store the TodoList resource under the newly created object move_to(&obj_signer, todo_list); // increment the counter let counter = borrow_global_mut<UserTodoListCounter>(sender_address); counter.counter = counter.counter + 1;}#[view]public fun get_todo_list(sender: address, todo_list_idx: u64): (address, u64) acquires TodoList { let todo_list_obj_addr = object::create_object_address(&sender, bcs::to_bytes(&todo_list_idx)); let todo_list = borrow_global<TodoList>(todo_list_obj_addr); (todo_list.owner, vector::length(&todo_list.todos))}
- Under a Resource Account: Resource Account is what we used before object, we recommend using Object instead of Resource Account now.
Bonus: Aptos Supports Map Data Structure
Unlike Solana where developers always need to use multiple accounts to simulate a map data structure, Aptos shipped with built-in Map called table at 0x1::table
. In the todo list app, we can replace the TodoList
with a new struct AllTodoLists
:
use aptos_std::table::Table;struct AllTodoLists has key { todo_lists: Table<address, vector<Todo>>,}
This way we only need 1 AllTodoLists
resource to store all todo lists. We can store it under the todo list app address, for easy access. However, if the app grows, it's better to use the TodoList
struct and store each todo list under an object for better scalability.