Generate typescript solana web3 clients from anchor IDLs.
# npm
$ npm install --global anchor-client-gen
# yarn
$ yarn global add anchor-client-gen
To get the beta build which has unreleased features, install with anchor-client-gen@beta
.
Usage: main [options] <idl> <out>
Generate solana web3 client code from the specified anchor IDL.
Arguments:
idl anchor IDL file path or '-' to read from stdin
out output directory
Options:
--program-id <PROGRAM_ID> optional program ID to be included in the code
-V, --version output the version number
-h, --help display help for command
$ anchor-client-gen path/to/idl.json output/directory
This will generate code to output/directory
:
.
├── accounts
│ ├── FooAccount.ts
│ └── index.ts
├── instructions
│ ├── someInstruction.ts
│ ├── otherInstruction.ts
│ └── index.ts
├── types
│ ├── BarStruct.ts
│ ├── BazEnum.ts
│ └── index.ts
├── errors
│ ├── anchor.ts
│ ├── custom.ts
│ └── index.ts
└── programId.ts
For more examples of the generated code, check out the examples directory.
The following packages are required for the generated client to work:
@solana/web3.js
bn.js
@coral-xyz/borsh
buffer-layout
Install them in your project with:
// npm
$ npm install @solana/web3.js bn.js @coral-xyz/borsh buffer-layout
// yarn
$ yarn add @solana/web3.js bn.js @coral-xyz/borsh buffer-layout
For typescript, the buffer-layout
types can be found in the @coral-xyz/borsh
package.
tsconfig.json
:
{
"compilerOptions": {
"types": ["<existing types>", "../node_modules/@coral-xyz/anchor/types"]
}
}
import { someInstruction } from "./output/directory/instructions"
const fooAccount = generateKeyPairSigner()
// call an instruction
const ix = someInstruction({
fooParam: "...",
barParam: "...",
...
}, {
fooAccount: fooAccount, // signer
barAccount: address("..."),
...
})
const blockhash = await rpc
.getLatestBlockhash({ commitment: "finalized" })
.send()
const tx = await pipe(
createTransactionMessage({ version: 0 }),
(tx) => appendTransactionMessageInstruction(ix, tx),
(tx) => setTransactionMessageFeePayerSigner(payer, tx),
(tx) =>
setTransactionMessageLifetimeUsingBlockhash(
{
blockhash: blockhash.value.blockhash,
lastValidBlockHeight: blockhash.value.lastValidBlockHeight,
},
tx
),
(tx) => signTransactionMessageWithSigners(tx)
)
const sendAndConfirmFn = sendAndConfirmTransactionFactory({
rpc,
rpcSubscriptions,
})
await sendAndConfirmFn(tx)
import { FooAccount } from "./output/directory/accounts"
// fetch an account
const addr = address("...")
const acc = FooAccount.fetch(rpc, addr)
if (acc === null) {
// the fetch method returns null when the account is uninitialized
console.log("account not found")
return
}
// convert to a JSON object
const obj = acc.toJSON()
console.log(obj)
// load from JSON
const accFromJSON = FooAccount.fromJSON(obj)
// structs
import { BarStruct } from "./output/directory/types"
const barStruct = new BarStruct({
someField: "...",
otherField: "...",
})
console.log(barStruct.toJSON())
// enums
import { BazEnum } from "./output/directory/types"
const tupleEnum = new BazEnum.SomeTupleKind([true, false, "some value"])
const structEnum = new BazEnum.SomeStructKind({
field1: "...",
field2: "...",
})
const discEnum = new BazEnum.SomeDiscriminantKind()
console.log(tupleEnum.toJSON(), structEnum.toJSON(), discEnum.toJSON())
// types are used as arguments in instruction calls (where needed):
const ix = someInstruction({
someStructField: barStruct,
someEnumField: tupleEnum,
...
}, {
// accounts
...
})
// in case of struct fields, it's also possible to pass them as objects:
const ix = someInstrution({
someStructField: {
someField: "...",
otherField: "...",
},
...,
}, {
// accounts
...
})
import { fromTxError } from "./output/directory/errors"
import { SomeCustomError } from "./output/directory/errors/custom"
try {
await sendAndConfirmFn(tx)
} catch (e) {
const parsed = fromTxError(e)
if (parsed !== null && parsed instanceof SomeCustomError) {
console.log(
"SomeCustomError was thrown",
parsed.code,
parsed.name,
parsed.msg
)
}
}
The client generator pulls the program ID from:
- the input IDL
- the
--program-id
flag
These are then written into the programId.ts
file.
The PROGRAM_ID
constant inside programId.ts
can be (and should be) modified to define the correct program ID as the client relies on it to do checks when fetching accounts etc. The PROGRAM_ID
constant is safe to modify as it will be preserved across multiple code generations. The imports in this file are also preserved.
The package minor versions match anchor minor versions. So, for example, package version v0.22.x
will match anchor v0.22.y
. The earliest supported anchor version is v0.22
, but the generator probably also works with older versions of anchor since the IDL format is mostly backwards compatible.