Hardhat-style deployment toolkit for Solana Anchor programs with advanced seeding capabilities.
Rocket Anchor brings the familiar Hardhat developer experience to Solana, making it easy to deploy, manage, and seed your Anchor programs across multiple networks with a simple, declarative configuration.
- Features
- Installation
- Quick Start
- Configuration
- CLI Commands
- Seeding & Initialization
- Programmatic Usage
- Advanced Topics
- Examples
- Best Practices
- Troubleshooting
- CI/CD Integration
- API Reference
- Contributing
- License
- π― Hardhat-style Configuration - Familiar
ra.config.tsfor network and deployment settings - π Multi-Network Support - Easy switching between localnet, devnet, testnet, and mainnet
- π± Declarative Seeding - Initialize and populate programs with simple configuration
- π Flexible Keypair Management - Support for file paths, base58 strings, and environment variables
- π PDA Resolution - Automatic Program Derived Address generation in seed scripts
- β Deployment Verification - Built-in on-chain verification
- π¦ Automated Building - Integrated Anchor build support
- π¨ Beautiful CLI - Colored output with clear progress indicators
- π TypeScript First - Full type safety and IntelliSense support
- π Programmatic API - Use in your own scripts and tools
- π Fast & Efficient - Optimized deployment pipeline
npm install -g rocket-anchornpm install --save-dev rocket-anchornpx rocket-anchor initnpx ra initThis creates ra.config.ts in your project root.
Edit ra.config.ts:
import type { RAConfig } from "rocket-anchor";
const config: RAConfig = {
networks: {
solana_devnet: {
url: 'https://api.devnet.solana.com',
accounts: ['~/.config/solana/devnet.json'],
commitment: 'confirmed',
type: 'devnet' // localnet | devnet | testnet | mainnet
},
},
};
export default config;npx ra deploy --network devnetCreate seeds/index.ts:
import type { SeedConfig } from 'rocket-anchor';
export const seeds: SeedConfig[] = [
{
program: 'my_program',
initialize: {
function: 'initialize',
accounts: {
authority: 'signer',
state: 'pda:state',
systemProgram: 'systemProgram',
},
args: ['Production', 1000],
},
},
];
export default seeds;Deploy with seeding:
npx ra deploy --network devnet --seedinterface NetworkConfig {
url: string; // RPC endpoint URL
accounts?: string[]; // Keypair paths or base58 keys
timeout?: number; // Transaction timeout (ms)
commitment?: Commitment; // 'processed' | 'confirmed' | 'finalized'
skipPreflight?: boolean; // Skip preflight checks
websocket?: string; // WebSocket endpoint (optional)
type: NetworkType // Newtork type, one of these: localnet | devnet | testnet | mainnet
}import type { RAConfig } from "rocket-anchor";
import * as dotenv from 'dotenv';
dotenv.config();
const config: RAConfig = {
networks: {
solana_localnet: {
url: 'http://127.0.0.1:8899',
accounts: ['~/.config/solana/id.json'],
commitment: 'confirmed',
type: 'localnet'
},
solana_devnet: {
url: process.env.DEVNET_RPC_URL || 'https://api.devnet.solana.com',
accounts: [process.env.DEVNET_KEYPAIR_PATH!],
commitment: 'confirmed',
timeout: 60000,
type: 'devnet'
},
solana_testnet: {
url: 'https://api.testnet.solana.com',
accounts: ['./keypairs/testnet.json'],
commitment: 'confirmed',
type: 'testnet'
},
solana_mainnet: {
url: process.env.MAINNET_RPC_URL || 'https://api.mainnet-beta.solana.com',
accounts: [process.env.MAINNET_KEYPAIR_PATH!],
commitment: 'finalized',
skipPreflight: false,
timeout: 90000,
websocket: 'wss://api.mainnet-beta.solana.com',
type: 'mainnet'
},
},
paths: {
programs: './programs',
tests: './tests',
artifacts: './target',
},
solana: {
version: '1.18.0',
},
anchor: {
version: '0.29.0',
},
};
export default config;Deploy Anchor programs to a specified network.
# Basic deployment
npx ra deploy --network solana_devnet
# Deploy specific program
npx ra deploy --network solana_devnet --program token_vault
# Skip build step
npx ra deploy --network solana_devnet --skip-build
# Deploy and verify
npx ra deploy --network solana_mainnet --verify
# Deploy as non-upgradeable
npx ra deploy --network solana_mainnet --upgradeable false
# Deploy and run seeds
npx ra deploy --network solana_devnet --seed
# Deploy with custom seed script
npx ra deploy --network solana_devnet --seed --seed-script ./scripts/custom.tsRun initialization and seed scripts for deployed programs.
# Run seeds
npx ra seed --network solana_devnet
# Seed specific program
npx ra seed --network solana_devnet --program my_program
# Use custom seed script
npx ra seed --network solana_devnet --script ./scripts/advanced-seed.tsBuild Anchor programs.
# Standard build
npx ra build
# Verifiable build
npx ra build --verifiableRun Anchor tests.
# Run tests on localnet
npx ra test
# Run tests on specific network
npx ra test --network solana_devnetInitialize configuration file.
npx ra initRocket Anchor provides powerful seeding capabilities to automatically initialize and populate your programs after deployment.
Create seeds/index.ts:
import type { SeedConfig } from 'rocket-anchor';
export const seeds: SeedConfig[] = [
{
program: 'counter',
initialize: {
function: 'initialize',
accounts: {
counter: 'pda:counter',
authority: 'signer',
systemProgram: 'systemProgram',
},
args: [0], // initial_count
},
seeds: [
{
function: 'increment',
accounts: {
counter: 'pda:counter',
authority: 'signer',
},
args: [],
repeat: 5, // Run 5 times
},
],
},
];
export default seeds;Rocket Anchor supports multiple account resolution patterns:
accounts: {
authority: 'signer', // Uses deployer wallet
payer: 'payer', // Also uses deployer wallet
}accounts: {
systemProgram: 'systemProgram', // SystemProgram.programId
rent: 'rent', // SYSVAR_RENT_PUBKEY
}accounts: {
newAccount: 'new:', // Generates and signs with new keypair
}accounts: {
vault: 'pda:vault', // Single seed
userVault: 'pda:vault:signer', // Multiple seeds
}accounts: {
treasury: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
}import type { SeedConfig } from 'rocket-anchor';
export const seeds: SeedConfig[] = [
{
program: 'marketplace',
initialize: {
function: 'initialize',
accounts: {
marketplace: 'pda:marketplace',
authority: 'signer',
feeRecipient: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
systemProgram: 'systemProgram',
rent: 'rent',
},
args: [
500, // fee_basis_points (5%)
'Main Marketplace',
],
},
seeds: [
{
function: 'createCollection',
accounts: {
marketplace: 'pda:marketplace',
collection: 'new:',
authority: 'signer',
systemProgram: 'systemProgram',
},
args: ['NFT Collection', 'NFTC'],
repeat: 3, // Create 3 collections
},
{
function: 'updateFee',
accounts: {
marketplace: 'pda:marketplace',
authority: 'signer',
},
args: [250], // Lower fee to 2.5%
},
],
},
];
export default seeds;For complex seeding logic, create custom TypeScript scripts:
// scripts/custom-seed.ts
import * as anchor from '@coral-xyz/anchor';
import { Program, AnchorProvider } from '@coral-xyz/anchor';
import { PublicKey, Keypair, SystemProgram } from '@solana/web3.js';
export async function customSeed(
provider: AnchorProvider,
programId: PublicKey
) {
const program = new Program(idl, programId, provider);
// Complex initialization logic
const config = await program.account.config.fetch(configPda);
if (config.initialized) {
console.log('Already initialized, skipping...');
return;
}
// Conditional seeding based on network
const network = provider.connection.rpcEndpoint;
const isMainnet = network.includes('mainnet');
const feeRate = isMainnet ? 100 : 50; // 1% mainnet, 0.5% devnet
await program.methods
.initialize(feeRate)
.accounts({
config: configPda,
authority: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.rpc();
console.log('Custom seed completed!');
}
export default customSeed;Use custom script:
npx ra seed --network solana_devnet --script ./scripts/custom-seed.tsRocket Anchor can be used programmatically in your TypeScript/JavaScript projects.
import { RocketAnchor } from 'rocket-anchor';
async function deployPrograms() {
const ra = new RocketAnchor();
// Load configuration
await ra.loadConfig('./ra.config.ts');
// Deploy
const results = await ra.deploy({
network: 'devnet',
program: 'my_program',
skipBuild: false,
verify: true,
seed: true,
});
// Check results
results.forEach(result => {
if (result.success) {
console.log(`β
${result.programName}: ${result.programId}`);
console.log(` Tx: ${result.txSignature}`);
} else {
console.error(`β ${result.programName}: ${result.error}`);
}
});
}
deployPrograms().catch(console.error);import { RocketAnchor, DeployOptions } from 'rocket-anchor';
class DeploymentManager {
private ra: RocketAnchor;
constructor() {
this.ra = new RocketAnchor();
}
async deployToMultipleNetworks(networks: string[]) {
await this.ra.loadConfig();
for (const network of networks) {
console.log(`\nDeploying to ${network}...`);
try {
const results = await this.ra.deploy({
network,
verify: network === 'mainnet',
seed: true,
});
await this.saveDeployment(network, results);
} catch (error) {
console.error(`Failed to deploy to ${network}:`, error);
}
}
}
async saveDeployment(network: string, results: DeployResult[]) {
// Save deployment info to database or file
const deployment = {
network,
timestamp: new Date().toISOString(),
programs: results.map(r => ({
name: r.programName,
programId: r.programId,
txSignature: r.txSignature,
})),
};
// Save logic here
console.log('Deployment saved:', deployment);
}
async seedWithRetry(network: string, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
await this.ra.seed(network);
console.log('Seeding successful');
return;
} catch (error) {
console.log(`Seed attempt ${i + 1} failed, retrying...`);
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
}
}
// Usage
const manager = new DeploymentManager();
await manager.deployToMultipleNetworks(['solana_devnet', 'solana_testnet']);Use .env files for sensitive data:
# .env
DEVNET_RPC_URL=https://api.devnet.solana.com
MAINNET_RPC_URL=https://my-private-rpc.com
DEVNET_KEYPAIR_PATH=./keypairs/devnet.json
MAINNET_KEYPAIR_PATH=./keypairs/mainnet.json// ra.config.ts
import * as dotenv from 'dotenv';
dotenv.config();
const config: RAConfig = {
networks: {
devnet: {
url: process.env.DEVNET_RPC_URL!,
accounts: [process.env.DEVNET_KEYPAIR_PATH!],
},
mainnet: {
url: process.env.MAINNET_RPC_URL!,
accounts: [process.env.MAINNET_KEYPAIR_PATH!],
},
},
};Manage different keypairs for different purposes:
const config: RAConfig = {
networks: {
solana_mainnet: {
url: 'https://api.mainnet-beta.solana.com',
accounts: [
'./keypairs/mainnet-deployer.json', // Primary deployer
'./keypairs/mainnet-authority.json', // Program authority
],
},
},
};// In your deployment script
import { execSync } from 'child_process';
// Custom build with features
execSync('anchor build -- --features mainnet', { stdio: 'inherit' });
// Then deploy
await ra.deploy({
network: 'solana_mainnet',
skipBuild: true, // Already built
});// programs/counter/src/lib.rs
use anchor_lang::prelude::*;
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, initial_count: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = initial_count;
counter.authority = ctx.accounts.authority.key();
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + 8 + 32,
seeds = [b"counter"],
bump
)]
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, seeds = [b"counter"], bump)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub count: u64,
pub authority: Pubkey,
}// seeds/index.ts
import type { SeedConfig } from 'rocket-anchor';
export const seeds: SeedConfig[] = [
{
program: 'counter',
initialize: {
function: 'initialize',
accounts: {
counter: 'pda:counter',
authority: 'signer',
systemProgram: 'systemProgram',
},
args: [0],
},
seeds: [
{
function: 'increment',
accounts: {
counter: 'pda:counter',
authority: 'signer',
},
args: [],
repeat: 10,
},
],
},
];
export default seeds;# Deploy and seed
npx ra deploy --network solana_devnet --seed
# Counter will be at 10 (0 + 10 increments)// seeds/index.ts
import type { SeedConfig } from 'rocket-anchor';
export const seeds: SeedConfig[] = [
{
program: 'token_vault',
initialize: {
function: 'initializeVault',
accounts: {
vault: 'pda:vault',
authority: 'signer',
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
systemProgram: 'systemProgram',
tokenProgram: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
rent: 'rent',
},
args: [500], // 5% fee
},
},
];
export default seeds;Never commit private keys:
# .gitignore
*.json
!package.json
!tsconfig.json
.env
keypairs/Use environment variables for sensitive data:
const config: RAConfig = {
networks: {
solana_mainnet: {
url: process.env.MAINNET_RPC_URL!,
accounts: [process.env.MAINNET_KEYPAIR_PATH!],
type: "mainnet"
},
},
};Use hardware wallets for mainnet:
Consider integrating Ledger support for mainnet deployments.
Always test on devnet first:
# Test on devnet
npx ra deploy --network solana_devnet --seed
# Thoroughly test all functionality
# Then deploy to mainnet
npx ra deploy --network solana_mainnet --verifyUse separate keypairs per network:
keypairs/
βββ localnet.json
βββ devnet.json
βββ testnet.json
βββ mainnet.json
Verify solana_mainnet deployments:
npx ra deploy --network solana_mainnet --verifyUse verifiable builds for mainnet:
npx ra build --verifiable
npx ra deploy --network solana_mainnet --skip-buildKeep deployment logs:
npx ra deploy --network solana_mainnet 2>&1 | tee deployment-$(date +%Y%m%d-%H%M%S).logUse different configs for different environments:
ra.config.dev.ts
ra.config.staging.ts
ra.config.prod.ts
export RA_CONFIG=ra.config.prod.ts
npx ra deploy --network solana_mainnetSolution:
npx ra initSolution:
# For devnet
solana airdrop 2 <your-address> --url devnet
# For mainnet, fund your walletSolution:
# Build your program first
anchor buildSolution:
Check your network name matches the config:
networks: {
devnet: { ... } // Use: --network devnet
}Solution:
# Ensure program is built
anchor build
# Check target/idl/ directory exists
ls target/idl/Enable verbose logging:
DEBUG=ra:* npx ra deploy --network solana_mainnet- GitHub Issues: https://github.com/ra-sun-gold/rocket-anchor/issues
- Discussions: https://github.com/ra-sun-gold/rocket-anchor/discussions
- Discord: Join our Discord
name: Deploy to Devnet
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Install Rocket Anchor
run: npm install -g rocket-anchor
- name: Setup Solana
run: |
sh -c "$(curl -sSfL https://release.solana.com/v1.18.0/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
- name: Setup Anchor
run: |
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
avm install 0.29.0
avm use 0.29.0
- name: Create keypair
run: |
echo '${{ secrets.DEPLOYER_KEYPAIR }}' > deployer.json
- name: Deploy
run: npx ra deploy --network devnet --seed
env:
DEPLOYER_KEYPAIR_PATH: ./deployer.json
- name: Cleanup
if: always()
run: rm -f deployer.jsondeploy:
stage: deploy
image: node:18
before_script:
- npm install -g rocket-anchor
- curl -sSfL https://release.solana.com/v1.18.0/install | sh
- export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
script:
- echo "$DEPLOYER_KEYPAIR" > deployer.json
- npx ra deploy --network solana_devnet --seed
after_script:
- rm -f deployer.json
only:
- mainclass RocketAnchor {
// Load configuration from file
async loadConfig(configPath?: string): Promise<RAConfig>
// Deploy programs
async deploy(options: DeployOptions): Promise<DeployResult[]>
// Run seed scripts
async seed(network: string, program?: string, seedScript?: string): Promise<void>
// Get loaded configuration
getConfig(): RAConfig | null
}interface DeployOptions {
network: string;
program?: string;
skipBuild?: boolean;
verify?: boolean;
upgradeable?: boolean;
programId?: string;
seed?: boolean;
seedScript?: string;
}
interface DeployResult {
success: boolean;
programName: string;
programId: string;
txSignature?: string;
error?: string;
}
interface SeedConfig {
program: string;
initialize?: {
function: string;
accounts: { [key: string]: string };
args: any[];
};
seeds?: {
function: string;
accounts: { [key: string]: string };
args: any[];
repeat?: number;
}[];
}// Load configuration
export function loadConfig(configPath?: string): Promise<RAConfig>
// Deploy programs
export function deploy(
config: RAConfig,
networkName: string,
options: DeployOptions
): Promise<DeployResult[]>
// Run seed scripts
export function runSeeds(
config: RAConfig,
networkName: string,
options: { program?: string; seedScript?: string }
): Promise<void>
// Load keypair
export function loadKeypair(keyPath?: string): Promise<Keypair>Contributions are welcome! Please follow these guidelines:
git clone https://github.com/ra-sun-gold/rocket-anchor.git
cd rocket-anchor
npm install
npm run build
npm linknpm testnpm run lint
npm run format- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Run tests:
npm test - Commit:
git commit -am 'Add new feature' - Push:
git push origin feature/my-feature - Create a Pull Request
feat:New featurefix:Bug fixdocs:Documentation changesstyle:Code style changesrefactor:Code refactoringtest:Test updateschore:Build/tooling changes
MIT License - see LICENSE file for details.
Copyright (c) 2025 Ra ra@maxxpainn.com
- Documentation: https://github.com/ra-sun-gold/rocket-anchor
- Issues: https://github.com/ra-sun-gold/rocket-anchor/issues
- Discussions: https://github.com/ra-sun-gold/rocket-anchor/discussions
- Email: ra@maxxpainn.com
Made with β€οΈ for the Solana community