ts: Improve TypeScript types (#739)
This commit is contained in:
parent
8eec4d3e3d
commit
90df0b1976
|
@ -5,7 +5,7 @@
|
|||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"types": "dist/index.d.ts",
|
||||
"types": "dist/cjs/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
@ -58,6 +58,6 @@
|
|||
"ts-jest": "^26.4.3",
|
||||
"ts-node": "^9.0.0",
|
||||
"typedoc": "^0.20.36",
|
||||
"typescript": "^4.0.5"
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@ export class AccountsCoder {
|
|||
): Promise<Buffer> {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const layout = this.accountLayouts.get(accountName);
|
||||
if (!layout) {
|
||||
throw new Error(`Unknown account: ${accountName}`);
|
||||
}
|
||||
const len = layout.encode(account, buffer);
|
||||
let accountData = buffer.slice(0, len);
|
||||
let discriminator = await accountDiscriminator(accountName);
|
||||
|
@ -45,6 +48,9 @@ export class AccountsCoder {
|
|||
// Chop off the discriminator before decoding.
|
||||
const data = ix.slice(8);
|
||||
const layout = this.accountLayouts.get(accountName);
|
||||
if (!layout) {
|
||||
throw new Error(`Unknown account: ${accountName}`);
|
||||
}
|
||||
return layout.decode(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,29 +3,21 @@ import { sha256 } from "js-sha256";
|
|||
import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl";
|
||||
import { IdlError } from "../error";
|
||||
|
||||
export function accountSize(
|
||||
idl: Idl,
|
||||
idlAccount: IdlTypeDef
|
||||
): number | undefined {
|
||||
export function accountSize(idl: Idl, idlAccount: IdlTypeDef): number {
|
||||
if (idlAccount.type.kind === "enum") {
|
||||
let variantSizes = idlAccount.type.variants.map(
|
||||
(variant: IdlEnumVariant) => {
|
||||
if (variant.fields === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return (
|
||||
variant.fields
|
||||
// @ts-ignore
|
||||
.map((f: IdlField | IdlType) => {
|
||||
// @ts-ignore
|
||||
if (f.name === undefined) {
|
||||
throw new Error("Tuple enum variants not yet implemented.");
|
||||
}
|
||||
// @ts-ignore
|
||||
return typeSize(idl, f.type);
|
||||
})
|
||||
.reduce((a: number, b: number) => a + b)
|
||||
);
|
||||
return variant.fields
|
||||
.map((f: IdlField | IdlType) => {
|
||||
if (!(typeof f === "object" && "name" in f)) {
|
||||
throw new Error("Tuple enum variants not yet implemented.");
|
||||
}
|
||||
return typeSize(idl, f.type);
|
||||
})
|
||||
.reduce((a: number, b: number) => a + b);
|
||||
}
|
||||
);
|
||||
return Math.max(...variantSizes) + 1;
|
||||
|
@ -71,19 +63,14 @@ function typeSize(idl: Idl, ty: IdlType): number {
|
|||
case "publicKey":
|
||||
return 32;
|
||||
default:
|
||||
// @ts-ignore
|
||||
if (ty.vec !== undefined) {
|
||||
if ("vec" in ty) {
|
||||
return 1;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.option !== undefined) {
|
||||
// @ts-ignore
|
||||
if ("option" in ty) {
|
||||
return 1 + typeSize(idl, ty.option);
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.defined !== undefined) {
|
||||
// @ts-ignore
|
||||
const filtered = idl.types.filter((t) => t.name === ty.defined);
|
||||
if ("defined" in ty) {
|
||||
const filtered = idl.types?.filter((t) => t.name === ty.defined) ?? [];
|
||||
if (filtered.length !== 1) {
|
||||
throw new IdlError(`Type not found: ${JSON.stringify(ty)}`);
|
||||
}
|
||||
|
@ -91,13 +78,9 @@ function typeSize(idl: Idl, ty: IdlType): number {
|
|||
|
||||
return accountSize(idl, typeDef);
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.array !== undefined) {
|
||||
// @ts-ignore
|
||||
if ("array" in ty) {
|
||||
let arrayTy = ty.array[0];
|
||||
// @ts-ignore
|
||||
let arraySize = ty.array[1];
|
||||
// @ts-ignore
|
||||
return typeSize(idl, arrayTy) * arraySize;
|
||||
}
|
||||
throw new Error(`Invalid type ${JSON.stringify(ty)}`);
|
||||
|
|
|
@ -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;
|
||||
// This will throw if log length is not a multiple of 4.
|
||||
try {
|
||||
|
@ -63,7 +63,10 @@ export class EventCoder {
|
|||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ import { IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl";
|
|||
import { IdlError } from "../error";
|
||||
|
||||
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 =
|
||||
field.name !== undefined ? camelCase(field.name) : undefined;
|
||||
switch (field.type) {
|
||||
|
@ -52,8 +55,7 @@ export class IdlCoder {
|
|||
return borsh.publicKey(fieldName);
|
||||
}
|
||||
default: {
|
||||
// @ts-ignore
|
||||
if (field.type.vec) {
|
||||
if ("vec" in field.type) {
|
||||
return borsh.vec(
|
||||
IdlCoder.fieldLayout(
|
||||
{
|
||||
|
@ -65,36 +67,30 @@ export class IdlCoder {
|
|||
),
|
||||
fieldName
|
||||
);
|
||||
// @ts-ignore
|
||||
} else if (field.type.option) {
|
||||
} else if ("option" in field.type) {
|
||||
return borsh.option(
|
||||
IdlCoder.fieldLayout(
|
||||
{
|
||||
name: undefined,
|
||||
// @ts-ignore
|
||||
type: field.type.option,
|
||||
},
|
||||
types
|
||||
),
|
||||
fieldName
|
||||
);
|
||||
// @ts-ignore
|
||||
} else if (field.type.defined) {
|
||||
} else if ("defined" in field.type) {
|
||||
const defined = field.type.defined;
|
||||
// User defined type.
|
||||
if (types === undefined) {
|
||||
throw new IdlError("User defined types not provided");
|
||||
}
|
||||
// @ts-ignore
|
||||
const filtered = types.filter((t) => t.name === field.type.defined);
|
||||
const filtered = types.filter((t) => t.name === defined);
|
||||
if (filtered.length !== 1) {
|
||||
throw new IdlError(`Type not found: ${JSON.stringify(field)}`);
|
||||
}
|
||||
return IdlCoder.typeDefLayout(filtered[0], types, fieldName);
|
||||
// @ts-ignore
|
||||
} else if (field.type.array) {
|
||||
// @ts-ignore
|
||||
} else if ("array" in field.type) {
|
||||
let arrayTy = field.type.array[0];
|
||||
// @ts-ignore
|
||||
let arrayLen = field.type.array[1];
|
||||
let innerLayout = IdlCoder.fieldLayout(
|
||||
{
|
||||
|
@ -113,7 +109,7 @@ export class IdlCoder {
|
|||
|
||||
public static typeDefLayout(
|
||||
typeDef: IdlTypeDef,
|
||||
types: IdlTypeDef[],
|
||||
types: IdlTypeDef[] = [],
|
||||
name?: string
|
||||
): Layout {
|
||||
if (typeDef.type.kind === "struct") {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
IdlTypeDef,
|
||||
IdlAccount,
|
||||
IdlAccountItem,
|
||||
IdlTypeDefTyStruct,
|
||||
} from "../idl";
|
||||
import { IdlCoder } from "./idl";
|
||||
import { sighash } from "./common";
|
||||
|
@ -77,7 +78,11 @@ export class InstructionCoder {
|
|||
private _encode(nameSpace: string, ixName: string, ix: any): Buffer {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
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);
|
||||
return Buffer.concat([sighash(nameSpace, ixName), data]);
|
||||
}
|
||||
|
@ -215,21 +220,20 @@ class InstructionFormatter {
|
|||
return idlType as string;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (idlType.vec) {
|
||||
// @ts-ignore
|
||||
if ("vec" in idlType) {
|
||||
return `Vec<${this.formatIdlType(idlType.vec)}>`;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (idlType.option) {
|
||||
// @ts-ignore
|
||||
if ("option" in idlType) {
|
||||
return `Option<${this.formatIdlType(idlType.option)}>`;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (idlType.defined) {
|
||||
// @ts-ignore
|
||||
if ("defined" in idlType) {
|
||||
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(
|
||||
|
@ -296,9 +300,10 @@ class InstructionFormatter {
|
|||
types: IdlTypeDef[]
|
||||
): string {
|
||||
if (typeDef.type.kind === "struct") {
|
||||
const struct: IdlTypeDefTyStruct = typeDef.type;
|
||||
const fields = Object.keys(data)
|
||||
.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) {
|
||||
throw new Error("Unable to find type");
|
||||
}
|
||||
|
@ -314,12 +319,13 @@ class InstructionFormatter {
|
|||
}
|
||||
// Struct enum.
|
||||
if (typeDef.type.variants[0].name) {
|
||||
const variants = typeDef.type.variants;
|
||||
const variant = Object.keys(data)[0];
|
||||
const enumType = data[variant];
|
||||
const namedFields = Object.keys(enumType)
|
||||
.map((f) => {
|
||||
const fieldData = enumType[f];
|
||||
const idlField = typeDef.type.variants[variant]?.filter(
|
||||
const idlField = variants[variant]?.filter(
|
||||
(v: IdlField) => v.name === f
|
||||
)[0];
|
||||
if (idlField === undefined) {
|
||||
|
|
|
@ -16,8 +16,9 @@ export class TypesCoder {
|
|||
this.layouts = new Map();
|
||||
return;
|
||||
}
|
||||
const layouts = idl.types.map((acc) => {
|
||||
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
||||
const types = idl.types;
|
||||
const layouts = types.map((acc) => {
|
||||
return [acc.name, IdlCoder.typeDefLayout(acc, types)];
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -27,12 +28,18 @@ export class TypesCoder {
|
|||
public encode<T = any>(accountName: string, account: T): Buffer {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const layout = this.layouts.get(accountName);
|
||||
if (!layout) {
|
||||
throw new Error(`Unknown account type: ${accountName}`);
|
||||
}
|
||||
const len = layout.encode(account, buffer);
|
||||
return buffer.slice(0, len);
|
||||
}
|
||||
|
||||
public decode<T = any>(accountName: string, ix: Buffer): T {
|
||||
const layout = this.layouts.get(accountName);
|
||||
if (!layout) {
|
||||
throw new Error(`Unknown account type: ${accountName}`);
|
||||
}
|
||||
return layout.decode(ix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
export class ProgramError extends Error {
|
||||
|
|
|
@ -60,12 +60,18 @@ export type IdlTypeDef = {
|
|||
type: IdlTypeDefTy;
|
||||
};
|
||||
|
||||
type IdlTypeDefTy = {
|
||||
kind: "struct" | "enum";
|
||||
fields?: IdlTypeDefStruct;
|
||||
variants?: IdlEnumVariant[];
|
||||
export type IdlTypeDefTyStruct = {
|
||||
kind: "struct";
|
||||
fields: IdlTypeDefStruct;
|
||||
};
|
||||
|
||||
export type IdlTypeDefTyEnum = {
|
||||
kind: "enum";
|
||||
variants: IdlEnumVariant[];
|
||||
};
|
||||
|
||||
type IdlTypeDefTy = IdlTypeDefTyEnum | IdlTypeDefTyStruct;
|
||||
|
||||
type IdlTypeDefStruct = Array<IdlField>;
|
||||
|
||||
export type IdlType =
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import BN from "bn.js";
|
||||
import * as web3 from "@solana/web3.js";
|
||||
import Provider, {
|
||||
export { default as BN } from "bn.js";
|
||||
export * as web3 from "@solana/web3.js";
|
||||
export {
|
||||
default as Provider,
|
||||
getProvider,
|
||||
setProvider,
|
||||
NodeWallet as Wallet,
|
||||
} from "./provider";
|
||||
import Coder, {
|
||||
export {
|
||||
default as Coder,
|
||||
InstructionCoder,
|
||||
EventCoder,
|
||||
StateCoder,
|
||||
|
@ -13,15 +15,15 @@ import Coder, {
|
|||
AccountsCoder,
|
||||
} from "./coder";
|
||||
|
||||
import { ProgramError } from "./error";
|
||||
import { Instruction } from "./coder/instruction";
|
||||
import { Idl } from "./idl";
|
||||
import workspace from "./workspace";
|
||||
import * as utils from "./utils";
|
||||
import { Program } from "./program";
|
||||
import { Address } from "./program/common";
|
||||
import { Event } from "./program/event";
|
||||
import {
|
||||
export * from "./error";
|
||||
export { Instruction } from "./coder/instruction";
|
||||
export { Idl } from "./idl";
|
||||
export { default as workspace } from "./workspace";
|
||||
export * as utils from "./utils";
|
||||
export { Program } from "./program";
|
||||
export { Address } from "./program/common";
|
||||
export { Event } from "./program/event";
|
||||
export {
|
||||
ProgramAccount,
|
||||
AccountNamespace,
|
||||
AccountClient,
|
||||
|
@ -35,43 +37,5 @@ import {
|
|||
InstructionNamespace,
|
||||
InstructionFn,
|
||||
} from "./program/namespace";
|
||||
import { Context, Accounts } from "./program/context";
|
||||
import { 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,
|
||||
};
|
||||
export { Context, Accounts } from "./program/context";
|
||||
export { EventParser } from "./program/event";
|
||||
|
|
|
@ -40,13 +40,11 @@ export function toInstruction(
|
|||
// Throws error if any account required for the `ix` is not given.
|
||||
export function validateAccounts(
|
||||
ixAccounts: IdlAccountItem[],
|
||||
accounts: Accounts
|
||||
accounts: Accounts = {}
|
||||
) {
|
||||
ixAccounts.forEach((acc) => {
|
||||
// @ts-ignore
|
||||
if (acc.accounts !== undefined) {
|
||||
// @ts-ignore
|
||||
validateAccounts(acc.accounts, accounts[acc.name]);
|
||||
if ("accounts" in acc) {
|
||||
validateAccounts(acc.accounts, accounts[acc.name] as Accounts);
|
||||
} else {
|
||||
if (accounts[acc.name] === undefined) {
|
||||
throw new Error(`Invalid arguments: ${acc.name} not provided.`);
|
||||
|
|
|
@ -6,9 +6,9 @@ import Provider from "../provider";
|
|||
const LOG_START_INDEX = "Program log: ".length;
|
||||
|
||||
// Deserialized event.
|
||||
export type Event = {
|
||||
export type Event<T = Record<string, unknown>> = {
|
||||
name: string;
|
||||
data: Object;
|
||||
data: T;
|
||||
};
|
||||
|
||||
type EventCallback = (event: any, slot: number) => void;
|
||||
|
@ -71,7 +71,7 @@ export class EventManager {
|
|||
}
|
||||
this._eventListeners.set(
|
||||
eventName,
|
||||
this._eventListeners.get(eventName).concat(listener)
|
||||
(this._eventListeners.get(eventName) ?? []).concat(listener)
|
||||
);
|
||||
|
||||
// Store the callback into the listener map.
|
||||
|
@ -93,8 +93,11 @@ export class EventManager {
|
|||
const allListeners = this._eventListeners.get(event.name);
|
||||
if (allListeners) {
|
||||
allListeners.forEach((listener) => {
|
||||
const [, callback] = this._eventCallbacks.get(listener);
|
||||
callback(event.data, ctx.slot);
|
||||
const listenerCb = this._eventCallbacks.get(listener);
|
||||
if (listenerCb) {
|
||||
const [, callback] = listenerCb;
|
||||
callback(event.data, ctx.slot);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -128,10 +131,12 @@ export class EventManager {
|
|||
// Kill the websocket connection if all listeners have been removed.
|
||||
if (this._eventCallbacks.size == 0) {
|
||||
assert.ok(this._eventListeners.size === 0);
|
||||
await this._provider.connection.removeOnLogsListener(
|
||||
this._onLogsSubscriptionId
|
||||
);
|
||||
this._onLogsSubscriptionId = undefined;
|
||||
if (this._onLogsSubscriptionId !== undefined) {
|
||||
await this._provider.connection.removeOnLogsListener(
|
||||
this._onLogsSubscriptionId
|
||||
);
|
||||
this._onLogsSubscriptionId = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -243,7 +248,10 @@ class ExecutionContext {
|
|||
constructor(log: string) {
|
||||
// Assumes the first log in every transaction is an `invoke` log from the
|
||||
// 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];
|
||||
}
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ export class Program {
|
|||
* one can use this to send transactions and read accounts for the state
|
||||
* abstraction.
|
||||
*/
|
||||
readonly state: StateClient;
|
||||
readonly state?: StateClient;
|
||||
|
||||
/**
|
||||
* Address of the program.
|
||||
|
@ -210,14 +210,6 @@ export class Program {
|
|||
}
|
||||
private _programId: PublicKey;
|
||||
|
||||
/**
|
||||
* IDL defining the program's interface.
|
||||
*/
|
||||
public get idl(): Idl {
|
||||
return this._idl;
|
||||
}
|
||||
private _idl: Idl;
|
||||
|
||||
/**
|
||||
* Coder for serializing requests.
|
||||
*/
|
||||
|
@ -226,14 +218,6 @@ export class Program {
|
|||
}
|
||||
private _coder: Coder;
|
||||
|
||||
/**
|
||||
* Wallet and network provider.
|
||||
*/
|
||||
public get provider(): Provider {
|
||||
return this._provider;
|
||||
}
|
||||
private _provider: Provider;
|
||||
|
||||
/**
|
||||
* Handles event subscriptions.
|
||||
*/
|
||||
|
@ -245,19 +229,23 @@ 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 defining the program's interface.
|
||||
*/
|
||||
public readonly idl: Idl,
|
||||
programId: Address,
|
||||
/**
|
||||
* Wallet and network provider.
|
||||
*/
|
||||
public readonly provider: Provider = getProvider()
|
||||
) {
|
||||
programId = translateAddress(programId);
|
||||
|
||||
// Fields.
|
||||
this._idl = idl;
|
||||
this._programId = programId;
|
||||
this._provider = provider ?? getProvider();
|
||||
this._coder = new Coder(idl);
|
||||
this._events = new EventManager(
|
||||
this._programId,
|
||||
this._provider,
|
||||
this._coder
|
||||
);
|
||||
this._events = new EventManager(this._programId, provider, this._coder);
|
||||
|
||||
// Dynamic namespaces.
|
||||
const [
|
||||
|
@ -267,7 +255,7 @@ export class Program {
|
|||
account,
|
||||
simulate,
|
||||
state,
|
||||
] = NamespaceFactory.build(idl, this._coder, programId, this._provider);
|
||||
] = NamespaceFactory.build(idl, this._coder, programId, provider);
|
||||
this.rpc = rpc;
|
||||
this.instruction = instruction;
|
||||
this.transaction = transaction;
|
||||
|
@ -285,10 +273,16 @@ 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(
|
||||
address: Address,
|
||||
provider?: Provider
|
||||
): Promise<Program> {
|
||||
const programId = translateAddress(address);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -301,12 +295,18 @@ 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(
|
||||
address: Address,
|
||||
provider?: Provider
|
||||
): Promise<Idl | null> {
|
||||
provider = provider ?? getProvider();
|
||||
const programId = translateAddress(address);
|
||||
|
||||
const idlAddr = await idlAddress(programId);
|
||||
const accountInfo = await provider.connection.getAccountInfo(idlAddr);
|
||||
if (!accountInfo) {
|
||||
return null;
|
||||
}
|
||||
// Chop off account discriminator.
|
||||
let idlAccount = decodeIdlAccount(accountInfo.data.slice(8));
|
||||
const inflatedIdl = inflate(idlAccount.data);
|
||||
|
|
|
@ -28,7 +28,7 @@ export default class AccountFactory {
|
|||
): AccountNamespace {
|
||||
const accountFns: AccountNamespace = {};
|
||||
|
||||
idl.accounts.forEach((idlAccount) => {
|
||||
idl.accounts?.forEach((idlAccount) => {
|
||||
const name = camelCase(idlAccount.name);
|
||||
accountFns[name] = new AccountClient(
|
||||
idl,
|
||||
|
@ -113,7 +113,8 @@ export class AccountClient {
|
|||
this._programId = programId;
|
||||
this._provider = provider ?? getProvider();
|
||||
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.
|
||||
*/
|
||||
subscribe(address: Address, commitment?: Commitment): EventEmitter {
|
||||
if (subscriptions.get(address.toString())) {
|
||||
return subscriptions.get(address.toString()).ee;
|
||||
const sub = subscriptions.get(address.toString());
|
||||
if (sub) {
|
||||
return sub.ee;
|
||||
}
|
||||
|
||||
const ee = new EventEmitter();
|
||||
|
|
|
@ -34,7 +34,7 @@ export default class NamespaceFactory {
|
|||
TransactionNamespace,
|
||||
AccountNamespace,
|
||||
SimulateNamespace,
|
||||
StateClient
|
||||
StateClient | undefined
|
||||
] {
|
||||
const rpc: RpcNamespace = {};
|
||||
const instruction: InstructionNamespace = {};
|
||||
|
|
|
@ -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 { IdlError } from "../../error";
|
||||
import {
|
||||
|
@ -41,19 +45,22 @@ export default class InstructionNamespaceFactory {
|
|||
};
|
||||
|
||||
// Utility fn for ordering the accounts for this instruction.
|
||||
ix["accounts"] = (accs: Accounts) => {
|
||||
ix["accounts"] = (accs: Accounts = {}) => {
|
||||
return InstructionNamespaceFactory.accountsArray(accs, idlIx.accounts);
|
||||
};
|
||||
|
||||
return ix;
|
||||
}
|
||||
|
||||
public static accountsArray(ctx: Accounts, accounts: IdlAccountItem[]): any {
|
||||
public static accountsArray(
|
||||
ctx: Accounts,
|
||||
accounts: IdlAccountItem[]
|
||||
): AccountMeta[] {
|
||||
return accounts
|
||||
.map((acc: IdlAccountItem) => {
|
||||
// Nested accounts.
|
||||
// @ts-ignore
|
||||
const nestedAccounts: IdlAccountItem[] | undefined = acc.accounts;
|
||||
const nestedAccounts: IdlAccountItem[] | undefined =
|
||||
"accounts" in acc ? acc.accounts : undefined;
|
||||
if (nestedAccounts !== undefined) {
|
||||
const rpcAccs = ctx[acc.name] as Accounts;
|
||||
return InstructionNamespaceFactory.accountsArray(
|
||||
|
@ -113,7 +120,7 @@ export interface InstructionNamespace {
|
|||
*/
|
||||
export type InstructionFn = IxProps & ((...args: any[]) => any);
|
||||
type IxProps = {
|
||||
accounts: (ctx: Accounts) => any;
|
||||
accounts: (ctx: Accounts) => AccountMeta[];
|
||||
};
|
||||
|
||||
export type InstructionEncodeFn = (ixName: string, ix: any) => Buffer;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TransactionSignature } from "@solana/web3.js";
|
||||
import Provider from "../../provider";
|
||||
import { IdlInstruction } from "../../idl";
|
||||
import { splitArgsAndCtx } from "../context";
|
||||
import { Context, splitArgsAndCtx } from "../context";
|
||||
import { TransactionFn } from "./transaction";
|
||||
import { ProgramError } from "../../error";
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
PublicKey,
|
||||
RpcResponseAndContext,
|
||||
SimulatedTransactionResponse,
|
||||
} from "@solana/web3.js";
|
||||
import Provider from "../../provider";
|
||||
import { IdlInstruction } from "../../idl";
|
||||
import { splitArgsAndCtx } from "../context";
|
||||
|
@ -21,7 +25,9 @@ export default class SimulateFactory {
|
|||
const simulate = async (...args: any[]): Promise<SimulateResponse> => {
|
||||
const tx = txFn(...args);
|
||||
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
||||
let resp = undefined;
|
||||
let resp:
|
||||
| RpcResponseAndContext<SimulatedTransactionResponse>
|
||||
| undefined = undefined;
|
||||
try {
|
||||
resp = await provider.simulate(tx, ctx.signers, ctx.options);
|
||||
} catch (err) {
|
||||
|
@ -43,7 +49,7 @@ export default class SimulateFactory {
|
|||
throw new Error("Simulated logs not found");
|
||||
}
|
||||
|
||||
const events = [];
|
||||
const events: Event[] = [];
|
||||
if (idl.events) {
|
||||
let parser = new EventParser(programId, coder);
|
||||
parser.parseLogs(logs, (event) => {
|
||||
|
|
|
@ -56,37 +56,25 @@ export class StateClient {
|
|||
}
|
||||
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 _coder: Coder;
|
||||
private _idl: Idl;
|
||||
private _sub: Subscription | null;
|
||||
|
||||
constructor(
|
||||
idl: Idl,
|
||||
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._programId = programId;
|
||||
this._address = programStateAddress(programId);
|
||||
this._provider = provider ?? getProvider();
|
||||
this._coder = coder ?? new Coder(idl);
|
||||
this._sub = null;
|
||||
|
||||
// Build namespaces.
|
||||
|
@ -99,7 +87,7 @@ export class StateClient {
|
|||
let transaction: TransactionNamespace = {};
|
||||
let rpc: RpcNamespace = {};
|
||||
|
||||
idl.state.methods.forEach((m: IdlStateMethod) => {
|
||||
idl.state?.methods.forEach((m: IdlStateMethod) => {
|
||||
// Build instruction method.
|
||||
const ixItem = InstructionNamespaceFactory.build(
|
||||
m,
|
||||
|
@ -147,9 +135,11 @@ export class StateClient {
|
|||
throw new Error(`Account does not exist ${addr.toString()}`);
|
||||
}
|
||||
// Assert the account discriminator is correct.
|
||||
const expectedDiscriminator = await stateDiscriminator(
|
||||
this._idl.state.struct.name
|
||||
);
|
||||
const state = this._idl.state;
|
||||
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))) {
|
||||
throw new Error("Invalid account discriminator");
|
||||
}
|
||||
|
|
|
@ -61,7 +61,9 @@ export default class Provider {
|
|||
* (This api is for Node only.)
|
||||
*/
|
||||
static env(): Provider {
|
||||
if (isBrowser) return;
|
||||
if (isBrowser) {
|
||||
throw new Error(`Provider env is not available on browser.`);
|
||||
}
|
||||
|
||||
const process = require("process");
|
||||
const url = process.env.ANCHOR_PROVIDER_URL;
|
||||
|
@ -102,7 +104,7 @@ export default class Provider {
|
|||
|
||||
await this.wallet.signTransaction(tx);
|
||||
signers
|
||||
.filter((s) => s !== undefined)
|
||||
.filter((s): s is Signer => s !== undefined)
|
||||
.forEach((kp) => {
|
||||
tx.partialSign(kp);
|
||||
});
|
||||
|
@ -144,7 +146,7 @@ export default class Provider {
|
|||
tx.recentBlockhash = blockhash.blockhash;
|
||||
|
||||
signers
|
||||
.filter((s) => s !== undefined)
|
||||
.filter((s): s is Signer => s !== undefined)
|
||||
.forEach((kp) => {
|
||||
tx.partialSign(kp);
|
||||
});
|
||||
|
@ -154,7 +156,7 @@ export default class Provider {
|
|||
|
||||
const signedTxs = await this.wallet.signAllTransactions(txs);
|
||||
|
||||
const sigs = [];
|
||||
const sigs: TransactionSignature[] = [];
|
||||
|
||||
for (let k = 0; k < txs.length; k += 1) {
|
||||
const tx = signedTxs[k];
|
||||
|
@ -178,14 +180,11 @@ export default class Provider {
|
|||
async simulate(
|
||||
tx: Transaction,
|
||||
signers?: Array<Signer | undefined>,
|
||||
opts?: ConfirmOptions
|
||||
opts: ConfirmOptions = this.opts
|
||||
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
|
||||
if (signers === undefined) {
|
||||
signers = [];
|
||||
}
|
||||
if (opts === undefined) {
|
||||
opts = this.opts;
|
||||
}
|
||||
|
||||
tx.feePayer = this.wallet.publicKey;
|
||||
tx.recentBlockhash = (
|
||||
|
@ -196,7 +195,7 @@ export default class Provider {
|
|||
|
||||
await this.wallet.signTransaction(tx);
|
||||
signers
|
||||
.filter((s) => s !== undefined)
|
||||
.filter((s): s is Signer => s !== undefined)
|
||||
.forEach((kp) => {
|
||||
tx.partialSign(kp);
|
||||
});
|
||||
|
@ -204,7 +203,7 @@ export default class Provider {
|
|||
return await simulateTransaction(
|
||||
this.connection,
|
||||
tx,
|
||||
opts.commitment ?? this.opts.commitment
|
||||
opts.commitment ?? this.opts.commitment ?? "recent"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,9 @@ function attachWorkspaceOverride(
|
|||
if (typeof entry !== "string" && entry.idl) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
"outDir": "dist/esm/",
|
||||
"rootDir": "./src",
|
||||
"declarationDir": "dist",
|
||||
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
|
@ -16,6 +15,7 @@
|
|||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": true,
|
||||
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
|
@ -23,7 +23,7 @@
|
|||
"baseUrl": ".",
|
||||
"typeRoots": ["types/", "node_modules/@types"],
|
||||
"paths": {
|
||||
"@solana/web3.js": ["./node_modules/@solana/web3.js/lib"]
|
||||
"@solana/web3.js": ["./node_modules/@solana/web3.js/lib"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5303,10 +5303,10 @@ typedoc@^0.20.36:
|
|||
shiki "^0.9.3"
|
||||
typedoc-default-themes "^0.12.10"
|
||||
|
||||
typescript@^4.0.5:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
|
||||
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
|
||||
typescript@^4.4.3:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
|
||||
integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.13.5"
|
||||
|
|
Loading…
Reference in New Issue