Summary
- Native (non-Anchor) Nexis Native Chain development requires manual serialization and deserialization of data.
- Transactions are made up of an array of instructions, a single transaction can have any number of instructions in it, each targeting different programs. When a transaction is submitted, the Nexis Native Chain runtime will process its instructions in order and atomically, meaning that if any of the instructions fail for any reason, the entire transaction will fail to be processed.
- Every instruction is made up of 3 components: the intended program’s ID, an array of all accounts involved, and a byte buffer of instruction data.
- Every transaction contains an array of all accounts it intends to read or write, one or more instructions, a recent blockhash, and one or more signatures.
- To pass instruction data from a client, it must be serialized into a byte buffer. To facilitate this process of serialization, we will be using Borsh.
- Transactions can fail to be processed by the blockchain for any number of reasons, we’ll discuss some of the most common ones here.
Lesson
Transactions
This course requires completing
Introduction to Nexis Native Chain or equivalent
knowledge. It’s also aimed at advanced developers that prefer more control over
the ease of use and safe defaults Anchor provides. If you’re new to developing
onchain programs you may prefer
Anchor
Transaction Contents
Every transaction contains:- An array that includes every account it intends to read or write
- One or more instructions
- A recent blockhash
- One or more signatures
@nexis-network/web3.js
simplifies this process for you so that all you need to focus
on is adding instructions and signatures. The library builds the array of
accounts based on that information and handles the logic for including a recent
blockhash.
Instructions
Every instruction contains:- The program ID (public key) of the intended program
- An array listing every account that will be read from or written to during execution
- A byte buffer of instruction data
Instruction Data
The ability to add arbitrary data to an instruction ensures that programs can be dynamic and flexible enough for broad use cases in the same way that the body of an HTTP request lets you build dynamic and flexible REST APIs. Just as the structure of the body of an HTTP request is dependent on the endpoint you intend to call, the structure of the byte buffer used as instruction data is entirely dependent on the recipient program. If you’re building a full-stack dApp on your own, then you’ll need to copy the same structure that you used when building the program over to the client-side code. If you’re working with another developer who is handling the program development, you can coordinate to ensure matching buffer layouts. Let’s think about a concrete example. Imagine working on a Web3 game and being responsible for writing client-side code that interacts with a player inventory program. The program was designed to allow the client to:- Add inventory based on a player’s game-play results
- Transfer inventory from one player to another
- Equip a player with selected inventory items
Serialization
In addition to knowing what information to include in an instruction data buffer, you also need to serialize it properly. The most common serializer used in Nexis Native Chain is Borsh. Per the website:Borsh stands for Binary Object Representation Serializer for Hashing. It is meant to be used in security-critical projects as it prioritizes consistency, safety, speed; and comes with a strict specification.Borsh maintains a JS library that handles serializing common types into a buffer. There are also other packages built on top of Borsh that try to make this process even easier. We’ll be using the
@coral-xyz/borsh
library which can be installed using npm
.
Building off of the previous game inventory example, let’s look at a
hypothetical scenario where we are instructing the program to equip a player
with a given item. Assume the program is designed to accept a buffer that
represents a struct with the following properties:
variant
is an unsigned, 8-bit integer that instructs the program which instruction, or function, to execute.playerId
is an unsigned, 16-bit integer that represents the player ID of the player who is to be equipped with the given item.itemId
is an unsigned, 256-bit integer that represents the item ID of the item that will be equipped for the given player.
encode
method. This method
accepts as arguments an object representing the data to be serialized and a
buffer. In the below example, we allocate a new buffer that’s much larger than
needed, then encode the data into that buffer and slice the original buffer down
into a new buffer that’s only as large as needed.
player
,playerInfoAccount
, andPROGRAM_ID
are already defined somewhere outside the code snippetplayer
is a user’s public keyplayerInfoAccount
is the public key of the account where inventory changes will be writtenSystemProgram
will be used in the process of executing the instruction.
Lab
Let’s practice this together by building a Movie Review app that lets users submit a movie review and have it stored on Nexis Native Chain’s network. We’ll build this app a little bit at a time over the next few lessons, adding new functionality each lesson.
CenYq6bDRB7p73EjsPEpiYN7uveyPUTdXkDkgUduboaN
.
1. Download the starter code
Before we get started, go ahead and 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 for now, the movies displayed on the page when you run npm run dev
are mocks. In this lesson, we’ll focus on adding a new review but we won’t be
able to see that review displayed. Next lesson, we’ll focus on deserializing
custom data from onchain accounts.
2. Create the buffer layout
Remember that to properly interact with a Nexis Native Chain program, you need to know how it expects data to be structured. Our Movie Review program expects instruction data to contain:variant
as an unsigned, 8-bit integer representing which instruction should be executed (in other words which function on the program should be called).title
as a string representing the title of the movie that you are reviewing.rating
as an unsigned, 8-bit integer representing the rating out of 5 that you are giving to the movie you are reviewing.description
as a string representing the written portion of the review you are leaving for the movie.
borsh
layout in the Movie
class. Start by importing
@coral-xyz/borsh
. Next, create a borshInstructionSchema
property and set it
to the appropriate borsh
struct containing the properties listed above.
3. Create a method to serialize data
Now that we have the buffer layout set up, let’s create a method inMovie
called serialize()
that will return a Buffer
with a Movie
object’s
properties encoded into the appropriate layout.
{ ...this, variant: 0 }
into the buffer. Because the Movie
class
definition contains 3 of the 4 properties required by the buffer layout and uses
the same naming, we can use it directly with the spread operator and just add
the variant
property. Finally, the method returns a new buffer that leaves off
the unused portion of the original.
4. Send a transaction when the user submits the form
Now that we have the building blocks for the instruction data, we can create and send the transaction when a user submits the form. OpenForm.tsx
and locate
the handleTransactionSubmit
function. This gets called by handleSubmit
each
time a user submits the Movie Review form.
Inside this function, we’ll be creating and sending the transaction that
contains the data submitted through the form.
Start by importing @nexis-network/web3.js
and importing useConnection
and
useWallet
from @nexis-network/wallet-adapter-react
.
handleSubmit
function, call useConnection()
to get a
connection
object and call useWallet()
to get publicKey
and
sendTransaction
.
handleTransactionSubmit
, let’s talk about what needs to be
done. We need to:
- Check that
publicKey
exists to ensure that the user has connected their wallet. - Call
serialize()
onmovie
to get a buffer representing the instruction data. - Create a new
Transaction
object. - Get all of the accounts that the transaction will read or write.
- Create a new
Instruction
object that includes all of these accounts in thekeys
argument, includes the buffer in thedata
argument, and includes the program’s public key in theprogramId
argument. - Add the instruction from the last step to the transaction.
- Call
sendTransaction
, passing in the assembled transaction.
pda
is the address to the account where data will be
stored:
SystemProgram
, so our array needs to include web3.SystemProgram.programId
as
well.
With that, we can finish the remaining steps:
Challenge
Now it’s your turn to build something independently. Create an application that lets students of this course introduce themselves! 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 instruction buffer layout in
StudentIntro.ts
. The program expects instruction data to contain:variant
as an unsigned, 8-bit integer representing the instruction to run (should be 0).name
as a string representing the student’s name.message
as a string representing the message the student is sharing about their Nexis Native Chain journey.
- Create a method in
StudentIntro.ts
that will use the buffer layout to serialize aStudentIntro
object. - In the
Form
component, implement thehandleTransactionSubmit
function so that it serializes aStudentIntro
, builds the appropriate transaction and transaction instructions, and submits the transaction to the user’s wallet. - You should now be able to submit introductions and have the information stored onchain! Be sure to log the transaction ID and look at it in Nexis Native Chain Explorer to verify that it worked.