Learn how to efficiently query account data from Nexis Native Chain.
getProgramAccounts
function that will enable things like
paging, ordering accounts, and filtering.
dataSlice
to only fetch the data you needgetProgramAccounts
function that you use to get all of the
accounts takes a configuration object as an argument. One of the configuration
options is dataSlice
which lets you provide two things:
offset
- the offset from the beginning of the data buffer to start slicinglength
- the number of bytes to return, starting from the provided offsetdataSlice
in the configuration object, the function will
only return the subset of the data buffer that you specified.
dataSlice
of { offset: 0, length: 0 }
. You can then map the
result to a list of account keys whose data you can fetch only when needed.
getMultipleAccountsInfo
method:
dataSlice
option is also helpful when you need to order a list of accounts
while paging. You still don’t want to fetch all the data at once, but you do
need all of the keys and a way to order them upfront. In this case, you need to
understand the layout of the account data and configure the data slice to only
be the data you need to use for ordering.
For example, you might have an account that stores contact information like so:
initialized
as a booleanphoneNumber
as an unsigned, 64-bit integerfirstName
as a stringsecondName
as a stringinitialized
, takes the first byte, then phoneNumber
takes another 8,
so the firstName
field starts at offset 1 + 8 = 9
. However, dynamic data
fields in borsh use the first 4 bytes to record the length of the data, so we
can skip an additional 4 bytes, making the offset 13.
You then need to determine the length to make the data slice. Since the length
is variable, we can’t know for sure before fetching the data. But you can choose
a length that is large enough to cover most cases and short enough to not be too
much of a burden to fetch. 15 bytes is plenty for most first names but would
result in a small enough download even with a million users.
Once you’ve fetched accounts with the given data slice, you can use the sort
method to sort the array before mapping it to an array of public keys.
filters
to only retrieve specific accountsfilters
configuration option comes in. This option is an array that
can have objects matching the following:
memcmp
- compares a provided series of bytes with program account data at a
particular offset. Fields:
offset
- the number to offset into program account data before comparing
databytes
- a base-58 encoded string representing the data to match; limited
to less than 129 bytesdataSize
- compares the program account data length with the provided data
sizememcmp
filter:
firstName
in the data layout is 9 and we want to additionally
skip the first 4 bytes indicating the length of the string.bs58`` to perform base-58 encoding on the search term. You can install it using
npm
install bs58`.WalletContextProvider
we created in the Wallets lesson, a Card
component for
displaying a movie review, a MovieList
component that displays reviews in a
list, a Form
component for submitting a new review, and a Movie.ts
file that
contains a class definition for a Movie
object.
MovieCoordinator.ts
and declare a
MovieCoordinator
class. Then let’s move the MOVIE_REVIEW_PROGRAM_ID
constant
from MovieList
into this new file since we’ll be moving all references to it
MovieCoordinator
to create a paging implementation. A quick
note before we dive in: this will be as simple a paging implementation as
possible so that we can focus on the complex part of interacting with Nexis Native Chain
accounts. You can, and should, do better for a production application.
With that out of the way, let’s create a static property accounts
of type
web3.PublicKey[]
, a static function
prefetchAccounts(connection: web3.Connection)
, and a static function
fetchPage(connection: web3.Connection, page: number, perPage: number): Promise<Movie[]>
.
You’ll also need to import @nexis-network/web3.js
and Movie
.
prefetchAccounts
to do this and set the retrieved public keys to
the static accounts
property.
fetchPage
method. First, if the accounts haven’t been
prefetched yet, we’ll need to do that. Then, we can get the account public keys
that correspond to the requested page and call
connection.getMultipleAccountsInfo
. Finally, we deserialize the account data
and return the corresponding Movie
objects.
MovieList
to use these methods. In
MovieList.tsx
, add const [page, setPage] = useState(1)
near the existing
useState
calls. Then, update useEffect
to call MovieCoordinator.fetchPage
instead of fetching the accounts inline.
initialized
- unsigned 8-bit integer; 1 byterating
- unsigned 8-bit integer; 1 bytetitle
- string; unknown number of bytesdescription
- string; unknown number of bytestitle
is 2. The length, however, is indeterminate, so we can just provide what seems
to be a reasonable length. I’ll stick with 18 as that will cover the length of
most titles without fetching too much data every time.
Once we’ve modified the data slice in getProgramAccounts
, we then need to
actually sort the returned array. To do this, we need to compare the part of the
data buffer that actually corresponds to title
. The first 4 bytes of a dynamic
field in Borsh are used to store the length of the field in bytes. So in any
given buffer data
that is sliced the way we discussed above, the string
portion is data.slice(4, 4 + data[0])
.
Now that we’ve thought through this, let’s modify the implementation of
prefetchAccounts
in MovieCoordinator
:
search
parameter to prefetchAccounts
and reconfigure
the body of the function to use it.
We can use the filters
property of the config
parameter of
getProgramAccounts
to filter accounts by specific data. The offset to the
title
fields is 2, but the first 4 bytes are the length of the title so the
actual offset to the string itself is 6. Remember that the bytes need to be base
58 encoded, so let’s install and import bs58
.
search
parameter to fetchPage
and update its call to
prefetchAccounts
to pass it along. We’ll also need to add a reload
boolean
parameter to fetchPage
so that we can force a refresh of the account
prefetching every time the search value changes.
MovieList
to call this properly.
First, add const [search, setSearch] = useState('')
near the other useState
calls. Then update the call to MovieCoordinator.fetchPage
in the useEffect
to pass the search
parameter and to reload when search !== ''
.
search
: