Browse Source

ts: setup associated token program idl (#1939)

Daniel Marin 3 years ago
parent
commit
a5dbc7b850

+ 1 - 0
CHANGELOG.md

@@ -25,6 +25,7 @@ com/project-serum/anchor/pull/1841)).
 * ts: Implement a coder for system program ([#1920](https://github.com/project-serum/anchor/pull/1920)).
 * ts: Add `program.coder.types` for encoding/decoding user-defined types ([#1931](https://github.com/project-serum/anchor/pull/1931)).
 * client: Add send_with_spinner_and_config function to RequestBuilder ([#1926](https://github.com/project-serum/anchor/pull/1926)).
+* ts: Implement a coder for SPL associated token program ([#1939](https://github.com/project-serum/anchor/pull/1939)).
 
 ### Fixes
 

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

@@ -2,6 +2,7 @@
 custom_coder = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 spl_token = "FmpfPa1LHEYRbueNMnwNVd2JvyQ89GXGWdyZEXNNKV8w"
 native_system = "9NxAd91hhJ3ZBTHytYP894y4ESRKG7n8VbLgdyYGJFLB"
+spl_associated_token = "4dUGnkre6uBhX1abB4ofkoecGN4aDXdiWSaWLUjVw6bh"
 
 [registry]
 url = "https://anchor.projectserum.com"

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

@@ -0,0 +1,22 @@
+[package]
+name = "spl-associated-token"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "spl_associated_token"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[profile.release]
+overflow-checks = true
+
+[dependencies]
+anchor-lang = "0.24.2"

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

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

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

@@ -0,0 +1,33 @@
+// This file is autogenerated with https://github.com/acheroncrypto/native-to-anchor
+
+use anchor_lang::prelude::*;
+
+declare_id!("4dUGnkre6uBhX1abB4ofkoecGN4aDXdiWSaWLUjVw6bh");
+
+#[program]
+pub mod spl_associated_token {
+    use super::*;
+
+    pub fn create(ctx: Context<Create>) -> Result<()> {
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct Create<'info> {
+    #[account(mut)]
+    authority: Signer<'info>,
+    #[account(mut)]
+    /// CHECK:
+    associated_account: AccountInfo<'info>,
+    /// CHECK:
+    owner: AccountInfo<'info>,
+    /// CHECK:
+    mint: AccountInfo<'info>,
+    /// CHECK:
+    system_program: AccountInfo<'info>,
+    /// CHECK:
+    token_program: AccountInfo<'info>,
+    /// CHECK:
+    rent: AccountInfo<'info>,
+}

+ 73 - 0
tests/custom-coder/tests/spl-associated-token-coder.ts

@@ -0,0 +1,73 @@
+import * as anchor from "@project-serum/anchor";
+import { Native, Spl } from "@project-serum/anchor";
+import { Keypair, PublicKey } from "@solana/web3.js";
+import * as assert from "assert";
+import BN from "bn.js";
+
+describe("spl-associated-token-coder", () => {
+  // Configure the client to use the local cluster.
+  const provider = anchor.AnchorProvider.env();
+  anchor.setProvider(provider);
+
+  // Client.
+  const program = Spl.associatedToken();
+  const systemProgram = Native.system();
+  const tokenProgram = Spl.token();
+
+  it("Creates an account", async () => {
+    // arrange
+    const mintKeypair = Keypair.generate();
+    const mintDecimals = 6;
+    const mintSize = tokenProgram.coder.accounts.size(
+      tokenProgram.idl.accounts[0]
+    );
+    const mintRentExemption =
+      await provider.connection.getMinimumBalanceForRentExemption(mintSize);
+    const [associatedToken] = await PublicKey.findProgramAddress(
+      [
+        provider.publicKey.toBuffer(),
+        tokenProgram.programId.toBuffer(),
+        mintKeypair.publicKey.toBuffer(),
+      ],
+      program.programId
+    );
+
+    // act
+    await program.methods
+      .create()
+      .accounts({
+        authority: provider.wallet.publicKey,
+        mint: mintKeypair.publicKey,
+        owner: provider.wallet.publicKey,
+        associatedAccount: associatedToken,
+      })
+      .preInstructions(
+        await Promise.all([
+          systemProgram.methods
+            .createAccount(
+              new BN(mintRentExemption),
+              new BN(mintSize),
+              tokenProgram.programId
+            )
+            .accounts({
+              from: provider.wallet.publicKey,
+              to: mintKeypair.publicKey,
+            })
+            .instruction(),
+          tokenProgram.methods
+            .initializeMint(mintDecimals, provider.wallet.publicKey, null)
+            .accounts({
+              mint: mintKeypair.publicKey,
+            })
+            .instruction(),
+        ])
+      )
+      .signers([mintKeypair])
+      .rpc();
+    // assert
+    const tokenAccount = await tokenProgram.account.token.fetch(
+      associatedToken
+    );
+    assert.ok(tokenAccount.mint.equals(mintKeypair.publicKey));
+  });
+});

+ 42 - 0
ts/src/coder/spl-associated-token/accounts.ts

@@ -0,0 +1,42 @@
+import { AccountsCoder } from "../index.js";
+import { Idl, IdlTypeDef } from "../../idl.js";
+import { accountSize } from "../common";
+
+export class SplAssociatedTokenAccountsCoder<A extends string = string>
+  implements AccountsCoder
+{
+  constructor(private idl: Idl) {}
+
+  public async encode<T = any>(accountName: A, account: T): Promise<Buffer> {
+    switch (accountName) {
+      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) {
+      default: {
+        throw new Error(`Invalid account name: ${accountName}`);
+      }
+    }
+  }
+
+  // TODO: this won't use the appendData.
+  public memcmp(accountName: A, _appendData?: Buffer): any {
+    switch (accountName) {
+      default: {
+        throw new Error(`Invalid account name: ${accountName}`);
+      }
+    }
+  }
+
+  public size(idlAccount: IdlTypeDef): number {
+    return accountSize(this.idl, idlAccount) ?? 0;
+  }
+}

+ 14 - 0
ts/src/coder/spl-associated-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 SplAssociatedTokenEventsCoder implements EventCoder {
+  constructor(_idl: Idl) {}
+
+  decode<E extends IdlEvent = IdlEvent, T = Record<string, string>>(
+    _log: string
+  ): Event<E, T> | null {
+    throw new Error("SPL associated token program does not have events");
+  }
+}

+ 26 - 0
ts/src/coder/spl-associated-token/index.ts

@@ -0,0 +1,26 @@
+import { Idl } from "../../idl.js";
+import { Coder } from "../index.js";
+import { SplAssociatedTokenInstructionCoder } from "./instruction.js";
+import { SplAssociatedTokenStateCoder } from "./state.js";
+import { SplAssociatedTokenAccountsCoder } from "./accounts.js";
+import { SplAssociatedTokenEventsCoder } from "./events.js";
+import { SplAssociatedTokenTypesCoder } from "./types.js";
+
+/**
+ * Coder for the SPL token program.
+ */
+export class SplAssociatedTokenCoder implements Coder {
+  readonly instruction: SplAssociatedTokenInstructionCoder;
+  readonly accounts: SplAssociatedTokenAccountsCoder;
+  readonly state: SplAssociatedTokenStateCoder;
+  readonly events: SplAssociatedTokenEventsCoder;
+  readonly types: SplAssociatedTokenTypesCoder;
+
+  constructor(idl: Idl) {
+    this.instruction = new SplAssociatedTokenInstructionCoder(idl);
+    this.accounts = new SplAssociatedTokenAccountsCoder(idl);
+    this.events = new SplAssociatedTokenEventsCoder(idl);
+    this.state = new SplAssociatedTokenStateCoder(idl);
+    this.types = new SplAssociatedTokenTypesCoder(idl);
+  }
+}

+ 22 - 0
ts/src/coder/spl-associated-token/instruction.ts

@@ -0,0 +1,22 @@
+import camelCase from "camelcase";
+import { Idl } from "../../idl.js";
+import { InstructionCoder } from "../index.js";
+
+export class SplAssociatedTokenInstructionCoder implements InstructionCoder {
+  constructor(_: Idl) {}
+
+  encode(ixName: string, _: any): Buffer {
+    switch (camelCase(ixName)) {
+      case "create": {
+        return Buffer.alloc(0);
+      }
+      default: {
+        throw new Error(`Invalid instruction: ${ixName}`);
+      }
+    }
+  }
+
+  encodeState(_ixName: string, _ix: any): Buffer {
+    throw new Error("SPL associated token does not have state");
+  }
+}

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

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

+ 13 - 0
ts/src/coder/spl-associated-token/types.ts

@@ -0,0 +1,13 @@
+import { TypesCoder } from "../index.js";
+import { Idl } from "../../idl.js";
+
+export class SplAssociatedTokenTypesCoder implements TypesCoder {
+  constructor(_idl: Idl) {}
+
+  encode<T = any>(_name: string, _type: T): Buffer {
+    throw new Error("SPL associated token does not have user-defined types");
+  }
+  decode<T = any>(_name: string, _typeData: Buffer): T {
+    throw new Error("SPL associated token does not have user-defined types");
+  }
+}

+ 120 - 0
ts/src/spl/associated-token.ts

@@ -0,0 +1,120 @@
+import { PublicKey } from "@solana/web3.js";
+import { Program } from "../program/index.js";
+import Provider from "../provider.js";
+import { SplAssociatedTokenCoder } from "../coder/spl-associated-token/index.js";
+
+const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey(
+  "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+);
+
+export function program(provider?: Provider): Program<SplAssociatedToken> {
+  return new Program<SplAssociatedToken>(
+    IDL,
+    ASSOCIATED_TOKEN_PROGRAM_ID,
+    provider,
+    coder()
+  );
+}
+
+export function coder(): SplAssociatedTokenCoder {
+  return new SplAssociatedTokenCoder(IDL);
+}
+
+/**
+ * SplAssociatedToken IDL.
+ */
+export type SplAssociatedToken = {
+  version: "0.1.0";
+  name: "spl_associated_token";
+  instructions: [
+    {
+      name: "create";
+      accounts: [
+        {
+          name: "authority";
+          isMut: true;
+          isSigner: true;
+        },
+        {
+          name: "associatedAccount";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "owner";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "mint";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "systemProgram";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "tokenProgram";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "rent";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [];
+    }
+  ];
+};
+
+export const IDL: SplAssociatedToken = {
+  version: "0.1.0",
+  name: "spl_associated_token",
+  instructions: [
+    {
+      name: "create",
+      accounts: [
+        {
+          name: "authority",
+          isMut: true,
+          isSigner: true,
+        },
+        {
+          name: "associatedAccount",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "owner",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "mint",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "systemProgram",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "tokenProgram",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "rent",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [],
+    },
+  ],
+};

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

@@ -1,4 +1,8 @@
 import { Program, Provider } from "../index.js";
+import {
+  program as associatedTokenProgram,
+  SplAssociatedToken,
+} from "./associated-token.js";
 import { program as tokenProgram, SplToken } from "./token.js";
 
 export { SplToken } from "./token.js";
@@ -7,4 +11,10 @@ export class Spl {
   public static token(provider?: Provider): Program<SplToken> {
     return tokenProgram(provider);
   }
+
+  public static associatedToken(
+    provider?: Provider
+  ): Program<SplAssociatedToken> {
+    return associatedTokenProgram(provider);
+  }
 }