Skip to content

A tool for generating solana web3 clients from anchor IDLs.

License

Notifications You must be signed in to change notification settings

kklas/anchor-client-gen

Repository files navigation

anchor-client-gen

npm npm GitHub Workflow Status

Generate typescript solana web3 clients from anchor IDLs.

Installation

# 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

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

Example

$ 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.

Using the generated client

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"]
  }
}

Instructions

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)

Accounts

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)

Types

// 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
  ...
})

Errors

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
    )
  }
}

Program ID

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.

Versioning

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.

About

A tool for generating solana web3 clients from anchor IDLs.

Resources

License

Stars

Watchers

Forks

Packages

No packages published