Fetching Accounts

Let's see how we can fetch account data from the Solana blockchain using Umi. For that, we will need the RpcInterface to fetch accounts with serialized data and serializers to help deserialize them.

Account definitions

Umi defines an account with serialized data as an RpcAccount. It contains information from the account header — i.e. the SOL on the account, the program owner, etc. — and the account's public key and serialized data.

type RpcAccount = AccountHeader & {
  publicKey: PublicKey;
  data: Uint8Array;
};

It also defines a MaybeRpcAccount type that represents an RpcAccount that may or may exist. When the account does not exist, it keeps track of its public key so that, in a list of accounts, we know which public key was not found.

type MaybeRpcAccount =
  | ({ exists: true } & RpcAccount)
  | { exists: false; publicKey: PublicKey };

When dealing with MaybeRpcAccounts, you may use the assertAccountExists helper method to assert that an account exists and fail otherwise.

assertAccountExists(myMaybeAccount);
// From now on, we know myMaybeAccount is an RpcAccount.

Last but not least, it provides a generic Account type that directly exposes the deserialized data — represented as a generic type T — with two extra attributes: publicKey and header. This allows us to directly access the deserialized data without nested data attributes.

type Account<T extends object> = T & {
  publicKey: PublicKey;
  header: AccountHeader;
};

Fetching RPC accounts

Now that we know how accounts are represented in Umi, let's see how we can fetch them.

First of all, we can fetch a single account using the getAccount method of the RpcInterface. This will return a MaybeRpcAccount instance since the account may or may not exist. As mentioned above, you may use the assertAccountExists function to ensure it does.

const myAccount = await umi.rpc.getAccount(myPublicKey);
assertAccountExists(myAccount);

Note that if you are only interested to know if an account exists at the given address, you may use the accountExists method instead.

const accountExists = await umi.rpc.accountExists(myPublicKey);

If you need to fetch multiple accounts at once, you may use the getAccounts method instead. This will return a list of MaybeRpcAccounts, one for each public key you passed in.

const myAccounts = await umi.rpc.getAccounts(myPublicKeys);

Finally, the getProgramAccounts method can be used to fetch all accounts from a given program that match a given set of filters. This method returns a list of RpcAccount directly since it will only return accounts that exist. Refer to the following Get Program Account documentation to learn more about filters and data slicing.

// Fetch all accounts from a program.
const allProgramAccounts = await umi.rpc.getProgramAccounts(myProgramId);

// Fetch a slice of all accounts from a program.
const slicedProgramAccounts = await umi.rpc.getProgramAccounts(myProgramId, {
  dataSlice: { offset: 32, length: 8 },
});

// Fetch some accounts from a program that matches a given set of filters.
const filteredProgramAccounts = await umi.rpc.getProgramAccounts(myProgramId, {
  filters: [
    { dataSize: 42 },
    { memcmp: { offset: 0, bytes: new Uint8Array([1, 2, 3]) } },
  ],
});

Note that when fetching program accounts, you might be interested in GpaBuilders.

Deserializing accounts

In order to turn a RpcAccount into a deserialized Account<T>, we simply need the deserializeAccount function and a Serializer that knows how to deserialize the account's data. You can read more about Serializers in the Serializers page but here's a quick example assuming the data is composed of two public keys and one u64 number.

import { assertAccountExists, deserializeAccount } from '@metaplex-foundation/umi';
import { struct, publicKey, u64 } from '@metaplex-foundation/umi/serializers';

// Given an existing RPC account.
const myRpcAccount = await umi.rpc.getAccount(myPublicKey);
assertAccountExists(myRpcAccount);

// And an account data serializer.
const myDataSerializer = struct([
  ['source', publicKey()],
  ['destination', publicKey()],
  ['amount', u64()],
]);

// We can deserialize the account like so.
const myAccount = deserializeAccount(rawAccount, myDataSerializer);
// myAccount.source -> PublicKey
// myAccount.destination -> PublicKey
// myAccount.amount -> bigint
// myAccount.publicKey -> PublicKey
// myAccount.header -> AccountHeader

Note that, in practice, program libraries should provide account data serializers and helpers for you. Here's an example using a Kinobi-generated library.

import { Metadata, deserializeMetadata, fetchMetadata, safeFetchMetadata } from '@metaplex-foundation/mpl-token-metadata';

// Deserializes a metadata account.
const metadata: Metadata = deserializeMetadata(umi, unparsedMetadataAccount);

// Fetch and deserialize a metadata account, fail if the account does not exist.
const metadata: Metadata = await fetchMetadata(umi, metadataPublicKey);

// Fetch and deserialize a metadata account, return null if the account does not exist.
const metadata: Metadata | null = await safeFetchMetadata(umi, metadataPublicKey);

Last updated