Solana

Solana

Solana is a high-performance blockchain that can support thousands of nodes and 50,000 transactions per second. It is designed to be a fast, secure, and scalable platform for decentralized applications and crypto projects.

Solana uses a unique combination of technologies to achieve its high performance, including a proof-of-stake consensus mechanism, a novel architecture called the Tower BFT, and a system of parallel processing called Gulf Stream.

Solana’s native token, SOL, is used to pay for transactions on the network and to secure the blockchain through staking.

Client-side development involves building applications that interact with on-chain solana programs. This is done using TypeScript. Onchain program development is creating custom apps that run on the blockchain, using the Rust language.

Terminology

  • Keypair: A matching pair of public key and secret key.
  • Public key: An address that points to an account on the Solana network.
  • Secret key: Used to verify the authority over an account.
  • Asymmetric cryptography: Also called public key cryptography, in which each participant possess a pair of keys.
  • Encryption (Asymmetric): If encrypted with the public key, only the secret key can be used to read it.
  • Signature (Asymmetrics): If encrypted with a secret key, the authenticity of the signature can be verified using the public key.
  • SOL: Solana’s native token.
  • Lamport: 1 billionth of a SOL.
  • Transactions: A set of instructions that invoke solana programs. All modifications to onchain data happen through transactions.
  • SPL Tokens: All other tokens (fungible or non-fungible) on the Solana blockchain, except the native token SOL.
  • Wallets: Applications that store the secret key and secure transaction signing. e.g. Phantom Wallet
  • PDAs: 32 byte strings store accounts that are designed to be controlled by a specific program.

On-chain Development

Each solana cluster, mainnet-beta, testnet, devnet or localnet is effectively a single computer with a globally synchronized state. devnet is where we test our application, whereas testnet is for validators.

Solana programs are most commonly written in Rust, typically with the Anchor Framework. The framework handle common tasks like redirecting incoming instructions to the right instruction handler, deserializing data for incoming transactions, verifying accounts etc.

Each program is associated with an address, typically created during anchor init. It’s stored in the target/deploy directory.

Instruction handlers are crucial components of smart contracts which how the program should respond to specific instructions. Instruction handlers are like HTTP route handlers, and incoming transactions are like HTTP requests. But instead of returning data, the handlers write their data to accounts on Solana. Programs can transfer SOL, which will end up in user wallet addresses. Programs store data as key-value pairs in Program-Derived Addresses (PDAs).

Native / Non-Anchor development requires manual serialization & deserialization of data, which is the process of converting, the data into a format that is easily stored or transmitted, and back. Solana uses the BORSH serializer.

Transactions are made up of an array of instructions. Every transaction contains

  • An array of every account it intends to read or write
  • One or more instructions
  • A recent blockhash
  • One or more signatures

@solana/web3.js simplifies this to just instructions and signatures. Every Instruction contains

  • The program ID of the intended program
  • An array of accounts involved
  • Instruction data

The instructions in a transaction are processed Atomically.

Anchor

Anchor is a framework for building Solana programs. Anchor programs are written in Rust, and the framework compiles them into Solana programs.

Initialize a project:

anchor init demo

Build the project:

cd demo
anchor build 

This would store the program keypair in target/deploy.

If the build fails, it’s due to Anchor using an older version of rust (run cargo-build-sbf --version). To fix this, upgrade to a newer version of solana tools and rerun build.

solana-install init 1.18.18

To obtain the public key and to update it in the program, run:

anchor keys list
anchor keys sync

Update the following part in the Anchor.toml file for deployment to the devnet:

[provider]
cluster = "Devnet"

Now deploy the project:

anchor deploy

This may fail due to insufficient balance. Hence, obtain free SOL using solana airdrop 2 and then try again.

Run solana address to get the user account address, and solana balance [address] for viewing the amount of SOL in the account.

The following command deploys after running tests located in /tests/demo.ts:

anchor test

Anchor Program

Anchor uses macros and traits to generate boilerplate code. The main macros and traits include:

  • declare_id: To declare the on-chain address of the program.
    declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
    
  • #[program]: macro denoting the module containing instruction logic.
    #[program]
    mod program_module_name {
      use super::*;
    
      // Each public function inside a module with #[program] attribute macro
      // will be treated as a separate instruction.
      pub fn instruction_one(ctx: Context<InstructionAccounts>, instruction_data: u64) -> Result<()> {
          ctx.accounts.account_name.data = instruction_data;
          Ok(())
      }
    }
    
    • The Context argument, ctx enables us to access the instruction metadata (ctx.program_id) and accounts (ctx.accounts) passed to the instruction handler.
  • Accounts: a trait applied to structs denoting the list of accounts required for an instruction.

    #[derive(Accounts)]
    pub struct InstructionAccounts {
      // InstructionAccounts struct includes 3 accounts: 
      // account_name (Account), user (Signer), system_program (Program)
    
      // Constraints for each field is provided using #[account(...)]
      // 'Account' verifies program ownership
      #[account(init, payer = user, space = 8 + 8)] 
      pub account_name: Account<'info, AccountStruct>,
      // 'Signer' validates the transaction
      #[account(mut)]
      pub user: Signer<'info>,
      // 'Program' validates that the account is a program
      pub system_program: Program<'info, System>,
    }
    
  • #[account]: an attribute macro used to define custom Solana account types. It enables serialization and deserialization.
    #[account]
    pub struct AccountStruct {
        data: u64
    }
    

Client-Side Anchor Development

An IDL (Interface Description Language) file must be created at /target/idl/, which represents the structure of a program. To interact with an Anchor program on the client-side, we use the @coral-xyz/anchor typescript client.

The Program instance represents a Solana program and provides a custom API for reading and writing to the program. To create it, we need

  • IDL file
  • Connection (to the cluster)
  • Wallet (keypair for payment and signing)
  • ProgramId

program

The Anchor Provider object combines a connection to a cluster and a wallet to enable transaction signing. The MethodsBuilder instance provides an interface through Program for building instructions and transactions.

Create a new frontend project:

npx create-react-app demo-frontend --template typescript
npm install # Install dependencies
npm run dev # Start the development server

Add the IDL to the root of our client-side project and import it to demo-frontend/components/Initialize.tsx via

import idl from "../idl.json"

Next, setup wallet and connection:

import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react"

const { connection } = useConnection()
const wallet = useAnchorWallet()

Now set up the Provider:

import { AnchorProvider, setProvider } from "@coral-xyz/anchor"

const provider = new AnchorProvider(connection, wallet, {})
setProvider(provider)

Create an instance of Program:

import { Program, Idl, AnchorProvider, setProvider } from "@coral-xyz/anchor"

const programId = new PublicKey("5ydvbN6hFgsnK3m8uszNGD9fS69tUyGv96MtLVYBh4bC")
const program = new Program(idl as Idl, programId)

Once the program object is set up, we can use the Anchor MethodsBuilder to build instructions and transactions related to the program.

// Send Transaction
await program.methods
  .instructionName(instructionDataInputs) // Call the method on the Rust app
  .accounts({}) // Accounts expected by the method based on the IDL
  .signers([]) // (Optional) Additional signers
  .rpc() // Create and send transaction, return TransactionSignature
  // Wallet from Provider is automatically included while using 'rpc()'
// Create first instruction
const instructionOne = await program.methods
  .instructionOneName(instructionOneDataInputs)
  .accounts({})
  .instruction()

// Create second instruction
const instructionTwo = await program.methods
  .instructionTwoName(instructionTwoDataInputs)
  .accounts({})
  .instruction()

// Add both instructions to a single transaction
const transaction = new Transaction().add(instructionOne, instructionTwo)

// Send transaction
await sendTransaction(transaction, connection)

The transactions can be inspected at:

  https://explorer.solana.com/tx/<transaction_id>?cluster=devnet

Note that, client side uses PascalCase, whereas for the on-chain code in Rust, snake_case is used.

SPL Tokens

SPL(Solana Program Library) tokens are fungible / non-fungible tokens built on the Solana blockchain.

For interacting with an SPL token, install the command line utility:

cargo install spl-token-cli

To create a token, run:

spl-token create-token

which will return the token identifier and the token signature. To check the balance of tokens for the current address:

spl-token supply <token-identifier>

Now we can mint some new tokens via:

spl-token mint <token-identifier> <token-amount>

To get a tokens and your balance, run:

spl-token accounts

To send our SPL tokens, do:

spl-token transfer <token-identifier> <token-amount> <wallet-address> --fund-recipient