Browse Source

More granular types

Ian Macalinao 4 years ago
parent
commit
5980b9c9d0

+ 3 - 3
ts/src/program/context.ts

@@ -55,12 +55,12 @@ export type Context<A extends Accounts = Accounts> = {
  * If multiple accounts are nested in the rust program, then they should be
  * nested here.
  */
-export type Accounts<A extends IdlAccountItem[] = IdlAccountItem[]> = {
-  [K in A[number]["name"]]: Account<A[number]>;
+export type Accounts<A extends IdlAccountItem = IdlAccountItem> = {
+  [N in A["name"]]: Account<A & { name: N }>;
 };
 
 type Account<A extends IdlAccountItem> = A extends IdlAccounts
-  ? Accounts<NonNullable<A["accounts"]>>
+  ? Accounts<A["accounts"][number]>
   : Address;
 
 export function splitArgsAndCtx(

+ 6 - 6
ts/src/program/index.ts

@@ -73,7 +73,7 @@ export class Program<IDL extends Idl = Idl> {
    * });
    * ```
    */
-  readonly rpc: RpcNamespace<IDL>;
+  readonly rpc: RpcNamespace<IDL, IDL["instructions"][number]>;
 
   /**
    * The namespace provides handles to an [[AccountClient]] object for each
@@ -95,7 +95,7 @@ export class Program<IDL extends Idl = Idl> {
    *
    * For the full API, see the [[AccountClient]] reference.
    */
-  readonly account: AccountNamespace;
+  readonly account: AccountNamespace<IDL>;
 
   /**
    * The namespace provides functions to build [[TransactionInstruction]]
@@ -126,7 +126,7 @@ export class Program<IDL extends Idl = Idl> {
    * });
    * ```
    */
-  readonly instruction: InstructionNamespace<IDL>;
+  readonly instruction: InstructionNamespace<IDL, IDL["instructions"][number]>;
 
   /**
    * The namespace provides functions to build [[Transaction]] objects for each
@@ -157,7 +157,7 @@ export class Program<IDL extends Idl = Idl> {
    * });
    * ```
    */
-  readonly transaction: TransactionNamespace<IDL>;
+  readonly transaction: TransactionNamespace<IDL, IDL["instructions"][number]>;
 
   /**
    * The namespace provides functions to simulate transactions for each method
@@ -193,14 +193,14 @@ export class Program<IDL extends Idl = Idl> {
    * });
    * ```
    */
-  readonly simulate: SimulateNamespace<IDL>;
+  readonly simulate: SimulateNamespace<IDL, IDL["instructions"][number]>;
 
   /**
    * A client for the program state. Similar to the base [[Program]] client,
    * one can use this to send transactions and read accounts for the state
    * abstraction.
    */
-  readonly state: StateClient;
+  readonly state: StateClient<IDL>;
 
   /**
    * Address of the program.

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

@@ -24,7 +24,7 @@ export default class AccountFactory {
     coder: Coder,
     programId: PublicKey,
     provider: Provider
-  ): AccountNamespace {
+  ): AccountNamespace<IDL> {
     const accountFns: AccountNamespace = {};
 
     idl.accounts.forEach((idlAccount) => {
@@ -38,7 +38,7 @@ export default class AccountFactory {
       );
     });
 
-    return accountFns;
+    return accountFns as AccountNamespace<IDL>;
   }
 }
 

+ 5 - 4
ts/src/program/namespace/index.ts

@@ -10,6 +10,7 @@ import RpcFactory, { RpcNamespace } from "./rpc";
 import AccountFactory, { AccountNamespace } from "./account";
 import SimulateFactory, { SimulateNamespace } from "./simulate";
 import { parseIdlErrors } from "../common";
+import { AllInstructions } from "./types";
 
 // Re-exports.
 export { StateClient } from "./state";
@@ -32,9 +33,9 @@ export default class NamespaceFactory {
     RpcNamespace<IDL>,
     InstructionNamespace<IDL>,
     TransactionNamespace<IDL>,
-    AccountNamespace,
+    AccountNamespace<IDL>,
     SimulateNamespace<IDL>,
-    StateClient
+    StateClient<IDL>
   ] {
     const rpc: RpcNamespace = {};
     const instruction: InstructionNamespace = {};
@@ -45,8 +46,8 @@ export default class NamespaceFactory {
 
     const state = StateFactory.build(idl, coder, programId, provider);
 
-    idl.instructions.forEach(<I extends IdlInstruction>(idlIx: I) => {
-      const ixItem = InstructionFactory.build(
+    idl.instructions.forEach(<I extends AllInstructions<IDL>>(idlIx: I) => {
+      const ixItem = InstructionFactory.build<IDL, I>(
         idlIx,
         (ixName, ix) => coder.instruction.encode(ixName, ix),
         programId

+ 12 - 9
ts/src/program/namespace/instruction.ts

@@ -13,16 +13,17 @@ import {
 } from "../common";
 import { Accounts, splitArgsAndCtx } from "../context";
 import {
+  AllInstructions,
   AllInstructionsMap,
   InstructionContextFn,
   InstructionContextFnArgs,
-  MakeAllInstructionsNamespace,
+  MakeInstructionsNamespace,
 } from "./types";
 
 export default class InstructionNamespaceFactory {
-  public static build<IDL extends Idl, I extends IdlInstruction>(
+  public static build<IDL extends Idl, I extends AllInstructions<IDL>>(
     idlIx: I,
-    encodeFn: InstructionEncodeFn,
+    encodeFn: InstructionEncodeFn<I>,
     programId: PublicKey
   ): InstructionFn<IDL, I> {
     if (idlIx.name === "_inner") {
@@ -53,7 +54,7 @@ export default class InstructionNamespaceFactory {
     };
 
     // Utility fn for ordering the accounts for this instruction.
-    ix["accounts"] = (accs: Accounts<I["accounts"]>) => {
+    ix["accounts"] = (accs: Accounts<I["accounts"][number]>) => {
       return InstructionNamespaceFactory.accountsArray(accs, idlIx.accounts);
     };
 
@@ -116,14 +117,16 @@ export default class InstructionNamespaceFactory {
  * ```
  */
 export type InstructionNamespace<
-  IDL extends Idl = Idl
-> = MakeAllInstructionsNamespace<
+  IDL extends Idl = Idl,
+  I extends IdlInstruction = IDL["instructions"][number]
+> = MakeInstructionsNamespace<
   IDL,
+  I,
   TransactionInstruction,
   {
     [M in keyof AllInstructionsMap<IDL>]: {
       accounts: (
-        ctx: Accounts<AllInstructionsMap<IDL>[M]["accounts"]>
+        ctx: Accounts<AllInstructionsMap<IDL>[M]["accounts"][number]>
       ) => unknown;
     };
   }
@@ -136,9 +139,9 @@ export type InstructionNamespace<
  */
 export type InstructionFn<
   IDL extends Idl = Idl,
-  I extends IdlInstruction = IdlInstruction
+  I extends AllInstructions<IDL> = AllInstructions<IDL>
 > = InstructionContextFn<IDL, I, TransactionInstruction> &
-  IxProps<Accounts<I["accounts"]>>;
+  IxProps<Accounts<I["accounts"][number]>>;
 
 type IxProps<A extends Accounts> = {
   /**

+ 11 - 8
ts/src/program/namespace/rpc.ts

@@ -4,10 +4,14 @@ import { Idl, IdlInstruction } from "../../idl";
 import { splitArgsAndCtx } from "../context";
 import { TransactionFn } from "./transaction";
 import { ProgramError } from "../../error";
-import { InstructionContextFn, MakeInstructionsNamespace } from "./types";
+import {
+  AllInstructions,
+  InstructionContextFn,
+  MakeInstructionsNamespace,
+} from "./types";
 
 export default class RpcFactory {
-  public static build<IDL extends Idl, I extends IDL["instructions"][number]>(
+  public static build<IDL extends Idl, I extends AllInstructions<IDL>>(
     idlIx: I,
     txFn: TransactionFn<IDL, I>,
     idlErrors: Map<number, string>,
@@ -67,11 +71,10 @@ export default class RpcFactory {
  * });
  * ```
  */
-export type RpcNamespace<IDL extends Idl = Idl> = MakeInstructionsNamespace<
-  IDL,
-  IDL["instructions"][number],
-  Promise<TransactionSignature>
->;
+export type RpcNamespace<
+  IDL extends Idl = Idl,
+  I extends AllInstructions<IDL> = AllInstructions<IDL>
+> = MakeInstructionsNamespace<IDL, I, Promise<TransactionSignature>>;
 
 /**
  * RpcFn is a single RPC method generated from an IDL, sending a transaction
@@ -79,5 +82,5 @@ export type RpcNamespace<IDL extends Idl = Idl> = MakeInstructionsNamespace<
  */
 export type RpcFn<
   IDL extends Idl = Idl,
-  I extends IDL["instructions"][number] = IDL["instructions"][number]
+  I extends AllInstructions<IDL> = AllInstructions<IDL>
 > = InstructionContextFn<IDL, I, Promise<TransactionSignature>>;

+ 5 - 3
ts/src/program/namespace/simulate.ts

@@ -10,7 +10,7 @@ import {
   AllInstructions,
   IdlTypes,
   InstructionContextFn,
-  MakeAllInstructionsNamespace,
+  MakeInstructionsNamespace,
 } from "./types";
 import { Event } from "../event";
 
@@ -98,9 +98,11 @@ export default class SimulateFactory {
  * ```
  */
 export type SimulateNamespace<
-  IDL extends Idl = Idl
-> = MakeAllInstructionsNamespace<
+  IDL extends Idl = Idl,
+  I extends AllInstructions<IDL> = AllInstructions<IDL>
+> = MakeInstructionsNamespace<
   IDL,
+  I,
   Promise<SimulateResponse<IDL["events"][number], IdlTypes<IDL>>>
 >;
 

+ 64 - 46
ts/src/program/namespace/state.ts

@@ -5,9 +5,10 @@ import {
   SystemProgram,
   SYSVAR_RENT_PUBKEY,
   Commitment,
+  AccountMeta,
 } from "@solana/web3.js";
 import Provider from "../../provider";
-import { Idl, IdlStateMethod } from "../../idl";
+import { Idl, IdlAccountItem, IdlInstruction, IdlStateMethod } from "../../idl";
 import Coder, { stateDiscriminator } from "../../coder";
 import { RpcNamespace, InstructionNamespace, TransactionNamespace } from "./";
 import { getProvider } from "../../";
@@ -19,12 +20,12 @@ import RpcNamespaceFactory from "./rpc";
 import TransactionNamespaceFactory from "./transaction";
 
 export default class StateFactory {
-  public static build(
-    idl: Idl,
+  public static build<IDL extends Idl>(
+    idl: IDL,
     coder: Coder,
     programId: PublicKey,
     provider: Provider
-  ): StateClient | undefined {
+  ): StateClient<IDL> | undefined {
     if (idl.state === undefined) {
       return undefined;
     }
@@ -37,21 +38,27 @@ export default class StateFactory {
  * one can use this to send transactions and read accounts for the state
  * abstraction.
  */
-export class StateClient {
+export class StateClient<IDL extends Idl> {
   /**
    * [[RpcNamespace]] for all state methods.
    */
-  readonly rpc: RpcNamespace;
+  readonly rpc: RpcNamespace<IDL, IDL["state"]["methods"][number]>;
 
   /**
    * [[InstructionNamespace]] for all state methods.
    */
-  readonly instruction: InstructionNamespace;
+  readonly instruction: InstructionNamespace<
+    IDL,
+    IDL["state"]["methods"][number]
+  >;
 
   /**
    * [[TransactionNamespace]] for all state methods.
    */
-  readonly transaction: TransactionNamespace;
+  readonly transaction: TransactionNamespace<
+    IDL,
+    IDL["state"]["methods"][number]
+  >;
 
   /**
    * Returns the program ID owning the state.
@@ -78,11 +85,11 @@ export class StateClient {
 
   private _address: PublicKey;
   private _coder: Coder;
-  private _idl: Idl;
+  private _idl: IDL;
   private _sub: Subscription | null;
 
   constructor(
-    idl: Idl,
+    idl: IDL,
     programId: PublicKey,
     provider?: Provider,
     coder?: Coder
@@ -96,46 +103,57 @@ export class StateClient {
 
     // Build namespaces.
     const [instruction, transaction, rpc] = ((): [
-      InstructionNamespace,
-      TransactionNamespace,
-      RpcNamespace
+      InstructionNamespace<IDL, IDL["state"]["methods"][number]>,
+      TransactionNamespace<IDL, IDL["state"]["methods"][number]>,
+      RpcNamespace<IDL, IDL["state"]["methods"][number]>
     ] => {
       let instruction: InstructionNamespace = {};
       let transaction: TransactionNamespace = {};
       let rpc: RpcNamespace = {};
 
-      idl.state.methods.forEach((m: IdlStateMethod) => {
-        // Build instruction method.
-        const ixItem = InstructionNamespaceFactory.build(
-          m,
-          (ixName: string, ix: any) =>
-            coder.instruction.encodeState(ixName, ix),
-          programId
-        );
-        ixItem["accounts"] = (accounts: Accounts) => {
-          const keys = stateInstructionKeys(programId, provider, m, accounts);
-          return keys.concat(
-            InstructionNamespaceFactory.accountsArray(accounts, m.accounts)
+      idl.state.methods.forEach(
+        <I extends IDL["state"]["methods"][number]>(m: I) => {
+          // Build instruction method.
+          const ixItem = InstructionNamespaceFactory.build<IDL, I>(
+            m,
+            (ixName, ix) => coder.instruction.encodeState(ixName, ix),
+            programId
+          );
+          ixItem["accounts"] = (accounts) => {
+            const keys = stateInstructionKeys(programId, provider, m, accounts);
+            return keys.concat(
+              InstructionNamespaceFactory.accountsArray(accounts, m.accounts)
+            );
+          };
+          // Build transaction method.
+          const txItem = TransactionNamespaceFactory.build(m, ixItem);
+          // Build RPC method.
+          const rpcItem = RpcNamespaceFactory.build(
+            m,
+            txItem,
+            parseIdlErrors(idl),
+            provider
           );
-        };
-        // Build transaction method.
-        const txItem = TransactionNamespaceFactory.build(m, ixItem);
-        // Build RPC method.
-        const rpcItem = RpcNamespaceFactory.build(
-          m,
-          txItem,
-          parseIdlErrors(idl),
-          provider
-        );
 
-        // Attach them all to their respective namespaces.
-        const name = camelCase(m.name);
-        instruction[name] = ixItem;
-        transaction[name] = txItem;
-        rpc[name] = rpcItem;
-      });
+          // Attach them all to their respective namespaces.
+          const name = camelCase(m.name);
+          instruction[name] = ixItem;
+          transaction[name] = txItem;
+          rpc[name] = rpcItem;
+        }
+      );
 
-      return [instruction, transaction, rpc];
+      return [
+        instruction as InstructionNamespace<
+          IDL,
+          IDL["state"]["methods"][number]
+        >,
+        transaction as TransactionNamespace<
+          IDL,
+          IDL["state"]["methods"][number]
+        >,
+        rpc as RpcNamespace<IDL, IDL["state"]["methods"][number]>,
+      ];
     })();
     this.instruction = instruction;
     this.transaction = transaction;
@@ -218,12 +236,12 @@ function programStateAddress(programId: PublicKey): PublicKey {
 
 // Returns the common keys that are prepended to all instructions targeting
 // the "state" of a program.
-function stateInstructionKeys(
+function stateInstructionKeys<M extends IdlStateMethod>(
   programId: PublicKey,
   provider: Provider,
-  m: IdlStateMethod,
-  accounts: Accounts
-) {
+  m: M,
+  accounts: Accounts<M["accounts"][number]>
+): AccountMeta[] {
   if (m.name === "new") {
     // Ctor `new` method.
     const [programSigner] = findProgramAddressSync([], programId);

+ 4 - 3
ts/src/program/namespace/transaction.ts

@@ -5,7 +5,7 @@ import { InstructionFn } from "./instruction";
 import {
   AllInstructions,
   InstructionContextFn,
-  MakeAllInstructionsNamespace,
+  MakeInstructionsNamespace,
 } from "./types";
 
 export default class TransactionFactory {
@@ -57,8 +57,9 @@ export default class TransactionFactory {
  * ```
  */
 export type TransactionNamespace<
-  IDL extends Idl = Idl
-> = MakeAllInstructionsNamespace<IDL, Transaction>;
+  IDL extends Idl = Idl,
+  I extends AllInstructions<IDL> = AllInstructions<IDL>
+> = MakeInstructionsNamespace<IDL, I, Transaction>;
 
 /**
  * Tx is a function to create a `Transaction` for a given program instruction.

+ 5 - 10
ts/src/program/namespace/types.ts

@@ -24,14 +24,6 @@ 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,
@@ -51,11 +43,14 @@ export type MakeInstructionsNamespace<
 export type InstructionContextFnArgs<
   IDL extends Idl,
   I extends IDL["instructions"][number]
-> = [...ArgsTuple<I["args"], IdlTypes<IDL>>, Context<Accounts<I["accounts"]>>];
+> = [
+  ...ArgsTuple<I["args"], IdlTypes<IDL>>,
+  Context<Accounts<I["accounts"][number]>>
+];
 
 export type InstructionContextFn<
   IDL extends Idl,
-  I extends IDL["instructions"][number],
+  I extends AllInstructions<IDL>,
   Ret
 > = (...args: InstructionContextFnArgs<IDL, I>) => Ret;