123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- import camelCase from "camelcase";
- import EventEmitter from "eventemitter3";
- import * as bs58 from "bs58";
- import {
- Signer,
- PublicKey,
- SystemProgram,
- TransactionInstruction,
- Commitment,
- } from "@solana/web3.js";
- import Provider from "../../provider";
- import { Idl, IdlTypeDef } from "../../idl";
- import Coder, {
- ACCOUNT_DISCRIMINATOR_SIZE,
- accountDiscriminator,
- accountSize,
- } from "../../coder";
- import { Subscription, Address, translateAddress } from "../common";
- import { getProvider } from "../../";
- import { AllAccountsMap, IdlTypes, TypeDef } from "./types";
- import * as pubkeyUtil from "../../utils/pubkey";
- export default class AccountFactory {
- public static build<IDL extends Idl>(
- idl: IDL,
- coder: Coder,
- programId: PublicKey,
- provider: Provider
- ): AccountNamespace<IDL> {
- const accountFns: AccountNamespace = {};
- idl.accounts.forEach((idlAccount) => {
- const name = camelCase(idlAccount.name);
- accountFns[name] = new AccountClient<IDL>(
- idl,
- idlAccount,
- programId,
- provider,
- coder
- );
- });
- return accountFns as AccountNamespace<IDL>;
- }
- }
- /**
- * The namespace provides handles to an [[AccountClient]] object for each
- * account in a program.
- *
- * ## Usage
- *
- * ```javascript
- * account.<account-client>
- * ```
- *
- * ## Example
- *
- * To fetch a `Counter` account from the above example,
- *
- * ```javascript
- * const counter = await program.account.counter.fetch(address);
- * ```
- *
- * For the full API, see the [[AccountClient]] reference.
- */
- export type AccountNamespace<IDL extends Idl = Idl> = {
- [M in keyof AllAccountsMap<IDL>]: AccountClient<IDL>
- }
- export class AccountClient<
- IDL extends Idl = Idl,
- A extends IDL["accounts"][number] = IDL["accounts"][number],
- T = TypeDef<A, IdlTypes<IDL>>
- > {
- /**
- * Returns the number of bytes in this account.
- */
- get size(): number {
- return this._size;
- }
- private _size: number;
- /**
- * Returns the program ID owning all accounts.
- */
- get programId(): PublicKey {
- return this._programId;
- }
- 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 _coder: Coder;
- private _idlAccount: A;
- constructor(
- idl: IDL,
- idlAccount: A,
- programId: PublicKey,
- provider?: Provider,
- coder?: Coder
- ) {
- this._idlAccount = idlAccount;
- this._programId = programId;
- this._provider = provider ?? getProvider();
- this._coder = coder ?? new Coder(idl);
- this._size = ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount);
- }
- /**
- * Returns a deserialized account.
- *
- * @param address The address of the account to fetch.
- */
- async fetch(address: Address): Promise<T> {
- const accountInfo = await this._provider.connection.getAccountInfo(
- translateAddress(address)
- );
- if (accountInfo === null) {
- throw new Error(`Account does not exist ${address.toString()}`);
- }
- // Assert the account discriminator is correct.
- const discriminator = await accountDiscriminator(this._idlAccount.name);
- if (discriminator.compare(accountInfo.data.slice(0, 8))) {
- throw new Error("Invalid account discriminator");
- }
- return this._coder.accounts.decode<T>(
- this._idlAccount.name,
- accountInfo.data
- );
- }
- /**
- * Returns all instances of this account type for the program.
- */
- async all(filter?: Buffer): Promise<ProgramAccount<T>[]> {
- let bytes = await accountDiscriminator(this._idlAccount.name);
- if (filter !== undefined) {
- bytes = Buffer.concat([bytes, filter]);
- }
- let resp = await this._provider.connection.getProgramAccounts(
- this._programId,
- {
- commitment: this._provider.connection.commitment,
- filters: [
- {
- memcmp: {
- offset: 0,
- bytes: bs58.encode(bytes),
- },
- },
- ],
- }
- );
- return resp.map(({ pubkey, account }) => {
- return {
- publicKey: pubkey,
- account: this._coder.accounts.decode(
- this._idlAccount.name,
- account.data
- ),
- };
- });
- }
- /**
- * Returns an `EventEmitter` emitting a "change" event whenever the account
- * changes.
- */
- subscribe(address: Address, commitment?: Commitment): EventEmitter {
- if (subscriptions.get(address.toString())) {
- return subscriptions.get(address.toString()).ee;
- }
- const ee = new EventEmitter();
- address = translateAddress(address);
- const listener = this._provider.connection.onAccountChange(
- address,
- (acc) => {
- const account = this._coder.accounts.decode(
- this._idlAccount.name,
- acc.data
- );
- ee.emit("change", account);
- },
- commitment
- );
- subscriptions.set(address.toString(), {
- ee,
- listener,
- });
- return ee;
- }
- /**
- * Unsubscribes from the account at the given address.
- */
- unsubscribe(address: Address) {
- let sub = subscriptions.get(address.toString());
- if (!sub) {
- console.warn("Address is not subscribed");
- return;
- }
- if (subscriptions) {
- this._provider.connection
- .removeAccountChangeListener(sub.listener)
- .then(() => {
- subscriptions.delete(address.toString());
- })
- .catch(console.error);
- }
- }
- /**
- * Returns an instruction for creating this account.
- */
- async createInstruction(
- signer: Signer,
- sizeOverride?: number
- ): Promise<TransactionInstruction> {
- const size = this.size;
- return SystemProgram.createAccount({
- fromPubkey: this._provider.wallet.publicKey,
- newAccountPubkey: signer.publicKey,
- space: sizeOverride ?? size,
- lamports: await this._provider.connection.getMinimumBalanceForRentExemption(
- sizeOverride ?? size
- ),
- programId: this._programId,
- });
- }
- /**
- * Function returning the associated account. Args are keys to associate.
- * Order matters.
- */
- async associated(...args: Array<PublicKey | Buffer>): Promise<T> {
- const addr = await this.associatedAddress(...args);
- return await this.fetch(addr);
- }
- /**
- * Function returning the associated address. Args are keys to associate.
- * Order matters.
- */
- async associatedAddress(
- ...args: Array<PublicKey | Buffer>
- ): Promise<PublicKey> {
- return await pubkeyUtil.associated(this._programId, ...args);
- }
- }
- /**
- * @hidden
- *
- * Deserialized account owned by a program.
- */
- export type ProgramAccount<T = any> = {
- publicKey: PublicKey;
- account: T;
- };
- // Tracks all subscriptions.
- const subscriptions: Map<string, Subscription> = new Map();
|