When building a Stacks app, you can query on-chain state from a Stacks contract by calling "read-only functions". These are functions exposed by the contract to help apps and other contracts integrate with the contract.
Setting up the ClarigenClient
The @clarigen/core
package exposes a ClarigenClient
instance, which provides an easy way to call read-only functions in your app.
To construct the ClarigenClient
, you need to include either:
- A
string
pointing to a Stacks API endpoint - A
StacksNetwork
instance from@stacks/network
To use an API endpoint:
import { ClarigenClient } from '@clarigen/core';
const stacksApi = 'https://stacks-node-api.mainnet.stacks.co';
const client = new ClarigenClient(stacksApi);
Or, using a StacksNetwork
instance:
import { ClarigenClient } from '@clarigen/core';
import { StacksMainnet } from '@stacks/network';
const network = new StacksMainnet();
const client = new ClarigenClient(network);
Using ClarigenClient
To call individual contract functions, you'll need to have a contract factory setup. In these examples, we'll use a hypothetical NFT contract, nftContract
to call functions.
ro
In Clarigen, "ro" stands for "read-only". It's a shorthand for calling read-only methods.
Using ro
returns the exact value returns from the contract function, but typed and converted to JS-native values.
import { clarigen } from './clarigen-client';
// import your contract
import { nftContract } from './clarigen-contracts';
export async function getOwner(id: number | bigint) {
const response = await clarigen.ro(nftContract.getOwner(id));
if (response.isOk) {
return response.value;
}
throw new Error(`Unexpected error: ${response.value}`);
}
roOk
roOk
is a shorthand to call a read-only function and automatically scope to the ok
type of the contract's response. It is only suitable for contract functions that return the (response)
Clarity type.
In the above example, we were manually checking to see that the contract returned an ok
response. We can shorten that to be:
import { clarigen } from './clarigen-client';
import { nftContract } from './clarigen-contracts';
export async function getOwner(id: number | bigint) {
const owner = await clarigen.roOk(nftContract.getOwner(id));
return owner;
}
roErr
Sometimes, the assumption is that an err
is returned from a function, otherwise you want to throw an error. roErr
is a shorthand for that.
import { clarigen } from './clarigen-client';
import { nftContract } from './clarigen-contracts';
// A function that throws if an NFT has an owner:
export async function expectUnowned(id: number | bigint) {
// throws an error unless the response is `err`:
await clarigen.roErr(nftContract.getOwner(id));
return true;
}
Fetching microblock-based state
If you want to query the state of a contract based on the most recent microblock, you can include the latest
option. This is provided by default.
import { clarigen } from './clarigen-client';
import { nftContract } from './clarigen-contracts';
export async function getOwner(id: number | bigint) {
const owner = await clarigen.roOk(nftContract.getOwner(id), { latest: true });
return owner;
}
Returning JSON-valid types
By default, Clarigen returns bigint
for integers and UInt8Array
values for buffers. If you only want to receive valid JSON, you can use the json
option.
When json: true
is specified, integers are returned as string
(to prevent overflows), and buffers are returned as hex-encoded string
.
For example, a u1000
integer would be "1000"
, and 0xdeadbeef
would be "deadbeef"
.
// Return a `string`
export async function getBalanceOf(owner: string): string {
return client.roOk(tokenContract.getBalanceOf(owner), { json: true });
}
Calling read-only functions without a ClarigenClient
instance
If you want to call read-only functions without having to instantiate a ClarigenClient
instance, the @clarigen/core
package exposes pure functions for you. These are available as ro
, roOk
, and roErr
.
When using these functions, you must specify a url
or network
option.
Using url
:
import { ro } from '@clarigen/core';
import { nftContract } from './clarigen-contracts';
const stacksApiUrl = 'https://stacks-node-api.mainnet.stacks.co';
export function getOwner(id: bigint) {
return ro(nftContract.getOwner(id), {
url: stacksApiUrl,
});
}
Or using network
:
import { ro } from '@clarigen/core';
import { nftContract } from './clarigen-contracts';
import { StacksMainnet } from '@stacks/network';
const network = new StacksMainnet();
export function getOwner(id: bigint) {
return ro(nftContract.getOwner(id), {
network,
});
}