The declare_program!() macro enables dependency-free interaction with Anchor programs by generating Rust modules from a program’s IDL file. This eliminates the need to add the external program as a crate dependency.
Overview
Traditionally, to interact with an external program, you would add it as a dependency in Cargo.toml. The declare_program!() macro provides an alternative approach:
Traditional Approach:
[ dependencies ]
external_program = { version = "0.1.0" , features = [ "cpi" ] }
With declare_program!:
declare_program! ( external_program );
The macro generates all necessary modules for both on-chain (CPI) and off-chain (client) interactions.
Generated Modules
The declare_program!() macro generates the following modules:
Module Description Use Case cpiCross-program invocation helpers Making CPIs from on-chain programs clientClient instruction builders Building transactions from off-chain clients accountsAccount data types Accessing program state programProgram ID constant Identifying the program constantsProgram constants Using program-defined constants eventsProgram events Listening to program events typesCustom types Using program-defined structs/enums errorsProgram errors Handling program-specific errors
Setup
1. Obtain the IDL File
You need the target program’s IDL file (JSON format). Place it in an /idls directory anywhere in your project structure:
project/
├── idls/
│ └── external_program.json
├── programs/
│ └── your_program/
│ └── src/
│ └── lib.rs
└── Cargo.toml
The /idls directory can be at any level in your project.
2. Invoke the Macro
In your program, invoke the macro with the IDL filename (without extension):
use anchor_lang :: prelude ::* ;
declare_program! ( external_program ); // Looks for idls/external_program.json
On-Chain Usage (CPI)
Use the generated cpi module to make cross-program invocations.
Complete Example
Let’s say you want to call an external counter program:
Target Program (external_program):
use anchor_lang :: prelude ::* ;
declare_id! ( "ExternalProgram11111111111111111111111111111" );
#[program]
pub mod external_program {
use super ::* ;
pub fn increment ( ctx : Context < Increment >) -> Result <()> {
let counter = & mut ctx . accounts . counter;
counter . count += 1 ;
Ok (())
}
}
#[derive( Accounts )]
pub struct Increment <' info > {
#[account( mut )]
pub counter : Account <' info , Counter >,
}
#[account]
pub struct Counter {
pub count : u64 ,
}
Your Program (caller):
use anchor_lang :: prelude ::* ;
declare_id! ( "YourProgram111111111111111111111111111111111" );
// Generate modules from IDL
declare_program! ( external_program );
// Import generated types
use external_program :: {
accounts :: Counter ,
cpi :: { self , accounts :: Increment },
program :: ExternalProgram ,
};
#[program]
pub mod your_program {
use super ::* ;
pub fn call_external_increment ( ctx : Context < CallExternal >) -> Result <()> {
// Create CPI context
let cpi_ctx = CpiContext :: new (
ctx . accounts . external_program . to_account_info (),
Increment {
counter : ctx . accounts . counter . to_account_info (),
},
);
// Make CPI call
cpi :: increment ( cpi_ctx ) ? ;
Ok (())
}
}
#[derive( Accounts )]
pub struct CallExternal <' info > {
#[account( mut )]
pub counter : Account <' info , Counter >,
pub external_program : Program <' info , ExternalProgram >,
}
CPI with Seeds (PDA Signing)
When your program needs to sign the CPI as a PDA:
pub fn call_with_seeds ( ctx : Context < CallWithSeeds >) -> Result <()> {
let seeds = & [
b"authority" ,
& [ ctx . bumps . authority],
];
let signer_seeds = & [ & seeds [ .. ]];
let cpi_ctx = CpiContext :: new_with_signer (
ctx . accounts . external_program . to_account_info (),
Increment {
counter : ctx . accounts . counter . to_account_info (),
},
signer_seeds ,
);
cpi :: increment ( cpi_ctx ) ? ;
Ok (())
}
#[derive( Accounts )]
pub struct CallWithSeeds <' info > {
#[account(
mut ,
seeds = [ b"authority" ],
bump ,
)]
pub authority : SystemAccount <' info >,
#[account( mut )]
pub counter : Account <' info , Counter >,
pub external_program : Program <' info , ExternalProgram >,
}
Off-Chain Usage (Client)
Use the generated client module to build instructions from your off-chain client.
Rust Client Example
use anchor_client :: {
solana_client :: rpc_client :: RpcClient ,
solana_sdk :: {
commitment_config :: CommitmentConfig ,
signature :: Keypair ,
signer :: Signer ,
},
Client , Cluster ,
};
use anchor_lang :: prelude ::* ;
use std :: rc :: Rc ;
// Generate modules from IDL
declare_program! ( external_program );
use external_program :: {
accounts :: Counter ,
client :: {accounts, args},
};
#[tokio :: main]
async fn main () -> anyhow :: Result <()> {
let payer = Keypair :: new ();
let counter = Keypair :: new ();
// Create client
let client = Client :: new_with_options (
Cluster :: Localnet ,
Rc :: new ( payer ),
CommitmentConfig :: confirmed (),
);
let program = client . program ( external_program :: ID ) ? ;
// Build increment instruction
let increment_ix = program
. request ()
. accounts ( accounts :: Increment {
counter : counter . pubkey (),
})
. args ( args :: Increment )
. instructions () ?
. remove ( 0 );
// Send transaction
let signature = program
. request ()
. instruction ( increment_ix )
. send ()
. await ? ;
println! ( "Transaction signature: {}" , signature );
// Fetch account data
let counter_account : Counter = program
. account :: < Counter >( counter . pubkey ())
. await ? ;
println! ( "Counter: {}" , counter_account . count);
Ok (())
}
TypeScript Client
For TypeScript clients, use the generated IDL types:
import * as anchor from "@anchor-lang/core" ;
import { Program } from "@anchor-lang/core" ;
import { ExternalProgram } from "./target/types/external_program" ;
const program = anchor . workspace . ExternalProgram as Program < ExternalProgram >;
const counterKeypair = anchor . web3 . Keypair . generate ();
// Call increment instruction
const tx = await program . methods
. increment ()
. accounts ({
counter: counterKeypair . publicKey ,
})
. rpc ();
console . log ( "Transaction signature:" , tx );
// Fetch account
const counter = await program . account . counter . fetch (
counterKeypair . publicKey
);
console . log ( "Count:" , counter . count . toString ());
Working with Events
If the external program emits events, use the generated events module:
use external_program :: events :: CounterUpdated ;
pub fn handle_event ( ctx : Context < HandleEvent >) -> Result <()> {
// Events are typically read by off-chain listeners
// but you can access event types for type safety
Ok (())
}
Off-chain event listening (Rust):
let mut event_subscriber = program . events () ? ;
loop {
let event = event_subscriber . next () . await ? ;
match event {
external_program :: events :: CounterUpdated { old_count , new_count } => {
println! ( "Counter updated: {} -> {}" , old_count , new_count );
}
_ => {}
}
}
Handling Errors
Access program-specific errors through the generated errors module:
use external_program :: errors :: ErrorCode ;
pub fn handle_error ( ctx : Context < HandleError >) -> Result <()> {
// Check for specific error conditions
if some_condition {
return Err ( ErrorCode :: CustomError . into ());
}
Ok (())
}
Working with Custom Types
Use the generated types module to access program-defined structs and enums:
use external_program :: types :: { CustomStruct , CustomEnum };
pub fn use_custom_types (
ctx : Context < UseCustom >,
data : CustomStruct ,
variant : CustomEnum ,
) -> Result <()> {
// Use the custom types
msg! ( "Received custom data" );
Ok (())
}
Multiple Programs
Declare multiple external programs in the same file:
use anchor_lang :: prelude ::* ;
// Declare multiple programs
declare_program! ( program_a );
declare_program! ( program_b );
declare_program! ( program_c );
use program_a :: cpi as program_a_cpi;
use program_b :: cpi as program_b_cpi;
use program_c :: cpi as program_c_cpi;
#[program]
pub mod your_program {
use super ::* ;
pub fn call_multiple ( ctx : Context < CallMultiple >) -> Result <()> {
// Call program A
program_a_cpi :: instruction_a (
CpiContext :: new ( /* ... */ ),
) ? ;
// Call program B
program_b_cpi :: instruction_b (
CpiContext :: new ( /* ... */ ),
) ? ;
Ok (())
}
}
Advantages
Zero Dependencies No need to add external programs as crate dependencies, reducing compilation time and dependency conflicts.
Version Flexibility Work with different versions of external programs by simply swapping IDL files.
Unified Interface Single macro generates both on-chain (CPI) and off-chain (client) code.
Type Safety Fully typed interfaces generated from IDL ensure compile-time safety.
Limitations
Account Validation : The declare_program!() macro generates type definitions but doesn’t include the constraint validation logic from the original program. Always validate account relationships in your program.
IDL Availability : Requires the external program to publish its IDL. Programs without IDLs cannot be used with this macro.
Custom Types : Complex types that don’t derive AnchorSerialize/AnchorDeserialize may require manual handling.
Best Practices
Commit IDL files to version control to ensure reproducible builds: # .gitignore
# Don't ignore IDL files
!idls/*.json
Always verify the program ID matches the expected address: require_keys_eq! (
ctx . accounts . external_program . key (),
external_program :: ID ,
ErrorCode :: InvalidProgram
);
Document where IDL files come from: // IDL from: https://github.com/project/external-program/releases/tag/v1.0.0
declare_program! ( external_program );
Thoroughly test all CPI interactions: #[cfg(test)]
mod tests {
use super ::* ;
#[test]
fn test_external_cpi () {
// Test CPI calls
}
}
Troubleshooting
IDL Not Found
Error:
error: could not find IDL file for program 'external_program'
Solution:
Ensure the IDL file exists in an /idls directory:
project/
└── idls/
└── external_program.json
Module Not Found
Error:
error[E0433]: failed to resolve: use of undeclared crate or module `external_program`
Solution:
Add the declare_program!() invocation:
declare_program! ( external_program );
Type Mismatch
Error:
expected struct `Account<'_, Counter>`, found `InterfaceAccount<'_, ...>`
Solution:
Use the generated account types:
use external_program :: accounts :: Counter ;
pub counter : Account <' info , Counter >,
Resources