Accessing Data From an Antelope Smart Contract

This guide will show how the Table class provided by the Contract Kit can be used to access table data from an existing smart contract in a web application.

NOTE: This guide was originally written in August of 2023 and is based upon the 0.4.x release of @wharfkit/contract. It will be updated once the Contract Kit is finalized to reflect any potential changes being made as we work towards a 1.0.0 release.

Getting Started

Please review the Dynamically Loading Antelope Smart Contracts guide before proceeding, to understand how to establish a Contract instance in an application.

The Table

Prior to the Contract Kit, developers would typically call the get_table_rows API endpoint directly and specify all the required parameters. This often proved difficult, due to its verbose syntax and various ways to accidentally provide incorrect parameters.

To make this process easier, Wharf now offers the Table class, which represents a specific table of a smart contract when instantiated. A Table instance can be manually created or loaded dynamically from a Contract instance by calling the table method on it and providing the table name.

const table = contract.table("table_name")

Additionally, a second parameter can be provided to specify a default scope to load the table by. This is useful on contracts where every account has its own scope, like the eosio.token contract.

const contract = await kit.load("eosio.token")
const table = contract.table("accounts", "teamgreymass")

With a Table instance created, it can be used to create table cursors and retrieve data from an API endpoint.

Retrieving Data

The Table class offers a few methods to retrieve data based on different use cases.

Get Single Row

The async get method on a Table instance will get a single row from the table as a typed object. If no parameters are passed, it will simply get and return the first table row.

const result = await table.get()

A string parameter can also optionally be passed, allowing the return of one table row where the primary key matches the provided value.

const result = await table.get("primary_key_value")

Finally, a second parameter can also optionally be passed to further refine the table row that will be returned. The example below passes in an alternative scope from the default:

const result = await table.get("key_value", {
  scope: "scope_name",
})

Additional options such as key_type and index may also be passed to alter which index is used to perform the lookup. Additional documentation for this will follow, but feel free to view the source code for now to see how these options convert to raw get_table_rows parameters.

Get Multiple Rows

Each table also offers a query method that will return a table cursor based on the provided query parameters. This cursor is a reusable object for the query provided, and can be used to iterate over large numbers of rows. The query parameters provided in the call will help to refine the range of rows being returned.

// Establish Table instance from the contract
const table = contract.table("voters")

// Return a cursor based on a query
const cursor = table.query({
  from: "bar",
  to: "baz",
})

The cursor returned here will be set to look for rows where the primary key is between bar and baz. Additional parameters can be defined to specify an alternative index instead of the primary key, as well as a number of other parameters we’ll outline in the documentation.

The table cursor being returned as the result of a query doesn’t contain table rows, but provides an async next method to return the next set of rows from the cursor. The voters table on the system contract on many Antelope-based blockchains serve as an excellent example of a table requiring this, since it often contains hundreds of thousands of rows. Bulk data like this needs to be iterated over in the application code.

const cursor = contract.table("voters").query()

const rows = await cursor.next()
// rows 1 - 1000

This approach allows the developer to get the first subset of the data from the API and process it or present it to a user. When additional sets of data are required, additional calls to the same cursor can be made for the second, third, or fourth chunk of data.

const rows = await cursor.next()
// rows 1000 - 2000

const rows = await cursor.next()
// rows 2001 - 3000

const rows = await cursor.next()
// rows 3001 - 4000

The cursor will internally keep track of where it left off in the table and automatically request the next set of records. This position in the cursor can be reset to start again from the beginning.

const rows = await cursor.next()
// rows 1 - 1000

const rows = await cursor.next()
// rows 1001 - 2000

const rows = await cursor.reset().next()
// rows 1 - 1000

A specific number of rows per request may also be passed directly to each next call, to specify the number of rows to request.

const rows = await cursor.next(10)
// rows 1 - 10

const rows = await cursor.next(10)
// rows 11 - 20

And finally, the cursor also offers an all method that will return all of the table data based on the query parameters provided.

const rows = await cursor.all()

The cursor does this by recursively making API calls to the provided endpoint until it has reached the end of a table.

Cursors have a number of other methods and options to further tweak how they operate. We’ll provide more information as the documentation for the Contract Kit comes online.

Get Table Scopes

The Table class also offers a method to query the blockchain for the scopes in current table state.

The async scopes method acts much like query, in that it returns a cursor to facilitate loading large amounts of data. The cursor can then be called with either next or all to make a request to the blockchain to retrieve a subset of the scopes.

The eosio.msig contract will serve as our example here, which contains a large amount of scopes since every account using it is entered in their own scope.

const cursor = contract.table("proposal").scopes()

const scopes = await cursor.next(100)

This will return the first 100 scopes that exist in the table.

More…

Stay tuned as more guides, documentation and example codebases are added, or ask questions in the Github discussion board for WharfKit!