LogoAnchor Docs

Dependency Free Composability

Learn how to use Anchor's declare_program macro to interact with programs without additional dependencies.

The declare_program!() macro simplifies the process of interacting with Anchor programs by generating Rust modules (from a program's IDL) that can be used in both on-chain and off-chain code. You can find an example program here.

The following modules are generated by the declare_program!() macro:

ModuleDescription
cpiHelper functions for making cross-program invocations (CPIs) to the program from other on-chain programs
clientAccounts and arguments required to build program instructions to add to client-side transactions
accountAccount data types (program state) defined in the program
programProgram ID constant used to identify the program
constantsProgram constants defined in the program
eventsProgram events defined in the program
typesProgram types defined in the program

Examples

The following examples demonstrate how to use the declare_program!() macro in two scenarios:

  1. Making Cross Program Invocations (CPIs) from one program to another program
  2. Building client-side transactions to invoke a program's instructions

Both examples show how the modules generated by the declare_program!() macro simplify program interactions, whether you're writing on-chain or off-chain code.

On-chain CPI

To use the declare_program!() macro, you need the IDL file for the target program. The IDL file must be placed in a directory named /idls in your project. The /idls directory can be located at any level in your project structure. For example, your project could have this layout:

example.json
lib.rs
Cargo.toml

Below is the source code (lib.rs) for the target (callee) program that generates the example.json IDL file shown above.

Using the program's IDL file, another program can use the declare_program!() macro to generate a CPI module, enabling it to make CPIs to this program's instructions.

use anchor_lang::prelude::*;
 
declare_id!("8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8");
 
#[program]
pub mod example {
    use super::*;
 
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let counter = &ctx.accounts.counter;
        msg!("Counter account created! Current count: {}", counter.count);
        Ok(())
    }
 
    pub fn increment(ctx: Context<Increment>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        msg!("Previous counter: {}", counter.count);
 
        counter.count += 1;
        msg!("Counter incremented! Current count: {}", counter.count);
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
 
    #[account(
        init,
        payer = payer,
        space = 8 + 8
    )]
    pub counter: Account<'info, Counter>,
    pub system_program: Program<'info, System>,
}
 
#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut)]
    pub counter: Account<'info, Counter>,
}
 
#[account]
pub struct Counter {
    pub count: u64,
}

Below is the source code (lib.rs) for the caller program (example-cpi) that uses the declare_program!() macro to generate a CPI module to invoke the instructions defined in the callee program above.

use anchor_lang::prelude::*;
 
declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");
 


declare_program!(example);
use example::{
    accounts::Counter,
    cpi::{
        self,
        accounts::{Increment, Initialize},
    },
    program::Example,
};
 
#[program]
pub mod example_cpi {
 
    use super::*;
 
    pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> {
        // Create CPI context for initialize
        let cpi_ctx = CpiContext::new(
            ctx.accounts.example_program.to_account_info(),
            Initialize {
                payer: ctx.accounts.payer.to_account_info(),
                counter: ctx.accounts.counter.to_account_info(),
                system_program: ctx.accounts.system_program.to_account_info(),
            },
        );
 
        // Invoke the initialize instruction
        cpi::initialize(cpi_ctx)?;
        Ok(())
    }
 
    pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> {
        // Create CPI context for increment
        let cpi_ctx = CpiContext::new(
            ctx.accounts.example_program.to_account_info(),
            Increment {
                counter: ctx.accounts.counter.to_account_info(),
            },
        );
 
        // Invoke the increment instruction
        cpi::increment(cpi_ctx)?;
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct InitializeCpi<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(mut)]
    pub counter: Signer<'info>,
    pub system_program: Program<'info, System>,
    pub example_program: Program<'info, Example>,
}
 
#[derive(Accounts)]
pub struct IncrementCpi<'info> {
    #[account(mut)]
    pub counter: Account<'info, Counter>,
    pub example_program: Program<'info, Example>,
}

Explanation

The declare_program!() macro takes a single argument - the name of the program's IDL file (e.g. example.json):

declare_program!(example);  // Looks for /idls/example.json

Bring into scope the generated modules:

use example::{
    accounts::Counter, // Account types
    cpi::{             // Cross program invocation helpers
        self,
        accounts::{Increment, Initialize},
    },
    program::Example,  // Program type
};

Use the imported types in the account validation structs:

#[derive(Accounts)]
pub struct IncrementCpi<'info> {
    // Counter type from accounts module
    #[account(mut)]


    pub counter: Account<'info, Counter>,
 
    // Example type from program module


    pub example_program: Program<'info, Example>,
}

Use the CPI module to invoke the program's instructions:

pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> {
    // Create CPI context for initialize
    let cpi_ctx = CpiContext::new(
        ctx.accounts.example_program.to_account_info(),
        Initialize {
            payer: ctx.accounts.payer.to_account_info(),
            counter: ctx.accounts.counter.to_account_info(),
            system_program: ctx.accounts.system_program.to_account_info(),
        },
    );
 
    // Invoke the initialize instruction

    cpi::initialize(cpi_ctx)?;
    Ok(())
}
pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> {
    // Create CPI context for increment
    let cpi_ctx = CpiContext::new(
        ctx.accounts.example_program.to_account_info(),
        Increment {
            counter: ctx.accounts.counter.to_account_info(),
        },
    );
 
    // Invoke the increment instruction

    cpi::increment(cpi_ctx)?;
    Ok(())
}

Off-chain Client

To use the declare_program!() macro, you need the IDL file for the target program. The IDL file must be placed in a directory named /idls in your project. The /idls directory can be located at any level in your project structure. For example, your project could have this layout:

example.json
main.rs
Cargo.toml

Below is the source code (lib.rs) for the target program that generates the example.json IDL file shown above. The program's IDL can then be used in a client script along with the declare_program!() macro to generate a Client module to build the program's instructions.

use anchor_lang::prelude::*;
 
declare_id!("6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC");
 
#[program]
pub mod example {
    use super::*;
 
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let counter = &ctx.accounts.counter;
        msg!("Counter account created! Current count: {}", counter.count);
        Ok(())
    }
 
    pub fn increment(ctx: Context<Increment>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        msg!("Previous counter: {}", counter.count);
 
        counter.count += 1;
        msg!("Counter incremented! Current count: {}", counter.count);
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
 
    #[account(
        init,
        payer = payer,
        space = 8 + 8
    )]
    pub counter: Account<'info, Counter>,
    pub system_program: Program<'info, System>,
}
 
#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut)]
    pub counter: Account<'info, Counter>,
}
 
#[account]
pub struct Counter {
    pub count: u64,
}

Below is the client script (main.rs) that uses the declare_program!() macro to generate a Client module to build the program's instructions.

use anchor_client::{
    solana_client::rpc_client::RpcClient,
    solana_sdk::{
        commitment_config::CommitmentConfig, native_token::LAMPORTS_PER_SOL, signature::Keypair,
        signer::Signer, system_program,
    },
    Client, Cluster,
};
use anchor_lang::prelude::*;
use std::rc::Rc;
 
declare_program!(example);
use example::{accounts::Counter, client::accounts, client::args};
 
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let connection = RpcClient::new_with_commitment(
        "http://127.0.0.1:8899", // Local validator URL
        CommitmentConfig::confirmed(),
    );
 
    // Generate Keypairs and request airdrop
    let payer = Keypair::new();
    let counter = Keypair::new();
    println!("Generated Keypairs:");
    println!("   Payer: {}", payer.pubkey());
    println!("   Counter: {}", counter.pubkey());
 
    println!("\nRequesting 1 SOL airdrop to payer");
    let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;
 
    // Wait for airdrop confirmation
    while !connection.confirm_transaction(&airdrop_signature)? {
        std::thread::sleep(std::time::Duration::from_millis(100));
    }
    println!("   Airdrop confirmed!");
 
    // Create program client
    let provider = Client::new_with_options(
        Cluster::Localnet,
        Rc::new(payer),
        CommitmentConfig::confirmed(),
    );
    let program = provider.program(example::ID)?;
 
    // Build and send instructions
    println!("\nSend transaction with initialize and increment instructions");
    let initialize_ix = program
        .request()
        .accounts(accounts::Initialize {
            counter: counter.pubkey(),
            payer: program.payer(),
            system_program: system_program::ID,
        })
        .args(args::Initialize)
        .instructions()?
        .remove(0);
 
    let increment_ix = program
        .request()
        .accounts(accounts::Increment {
            counter: counter.pubkey(),
        })
        .args(args::Increment)
        .instructions()?
        .remove(0);
 
    let signature = program
        .request()
        .instruction(initialize_ix)
        .instruction(increment_ix)
        .signer(&counter)
        .send()
        .await?;
    println!("   Transaction confirmed: {}", signature);
 
    println!("\nFetch counter account data");
    let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?;
    println!("   Counter value: {}", counter_account.count);
    Ok(())
}

The declare_program!() macro takes a single argument - the name of the program's IDL file (e.g. example.json):

declare_program!(example);  // Looks for /idls/example.json

Bring into scope the generated modules:

use example::{
  accounts::Counter,  // Program Account types
  client::accounts,   // Accounts for program instructions
  client::args,       // Arguments for program instructions
};

Use the Client module to build the program's instructions:

// Build initialize instruction
let initialize_ix = program
    .request()
    // Accounts required for initialize instruction
    .accounts(accounts::Initialize {
        counter: counter.pubkey(),
        payer: program.payer(),
        system_program: system_program::ID,
    })
    // Arguments for initialize instruction (discriminator)
    .args(args::Initialize)
    .instructions()?
    .remove(0);
// Build increment instruction
let increment_ix = program
    .request()
    // Accounts required for increment instruction
    .accounts(accounts::Increment {
        counter: counter.pubkey(),
    })
    // Arguments for increment instruction (discriminator)
    .args(args::Increment)
    .instructions()?
    .remove(0);

Add the program's instructions to a transaction and send the transaction:

let signature = program
    .request()
    .instruction(initialize_ix)
    .instruction(increment_ix)
    .signer(&counter)
    .send()
    .await?;

Use the Account module to fetch and deserialize the program's account types:

// Counter type from accounts module
let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?;

On this page

Edit on GitHub