FortiBlox LogoFortiBlox Docs
NexusSDKs

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 latest

Verify installation:

anchor --version

Install Node.js Dependencies

For TypeScript client development:

npm install --save @coral-xyz/anchor @solana/web3.js

Install 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-project

This 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/rpc

Configure 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.json

Or 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 spec

Best 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

  1. Batch Transactions: Combine multiple instructions when possible
  2. Preflight Checks: Use skipPreflight: true only when confident
  3. Commitment Levels: Balance speed and reliability
  4. Connection Reuse: Create connections once, reuse everywhere
  5. 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

Additional Resources