Features
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 MaybeRpcAccount
s, 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 MaybeRpcAccount
s, 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 GpaBuilder
s.
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 Serializer
s 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);