Anchor Framework
Use FortiBlox Nexus RPC with Anchor for Solana program development and testing
Anchor Framework
Anchor is the most popular framework for Solana program development. This guide shows you how to configure Anchor to use FortiBlox Nexus RPC for development, testing, and deployment.
Installation
Install Anchor CLI and related tools:
Install Anchor CLI
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
avm install latest
avm use latestVerify installation:
anchor --versionInstall Node.js Dependencies
For TypeScript client development:
npm install --save @coral-xyz/anchor @solana/web3.jsInstall Solana CLI
Anchor requires Solana CLI tools:
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"Project Setup
Create New Anchor Project
anchor init my-solana-project
cd my-solana-projectThis creates a project structure:
my-solana-project/
├── Anchor.toml # Anchor configuration
├── Cargo.toml # Rust workspace
├── programs/ # Your Solana programs
│ └── my-solana-project/
├── tests/ # TypeScript tests
├── migrations/ # Deployment scripts
└── app/ # Frontend (optional)Authentication Setup
Environment Variables
Create a .env file in your project root:
FORTIBLOX_API_KEY=your-api-key-here
ANCHOR_PROVIDER_URL=https://nexus.fortiblox.com/rpcConfigure Anchor.toml
Edit Anchor.toml to use FortiBlox RPC:
[toolchain]
[features]
seeds = false
skip-lint = false
[programs.mainnet]
my_solana_project = "YourProgramIDHere"
[programs.devnet]
my_solana_project = "YourProgramIDHere"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "mainnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
[test]
startup_wait = 5000
# Add custom RPC endpoints
[[test.validator.rpc_url]]
url = "https://nexus.fortiblox.com/rpc"Configure Provider in Tests
Update your test files to use FortiBlox:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MyProgram } from "../target/types/my_program";
describe("my-program", () => {
// Configure provider with FortiBlox
const connection = new anchor.web3.Connection(
process.env.ANCHOR_PROVIDER_URL || "https://nexus.fortiblox.com/rpc",
{
commitment: "confirmed",
httpHeaders: {
"X-API-Key": process.env.FORTIBLOX_API_KEY || "",
},
}
);
const wallet = anchor.Wallet.local();
const provider = new anchor.AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
anchor.setProvider(provider);
const program = anchor.workspace.MyProgram as Program<MyProgram>;
it("Is initialized!", async () => {
// Your test code here
});
});Code Examples
1. Basic Program Structure
Create a simple counter program:
// programs/counter/src/lib.rs
use anchor_lang::prelude::*;
declare_id!("YourProgramIDHere");
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
counter.authority = ctx.accounts.authority.key();
msg!("Counter initialized!");
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = counter.count.checked_add(1).unwrap();
msg!("Counter incremented to: {}", counter.count);
Ok(())
}
pub fn decrement(ctx: Context<Decrement>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = counter.count.checked_sub(1).unwrap();
msg!("Counter decremented to: {}", counter.count);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + Counter::INIT_SPACE
)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[derive(Accounts)]
pub struct Decrement<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
#[derive(InitSpace)]
pub struct Counter {
pub count: u64,
pub authority: Pubkey,
}2. Testing with FortiBlox
Create comprehensive tests:
// tests/counter.ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Counter } from "../target/types/counter";
import { assert } from "chai";
describe("counter", () => {
// Configure FortiBlox connection
const connection = new anchor.web3.Connection(
process.env.ANCHOR_PROVIDER_URL || "https://nexus.fortiblox.com/rpc",
{
commitment: "confirmed",
httpHeaders: {
"X-API-Key": process.env.FORTIBLOX_API_KEY || "",
},
}
);
const wallet = anchor.Wallet.local();
const provider = new anchor.AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
anchor.setProvider(provider);
const program = anchor.workspace.Counter as Program<Counter>;
const counterKeypair = anchor.web3.Keypair.generate();
it("Initializes the counter", async () => {
console.log("Initializing counter...");
const tx = await program.methods
.initialize()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counterKeypair])
.rpc();
console.log("Transaction signature:", tx);
// Fetch the account
const counterAccount = await program.account.counter.fetch(
counterKeypair.publicKey
);
assert.equal(counterAccount.count.toNumber(), 0);
assert.equal(
counterAccount.authority.toBase58(),
provider.wallet.publicKey.toBase58()
);
});
it("Increments the counter", async () => {
console.log("Incrementing counter...");
const tx = await program.methods
.increment()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
console.log("Transaction signature:", tx);
const counterAccount = await program.account.counter.fetch(
counterKeypair.publicKey
);
assert.equal(counterAccount.count.toNumber(), 1);
});
it("Decrements the counter", async () => {
console.log("Decrementing counter...");
const tx = await program.methods
.decrement()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
console.log("Transaction signature:", tx);
const counterAccount = await program.account.counter.fetch(
counterKeypair.publicKey
);
assert.equal(counterAccount.count.toNumber(), 0);
});
it("Increments multiple times", async () => {
console.log("Testing multiple increments...");
for (let i = 0; i < 5; i++) {
await program.methods
.increment()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
}
const counterAccount = await program.account.counter.fetch(
counterKeypair.publicKey
);
assert.equal(counterAccount.count.toNumber(), 5);
console.log("Counter value:", counterAccount.count.toNumber());
});
});3. Client Integration
Build a TypeScript client for your program:
// app/client.ts
import * as anchor from "@coral-xyz/anchor";
import { Program, AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { Counter } from "../target/types/counter";
import idl from "../target/idl/counter.json";
export class CounterClient {
private program: Program<Counter>;
private provider: AnchorProvider;
constructor(apiKey: string, walletKeypair: Keypair) {
const connection = new Connection(
"https://nexus.fortiblox.com/rpc",
{
commitment: "confirmed",
httpHeaders: { "X-API-Key": apiKey },
}
);
const wallet = new Wallet(walletKeypair);
this.provider = new AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
this.program = new Program(
idl as any,
new PublicKey(idl.metadata.address),
this.provider
);
}
async initialize(counterKeypair: Keypair): Promise<string> {
const tx = await this.program.methods
.initialize()
.accounts({
counter: counterKeypair.publicKey,
authority: this.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counterKeypair])
.rpc();
console.log("Counter initialized:", tx);
return tx;
}
async increment(counterAddress: PublicKey): Promise<string> {
const tx = await this.program.methods
.increment()
.accounts({
counter: counterAddress,
authority: this.provider.wallet.publicKey,
})
.rpc();
console.log("Counter incremented:", tx);
return tx;
}
async decrement(counterAddress: PublicKey): Promise<string> {
const tx = await this.program.methods
.decrement()
.accounts({
counter: counterAddress,
authority: this.provider.wallet.publicKey,
})
.rpc();
console.log("Counter decremented:", tx);
return tx;
}
async getCounter(counterAddress: PublicKey): Promise<{
count: number;
authority: string;
}> {
const account = await this.program.account.counter.fetch(counterAddress);
return {
count: account.count.toNumber(),
authority: account.authority.toBase58(),
};
}
async getAllCounters(): Promise<
Array<{
address: string;
count: number;
authority: string;
}>
> {
const accounts = await this.program.account.counter.all();
return accounts.map((acc) => ({
address: acc.publicKey.toBase58(),
count: acc.account.count.toNumber(),
authority: acc.account.authority.toBase58(),
}));
}
}
// Example usage
async function main() {
const apiKey = process.env.FORTIBLOX_API_KEY || "";
const walletKeypair = Keypair.generate(); // Or load from file
const client = new CounterClient(apiKey, walletKeypair);
// Create new counter
const counterKeypair = Keypair.generate();
await client.initialize(counterKeypair);
// Increment counter
await client.increment(counterKeypair.publicKey);
// Get counter value
const counter = await client.getCounter(counterKeypair.publicKey);
console.log("Counter value:", counter.count);
}4. Program with Token Operations
Create a more complex program with SPL Token operations:
// programs/token-vault/src/lib.rs
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
declare_id!("YourProgramIDHere");
#[program]
pub mod token_vault {
use super::*;
pub fn initialize_vault(ctx: Context<InitializeVault>) -> Result<()> {
let vault = &mut ctx.accounts.vault;
vault.authority = ctx.accounts.authority.key();
vault.token_account = ctx.accounts.token_account.key();
msg!("Vault initialized!");
Ok(())
}
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.user_token_account.to_account_info(),
to: ctx.accounts.vault_token_account.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
amount,
)?;
msg!("Deposited {} tokens", amount);
Ok(())
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let seeds = &[
b"vault",
ctx.accounts.vault.authority.as_ref(),
&[ctx.accounts.vault.bump],
];
let signer = &[&seeds[..]];
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.vault_token_account.to_account_info(),
to: ctx.accounts.user_token_account.to_account_info(),
authority: ctx.accounts.vault.to_account_info(),
},
signer,
),
amount,
)?;
msg!("Withdrew {} tokens", amount);
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeVault<'info> {
#[account(
init,
payer = authority,
space = 8 + Vault::INIT_SPACE,
seeds = [b"vault", authority.key().as_ref()],
bump
)]
pub vault: Account<'info, Vault>,
pub token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub user_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub vault_token_account: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(
mut,
has_one = authority,
seeds = [b"vault", authority.key().as_ref()],
bump = vault.bump
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub user_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub vault_token_account: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
#[account]
#[derive(InitSpace)]
pub struct Vault {
pub authority: Pubkey,
pub token_account: Pubkey,
pub bump: u8,
}5. Event Listening
Listen to program events:
// app/event-listener.ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Connection, PublicKey } from "@solana/web3.js";
async function listenToEvents(
programId: PublicKey,
apiKey: string
) {
const connection = new Connection(
"https://nexus.fortiblox.com/rpc",
{
commitment: "confirmed",
wsEndpoint: "wss://nexus.fortiblox.com/rpc",
httpHeaders: { "X-API-Key": apiKey },
}
);
const provider = anchor.AnchorProvider.local();
const program = new Program(idl, programId, provider);
console.log("Listening for events...");
// Listen to all events
program.addEventListener("counterIncremented", (event, slot) => {
console.log("Counter Incremented Event:");
console.log("- Slot:", slot);
console.log("- New Value:", event.newValue.toString());
console.log("- Authority:", event.authority.toBase58());
});
program.addEventListener("counterDecremented", (event, slot) => {
console.log("Counter Decremented Event:");
console.log("- Slot:", slot);
console.log("- New Value:", event.newValue.toString());
console.log("- Authority:", event.authority.toBase58());
});
// Keep process running
process.on("SIGINT", () => {
console.log("\nStopping event listener...");
process.exit(0);
});
}
// Usage
const programId = new PublicKey("YourProgramIDHere");
const apiKey = process.env.FORTIBLOX_API_KEY || "";
listenToEvents(programId, apiKey);6. Deploy Program
Deploy your program using FortiBlox RPC:
# Build program
anchor build
# Deploy to mainnet via FortiBlox
anchor deploy --provider.cluster mainnet \
--provider.wallet ~/.config/solana/id.jsonOr use a deployment script:
// migrations/deploy.ts
import * as anchor from "@coral-xyz/anchor";
import { Connection, Keypair } from "@solana/web3.js";
import fs from "fs";
async function deploy() {
const apiKey = process.env.FORTIBLOX_API_KEY || "";
const connection = new Connection(
"https://nexus.fortiblox.com/rpc",
{
commitment: "confirmed",
httpHeaders: { "X-API-Key": apiKey },
}
);
// Load wallet
const walletKeypair = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(
fs.readFileSync(process.env.ANCHOR_WALLET || "", "utf-8")
)
)
);
const wallet = new anchor.Wallet(walletKeypair);
const provider = new anchor.AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
anchor.setProvider(provider);
// Load program
const program = anchor.workspace.Counter;
console.log("Deploying program...");
console.log("Program ID:", program.programId.toBase58());
// Perform any initialization or setup here
console.log("Deployment complete!");
}
deploy();7. Testing Best Practices
Create a comprehensive test suite:
// tests/comprehensive.ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Counter } from "../target/types/counter";
import { assert, expect } from "chai";
describe("comprehensive tests", () => {
const connection = new anchor.web3.Connection(
process.env.ANCHOR_PROVIDER_URL || "https://nexus.fortiblox.com/rpc",
{
commitment: "confirmed",
httpHeaders: {
"X-API-Key": process.env.FORTIBLOX_API_KEY || "",
},
}
);
const wallet = anchor.Wallet.local();
const provider = new anchor.AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
anchor.setProvider(provider);
const program = anchor.workspace.Counter as Program<Counter>;
describe("initialization", () => {
it("should initialize with correct values", async () => {
const counterKeypair = anchor.web3.Keypair.generate();
await program.methods
.initialize()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counterKeypair])
.rpc();
const account = await program.account.counter.fetch(
counterKeypair.publicKey
);
assert.equal(account.count.toNumber(), 0);
assert.equal(
account.authority.toBase58(),
provider.wallet.publicKey.toBase58()
);
});
});
describe("operations", () => {
let counterKeypair: anchor.web3.Keypair;
beforeEach(async () => {
counterKeypair = anchor.web3.Keypair.generate();
await program.methods
.initialize()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counterKeypair])
.rpc();
});
it("should increment correctly", async () => {
await program.methods
.increment()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
const account = await program.account.counter.fetch(
counterKeypair.publicKey
);
assert.equal(account.count.toNumber(), 1);
});
it("should decrement correctly", async () => {
// First increment
await program.methods
.increment()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
// Then decrement
await program.methods
.decrement()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
const account = await program.account.counter.fetch(
counterKeypair.publicKey
);
assert.equal(account.count.toNumber(), 0);
});
it("should handle multiple operations", async () => {
for (let i = 0; i < 10; i++) {
await program.methods
.increment()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
}
const account = await program.account.counter.fetch(
counterKeypair.publicKey
);
assert.equal(account.count.toNumber(), 10);
});
});
describe("error handling", () => {
it("should fail with wrong authority", async () => {
const counterKeypair = anchor.web3.Keypair.generate();
const wrongAuthority = anchor.web3.Keypair.generate();
await program.methods
.initialize()
.accounts({
counter: counterKeypair.publicKey,
authority: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counterKeypair])
.rpc();
try {
await program.methods
.decrement()
.accounts({
counter: counterKeypair.publicKey,
authority: wrongAuthority.publicKey,
})
.signers([wrongAuthority])
.rpc();
assert.fail("Should have thrown an error");
} catch (error) {
expect(error).to.exist;
}
});
});
});Running Tests
Execute your tests using FortiBlox RPC:
# Run all tests
anchor test --skip-local-validator
# Run specific test file
anchor test --skip-local-validator tests/counter.ts
# Run with verbose output
anchor test --skip-local-validator -- --reporter specBest Practices
1. Environment Configuration
Use dotenv for managing environments:
import dotenv from "dotenv";
dotenv.config();
const getConfig = () => ({
rpcUrl: process.env.ANCHOR_PROVIDER_URL || "https://nexus.fortiblox.com/rpc",
apiKey: process.env.FORTIBLOX_API_KEY || "",
commitment: "confirmed" as anchor.web3.Commitment,
});2. Error Handling
Implement robust error handling:
async function safeRpcCall<T>(
operation: () => Promise<T>
): Promise<T | null> {
try {
return await operation();
} catch (error: any) {
if (error.code === 429) {
console.error("Rate limit exceeded");
// Implement retry logic
} else if (error.code === 401 || error.code === 403) {
console.error("Authentication failed");
} else {
console.error("RPC error:", error.message);
}
return null;
}
}3. Transaction Confirmation
Wait for proper transaction confirmation:
async function sendAndConfirmTx(
program: Program,
instructions: any,
signers: any[]
) {
const tx = await program.methods
.yourMethod()
.accounts(instructions)
.signers(signers)
.rpc();
// Wait for confirmation
await program.provider.connection.confirmTransaction(tx, "confirmed");
console.log("Transaction confirmed:", tx);
return tx;
}4. Connection Management
Reuse connection instances:
class AnchorService {
private static instance: AnchorService;
public connection: Connection;
public provider: AnchorProvider;
private constructor(apiKey: string) {
this.connection = new Connection(
"https://nexus.fortiblox.com/rpc",
{
commitment: "confirmed",
httpHeaders: { "X-API-Key": apiKey },
}
);
const wallet = anchor.Wallet.local();
this.provider = new AnchorProvider(this.connection, wallet, {
commitment: "confirmed",
});
}
static getInstance(apiKey: string): AnchorService {
if (!AnchorService.instance) {
AnchorService.instance = new AnchorService(apiKey);
}
return AnchorService.instance;
}
}Performance Tips
- Batch Transactions: Combine multiple instructions when possible
- Preflight Checks: Use
skipPreflight: trueonly when confident - Commitment Levels: Balance speed and reliability
- Connection Reuse: Create connections once, reuse everywhere
- Parallel Testing: Run independent tests concurrently
// Parallel account fetching
const accounts = await Promise.all([
program.account.counter.fetch(address1),
program.account.counter.fetch(address2),
program.account.counter.fetch(address3),
]);Next Steps
- Explore Solana Web3.js for client development
- Check out Python SDK for backend services
- Review API Reference for all available methods
- Join our Discord for community support