Skip to content

Build a native Solana program to create/initialize an account, deposit SOL into it, and withdraw 10% of the deposited SOL at a given time from it.

Notifications You must be signed in to change notification settings

Laugharne/ssf_s4_exo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Solana Native Developpement

Deployed program

Program Id: EZau1t4dYPQbndegQx5XynT6oADjVSMShuK5UePeWyP1

Program on devnet : Solana Explorer Link

Transaction on devnet : Solana Explorer Link


Overview

Exercise: Build a native Solana program to create/initialize an account, deposit SOL into it, and withdraw 10% of the deposited SOL at a given time from it.

Topics in this exercice :

  • Rust
  • PDA
  • CPI
  • SOL transfer
  • Build native solana program & deploy on devnet

Native Solana Project

cargo new my-solana-program --lib
cd my-solana-program

Add Solana dependancies

In Cargo.toml file, add needed solana dependancies :

Replace 1.18.17 by the last stable needed version.

[dependencies]
solana-program = "1.18.17"
borsh = "1.5.1"            # Borsh for serialisation/deserialisation
borsh-derive = "1.5.1"     # using `derive` macro

And libs, if you want to generate .so...

[lib]
crate-type = ["cdylib", "lib"]

Compile the program

Compile your program to generate a Solana-compatible binary.

cargo build-bpf

This will generate a .so file in the target/deploy/ directory that is the program binary.

Deploy the program

Deploy the compiled program to a Solana network (like devnet):

solana program deploy target/deploy/program.so

This will return the address of your program on Solana.

Components

processor.rs

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub enum NativeVaultInstruction {
    Initialize(),
    Deposit(u64),
    PartialWithdraw(),
}
pub fn process_instruction(
    program_id: &Pubkey,
    accounts  : &[AccountInfo],
    input     : &[u8],
) -> ProgramResult {
    let instruction: NativeVaultInstruction = NativeVaultInstruction::try_from_slice(input)?;

    match instruction {

        NativeVaultInstruction::Initialize() => initialize(
            program_id,
            accounts,
        ),

        NativeVaultInstruction::Deposit(args) => deposit(
            program_id,
            accounts,
            args
        ),

        NativeVaultInstruction::PartialWithdraw() => partial_withdraw(
            program_id,
            accounts,
        ),

    }

}

instructions.rs

Creating account

See initialize() funcion for more comments

// Creating account
let rent: Rent = Rent::get()?;

let required_lamports: u64 = rent.minimum_balance(
    std::mem::size_of::<Vault>()
);

let ix: solana_program::instruction::Instruction = system_instruction::create_account(
    &user.key,
    &vault.key,
    required_lamports,
    std::mem::size_of::<Vault>() as u64,
    program_id,
);

// `ix` contains the account creation instruction ready to be executed.

// Sends the account creation instruction to the Solana blockchain for execution.
invoke(
    &ix,
    &[
        user.clone          (),
        vault.clone         (),
        system_program.clone(),
    ]
)?;

If the instruction is successfully executed, the vault account will be created on the Solana blockchain, and the balance required for the rent will be transferred from the user account to the vault account.

Data access (serialization/usage/deserialization)

// Access (deserialization) to the data inside the `vault` account
let mut vault_data: Vault = Vault::try_from_slice(&vault.data.borrow())?;
vault_data.owner = *user.key;
// Serializes the updated Vault structure and writes that data to
// the vault account, storing the new values ​​in the blockchain.
vault_data.serialize(&mut &mut vault.data.borrow_mut()[..])?;

PDA creation

Derive the PDA using the user's public key

let (computed_pda, bump_seed) = Pubkey::find_program_address(
    &[
        TAG_SSF_PDA    ,     // A constant seed used to differentiate this PDA.
        user.key.as_ref(),   // The public key of the user.
    ],
    program_id  // The program ID for which the address is derived.
);

Verify that the derived PDA matches the provided PDA.

if user_pda.key != &computed_pda {
    return Err(ProgramError::InvalidAccountData);
}

If the PDA account is empty (new), it needs to be initialized.

if user_pda.data_is_empty() {
    // Gets the rent exemption amount required to keep the account alive.
    let rent: Rent = Rent::get()?;
    let required_lamports: u64 = rent.minimum_balance(std::mem::size_of::<Pda>());

    // Creates an instruction to create a new account for the PDA.
    let ix: solana_program::instruction::Instruction = system_instruction::create_account(
        &user.key               ,            // The user paying for the creation of the account.
        &computed_pda           ,            // The PDA to be created.
        required_lamports       ,            // The lamports needed for rent exemption.
        std::mem::size_of::<Pda>() as u64,   // Size of the account in bytes.
        program_id              ,            // The program ID for which the account is being created.
    );

    // Invokes the instruction to create the PDA account.
    invoke(
        &ix,
        &[
            user.clone          (),   // User's account
            user_pda.clone      (),   // PDA account
            system_program.clone(),   // System program account
        ],
    )?;

    // Initialize the PDA's data with default values.
    let mut vault_data: Pda = Pda {
        signer      : *user.key,   // Sets the user as the owner of the PDA.
        balance     : 0,           // Initial balance of the PDA.
        deposit_time: 0,           // Initial deposit timestamp set to 0.
        done        : false,       // Indicates if the PDA is finalized (closed).
    };

    vault_data.serialize(&mut &mut user_pda.data.borrow_mut()[..])?;

}

Get timestamp

// Expressed as Unix time (i.e. seconds since the Unix epoch).
let clock: Clock        = Clock::get()?;
vault_data.deposit_time = clock.unix_timestamp as i64;

SOL transfer

// Expressed as Unix time (i.e. seconds since the Unix epoch).
let clock: Clock        = Clock::get()?;
vault_data.deposit_time = clock.unix_timestamp as i64;

Tree repository

.
├── client
│   ├── README.md
│   ├── bun.lockb
│   ├── index.ts
│   ├── package.json
│   └── tsconfig.json
├── src
│   ├── instructions.rs
│   ├── lib.rs
│   └── processor.rs
├── .gitignore
├── 2024-08-11-14-07-55.png
├── Cargo.lock
├── Cargo.toml
└── README.md

Resources

About

Build a native Solana program to create/initialize an account, deposit SOL into it, and withdraw 10% of the deposited SOL at a given time from it.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published