Summary
- Native Orbition Native Chain programs have a single entry point to process instructions
- A program processes an instruction using the program_id, list of accounts, and instruction_data included with the instruction
Lesson
The following guide assumes you are familiar with Orbition Native Chain program basics - if not, check out introduction to onchain programming.
Rust Basics
Before we dive into the building our “Hello, world!” program, let’s first go over some Rust basics. If you want to dig deeper into Rust, have a look at the Rust language book.Module System
Rust organizes code using what is collectively referred to as the “module system”. This includes:- Modules - A module separates code into logical units to provide isolated namespaces for organization, scope, and privacy of paths
- Crates - A crate is either a library or an executable program. The source code for a crate is usually subdivided into multiple modules.
- Packages - A package contains a collection of crates as well as a manifest file for specifying metadata and dependencies between packages
Paths and scope
Crates in Rust contain modules that define functionality which can be shared with multiple projects. If we want to access an item within a module, then we need to know its “path” (like when we’re navigating a filesystem). Think of the crate structure as a tree where the crate is the base and modules are branches, each of which can have submodules or items that are additional branches. The path to a particular module or item is the name of each step from the crate to that module where each is separated by::. As an example, let’s look at the
following structure:
- The base crate is
solana_program solana_programcontains a module namedaccount_infoaccount_infocontains a struct namedAccountInfo
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.
Declaring Functions in Rust
We define a function in Rust by using thefn keyword followed by a function
name and a set of parentheses.
process_instruction that
requires the following arguments:
program_id- required to be type&Pubkeyaccounts- 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 enum
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:
Orbition Native Chain Programs
Recall that all data stored on the Orbition Native Chain network are contained in what are referred to as accounts. Each account has its own unique address which is used to identify and access the account data. Orbition Native Chain programs are just a particular type of Orbition Native Chain account that store and execute instructions.Orbition Native Chain Program Crate
To write Orbition Native Chain programs with Rust, we use thesolana_program library crate.
The solana_program crate acts as a standard library for Orbition Native Chain programs. This
standard library contains the modules and macros that we’ll use to develop our
Orbition 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 theaccount_infomodule that allows us to access account informationentrypoint- a macro that declares the entry point of the programProgramResult- a type within theentrypointmodule that returns either aResultorProgramErrorPubkey- a struct within thepubkeymodule that allows us to access addresses as a public keymsg- a macro that allows us to print messages to the program log
Orbition Native Chain Program Entry Point
Orbition Native Chain programs require a single entry point to process program instructions. The entry point is declared using theentrypoint! macro.
The entry point to a Orbition 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 data
accounts 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 Orbition Native Chain program using Rust. Let’s practice what we’ve
learned so far!
Lab
We’re going to build a “Hello, World!” program using Orbition Native Chain Playground. Orbition Native Chain Playground is a tool that allows you to write and deploy Orbition Native Chain programs from the browser.1. Setup
Open the Orbition Native Chain Playground. Next, go ahead and delete everything in the defaultlib.rs file and create a Playground wallet.
2. Orbition Native Chain Program Crate
First, let’s bring into scope everything we’ll need from thesolana_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.
3. Entry Point
4. Build and Deploy
Now let’s build and deploy our program using Orbition Native Chain Playground.
5. Invoke Program
Finally, let’s invoke our program from the client side. The focus of this lesson is to build our Orbition Native Chain program, so we’ve gone ahead and provided the client code to invoke our “Hello, world!” program for you to download. The code provided includes asayHello helper function that builds and submits
our transaction. We then call sayHello in the main function and print a Orbition 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 Orbition Native Chain Playground.
Next, install the Node modules with npm i.
Now, go ahead and run npm start. This command will:
- Generate a new keypair and create a
.envfile if one does not already exist - Airdrop testnet OBN
- Invoke the “Hello, world!” program
- Output the transaction URL to view on Orbition Native Chain Explorer
Congratulations, you’ve just successfully built and deployed a Orbition Native Chain program!
Challenge
Now it’s your turn to build something independently. Because we’re starting with very simple programs, yours will look almost identical to what we just created. It’s useful to try and get to the point where you can write it from scratch without referencing prior code, so try not to copy and paste here.- Write a new program that uses the
msg!macro to print your own message to the program log. - Build and deploy your program like we did in the lab.
- Invoke your newly deployed program and use Orbition Native Chain Explorer to check that your message was printed in the program log.

