ts: Improve TypeScript types (#739)

This commit is contained in:
Ian Macalinao 2021-09-16 13:42:11 -07:00 committed by GitHub
parent 8eec4d3e3d
commit 90df0b1976
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 209 additions and 220 deletions

View File

@ -5,7 +5,7 @@
"main": "dist/cjs/index.js", "main": "dist/cjs/index.js",
"module": "dist/esm/index.js", "module": "dist/esm/index.js",
"license": "(MIT OR Apache-2.0)", "license": "(MIT OR Apache-2.0)",
"types": "dist/index.d.ts", "types": "dist/cjs/index.d.ts",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@ -58,6 +58,6 @@
"ts-jest": "^26.4.3", "ts-jest": "^26.4.3",
"ts-node": "^9.0.0", "ts-node": "^9.0.0",
"typedoc": "^0.20.36", "typedoc": "^0.20.36",
"typescript": "^4.0.5" "typescript": "^4.4.3"
} }
} }

View File

@ -35,6 +35,9 @@ export class AccountsCoder {
): Promise<Buffer> { ): Promise<Buffer> {
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer. const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
const layout = this.accountLayouts.get(accountName); const layout = this.accountLayouts.get(accountName);
if (!layout) {
throw new Error(`Unknown account: ${accountName}`);
}
const len = layout.encode(account, buffer); const len = layout.encode(account, buffer);
let accountData = buffer.slice(0, len); let accountData = buffer.slice(0, len);
let discriminator = await accountDiscriminator(accountName); let discriminator = await accountDiscriminator(accountName);
@ -45,6 +48,9 @@ export class AccountsCoder {
// Chop off the discriminator before decoding. // Chop off the discriminator before decoding.
const data = ix.slice(8); const data = ix.slice(8);
const layout = this.accountLayouts.get(accountName); const layout = this.accountLayouts.get(accountName);
if (!layout) {
throw new Error(`Unknown account: ${accountName}`);
}
return layout.decode(data); return layout.decode(data);
} }
} }

View File

@ -3,29 +3,21 @@ import { sha256 } from "js-sha256";
import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl"; import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl";
import { IdlError } from "../error"; import { IdlError } from "../error";
export function accountSize( export function accountSize(idl: Idl, idlAccount: IdlTypeDef): number {
idl: Idl,
idlAccount: IdlTypeDef
): number | undefined {
if (idlAccount.type.kind === "enum") { if (idlAccount.type.kind === "enum") {
let variantSizes = idlAccount.type.variants.map( let variantSizes = idlAccount.type.variants.map(
(variant: IdlEnumVariant) => { (variant: IdlEnumVariant) => {
if (variant.fields === undefined) { if (variant.fields === undefined) {
return 0; return 0;
} }
return ( return variant.fields
variant.fields
// @ts-ignore
.map((f: IdlField | IdlType) => { .map((f: IdlField | IdlType) => {
// @ts-ignore if (!(typeof f === "object" && "name" in f)) {
if (f.name === undefined) {
throw new Error("Tuple enum variants not yet implemented."); throw new Error("Tuple enum variants not yet implemented.");
} }
// @ts-ignore
return typeSize(idl, f.type); return typeSize(idl, f.type);
}) })
.reduce((a: number, b: number) => a + b) .reduce((a: number, b: number) => a + b);
);
} }
); );
return Math.max(...variantSizes) + 1; return Math.max(...variantSizes) + 1;
@ -71,19 +63,14 @@ function typeSize(idl: Idl, ty: IdlType): number {
case "publicKey": case "publicKey":
return 32; return 32;
default: default:
// @ts-ignore if ("vec" in ty) {
if (ty.vec !== undefined) {
return 1; return 1;
} }
// @ts-ignore if ("option" in ty) {
if (ty.option !== undefined) {
// @ts-ignore
return 1 + typeSize(idl, ty.option); return 1 + typeSize(idl, ty.option);
} }
// @ts-ignore if ("defined" in ty) {
if (ty.defined !== undefined) { const filtered = idl.types?.filter((t) => t.name === ty.defined) ?? [];
// @ts-ignore
const filtered = idl.types.filter((t) => t.name === ty.defined);
if (filtered.length !== 1) { if (filtered.length !== 1) {
throw new IdlError(`Type not found: ${JSON.stringify(ty)}`); throw new IdlError(`Type not found: ${JSON.stringify(ty)}`);
} }
@ -91,13 +78,9 @@ function typeSize(idl: Idl, ty: IdlType): number {
return accountSize(idl, typeDef); return accountSize(idl, typeDef);
} }
// @ts-ignore if ("array" in ty) {
if (ty.array !== undefined) {
// @ts-ignore
let arrayTy = ty.array[0]; let arrayTy = ty.array[0];
// @ts-ignore
let arraySize = ty.array[1]; let arraySize = ty.array[1];
// @ts-ignore
return typeSize(idl, arrayTy) * arraySize; return typeSize(idl, arrayTy) * arraySize;
} }
throw new Error(`Invalid type ${JSON.stringify(ty)}`); throw new Error(`Invalid type ${JSON.stringify(ty)}`);

View File

@ -46,7 +46,7 @@ export class EventCoder {
); );
} }
public decode(log: string): Event | null { public decode<T = Record<string, unknown>>(log: string): Event<T> | null {
let logArr: Buffer; let logArr: Buffer;
// This will throw if log length is not a multiple of 4. // This will throw if log length is not a multiple of 4.
try { try {
@ -63,7 +63,10 @@ export class EventCoder {
} }
const layout = this.layouts.get(eventName); const layout = this.layouts.get(eventName);
const data = layout.decode(logArr.slice(8)); if (!layout) {
throw new Error(`Unknown event: ${eventName}`);
}
const data = layout.decode(logArr.slice(8)) as T;
return { data, name: eventName }; return { data, name: eventName };
} }
} }

View File

@ -5,7 +5,10 @@ import { IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl";
import { IdlError } from "../error"; import { IdlError } from "../error";
export class IdlCoder { export class IdlCoder {
public static fieldLayout(field: IdlField, types?: IdlTypeDef[]): Layout { public static fieldLayout(
field: { name?: string } & Pick<IdlField, "type">,
types?: IdlTypeDef[]
): Layout {
const fieldName = const fieldName =
field.name !== undefined ? camelCase(field.name) : undefined; field.name !== undefined ? camelCase(field.name) : undefined;
switch (field.type) { switch (field.type) {
@ -52,8 +55,7 @@ export class IdlCoder {
return borsh.publicKey(fieldName); return borsh.publicKey(fieldName);
} }
default: { default: {
// @ts-ignore if ("vec" in field.type) {
if (field.type.vec) {
return borsh.vec( return borsh.vec(
IdlCoder.fieldLayout( IdlCoder.fieldLayout(
{ {
@ -65,36 +67,30 @@ export class IdlCoder {
), ),
fieldName fieldName
); );
// @ts-ignore } else if ("option" in field.type) {
} else if (field.type.option) {
return borsh.option( return borsh.option(
IdlCoder.fieldLayout( IdlCoder.fieldLayout(
{ {
name: undefined, name: undefined,
// @ts-ignore
type: field.type.option, type: field.type.option,
}, },
types types
), ),
fieldName fieldName
); );
// @ts-ignore } else if ("defined" in field.type) {
} else if (field.type.defined) { const defined = field.type.defined;
// User defined type. // User defined type.
if (types === undefined) { if (types === undefined) {
throw new IdlError("User defined types not provided"); throw new IdlError("User defined types not provided");
} }
// @ts-ignore const filtered = types.filter((t) => t.name === defined);
const filtered = types.filter((t) => t.name === field.type.defined);
if (filtered.length !== 1) { if (filtered.length !== 1) {
throw new IdlError(`Type not found: ${JSON.stringify(field)}`); throw new IdlError(`Type not found: ${JSON.stringify(field)}`);
} }
return IdlCoder.typeDefLayout(filtered[0], types, fieldName); return IdlCoder.typeDefLayout(filtered[0], types, fieldName);
// @ts-ignore } else if ("array" in field.type) {
} else if (field.type.array) {
// @ts-ignore
let arrayTy = field.type.array[0]; let arrayTy = field.type.array[0];
// @ts-ignore
let arrayLen = field.type.array[1]; let arrayLen = field.type.array[1];
let innerLayout = IdlCoder.fieldLayout( let innerLayout = IdlCoder.fieldLayout(
{ {
@ -113,7 +109,7 @@ export class IdlCoder {
public static typeDefLayout( public static typeDefLayout(
typeDef: IdlTypeDef, typeDef: IdlTypeDef,
types: IdlTypeDef[], types: IdlTypeDef[] = [],
name?: string name?: string
): Layout { ): Layout {
if (typeDef.type.kind === "struct") { if (typeDef.type.kind === "struct") {

View File

@ -10,6 +10,7 @@ import {
IdlTypeDef, IdlTypeDef,
IdlAccount, IdlAccount,
IdlAccountItem, IdlAccountItem,
IdlTypeDefTyStruct,
} from "../idl"; } from "../idl";
import { IdlCoder } from "./idl"; import { IdlCoder } from "./idl";
import { sighash } from "./common"; import { sighash } from "./common";
@ -77,7 +78,11 @@ export class InstructionCoder {
private _encode(nameSpace: string, ixName: string, ix: any): Buffer { private _encode(nameSpace: string, ixName: string, ix: any): Buffer {
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer. const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
const methodName = camelCase(ixName); const methodName = camelCase(ixName);
const len = this.ixLayout.get(methodName).encode(ix, buffer); const layout = this.ixLayout.get(methodName);
if (!layout) {
throw new Error(`Unknown method: ${methodName}`);
}
const len = layout.encode(ix, buffer);
const data = buffer.slice(0, len); const data = buffer.slice(0, len);
return Buffer.concat([sighash(nameSpace, ixName), data]); return Buffer.concat([sighash(nameSpace, ixName), data]);
} }
@ -215,21 +220,20 @@ class InstructionFormatter {
return idlType as string; return idlType as string;
} }
// @ts-ignore if ("vec" in idlType) {
if (idlType.vec) {
// @ts-ignore
return `Vec<${this.formatIdlType(idlType.vec)}>`; return `Vec<${this.formatIdlType(idlType.vec)}>`;
} }
// @ts-ignore if ("option" in idlType) {
if (idlType.option) {
// @ts-ignore
return `Option<${this.formatIdlType(idlType.option)}>`; return `Option<${this.formatIdlType(idlType.option)}>`;
} }
// @ts-ignore if ("defined" in idlType) {
if (idlType.defined) {
// @ts-ignore
return idlType.defined; return idlType.defined;
} }
if ("array" in idlType) {
return `Array<${idlType.array[0]}; ${idlType.array[1]}>`;
}
throw new Error(`Unknown IDL type: ${idlType}`);
} }
private static formatIdlData( private static formatIdlData(
@ -296,9 +300,10 @@ class InstructionFormatter {
types: IdlTypeDef[] types: IdlTypeDef[]
): string { ): string {
if (typeDef.type.kind === "struct") { if (typeDef.type.kind === "struct") {
const struct: IdlTypeDefTyStruct = typeDef.type;
const fields = Object.keys(data) const fields = Object.keys(data)
.map((k) => { .map((k) => {
const f = typeDef.type.fields.filter((f) => f.name === k)[0]; const f = struct.fields.filter((f) => f.name === k)[0];
if (f === undefined) { if (f === undefined) {
throw new Error("Unable to find type"); throw new Error("Unable to find type");
} }
@ -314,12 +319,13 @@ class InstructionFormatter {
} }
// Struct enum. // Struct enum.
if (typeDef.type.variants[0].name) { if (typeDef.type.variants[0].name) {
const variants = typeDef.type.variants;
const variant = Object.keys(data)[0]; const variant = Object.keys(data)[0];
const enumType = data[variant]; const enumType = data[variant];
const namedFields = Object.keys(enumType) const namedFields = Object.keys(enumType)
.map((f) => { .map((f) => {
const fieldData = enumType[f]; const fieldData = enumType[f];
const idlField = typeDef.type.variants[variant]?.filter( const idlField = variants[variant]?.filter(
(v: IdlField) => v.name === f (v: IdlField) => v.name === f
)[0]; )[0];
if (idlField === undefined) { if (idlField === undefined) {

View File

@ -16,8 +16,9 @@ export class TypesCoder {
this.layouts = new Map(); this.layouts = new Map();
return; return;
} }
const layouts = idl.types.map((acc) => { const types = idl.types;
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)]; const layouts = types.map((acc) => {
return [acc.name, IdlCoder.typeDefLayout(acc, types)];
}); });
// @ts-ignore // @ts-ignore
@ -27,12 +28,18 @@ export class TypesCoder {
public encode<T = any>(accountName: string, account: T): Buffer { public encode<T = any>(accountName: string, account: T): Buffer {
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer. const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
const layout = this.layouts.get(accountName); const layout = this.layouts.get(accountName);
if (!layout) {
throw new Error(`Unknown account type: ${accountName}`);
}
const len = layout.encode(account, buffer); const len = layout.encode(account, buffer);
return buffer.slice(0, len); return buffer.slice(0, len);
} }
public decode<T = any>(accountName: string, ix: Buffer): T { public decode<T = any>(accountName: string, ix: Buffer): T {
const layout = this.layouts.get(accountName); const layout = this.layouts.get(accountName);
if (!layout) {
throw new Error(`Unknown account type: ${accountName}`);
}
return layout.decode(ix); return layout.decode(ix);
} }
} }

View File

@ -1,4 +1,9 @@
export class IdlError extends Error {} export class IdlError extends Error {
constructor(message: string) {
super(message);
this.name = "IdlError";
}
}
// An error from a user defined program. // An error from a user defined program.
export class ProgramError extends Error { export class ProgramError extends Error {

View File

@ -60,12 +60,18 @@ export type IdlTypeDef = {
type: IdlTypeDefTy; type: IdlTypeDefTy;
}; };
type IdlTypeDefTy = { export type IdlTypeDefTyStruct = {
kind: "struct" | "enum"; kind: "struct";
fields?: IdlTypeDefStruct; fields: IdlTypeDefStruct;
variants?: IdlEnumVariant[];
}; };
export type IdlTypeDefTyEnum = {
kind: "enum";
variants: IdlEnumVariant[];
};
type IdlTypeDefTy = IdlTypeDefTyEnum | IdlTypeDefTyStruct;
type IdlTypeDefStruct = Array<IdlField>; type IdlTypeDefStruct = Array<IdlField>;
export type IdlType = export type IdlType =

View File

@ -1,11 +1,13 @@
import BN from "bn.js"; export { default as BN } from "bn.js";
import * as web3 from "@solana/web3.js"; export * as web3 from "@solana/web3.js";
import Provider, { export {
default as Provider,
getProvider, getProvider,
setProvider, setProvider,
NodeWallet as Wallet, NodeWallet as Wallet,
} from "./provider"; } from "./provider";
import Coder, { export {
default as Coder,
InstructionCoder, InstructionCoder,
EventCoder, EventCoder,
StateCoder, StateCoder,
@ -13,15 +15,15 @@ import Coder, {
AccountsCoder, AccountsCoder,
} from "./coder"; } from "./coder";
import { ProgramError } from "./error"; export * from "./error";
import { Instruction } from "./coder/instruction"; export { Instruction } from "./coder/instruction";
import { Idl } from "./idl"; export { Idl } from "./idl";
import workspace from "./workspace"; export { default as workspace } from "./workspace";
import * as utils from "./utils"; export * as utils from "./utils";
import { Program } from "./program"; export { Program } from "./program";
import { Address } from "./program/common"; export { Address } from "./program/common";
import { Event } from "./program/event"; export { Event } from "./program/event";
import { export {
ProgramAccount, ProgramAccount,
AccountNamespace, AccountNamespace,
AccountClient, AccountClient,
@ -35,43 +37,5 @@ import {
InstructionNamespace, InstructionNamespace,
InstructionFn, InstructionFn,
} from "./program/namespace"; } from "./program/namespace";
import { Context, Accounts } from "./program/context"; export { Context, Accounts } from "./program/context";
import { EventParser } from "./program/event"; export { EventParser } from "./program/event";
export {
workspace,
Program,
AccountNamespace,
AccountClient,
StateClient,
RpcNamespace,
RpcFn,
SimulateNamespace,
SimulateFn,
TransactionNamespace,
TransactionFn,
InstructionNamespace,
InstructionFn,
ProgramAccount,
Context,
Accounts,
Coder,
InstructionCoder,
EventCoder,
StateCoder,
TypesCoder,
AccountsCoder,
Event,
Instruction,
setProvider,
getProvider,
Provider,
BN,
web3,
Idl,
utils,
Wallet,
Address,
EventParser,
ProgramError,
};

View File

@ -40,13 +40,11 @@ export function toInstruction(
// Throws error if any account required for the `ix` is not given. // Throws error if any account required for the `ix` is not given.
export function validateAccounts( export function validateAccounts(
ixAccounts: IdlAccountItem[], ixAccounts: IdlAccountItem[],
accounts: Accounts accounts: Accounts = {}
) { ) {
ixAccounts.forEach((acc) => { ixAccounts.forEach((acc) => {
// @ts-ignore if ("accounts" in acc) {
if (acc.accounts !== undefined) { validateAccounts(acc.accounts, accounts[acc.name] as Accounts);
// @ts-ignore
validateAccounts(acc.accounts, accounts[acc.name]);
} else { } else {
if (accounts[acc.name] === undefined) { if (accounts[acc.name] === undefined) {
throw new Error(`Invalid arguments: ${acc.name} not provided.`); throw new Error(`Invalid arguments: ${acc.name} not provided.`);

View File

@ -6,9 +6,9 @@ import Provider from "../provider";
const LOG_START_INDEX = "Program log: ".length; const LOG_START_INDEX = "Program log: ".length;
// Deserialized event. // Deserialized event.
export type Event = { export type Event<T = Record<string, unknown>> = {
name: string; name: string;
data: Object; data: T;
}; };
type EventCallback = (event: any, slot: number) => void; type EventCallback = (event: any, slot: number) => void;
@ -71,7 +71,7 @@ export class EventManager {
} }
this._eventListeners.set( this._eventListeners.set(
eventName, eventName,
this._eventListeners.get(eventName).concat(listener) (this._eventListeners.get(eventName) ?? []).concat(listener)
); );
// Store the callback into the listener map. // Store the callback into the listener map.
@ -93,8 +93,11 @@ export class EventManager {
const allListeners = this._eventListeners.get(event.name); const allListeners = this._eventListeners.get(event.name);
if (allListeners) { if (allListeners) {
allListeners.forEach((listener) => { allListeners.forEach((listener) => {
const [, callback] = this._eventCallbacks.get(listener); const listenerCb = this._eventCallbacks.get(listener);
if (listenerCb) {
const [, callback] = listenerCb;
callback(event.data, ctx.slot); callback(event.data, ctx.slot);
}
}); });
} }
}); });
@ -128,6 +131,7 @@ export class EventManager {
// Kill the websocket connection if all listeners have been removed. // Kill the websocket connection if all listeners have been removed.
if (this._eventCallbacks.size == 0) { if (this._eventCallbacks.size == 0) {
assert.ok(this._eventListeners.size === 0); assert.ok(this._eventListeners.size === 0);
if (this._onLogsSubscriptionId !== undefined) {
await this._provider.connection.removeOnLogsListener( await this._provider.connection.removeOnLogsListener(
this._onLogsSubscriptionId this._onLogsSubscriptionId
); );
@ -135,6 +139,7 @@ export class EventManager {
} }
} }
} }
}
export class EventParser { export class EventParser {
private coder: Coder; private coder: Coder;
@ -243,7 +248,10 @@ class ExecutionContext {
constructor(log: string) { constructor(log: string) {
// Assumes the first log in every transaction is an `invoke` log from the // Assumes the first log in every transaction is an `invoke` log from the
// runtime. // runtime.
const program = /^Program (.*) invoke.*$/g.exec(log)[1]; const program = /^Program (.*) invoke.*$/g.exec(log)?.[1];
if (!program) {
throw new Error(`Could not find program invocation log line`);
}
this.stack = [program]; this.stack = [program];
} }

View File

@ -200,7 +200,7 @@ export class Program {
* one can use this to send transactions and read accounts for the state * one can use this to send transactions and read accounts for the state
* abstraction. * abstraction.
*/ */
readonly state: StateClient; readonly state?: StateClient;
/** /**
* Address of the program. * Address of the program.
@ -210,14 +210,6 @@ export class Program {
} }
private _programId: PublicKey; private _programId: PublicKey;
/**
* IDL defining the program's interface.
*/
public get idl(): Idl {
return this._idl;
}
private _idl: Idl;
/** /**
* Coder for serializing requests. * Coder for serializing requests.
*/ */
@ -226,14 +218,6 @@ export class Program {
} }
private _coder: Coder; private _coder: Coder;
/**
* Wallet and network provider.
*/
public get provider(): Provider {
return this._provider;
}
private _provider: Provider;
/** /**
* Handles event subscriptions. * Handles event subscriptions.
*/ */
@ -245,19 +229,23 @@ export class Program {
* @param provider The network and wallet context to use. If not provided * @param provider The network and wallet context to use. If not provided
* then uses [[getProvider]]. * then uses [[getProvider]].
*/ */
public constructor(idl: Idl, programId: Address, provider?: Provider) { public constructor(
/**
* IDL defining the program's interface.
*/
public readonly idl: Idl,
programId: Address,
/**
* Wallet and network provider.
*/
public readonly provider: Provider = getProvider()
) {
programId = translateAddress(programId); programId = translateAddress(programId);
// Fields. // Fields.
this._idl = idl;
this._programId = programId; this._programId = programId;
this._provider = provider ?? getProvider();
this._coder = new Coder(idl); this._coder = new Coder(idl);
this._events = new EventManager( this._events = new EventManager(this._programId, provider, this._coder);
this._programId,
this._provider,
this._coder
);
// Dynamic namespaces. // Dynamic namespaces.
const [ const [
@ -267,7 +255,7 @@ export class Program {
account, account,
simulate, simulate,
state, state,
] = NamespaceFactory.build(idl, this._coder, programId, this._provider); ] = NamespaceFactory.build(idl, this._coder, programId, provider);
this.rpc = rpc; this.rpc = rpc;
this.instruction = instruction; this.instruction = instruction;
this.transaction = transaction; this.transaction = transaction;
@ -285,10 +273,16 @@ export class Program {
* @param programId The on-chain address of the program. * @param programId The on-chain address of the program.
* @param provider The network and wallet context. * @param provider The network and wallet context.
*/ */
public static async at(address: Address, provider?: Provider) { public static async at(
address: Address,
provider?: Provider
): Promise<Program> {
const programId = translateAddress(address); const programId = translateAddress(address);
const idl = await Program.fetchIdl(programId, provider); const idl = await Program.fetchIdl(programId, provider);
if (!idl) {
throw new Error(`IDL not found for program: ${address.toString()}`);
}
return new Program(idl, programId, provider); return new Program(idl, programId, provider);
} }
@ -301,12 +295,18 @@ export class Program {
* @param programId The on-chain address of the program. * @param programId The on-chain address of the program.
* @param provider The network and wallet context. * @param provider The network and wallet context.
*/ */
public static async fetchIdl(address: Address, provider?: Provider) { public static async fetchIdl(
address: Address,
provider?: Provider
): Promise<Idl | null> {
provider = provider ?? getProvider(); provider = provider ?? getProvider();
const programId = translateAddress(address); const programId = translateAddress(address);
const idlAddr = await idlAddress(programId); const idlAddr = await idlAddress(programId);
const accountInfo = await provider.connection.getAccountInfo(idlAddr); const accountInfo = await provider.connection.getAccountInfo(idlAddr);
if (!accountInfo) {
return null;
}
// Chop off account discriminator. // Chop off account discriminator.
let idlAccount = decodeIdlAccount(accountInfo.data.slice(8)); let idlAccount = decodeIdlAccount(accountInfo.data.slice(8));
const inflatedIdl = inflate(idlAccount.data); const inflatedIdl = inflate(idlAccount.data);

View File

@ -28,7 +28,7 @@ export default class AccountFactory {
): AccountNamespace { ): AccountNamespace {
const accountFns: AccountNamespace = {}; const accountFns: AccountNamespace = {};
idl.accounts.forEach((idlAccount) => { idl.accounts?.forEach((idlAccount) => {
const name = camelCase(idlAccount.name); const name = camelCase(idlAccount.name);
accountFns[name] = new AccountClient( accountFns[name] = new AccountClient(
idl, idl,
@ -113,7 +113,8 @@ export class AccountClient {
this._programId = programId; this._programId = programId;
this._provider = provider ?? getProvider(); this._provider = provider ?? getProvider();
this._coder = coder ?? new Coder(idl); this._coder = coder ?? new Coder(idl);
this._size = ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount); this._size =
ACCOUNT_DISCRIMINATOR_SIZE + (accountSize(idl, idlAccount) ?? 0);
} }
/** /**
@ -177,8 +178,9 @@ export class AccountClient {
* changes. * changes.
*/ */
subscribe(address: Address, commitment?: Commitment): EventEmitter { subscribe(address: Address, commitment?: Commitment): EventEmitter {
if (subscriptions.get(address.toString())) { const sub = subscriptions.get(address.toString());
return subscriptions.get(address.toString()).ee; if (sub) {
return sub.ee;
} }
const ee = new EventEmitter(); const ee = new EventEmitter();

View File

@ -34,7 +34,7 @@ export default class NamespaceFactory {
TransactionNamespace, TransactionNamespace,
AccountNamespace, AccountNamespace,
SimulateNamespace, SimulateNamespace,
StateClient StateClient | undefined
] { ] {
const rpc: RpcNamespace = {}; const rpc: RpcNamespace = {};
const instruction: InstructionNamespace = {}; const instruction: InstructionNamespace = {};

View File

@ -1,4 +1,8 @@
import { PublicKey, TransactionInstruction } from "@solana/web3.js"; import {
AccountMeta,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { IdlAccount, IdlInstruction, IdlAccountItem } from "../../idl"; import { IdlAccount, IdlInstruction, IdlAccountItem } from "../../idl";
import { IdlError } from "../../error"; import { IdlError } from "../../error";
import { import {
@ -41,19 +45,22 @@ export default class InstructionNamespaceFactory {
}; };
// Utility fn for ordering the accounts for this instruction. // Utility fn for ordering the accounts for this instruction.
ix["accounts"] = (accs: Accounts) => { ix["accounts"] = (accs: Accounts = {}) => {
return InstructionNamespaceFactory.accountsArray(accs, idlIx.accounts); return InstructionNamespaceFactory.accountsArray(accs, idlIx.accounts);
}; };
return ix; return ix;
} }
public static accountsArray(ctx: Accounts, accounts: IdlAccountItem[]): any { public static accountsArray(
ctx: Accounts,
accounts: IdlAccountItem[]
): AccountMeta[] {
return accounts return accounts
.map((acc: IdlAccountItem) => { .map((acc: IdlAccountItem) => {
// Nested accounts. // Nested accounts.
// @ts-ignore const nestedAccounts: IdlAccountItem[] | undefined =
const nestedAccounts: IdlAccountItem[] | undefined = acc.accounts; "accounts" in acc ? acc.accounts : undefined;
if (nestedAccounts !== undefined) { if (nestedAccounts !== undefined) {
const rpcAccs = ctx[acc.name] as Accounts; const rpcAccs = ctx[acc.name] as Accounts;
return InstructionNamespaceFactory.accountsArray( return InstructionNamespaceFactory.accountsArray(
@ -113,7 +120,7 @@ export interface InstructionNamespace {
*/ */
export type InstructionFn = IxProps & ((...args: any[]) => any); export type InstructionFn = IxProps & ((...args: any[]) => any);
type IxProps = { type IxProps = {
accounts: (ctx: Accounts) => any; accounts: (ctx: Accounts) => AccountMeta[];
}; };
export type InstructionEncodeFn = (ixName: string, ix: any) => Buffer; export type InstructionEncodeFn = (ixName: string, ix: any) => Buffer;

View File

@ -1,7 +1,7 @@
import { TransactionSignature } from "@solana/web3.js"; import { TransactionSignature } from "@solana/web3.js";
import Provider from "../../provider"; import Provider from "../../provider";
import { IdlInstruction } from "../../idl"; import { IdlInstruction } from "../../idl";
import { splitArgsAndCtx } from "../context"; import { Context, splitArgsAndCtx } from "../context";
import { TransactionFn } from "./transaction"; import { TransactionFn } from "./transaction";
import { ProgramError } from "../../error"; import { ProgramError } from "../../error";

View File

@ -1,4 +1,8 @@
import { PublicKey } from "@solana/web3.js"; import {
PublicKey,
RpcResponseAndContext,
SimulatedTransactionResponse,
} from "@solana/web3.js";
import Provider from "../../provider"; import Provider from "../../provider";
import { IdlInstruction } from "../../idl"; import { IdlInstruction } from "../../idl";
import { splitArgsAndCtx } from "../context"; import { splitArgsAndCtx } from "../context";
@ -21,7 +25,9 @@ export default class SimulateFactory {
const simulate = async (...args: any[]): Promise<SimulateResponse> => { const simulate = async (...args: any[]): Promise<SimulateResponse> => {
const tx = txFn(...args); const tx = txFn(...args);
const [, ctx] = splitArgsAndCtx(idlIx, [...args]); const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
let resp = undefined; let resp:
| RpcResponseAndContext<SimulatedTransactionResponse>
| undefined = undefined;
try { try {
resp = await provider.simulate(tx, ctx.signers, ctx.options); resp = await provider.simulate(tx, ctx.signers, ctx.options);
} catch (err) { } catch (err) {
@ -43,7 +49,7 @@ export default class SimulateFactory {
throw new Error("Simulated logs not found"); throw new Error("Simulated logs not found");
} }
const events = []; const events: Event[] = [];
if (idl.events) { if (idl.events) {
let parser = new EventParser(programId, coder); let parser = new EventParser(programId, coder);
parser.parseLogs(logs, (event) => { parser.parseLogs(logs, (event) => {

View File

@ -56,37 +56,25 @@ export class StateClient {
} }
private _programId: PublicKey; private _programId: PublicKey;
/**
* Returns the client's wallet and network provider.
*/
get provider(): Provider {
return this._provider;
}
private _provider: Provider;
/**
* Returns the coder.
*/
get coder(): Coder {
return this._coder;
}
private _address: PublicKey; private _address: PublicKey;
private _coder: Coder;
private _idl: Idl; private _idl: Idl;
private _sub: Subscription | null; private _sub: Subscription | null;
constructor( constructor(
idl: Idl, idl: Idl,
programId: PublicKey, programId: PublicKey,
provider?: Provider, /**
coder?: Coder * Returns the client's wallet and network provider.
*/
public readonly provider: Provider = getProvider(),
/**
* Returns the coder.
*/
public readonly coder: Coder = new Coder(idl)
) { ) {
this._idl = idl; this._idl = idl;
this._programId = programId; this._programId = programId;
this._address = programStateAddress(programId); this._address = programStateAddress(programId);
this._provider = provider ?? getProvider();
this._coder = coder ?? new Coder(idl);
this._sub = null; this._sub = null;
// Build namespaces. // Build namespaces.
@ -99,7 +87,7 @@ export class StateClient {
let transaction: TransactionNamespace = {}; let transaction: TransactionNamespace = {};
let rpc: RpcNamespace = {}; let rpc: RpcNamespace = {};
idl.state.methods.forEach((m: IdlStateMethod) => { idl.state?.methods.forEach((m: IdlStateMethod) => {
// Build instruction method. // Build instruction method.
const ixItem = InstructionNamespaceFactory.build( const ixItem = InstructionNamespaceFactory.build(
m, m,
@ -147,9 +135,11 @@ export class StateClient {
throw new Error(`Account does not exist ${addr.toString()}`); throw new Error(`Account does not exist ${addr.toString()}`);
} }
// Assert the account discriminator is correct. // Assert the account discriminator is correct.
const expectedDiscriminator = await stateDiscriminator( const state = this._idl.state;
this._idl.state.struct.name if (!state) {
); throw new Error("State is not specified in IDL.");
}
const expectedDiscriminator = await stateDiscriminator(state.struct.name);
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) { if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
throw new Error("Invalid account discriminator"); throw new Error("Invalid account discriminator");
} }

View File

@ -61,7 +61,9 @@ export default class Provider {
* (This api is for Node only.) * (This api is for Node only.)
*/ */
static env(): Provider { static env(): Provider {
if (isBrowser) return; if (isBrowser) {
throw new Error(`Provider env is not available on browser.`);
}
const process = require("process"); const process = require("process");
const url = process.env.ANCHOR_PROVIDER_URL; const url = process.env.ANCHOR_PROVIDER_URL;
@ -102,7 +104,7 @@ export default class Provider {
await this.wallet.signTransaction(tx); await this.wallet.signTransaction(tx);
signers signers
.filter((s) => s !== undefined) .filter((s): s is Signer => s !== undefined)
.forEach((kp) => { .forEach((kp) => {
tx.partialSign(kp); tx.partialSign(kp);
}); });
@ -144,7 +146,7 @@ export default class Provider {
tx.recentBlockhash = blockhash.blockhash; tx.recentBlockhash = blockhash.blockhash;
signers signers
.filter((s) => s !== undefined) .filter((s): s is Signer => s !== undefined)
.forEach((kp) => { .forEach((kp) => {
tx.partialSign(kp); tx.partialSign(kp);
}); });
@ -154,7 +156,7 @@ export default class Provider {
const signedTxs = await this.wallet.signAllTransactions(txs); const signedTxs = await this.wallet.signAllTransactions(txs);
const sigs = []; const sigs: TransactionSignature[] = [];
for (let k = 0; k < txs.length; k += 1) { for (let k = 0; k < txs.length; k += 1) {
const tx = signedTxs[k]; const tx = signedTxs[k];
@ -178,14 +180,11 @@ export default class Provider {
async simulate( async simulate(
tx: Transaction, tx: Transaction,
signers?: Array<Signer | undefined>, signers?: Array<Signer | undefined>,
opts?: ConfirmOptions opts: ConfirmOptions = this.opts
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> { ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
if (signers === undefined) { if (signers === undefined) {
signers = []; signers = [];
} }
if (opts === undefined) {
opts = this.opts;
}
tx.feePayer = this.wallet.publicKey; tx.feePayer = this.wallet.publicKey;
tx.recentBlockhash = ( tx.recentBlockhash = (
@ -196,7 +195,7 @@ export default class Provider {
await this.wallet.signTransaction(tx); await this.wallet.signTransaction(tx);
signers signers
.filter((s) => s !== undefined) .filter((s): s is Signer => s !== undefined)
.forEach((kp) => { .forEach((kp) => {
tx.partialSign(kp); tx.partialSign(kp);
}); });
@ -204,7 +203,7 @@ export default class Provider {
return await simulateTransaction( return await simulateTransaction(
this.connection, this.connection,
tx, tx,
opts.commitment ?? this.opts.commitment opts.commitment ?? this.opts.commitment ?? "recent"
); );
} }
} }

View File

@ -97,6 +97,9 @@ function attachWorkspaceOverride(
if (typeof entry !== "string" && entry.idl) { if (typeof entry !== "string" && entry.idl) {
idl = JSON.parse(require("fs").readFileSync(entry.idl, "utf-8")); idl = JSON.parse(require("fs").readFileSync(entry.idl, "utf-8"));
} }
if (!idl) {
throw new Error(`Error loading workspace IDL for ${programName}`);
}
workspaceCache[wsProgramName] = new Program(idl, overrideAddress); workspaceCache[wsProgramName] = new Program(idl, overrideAddress);
}); });
} }

View File

@ -7,7 +7,6 @@
"outDir": "dist/esm/", "outDir": "dist/esm/",
"rootDir": "./src", "rootDir": "./src",
"declarationDir": "dist",
"sourceMap": true, "sourceMap": true,
"declaration": true, "declaration": true,
@ -16,6 +15,7 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"noImplicitAny": false, "noImplicitAny": false,
"strictNullChecks": true,
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,

View File

@ -5303,10 +5303,10 @@ typedoc@^0.20.36:
shiki "^0.9.3" shiki "^0.9.3"
typedoc-default-themes "^0.12.10" typedoc-default-themes "^0.12.10"
typescript@^4.0.5: typescript@^4.4.3:
version "4.1.3" version "4.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
uglify-js@^3.1.4: uglify-js@^3.1.4:
version "3.13.5" version "3.13.5"