Pārlūkot izejas kodu

Adds more types

Ian Macalinao 4 gadi atpakaļ
vecāks
revīzija
6488b11de9

+ 1 - 1
ts/src/idl.ts

@@ -111,7 +111,7 @@ type IdlEnumFieldsNamed = IdlField[];
 
 type IdlEnumFieldsTuple = IdlType[];
 
-type IdlErrorCode = {
+export type IdlErrorCode = {
   code: number;
   name: string;
   msg?: string;

+ 2 - 1
ts/src/index.ts

@@ -17,7 +17,7 @@ import workspace from "./workspace";
 import * as utils from "./utils";
 import { Program } from "./program";
 import { Address } from "./program/common";
-import { Event } from "./program/event";
+import { Event, EventParser } from "./program/event";
 import {
   ProgramAccount,
   AccountNamespace,
@@ -67,4 +67,5 @@ export {
   utils,
   Wallet,
   Address,
+  EventParser,
 };

+ 9 - 5
ts/src/program/context.ts

@@ -5,16 +5,16 @@ import {
   TransactionInstruction,
 } from "@solana/web3.js";
 import { Address } from "./common";
-import { IdlInstruction } from "../idl";
+import { IdlAccountItem, IdlAccounts, IdlInstruction } from "../idl";
 
 /**
  * Context provides all non-argument inputs for generating Anchor transactions.
  */
-export type Context = {
+export type Context<A extends Accounts = Accounts> = {
   /**
    * Accounts used in the instruction context.
    */
-  accounts?: Accounts;
+  accounts?: A;
 
   /**
    * All accounts to pass into an instruction *after* the main `accounts`.
@@ -55,10 +55,14 @@ export type Context = {
  * If multiple accounts are nested in the rust program, then they should be
  * nested here.
  */
-export type Accounts = {
-  [key: string]: Address | Accounts;
+export type Accounts<A extends IdlAccountItem[] = IdlAccountItem[]> = {
+  [K in A[number]["name"]]: Account<A[number]>;
 };
 
+type Account<A extends IdlAccountItem> = A extends IdlAccounts
+  ? Accounts<NonNullable<A["accounts"]>>
+  : Address;
+
 export function splitArgsAndCtx(
   idlIx: IdlInstruction,
   args: any[]

+ 12 - 3
ts/src/program/event.ts

@@ -1,13 +1,22 @@
 import { PublicKey } from "@solana/web3.js";
 import * as assert from "assert";
+import { IdlEvent, IdlEventField } from "src/idl";
 import Coder from "../coder";
+import { DecodeType } from "./namespace/types";
 
 const LOG_START_INDEX = "Program log: ".length;
 
 // Deserialized event.
-export type Event = {
-  name: string;
-  data: Object;
+export type Event<
+  E extends IdlEvent = IdlEvent,
+  Defined = Record<string, never>
+> = {
+  name: E["name"];
+  data: EventData<E["fields"][number], Defined>;
+};
+
+type EventData<T extends IdlEventField, Defined> = {
+  [N in T["name"]]: DecodeType<(T & { name: N })["type"], Defined>;
 };
 
 export class EventParser {

+ 17 - 11
ts/src/program/index.ts

@@ -42,7 +42,7 @@ import { Address, translateAddress } from "./common";
  * below will refer to the two counter examples found
  * [here](https://github.com/project-serum/anchor#examples).
  */
-export class Program {
+export class Program<IDL extends Idl = Idl> {
   /**
    * Async methods to send signed transactions to *non*-state methods on the
    * program, returning a [[TransactionSignature]].
@@ -73,7 +73,7 @@ export class Program {
    * });
    * ```
    */
-  readonly rpc: RpcNamespace;
+  readonly rpc: RpcNamespace<IDL>;
 
   /**
    * The namespace provides handles to an [[AccountClient]] object for each
@@ -126,7 +126,7 @@ export class Program {
    * });
    * ```
    */
-  readonly instruction: InstructionNamespace;
+  readonly instruction: InstructionNamespace<IDL>;
 
   /**
    * The namespace provides functions to build [[Transaction]] objects for each
@@ -157,7 +157,7 @@ export class Program {
    * });
    * ```
    */
-  readonly transaction: TransactionNamespace;
+  readonly transaction: TransactionNamespace<IDL>;
 
   /**
    * The namespace provides functions to simulate transactions for each method
@@ -193,7 +193,7 @@ export class Program {
    * });
    * ```
    */
-  readonly simulate: SimulateNamespace;
+  readonly simulate: SimulateNamespace<IDL>;
 
   /**
    * A client for the program state. Similar to the base [[Program]] client,
@@ -213,10 +213,10 @@ export class Program {
   /**
    * IDL defining the program's interface.
    */
-  public get idl(): Idl {
+  public get idl(): IDL {
     return this._idl;
   }
-  private _idl: Idl;
+  private _idl: IDL;
 
   /**
    * Coder for serializing requests.
@@ -240,7 +240,7 @@ export class Program {
    * @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) {
     programId = translateAddress(programId);
 
     // Fields.
@@ -275,10 +275,13 @@ export class Program {
    * @param programId The on-chain address of the program.
    * @param provider  The network and wallet context.
    */
-  public static async at(address: Address, provider?: Provider) {
+  public static async at<IDL extends Idl = Idl>(
+    address: Address,
+    provider?: Provider
+  ) {
     const programId = translateAddress(address);
 
-    const idl = await Program.fetchIdl(programId, provider);
+    const idl = await Program.fetchIdl<IDL>(programId, provider);
     return new Program(idl, programId, provider);
   }
 
@@ -291,7 +294,10 @@ export class Program {
    * @param programId The on-chain address of the program.
    * @param provider  The network and wallet context.
    */
-  public static async fetchIdl(address: Address, provider?: Provider) {
+  public static async fetchIdl<IDL extends Idl = Idl>(
+    address: Address,
+    provider?: Provider
+  ): Promise<IDL> {
     provider = provider ?? getProvider();
     const programId = translateAddress(address);
 

+ 9 - 9
ts/src/program/namespace/account.ts

@@ -19,8 +19,8 @@ import { Subscription, Address, translateAddress } from "../common";
 import { getProvider } from "../../";
 
 export default class AccountFactory {
-  public static build(
-    idl: Idl,
+  public static build<IDL extends Idl>(
+    idl: IDL,
     coder: Coder,
     programId: PublicKey,
     provider: Provider
@@ -29,7 +29,7 @@ export default class AccountFactory {
 
     idl.accounts.forEach((idlAccount) => {
       const name = camelCase(idlAccount.name);
-      accountFns[name] = new AccountClient(
+      accountFns[name] = new AccountClient<IDL>(
         idl,
         idlAccount,
         programId,
@@ -62,11 +62,11 @@ export default class AccountFactory {
  *
  * For the full API, see the [[AccountClient]] reference.
  */
-export interface AccountNamespace {
-  [key: string]: AccountClient;
+export interface AccountNamespace<IDL extends Idl = Idl> {
+  [key: string]: AccountClient<IDL>;
 }
 
-export class AccountClient {
+export class AccountClient<IDL extends Idl = Idl> {
   /**
    * Returns the number of bytes in this account.
    */
@@ -99,11 +99,11 @@ export class AccountClient {
   }
   private _coder: Coder;
 
-  private _idlAccount: IdlTypeDef;
+  private _idlAccount: IDL["accounts"][number];
 
   constructor(
-    idl: Idl,
-    idlAccount: IdlTypeDef,
+    idl: IDL,
+    idlAccount: IDL["accounts"][number],
     programId: PublicKey,
     provider?: Provider,
     coder?: Coder

+ 17 - 10
ts/src/program/namespace/index.ts

@@ -2,7 +2,7 @@ import camelCase from "camelcase";
 import { PublicKey } from "@solana/web3.js";
 import Coder from "../../coder";
 import Provider from "../../provider";
-import { Idl } from "../../idl";
+import { Idl, IdlInstruction } from "../../idl";
 import StateFactory, { StateClient } from "./state";
 import InstructionFactory, { InstructionNamespace } from "./instruction";
 import TransactionFactory, { TransactionNamespace } from "./transaction";
@@ -23,17 +23,17 @@ export default class NamespaceFactory {
   /**
    * Generates all namespaces for a given program.
    */
-  public static build(
-    idl: Idl,
+  public static build<IDL extends Idl>(
+    idl: IDL,
     coder: Coder,
     programId: PublicKey,
     provider: Provider
   ): [
-    RpcNamespace,
-    InstructionNamespace,
-    TransactionNamespace,
+    RpcNamespace<IDL>,
+    InstructionNamespace<IDL>,
+    TransactionNamespace<IDL>,
     AccountNamespace,
-    SimulateNamespace,
+    SimulateNamespace<IDL>,
     StateClient
   ] {
     const rpc: RpcNamespace = {};
@@ -45,10 +45,10 @@ export default class NamespaceFactory {
 
     const state = StateFactory.build(idl, coder, programId, provider);
 
-    idl.instructions.forEach((idlIx) => {
+    idl.instructions.forEach(<I extends IdlInstruction>(idlIx: I) => {
       const ixItem = InstructionFactory.build(
         idlIx,
-        (ixName: string, ix: any) => coder.instruction.encode(ixName, ix),
+        (ixName, ix) => coder.instruction.encode(ixName, ix),
         programId
       );
       const txItem = TransactionFactory.build(idlIx, ixItem);
@@ -75,6 +75,13 @@ export default class NamespaceFactory {
       ? AccountFactory.build(idl, coder, programId, provider)
       : {};
 
-    return [rpc, instruction, transaction, account, simulate, state];
+    return [
+      rpc as RpcNamespace<IDL>,
+      instruction as InstructionNamespace<IDL>,
+      transaction as TransactionNamespace<IDL>,
+      account,
+      simulate as SimulateNamespace<IDL>,
+      state,
+    ];
   }
 }

+ 42 - 13
ts/src/program/namespace/instruction.ts

@@ -3,7 +3,7 @@ import {
   PublicKey,
   TransactionInstruction,
 } from "@solana/web3.js";
-import { IdlAccount, IdlInstruction, IdlAccountItem } from "../../idl";
+import { Idl, IdlAccount, IdlAccountItem, IdlInstruction } from "../../idl";
 import { IdlError } from "../../error";
 import {
   toInstruction,
@@ -12,18 +12,26 @@ import {
   Address,
 } from "../common";
 import { Accounts, splitArgsAndCtx } from "../context";
+import {
+  AllInstructionsMap,
+  InstructionContextFn,
+  InstructionContextFnArgs,
+  MakeAllInstructionsNamespace,
+} from "./types";
 
 export default class InstructionNamespaceFactory {
-  public static build(
-    idlIx: IdlInstruction,
+  public static build<IDL extends Idl, I extends IdlInstruction>(
+    idlIx: I,
     encodeFn: InstructionEncodeFn,
     programId: PublicKey
-  ): InstructionFn {
+  ): InstructionFn<IDL, I> {
     if (idlIx.name === "_inner") {
       throw new IdlError("the _inner name is reserved");
     }
 
-    const ix = (...args: any[]): TransactionInstruction => {
+    const ix = (
+      ...args: InstructionContextFnArgs<IDL, I>
+    ): TransactionInstruction => {
       const [ixArgs, ctx] = splitArgsAndCtx(idlIx, [...args]);
       validateAccounts(idlIx.accounts, ctx.accounts);
       validateInstruction(idlIx, ...args);
@@ -45,7 +53,7 @@ export default class InstructionNamespaceFactory {
     };
 
     // Utility fn for ordering the accounts for this instruction.
-    ix["accounts"] = (accs: Accounts) => {
+    ix["accounts"] = (accs: Accounts<I["accounts"]>) => {
       return InstructionNamespaceFactory.accountsArray(accs, idlIx.accounts);
     };
 
@@ -107,21 +115,42 @@ export default class InstructionNamespaceFactory {
  * });
  * ```
  */
-export interface InstructionNamespace {
-  [key: string]: InstructionFn;
-}
+export type InstructionNamespace<
+  IDL extends Idl = Idl
+> = MakeAllInstructionsNamespace<
+  IDL,
+  TransactionInstruction,
+  {
+    [M in keyof AllInstructionsMap<IDL>]: {
+      accounts: (
+        ctx: Accounts<AllInstructionsMap<IDL>[M]["accounts"]>
+      ) => unknown;
+    };
+  }
+>;
 
 /**
  * Function to create a `TransactionInstruction` generated from an IDL.
  * Additionally it provides an `accounts` utility method, returning a list
  * of ordered accounts for the instruction.
  */
-export type InstructionFn = IxProps & ((...args: any[]) => any);
-type IxProps = {
-  accounts: (ctx: Accounts) => readonly AccountMeta[];
+export type InstructionFn<
+  IDL extends Idl = Idl,
+  I extends IdlInstruction = IdlInstruction
+> = InstructionContextFn<IDL, I, TransactionInstruction> &
+  IxProps<Accounts<I["accounts"]>>;
+
+type IxProps<A extends Accounts> = {
+  /**
+   * Returns an ordered list of accounts associated with the instruction.
+   */
+  accounts: (ctx: A) => AccountMeta[];
 };
 
-export type InstructionEncodeFn = (ixName: string, ix: any) => Buffer;
+export type InstructionEncodeFn<I extends IdlInstruction = IdlInstruction> = (
+  ixName: I["name"],
+  ix: any
+) => Buffer;
 
 // Throws error if any argument required for the `ix` is not given.
 function validateInstruction(ix: IdlInstruction, ...args: any[]) {

+ 15 - 9
ts/src/program/namespace/rpc.ts

@@ -1,18 +1,19 @@
 import { TransactionSignature } from "@solana/web3.js";
 import Provider from "../../provider";
-import { IdlInstruction } from "../../idl";
+import { Idl, IdlInstruction } from "../../idl";
 import { splitArgsAndCtx } from "../context";
 import { TransactionFn } from "./transaction";
 import { ProgramError } from "../../error";
+import { InstructionContextFn, MakeInstructionsNamespace } from "./types";
 
 export default class RpcFactory {
-  public static build(
-    idlIx: IdlInstruction,
-    txFn: TransactionFn,
+  public static build<IDL extends Idl, I extends IDL["instructions"][number]>(
+    idlIx: I,
+    txFn: TransactionFn<IDL, I>,
     idlErrors: Map<number, string>,
     provider: Provider
   ): RpcFn {
-    const rpc = async (...args: any[]): Promise<TransactionSignature> => {
+    const rpc: RpcFn<IDL, I> = async (...args) => {
       const tx = txFn(...args);
       const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
       try {
@@ -66,12 +67,17 @@ export default class RpcFactory {
  * });
  * ```
  */
-export interface RpcNamespace {
-  [key: string]: RpcFn;
-}
+export type RpcNamespace<IDL extends Idl = Idl> = MakeInstructionsNamespace<
+  IDL,
+  IDL["instructions"][number],
+  Promise<TransactionSignature>
+>;
 
 /**
  * RpcFn is a single RPC method generated from an IDL, sending a transaction
  * paid for and signed by the configured provider.
  */
-export type RpcFn = (...args: any[]) => Promise<TransactionSignature>;
+export type RpcFn<
+  IDL extends Idl = Idl,
+  I extends IDL["instructions"][number] = IDL["instructions"][number]
+> = InstructionContextFn<IDL, I, Promise<TransactionSignature>>;

+ 32 - 16
ts/src/program/namespace/simulate.ts

@@ -1,24 +1,30 @@
 import { PublicKey } from "@solana/web3.js";
 import Provider from "../../provider";
-import { IdlInstruction } from "../../idl";
 import { splitArgsAndCtx } from "../context";
 import { TransactionFn } from "./transaction";
 import { EventParser } from "../event";
 import Coder from "../../coder";
-import { Idl } from "../../idl";
+import { Idl, IdlEvent } from "../../idl";
 import { ProgramError } from "../../error";
+import {
+  AllInstructions,
+  IdlTypes,
+  InstructionContextFn,
+  MakeAllInstructionsNamespace,
+} from "./types";
+import { Event } from "../event";
 
 export default class SimulateFactory {
-  public static build(
-    idlIx: IdlInstruction,
-    txFn: TransactionFn,
+  public static build<IDL extends Idl, I extends AllInstructions<IDL>>(
+    idlIx: AllInstructions<IDL>,
+    txFn: TransactionFn<IDL>,
     idlErrors: Map<number, string>,
     provider: Provider,
     coder: Coder,
     programId: PublicKey,
-    idl: Idl
-  ): SimulateFn {
-    const simulate = async (...args: any[]): Promise<SimulateResponse> => {
+    idl: IDL
+  ): SimulateFn<IDL, I> {
+    const simulate: SimulateFn<IDL> = async (...args) => {
       const tx = txFn(...args);
       const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
       let resp = undefined;
@@ -91,19 +97,29 @@ export default class SimulateFactory {
  * });
  * ```
  */
-export interface SimulateNamespace {
-  [key: string]: SimulateFn;
-}
+export type SimulateNamespace<
+  IDL extends Idl = Idl
+> = MakeAllInstructionsNamespace<
+  IDL,
+  Promise<SimulateResponse<IDL["events"][number], IdlTypes<IDL>>>
+>;
 
 /**
- * RpcFn is a single method generated from an IDL. It simulates a method
+ * SimulateFn is a single method generated from an IDL. It simulates a method
  * against a cluster configured by the provider, returning a list of all the
  * events and raw logs that were emitted during the execution of the
  * method.
  */
-export type SimulateFn = (...args: any[]) => Promise<SimulateResponse>;
+export type SimulateFn<
+  IDL extends Idl = Idl,
+  I extends AllInstructions<IDL> = AllInstructions<IDL>
+> = InstructionContextFn<
+  IDL,
+  I,
+  Promise<SimulateResponse<IDL["events"][number], IdlTypes<IDL>>>
+>;
 
-type SimulateResponse = {
-  events: Event[];
-  raw: string[];
+type SimulateResponse<E extends IdlEvent, Defined> = {
+  events: readonly Event<E, Defined>[];
+  raw: readonly string[];
 };

+ 18 - 10
ts/src/program/namespace/transaction.ts

@@ -1,14 +1,19 @@
 import { Transaction } from "@solana/web3.js";
-import { IdlInstruction } from "../../idl";
+import { Idl, IdlInstruction } from "../../idl";
 import { splitArgsAndCtx } from "../context";
 import { InstructionFn } from "./instruction";
+import {
+  AllInstructions,
+  InstructionContextFn,
+  MakeAllInstructionsNamespace,
+} from "./types";
 
 export default class TransactionFactory {
-  public static build(
-    idlIx: IdlInstruction,
-    ixFn: InstructionFn
-  ): TransactionFn {
-    const txFn = (...args: any[]): Transaction => {
+  public static build<IDL extends Idl, I extends AllInstructions<IDL>>(
+    idlIx: I,
+    ixFn: InstructionFn<IDL, I>
+  ): TransactionFn<IDL, I> {
+    const txFn: TransactionFn<IDL, I> = (...args): Transaction => {
       const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
       const tx = new Transaction();
       if (ctx.instructions !== undefined) {
@@ -51,11 +56,14 @@ export default class TransactionFactory {
  * });
  * ```
  */
-export interface TransactionNamespace {
-  [key: string]: TransactionFn;
-}
+export type TransactionNamespace<
+  IDL extends Idl = Idl
+> = MakeAllInstructionsNamespace<IDL, Transaction>;
 
 /**
  * Tx is a function to create a `Transaction` for a given program instruction.
  */
-export type TransactionFn = (...args: any[]) => Transaction;
+export type TransactionFn<
+  IDL extends Idl = Idl,
+  I extends AllInstructions<IDL> = AllInstructions<IDL>
+> = InstructionContextFn<IDL, I, Transaction>;

+ 110 - 0
ts/src/program/namespace/types.ts

@@ -0,0 +1,110 @@
+import { PublicKey, TransactionInstruction } from "@solana/web3.js";
+import BN from "bn.js";
+import { Idl } from "src";
+import { IdlField, IdlInstruction, IdlType, IdlTypeDef } from "../../idl";
+import { Accounts, Context } from "../context";
+
+/**
+ * All instructions for an IDL.
+ */
+export type AllInstructions<IDL extends Idl> = IDL["instructions"][number] &
+  IDL["state"]["methods"][number];
+
+/**
+ * Returns a type of instruction name to the IdlInstruction.
+ */
+export type InstructionMap<I extends IdlInstruction> = {
+  [K in I["name"]]: I & { name: K };
+};
+
+/**
+ * Returns a type of instruction name to the IdlInstruction.
+ */
+export type AllInstructionsMap<IDL extends Idl> = InstructionMap<
+  AllInstructions<IDL>
+>;
+
+export type MakeAllInstructionsNamespace<
+  IDL extends Idl,
+  Ret,
+  Mk extends { [M in keyof InstructionMap<AllInstructions<IDL>>]: unknown } = {
+    [M in keyof InstructionMap<AllInstructions<IDL>>]: unknown;
+  }
+> = MakeInstructionsNamespace<IDL, AllInstructions<IDL>, Ret, Mk>;
+
+export type MakeInstructionsNamespace<
+  IDL extends Idl,
+  I extends IdlInstruction,
+  Ret,
+  Mk extends { [M in keyof InstructionMap<I>]: unknown } = {
+    [M in keyof InstructionMap<I>]: unknown;
+  }
+> = {
+  [M in keyof InstructionMap<I>]: InstructionContextFn<
+    IDL,
+    InstructionMap<I>[M],
+    Ret
+  > &
+    Mk[M];
+};
+
+export type InstructionContextFnArgs<
+  IDL extends Idl,
+  I extends IDL["instructions"][number]
+> = [...ArgsTuple<I["args"], IdlTypes<IDL>>, Context<Accounts<I["accounts"]>>];
+
+export type InstructionContextFn<
+  IDL extends Idl,
+  I extends IDL["instructions"][number],
+  Ret
+> = (...args: InstructionContextFnArgs<IDL, I>) => Ret;
+
+type TypeMap = {
+  publicKey: PublicKey;
+  u64: BN;
+  i64: BN;
+} & {
+  [K in "u8" | "i8" | "u16" | "i16" | "u32" | "i32"]: number;
+};
+
+export type DecodeType<T extends IdlType, Defined> = T extends keyof TypeMap
+  ? TypeMap[T]
+  : T extends { defined: keyof Defined }
+  ? Defined[T["defined"]]
+  : T extends { option: { defined: keyof Defined } }
+  ? Defined[T["option"]["defined"]] | null
+  : T extends { vec: { defined: keyof Defined } }
+  ? Defined[T["vec"]["defined"]][]
+  : unknown;
+
+/**
+ * Tuple of arguments
+ */
+type ArgsTuple<A extends IdlField[], Defined> = {
+  [K in keyof A]: A[K] extends IdlField
+    ? DecodeType<A[K]["type"], Defined>
+    : unknown;
+} &
+  unknown[];
+
+type FieldsOfType<I extends IdlTypeDef> = NonNullable<
+  I["type"]["fields"]
+>[number];
+
+type TypeDef<I extends IdlTypeDef, Defined> = {
+  [F in FieldsOfType<I>["name"]]: DecodeType<
+    (FieldsOfType<I> & { name: F })["type"],
+    Defined
+  >;
+};
+
+type TypeDefDictionary<T extends IdlTypeDef[], Defined> = {
+  [K in T[number]["name"]]: TypeDef<T[number] & { name: K }, Defined>;
+};
+
+export type IdlTypes<T extends Idl> = TypeDefDictionary<
+  NonNullable<T["types"]>,
+  Record<string, never>
+>;
+
+export type IdlErrorInfo<IDL extends Idl> = NonNullable<IDL["errors"]>[number];