Writing Your First Arch Program
This guide will walk you through creating your first Arch program from scratch. We'll build a simple counter program that demonstrates the core concepts of Arch development while providing hands-on experience with the development workflow.
Prerequisites
Before starting, ensure you have:
- Rust and Cargo installed
- CLI installed and configured (see Quick Start Guide)
- A running validator node
- Basic understanding of Arch concepts
Project Setup
- Create a new project directory:
mkdir my-counter-program
cd my-counter-program
- Initialize a new Rust project:
cargo init --lib
- Add necessary dependencies to
Cargo.toml
:
[package]
name = "my-counter-program"
version = "0.1.0"
edition = "2021"
[dependencies]
arch-program = { git = "https://github.com/Arch-Network/arch-program" }
borsh = "0.10.3"
[lib]
crate-type = ["cdylib"]
Writing the Program
Let's create a simple program that:
- Stores a counter in an account
- Can increment the counter
- Can decrement the counter
- Can reset the counter
- Define our program's state structure in
src/lib.rs
:
#![allow(unused)] fn main() { use arch_program::{ account::AccountInfo, entrypoint, msg, program_error::ProgramError, pubkey::Pubkey, }; use borsh::{BorshDeserialize, BorshSerialize}; #[derive(BorshSerialize, BorshDeserialize, Debug)] pub struct CounterAccount { pub count: u64, } #[derive(BorshSerialize, BorshDeserialize, Debug)] pub enum CounterInstruction { Increment, Decrement, Reset, } }
- Implement the program logic:
#![allow(unused)] fn main() { entrypoint!(process_instruction); pub fn process_instruction( _program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> Result<(), ProgramError> { // Get the account to store counter data let account_iter = &mut accounts.iter(); let counter_account = next_account_info(account_iter)?; // Verify account is writable if !counter_account.is_writable { return Err(ProgramError::InvalidAccountData); } // Deserialize the instruction let instruction = CounterInstruction::try_from_slice(instruction_data)?; // Get current counter value let mut counter = match CounterAccount::try_from_slice(&counter_account.data.borrow()) { Ok(data) => data, Err(_) => CounterAccount { count: 0 }, }; // Process the instruction match instruction { CounterInstruction::Increment => { counter.count = counter.count.checked_add(1).ok_or(ProgramError::Custom(1))?; msg!("Counter incremented to {}", counter.count); } CounterInstruction::Decrement => { counter.count = counter.count.checked_sub(1).ok_or(ProgramError::Custom(2))?; msg!("Counter decremented to {}", counter.count); } CounterInstruction::Reset => { counter.count = 0; msg!("Counter reset to 0"); } } // Serialize and save the counter counter.serialize(&mut &mut counter_account.data.borrow_mut()[..])?; Ok(()) } }
Building the Program
- Build your program:
cargo build-sbf
This will create a compiled program at target/deploy/my_counter_program.so
Deploying the Program
- Deploy your program using the CLI:
cli deploy target/deploy/my_counter_program.so
Save the Program ID output - you'll need it to interact with your program.
Creating a Counter Account
Before we can use our counter, we need to create an account to store its state:
cli create-account <PROGRAM_ID> 8
Save the account address - you'll need it to interact with your counter.
Testing Your Program
You can now interact with your program using the CLI:
- Increment the counter:
cli invoke <PROGRAM_ID> <ACCOUNT_ADDRESS> --data 00
- Decrement the counter:
cli invoke <PROGRAM_ID> <ACCOUNT_ADDRESS> --data 01
- Reset the counter:
cli invoke <PROGRAM_ID> <ACCOUNT_ADDRESS> --data 02
Next Steps
Now that you've created your first program, you can:
- Add more features to the counter program
- Learn about cross-program invocation
- Explore more complex Program Examples
- Study the Understanding Arch Programs guide for deeper insights