
What if a Solana Anchor program is created with an older version of Anchor and you want to interact with it using a newer version of the Anchor framework? Or what if the on-chain program does not provide source code, but only the IDL? This article may help with all of this.
Anchor IDL (Interface Definition Language) is a JSON file that describes the interface of an Anchor program. It includes information about the program’s instructions, accounts, types, and events. The IDL is used by the Anchor framework to generate client code that can interact with the program.
The IDL is generated automatically when you build an Anchor program using the
Anchor CLI. The IDL file is typically located in the target/idl
directory of
your Anchor project.
Any good Anchor program should have the IDL file available on-chain, so you can
fetch it using the program’s public key. One way is to check the Solana Explorer.
See for example the IDL of Marinade Liquid Staking program.
Another way to fetch the IDL is to use the Anchor CLI:
anchor idl fetch <PROGRAM_ID> -o <OUTPUT_FILE>
E.g., for Marinade Liquid Staking program:
anchor idl fetch MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD | head -n 10
{
"version": "0.1.0",
"name": "marinade_finance",
"instructions": [
{
"name": "initialize",
"accounts": [
{
"name": "state",
"isMut": true,
Besides the IDL file, the Anchor program provides a binding for TypeScript client code.
When an Anchor program is built, the TypeScript bindings are generated in the target/types
directory.
It is not available directly on-chain.
Let’s take a look at Marinade Liquid Staking program.
It is a program that was written quite some time ago, and it uses an older version of
Anchor in version 0.27.0.
That’s a version released in March 2023 and it is dependent on Solana client v1.14.x.
The source code of the Liquid Staking program is available on GitHub, but just building it is a bit problematic
as the older versions of Solana client cannot be easily installed with the curl
toolchain command.
This one just does not work:
sh -c "$(curl -sSfL https://release.anza.xyz/v1.14.29/install)"
> curl: (22) The requested URL returned error: 404
The solution requires a few manual steps as the older versions of Solana client are still available on GitHub.
Download the Solana v1.14.29 tarball from the Solana releases page.
In my case, I downloaded the solana-release-x86_64-unknown-linux-gnu.tar.bz2
file.
Extract the tarball to $HOME/.local/share/solana/install/releases/<version>
directory.
VERSION=1.14.29
mkdir -p $HOME/.local/share/solana/install/releases/$VERSION
# creating the releases directory: $HOME/.local/share/solana/install/releases/$VERSION/solana-release/
tar xvjf solana-release-x86_64-unknown-linux-gnu.tar.bz2 -C $HOME/.local/share/solana/install/releases/$VERSION
Symlink the newly extracted directory
rm -f $HOME/.local/share/solana/install/active_release
ln -s $HOME/.local/share/solana/install/releases/$VERSION/solana-release $HOME/.local/share/solana/install/active_release
Install the Anchor version
# expected that the avm tool is already installed
avm install 0.27.0
avm use 0.27.0
Now you can build the Anchor program
git clone https://github.com/marinade-finance/liquid-staking-program.git
cd liquid-staking-program/
anchor build
ls target/idl/
> marinade_finance.json
ls target/types/
> marinade_finance.ts
The Anchor library is (hopefully we can say was) deeply coupled with particular versions of the Solana toolchain. So working with a program coded in Anchor version 0.27.0 where Solana version 1.17.x was required was problematic when integrating CPI calls or client linking with newer versions (e.g. Anchor 0.29.0).
Anchor developers strive to address this issue by making Anchor more independent of Solana tooling but these difficulties still occur especially when working with programs coded in older Anchor versions.
Additionally, from the past to nowadays there was one substantial change of IDL format that was introduced in version 0.30.0 and made required in Anchor 0.30.1. This is an issue especially for Anchor TypeScript SDK client that uses the IDL to understand the on-chain program structure and to serialize and deserialize the instruction calls to the program.
Anchor CLI from version 0.30.1 provides a utility that is capable of converting the IDL
from the older format to the newer one. The command is anchor idl convert
.
Running it simply with an input file does not work in case the IDL format does not contain
the metadata.address
field. This field is required in the newer IDL format
and it has to be provided manually when converting the IDL.
# downloading the IDL from the chain of older version
anchor --provider.cluster mainnet idl fetch \
MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD > /tmp/marinade_finance.json
# converting the IDL to the newer format
anchor idl convert /tmp/marinade_finance.json \
--program-id MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD \
-o /tmp/marinade_finance_v0.30.0.json
The way to overcome the issue with Anchor version incompatibilities, there are two options in Rust.
Either using the third party crate anchor-gen
that makes it possible to work
with programs of the older Anchor.
From Anchor 0.30.0 there is an option to use macro declare_program!
doing similar.
WARNING be aware that anchor-gen code has not been audited
When using the Anchor 0.30.1+
then declare_program
is probably the way to go for CPI calls within the program.
The example usage of the macro can be found
in the Anchor sources.
The idea is to have the idls
alongside the program using the macro. The declare_program!
then generates the required Rust structures
that can be used for interacting
with the program defined in the IDL.
Similarly for client calls you download the idl into a specific idls directory
and then use the macro declare_program!
with the name of the idl.json
file.
The use
directive then declares what instructions and accounts are imported to the client code.
Here is an example of the usage
[in Anchor client calls.
Anchor documents this approach at the chapter Dependency free composability.
When using the prior version of Anchor then you have to either upgrade (possibly recommended)
or use the anchor-gen
library. That works in a similar manner where a macro generates for you Rust code
based on IDL and that can be used within your project. As a difference there is created a separate "CPI crate"
for the program you want to interact with.
You can see the example of declaration of
Marinade Liquid Staking crate
inside of the anchor-gen/examples
.
You can see that you create the anchor-gen project
and place the IDL definition into idl.json
and from here the anchor_gen::generate_cpi_crate
macro builds the Rust structures and functions that can be called from your project.
This crate is linked to your project and used. Here is an example of the usage in Anchor client calls.
It could be good to align the versions of anchor-gen that work with particular Anchor versions to make it work with your project.
When you have the IDL file you can use it in your TypeScript client code. As mentioned before it is important to have the IDL in a format aligned with the Anchor client version you are using.
On building the Anchor program the TypeScript bindings are generated in the target/types
directory
while the IDL is in the target/idl
directory. The on-chain program provides the IDL only.
Having types for the TypeScript client is nice to have as it provides type safety.
When not having access to Anchor program source code you can generate the TypeScript types from the IDL
using my utility anchor-idl-to-ts.
anchor --provider.cluster mainnet idl fetch \
MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD > /tmp/marinade_finance.json
cargo install anchor-idl-to-ts
anchor-idl-to-ts /tmp/marinade_finance.json -o /tmp/marinade_finance.ts
With IDL and types you can use both in your TypeScript client code. The code that creates the Anchor program client looks similar. The most notable difference is that for older versions you need to manually provide the program address as it is not present in the IDL.
import { PublicKey } from '@solana/web3.js'
import { IDL as MarinadeIDL, MarinadeFinance } from './types/marinade'
import { AnchorProvider, IdlAccounts, Program, Wallet } from '@coral-xyz/anchor'
const MARINADE_LIQUID_STAKE_PROGRAM_ID = new PublicKey("MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD")
const MARINADE_LIQUID_STAKE_STATE = new PublicKey("8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC")
export type MarinadeProgram = Program<MarinadeFinance>
export type MarinadeState = IdlAccounts<MarinadeFinance>['state']
...
const wallet = new Wallet(keypair);
const provider = new AnchorProvider(
connection,
wallet,
AnchorProvider.defaultOptions(),
)
const program = new Program<MarinadeFinance>(MarinadeIDL, MARINADE_LIQUID_STAKE_PROGRAM_ID, provider)
const marinadeState = await program.account.state.fetch(MARINADE_LIQUID_STAKE_STATE)
You can see the example code for:
idl.metadata.address
fieldThe declare_program!
macro is developed to be capable of working with IDLs generated
in the older versions of Anchor.
But there are some pitfalls that you may encounter.
One is when the IDL does not contain the metadata.address
field.
When Anchor was generating the IDL in the older versions, the metadata address field
was not added until the IDL was uploaded to the chain.
For manual IDL deployment it meant that the address field was not present in the IDL.
You may see an error like this:
error: Program id missing in `idl.metadata.address` field
--> src/main.rs:10:18
|
10 | declare_program!(marinade);
In older versions of Anchor the IDL was not created with the address
field by default.
The address was added to the IDL for example only at the time the IDL was uploaded to the chain.
When working with an older IDL it may happen that the names of accounts or instructions
collide. They were not separated as strictly as nowadays. For example, in the Marinade Liquid Staking program
there is a named set of accounts used for an instruction in the same way as common
that generates the name collision.
error[E0428^]: the name `__cpi_client_accounts_common` is defined multiple times
--> src/main.rs:10:1
|
10 | declare_program!(marinade);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ `__cpi_client_accounts_common` redefined here
|
= note: `__cpi_client_accounts_common` must be defined only once in the type namespace of this module
= note: this error originates in the macro `declare_program` (in Nightly builds, run with -Z macro-backtrace for more info)
My resolution was to manually edit the IDL and rename the common
instruction account set to
common_<suffix>
.
This article addresses the common challenge of working with Solana Anchor programs built with older framework versions
when you need to interact with them using newer versions.
It covers how to fetch and convert IDL files between different formats, particularly the breaking change introduced
in Anchor 0.30.0. You’ll learn two main approaches for Rust integration (declare_program!
macro for newer versions
and the anchor-gen
crate for older ones) and how to set up TypeScript clients that work across versions.