Jelajahi Sumber

ts: `Coder` as interface and SPL token coder (#1259)

Armani Ferrante 3 tahun lalu
induk
melakukan
84e0852584
41 mengubah file dengan 2625 tambahan dan 126 penghapusan
  1. 4 0
      CHANGELOG.md
  2. 13 0
      tests/custom-coder/Anchor.toml
  3. 4 0
      tests/custom-coder/Cargo.toml
  4. 12 0
      tests/custom-coder/migrations/deploy.ts
  5. 19 0
      tests/custom-coder/package.json
  6. 19 0
      tests/custom-coder/programs/custom-coder/Cargo.toml
  7. 2 0
      tests/custom-coder/programs/custom-coder/Xargo.toml
  8. 14 0
      tests/custom-coder/programs/custom-coder/src/lib.rs
  9. 19 0
      tests/custom-coder/programs/spl-token/Cargo.toml
  10. 2 0
      tests/custom-coder/programs/spl-token/Xargo.toml
  11. 296 0
      tests/custom-coder/programs/spl-token/src/lib.rs
  12. 137 0
      tests/custom-coder/tests/custom-coder.ts
  13. 10 0
      tests/custom-coder/tsconfig.json
  14. 1 0
      tests/package.json
  15. 4 4
      tests/yarn.lock
  16. 41 6
      ts/src/coder/borsh/accounts.ts
  17. 4 3
      ts/src/coder/borsh/event.ts
  18. 2 2
      ts/src/coder/borsh/idl.ts
  19. 46 0
      ts/src/coder/borsh/index.ts
  20. 19 9
      ts/src/coder/borsh/instruction.ts
  21. 3 3
      ts/src/coder/borsh/state.ts
  22. 3 11
      ts/src/coder/common.ts
  23. 30 26
      ts/src/coder/index.ts
  24. 96 0
      ts/src/coder/spl-token/accounts.ts
  25. 151 0
      ts/src/coder/spl-token/buffer-layout.ts
  26. 14 0
      ts/src/coder/spl-token/events.ts
  27. 23 0
      ts/src/coder/spl-token/index.ts
  28. 349 0
      ts/src/coder/spl-token/instruction.ts
  29. 13 0
      ts/src/coder/spl-token/state.ts
  30. 5 0
      ts/src/idl.ts
  31. 3 10
      ts/src/index.ts
  32. 1 1
      ts/src/program/event.ts
  33. 8 3
      ts/src/program/index.ts
  34. 7 37
      ts/src/program/namespace/account.ts
  35. 2 2
      ts/src/program/namespace/index.ts
  36. 1 1
      ts/src/program/namespace/simulate.ts
  37. 2 2
      ts/src/program/namespace/state.ts
  38. 10 0
      ts/src/spl/index.ts
  39. 1230 0
      ts/src/spl/token.ts
  40. 2 2
      ts/tests/events.spec.ts
  41. 4 4
      ts/tests/transaction.spec.ts

+ 4 - 0
CHANGELOG.md

@@ -21,6 +21,10 @@ incremented for features.
 
 * lang: Allow repr overrides for zero copy accounts ([#1273](https://github.com/project-serum/anchor/pull/1273)).
 
+### Breaking
+
+* ts: `Coder` is now an interface and the existing class has been renamed to `BorshCoder`. This change allows the generation of Anchor clients for non anchor programs  ([#1259](https://github.com/project-serum/anchor/pull/1259/files)).
+
 ## [0.20.0] - 2022-01-06
 
 ### Fixes

+ 13 - 0
tests/custom-coder/Anchor.toml

@@ -0,0 +1,13 @@
+[programs.localnet]
+custom_coder = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+spl_token = "FmpfPa1LHEYRbueNMnwNVd2JvyQ89GXGWdyZEXNNKV8w"
+
+[registry]
+url = "https://anchor.projectserum.com"
+
+[provider]
+cluster = "localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 4 - 0
tests/custom-coder/Cargo.toml

@@ -0,0 +1,4 @@
+[workspace]
+members = [
+    "programs/*"
+]

+ 12 - 0
tests/custom-coder/migrations/deploy.ts

@@ -0,0 +1,12 @@
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@project-serum/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+};

+ 19 - 0
tests/custom-coder/package.json

@@ -0,0 +1,19 @@
+{
+  "name": "custom-coder",
+  "version": "0.20.0",
+  "license": "(MIT OR Apache-2.0)",
+  "homepage": "https://github.com/project-serum/anchor#readme",
+  "bugs": {
+    "url": "https://github.com/project-serum/anchor/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/project-serum/anchor.git"
+  },
+  "engines": {
+    "node": ">=11"
+  },
+  "scripts": {
+    "test": "anchor test"
+  }
+}

+ 19 - 0
tests/custom-coder/programs/custom-coder/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "custom-coder"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "custom_coder"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.20.0"

+ 2 - 0
tests/custom-coder/programs/custom-coder/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 14 - 0
tests/custom-coder/programs/custom-coder/src/lib.rs

@@ -0,0 +1,14 @@
+use anchor_lang::prelude::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[program]
+pub mod custom_coder {
+    use super::*;
+    pub fn initialize(_ctx: Context<Initialize>, a: Option<u8>) -> ProgramResult {
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct Initialize {}

+ 19 - 0
tests/custom-coder/programs/spl-token/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "spl-token"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "spl_token"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.20.0"

+ 2 - 0
tests/custom-coder/programs/spl-token/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 296 - 0
tests/custom-coder/programs/spl-token/src/lib.rs

@@ -0,0 +1,296 @@
+use anchor_lang::prelude::*;
+
+declare_id!("FmpfPa1LHEYRbueNMnwNVd2JvyQ89GXGWdyZEXNNKV8w");
+
+// This program is simply used to generate the IDL for the token program.
+//
+// Note that we manually add the COption<Pubkey> type to the IDL after
+// compiling.
+//
+#[program]
+pub mod spl_token {
+    use super::*;
+
+    pub fn initialize_mint(
+        ctx: Context<InitializeMint>,
+        decimals: u8,
+        mint_authority: Pubkey,
+        //        freeze_authority: COption<Pubkey>,
+    ) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn initialize_account(ctx: Context<InitializeAccount>) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn initialize_multisig(ctx: Context<InitializeMultisig>, m: u8) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn transfer(ctx: Context<Transfer>, amount: u64) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn approve(ctx: Context<Approve>, amount: u64) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn revoke(ctx: Context<Revoke>) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn set_authority(
+        ctx: Context<SetAuthority>,
+        authority_type: u8,
+        //        new_authority: COption<Pubkey>,
+    ) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn mint_to(ctx: Context<MintTo>, amount: u64) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn burn(ctx: Context<Burn>, amount: u64) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn close_account(ctx: Context<CloseAccount>) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn freeze_account(ctx: Context<FreezeAccount>) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn thaw_account(ctx: Context<ThawAccount>) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn transfer_checked(
+        ctx: Context<TransferChecked>,
+        amount: u64,
+        decimals: u8,
+    ) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn approve_checked(
+        ctx: Context<ApproveChecked>,
+        amount: u64,
+        decimals: u8,
+    ) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn mint_to_checked(
+        ctx: Context<MintToChecked>,
+        amount: u64,
+        decimals: u8,
+    ) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn burn_checked(ctx: Context<BurnChecked>, amount: u64, decimals: u8) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn initialize_account_2(
+        ctx: Context<InitializeAccount2>,
+        authority: Pubkey,
+    ) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn sync_native(ctx: Context<SyncNative>) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn initialize_account3(
+        ctx: Context<InitializeAccount3>,
+        authority: Pubkey,
+    ) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn initialize_multisig_2(ctx: Context<InitializeMultisig2>, m: u8) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn initialize_mint_2(
+        ctx: Context<InitializeMint2>,
+        decimals: u8,
+        mint_authority: Pubkey,
+        //        freeze_authority: COption<Pubkey>,
+    ) -> ProgramResult {
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct InitializeMint<'info> {
+    #[account(mut)]
+    mint: AccountInfo<'info>,
+    rent: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct InitializeAccount<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+    mint: AccountInfo<'info>,
+    authority: AccountInfo<'info>,
+    rent: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct InitializeMultisig<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+    rent: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct Transfer<'info> {
+    #[account(mut)]
+    source: AccountInfo<'info>,
+    #[account(mut)]
+    destination: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct Approve<'info> {
+    #[account(mut)]
+    source: AccountInfo<'info>,
+    delegate: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct Revoke<'info> {
+    #[account(mut)]
+    source: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct SetAuthority<'info> {
+    #[account(mut)]
+    pub mint: AccountInfo<'info>,
+    pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct MintTo<'info> {
+    #[account(mut)]
+    pub mint: AccountInfo<'info>,
+    #[account(mut)]
+    pub to: AccountInfo<'info>,
+    pub authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct Burn<'info> {
+    #[account(mut)]
+    source: AccountInfo<'info>,
+    #[account(mut)]
+    mint: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct CloseAccount<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+    #[account(mut)]
+    destination: AccountInfo<'info>,
+    authority: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct FreezeAccount<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+    mint: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct ThawAccount<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+    mint: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TransferChecked<'info> {
+    #[account(mut)]
+    source: AccountInfo<'info>,
+    mint: AccountInfo<'info>,
+    #[account(mut)]
+    destination: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct ApproveChecked<'info> {
+    #[account(mut)]
+    source: AccountInfo<'info>,
+    mint: AccountInfo<'info>,
+    delegate: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct MintToChecked<'info> {
+    #[account(mut)]
+    mint: AccountInfo<'info>,
+    #[account(mut)]
+    to: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct BurnChecked<'info> {
+    #[account(mut)]
+    source: AccountInfo<'info>,
+    #[account(mut)]
+    mint: AccountInfo<'info>,
+    authority: Signer<'info>,
+}
+
+#[derive(Accounts)]
+pub struct InitializeAccount2<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+    mint: AccountInfo<'info>,
+    rent: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct SyncNative<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct InitializeAccount3<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+    mint: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct InitializeMultisig2<'info> {
+    #[account(mut)]
+    account: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct InitializeMint2<'info> {
+    #[account(mut)]
+    mint: AccountInfo<'info>,
+}

+ 137 - 0
tests/custom-coder/tests/custom-coder.ts

@@ -0,0 +1,137 @@
+import * as anchor from "@project-serum/anchor";
+import { Spl } from "@project-serum/anchor";
+import * as assert from "assert";
+import BN from "bn.js";
+import { Keypair, SYSVAR_RENT_PUBKEY } from "@solana/web3.js";
+
+describe("custom-coder", () => {
+  // Configure the client to use the local cluster.
+  anchor.setProvider(anchor.Provider.env());
+
+  // Client.
+  const program = Spl.token();
+
+  // Constants.
+  const mintKeypair = Keypair.generate();
+  const aliceTokenKeypair = Keypair.generate();
+  const bobTokenKeypair = Keypair.generate();
+  const rent = SYSVAR_RENT_PUBKEY;
+
+  it("Creates a mint", async () => {
+    await program.rpc.initializeMint(
+      6,
+      program.provider.wallet.publicKey,
+      null,
+      {
+        accounts: {
+          mint: mintKeypair.publicKey,
+          rent,
+        },
+        signers: [mintKeypair],
+        preInstructions: [
+          await program.account.mint.createInstruction(mintKeypair),
+        ],
+      }
+    );
+    const mintAccount = await program.account.mint.fetch(mintKeypair.publicKey);
+    assert.ok(
+      mintAccount.mintAuthority.equals(program.provider.wallet.publicKey)
+    );
+    assert.ok(mintAccount.freezeAuthority === null);
+    assert.ok(mintAccount.decimals === 6);
+    assert.ok(mintAccount.isInitialized);
+    assert.ok(mintAccount.supply.toNumber() === 0);
+  });
+
+  it("Creates a token account for alice", async () => {
+    await program.rpc.initializeAccount({
+      accounts: {
+        account: aliceTokenKeypair.publicKey,
+        mint: mintKeypair.publicKey,
+        authority: program.provider.wallet.publicKey,
+        rent,
+      },
+      signers: [aliceTokenKeypair],
+      preInstructions: [
+        await program.account.token.createInstruction(aliceTokenKeypair),
+      ],
+    });
+    const token = await program.account.token.fetch(
+      aliceTokenKeypair.publicKey
+    );
+    assert.ok(token.authority.equals(program.provider.wallet.publicKey));
+    assert.ok(token.mint.equals(mintKeypair.publicKey));
+    assert.ok(token.amount.toNumber() === 0);
+    assert.ok(token.delegate === null);
+    assert.ok(token.state === 0);
+    assert.ok(token.isNative === null);
+    assert.ok(token.delegatedAmount.toNumber() === 0);
+    assert.ok(token.closeAuthority === null);
+  });
+
+  it("Mints a token to alice", async () => {
+    await program.rpc.mintTo(new BN(2), {
+      accounts: {
+        mint: mintKeypair.publicKey,
+        to: aliceTokenKeypair.publicKey,
+        authority: program.provider.wallet.publicKey,
+      },
+    });
+
+    const token = await program.account.token.fetch(
+      aliceTokenKeypair.publicKey
+    );
+    const mint = await program.account.mint.fetch(mintKeypair.publicKey);
+    assert.ok(token.amount.toNumber() === 2);
+    assert.ok(mint.supply.toNumber() === 2);
+  });
+
+  it("Creates a token for bob", async () => {
+    await program.rpc.initializeAccount({
+      accounts: {
+        account: bobTokenKeypair.publicKey,
+        mint: mintKeypair.publicKey,
+        authority: program.provider.wallet.publicKey,
+        rent,
+      },
+      signers: [bobTokenKeypair],
+      preInstructions: [
+        await program.account.token.createInstruction(bobTokenKeypair),
+      ],
+    });
+  });
+
+  it("Transfer a token from alice to bob", async () => {
+    await program.rpc.transfer(new BN(1), {
+      accounts: {
+        source: aliceTokenKeypair.publicKey,
+        destination: bobTokenKeypair.publicKey,
+        authority: program.provider.wallet.publicKey,
+      },
+    });
+    const aliceToken = await program.account.token.fetch(
+      aliceTokenKeypair.publicKey
+    );
+    const bobToken = await program.account.token.fetch(
+      bobTokenKeypair.publicKey
+    );
+    assert.ok(aliceToken.amount.toNumber() === 1);
+    assert.ok(bobToken.amount.toNumber() === 1);
+  });
+
+  it("Alice burns a token", async () => {
+    await program.rpc.burn(new BN(1), {
+      accounts: {
+        source: aliceTokenKeypair.publicKey,
+        mint: mintKeypair.publicKey,
+        authority: program.provider.wallet.publicKey,
+      },
+    });
+    const aliceToken = await program.account.token.fetch(
+      aliceTokenKeypair.publicKey
+    );
+    const mint = await program.account.mint.fetch(mintKeypair.publicKey);
+    assert.ok(aliceToken.amount.toNumber() === 0);
+    assert.ok(mint.supply.toNumber() === 1);
+  });
+});

+ 10 - 0
tests/custom-coder/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true
+  }
+}

+ 1 - 0
tests/package.json

@@ -10,6 +10,7 @@
     "cfo",
     "chat",
     "composite",
+    "custom-coder",
     "errors",
     "escrow",
     "events",

+ 4 - 4
tests/yarn.lock

@@ -50,10 +50,10 @@
     snake-case "^3.0.4"
     toml "^3.0.0"
 
-"@project-serum/anchor@^0.19.0":
-  version "0.19.0"
-  resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.19.0.tgz#79f1fbe7c3134860ccbfe458a0e09daf79644885"
-  integrity sha512-cs0LBmJOrL9eJ8MRNqitnzbpCT5QEzVdJmiIjfNV5YaGn1K9vISR7DtISj3Bdl3KBdLqii4CTw1mpHdi8iXUCg==
+"@project-serum/anchor@^0.20.0":
+  version "0.20.0"
+  resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.20.0.tgz#547f5c0ff7e66809fa7118b2e3abd8087b5ec519"
+  integrity sha512-p1KOiqGBIbNsopMrSVoPwgxR1iPffsdjMNCOysahTPL9whX2CLX9HQCdopHjYaGl7+SdHRuXml6Wahk/wUmC8g==
   dependencies:
     "@project-serum/borsh" "^0.2.2"
     "@solana/web3.js" "^1.17.0"

+ 41 - 6
ts/src/coder/accounts.ts → ts/src/coder/borsh/accounts.ts

@@ -1,9 +1,12 @@
+import bs58 from "bs58";
 import { Buffer } from "buffer";
 import { Layout } from "buffer-layout";
-import { Idl } from "../idl.js";
-import { IdlCoder } from "./idl.js";
-import { sha256 } from "js-sha256";
 import camelcase from "camelcase";
+import { sha256 } from "js-sha256";
+import { Idl, IdlTypeDef } from "../../idl.js";
+import { IdlCoder } from "./idl.js";
+import { AccountsCoder } from "../index.js";
+import { accountSize } from "../common.js";
 
 /**
  * Number of bytes of the account discriminator.
@@ -13,12 +16,18 @@ export const ACCOUNT_DISCRIMINATOR_SIZE = 8;
 /**
  * Encodes and decodes account objects.
  */
-export class AccountsCoder<A extends string = string> {
+export class BorshAccountsCoder<A extends string = string>
+  implements AccountsCoder {
   /**
    * Maps account type identifier to a layout.
    */
   private accountLayouts: Map<A, Layout>;
 
+  /**
+   * IDL whose acconts will be coded.
+   */
+  private idl: Idl;
+
   public constructor(idl: Idl) {
     if (idl.accounts === undefined) {
       this.accountLayouts = new Map();
@@ -29,6 +38,7 @@ export class AccountsCoder<A extends string = string> {
     });
 
     this.accountLayouts = new Map(layouts);
+    this.idl = idl;
   }
 
   public async encode<T = any>(accountName: A, account: T): Promise<Buffer> {
@@ -39,11 +49,20 @@ export class AccountsCoder<A extends string = string> {
     }
     const len = layout.encode(account, buffer);
     let accountData = buffer.slice(0, len);
-    let discriminator = AccountsCoder.accountDiscriminator(accountName);
+    let discriminator = BorshAccountsCoder.accountDiscriminator(accountName);
     return Buffer.concat([discriminator, accountData]);
   }
 
-  public decode<T = any>(accountName: A, ix: Buffer): T {
+  public decode<T = any>(accountName: A, data: Buffer): T {
+    // Assert the account discriminator is correct.
+    const discriminator = BorshAccountsCoder.accountDiscriminator(accountName);
+    if (discriminator.compare(data.slice(0, 8))) {
+      throw new Error("Invalid account discriminator");
+    }
+    return this.decodeUnchecked(accountName, data);
+  }
+
+  public decodeUnchecked<T = any>(accountName: A, ix: Buffer): T {
     // Chop off the discriminator before decoding.
     const data = ix.slice(ACCOUNT_DISCRIMINATOR_SIZE);
     const layout = this.accountLayouts.get(accountName);
@@ -53,6 +72,22 @@ export class AccountsCoder<A extends string = string> {
     return layout.decode(data);
   }
 
+  public memcmp(accountName: A, appendData?: Buffer): any {
+    const discriminator = BorshAccountsCoder.accountDiscriminator(accountName);
+    return {
+      offset: 0,
+      bytes: bs58.encode(
+        appendData ? Buffer.concat([discriminator, appendData]) : discriminator
+      ),
+    };
+  }
+
+  public size(idlAccount: IdlTypeDef): number {
+    return (
+      ACCOUNT_DISCRIMINATOR_SIZE + (accountSize(this.idl, idlAccount) ?? 0)
+    );
+  }
+
   /**
    * Calculates and returns a unique 8 byte discriminator prepended to all anchor accounts.
    *

+ 4 - 3
ts/src/coder/event.ts → ts/src/coder/borsh/event.ts

@@ -2,11 +2,12 @@ import { Buffer } from "buffer";
 import * as base64 from "base64-js";
 import { Layout } from "buffer-layout";
 import { sha256 } from "js-sha256";
-import { Idl, IdlEvent, IdlTypeDef } from "../idl.js";
-import { Event, EventData } from "../program/event.js";
+import { Idl, IdlEvent, IdlTypeDef } from "../../idl.js";
+import { Event, EventData } from "../../program/event.js";
 import { IdlCoder } from "./idl.js";
+import { EventCoder } from "../index.js";
 
-export class EventCoder {
+export class BorshEventCoder implements EventCoder {
   /**
    * Maps account type identifier to a layout.
    */

+ 2 - 2
ts/src/coder/idl.ts → ts/src/coder/borsh/idl.ts

@@ -1,8 +1,8 @@
 import camelCase from "camelcase";
 import { Layout } from "buffer-layout";
 import * as borsh from "@project-serum/borsh";
-import { IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl.js";
-import { IdlError } from "../error.js";
+import { IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../../idl.js";
+import { IdlError } from "../../error.js";
 
 export class IdlCoder {
   public static fieldLayout(

+ 46 - 0
ts/src/coder/borsh/index.ts

@@ -0,0 +1,46 @@
+import { Idl } from "../../idl.js";
+import { BorshInstructionCoder } from "./instruction.js";
+import { BorshAccountsCoder } from "./accounts.js";
+import { BorshEventCoder } from "./event.js";
+import { BorshStateCoder } from "./state.js";
+import { Coder } from "../index.js";
+
+export { BorshInstructionCoder } from "./instruction.js";
+export { BorshAccountsCoder, ACCOUNT_DISCRIMINATOR_SIZE } from "./accounts.js";
+export { BorshEventCoder, eventDiscriminator } from "./event.js";
+export { BorshStateCoder, stateDiscriminator } from "./state.js";
+
+/**
+ * BorshCoder is the default Coder for Anchor programs implementing the
+ * borsh based serialization interface.
+ */
+export class BorshCoder<A extends string = string> implements Coder {
+  /**
+   * Instruction coder.
+   */
+  readonly instruction: BorshInstructionCoder;
+
+  /**
+   * Account coder.
+   */
+  readonly accounts: BorshAccountsCoder<A>;
+
+  /**
+   * Coder for state structs.
+   */
+  readonly state: BorshStateCoder;
+
+  /**
+   * Coder for events.
+   */
+  readonly events: BorshEventCoder;
+
+  constructor(idl: Idl) {
+    this.instruction = new BorshInstructionCoder(idl);
+    this.accounts = new BorshAccountsCoder(idl);
+    this.events = new BorshEventCoder(idl);
+    if (idl.state) {
+      this.state = new BorshStateCoder(idl);
+    }
+  }
+}

+ 19 - 9
ts/src/coder/instruction.ts → ts/src/coder/borsh/instruction.ts

@@ -1,8 +1,11 @@
+import bs58 from "bs58";
 import { Buffer } from "buffer";
-import camelCase from "camelcase";
 import { Layout } from "buffer-layout";
+import camelCase from "camelcase";
+import { snakeCase } from "snake-case";
+import { sha256 } from "js-sha256";
 import * as borsh from "@project-serum/borsh";
-import bs58 from "bs58";
+import { AccountMeta, PublicKey } from "@solana/web3.js";
 import {
   Idl,
   IdlField,
@@ -16,10 +19,9 @@ import {
   IdlTypeOption,
   IdlTypeDefined,
   IdlAccounts,
-} from "../idl";
+} from "../../idl.js";
 import { IdlCoder } from "./idl.js";
-import { sighash } from "./common.js";
-import { AccountMeta, PublicKey } from "@solana/web3.js";
+import { InstructionCoder } from "../index.js";
 
 /**
  * Namespace for state method function signatures.
@@ -34,7 +36,7 @@ export const SIGHASH_GLOBAL_NAMESPACE = "global";
 /**
  * Encodes and decodes program instructions.
  */
-export class InstructionCoder {
+export class BorshInstructionCoder implements InstructionCoder {
   // Instruction args layout. Maps namespaced method
   private ixLayout: Map<string, Layout>;
 
@@ -42,7 +44,7 @@ export class InstructionCoder {
   private sighashLayouts: Map<string, { layout: Layout; name: string }>;
 
   public constructor(private idl: Idl) {
-    this.ixLayout = InstructionCoder.parseIxLayout(idl);
+    this.ixLayout = BorshInstructionCoder.parseIxLayout(idl);
 
     const sighashLayouts = new Map();
     idl.instructions.forEach((ix) => {
@@ -69,14 +71,14 @@ export class InstructionCoder {
   /**
    * Encodes a program instruction.
    */
-  public encode(ixName: string, ix: any) {
+  public encode(ixName: string, ix: any): Buffer {
     return this._encode(SIGHASH_GLOBAL_NAMESPACE, ixName, ix);
   }
 
   /**
    * Encodes a program state instruction.
    */
-  public encodeState(ixName: string, ix: any) {
+  public encodeState(ixName: string, ix: any): Buffer {
     return this._encode(SIGHASH_STATE_NAMESPACE, ixName, ix);
   }
 
@@ -379,3 +381,11 @@ function sentenceCase(field: string): string {
   const result = field.replace(/([A-Z])/g, " $1");
   return result.charAt(0).toUpperCase() + result.slice(1);
 }
+
+// Not technically sighash, since we don't include the arguments, as Rust
+// doesn't allow function overloading.
+function sighash(nameSpace: string, ixName: string): Buffer {
+  let name = snakeCase(ixName);
+  let preimage = `${nameSpace}:${name}`;
+  return Buffer.from(sha256.digest(preimage)).slice(0, 8);
+}

+ 3 - 3
ts/src/coder/state.ts → ts/src/coder/borsh/state.ts

@@ -1,11 +1,11 @@
 import { Buffer } from "buffer";
 import { Layout } from "buffer-layout";
 import { sha256 } from "js-sha256";
-import { Idl } from "../idl.js";
+import { Idl } from "../../idl.js";
 import { IdlCoder } from "./idl.js";
-import * as features from "../utils/features.js";
+import * as features from "../../utils/features.js";
 
-export class StateCoder {
+export class BorshStateCoder {
   private layout: Layout;
 
   public constructor(idl: Idl) {

+ 3 - 11
ts/src/coder/common.ts

@@ -1,6 +1,3 @@
-import { Buffer } from "buffer";
-import { snakeCase } from "snake-case";
-import { sha256 } from "js-sha256";
 import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl.js";
 import { IdlError } from "../error.js";
 
@@ -70,6 +67,9 @@ function typeSize(idl: Idl, ty: IdlType): number {
       if ("option" in ty) {
         return 1 + typeSize(idl, ty.option);
       }
+      if ("coption" in ty) {
+        return 4 + typeSize(idl, ty.coption);
+      }
       if ("defined" in ty) {
         const filtered = idl.types?.filter((t) => t.name === ty.defined) ?? [];
         if (filtered.length !== 1) {
@@ -87,11 +87,3 @@ function typeSize(idl: Idl, ty: IdlType): number {
       throw new Error(`Invalid type ${JSON.stringify(ty)}`);
   }
 }
-
-// Not technically sighash, since we don't include the arguments, as Rust
-// doesn't allow function overloading.
-export function sighash(nameSpace: string, ixName: string): Buffer {
-  let name = snakeCase(ixName);
-  let preimage = `${nameSpace}:${name}`;
-  return Buffer.from(sha256.digest(preimage)).slice(0, 8);
-}

+ 30 - 26
ts/src/coder/index.ts

@@ -1,20 +1,13 @@
-import { Idl } from "../idl.js";
-import { InstructionCoder } from "./instruction.js";
-import { AccountsCoder } from "./accounts.js";
-import { EventCoder } from "./event.js";
-import { StateCoder } from "./state.js";
-import { sighash } from "./common.js";
-
-export { accountSize } from "./common.js";
-export { InstructionCoder } from "./instruction.js";
-export { AccountsCoder, ACCOUNT_DISCRIMINATOR_SIZE } from "./accounts.js";
-export { EventCoder, eventDiscriminator } from "./event.js";
-export { StateCoder, stateDiscriminator } from "./state.js";
+import { IdlEvent, IdlTypeDef } from "../idl.js";
+import { Event } from "../program/event.js";
+
+export * from "./borsh/index.js";
+export * from "./spl-token/index.js";
 
 /**
  * Coder provides a facade for encoding and decoding all IDL related objects.
  */
-export default class Coder<A extends string = string> {
+export interface Coder {
   /**
    * Instruction coder.
    */
@@ -23,7 +16,7 @@ export default class Coder<A extends string = string> {
   /**
    * Account coder.
    */
-  readonly accounts: AccountsCoder<A>;
+  readonly accounts: AccountsCoder;
 
   /**
    * Coder for state structs.
@@ -34,17 +27,28 @@ export default class Coder<A extends string = string> {
    * Coder for events.
    */
   readonly events: EventCoder;
+}
+
+export interface StateCoder {
+  encode<T = any>(name: string, account: T): Promise<Buffer>;
+  decode<T = any>(ix: Buffer): T;
+}
+
+export interface AccountsCoder<A extends string = string> {
+  encode<T = any>(accountName: A, account: T): Promise<Buffer>;
+  decode<T = any>(accountName: A, ix: Buffer): T;
+  decodeUnchecked<T = any>(accountName: A, ix: Buffer): T;
+  memcmp(accountName: A, appendData?: Buffer): any;
+  size(idlAccount: IdlTypeDef): number;
+}
+
+export interface InstructionCoder {
+  encode(ixName: string, ix: any): Buffer;
+  encodeState(ixName: string, ix: any): Buffer;
+}
 
-  constructor(idl: Idl) {
-    this.instruction = new InstructionCoder(idl);
-    this.accounts = new AccountsCoder(idl);
-    this.events = new EventCoder(idl);
-    if (idl.state) {
-      this.state = new StateCoder(idl);
-    }
-  }
-
-  public sighash(nameSpace: string, ixName: string): Buffer {
-    return sighash(nameSpace, ixName);
-  }
+export interface EventCoder {
+  decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
+    log: string
+  ): Event<E, T> | null;
 }

+ 96 - 0
ts/src/coder/spl-token/accounts.ts

@@ -0,0 +1,96 @@
+import * as BufferLayout from "buffer-layout";
+import { publicKey, uint64, coption, bool } from "./buffer-layout.js";
+import { AccountsCoder } from "../index.js";
+import { Idl, IdlTypeDef } from "../../idl.js";
+import { accountSize } from "../common";
+
+export class SplTokenAccountsCoder<A extends string = string>
+  implements AccountsCoder {
+  constructor(private idl: Idl) {}
+
+  public async encode<T = any>(accountName: A, account: T): Promise<Buffer> {
+    switch (accountName) {
+      case "Token": {
+        const buffer = Buffer.alloc(165);
+        const len = TOKEN_ACCOUNT_LAYOUT.encode(account, buffer);
+        return buffer.slice(0, len);
+      }
+      case "Mint": {
+        const buffer = Buffer.alloc(82);
+        const len = MINT_ACCOUNT_LAYOUT.encode(account, buffer);
+        return buffer.slice(0, len);
+      }
+      default: {
+        throw new Error(`Invalid account name: ${accountName}`);
+      }
+    }
+  }
+
+  public decode<T = any>(accountName: A, ix: Buffer): T {
+    return this.decodeUnchecked(accountName, ix);
+  }
+
+  public decodeUnchecked<T = any>(accountName: A, ix: Buffer): T {
+    switch (accountName) {
+      case "Token": {
+        return decodeTokenAccount(ix);
+      }
+      case "Mint": {
+        return decodeMintAccount(ix);
+      }
+      default: {
+        throw new Error(`Invalid account name: ${accountName}`);
+      }
+    }
+  }
+
+  // TODO: this won't use the appendData.
+  public memcmp(accountName: A, _appendData?: Buffer): any {
+    switch (accountName) {
+      case "Token": {
+        return {
+          dataSize: 165,
+        };
+      }
+      case "Mint": {
+        return {
+          dataSize: 82,
+        };
+      }
+      default: {
+        throw new Error(`Invalid account name: ${accountName}`);
+      }
+    }
+  }
+
+  public size(idlAccount: IdlTypeDef): number {
+    return accountSize(this.idl, idlAccount) ?? 0;
+  }
+}
+
+function decodeMintAccount<T = any>(ix: Buffer): T {
+  return MINT_ACCOUNT_LAYOUT.decode(ix) as T;
+}
+
+function decodeTokenAccount<T = any>(ix: Buffer): T {
+  return TOKEN_ACCOUNT_LAYOUT.decode(ix) as T;
+}
+
+const MINT_ACCOUNT_LAYOUT = BufferLayout.struct([
+  coption(publicKey(), "mintAuthority"),
+  uint64("supply"),
+  BufferLayout.u8("decimals"),
+  bool("isInitialized"),
+  coption(publicKey(), "freezeAuthority"),
+]);
+
+const TOKEN_ACCOUNT_LAYOUT = BufferLayout.struct([
+  publicKey("mint"),
+  publicKey("authority"),
+  uint64("amount"),
+  coption(publicKey(), "delegate"),
+  BufferLayout.u8("state"),
+  coption(uint64(), "isNative"),
+  uint64("delegatedAmount"),
+  coption(publicKey(), "closeAuthority"),
+]);

+ 151 - 0
ts/src/coder/spl-token/buffer-layout.ts

@@ -0,0 +1,151 @@
+import BN from "bn.js";
+import * as BufferLayout from "buffer-layout";
+import { Layout } from "buffer-layout";
+import { PublicKey } from "@solana/web3.js";
+
+export function uint64(property?: string): Layout<u64 | null> {
+  return new WrappedLayout(
+    BufferLayout.blob(8),
+    (b: Buffer) => u64.fromBuffer(b),
+    (n: u64) => n.toBuffer(),
+    property
+  );
+}
+
+export function bool(property?: string): Layout<boolean> {
+  return new WrappedLayout(BufferLayout.u8(), decodeBool, encodeBool, property);
+}
+
+export function publicKey(property?: string): Layout<PublicKey> {
+  return new WrappedLayout(
+    BufferLayout.blob(32),
+    (b: Buffer) => new PublicKey(b),
+    (key: PublicKey) => key.toBuffer(),
+    property
+  );
+}
+
+export function coption<T>(
+  layout: Layout<T>,
+  property?: string
+): Layout<T | null> {
+  return new COptionLayout<T>(layout, property);
+}
+
+class WrappedLayout<T, U> extends Layout<U> {
+  layout: Layout<T>;
+  decoder: (data: T) => U;
+  encoder: (src: U) => T;
+
+  constructor(
+    layout: Layout<T>,
+    decoder: (data: T) => U,
+    encoder: (src: U) => T,
+    property?: string
+  ) {
+    super(layout.span, property);
+    this.layout = layout;
+    this.decoder = decoder;
+    this.encoder = encoder;
+  }
+
+  decode(b: Buffer, offset?: number): U {
+    return this.decoder(this.layout.decode(b, offset));
+  }
+
+  encode(src: U, b: Buffer, offset?: number): number {
+    return this.layout.encode(this.encoder(src), b, offset);
+  }
+
+  getSpan(b: Buffer, offset?: number): number {
+    return this.layout.getSpan(b, offset);
+  }
+}
+
+export class COptionLayout<T> extends Layout<T | null> {
+  layout: Layout<T>;
+  discriminator: Layout<number>;
+
+  constructor(layout: Layout<T>, property?: string) {
+    super(-1, property);
+    this.layout = layout;
+    this.discriminator = BufferLayout.u32();
+  }
+
+  encode(src: T | null, b: Buffer, offset = 0): number {
+    if (src === null || src === undefined) {
+      return this.discriminator.encode(0, b, offset);
+    }
+    this.discriminator.encode(1, b, offset);
+    return this.layout.encode(src, b, offset + 4) + 4;
+  }
+
+  decode(b: Buffer, offset = 0): T | null {
+    const discriminator = b[offset];
+    if (discriminator === 0) {
+      return null;
+    } else if (discriminator === 1) {
+      return this.layout.decode(b, offset + 4);
+    }
+    throw new Error("Invalid option " + this.property);
+  }
+
+  getSpan(b: Buffer, offset = 0): number {
+    const discriminator = b[offset];
+    if (discriminator === 0) {
+      return 1;
+    } else if (discriminator === 1) {
+      return this.layout.getSpan(b, offset + 4) + 4;
+    }
+    throw new Error("Invalid option " + this.property);
+  }
+}
+
+function decodeBool(value: number): boolean {
+  if (value === 0) {
+    return false;
+  } else if (value === 1) {
+    return true;
+  }
+  throw new Error("Invalid bool: " + value);
+}
+
+function encodeBool(value: boolean): number {
+  return value ? 1 : 0;
+}
+
+export class u64 extends BN {
+  /**
+   * Convert to Buffer representation
+   */
+  toBuffer(): Buffer {
+    const a = super.toArray().reverse();
+    const b = Buffer.from(a);
+    if (b.length === 8) {
+      return b;
+    }
+    if (b.length >= 8) {
+      throw new Error("u64 too large");
+    }
+
+    const zeroPad = Buffer.alloc(8);
+    b.copy(zeroPad);
+    return zeroPad;
+  }
+
+  /**
+   * Construct a u64 from Buffer representation
+   */
+  static fromBuffer(buffer: Buffer): u64 {
+    if (buffer.length !== 8) {
+      throw new Error(`Invalid buffer length: ${buffer.length}`);
+    }
+    return new u64(
+      [...buffer]
+        .reverse()
+        .map((i) => `00${i.toString(16)}`.slice(-2))
+        .join(""),
+      16
+    );
+  }
+}

+ 14 - 0
ts/src/coder/spl-token/events.ts

@@ -0,0 +1,14 @@
+import { EventCoder } from "../index.js";
+import { Idl } from "../../idl.js";
+import { Event } from "../../program/event";
+import { IdlEvent } from "../../idl";
+
+export class SplTokenEventsCoder implements EventCoder {
+  constructor(_idl: Idl) {}
+
+  decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
+    _log: string
+  ): Event<E, T> | null {
+    throw new Error("SPL token program does not have events");
+  }
+}

+ 23 - 0
ts/src/coder/spl-token/index.ts

@@ -0,0 +1,23 @@
+import { Idl } from "../../idl.js";
+import { Coder } from "../index.js";
+import { SplTokenInstructionCoder } from "./instruction.js";
+import { SplTokenStateCoder } from "./state.js";
+import { SplTokenAccountsCoder } from "./accounts.js";
+import { SplTokenEventsCoder } from "./events.js";
+
+/**
+ * Coder for the SPL token program.
+ */
+export class SplTokenCoder implements Coder {
+  readonly instruction: SplTokenInstructionCoder;
+  readonly accounts: SplTokenAccountsCoder;
+  readonly state: SplTokenStateCoder;
+  readonly events: SplTokenEventsCoder;
+
+  constructor(idl: Idl) {
+    this.instruction = new SplTokenInstructionCoder(idl);
+    this.accounts = new SplTokenAccountsCoder(idl);
+    this.events = new SplTokenEventsCoder(idl);
+    this.state = new SplTokenStateCoder(idl);
+  }
+}

+ 349 - 0
ts/src/coder/spl-token/instruction.ts

@@ -0,0 +1,349 @@
+import * as BufferLayout from "buffer-layout";
+import camelCase from "camelcase";
+import { PublicKey } from "@solana/web3.js";
+import { InstructionCoder } from "../index.js";
+import { Idl } from "../../idl.js";
+
+export class SplTokenInstructionCoder implements InstructionCoder {
+  constructor(_: Idl) {}
+
+  encode(ixName: string, ix: any): Buffer {
+    switch (camelCase(ixName)) {
+      case "initializeMint": {
+        return encodeInitializeMint(ix);
+      }
+      case "initializeAccount": {
+        return encodeInitializeAccount(ix);
+      }
+      case "initializeMultisig": {
+        return encodeInitializeMultisig(ix);
+      }
+      case "transfer": {
+        return encodeTransfer(ix);
+      }
+      case "approve": {
+        return encodeApprove(ix);
+      }
+      case "revoke": {
+        return encodeRevoke(ix);
+      }
+      case "setAuthority": {
+        return encodeSetAuthority(ix);
+      }
+      case "mintTo": {
+        return encodeMintTo(ix);
+      }
+      case "burn": {
+        return encodeBurn(ix);
+      }
+      case "closeAccount": {
+        return encodeCloseAccount(ix);
+      }
+      case "freezeAccount": {
+        return encodeFreezeAccount(ix);
+      }
+      case "thawAccount": {
+        return encodeThawAccount(ix);
+      }
+      case "transferChecked": {
+        return encodeTransferChecked(ix);
+      }
+      case "approvedChecked": {
+        return encodeApproveChecked(ix);
+      }
+      case "mintToChecked": {
+        return encodeMintToChecked(ix);
+      }
+      case "burnChecked": {
+        return encodeBurnChecked(ix);
+      }
+      case "intializeAccount2": {
+        return encodeInitializeAccount2(ix);
+      }
+      case "syncNative": {
+        return encodeSyncNative(ix);
+      }
+      case "initializeAccount3": {
+        return encodeInitializeAccount3(ix);
+      }
+      case "initializeMultisig2": {
+        return encodeInitializeMultisig2(ix);
+      }
+      case "initializeMint2": {
+        return encodeInitializeMint2(ix);
+      }
+      default: {
+        throw new Error(`Invalid instruction: ${ixName}`);
+      }
+    }
+  }
+
+  encodeState(_ixName: string, _ix: any): Buffer {
+    throw new Error("SPL token does not have state");
+  }
+}
+
+function encodeInitializeMint({
+  decimals,
+  mintAuthority,
+  freezeAuthority,
+}: any): Buffer {
+  return encodeData({
+    initializeMint: {
+      decimals,
+      mintAuthority: mintAuthority.toBuffer(),
+      freezeAuthorityOption: !!freezeAuthority,
+      freezeAuthority: (freezeAuthority || PublicKey.default).toBuffer(),
+    },
+  });
+}
+
+function encodeInitializeAccount(_ix: any): Buffer {
+  return encodeData({
+    initializeAccount: {},
+  });
+}
+
+function encodeInitializeMultisig({ m }: any): Buffer {
+  return encodeData({
+    initializeMultisig: {
+      m,
+    },
+  });
+}
+
+function encodeTransfer({ amount }: any): Buffer {
+  return encodeData({
+    transfer: { amount },
+  });
+}
+
+function encodeApprove({ amount }: any): Buffer {
+  return encodeData({
+    approve: { amount },
+  });
+}
+
+function encodeRevoke(_ix: any): Buffer {
+  return encodeData({
+    revoke: {},
+  });
+}
+
+function encodeSetAuthority({ authorityType, newAuthority }: any): Buffer {
+  return encodeData({
+    setAuthority: { authorityType, newAuthority },
+  });
+}
+
+function encodeMintTo({ amount }: any): Buffer {
+  return encodeData({
+    mintTo: { amount },
+  });
+}
+
+function encodeBurn({ amount }: any): Buffer {
+  return encodeData({
+    burn: { amount },
+  });
+}
+
+function encodeCloseAccount(_: any): Buffer {
+  return encodeData({
+    closeAccount: {},
+  });
+}
+
+function encodeFreezeAccount(_: any): Buffer {
+  return encodeData({
+    freezeAccount: {},
+  });
+}
+
+function encodeThawAccount(_: any): Buffer {
+  return encodeData({
+    thawAccount: {},
+  });
+}
+
+function encodeTransferChecked({ amount, decimals }: any): Buffer {
+  return encodeData({
+    transferChecked: { amount, decimals },
+  });
+}
+
+function encodeApproveChecked({ amount, decimals }: any): Buffer {
+  return encodeData({
+    approveChecked: { amount, decimals },
+  });
+}
+
+function encodeMintToChecked({ amount, decimals }: any): Buffer {
+  return encodeData({
+    mintToChecked: { amount, decimals },
+  });
+}
+
+function encodeBurnChecked({ amount, decimals }: any): Buffer {
+  return encodeData({
+    burnChecked: { amount, decimals },
+  });
+}
+
+function encodeInitializeAccount2({ authority }: any): Buffer {
+  return encodeData({
+    initilaizeAccount2: { authority },
+  });
+}
+
+function encodeSyncNative(_: any): Buffer {
+  return encodeData({
+    syncNative: {},
+  });
+}
+
+function encodeInitializeAccount3({ authority }: any): Buffer {
+  return encodeData({
+    initializeAccount3: { authority },
+  });
+}
+
+function encodeInitializeMultisig2({ m }: any): Buffer {
+  return encodeData({
+    initializeMultisig2: { m },
+  });
+}
+
+function encodeInitializeMint2({
+  decimals,
+  mintAuthority,
+  freezeAuthority,
+}: any): Buffer {
+  return encodeData({
+    encodeInitializeMint2: { decimals, mintAuthority, freezeAuthority },
+  });
+}
+
+const LAYOUT = BufferLayout.union(BufferLayout.u8("instruction"));
+LAYOUT.addVariant(
+  0,
+  BufferLayout.struct([
+    BufferLayout.u8("decimals"),
+    BufferLayout.blob(32, "mintAuthority"),
+    BufferLayout.u8("freezeAuthorityOption"),
+    publicKey("freezeAuthority"),
+  ]),
+  "initializeMint"
+);
+LAYOUT.addVariant(1, BufferLayout.struct([]), "initializeAccount");
+LAYOUT.addVariant(
+  2,
+  BufferLayout.struct([BufferLayout.u8("m")]),
+  "initializeMultisig"
+);
+LAYOUT.addVariant(
+  3,
+  BufferLayout.struct([BufferLayout.nu64("amount")]),
+  "transfer"
+);
+LAYOUT.addVariant(
+  4,
+  BufferLayout.struct([BufferLayout.nu64("amount")]),
+  "approve"
+);
+LAYOUT.addVariant(5, BufferLayout.struct([]), "revoke");
+LAYOUT.addVariant(
+  6,
+  BufferLayout.struct([
+    BufferLayout.u8("authorityType"),
+    BufferLayout.u8("newAuthorityOption"),
+    publicKey("newAuthority"),
+  ]),
+  "setAuthority"
+);
+LAYOUT.addVariant(
+  7,
+  BufferLayout.struct([BufferLayout.nu64("amount")]),
+  "mintTo"
+);
+LAYOUT.addVariant(
+  8,
+  BufferLayout.struct([BufferLayout.nu64("amount")]),
+  "burn"
+);
+LAYOUT.addVariant(9, BufferLayout.struct([]), "closeAccount");
+LAYOUT.addVariant(10, BufferLayout.struct([]), "freezeAccount");
+LAYOUT.addVariant(11, BufferLayout.struct([]), "thawAccount");
+LAYOUT.addVariant(
+  12,
+  BufferLayout.struct([
+    BufferLayout.nu64("amount"),
+    BufferLayout.u8("decimals"),
+  ]),
+  "transferChecked"
+);
+LAYOUT.addVariant(
+  13,
+  BufferLayout.struct([
+    BufferLayout.nu64("amount"),
+    BufferLayout.u8("decimals"),
+  ]),
+  "approvedChecked"
+);
+LAYOUT.addVariant(
+  14,
+  BufferLayout.struct([
+    BufferLayout.nu64("amount"),
+    BufferLayout.u8("decimals"),
+  ]),
+  "mintToChecked"
+);
+LAYOUT.addVariant(
+  15,
+  BufferLayout.struct([
+    BufferLayout.nu64("amount"),
+    BufferLayout.u8("decimals"),
+  ]),
+  "burnedChecked"
+);
+LAYOUT.addVariant(
+  16,
+  BufferLayout.struct([publicKey("authority")]),
+  "InitializeAccount2"
+);
+LAYOUT.addVariant(17, BufferLayout.struct([]), "syncNative");
+LAYOUT.addVariant(
+  18,
+  BufferLayout.struct([publicKey("authority")]),
+  "initializeAccount3"
+);
+LAYOUT.addVariant(
+  19,
+  BufferLayout.struct([BufferLayout.u8("m")]),
+  "initializeMultisig2"
+);
+LAYOUT.addVariant(
+  20,
+  BufferLayout.struct([
+    BufferLayout.u8("decimals"),
+    publicKey("mintAuthority"),
+    BufferLayout.u8("freezeAuthorityOption"),
+    publicKey("freezeAuthority"),
+  ]),
+  "initializeMint2"
+);
+
+function publicKey(property: string): any {
+  return BufferLayout.blob(32, property);
+}
+
+function encodeData(instruction: any): Buffer {
+  let b = Buffer.alloc(instructionMaxSpan);
+  let span = LAYOUT.encode(instruction, b);
+  return b.slice(0, span);
+}
+
+const instructionMaxSpan = Math.max(
+  // @ts-ignore
+  ...Object.values(LAYOUT.registry).map((r) => r.span)
+);

+ 13 - 0
ts/src/coder/spl-token/state.ts

@@ -0,0 +1,13 @@
+import { StateCoder } from "../index.js";
+import { Idl } from "../../idl";
+
+export class SplTokenStateCoder implements StateCoder {
+  constructor(_idl: Idl) {}
+
+  encode<T = any>(_name: string, _account: T): Promise<Buffer> {
+    throw new Error("SPL token does not have state");
+  }
+  decode<T = any>(_ix: Buffer): T {
+    throw new Error("SPL token does not have state");
+  }
+}

+ 5 - 0
ts/src/idl.ts

@@ -99,6 +99,7 @@ export type IdlType =
   | "publicKey"
   | IdlTypeDefined
   | IdlTypeOption
+  | IdlTypeCOption
   | IdlTypeVec
   | IdlTypeArray;
 
@@ -111,6 +112,10 @@ export type IdlTypeOption = {
   option: IdlType;
 };
 
+export type IdlTypeCOption = {
+  coption: IdlType;
+};
+
 export type IdlTypeVec = {
   vec: IdlType;
 };

+ 3 - 10
ts/src/index.ts

@@ -3,20 +3,13 @@ import { isBrowser } from "./utils/common.js";
 export { default as BN } from "bn.js";
 export * as web3 from "@solana/web3.js";
 export { default as Provider, getProvider, setProvider } from "./provider.js";
-export {
-  default as Coder,
-  InstructionCoder,
-  EventCoder,
-  StateCoder,
-  AccountsCoder,
-} from "./coder/index.js";
-
 export * from "./error.js";
-export { Instruction } from "./coder/instruction.js";
+export { Instruction } from "./coder/borsh/instruction.js";
 export { Idl } from "./idl.js";
-
+export * from "./coder/index.js";
 export * as utils from "./utils/index.js";
 export * from "./program/index.js";
+export * from "./spl/index.js";
 
 export declare const workspace: any;
 export declare const Wallet: import("./nodewallet").default;

+ 1 - 1
ts/src/program/event.ts

@@ -1,7 +1,7 @@
 import { PublicKey } from "@solana/web3.js";
 import * as assert from "assert";
 import { IdlEvent, IdlEventField } from "../idl.js";
-import Coder from "../coder/index.js";
+import { Coder } from "../coder/index.js";
 import { DecodeType } from "./namespace/types.js";
 import Provider from "../provider.js";
 

+ 8 - 3
ts/src/program/index.ts

@@ -2,7 +2,7 @@ import { inflate } from "pako";
 import { PublicKey } from "@solana/web3.js";
 import Provider, { getProvider } from "../provider.js";
 import { Idl, idlAddress, decodeIdlAccount } from "../idl.js";
-import Coder from "../coder/index.js";
+import { Coder, BorshCoder } from "../coder/index.js";
 import NamespaceFactory, {
   RpcNamespace,
   InstructionNamespace,
@@ -249,7 +249,12 @@ export class Program<IDL extends Idl = Idl> {
    * @param provider  The network and wallet context to use. If not provided
    *                  then uses [[getProvider]].
    */
-  public constructor(idl: IDL, programId: Address, provider?: Provider) {
+  public constructor(
+    idl: IDL,
+    programId: Address,
+    provider?: Provider,
+    coder?: Coder
+  ) {
     programId = translateAddress(programId);
 
     if (!provider) {
@@ -260,7 +265,7 @@ export class Program<IDL extends Idl = Idl> {
     this._idl = idl;
     this._provider = provider;
     this._programId = programId;
-    this._coder = new Coder(idl);
+    this._coder = coder ?? new BorshCoder(idl);
     this._events = new EventManager(this._programId, provider, this._coder);
 
     // Dynamic namespaces.

+ 7 - 37
ts/src/program/namespace/account.ts

@@ -1,7 +1,5 @@
-import { Buffer } from "buffer";
 import camelCase from "camelcase";
 import EventEmitter from "eventemitter3";
-import bs58 from "bs58";
 import {
   Signer,
   PublicKey,
@@ -13,11 +11,7 @@ import {
 } from "@solana/web3.js";
 import Provider, { getProvider } from "../../provider.js";
 import { Idl, IdlTypeDef } from "../../idl.js";
-import Coder, {
-  ACCOUNT_DISCRIMINATOR_SIZE,
-  accountSize,
-  AccountsCoder,
-} from "../../coder/index.js";
+import { Coder, BorshCoder } from "../../coder/index.js";
 import { Subscription, Address, translateAddress } from "../common.js";
 import { AllAccountsMap, IdlTypes, TypeDef } from "./types.js";
 import * as pubkeyUtil from "../../utils/pubkey.js";
@@ -126,9 +120,8 @@ export class AccountClient<
     this._idlAccount = idlAccount;
     this._programId = programId;
     this._provider = provider ?? getProvider();
-    this._coder = coder ?? new Coder(idl);
-    this._size =
-      ACCOUNT_DISCRIMINATOR_SIZE + (accountSize(idl, idlAccount) ?? 0);
+    this._coder = coder ?? new BorshCoder(idl);
+    this._size = this._coder.accounts.size(idlAccount);
   }
 
   /**
@@ -144,15 +137,6 @@ export class AccountClient<
     if (accountInfo === null) {
       return null;
     }
-
-    // Assert the account discriminator is correct.
-    const discriminator = AccountsCoder.accountDiscriminator(
-      this._idlAccount.name
-    );
-    if (discriminator.compare(accountInfo.data.slice(0, 8))) {
-      throw new Error("Invalid account discriminator");
-    }
-
     return this._coder.accounts.decode<T>(
       this._idlAccount.name,
       accountInfo.data
@@ -188,17 +172,11 @@ export class AccountClient<
       commitment
     );
 
-    const discriminator = AccountsCoder.accountDiscriminator(
-      this._idlAccount.name
-    );
     // Decode accounts where discriminator is correct, null otherwise
     return accounts.map((account) => {
       if (account == null) {
         return null;
       }
-      if (discriminator.compare(account?.account.data.slice(0, 8))) {
-        return null;
-      }
       return this._coder.accounts.decode(
         this._idlAccount.name,
         account?.account.data
@@ -223,24 +201,16 @@ export class AccountClient<
   async all(
     filters?: Buffer | GetProgramAccountsFilter[]
   ): Promise<ProgramAccount<T>[]> {
-    const discriminator = AccountsCoder.accountDiscriminator(
-      this._idlAccount.name
-    );
-
     let resp = await this._provider.connection.getProgramAccounts(
       this._programId,
       {
         commitment: this._provider.connection.commitment,
         filters: [
           {
-            memcmp: {
-              offset: 0,
-              bytes: bs58.encode(
-                filters instanceof Buffer
-                  ? Buffer.concat([discriminator, filters])
-                  : discriminator
-              ),
-            },
+            memcmp: this.coder.accounts.memcmp(
+              this._idlAccount.name,
+              filters instanceof Buffer ? filters : undefined
+            ),
           },
           ...(Array.isArray(filters) ? filters : []),
         ],

+ 2 - 2
ts/src/program/namespace/index.ts

@@ -1,8 +1,8 @@
 import camelCase from "camelcase";
 import { PublicKey } from "@solana/web3.js";
-import Coder from "../../coder/index.js";
+import { Coder } from "../../coder/index.js";
 import Provider from "../../provider.js";
-import { Idl, IdlInstruction } from "../../idl.js";
+import { Idl } from "../../idl.js";
 import StateFactory, { StateClient } from "./state.js";
 import InstructionFactory, { InstructionNamespace } from "./instruction.js";
 import TransactionFactory, { TransactionNamespace } from "./transaction.js";

+ 1 - 1
ts/src/program/namespace/simulate.ts

@@ -7,7 +7,7 @@ import Provider from "../../provider.js";
 import { splitArgsAndCtx } from "../context.js";
 import { TransactionFn } from "./transaction.js";
 import { EventParser, Event } from "../event.js";
-import Coder from "../../coder/index.js";
+import { Coder } from "../../coder/index.js";
 import { Idl, IdlEvent } from "../../idl.js";
 import { ProgramError } from "../../error.js";
 import * as features from "../../utils/features.js";

+ 2 - 2
ts/src/program/namespace/state.ts

@@ -8,7 +8,7 @@ import {
 } from "@solana/web3.js";
 import Provider, { getProvider } from "../../provider.js";
 import { Idl, IdlInstruction, IdlStateMethod, IdlTypeDef } from "../../idl.js";
-import Coder, { stateDiscriminator } from "../../coder/index.js";
+import { BorshCoder, Coder, stateDiscriminator } from "../../coder/index.js";
 import {
   RpcNamespace,
   InstructionNamespace,
@@ -87,7 +87,7 @@ export class StateClient<IDL extends Idl> {
     /**
      * Returns the coder.
      */
-    public readonly coder: Coder = new Coder(idl)
+    public readonly coder: Coder = new BorshCoder(idl)
   ) {
     this._idl = idl;
     this._programId = programId;

+ 10 - 0
ts/src/spl/index.ts

@@ -0,0 +1,10 @@
+import { Program } from "../index.js";
+import { program as tokenProgram, SplToken } from "./token.js";
+
+export { SplToken } from "./token.js";
+
+export class Spl {
+  public static token(): Program<SplToken> {
+    return tokenProgram();
+  }
+}

+ 1230 - 0
ts/src/spl/token.ts

@@ -0,0 +1,1230 @@
+import { PublicKey } from "@solana/web3.js";
+import { Program } from "../program/index.js";
+import Provider from "../provider.js";
+import { SplTokenCoder } from "../coder/spl-token/index.js";
+
+const TOKEN_PROGRAM_ID = new PublicKey(
+  "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+);
+
+export function program(provider?: Provider): Program<SplToken> {
+  return new Program<SplToken>(
+    IDL,
+    TOKEN_PROGRAM_ID,
+    provider,
+    new SplTokenCoder(IDL)
+  );
+}
+
+/**
+ * SplToken IDL.
+ */
+export type SplToken = {
+  version: "0.1.0";
+  name: "spl_token";
+  instructions: [
+    {
+      name: "initializeMint";
+      accounts: [
+        {
+          name: "mint";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "rent";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "decimals";
+          type: "u8";
+        },
+        {
+          name: "mintAuthority";
+          type: "publicKey";
+        },
+        {
+          name: "freezeAuthority";
+          type: {
+            coption: "publicKey";
+          };
+        }
+      ];
+    },
+    {
+      name: "initializeAccount";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "rent";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "initializeMultisig";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "rent";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "m";
+          type: "u8";
+        }
+      ];
+    },
+    {
+      name: "transfer";
+      accounts: [
+        {
+          name: "source";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "destination";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "amount";
+          type: "u64";
+        }
+      ];
+    },
+    {
+      name: "approve";
+      accounts: [
+        {
+          name: "source";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "delegate";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "amount";
+          type: "u64";
+        }
+      ];
+    },
+    {
+      name: "revoke";
+      accounts: [
+        {
+          name: "source";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "setAuthority";
+      accounts: [
+        {
+          name: "mint";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "authorityType";
+          type: "u8";
+        },
+        {
+          name: "newAuthority";
+          type: {
+            coption: "publicKey";
+          };
+        }
+      ];
+    },
+    {
+      name: "mintTo";
+      accounts: [
+        {
+          name: "mint";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "to";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "amount";
+          type: "u64";
+        }
+      ];
+    },
+    {
+      name: "burn";
+      accounts: [
+        {
+          name: "source";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "amount";
+          type: "u64";
+        }
+      ];
+    },
+    {
+      name: "closeAccount";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "destination";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "freezeAccount";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "thawAccount";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "transferChecked";
+      accounts: [
+        {
+          name: "source";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "destination";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "amount";
+          type: "u64";
+        },
+        {
+          name: "decimals";
+          type: "u8";
+        }
+      ];
+    },
+    {
+      name: "approveChecked";
+      accounts: [
+        {
+          name: "source";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "delegate";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "amount";
+          type: "u64";
+        },
+        {
+          name: "decimals";
+          type: "u8";
+        }
+      ];
+    },
+    {
+      name: "mintToChecked";
+      accounts: [
+        {
+          name: "mint";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "to";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "amount";
+          type: "u64";
+        },
+        {
+          name: "decimals";
+          type: "u8";
+        }
+      ];
+    },
+    {
+      name: "burnChecked";
+      accounts: [
+        {
+          name: "source";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "authority";
+          isMut: false;
+          isSigner: true;
+        }
+      ];
+      args: [
+        {
+          name: "amount";
+          type: "u64";
+        },
+        {
+          name: "decimals";
+          type: "u8";
+        }
+      ];
+    },
+    {
+      name: "initializeAccount2";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "rent";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "authority";
+          type: "publicKey";
+        }
+      ];
+    },
+    {
+      name: "syncNative";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "initializeAccount3";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "authority";
+          type: "publicKey";
+        }
+      ];
+    },
+    {
+      name: "initializeMultisig2";
+      accounts: [
+        {
+          name: "account";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "m";
+          type: "u8";
+        }
+      ];
+    },
+    {
+      name: "initializeMint2";
+      accounts: [
+        {
+          name: "mint";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "decimals";
+          type: "u8";
+        },
+        {
+          name: "mintAuthority";
+          type: "publicKey";
+        },
+        {
+          name: "freezeAuthority";
+          type: {
+            coption: "publicKey";
+          };
+        }
+      ];
+    }
+  ];
+  accounts: [
+    {
+      name: "Mint";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "mintAuthority";
+            type: {
+              coption: "publicKey";
+            };
+          },
+          {
+            name: "supply";
+            type: "u64";
+          },
+          {
+            name: "decimals";
+            type: "u8";
+          },
+          {
+            name: "isInitialized";
+            type: "bool";
+          },
+          {
+            name: "freezeAuthority";
+            type: {
+              coption: "publicKey";
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "Token";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "mint";
+            type: "publicKey";
+          },
+          {
+            name: "authority";
+            type: "publicKey";
+          },
+          {
+            name: "amount";
+            type: "u64";
+          },
+          {
+            name: "delegate";
+            type: {
+              coption: "publicKey";
+            };
+          },
+          {
+            name: "state";
+            type: "u8";
+          },
+          {
+            name: "isNative";
+            type: {
+              coption: "u64";
+            };
+          },
+          {
+            name: "delegatedAmount";
+            type: "u64";
+          },
+          {
+            name: "closeAuthority";
+            type: {
+              coption: "publicKey";
+            };
+          }
+        ];
+      };
+    }
+  ];
+};
+
+export const IDL: SplToken = {
+  version: "0.1.0",
+  name: "spl_token",
+  instructions: [
+    {
+      name: "initializeMint",
+      accounts: [
+        {
+          name: "mint",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "rent",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "decimals",
+          type: "u8",
+        },
+        {
+          name: "mintAuthority",
+          type: "publicKey",
+        },
+        {
+          name: "freezeAuthority",
+          type: {
+            coption: "publicKey",
+          },
+        },
+      ],
+    },
+    {
+      name: "initializeAccount",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "rent",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "initializeMultisig",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "rent",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "m",
+          type: "u8",
+        },
+      ],
+    },
+    {
+      name: "transfer",
+      accounts: [
+        {
+          name: "source",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "destination",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "amount",
+          type: "u64",
+        },
+      ],
+    },
+    {
+      name: "approve",
+      accounts: [
+        {
+          name: "source",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "delegate",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "amount",
+          type: "u64",
+        },
+      ],
+    },
+    {
+      name: "revoke",
+      accounts: [
+        {
+          name: "source",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "setAuthority",
+      accounts: [
+        {
+          name: "mint",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "authorityType",
+          type: "u8",
+        },
+        {
+          name: "newAuthority",
+          type: {
+            coption: "publicKey",
+          },
+        },
+      ],
+    },
+    {
+      name: "mintTo",
+      accounts: [
+        {
+          name: "mint",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "to",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "amount",
+          type: "u64",
+        },
+      ],
+    },
+    {
+      name: "burn",
+      accounts: [
+        {
+          name: "source",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "amount",
+          type: "u64",
+        },
+      ],
+    },
+    {
+      name: "closeAccount",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "destination",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "freezeAccount",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "thawAccount",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "transferChecked",
+      accounts: [
+        {
+          name: "source",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "destination",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "amount",
+          type: "u64",
+        },
+        {
+          name: "decimals",
+          type: "u8",
+        },
+      ],
+    },
+    {
+      name: "approveChecked",
+      accounts: [
+        {
+          name: "source",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "delegate",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "amount",
+          type: "u64",
+        },
+        {
+          name: "decimals",
+          type: "u8",
+        },
+      ],
+    },
+    {
+      name: "mintToChecked",
+      accounts: [
+        {
+          name: "mint",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "to",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "amount",
+          type: "u64",
+        },
+        {
+          name: "decimals",
+          type: "u8",
+        },
+      ],
+    },
+    {
+      name: "burnChecked",
+      accounts: [
+        {
+          name: "source",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "authority",
+          isMut: false,
+          isSigner: true,
+        },
+      ],
+      args: [
+        {
+          name: "amount",
+          type: "u64",
+        },
+        {
+          name: "decimals",
+          type: "u8",
+        },
+      ],
+    },
+    {
+      name: "initializeAccount2",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "rent",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "authority",
+          type: "publicKey",
+        },
+      ],
+    },
+    {
+      name: "syncNative",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "initializeAccount3",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "authority",
+          type: "publicKey",
+        },
+      ],
+    },
+    {
+      name: "initializeMultisig2",
+      accounts: [
+        {
+          name: "account",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "m",
+          type: "u8",
+        },
+      ],
+    },
+    {
+      name: "initializeMint2",
+      accounts: [
+        {
+          name: "mint",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "decimals",
+          type: "u8",
+        },
+        {
+          name: "mintAuthority",
+          type: "publicKey",
+        },
+        {
+          name: "freezeAuthority",
+          type: {
+            coption: "publicKey",
+          },
+        },
+      ],
+    },
+  ],
+  accounts: [
+    {
+      name: "Mint",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "mintAuthority",
+            type: {
+              coption: "publicKey",
+            },
+          },
+          {
+            name: "supply",
+            type: "u64",
+          },
+          {
+            name: "decimals",
+            type: "u8",
+          },
+          {
+            name: "isInitialized",
+            type: "bool",
+          },
+          {
+            name: "freezeAuthority",
+            type: {
+              coption: "publicKey",
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "Token",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "mint",
+            type: "publicKey",
+          },
+          {
+            name: "authority",
+            type: "publicKey",
+          },
+          {
+            name: "amount",
+            type: "u64",
+          },
+          {
+            name: "delegate",
+            type: {
+              coption: "publicKey",
+            },
+          },
+          {
+            name: "state",
+            type: "u8",
+          },
+          {
+            name: "isNative",
+            type: {
+              coption: "u64",
+            },
+          },
+          {
+            name: "delegatedAmount",
+            type: "u64",
+          },
+          {
+            name: "closeAuthority",
+            type: {
+              coption: "publicKey",
+            },
+          },
+        ],
+      },
+    },
+  ],
+};

+ 2 - 2
ts/tests/events.spec.ts

@@ -1,6 +1,6 @@
 import { PublicKey } from "@solana/web3.js";
 import { EventParser } from "../src/program/event";
-import { Coder } from "../src";
+import { BorshCoder } from "../src";
 
 describe("Events", () => {
   it("Parses multiple instructions", async () => {
@@ -22,7 +22,7 @@ describe("Events", () => {
         },
       ],
     };
-    const coder = new Coder(idl);
+    const coder = new BorshCoder(idl);
     const programId = PublicKey.default;
     const eventParser = new EventParser(programId, coder);
 

+ 4 - 4
ts/tests/transaction.spec.ts

@@ -1,6 +1,6 @@
 import TransactionFactory from "../src/program/namespace/transaction";
 import InstructionFactory from "../src/program/namespace/instruction";
-import { Coder } from "../src";
+import { BorshCoder } from "../src";
 import { PublicKey, TransactionInstruction } from "@solana/web3.js";
 
 describe("Transaction", () => {
@@ -27,7 +27,7 @@ describe("Transaction", () => {
   };
 
   it("should add pre instructions before method ix", async () => {
-    const coder = new Coder(idl);
+    const coder = new BorshCoder(idl);
     const programId = PublicKey.default;
     const ixItem = InstructionFactory.build(
       idl.instructions[0],
@@ -41,7 +41,7 @@ describe("Transaction", () => {
   });
 
   it("should add post instructions after method ix", async () => {
-    const coder = new Coder(idl);
+    const coder = new BorshCoder(idl);
     const programId = PublicKey.default;
     const ixItem = InstructionFactory.build(
       idl.instructions[0],
@@ -55,7 +55,7 @@ describe("Transaction", () => {
   });
 
   it("should throw error if both preInstructions and instructions are used", async () => {
-    const coder = new Coder(idl);
+    const coder = new BorshCoder(idl);
     const programId = PublicKey.default;
     const ixItem = InstructionFactory.build(
       idl.instructions[0],