123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import EventEmitter from "eventemitter3";
- import {
- PublicKey,
- SystemProgram,
- Transaction,
- TransactionSignature,
- TransactionInstruction,
- SYSVAR_RENT_PUBKEY,
- Commitment,
- } from "@solana/web3.js";
- import Provider from "../../provider";
- import { Idl, IdlStateMethod } from "../../idl";
- import Coder, { stateDiscriminator } from "../../coder";
- import { RpcNamespace, InstructionNamespace } from "./";
- import {
- Subscription,
- translateError,
- toInstruction,
- validateAccounts,
- } from "../common";
- import { Accounts, splitArgsAndCtx } from "../context";
- import InstructionNamespaceFactory from "./instruction";
- export type StateNamespace = () =>
- | Promise<any>
- | {
- address: () => Promise<PublicKey>;
- rpc: RpcNamespace;
- instruction: InstructionNamespace;
- subscribe: (commitment?: Commitment) => EventEmitter;
- unsubscribe: () => void;
- };
- export default class StateFactory {
- // Builds the state namespace.
- public static build(
- idl: Idl,
- coder: Coder,
- programId: PublicKey,
- idlErrors: Map<number, string>,
- provider: Provider
- ): StateNamespace | undefined {
- if (idl.state === undefined) {
- return undefined;
- }
- // Fetches the state object from the blockchain.
- const state = async (): Promise<any> => {
- const addr = await programStateAddress(programId);
- const accountInfo = await provider.connection.getAccountInfo(addr);
- if (accountInfo === null) {
- throw new Error(`Account does not exist ${addr.toString()}`);
- }
- // Assert the account discriminator is correct.
- const expectedDiscriminator = await stateDiscriminator(
- idl.state.struct.name
- );
- if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
- throw new Error("Invalid account discriminator");
- }
- return coder.state.decode(accountInfo.data);
- };
- // Namespace with all rpc functions.
- const rpc: RpcNamespace = {};
- const ix: InstructionNamespace = {};
- idl.state.methods.forEach((m: IdlStateMethod) => {
- const accounts = async (accounts: Accounts): Promise<any> => {
- const keys = await stateInstructionKeys(
- programId,
- provider,
- m,
- accounts
- );
- return keys.concat(
- InstructionNamespaceFactory.accountsArray(accounts, m.accounts)
- );
- };
- const ixFn = async (...args: any[]): Promise<TransactionInstruction> => {
- const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
- return new TransactionInstruction({
- keys: await accounts(ctx.accounts),
- programId,
- data: coder.instruction.encodeState(
- m.name,
- toInstruction(m, ...ixArgs)
- ),
- });
- };
- ixFn["accounts"] = accounts;
- ix[m.name] = ixFn;
- rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
- const [, ctx] = splitArgsAndCtx(m, [...args]);
- const tx = new Transaction();
- if (ctx.instructions !== undefined) {
- tx.add(...ctx.instructions);
- }
- tx.add(await ix[m.name](...args));
- try {
- const txSig = await provider.send(tx, ctx.signers, ctx.options);
- return txSig;
- } catch (err) {
- let translatedErr = translateError(idlErrors, err);
- if (translatedErr === null) {
- throw err;
- }
- throw translatedErr;
- }
- };
- });
- state["rpc"] = rpc;
- state["instruction"] = ix;
- // Calculates the address of the program's global state object account.
- state["address"] = async (): Promise<PublicKey> =>
- programStateAddress(programId);
- // Subscription singleton.
- let sub: null | Subscription = null;
- // Subscribe to account changes.
- state["subscribe"] = (commitment?: Commitment): EventEmitter => {
- if (sub !== null) {
- return sub.ee;
- }
- const ee = new EventEmitter();
- state["address"]().then((address) => {
- const listener = provider.connection.onAccountChange(
- address,
- (acc) => {
- const account = coder.state.decode(acc.data);
- ee.emit("change", account);
- },
- commitment
- );
- sub = {
- ee,
- listener,
- };
- });
- return ee;
- };
- // Unsubscribe from account changes.
- state["unsubscribe"] = () => {
- if (sub !== null) {
- provider.connection
- .removeAccountChangeListener(sub.listener)
- .then(async () => {
- sub = null;
- })
- .catch(console.error);
- }
- };
- return state;
- }
- }
- // Calculates the deterministic address of the program's "state" account.
- async function programStateAddress(programId: PublicKey): Promise<PublicKey> {
- let [registrySigner] = await PublicKey.findProgramAddress([], programId);
- return PublicKey.createWithSeed(registrySigner, "unversioned", programId);
- }
- // Returns the common keys that are prepended to all instructions targeting
- // the "state" of a program.
- async function stateInstructionKeys(
- programId: PublicKey,
- provider: Provider,
- m: IdlStateMethod,
- accounts: Accounts
- ) {
- if (m.name === "new") {
- // Ctor `new` method.
- const [programSigner] = await PublicKey.findProgramAddress([], programId);
- return [
- {
- pubkey: provider.wallet.publicKey,
- isWritable: false,
- isSigner: true,
- },
- {
- pubkey: await programStateAddress(programId),
- isWritable: true,
- isSigner: false,
- },
- { pubkey: programSigner, isWritable: false, isSigner: false },
- {
- pubkey: SystemProgram.programId,
- isWritable: false,
- isSigner: false,
- },
- { pubkey: programId, isWritable: false, isSigner: false },
- {
- pubkey: SYSVAR_RENT_PUBKEY,
- isWritable: false,
- isSigner: false,
- },
- ];
- } else {
- validateAccounts(m.accounts, accounts);
- return [
- {
- pubkey: await programStateAddress(programId),
- isWritable: true,
- isSigner: false,
- },
- ];
- }
- }
|