Create your first Orbitron Native Chain onchain program in Anchor.
Orbitron Native Chain’s ability to run arbitrary executable code is part of what makes it so powerful. Orbitron Native Chain programs, similar to “smart contracts” in other blockchain environments, are quite literally the backbone of the Orbitron Native Chain ecosystem. And the collection of programs grows daily as developers and creators dream up and deploy new programs.
This lesson will give you a basic introduction to writing and deploying a Orbitron Native Chain program using the Rust programming language and the Anchor framework.
Anchor makes writing Orbitron Native Chain programs easier, faster, and more secure, making it the “go-to” framework for Orbitron Native Chain development. It makes it easier to organize and reason about your code, implements common security checks automatically, and removes a significant amount of boilerplate code that is otherwise associated with writing a Orbitron Native Chain program.
Anchor uses macros and traits to generate boilerplate Rust code for you. These provide a clear structure to your program so you can more easily reason about your code. The main high-level macros and attributes are:
declare_id
- a macro for declaring the program’s onchain address#[program]
- an attribute macro used to denote the module containing the
program’s instruction logicAccounts
- a trait applied to structs representing the list of accounts
required for an instruction#[account]
- an attribute macro used to define custom account types for the
programLet’s talk about each of them before putting all the pieces together.
The declare_id
macro is used to specify the onchain address of the program
(i.e. the programId
). When you build an Anchor program for the first time, the
framework will generate a new keypair. This becomes the default keypair used to
deploy the program unless specified otherwise. The corresponding public key
should be used as the programId
specified in the declare_id!
macro.
The #[program]
attribute macro defines the module containing all of your
program’s instructions. This is where you implement the business logic for each
instruction in your program.
Each public function in the module with the #[program]
attribute will be
treated as a separate instruction.
Each instruction function requires a parameter of type Context
and can
optionally include additional function parameters representing instruction data.
Anchor will automatically handle instruction data deserialization so that you
can work with instruction data as Rust types.
Context
The Context
type exposes instruction metadata and accounts to your instruction
logic.
Context
is a generic type where T
defines the list of accounts an
instruction requires. When you use Context
, you specify the concrete type of
T
as a struct that adopts the Accounts
trait (e.g.
Context<AddMovieReviewAccounts>
). Through this context argument the
instruction can then access:
ctx.accounts
)ctx.program_id
) of the executing programctx.remaining_accounts
). The remaining_accounts
is
a vector that contains all accounts that were passed into the instruction but
are not declared in the Accounts
struct.Accounts
struct (ctx.bumps
)The Accounts
trait defines a data structure of validated accounts. Structs
adopting this trait define the list of accounts required for a given
instruction. These accounts are then exposed through an instruction’s Context
so that manual account iteration and deserialization is no longer necessary.
You typically apply the Accounts
trait through the derive
macro (e.g.
#[derive(Accounts)]
). This implements an Accounts
deserializer on the given
struct and removes the need to deserialize each account manually.
Implementations of the Accounts
trait are responsible for performing all
requisite constraint checks to ensure the accounts meet the conditions required
for the program to run securely. Constraints are provided for each field using
the #account(..)
attribute (more on that shortly).
For example, instruction_one
requires a Context
argument of type
InstructionAccounts
. The #[derive(Accounts)]
macro is used to implement the
InstructionAccounts
struct which includes three accounts: account_name
,
user
, and system_program
.
When instruction_one
is invoked, the program:
InstructionAccounts
structIf any accounts passed into instruction_one
fail the account validation or
security checks specified in the InstructionAccounts
struct, then the
instruction fails before even reaching the program logic.
You may have noticed in the previous example that one of the accounts in
InstructionAccounts
was of type Account
, one was of type Signer
, and one
was of type Program
.
Anchor provides a number of account types that can be used to represent accounts. Each type implements different account validation. We’ll go over a few of the common types you may encounter, but be sure to look through the full list of account types.
Account
Account
is a wrapper around UncheckedAccount
that verifies program ownership
and deserializes the underlying data into a Rust type.
Recall the previous example where InstructionAccounts
had a field
account_name
:
The Account
wrapper here does the following:
data
in the format of type AccountStruct
AccountStruct
type.When the account type specified in the Account
wrapper is defined within the
same crate using the #[account]
attribute macro, the program ownership check
is against the programId
defined in the declare_id!
macro.
The following are the checks performed:
Signer
The Signer
type validates that the given account signed the transaction. No
other ownership or type checks are done. You should only use the Signer
when
the underlying account data is not required in the instruction.
For the user
account in the previous example, the Signer
type specifies that
the user
account must be a signer of the instruction.
The following check is performed for you:
Program
The Program
type validates that the account is a certain program.
For the system_program
account in the previous example, the Program
type is
used to specify the program should be the system program. Anchor provides a
System
type which includes the programId
of the system program to check
against.
The following checks are performed for you:
#[account(..)]
The #[account(..)]
attribute macro is used to apply constraints to accounts.
We’ll go over a few constraint examples in this and future lessons, but at some
point be sure to look at the full
list of possible constraints.
Recall again the account_name
field from the InstructionAccounts
example.
Notice that the #[account(..)]
attribute contains three comma-separated
values:
init
- creates the account via a CPI to the system program and initializes
it (sets its account discriminator)payer
- specifies the payer for the account initialization to be the user
account defined in the structspace
- specifies that the space allocated for the account should be 8 + 8
bytes. The first 8 bytes are for a discriminator that Anchor automatically
adds to identify the account type. The next 8 bytes allocate space for the
data stored on the account as defined in the AccountStruct
type.For user
we use the #[account(..)]
attribute to specify that the given
account is mutable. The user
account must be marked as mutable because
lamports will be deducted from the account to pay for the initialization of
account_name
.
Note that the init
constraint placed on account_name
automatically includes
a mut
constraint so that both account_name
and user
are mutable accounts.
#[account]
The #[account]
attribute is applied to structs representing the data structure
of a Orbitron Native Chain account. It implements the following traits:
AccountSerialize
AccountDeserialize
AnchorSerialize
AnchorDeserialize
Clone
Discriminator
Owner
You can read more about the
details of each trait.
However, mostly what you need to know is that the #[account]
attribute enables
serialization and deserialization, and implements the discriminator and owner
traits for an account.
The discriminator is an 8-byte unique identifier for an account type derived from the first 8 bytes of the SHA256 hash of the account type’s name. The first 8 bytes are reserved for the account discriminator when implementing account serialization traits (which is almost always in an Anchor program).
As a result, any calls to AccountDeserialize
’s try_deserialize
will check
this discriminator. If it doesn’t match, an invalid account was given, and the
account deserialization will exit with an error.
The #[account]
attribute also implements the Owner
trait for a struct using
the programId
declared by declareId
of the crate #[account]
is used in. In
other words, all accounts initialized using an account type defined using the
#[account]
attribute within the program are also owned by the program.
As an example, let’s look at AccountStruct
used by the account_name
of
InstructionAccounts
The #[account]
attribute ensures that it can be used as an account in
InstructionAccounts
.
When the account_name
account is initialized:
AccountStruct
discriminatorAccountStruct
programId
from declare_id
When you combine all of these Anchor types you end up with a complete program. Below is an example of a basic Anchor program with a single instruction that:
You are now ready to build your own Orbitron Native Chain program using the Anchor framework!
Before we begin, install Anchor by following the steps from the Anchor docs.
For this lab we’ll create a simple counter program with two instructions:
Create a new project called anchor-counter
by running anchor init
:
Change into the new directory, then run anchor build
Anchor build will also generate a keypair for your new program - the keys are
saved in the target/deploy
directory.
Open the file lib.rs
and look at declare_id!
:
Run anchor keys sync
You’ll see the Anchor updates both:
declare_id!()
in lib.rs
Anchor.toml
To match the key generated during anchor build
:
Finally, delete the default code in lib.rs
until all that is left is the
following:
Counter
First, let’s use the #[account]
attribute to define a new Counter
account
type. The Counter
struct defines one count
field of type u64
. This means
that we can expect any new accounts initialized as a Counter
type to have a
matching data structure. The #[account]
attribute also automatically sets the
discriminator for a new account and sets the owner of the account as the
programId
from the declare_id!
macro.
Context
type Initialize
Next, using the #[derive(Accounts)]
macro, let’s implement the Initialize
type that lists and validates the accounts used by the initialize
instruction.
It’ll need the following accounts:
counter
- the counter account initialized in the instructionuser
- payer for the initializationsystem_program
- the system program is required for the initialization of
any new accountsinitialize
instructionNow that we have our Counter
account and Initialize
type , let’s implement
the initialize
instruction within #[program]
. This instruction requires a
Context
of type Initialize
and takes no additional instruction data. In the
instruction logic, we are simply setting the counter
account’s count
field
to 0
.
Context
type Update
Now, using the #[derive(Accounts)]
macro again, let’s create the Update
type
that lists the accounts that the increment
instruction requires. It’ll need
the following accounts:
counter
- an existing counter account to incrementuser
- payer for the transaction feeAgain, we’ll need to specify any constraints using the #[account(..)]
attribute:
increment
instructionLastly, within #[program]
, let’s implement an increment
instruction to
increment the count
once a counter
account is initialized by the first
instruction. This instruction requires a Context
of type Update
(implemented
in the next step) and takes no additional instruction data. In the instruction
logic, we are simply incrementing an existing counter
account’s count
field
by 1
.
All together, the complete program will look like this:
Run anchor build
to build the program.
Anchor tests are typically Typescript integration tests that use the mocha test
framework. We’ll learn more about testing later, but for now navigate to
anchor-counter.ts
and replace the default test code with the following:
The above code generates a new keypair for the counter
account we’ll be
initializing and creates placeholders for a test of each instruction.
Next, create the first test for the initialize
instruction:
Next, create the second test for the increment
instruction:
Lastly, run anchor test
and you should see the following output:
Running anchor test
automatically spins up a local test validator, deploys
your program, and runs your mocha tests against it. Don’t worry if you’re
confused by the tests for now - we’ll dig in more later.
Congratulations, you just built a Orbitron Native Chain program using the Anchor framework! Feel free to reference the solution code if you need some more time with it.
Now it’s your turn to build something independently. Because we’re starting with 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.
counter
accountincrement
and decrement
instructionAs always, get creative with these challenges and take them beyond the basic instructions if you want - and have fun!
Try to do this independently if you can! But if you get stuck, feel free to reference the solution code.
Create your first Orbitron Native Chain onchain program in Anchor.
Orbitron Native Chain’s ability to run arbitrary executable code is part of what makes it so powerful. Orbitron Native Chain programs, similar to “smart contracts” in other blockchain environments, are quite literally the backbone of the Orbitron Native Chain ecosystem. And the collection of programs grows daily as developers and creators dream up and deploy new programs.
This lesson will give you a basic introduction to writing and deploying a Orbitron Native Chain program using the Rust programming language and the Anchor framework.
Anchor makes writing Orbitron Native Chain programs easier, faster, and more secure, making it the “go-to” framework for Orbitron Native Chain development. It makes it easier to organize and reason about your code, implements common security checks automatically, and removes a significant amount of boilerplate code that is otherwise associated with writing a Orbitron Native Chain program.
Anchor uses macros and traits to generate boilerplate Rust code for you. These provide a clear structure to your program so you can more easily reason about your code. The main high-level macros and attributes are:
declare_id
- a macro for declaring the program’s onchain address#[program]
- an attribute macro used to denote the module containing the
program’s instruction logicAccounts
- a trait applied to structs representing the list of accounts
required for an instruction#[account]
- an attribute macro used to define custom account types for the
programLet’s talk about each of them before putting all the pieces together.
The declare_id
macro is used to specify the onchain address of the program
(i.e. the programId
). When you build an Anchor program for the first time, the
framework will generate a new keypair. This becomes the default keypair used to
deploy the program unless specified otherwise. The corresponding public key
should be used as the programId
specified in the declare_id!
macro.
The #[program]
attribute macro defines the module containing all of your
program’s instructions. This is where you implement the business logic for each
instruction in your program.
Each public function in the module with the #[program]
attribute will be
treated as a separate instruction.
Each instruction function requires a parameter of type Context
and can
optionally include additional function parameters representing instruction data.
Anchor will automatically handle instruction data deserialization so that you
can work with instruction data as Rust types.
Context
The Context
type exposes instruction metadata and accounts to your instruction
logic.
Context
is a generic type where T
defines the list of accounts an
instruction requires. When you use Context
, you specify the concrete type of
T
as a struct that adopts the Accounts
trait (e.g.
Context<AddMovieReviewAccounts>
). Through this context argument the
instruction can then access:
ctx.accounts
)ctx.program_id
) of the executing programctx.remaining_accounts
). The remaining_accounts
is
a vector that contains all accounts that were passed into the instruction but
are not declared in the Accounts
struct.Accounts
struct (ctx.bumps
)The Accounts
trait defines a data structure of validated accounts. Structs
adopting this trait define the list of accounts required for a given
instruction. These accounts are then exposed through an instruction’s Context
so that manual account iteration and deserialization is no longer necessary.
You typically apply the Accounts
trait through the derive
macro (e.g.
#[derive(Accounts)]
). This implements an Accounts
deserializer on the given
struct and removes the need to deserialize each account manually.
Implementations of the Accounts
trait are responsible for performing all
requisite constraint checks to ensure the accounts meet the conditions required
for the program to run securely. Constraints are provided for each field using
the #account(..)
attribute (more on that shortly).
For example, instruction_one
requires a Context
argument of type
InstructionAccounts
. The #[derive(Accounts)]
macro is used to implement the
InstructionAccounts
struct which includes three accounts: account_name
,
user
, and system_program
.
When instruction_one
is invoked, the program:
InstructionAccounts
structIf any accounts passed into instruction_one
fail the account validation or
security checks specified in the InstructionAccounts
struct, then the
instruction fails before even reaching the program logic.
You may have noticed in the previous example that one of the accounts in
InstructionAccounts
was of type Account
, one was of type Signer
, and one
was of type Program
.
Anchor provides a number of account types that can be used to represent accounts. Each type implements different account validation. We’ll go over a few of the common types you may encounter, but be sure to look through the full list of account types.
Account
Account
is a wrapper around UncheckedAccount
that verifies program ownership
and deserializes the underlying data into a Rust type.
Recall the previous example where InstructionAccounts
had a field
account_name
:
The Account
wrapper here does the following:
data
in the format of type AccountStruct
AccountStruct
type.When the account type specified in the Account
wrapper is defined within the
same crate using the #[account]
attribute macro, the program ownership check
is against the programId
defined in the declare_id!
macro.
The following are the checks performed:
Signer
The Signer
type validates that the given account signed the transaction. No
other ownership or type checks are done. You should only use the Signer
when
the underlying account data is not required in the instruction.
For the user
account in the previous example, the Signer
type specifies that
the user
account must be a signer of the instruction.
The following check is performed for you:
Program
The Program
type validates that the account is a certain program.
For the system_program
account in the previous example, the Program
type is
used to specify the program should be the system program. Anchor provides a
System
type which includes the programId
of the system program to check
against.
The following checks are performed for you:
#[account(..)]
The #[account(..)]
attribute macro is used to apply constraints to accounts.
We’ll go over a few constraint examples in this and future lessons, but at some
point be sure to look at the full
list of possible constraints.
Recall again the account_name
field from the InstructionAccounts
example.
Notice that the #[account(..)]
attribute contains three comma-separated
values:
init
- creates the account via a CPI to the system program and initializes
it (sets its account discriminator)payer
- specifies the payer for the account initialization to be the user
account defined in the structspace
- specifies that the space allocated for the account should be 8 + 8
bytes. The first 8 bytes are for a discriminator that Anchor automatically
adds to identify the account type. The next 8 bytes allocate space for the
data stored on the account as defined in the AccountStruct
type.For user
we use the #[account(..)]
attribute to specify that the given
account is mutable. The user
account must be marked as mutable because
lamports will be deducted from the account to pay for the initialization of
account_name
.
Note that the init
constraint placed on account_name
automatically includes
a mut
constraint so that both account_name
and user
are mutable accounts.
#[account]
The #[account]
attribute is applied to structs representing the data structure
of a Orbitron Native Chain account. It implements the following traits:
AccountSerialize
AccountDeserialize
AnchorSerialize
AnchorDeserialize
Clone
Discriminator
Owner
You can read more about the
details of each trait.
However, mostly what you need to know is that the #[account]
attribute enables
serialization and deserialization, and implements the discriminator and owner
traits for an account.
The discriminator is an 8-byte unique identifier for an account type derived from the first 8 bytes of the SHA256 hash of the account type’s name. The first 8 bytes are reserved for the account discriminator when implementing account serialization traits (which is almost always in an Anchor program).
As a result, any calls to AccountDeserialize
’s try_deserialize
will check
this discriminator. If it doesn’t match, an invalid account was given, and the
account deserialization will exit with an error.
The #[account]
attribute also implements the Owner
trait for a struct using
the programId
declared by declareId
of the crate #[account]
is used in. In
other words, all accounts initialized using an account type defined using the
#[account]
attribute within the program are also owned by the program.
As an example, let’s look at AccountStruct
used by the account_name
of
InstructionAccounts
The #[account]
attribute ensures that it can be used as an account in
InstructionAccounts
.
When the account_name
account is initialized:
AccountStruct
discriminatorAccountStruct
programId
from declare_id
When you combine all of these Anchor types you end up with a complete program. Below is an example of a basic Anchor program with a single instruction that:
You are now ready to build your own Orbitron Native Chain program using the Anchor framework!
Before we begin, install Anchor by following the steps from the Anchor docs.
For this lab we’ll create a simple counter program with two instructions:
Create a new project called anchor-counter
by running anchor init
:
Change into the new directory, then run anchor build
Anchor build will also generate a keypair for your new program - the keys are
saved in the target/deploy
directory.
Open the file lib.rs
and look at declare_id!
:
Run anchor keys sync
You’ll see the Anchor updates both:
declare_id!()
in lib.rs
Anchor.toml
To match the key generated during anchor build
:
Finally, delete the default code in lib.rs
until all that is left is the
following:
Counter
First, let’s use the #[account]
attribute to define a new Counter
account
type. The Counter
struct defines one count
field of type u64
. This means
that we can expect any new accounts initialized as a Counter
type to have a
matching data structure. The #[account]
attribute also automatically sets the
discriminator for a new account and sets the owner of the account as the
programId
from the declare_id!
macro.
Context
type Initialize
Next, using the #[derive(Accounts)]
macro, let’s implement the Initialize
type that lists and validates the accounts used by the initialize
instruction.
It’ll need the following accounts:
counter
- the counter account initialized in the instructionuser
- payer for the initializationsystem_program
- the system program is required for the initialization of
any new accountsinitialize
instructionNow that we have our Counter
account and Initialize
type , let’s implement
the initialize
instruction within #[program]
. This instruction requires a
Context
of type Initialize
and takes no additional instruction data. In the
instruction logic, we are simply setting the counter
account’s count
field
to 0
.
Context
type Update
Now, using the #[derive(Accounts)]
macro again, let’s create the Update
type
that lists the accounts that the increment
instruction requires. It’ll need
the following accounts:
counter
- an existing counter account to incrementuser
- payer for the transaction feeAgain, we’ll need to specify any constraints using the #[account(..)]
attribute:
increment
instructionLastly, within #[program]
, let’s implement an increment
instruction to
increment the count
once a counter
account is initialized by the first
instruction. This instruction requires a Context
of type Update
(implemented
in the next step) and takes no additional instruction data. In the instruction
logic, we are simply incrementing an existing counter
account’s count
field
by 1
.
All together, the complete program will look like this:
Run anchor build
to build the program.
Anchor tests are typically Typescript integration tests that use the mocha test
framework. We’ll learn more about testing later, but for now navigate to
anchor-counter.ts
and replace the default test code with the following:
The above code generates a new keypair for the counter
account we’ll be
initializing and creates placeholders for a test of each instruction.
Next, create the first test for the initialize
instruction:
Next, create the second test for the increment
instruction:
Lastly, run anchor test
and you should see the following output:
Running anchor test
automatically spins up a local test validator, deploys
your program, and runs your mocha tests against it. Don’t worry if you’re
confused by the tests for now - we’ll dig in more later.
Congratulations, you just built a Orbitron Native Chain program using the Anchor framework! Feel free to reference the solution code if you need some more time with it.
Now it’s your turn to build something independently. Because we’re starting with 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.
counter
accountincrement
and decrement
instructionAs always, get creative with these challenges and take them beyond the basic instructions if you want - and have fun!
Try to do this independently if you can! But if you get stuck, feel free to reference the solution code.