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 rerunbuild
.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, andsolana 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.
- The Context argument,
-
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
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