A tool for generating solana web3 clients from anchor IDLs.

Overview

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
  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
├── programId.ts
└── errors.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
  • @project-serum/borsh

Install them in your project with:

// npm
$ npm install @solana/web3.js bn.js @project-serum/borsh

// yarn
$ yarn add @solana/web3.js bn.js @project-serum/borsh

Instructions

import { someInstruction } from "./output/directory/instructions"

// call an instruction
const tx = new Transaction()
const fooAccount = new Keypar()

const ix = someInstruction({
  fooParam: "...",
  barParam: "...",
  ...
}, {
  fooAccount: fooAccount.publicKey, // signer
  barAccount: new PublicKey("..."),
  ...
})
tx.add(ix)

sendAndConfirmTransaction(connection, tx, [payer, fooAccount])

Accounts

import { FooAccount } from "./output/directory/accounts"

// fetch an account
const addr = new PublicKey("...")

const acc = FooAccount.fetch(connection, 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 { SomeCustomError, fromTxError } from "./output/directory/errors"

try {
  await sendAndConfirmTransaction(c, tx, [payer])
} 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.

Comments
  • Add programIdOverride

    Add programIdOverride

    Added

    // Use this only if you need to change the program ID dynamically during app runtime.
    export const programIdOverride: (() => PublicKey) | undefined = undefined
    

    to programId.ts.

    When programIdOverride function is defined, it will be called and used to override the program ID defined statically with the PROGRAM_ID constant.

    opened by kklas 11
  • map bytes to Uint8array

    map bytes to Uint8array

    Uint8Array is a js native representation for an array of unsigned bytes, the current Array mapping has bad performance characteristics, especially when working with large buffers. I'd rather leave the Array conversion to the user than making it part of the generator.

    opened by mschneider 4
  • using Box<MyType> for instruction arguments should be handled transparently

    using Box for instruction arguments should be handled transparently

    Problem:

    fn do_something(arg: Box<MyType>) -> Result<()>  {
         ...
    }
    

    Currently the above fails with the following error:

    generating programId.ts...
    generating errors.ts...
    generating instructions...
    Defined type not found: {"defined":"Box<MyType>"}
    

    Proposal:

    Disregard the Box and just look for MyType

    opened by JoeHowarth 4
  • Empty

    Empty "/types/index.ts"

    I noticed if I run this tool against an IDL with no "types" defined, the resultant /types/index.ts file will empty, which causes import errors in the other files.

    I was able to fix the errors by putting export default {}; in /types/index.ts.

    opened by ronanyeah 3
  • Possible to support multiple programs with inter-dependencies?

    Possible to support multiple programs with inter-dependencies?

    Apologies if this is a dumb question, let's say I have a project with a few programs:

    programs/main
    programs/admin
    programs/pools
    

    where the accounts, structs, enums, etc are defined in the main program, and referenced as a dependency in the other two. Anchor creates three IDL's. Is it possible to generate a client (or three different clients?) from this structure? Right now I don't think it's possible to expose the structs cross-program.

    opened by 0xBraunGuy 2
  • `kind` of typescript union members should be static

    `kind` of typescript union members should be static

    I'd like to branch on variant kind like below, but kind is an instance variable, not static, so the following doesn't work

    switch (myEnumValue.kind) {
      case MyEnum.Swap.kind:
        ...
      case MyEnum.LP.kind:
        ...
    }
    

    Proposal

    export class Swap {
      static readonly discriminator = 0        // <--- make these 2 fields static 
      static readonly kind = "Swap"
      readonly value: SwapValue
    
      constructor(value: SwapFields) {
        this.value = [new types.SwapInfo({ ...value[0] })]
      }
    
      toJSON(): SwapJSON {
        return {
          kind: "Swap",
          value: [this.value[0].toJSON()],
        }
      }
    
      toEncodable() {
        return {
          Swap: {
            _0: types.SwapInfo.toEncodable(this.value[0]),
          },
        }
      }
    }
    

    If there's another more idiomatic way of doing this please let me know instead

    opened by JoeHowarth 2
  • Simplifying client facing API by removing `connection` parameter

    Simplifying client facing API by removing `connection` parameter

    Interested in getting thoughts on dropping the connection parameter, or making it optional. I'm not clear on the use case on being able to target multiple or different connections.

    Anchor already provides a global straight-forward way to get the currently configure connection that could be used within the generated clients.

    getProvider().connection
    

    This would make the interface of e.g., fetch, cleaner IMO, passing just the address of the account to fetch, and save client authors having to pass around the connection.

      static async fetch(address: PublicKey): Promise<State | null> {
        const c = getProvider().connection
        const info = await c.getAccountInfo(address)
    
        if (info === null) {
          return null
        }
        if (!info.owner.equals(PROGRAM_ID)) {
          throw new Error("account doesn't belong to this program")
        }
    
        return this.decode(info.data)
      }
    
    ...
    
    const res = await State.fetch(state.publicKey)
    
    opened by eoin-betdex 2
  • noImplicitAny violations in generated code

    noImplicitAny violations in generated code

    import { PublicKey, Connection } from "@solana/web3.js"
    import BN from "bn.js" // eslint-disable-line @typescript-eslint/no-unused-vars
    import * as borsh from "@project-serum/borsh" // eslint-disable-line @typescript-eslint/no-unused-vars
    import * as types from "../types" // eslint-disable-line @typescript-eslint/no-unused-vars
    import { PROGRAM_ID } from "../programId"
    
    export interface OrderbookInfoFields {
      admin: PublicKey
      length: number
      applesMint: PublicKey
      orangesMint: PublicKey
      bump: number
      closed: boolean
      id: PublicKey
      tradeLog: Array<types.TradeRecordFields>
    }
    
    export interface OrderbookInfoJSON {
      admin: string
      length: number
      applesMint: string
      orangesMint: string
      bump: number
      closed: boolean
      id: string
      tradeLog: Array<types.TradeRecordJSON>
    }
    
    export class OrderbookInfo {
      readonly admin: PublicKey
      readonly length: number
      readonly applesMint: PublicKey
      readonly orangesMint: PublicKey
      readonly bump: number
      readonly closed: boolean
      readonly id: PublicKey
      readonly tradeLog: Array<types.TradeRecord>
    
      static readonly discriminator = Buffer.from([
        126, 118, 193, 78, 125, 233, 132, 90,
      ])
    
      static readonly layout = borsh.struct([
        borsh.publicKey("admin"),
        borsh.u32("length"),
        borsh.publicKey("applesMint"),
        borsh.publicKey("orangesMint"),
        borsh.u8("bump"),
        borsh.bool("closed"),
        borsh.publicKey("id"),
        borsh.vec(types.TradeRecord.layout(), "tradeLog"),
      ])
    
      constructor(fields: OrderbookInfoFields) {
        this.admin = fields.admin
        this.length = fields.length
        this.applesMint = fields.applesMint
        this.orangesMint = fields.orangesMint
        this.bump = fields.bump
        this.closed = fields.closed
        this.id = fields.id
        this.tradeLog = fields.tradeLog.map(
          (item) => new types.TradeRecord({ ...item })
        )
      }
    
      static async fetch(
        c: Connection,
        address: PublicKey
      ): Promise<OrderbookInfo | null> {
        const info = await c.getAccountInfo(address)
    
        if (info === null) {
          return null
        }
        if (!info.owner.equals(PROGRAM_ID)) {
          throw new Error("account doesn't belong to this program")
        }
    
        return this.decode(info.data)
      }
    
      static async fetchMultiple(
        c: Connection,
        addresses: PublicKey[]
      ): Promise<Array<OrderbookInfo | null>> {
        const infos = await c.getMultipleAccountsInfo(addresses)
    
        return infos.map((info) => {
          if (info === null) {
            return null
          }
          if (!info.owner.equals(PROGRAM_ID)) {
            throw new Error("account doesn't belong to this program")
          }
    
          return this.decode(info.data)
        })
      }
    
      static decode(data: Buffer): OrderbookInfo {
        if (!data.slice(0, 8).equals(OrderbookInfo.discriminator)) {
          throw new Error("invalid account discriminator")
        }
    
        const dec = OrderbookInfo.layout.decode(data.slice(8))
    
        return new OrderbookInfo({
          admin: dec.admin,
          length: dec.length,
          applesMint: dec.applesMint,
          orangesMint: dec.orangesMint,
          bump: dec.bump,
          closed: dec.closed,
          id: dec.id,
          tradeLog: dec.tradeLog.map((item) => types.TradeRecord.fromDecoded(item)),
        })
      }
    
      toJSON(): OrderbookInfoJSON {
        return {
          admin: this.admin.toString(),
          length: this.length,
          applesMint: this.applesMint.toString(),
          orangesMint: this.orangesMint.toString(),
          bump: this.bump,
          closed: this.closed,
          id: this.id.toString(),
          tradeLog: this.tradeLog.map((item) => item.toJSON()),
        }
      }
    
      static fromJSON(obj: OrderbookInfoJSON): OrderbookInfo {
        return new OrderbookInfo({
          admin: new PublicKey(obj.admin),
          length: obj.length,
          applesMint: new PublicKey(obj.applesMint),
          orangesMint: new PublicKey(obj.orangesMint),
          bump: obj.bump,
          closed: obj.closed,
          id: new PublicKey(obj.id),
          tradeLog: obj.tradeLog.map((item) => types.TradeRecord.fromJSON(item)),
        })
      }
    }
    
    

    Line 116 contains error (pictured) image Not sure if the "real" error is that dec should be typed, or what.

    opened by asktree 1
  • Generated client and type-check

    Generated client and type-check

    When i try to use generated client as npm package/yarn link in my main project where we have pre-commit/pre-build hooks i get type errors from ts type-check on dec. Is there any solution to for that ? (--skipLibCheck will not work on type error). Zrzut ekranu 2022-08-24 o 13 35 10

    opened by abrzezinski94 0
  • Yarn install fails on the specified anchor-cli version in package.json

    Yarn install fails on the specified anchor-cli version in package.json

    error An unexpected error occurred: "https://github.com/kklas/anchor-nightly/releases/download/anchor-cli-latest/anchor-cli-0.25.0-7eb8ca8.tgz: Request failed "404 Not Found"".

    opened by andreihrs 1
  • Enums don't use the correct discriminator when not in order

    Enums don't use the correct discriminator when not in order

    #[derive(Debug, TryFromPrimitive, PartialEq, Eq, Clone, Copy)]
    #[repr(u16)]
    pub enum GlobalConfigOption {
        EmergencyMode = 0,
        ...
    
        // 100
        ScopeProgramId = 100,
        ScopePriceId = 101,
    }
    

    Codegen:

    export interface ScopePriceIdJSON {
      kind: "ScopePriceId"
    }
    
    export class ScopePriceId {
      static readonly discriminator = 17
      static readonly kind = "ScopePriceId"
      readonly discriminator = 17
      readonly kind = "ScopePriceId"
    }
    

    This should be 101, not 17..

    opened by y2kappa 1
  • Support Uint8Array when applicable

    Support Uint8Array when applicable

    Hey @kklas , thank you for your help with the bytes type. I am currently working on a project, where I would like to use this for more data types, specifically:

    1. Vec<u8>
    2. fixed size arrays [u8; 32]

    I had a look at the code already and was able to get it to work on a local branch: https://github.com/mschneider/anchor-client-gen/tree/max/uint8array2 but curious to hear your thoughts as this somewhat breaks with the current abstraction of the library on a few levels.

    My ideal outcome would be:

    1. always use the right native array type
    2. always verify size when initializing from user input
    3. possibly pad when initializing from too short user input

    Let me know what you think about these changes.

    opened by mschneider 8
  • How to pass additional `remaining accounts`?

    How to pass additional `remaining accounts`?

    Anchor allows accessing accounts not defined on the Accounts struct through ctx.remaining_accounts. I currently don't see a way to pass these additional accounts in the generated client code cleanly.

    Proposal: Add optional arg to pass additional accounts

    export function redeem(args: RedeemArgs, accounts: RedeemAccounts, additionalAccounts?: AccountMeta[]) {...}
    

    Haven't tried it yet, but I believe there's a workaround where the returned TransactionInstruction can be mutated, but this is non-ideal

    opened by JoeHowarth 2
  • Event parsing support

    Event parsing support

    Event parsing in anchor-ts relies on IDL magic: https://github.com/project-serum/anchor/blob/master/ts/src/program/event.ts

    So it would be nice if the generated client came with an event parser.

    opened by kevinheavey 0
Owner
Krešimir Klas
Krešimir Klas
Quickly create an anchor program from templates and setup anchor dev environment.

Create Anchor App Quickly create an anchor program from templates and setup anchor dev environment. Example Installation npm i -g @create-anchor-app/c

null 24 Nov 28, 2022
Solana blockchain candy machine app boilerplate on top of Metaplex Candy Machine. NextJS, Tailwind, Anchor, SolanaLabs.React, dev/mainnet automation scripts.

NFT Candy Factory NOTE: This repo will prob only work on unix-based environments. The NFT Candy Factory project is designed to let users fork, customi

Kevin Faveri 261 Dec 30, 2022
Components and tools for building DeFi dapps on Solana + Anchor. Public domain license.

Solana DeFi Framework Components and tools for building DeFi dapps on Solana + Anchor. Public domain license. Status Pre-pre-pre-alpha. Contributing A

null 4 Mar 28, 2022
The best Solana Program Template (No Anchor!)

The Best Solana Program Template Includes Shank/Solita SDK generation, Amman support, scripts, .github configuration, and more! Environment Setup Inst

Sammy 50 Dec 24, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
solana-base-app is a base level, including most of the common features and wallet connectivity, try using `npx solana-base-app react my-app`

solana-base-app solana-base-app is for Solana beginners to get them up and running fast. To start run : run npx solana-base-app react my-app change th

UjjwalGupta49 33 Dec 27, 2022
A super simple web3 API to allow crypto interactions with the Solana Network 🔑🌐

orca.js A JavaScript API for web3 Transaction and Authenticating PLEASE NOTE: orca.js is still in development! And comming soon in 2022. Stay tuned on

null 3 Mar 20, 2022
A super simple web3 API to allow crypto interactions with the Solana Network 🔑🌐

orca.js A JavaScript API for web3 Transaction and Authenticating PLEASE NOTE: orca.js is still in development! And comming soon in 2022. Stay tuned on

null 3 Mar 20, 2022
NFT stacking frontend completion using web3 on solana for client Nelson project.

NFT stacking frontend completion using web3 on solana for client Nelson project.

Kapollo 12 Dec 16, 2022
Music World is web3 app built over Solana where anyone can add their favourite songs and see the other songs that are added by different people from around the globe.

?? Introduction Music World is web3 app built over Solana where anyone can add their favourite songs and see the other songs that are added by differe

Apoorv Dwivedi 3 Jun 10, 2022
A lightweight script to animate scrolling to anchor links.

DEPRECATION NOTICE: Smooth Scroll is, without a doubt, my most popular and widely used plugin. But in the time since I created it, a CSS-only method f

Chris Ferdinandi 5.4k Dec 26, 2022
Web3-citizens-app - React application based on smart contract using web3 and MetaMask extention.

Citizens App (web3-react-redux) React application based on smart contract using web3 and MetaMask extention. Start the applicarion Recomend to install

Denys Voloshyn 3 Aug 25, 2022
A web3 starter project using Typescript, Hardhat, ethers.js and @web3-react

Starter React Typescript Ethers.js Hardhat Project This repo contains a Hardhat and React Dapp starter project. The React Dapp in the frontend dir of

ChainShot 39 Dec 31, 2022
(🔗, 🌲) Web3 Link Tree is a free & open-source alternative to Linktree built with React.js, Next.js, Tailwind and Web3-React

Getting Started Read the detailed guide here Customize Add your name, wallet address, social media links and more in config.ts Images Save images to t

Naut 35 Sep 20, 2022
Collection of JSON-RPC APIs provided by Ethereum 1.0 clients

Ethereum JSON-RPC Specification View the spec The Ethereum JSON-RPC is a collection of methods that all clients implement. This interface allows downs

null 557 Jan 8, 2023
At BlackBox Vision we use heavily React Admin to build internal systems for our clients.

?? RA Firebase Starter ?? At BlackBox Vision we use heavily React Admin to build internal systems for our clients. Also, we rely on firebase a lot for

BlackBox Vision 2 Jun 7, 2022
To keep online organized data and be able to add listeners in certain paths by socket.io and http clients

The purpose of this project is to create a state machine server to keep organized data and be able to add listeners with socket.io in specific paths and in addition to providing a possible form of messages between socket.io clients.

Manga 3 Mar 19, 2022