State Lookup
In this guide, you will learn how to get the following using the Viem TypeScript library:
- the Flare address that corresponds to an XRPL account
- the total value of FXRP that belongs to a personal account tied to an XRPL address
- a list of all the registered vaults
- the total value of FXRP of a personal account, in each of the registered vaults
- a list of all the registered agent vaults
The code example is available on the Flare Foundation GitHub.
import { Wallet } from "xrpl";
import {
getAgentVaults,
getOperatorXrplAddresses,
getPersonalAccountAddress,
getVaults,
} from "./utils/smart-accounts";
import { getFxrpBalance } from "./utils/fassets";
import { publicClient } from "./utils/client";
import { erc4626Abi } from "viem";
import type { Address } from "viem";
async function getVaultBalance(vaultAddress: Address, accountAddress: Address) {
const vaultBalance = await publicClient.readContract({
address: vaultAddress,
abi: erc4626Abi,
functionName: "balanceOf",
args: [accountAddress],
});
return vaultBalance;
}
async function main() {
const xrplWallet = Wallet.fromSeed(process.env.XRPL_SEED!);
const operatorXrplAddress = await getOperatorXrplAddresses();
console.log("Operator XRPL addresses:", operatorXrplAddress, "\n");
const personalAccountAddress = await getPersonalAccountAddress(
xrplWallet.address,
);
console.log("Personal account address:", personalAccountAddress, "\n");
const fxrpBalance = await getFxrpBalance(personalAccountAddress);
console.log("Personal account FXRP balance:", fxrpBalance, "\n");
const vaults = await getVaults();
console.log("Vaults:", vaults, "\n");
for (const vault of vaults) {
const vaultBalance = await getVaultBalance(
vault.address,
personalAccountAddress,
);
console.log(`Vault ${vault.id} balance:`, vaultBalance, "\n");
}
const agentVaults = await getAgentVaults();
console.log("Agent vaults:", agentVaults, "\n");
}
void main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Details
Expected output
The following is an example of the expected output. The values will differ slightly, but the output should follow the same general format. The personal account address is tied to the XRPL address obtained from the XRPL secret value in the.env file.Operator XRPL addresses: [ 'rEyj8nsHLdgt79KJWzXR5BgF7ZbaohbXwq' ]
Personal account address: 0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F
Personal account FXRP balance: 0n
Vaults: [
{
id: 2n,
address: '0x2C5f203CAd22f3d351912e634dDd0f93C6D503d9',
type: 2
},
{
id: 1n,
address: '0x9BbE85a672Dd2fE2197516656FB2dC76c974954d',
type: 1
}
]
Vault 2 balance: 20000000n
Vault 1 balance: 0n
Agent vaults: [ { id: 1n, address: '0x55c815260cBE6c45Fe5bFe5FF32E3C7D746f14dC' } ]
Let us examine each of the helper functions in more detail. First of all, we need to define a Viem public client. Since the two Flare mainnets and the two testnets are already registered with Viem, we simply need to import them. Then, we create a basic public client with the HTTP transporter.
import { createPublicClient, http } from "viem";
import { flareTestnet } from "viem/chains";
export const publicClient = createPublicClient({
chain: flareTestnet,
transport: http(),
});
With that, we can look at the actual functions.
All of them rely primarily on the readContract function of the Viem publicClient to call read functions of contracts on the Flare chain.
Operator's XRPL addresses
The getOperatorXrplAddresses calls the getXrplProviderWallets function of the MasterAccountController contract.
The function returns an array of XRPL addresses; these are all the registered operator XRPL addresses.
export async function getOperatorXrplAddresses() {
const result = await publicClient.readContract({
address: MASTER_ACCOUNT_CONTROLLER_ADDRESS,
abi: coston2.iMasterAccountControllerAbi,
functionName: "getXrplProviderWallets",
args: [],
});
return result as string[];
}
Because the Flare smart accounts are still in development, the MasterAccountController contract has not yet been added to the FlareContractRegistry contract.
That is the reason why the address of the MasterAccountController has been hardcoded in the example repository.
export const MASTER_ACCOUNT_CONTROLLER_ADDRESS =
"0x32F662C63c1E24bB59B908249962F00B61C6638f";
Personal account of an XRPL address
There are many situations when it is important or even necessary to know the address of the PersonalAccount of an XRPL address;
like monitoring different emitted events and retrieving user balances.
The getPersonalAccountAddress function retrieves the address of the PersonalAccount contract, belonging to the given XRPL address.
It calls the getPersonalAccount function of the MasterAccountController contract, with the XRPL address as the function argument.
export async function getPersonalAccountAddress(xrplAddress: string) {
const personalAccountAddress = await publicClient.readContract({
address: MASTER_ACCOUNT_CONTROLLER_ADDRESS,
abi: coston2.iMasterAccountControllerAbi,
functionName: "getPersonalAccount",
args: [xrplAddress],
});
return personalAccountAddress;
}
Account's FXRP balance
The getFxrpBalance function looks up the current FXRP balance of the given address.
It calls the balanceOf function, which is a generic ERC-20 function of the FXRP token.
export async function getFxrpBalance(address: Address) {
const fxrpAddress = await getFxrpAddress();
const fxrpBalance = await publicClient.readContract({
address: fxrpAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [address],
});
return fxrpBalance;
}
The FXRP token address is obtained from the AssetManagerFXRP contract by calling its fAsset function.
export async function getFxrpAddress(): Promise<Address> {
const assetManagerAddress = await getAssetManagerFXRPAddress();
const fxrpAddress = await publicClient.readContract({
address: assetManagerAddress,
abi: coston2.iAssetManagerAbi,
functionName: "fAsset",
});
return fxrpAddress;
}
The AssetManagerFXRP contract is one of the contracts registered with the FlareContractRegistry contract.
This allows us to read the AssetManagerFXRP contract's address from the latter.
export async function getAssetManagerFXRPAddress(): Promise<Address> {
const assetManagerAddress =
await getContractAddressByName("AssetManagerFXRP");
return assetManagerAddress;
}
export async function getContractAddressByName(name: string) {
const contractAddress = await publicClient.readContract({
address: FLARE_CONTRACT_REGISTRY_ADDRESS,
abi: coston2.iFlareContractRegistryAbi,
functionName: "getContractAddressByName",
args: [name],
});
return contractAddress;
}
Vaults and vault balances
Certain instructions require the user to specify a vault ID. The user should also be able to check their share of each vault to which they have committed their funds. That is why it is important that we know how to retrieve those values.
To get the array of registered vaults, we define two helper types.
The getVaults function of the MasterAccountController contract returns an array of three arrays;
the first one represents the vault IDs, the second their addresses, and the third their types.
The vaults are either of the Firelight (1) or the Upshift (2) type.
The GetVaultsReturnType type allows us to properly interpret that data.
The getVaults function calls the function of the MasterAccountController contract with the same name.
It iterates over the three sub-arrays and constructs a new array of the Vault type.
export type Vault = {
id: bigint;
address: Address;
type: number;
};
export type GetVaultsReturnType = [bigint[], string[], number[]];
export async function getVaults(): Promise<Vault[]> {
const _vaults = (await publicClient.readContract({
address: MASTER_ACCOUNT_CONTROLLER_ADDRESS,
abi: coston2.iMasterAccountControllerAbi,
functionName: "getVaults",
args: [],
})) as GetVaultsReturnType;
const length = _vaults[0].length;
if (length === 0) {
return [];
}
const vaults = new Array(length) as Vault[];
_vaults[0].forEach((id, index) => {
vaults[index] = {
id,
address: _vaults[1][index]! as Address,
type: _vaults[2][index]!,
};
});
return vaults;
}
On the testnet, the vaults are mocks.
They are straightforward ERC-4626 implementations.
To get the vault balance of a Flare address, we call the ERC-46426 balanceOf function on the selected vault, with the account address as the argument.
async function getVaultBalance(vaultAddress: Address, accountAddress: Address) {
const vaultBalance = await publicClient.readContract({
address: vaultAddress,
abi: erc4626Abi,
functionName: "balanceOf",
args: [accountAddress],
});
return vaultBalance;
}
Agent vaults
Smart account instructions, which include a minting step (FXRPCollateralReservation, FirelightCollateralReservationAndDeposit, and UpshiftCollateralReservationAndDeposit), require the user to specify the ID of the agent vault they wish to use.
We obtain the list of all the registered agent vaults in a similar way to how we do for the deposit vaults.
export type AgentVault = {
id: bigint;
address: Address;
};
export type GetAgentVaultsReturnType = [bigint[], string[]];
export async function getAgentVaults(): Promise<AgentVault[]> {
const _vaults = await publicClient.readContract({
address: MASTER_ACCOUNT_CONTROLLER_ADDRESS,
abi: coston2.iMasterAccountControllerAbi,
functionName: "getAgentVaults",
args: [],
});
const length = _vaults[0].length;
if (length === 0) {
return [];
}
const vaults = new Array(length) as AgentVault[];
_vaults[0].forEach((id, index) => {
vaults[index] = {
id,
address: _vaults[1][index]!,
};
});
return vaults;
}