| title | Dependency Free Composability |
|---|---|
| description | 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:
| Module | Description |
|---|---|
cpi |
Helper functions for making cross-program invocations (CPIs) to the program from other on-chain programs |
client |
Accounts and arguments required to build program instructions to add to client-side transactions |
account |
Account data types (program state) defined in the program |
program |
Program ID constant used to identify the program |
constants |
Program constants defined in the program |
events |
Program events defined in the program |
types |
Program types defined in the program |
error |
Program errors defined in the program |
parsers |
Parsers for program accounts, instructions and events |
The following examples demonstrate how to use the declare_program!() macro in
two scenarios:
- Making Cross Program Invocations (CPIs) from one program to another program
- 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.
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:
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.
<Tabs items={["Callee Program", "IDL"]}>
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,
}{
"address": "8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8",
"metadata": {
"name": "example",
"version": "0.1.0",
"spec": "0.1.0",
"description": "Created with Anchor"
},
"instructions": [
{
"name": "increment",
"discriminator": [
11,
18,
104,
9,
104,
174,
59,
33
],
"accounts": [
{
"name": "counter",
"writable": true
}
],
"args": []
},
{
"name": "initialize",
"discriminator": [
175,
175,
109,
31,
13,
152,
155,
237
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "counter",
"writable": true,
"signer": true
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
}
],
"accounts": [
{
"name": "Counter",
"discriminator": [
255,
176,
4,
245,
188,
253,
124,
25
]
}
],
"types": [
{
"name": "Counter",
"type": {
"kind": "struct",
"fields": [
{
"name": "count",
"type": "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.
<Tabs items={["Caller Program", "Test"]}>
use anchor_lang::prelude::*;
declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");
// [!code word:declare_program]
// [!code highlight:9]
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.key(),
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.key(),
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>,
}import * as anchor from "@anchor-lang/core";
import { Program } from "@anchor-lang/core";
import { Example } from "../target/types/example";
import { ExampleCpi } from "../target/types/example_cpi";
import { Keypair } from "@solana/web3.js";
describe("example", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Example as Program<Example>;
const cpiProgram = anchor.workspace.ExampleCpi as Program<ExampleCpi>;
const counterAccount = Keypair.generate();
it("Is initialized!", async () => {
const transactionSignature = await cpiProgram.methods
.initializeCpi()
.accounts({
counter: counterAccount.publicKey,
})
.signers([counterAccount])
.rpc({ skipPreflight: true });
const accountData = await program.account.counter.fetch(
counterAccount.publicKey,
);
console.log(`Transaction Signature: ${transactionSignature}`);
console.log(`Count: ${accountData.count}`);
});
it("Increment", async () => {
const transactionSignature = await cpiProgram.methods
.incrementCpi()
.accounts({
counter: counterAccount.publicKey,
})
.rpc();
const accountData = await program.account.counter.fetch(
counterAccount.publicKey,
);
console.log(`Transaction Signature: ${transactionSignature}`);
console.log(`Count: ${accountData.count}`);
});
});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.jsonBring 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)]
// [!code word:Counter]
// [!code highlight]
pub counter: Account<'info, Counter>,
// Example type from program module
// [!code word:Example]
// [!code highlight]
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.key(),
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
// [!code highlight]
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.key(),
Increment {
counter: ctx.accounts.counter.to_account_info(),
},
);
// Invoke the increment instruction
// [!code highlight]
cpi::increment(cpi_ctx)?;
Ok(())
}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:
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.
<Tabs items={["Callee Program", "IDL"]}>
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,
}{
"address": "6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC",
"metadata": {
"name": "example",
"version": "0.1.0",
"spec": "0.1.0",
"description": "Created with Anchor"
},
"instructions": [
{
"name": "increment",
"discriminator": [11, 18, 104, 9, 104, 174, 59, 33],
"accounts": [
{
"name": "counter",
"writable": true
}
],
"args": []
},
{
"name": "initialize",
"discriminator": [175, 175, 109, 31, 13, 152, 155, 237],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "counter",
"writable": true,
"signer": true
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
}
],
"accounts": [
{
"name": "Counter",
"discriminator": [255, 176, 4, 245, 188, 253, 124, 25]
}
],
"types": [
{
"name": "Counter",
"type": {
"kind": "struct",
"fields": [
{
"name": "count",
"type": "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.
<Tabs items={["Client Script", "Dependencies"]}>
use anchor_client::{
solana_client::rpc_client::RpcClient,
solana_sdk::{
commitment_config::CommitmentConfig, native_token::LAMPORTS_PER_SOL, signature::Keypair,
system_program,
},
solana_signer::Signer,
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(())
}[package]
name = "rs"
version = "0.1.0"
edition = "2021"
[dependencies]
anchor-client = { version = "1.0.2", features = ["async"] }
anchor-lang = "1.0.2"
anyhow = "1.0.93"
tokio = { version = "1.0", features = ["full"] }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.jsonBring 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?;