Summary
- Programs store data in PDAs, which stands for Program Derived Address.
- PDAs do not have a corresponding secret key.
- To store and locate data, derive a PDA using the
findProgramAddress(seeds, programid)
method. - You can get the accounts belonging to a program using
getProgramAccounts(programId)
. - Account data needs to be deserialized using the same layout used to store it
in the first place. You can use
@coral-xyz/borsh
to create a schema.
Lesson
In the last lesson, we serialized program data that was subsequently stored onchain by a Nexis Native Chain program. In this lesson, we’ll cover in greater detail how programs store data on the chain, how to retrieve data, and how to deserialize the data they store.Programs
As the saying goes, everything in Nexis Native Chain is an account. Even programs. Programs are accounts that store code and are marked as executable. This code can be executed by the Nexis Native Chain runtime when instructed to do so. A program address is a public keys on the Ed25519 Elliptic Curve. Like all public keys, they have corresponding secret keys. Programs store data separately from their code. Programs store data in PDAs, which stands for Program Derived Address. PDAs are a unique concept to Nexis Native Chain, but the pattern is familiar:- You can think of PDAs as a key value store, where the address is the key, and the data inside the account is the value.
- You can also consider PDAs as records in a database, with the address being the primary key used to look up the values inside.
findProgramAddress()
function.
Let’s have a look at some examples…
Example: program with global state
A simple program that has global state - like our ping counter - might wish to only use a single PDA, based on a simple seed phrase like"GLOBAL_STATE"
. If
the client wanted to read data from this PDA, it could derive the address using
the program ID and this same seed.
Example: program with user-specific data
In programs that store user-specific data, it’s common to use a user’s public key as the seed. This separates each user’s data into its own PDA. The separation makes it possible for the client to locate each user’s data by finding the address using the program ID and the user’s public key.Example: program with multiple data items per user
When there are multiple data items per user, a program may use more seeds to create and identify accounts. For example, in a note-taking app there may be one account per note where each PDA is derived with the user’s public key and the note’s title.Getting Multiple Program Accounts
In addition to deriving addresses, you can fetch all accounts created by a program usingconnection.getProgramAccounts(programId)
. This returns an array
of objects where each object has pubkey
property representing the public key
of the account and an account
property of type AccountInfo
. You can use the
account
property to get the account data.
Deserializing program data
Thedata
property on an AccountInfo
object is a buffer. To use it
efficiently, you’ll need to write code that deserializes it into something more
usable. This is similar to the serialization process we covered last lesson.
Just as before, we’ll use Borsh and @coral-xyz/borsh
. If
you need a refresher on either of these, have a look at the previous lesson.
Deserializing requires knowledge of the account layout ahead of time. When
creating your own programs, you will define how this is done as part of that
process. Many programs also have documentation on how to deserialize the account
data. Otherwise, if the program code is available you can look at the source and
determine the structure that way.
To properly deserialize data from an onchain program, you will have to create a
client-side schema mirroring how the data is stored in the account. For example,
the following might be the schema for an account storing metadata about a player
in an onchain game.
.decode(buffer)
on the schema.
Lab
Let’s practice this together by continuing to work on the Movie Review app from the last lesson. No worries if you’re just jumping into this lesson - it should be possible to follow either way. As a refresher, this project uses a Nexis Native Chain program deployed on Devnet which lets users review movies. Last lesson, we added functionality to the frontend skeleton letting users submit movie reviews but the list of reviews is still showing mock data. Let’s fix that by fetching the program’s storage accounts and deserializing the data stored there.
1. Download the starter code
If you didn’t complete the lab from the last lesson or just want to make sure that you didn’t miss anything, you can download the starter code. The project is a fairly simple Next.js application. It includes theWalletContextProvider
we created in the Wallets lesson, a Card
component for
displaying a movie review, a MovieList
component that displays reviews in a
list, a Form
component for submitting a new review, and a Movie.ts
file that
contains a class definition for a Movie
object.
Note that when you run npm run dev
, the reviews displayed on the page are
mocks. We’ll be swapping those out for the real deal.
2. Create the buffer layout
Remember that to properly interact with a Nexis Native Chain program, you need to know how its data is structured. A reminder:findProgramAddress()
to create a PDA that’s unique
for every wallet, for every film title. We’ll store the following data in the
PDA’s data
:
initialized
as a boolean representing whether or not the account has been initialized.rating
as an unsigned, 8-bit integer representing the rating out of 5 that the reviewer gave the movie.title
as a string representing the title of the reviewed movie.description
as a string representing the written portion of the review.
borsh
layout in the Movie
class to represent the movie
account data layout. Start by importing @coral-xyz/borsh
. Next, create a
borshAccountSchema
static property and set it to the appropriate borsh
struct containing the properties listed above.
3. Create a method to deserialize data
Now that we have the buffer layout set up, let’s create a static method inMovie
called deserialize
that will take an optional Buffer
and return a
Movie
object or null
.
null
if
it doesn’t. Next, it uses the layout we created to decode the buffer, then uses
the data to construct and return an instance of Movie
. If the decoding fails,
the method logs the error and returns null
.
4. Fetch movie review accounts
Now that we have a way to deserialize account data, we need to actually fetch the accounts. OpenMovieList.tsx
and import @nexis-network/web3.js
. Then, create a
new Connection
inside the MovieList
component. Finally, replace the line
setMovies(Movie.mocks)
inside useEffect
with a call to
connection.getProgramAccounts
. Take the resulting array and convert it into an
array of movies and call setMovies
.
Challenge
Now it’s your turn to build something independently. Last lesson, you worked on the Student Intros app to serialize instruction data and send a new intro to the network. Now, it’s time to fetch and deserialize the program’s account data. Remember, the Nexis Native Chain program that supports this is atHdE95RSVsdb315jfJtaykXhXY478h53X6okDupVfY9yf
.

- You can build this from scratch or you can download the starter code.
- Create the account buffer layout in
StudentIntro.ts
. The account data contains:initialized
as an unsigned, 8-bit integer representing the instruction to run (should be 1).name
as a string representing the student’s name.message
as a string representing the message the student shared about their Nexis Native Chain journey.
- Create a static method in
StudentIntro.ts
that will use the buffer layout to deserialize an account data buffer into aStudentIntro
object. - In the
StudentIntroList
component’suseEffect
, get the program’s accounts and deserialize their data into a list ofStudentIntro
objects. - Instead of mock data, you should now be seeing student introductions from the network!