Browse Source

Instruction and rpc functions dynamically attached to program

armaniferrante 4 years ago
parent
commit
4d42da0146
13 changed files with 600 additions and 118 deletions
  1. 79 65
      examples/basic/idl.json
  2. 8 1
      examples/basic/src/lib.rs
  3. 35 15
      syn/src/idl.rs
  4. 8 9
      syn/src/parser/file.rs
  5. 6 4
      ts/package.json
  6. 168 0
      ts/src/coder.ts
  7. 1 0
      ts/src/error.ts
  8. 70 0
      ts/src/idl.ts
  9. 14 23
      ts/src/index.ts
  10. 44 0
      ts/src/program.ts
  11. 157 0
      ts/src/rpc.ts
  12. 3 1
      ts/test.js
  13. 7 0
      ts/yarn.lock

+ 79 - 65
examples/basic/idl.json

@@ -1,19 +1,19 @@
 {
   "version": "0.0.0",
   "name": "example",
-  "methods": [
+  "instructions": [
     {
       "name": "create_root",
       "accounts": [
         {
           "name": "authority",
-          "is_mut": false,
-          "is_signer": true
+          "isMut": false,
+          "isSigner": true
         },
         {
           "name": "root",
-          "is_mut": true,
-          "is_signer": false
+          "isMut": true,
+          "isSigner": false
         }
       ],
       "args": [
@@ -32,13 +32,13 @@
       "accounts": [
         {
           "name": "authority",
-          "is_mut": false,
-          "is_signer": true
+          "isMut": false,
+          "isSigner": true
         },
         {
           "name": "root",
-          "is_mut": true,
-          "is_signer": false
+          "isMut": true,
+          "isSigner": false
         }
       ],
       "args": [
@@ -53,13 +53,13 @@
       "accounts": [
         {
           "name": "root",
-          "is_mut": false,
-          "is_signer": false
+          "isMut": false,
+          "isSigner": false
         },
         {
           "name": "leaf",
-          "is_mut": true,
-          "is_signer": false
+          "isMut": true,
+          "isSigner": false
         }
       ],
       "args": [
@@ -80,86 +80,100 @@
       "accounts": [
         {
           "name": "authority",
-          "is_mut": false,
-          "is_signer": true
+          "isMut": false,
+          "isSigner": true
         },
         {
           "name": "root",
-          "is_mut": false,
-          "is_signer": false
+          "isMut": false,
+          "isSigner": false
         },
         {
           "name": "leaf",
-          "is_mut": true,
-          "is_signer": false
+          "isMut": true,
+          "isSigner": false
         }
       ],
       "args": [
         {
           "name": "data",
           "type": "u64"
+        },
+        {
+          "name": "custom",
+          "type": {
+            "option": {
+              "defined": "MyCustomType"
+            }
+          }
         }
       ]
     }
   ],
   "accounts": [
     {
-      "type": "struct",
       "name": "Root",
-      "fields": [
-        {
-          "name": "initialized",
-          "type": "bool"
-        },
-        {
-          "name": "authority",
-          "type": "publicKey"
-        },
-        {
-          "name": "data",
-          "type": "u64"
-        }
-      ]
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "initialized",
+            "type": "bool"
+          },
+          {
+            "name": "authority",
+            "type": "publicKey"
+          },
+          {
+            "name": "data",
+            "type": "u64"
+          }
+        ]
+      }
     },
     {
-      "type": "struct",
       "name": "Leaf",
-      "fields": [
-        {
-          "name": "initialized",
-          "type": "bool"
-        },
-        {
-          "name": "root",
-          "type": "publicKey"
-        },
-        {
-          "name": "data",
-          "type": "u64"
-        },
-        {
-          "name": "custom",
-          "type": {
-            "defined": "MyCustomType"
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "initialized",
+            "type": "bool"
+          },
+          {
+            "name": "root",
+            "type": "publicKey"
+          },
+          {
+            "name": "data",
+            "type": "u64"
+          },
+          {
+            "name": "custom",
+            "type": {
+              "defined": "MyCustomType"
+            }
           }
-        }
-      ]
+        ]
+      }
     }
   ],
   "types": [
     {
-      "type": "struct",
       "name": "MyCustomType",
-      "fields": [
-        {
-          "name": "my_data",
-          "type": "u64"
-        },
-        {
-          "name": "key",
-          "type": "publicKey"
-        }
-      ]
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "my_data",
+            "type": "u64"
+          },
+          {
+            "name": "key",
+            "type": "publicKey"
+          }
+        ]
+      }
     }
   ]
 }

+ 8 - 1
examples/basic/src/lib.rs

@@ -30,9 +30,16 @@ mod example {
         Ok(())
     }
 
-    pub fn update_leaf(ctx: Context<UpdateLeaf>, data: u64) -> ProgramResult {
+    pub fn update_leaf(
+        ctx: Context<UpdateLeaf>,
+        data: u64,
+        custom: Option<MyCustomType>,
+    ) -> ProgramResult {
         let leaf = &mut ctx.accounts.leaf;
         leaf.account.data = data;
+        if let Some(custom) = custom {
+            leaf.custom = custom;
+        }
         Ok(())
     }
 }

+ 35 - 15
syn/src/idl.rs

@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
 pub struct Idl {
     pub version: String,
     pub name: String,
-    pub methods: Vec<IdlMethod>,
+    pub instructions: Vec<IdlInstruction>,
     #[serde(skip_serializing_if = "Vec::is_empty", default)]
     pub accounts: Vec<IdlTypeDef>,
     #[serde(skip_serializing_if = "Vec::is_empty", default)]
@@ -12,13 +12,14 @@ pub struct Idl {
 }
 
 #[derive(Debug, Serialize, Deserialize)]
-pub struct IdlMethod {
+pub struct IdlInstruction {
     pub name: String,
     pub accounts: Vec<IdlAccount>,
     pub args: Vec<IdlField>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
 pub struct IdlAccount {
     pub name: String,
     pub is_mut: bool,
@@ -33,16 +34,17 @@ pub struct IdlField {
 }
 
 #[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "lowercase", tag = "type")]
-pub enum IdlTypeDef {
-    Struct {
-        name: String,
-        fields: Vec<IdlField>,
-    },
-    Enum {
-        name: String,
-        variants: Vec<EnumVariant>,
-    },
+pub struct IdlTypeDef {
+    pub name: String,
+    #[serde(rename = "type")]
+    pub ty: IdlTypeDefTy,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase", tag = "kind")]
+pub enum IdlTypeDefTy {
+    Struct { fields: Vec<IdlField> },
+    Enum { variants: Vec<EnumVariant> },
 }
 
 #[derive(Debug, Serialize, Deserialize)]
@@ -75,26 +77,44 @@ pub enum IdlType {
     String,
     PublicKey,
     Defined(String),
+    Option(Box<IdlType>),
 }
 
+#[derive(Debug, Serialize, Deserialize)]
+pub struct IdlTypePublicKey;
+
 impl std::str::FromStr for IdlType {
     type Err = anyhow::Error;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let r = match s {
+        // Eliminate whitespace.
+        let mut s = s.to_string();
+        s.retain(|c| !c.is_whitespace());
+
+        let r = match s.as_str() {
             "bool" => IdlType::Bool,
             "u8" => IdlType::U8,
             "i8" => IdlType::I8,
             "u16" => IdlType::U16,
             "i16" => IdlType::I16,
             "u32" => IdlType::U32,
-            "I32" => IdlType::I32,
+            "i32" => IdlType::I32,
             "u64" => IdlType::U64,
             "i64" => IdlType::I64,
             "Vec<u8>" => IdlType::Bytes,
             "String" => IdlType::String,
             "Pubkey" => IdlType::PublicKey,
-            _ => IdlType::Defined(s.to_string()),
+            _ => match s.to_string().strip_prefix("Option<") {
+                None => IdlType::Defined(s.to_string()),
+                Some(inner) => {
+                    let inner_ty = Self::from_str(
+                        inner
+                            .strip_suffix(">")
+                            .ok_or(anyhow::anyhow!("Invalid option"))?,
+                    )?;
+                    IdlType::Option(Box::new(inner_ty))
+                }
+            },
         };
         Ok(r)
     }

+ 8 - 9
syn/src/parser/file.rs

@@ -35,7 +35,7 @@ pub fn parse(filename: &str) -> Result<Idl> {
         acc_names
     };
 
-    let methods = p
+    let instructions = p
         .rpcs
         .iter()
         .map(|rpc| {
@@ -63,7 +63,7 @@ pub fn parse(filename: &str) -> Result<Idl> {
                     is_signer: acc.is_signer,
                 })
                 .collect::<Vec<_>>();
-            IdlMethod {
+            IdlInstruction {
                 name: rpc.ident.to_string(),
                 accounts,
                 args,
@@ -76,11 +76,7 @@ pub fn parse(filename: &str) -> Result<Idl> {
     let mut types = vec![];
     let ty_defs = parse_ty_defs(&f)?;
     for ty_def in ty_defs {
-        let name = match &ty_def {
-            IdlTypeDef::Struct { name, .. } => name,
-            IdlTypeDef::Enum { name, .. } => name,
-        };
-        if acc_names.contains(name) {
+        if acc_names.contains(&ty_def.name) {
             accounts.push(ty_def);
         } else {
             types.push(ty_def);
@@ -90,7 +86,7 @@ pub fn parse(filename: &str) -> Result<Idl> {
     Ok(Idl {
         version: "0.0.0".to_string(),
         name: p.name.to_string(),
-        methods,
+        instructions,
         types,
         accounts,
     })
@@ -174,7 +170,10 @@ fn parse_ty_defs(f: &syn::File) -> Result<Vec<IdlTypeDef>> {
                         _ => panic!("Only named structs are allowed."),
                     };
 
-                    return Some(fields.map(|fields| IdlTypeDef::Struct { name, fields }));
+                    return Some(fields.map(|fields| IdlTypeDef {
+                        name,
+                        ty: IdlTypeDefTy::Struct { fields },
+                    }));
                 }
                 None
             }

+ 6 - 4
ts/package.json

@@ -14,7 +14,7 @@
   "scripts": {
     "build": "yarn build:node",
     "build:node": "tsc && tsc -p tsconfig.cjs.json",
-    "watch": "tsc --watch",
+    "watch": "tsc -p tsconfig.cjs.json --watch",
     "test:unit": "jest test/unit",
     "test:integration": "jest test/integration --detectOpenHandles",
     "coverage": "jest --coverage test/unit",
@@ -25,10 +25,12 @@
     "@project-serum/common": "^0.0.1-beta.0",
     "@project-serum/lockup": "^0.0.1-beta.0",
     "@solana/spl-token": "0.0.11",
+    "@types/bn.js": "^4.11.6",
     "bn.js": "^5.1.2",
-    "buffer-layout": "^1.2.0"
+		"buffer-layout": "^1.2.0",
+    "camelcase": "^5.3.1"
   },
-	"devDependencies": {
+  "devDependencies": {
     "@commitlint/cli": "^8.2.0",
     "@commitlint/config-conventional": "^8.2.0",
     "@types/jest": "^26.0.15",
@@ -44,7 +46,7 @@
     "ts-jest": "^26.4.3",
     "ts-node": "^9.0.0",
     "typescript": "^4.0.5"
-	},
+  },
   "peerDependencies": {
     "@solana/web3.js": "^0.86.1"
   }

+ 168 - 0
ts/src/coder.ts

@@ -0,0 +1,168 @@
+import { Layout } from 'buffer-layout';
+import * as borsh from '@project-serum/borsh';
+import { Idl, IdlField, IdlTypeDef } from './idl';
+import { IdlError } from './error';
+
+/**
+ * Coder provides a facade for encoding and decoding all IDL related objects.
+ */
+export default class Coder {
+
+	/**
+	 * Instruction coder.
+	 */
+	readonly instruction: InstructionCoder;
+
+	/**
+	 * Account coder.
+	 */
+	readonly accounts: AccountsCoder;
+
+	constructor(idl: Idl) {
+		this.instruction = new InstructionCoder(idl);
+		this.accounts = new AccountsCoder(idl);
+	}
+}
+
+/**
+ * Encodes and decodes program instructions.
+ */
+class InstructionCoder<T = any> {
+
+	/**
+	 * Instruction enum layout.
+	 */
+	private ixLayout: Layout;
+
+	public constructor(idl: Idl) {
+		this.ixLayout = InstructionCoder.parseIxLayout(idl);
+	}
+
+	public encode(ix: T): Buffer {
+		const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
+		const len = this.ixLayout.encode(ix, buffer);
+		return buffer.slice(0, len);
+	}
+
+	public decode(ix: Buffer): T {
+		return this.ixLayout.decode(ix);
+	}
+
+	private static parseIxLayout(idl: Idl): Layout {
+		let ixLayouts = idl.instructions.map(ix => {
+			let fieldLayouts = ix.args.map(arg => IdlCoder.fieldLayout(arg, idl.types));
+			return borsh.struct(fieldLayouts, ix.name);
+		});
+		return borsh.rustEnum(ixLayouts);
+	}
+}
+
+/**
+ * Encodes and decodes account objects.
+ */
+class AccountsCoder {
+
+	/**
+	 * Maps account type identifier to a layout.
+	 */
+	private accountLayouts: Map<string, Layout>;
+
+	public constructor(idl: Idl) {
+		if (idl.accounts === undefined) {
+			this.accountLayouts = new Map();
+			return;
+		}
+		const layouts = idl
+			.accounts
+			.map(acc => {
+				return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
+			});
+
+		// @ts-ignore
+		this.accountLayouts = new Map(layouts);
+	}
+
+	public encode<T=any>(accountName: string, account: T): Buffer {
+		const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
+		const layout = this.accountLayouts.get(accountName);
+		const len = layout.encode(account, buffer);
+		return buffer.slice(0, len);
+	}
+
+	public decode<T=any>(accountName: string, ix: Buffer): T {
+		const layout = this.accountLayouts.get(accountName);
+		return layout.decode(ix);
+	}
+}
+
+class IdlCoder {
+	public static fieldLayout(field: IdlField, types?: IdlTypeDef[]): Layout {
+		switch (field.type) {
+			case "bool": {
+				return borsh.bool(field.name);
+			}
+			case "u8": {
+				return borsh.u8(field.name);
+			}
+			case "u32": {
+				return borsh.u32(field.name);
+			}
+			case "u64": {
+				return borsh.u64(field.name);
+			}
+			case "i64": {
+				return borsh.i64(field.name);
+			}
+			case "bytes": {
+				return borsh.vecU8(field.name);
+			}
+			case "string": {
+				return borsh.str(field.name);
+			}
+			case "publicKey": {
+				return borsh.publicKey(field.name);
+			}
+				// TODO: all the other types that need to be exported by the borsh package.
+			default: {
+				// @ts-ignore
+				if (field.type.option) {
+					return borsh.option(IdlCoder.fieldLayout({
+						name: undefined,
+						// @ts-ignore
+						type: field.type.option,
+					}, types), field.name)
+					// @ts-ignore
+				} else if (field.type.defined) {
+					// User defined type.
+					if (types === undefined) {
+						throw new IdlError('User defined types not provided');
+					}
+					// @ts-ignore
+					const name = field.type.defined;
+					const filtered = types.filter(t => t.name === name);
+					if (filtered.length !== 1) {
+						console.log(types);
+						console.log(name);
+						throw new IdlError('Type not found');
+					}
+					return IdlCoder.typeDefLayout(filtered[0], types, name);
+				} else {
+					throw new Error(`Not yet implemented: ${field}`);
+				}
+			}
+		}
+	}
+
+	public static typeDefLayout(typeDef: IdlTypeDef, types: IdlTypeDef[], name?: string): Layout {
+		if (typeDef.type.kind === 'struct') {
+			const fieldLayouts = typeDef
+				.type
+				.fields
+				.map(field => IdlCoder.fieldLayout(field, types));
+			return borsh.struct(fieldLayouts, name);
+		} else {
+			// TODO: enums
+			throw new Error('Enums not yet implemented');
+		}
+	}
+}

+ 1 - 0
ts/src/error.ts

@@ -0,0 +1 @@
+export class IdlError extends Error {}

+ 70 - 0
ts/src/idl.ts

@@ -0,0 +1,70 @@
+export type Idl = {
+	version: string;
+	name: string;
+	instructions: IdlInstruction[];
+	accounts?: IdlTypeDef[];
+	types?: IdlTypeDef[];
+}
+
+export type IdlInstruction = {
+	name: string;
+	accounts: IdlAccount[];
+	args: IdlField[];
+}
+
+export type IdlAccount = {
+	name: string;
+	isMut: boolean;
+	isSigner: boolean;
+}
+
+export type IdlField = {
+	name: string;
+	type: IdlType;
+}
+
+export type IdlTypeDef = {
+	name: string;
+	type: IdlTypeDefTy;
+};
+
+type IdlTypeDefTy = {
+	kind: "struct" | "enum";
+	fields?: IdlTypeDefStruct;
+	variants?: IdlTypeDefEnum;
+};
+
+type IdlTypeDefStruct = Array<IdlField>;
+
+// TODO
+type IdlTypeDefEnum = {
+	variants: IdlEnumVariant;
+};
+
+type IdlType = "bool"
+	| "u8"
+	| "i8"
+	| "u16"
+	| "i16"
+	| "u32"
+	| "i32"
+	| "u64"
+	| "i64"
+	| "bytes"
+	| "string"
+	| "publicKey"
+	| IdlTypeOption
+	| IdlTypeDefined;
+
+export type IdlTypeOption = {
+	option: IdlType;
+};
+
+// User defined type.
+export type IdlTypeDefined = {
+	defined: string;
+};
+
+type IdlEnumVariant = {
+	// todo
+};

+ 14 - 23
ts/src/index.ts

@@ -1,29 +1,20 @@
-import { PublicKey } from '@solana/web3.js';
+import { Program } from './program';
+import Coder from './coder';
+import { Provider } from '@project-serum/common';
 
-export class Program {
-	/**
-	 * Address of the program.
-	 */
-	public programId: PublicKey;
+let _provider: Provider | null = null;
 
-	/**
-	 * The inner variables required to implement the Program object.
-	 */
-	public _inner: ProgramInner;
-
-	public constructor(idl: Idl, programId: PublicKey, options?: ProgramOptions) {
-		this.programId = programId;
-		this._inner = {
-			options: options === undefined ? {} : options,
-		};
-		console.log("building",idl);
-	}
+function setProvider(provider: Provider) {
+	_provider = provider;
 }
 
-type Idl = any;
-
-type ProgramInner = {
-	options: ProgramOptions;
+function getProvider(): Provider {
+	return _provider;
 }
 
-type ProgramOptions = {};
+export {
+	Program,
+	Coder,
+	setProvider,
+	getProvider,
+};

+ 44 - 0
ts/src/program.ts

@@ -0,0 +1,44 @@
+import { PublicKey } from '@solana/web3.js';
+import { RpcFactory } from './rpc';
+import { Idl } from './idl';
+import Coder from './coder';
+import { Rpcs, Ixs } from './rpc';
+
+/**
+ * Program is the IDL deserialized representation of a Solana program.
+ */
+export class Program {
+	/**
+	 * Address of the program.
+	 */
+	readonly programId: PublicKey;
+
+	/**
+	 * IDL describing this program's interface.
+	 */
+	readonly idl: Idl;
+
+	/**
+	 * Async functions to invoke instructions against a Solana priogram running
+	 * on a cluster.
+	 */
+	readonly rpc: Rpcs;
+
+	/**
+	 * Functions to build `TransactionInstruction` objects.
+	 */
+	readonly instruction: Ixs;
+
+	public constructor(idl: Idl, programId: PublicKey) {
+		this.idl = idl;
+		this.programId = programId;
+
+		// Build the serializer.
+		const coder = new Coder(idl);
+
+		// Build the dynamic RPC functions.
+		const [rpcs, ixs] = RpcFactory.build(idl, coder, programId);
+		this.rpc = rpcs;
+		this.instruction = ixs;
+	}
+}

+ 157 - 0
ts/src/rpc.ts

@@ -0,0 +1,157 @@
+import camelCase from 'camelcase';
+import { Account, PublicKey, ConfirmOptions, Transaction, TransactionSignature, TransactionInstruction } from '@solana/web3.js';
+import { Idl, IdlInstruction } from './idl';
+import { IdlError } from './error';
+import Coder from './coder';
+import { getProvider } from './';
+
+/**
+ * Rpcs is a dynamically generated object with rpc methods attached.
+ */
+export interface Rpcs {
+  [key: string]: Rpc;
+}
+
+/**
+ * Ixs is a dynamically generated object with ix functions attached.
+ */
+export interface Ixs {
+  [key: string]: Ix;
+}
+
+/**
+ * Rpc is a single rpc method.
+ */
+export type Rpc = (ctx: RpcContext, ...args: any[]) => Promise<any>;
+
+/**
+ * Ix is a function to create a `TransactionInstruction`.
+ */
+export type Ix = (ctx: RpcContext, ...args: any[]) => TransactionInstruction;
+
+/**
+ * Options for an RPC invocation.
+ */
+type RpcOptions = ConfirmOptions;
+
+/**
+ * RpcContext provides all arguments for an RPC/IX invocation that are not
+ * covered by the instruction enum.
+ */
+type RpcContext = {
+	options?: RpcOptions;
+	accounts: RpcAccounts;
+	// Instructions to run *before* the specified rpc instruction.
+	instructions?: TransactionInstruction[];
+	signers?: Array<Account>;
+}
+
+/**
+ * Dynamic object representing a set of accounts given to an rpc/ix invocation.
+ * The name of each key should match the name for that account in the IDL.
+ */
+type RpcAccounts = {
+	[key: string]: PublicKey;
+}
+
+/**
+ * RpcFactory builds an Rpcs object for a given IDL.
+ */
+export class RpcFactory {
+
+  /**
+   * build dynamically generates RPC methods.
+   *
+   * @returns an object with all the RPC methods attached.
+   */
+	public static build(idl: Idl, coder: Coder, programId: PublicKey): [Rpcs, Ixs] {
+		const rpcs: Rpcs = {};
+		const ixFns: Ixs = {};
+		idl.instructions.forEach(idlIx=> {
+			// Function to create a raw `TransactionInstruction`.
+			const ix = RpcFactory.buildIx(
+				idlIx,
+				coder,
+				programId,
+			);
+			// Function to invoke an RPC against a cluster.
+			const rpc = RpcFactory.buildRpc(ix);
+
+			const name = camelCase(idlIx.name);
+			rpcs[name] = rpc;
+			ixFns[name] = ix;
+		});
+		return [rpcs, ixFns];
+	}
+
+	private static buildIx(idlIx: IdlInstruction, coder: Coder, programId: PublicKey): Ix {
+    if (idlIx.name === '_inner') {
+      throw new IdlError('the _inner name is reserved');
+    }
+
+    const ix = (ctx: RpcContext, ...args: any[]): TransactionInstruction => {
+			validateAccounts(idlIx, ctx.accounts);
+			validateInstruction(idlIx, args)
+
+			const keys = idlIx
+				.accounts
+				.map(acc => {
+					return { pubkey: ctx.accounts[acc.name], isWritable: acc.isMut, isSigner: acc.isSigner, }
+				});
+
+			return new TransactionInstruction({
+				keys,
+				programId,
+				data: coder.instruction.encode(toInstruction(idlIx, args)),
+			});
+    };
+
+    return ix;
+	}
+
+	private static buildRpc(ixFn: Ix): Rpc {
+    const rpc = async (ctx: RpcContext, ...args: any[]): Promise<TransactionSignature> => {
+			const tx = new Transaction();
+			if (ctx.instructions !== undefined) {
+				tx.add(...ctx.instructions);
+			}
+			tx.add(ixFn(ctx, ...args));
+			const provider = getProvider();
+			if (provider === null) {
+				throw new Error('Provider not found');
+			}
+
+			const txSig = await provider.send(tx, ctx.signers);
+			return txSig;
+    };
+
+    return rpc;
+	}
+}
+
+function toInstruction(idlIx: IdlInstruction, ...args: any[]) {
+	if (idlIx.args.length != args.length) {
+		throw new Error('Invalid argument length');
+	}
+	const ix: { [key: string]: any } = {};
+	let idx = 0;
+	idlIx.args.forEach(ixArg => {
+		ix[ixArg.name] = args[idx];
+		idx += 1;
+	});
+	return ix;
+}
+
+// Throws error if any account required for the `ix` is not given.
+function validateAccounts(ix: IdlInstruction, accounts: RpcAccounts) {
+	ix.accounts.forEach(acc => {
+		if (accounts[acc.name] === undefined) {
+			throw new Error(`Invalid arguments: ${acc.name} not provided.`);
+		}
+	});
+}
+
+// Throws error if any argument required for the `ix` is not given.
+function validateInstruction(ix: IdlInstruction, ...args: any[]) {
+	// todo
+}

+ 3 - 1
ts/test.js

@@ -1,10 +1,12 @@
 const anchor = require('.');
 
-console.log(anchor)
 function test() {
 		const fs = require('fs');
 		const idl = JSON.parse(fs.readFileSync('../examples/basic/idl.json', 'utf8'));
 		const program = new anchor.Program(idl);
+
+		console.log('RPCS', program.rpc);
+		console.log('IXS', program.instruction);
 }
 
 test();

+ 7 - 0
ts/yarn.lock

@@ -797,6 +797,13 @@
   dependencies:
     "@babel/types" "^7.3.0"
 
+"@types/bn.js@^4.11.6":
+  version "4.11.6"
+  resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
+  integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
+  dependencies:
+    "@types/node" "*"
+
 "@types/connect@^3.4.33":
   version "3.4.34"
   resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901"