Create an onchain program for Nexis Native Chain using native Rust, without Anchor.
::
. As an example, let’s look at the
following structure:
solana_program
solana_program
contains a module named account_info
account_info
contains a struct named AccountInfo
AccountInfo
would be solana_program::account_info::AccountInfo
.
Absent of any other keywords, we would need to reference this entire path to use
AccountInfo
in our code.
However, with the
use
keyword we can bring an item into scope so that it can be reused throughout a
file without specifying the full path each time. It’s common to see a series of
use
commands at the top of a Rust file.
fn
keyword followed by a function
name and a set of parentheses.
process_instruction
that
requires the following arguments:
program_id
- required to be type &Pubkey
accounts
- required to be type &[AccountInfo]
instruction_data
- required to be type &[u8]
&
in front of the type for each argument listed in the
process_instruction
function. In Rust, &
represents a ”reference” to another
variable. This allows you to refer to some value without taking ownership of it.
The “reference” is guaranteed to point to a valid value of a particular type.
The action of creating a reference in Rust is called “borrowing”.
In this example, when the process_instruction
function is called, a user must
pass in values for the required arguments. The process_instruction
function
then references the values passed in by the user, and guarantees that each value
is the correct data type specified in the process_instruction
function.
Additionally, note the brackets []
around &[AccountInfo]
and &[u8]
. This
means that the accounts
and instruction_data
arguments expect “slices” of
types AccountInfo
and u8
, respectively. A “slice” is similar to an array
(collection of objects of the same type), except the length is not known at
compile time. In other words, the accounts
and instruction_data
arguments
expect inputs of unknown length.
->
after the function.
In the example below, the process_instruction
function will now return a value
of type ProgramResult
. We will go over this in the next section.
Result
is a standard library type that represents two discrete outcomes:
success (Ok
) or failure (Err
). We’ll talk more about enums in a future
lesson, but you’ll see Ok
used later in this lesson so it’s important to cover
the basics.
When you use Ok
or Err
, you must include a value, the type of which is
determined by the context of the code. For example, a function that requires a
return value of type Result<String, i64>
is saying that the function can
either return Ok
with an embedded string value or Err
with an embedded
integer. In this example, the integer is an error code that can be used to
appropriately handle the error.
To return a success case with a string value, you would do the following:
solana_program
library crate.
The solana_program
crate acts as a standard library for Nexis Native Chain programs. This
standard library contains the modules and macros that we’ll use to develop our
Nexis Native Chain programs. If you want to dig deeper into the solana_program
crate, have
a look
at the solana_program
crate documentation.
For a basic program we will need to bring into scope the following items from
the solana_program
crate:
AccountInfo
- a struct within the account_info
module that allows us to
access account informationentrypoint
- a macro that declares the entry point of the programProgramResult
- a type within the entrypoint
module that returns either
a Result
or ProgramError
Pubkey
- a struct within the pubkey
module that allows us to access
addresses as a public keymsg
- a macro that allows us to print messages to the program logentrypoint!
macro.
The entry point to a Nexis Native Chain program requires a process_instruction
function
with the following arguments:
program_id
- the address of the account where the program is storedaccounts
- the list of accounts required to process the instructioninstruction_data
- the serialized, instruction-specific dataaccounts
argument. Any
additional inputs must be passed in through the instruction_data
argument.
Following program execution, the program must return a value of type
ProgramResult
. This type is a Result
where the embedded value of a success
case is ()
and the embedded value of a failure case is ProgramError
. ()
is
an empty value and ProgramError
is an error type defined in the
solana_program
crate.
…and there you have it - you now know all the things you need for the
foundations of creating a Nexis Native Chain program using Rust. Let’s practice what we’ve
learned so far!
lib.rs
file and create a Playground wallet.
solana_program
crate.
entrypoint!
macro
and create the process_instruction
function. The msg!
macro then allows us
to print “Hello, world!” to the program log when the program is invoked.
sayHello
helper function that builds and submits
our transaction. We then call sayHello
in the main function and print a Nexis Native Chain
Explorer URL to view our transaction details in the browser.
Open the index.ts
file you should see a variable named programId
. Go ahead
and update this with the program ID of the “Hello, world!” program you just
deployed using Nexis Native Chain Playground.
npm i
.
Now, go ahead and run npm start
. This command will:
.env
file if one does not already existmsg!
macro to print your own message to
the program log.