Create a balance fetcher
What is a balance?
A balance is a non-zero investment position held by a wallet address in a given app.
For example, perhaps joe.eth
has is holding some Pickle 3Crv jar tokens, and
has staked some Pickle Uniswap V2 LOOKS / ETH jar tokens. Our friend joe.eth
has two balances in the Pickle app.
We know how to enumerate these positions from the previous two sections, but how
do we show joe.eth
his positions?
Easy! We'll build a BalanceFetcher
that will give Pickle balances for any
address.
What is a balance fetcher?
In the Zapper API, a BalanceFetcher
class returns balances for the tokens and
contract positions (indexed from TokenFetcher
and ContractPositionFetcher
classes) of an application.
Generate a contract position fetcher
Our codegen utilities will automatically generate the boilerplate for a balance
fetcher. Run pnpm studio create-balance-fetcher pickle
. When prompted for the
network, select ethereum
.
Implement the balance fetcher
Let's open src/apps/pickle/ethereum/pickle.balance-fetcher.ts
. The skeleton
has been assembled for you, and you'll need to fill in the contents of the
getBalances
method in the EthereumPickleBalanceFetcher
class.
import { Inject } from "@nestjs/common";
import { IAppToolkit, APP_TOOLKIT } from "~app-toolkit/app-toolkit.interface";
import { Register } from "~app-toolkit/decorators";
import { presentBalanceFetcherResponse } from "~app-toolkit/helpers/presentation/balance-fetcher-response.present";
import { BalanceFetcher } from "~balance/balance-fetcher.interface";
import { Network } from "~types/network.interface";
import { PICKLE_DEFINITION } from "../pickle.definition";
const network = Network.ETHEREUM_MAINNET;
@Register.BalanceFetcher(PICKLE_DEFINITION.id, network)
export class EthereumPickleBalanceFetcher implements BalanceFetcher {
constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {}
async getBalances(_address: string) {
return presentBalanceFetcherResponse([]);
}
}
Again, quite similar to the TokenFetcher
and ContractPositionFetcher
, except
in this case, our stub method getBalances
also takes an address
as a
parameter.
Let's get to work!
Resolve jar token balances
We'll use a helper provided on the scope of our AppToolkit
to resolve jar
token balances. In a nutshell, it will simply iterate over all jar tokens, and
call the balanceOf
method on the provided address, returning a list of
AppTokenBalance
objects.
We'll return this as part of our response, and label this set of balances as
Jars
. This label is used by our mobile and frontend clients to categorize
balances by sections.
// ...
@Register.BalanceFetcher(PICKLE_DEFINITION.id, network)
export class EthereumPickleBalanceFetcher implements BalanceFetcher {
constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {}
async getJarTokenBalances(address: string) {
return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({
address,
appId: PICKLE_DEFINITION.id,
groupId: PICKLE_DEFINITION.groups.jar.id,
network: Network.ETHEREUM_MAINNET,
});
}
async getBalances(address: string) {
const [jarTokenBalances] = await Promise.all([
this.getJarTokenBalances(address),
]);
return presentBalanceFetcherResponse([
{
label: "Jars",
assets: jarTokenBalances,
},
]);
}
}
Find an address holding a Pickle vault token (hint: use Etherscan's Holders
tab in the token view) and open
http://localhost:5001/apps/pickle/balances?addresses[]=<ADDRESS>&network=ethereum
in your browser.
Resolve farm position balances
Contract position balances are a little more complicated than token balances.
There's no standard way to resolve the underlying balances, so we'll use the
ContractPositionBalanceHelper
and implement a strategy to resolve the farm
balances.
// ...
@Register.BalanceFetcher(PICKLE_DEFINITION.id, network)
export class EthereumPickleBalanceFetcher implements BalanceFetcher {
// ...
async getFarmBalances(address: string) {
return this.appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances(
{
address,
appId: PICKLE_DEFINITION.id,
groupId: PICKLE_DEFINITION.groups.jar.id,
network: Network.ETHEREUM_MAINNET,
resolveBalances: async ({ address, contractPosition, multicall }) => {
// Resolve the staked token and reward token from the contract position object
const stakedToken = contractPosition.tokens.find(isSupplied)!;
const rewardToken = contractPosition.tokens.find(isClaimable)!;
// Instantiate an Ethers contract instance
const contract =
this.pickleContractFactory.pickleGauge(contractPosition);
// Resolve the requested address' staked balance and earned balance
const [stakedBalanceRaw, rewardBalanceRaw] = await Promise.all([
multicall.wrap(contract).balanceOf(address),
multicall.wrap(contract).earned(address),
]);
// Drill the balance into the token object. Drill will push the balance into the token tree,
// thereby showing the user's exposure to underlying tokens of the jar token!
return [
drillBalance(stakedToken, stakedBalanceRaw.toString()),
drillBalance(rewardToken, rewardBalanceRaw.toString()),
];
},
}
);
}
async getBalances(address: string) {
const [jarTokenBalances, farmBalances] = await Promise.all([
this.getJarTokenBalances(address),
this.getFarmBalances(address),
]);
return presentBalanceFetcherResponse([
{
label: "Jars",
assets: jarTokenBalances,
},
{
label: "Farms",
assets: farmBalances,
},
]);
}
}
Find an address with an open Pickle farm position (hint: use Etherscan's
Transactions tab in the contract view) and open
http://localhost:5001/apps/pickle/balances?addresses[]=<ADDRESS>&network=ethereum
in your browser.
Voila! We have officially set up the fetchers that are neeeded to get our app's investments and balances in Zapper. You can continue to grow your app integration by implementing other networks, or supporting new tokens or farm types, but you are now armed with the tools to do so.
Remember that if you intend to add new NestJS providers to your application,
you'll need to continue to add them to the providers
in your module file. This
step was done for you previously by the code generator.
Let's submit our PR!