Build & Deploy Your First Program

This guide provides a step-by-step overview of deploying a program on the Sonic HyperGrid. Familiarity with command-line tools is necessary.

1. Install CLI Tools

Ensure that the Solana command line tools are installed as you will be using these tools throughout the deployment process.

2. Write Your Program

Develop your program logic. For those using Rust with Anchor, input your program logic into the lib.rs file located in the src directory of your project. Confirm that your program compiles correctly and passes all tests.

3. Build Your Program

Compile your program to a BPF (Berkeley Packet Filter) executable, the format required for on-chain programs:

With Anchor

Run anchor build. Anchor manages the compilation details.

Without Anchor

Use cargo build-bpf for direct Rust usage.

4. Deploy Your Program

Before deploying your program, ensure your CLI is set to the Sonic Devnet network using the following command:

solana config set --url https://devnet.sonic.game

After deploying your building your program and configuring your RPC URL, you can deploy your program with the following command:

solana program deploy <PATH_TO_YOUR_COMPILED_PROGRAM>

Ensure that you have enough SOL in your wallet to cover deployment costs. You can get some devnet tokens by using the Sonic faucet.


After configuring your network, you may now deploy your program to Sonic. Use the following commands based on your setup:

With Anchor

If you have the Anchor CLI installed, you can deploy your program with the following command. This handles the deployment automatically.

anchor deploy

Without Anchor

If you're using the native solana method to author your program, you can also use the Solana CLI to deploy your program.

5. Verify Deployment

Post-deployment, check that your program operates as intended. Interact with your program using Solana's web3.js library or the CLI tools to send transactions.

Additional Tips:

  • Keep your program's keypair secure for future upgrades or interactions.

  • Monitor the network and your program's performance, particularly if it gains widespread usage.

Example: "Hello Sonic!" Program

This example implements a simple Greeting Counter Program on Sonic.

With Solana Native Program

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program_error::ProgramError,
    pubkey::Pubkey,
};

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct GreetingAccount {
    pub counter: u32,
}

entrypoint!(process_instruction);

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    
    msg!("Hello, Sonic World!");
    
    let accounts_iter = &mut accounts.iter();
    let account = next_account_info(accounts_iter)?;

    if account.owner != program_id {
        msg!("Greeted account does not have the correct program id");
        return Err(ProgramError::IncorrectProgramId);
    }

    let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;
    greeting_account.counter += 1;
    greeting_account.serialize(&mut *account.data.borrow_mut())?;
    
    msg!("Greeted {} time(s)!", greeting_account.counter);
    Ok(())
}

With Anchor Program

If you are building with Anchor, you may use the variant below. Before deploying your program, please remember to change the program ID after building your program with anchor build.

use anchor_lang::prelude::*;

// !!! Replace with your program ID after running `anchor build` !!!
declare_id!("<REPLACE_WITH_YOUR_PROGRAM_ID>");

#[program]
pub mod hello_sonic_world {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, authority: Pubkey) -> Result<()> {
        let greeting_account = &mut ctx.accounts.greeting_account;
        greeting_account.counter = 0;
        greeting_account.authority = authority;
        Ok(())
    }

    pub fn increment_greeting(ctx: Context<IncrementGreeting>) -> Result<()> {
        msg!("Hello, Sonic World!");

        let greeting_account = &mut ctx.accounts.greeting_account;
        greeting_account.counter += 1;

        msg!("Greeted {} time(s)!", greeting_account.counter);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 8 + 4 + 32)]
    pub greeting_account: Account<'info, GreetingAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct IncrementGreeting<'info> {
    #[account(mut, has_one = authority)]
    pub greeting_account: Account<'info, GreetingAccount>,
    pub authority: Signer<'info>,
}

#[account]
pub struct GreetingAccount {
    pub counter: u32,
    pub authority: Pubkey,
}

Run your dAPP on Sonic Devnet

Example "Hello World" application in TypeScript:

With Solana Native Program dApp

import {
  Connection,
  PublicKey,
  Keypair,
  Transaction,
  TransactionInstruction,
  SystemProgram,
  sendAndConfirmTransaction
} from '@solana/web3.js';
import bs58 from 'bs58';
import * as borsh from 'borsh';

class GreetingAccount {
  counter = 0;
  constructor(fields?: { counter: number }) {
    if (fields) {
      this.counter = fields.counter;
    }
  }
}

const GreetingSchema = new Map([
  [GreetingAccount, { kind: 'struct', fields: [['counter', 'u32']] }]
]);

const programId = new PublicKey('<REPLACE_WITH_YOUR_PROGRAM_ID>');
const connection = new Connection('https://devnet.sonic.game', 'confirmed');
const feePayer = Keypair.fromSecretKey(
  bs58.decode(
    '<REPLACE_WITH_YOUR_PRIVATE_KEY>'
  )
);

async function sayHello() {
  const GREETING_SIZE = borsh.serialize(
    GreetingSchema,
    new GreetingAccount()
  ).length;

  const greetedAccountKeypair = new Keypair();
  const greetedAccountPubkey = greetedAccountKeypair.publicKey;

  const lamports =
    await connection.getMinimumBalanceForRentExemption(GREETING_SIZE);

  const createGreetingAccountIx = SystemProgram.createAccount({
    fromPubkey: feePayer.publicKey,
    lamports,
    newAccountPubkey: greetedAccountPubkey,
    programId: programId,
    space: GREETING_SIZE
  });

  // Create greet instruction
  const greetIx = new TransactionInstruction({
    keys: [
      {
        pubkey: greetedAccountPubkey,
        isSigner: false,
        isWritable: true
      }
    ],
    programId
  });

  const transaction = new Transaction().add(createGreetingAccountIx, greetIx);

  const txHash = await sendAndConfirmTransaction(connection, transaction, [
    feePayer,
    greetedAccountKeypair
  ]);
  console.log(`Use 'solana confirm -v ${txHash}' to see the logs`);

  const greetingAccount = await connection.getAccountInfo(greetedAccountPubkey);
  // Deserialize the account data
  const deserializedAccountData = borsh.deserialize(
    GreetingSchema,
    GreetingAccount,
    greetingAccount!.data
  );

  console.log(
    'Sonic Greeting succesful. Account data:',
    deserializedAccountData
  );
}

sayHello()
  .then(() => console.log('Done'))
  .catch(console.error);

With Anchor Program dApp

import {
  Connection,
  PublicKey,
  Keypair,
  Transaction,
  SystemProgram,
  sendAndConfirmTransaction
} from '@solana/web3.js';
import { Program, AnchorProvider, web3 } from '@project-serum/anchor';
import { IDL } from './idl/hello_sonic_world.json'; // Make sure you have the correct IDL file

const programId = new PublicKey('<REPLACE_WITH_YOUR_PROGRAM_ID>');
const connection = new Connection('https://devnet.solana.com', 'confirmed');
const feePayer = Keypair.fromSecretKey(
  bs58.decode('<REPLACE_WITH_YOUR_PRIVATE_KEY>')
);

// Anchor setup
const wallet = new AnchorProvider(connection, feePayer, {
  preflightCommitment: 'confirmed',
});

const program = new Program(IDL, programId, wallet);

async function sayHello() {
  // Create the greeting account
  const greetedAccountKeypair = Keypair.generate();
  const greetedAccountPubkey = greetedAccountKeypair.publicKey;

  const lamports = await connection.getMinimumBalanceForRentExemption(
    8 + 4 + 32 // space for the account (discriminator, counter, and authority)
  );

  const createGreetingAccountIx = SystemProgram.createAccount({
    fromPubkey: feePayer.publicKey,
    lamports,
    newAccountPubkey: greetedAccountPubkey,
    programId: program.programId,
    space: 8 + 4 + 32, // size of the GreetingAccount (8 discriminator + 4 counter + 32 authority)
  });

  const transaction = new Transaction().add(createGreetingAccountIx);

  await sendAndConfirmTransaction(connection, transaction, [
    feePayer,
    greetedAccountKeypair,
  ]);

  console.log(`Greeting account created with public key: ${greetedAccountPubkey.toBase58()}`);

  // Initialize the greeting account
  await program.rpc.initialize(feePayer.publicKey, {
    accounts: {
      greetingAccount: greetedAccountPubkey,
      user: feePayer.publicKey,
      systemProgram: SystemProgram.programId,
    },
    signers: [greetedAccountKeypair, feePayer],
  });

  console.log('Greeting account initialized');

  // Increment the greeting counter
  await program.rpc.incrementGreeting({
    accounts: {
      greetingAccount: greetedAccountPubkey,
      authority: feePayer.publicKey,
    },
  });

  console.log('Greeting incremented');

  // Fetch the account data
  const account = await program.account.greetingAccount.fetch(
    greetedAccountPubkey
  );

  console.log('Greeting successful. Account data:', account);
}

sayHello()
  .then(() => console.log('Done'))
  .catch(console.error);

Running the above script [either with Solana Native, or with Anchor] programs should output the following:

Use 'solana confirm -v TsCwj8s8meQFSfqUKdHqDQVzfUbqRcugP2gJ5kaGCj4v4D7aKvohqQQncvUFunqZVH9sS6jhESMjwe6SHGPfrXf' to see the logs
Sonic Greeting succesful. Account data: GreetingAccount { counter: 1 }
Done

An example the above executed transaction can be found here. The program address for this program is here.

Last updated