<aside> 📜 TABLE OF CONTENTS
</aside>
Vertex Version: 1.0.0 Date: 2025-04-27
In this section, we'll explore how to Indexing in Vertex đź’».
In Vertex side 2 to side
IDL: managing all IDL of program in Solana.
Home: Manage all and your Indexer in Vertex
Now a days, IDL is very common for builder, developer in Solana if they using anchor as framework to build Smart Contract, each program famous in Solana like Raydium, Orca, Kamino, … already publish their IDL. IDL act as the interface
, struct
about the PDA data on chain, helps us reduce the time to write the borsh shema to decode the byte data to object data.
đź““ Recommend
: If you want to index data of what program, PDA that already publish their IDL so choose that to reduce the time write script index, if no you need to write borsh schema inside the script
INDEXER
Indexer is where you can view the structure of table that index data on chain, where you can handle your index data. Firstly create your own Indexer
This is the main part when you index data by Vertex.
Table
This is where your actual data is store when you index, you must choose what the structure of table you want to create.
Trigger & Transformer
Traditionally, when developers index data in Solana, they need two key components:
Vertex is same, developer must specific what thing they want, vertex reduce the time build the infra for listen event change from on chain, let developers focus 100% to handle the transformer. Go to the step create Indexer, you see that you can choose use IDL or not, because of that when your write the “transformer” is have different part with no IDL and have IDL
Firstly take a look of how to create a Trigger and Transformer
The section on the left functions as the Trigger
. Here, you specify the on-chain events that will initiate the indexing process. You must select the type of trigger you want to use. Choosing WS (WebSocket) allows for real-time listening to on-chain data. When a relevant action occurs that changes the data, the indexing process is activated. You also need to specify which Program Derived Address (PDA) you want to monitor for these triggering events, so its data can be transformed.
Has IDL
If your Indexer choose IDL, the value inside the PDA is very important, this is the way how we know what is the account name inside the IDL to parse data
Here is the code when you using anchor to fetch and parse data on chain
...
const seeds = [Buffer.from("campaign"), campaignIdBuffer];
const campaignPDA = PublicKey.findProgramAddressSync(
seeds,
program.programId
)[0];
const campaignData = await program.account.***campaign***.fetch(campaignPDA);
...
You can see behind the account must is the name of account (PDA) want to parse, so you must choose the right name if no will false
Transformer
The next important that is the script transformer, in here is the core logic when you index data. The transformer script have a interface that developer must follow to use
Inside the transformer scripts user MUST have the function name execute, below is the interface of this execute function developer must handle
interface Context {
pdaParser?: any;
pdaBuffer: Buffer;
}
export interface ITransformResult {
action: 'INSERT' | 'UPDATE' | 'DELETE';
data?: ObjectType;
where?: ObjectType;
}
function execute(context): Promise<ITransformResult[]> {
// Handle Core Logic in here
// Sample data return
return [{
action: 'UPDATE',
data: {
advertiser: 'update advertiser',
},
where: {
campaign_id: Number(parser.campaignId),
}
}]
}
The execute function receive one param with the structure is object had 2 field:
pdaParser
: this is optional data. Basically when user choose IDL in the main thread of Vertex already handle by calling data onchain and parse that data using IDL, so if indexer don’t choose IDL this will be null when Vertex execute the functionpdaBuffer
: this is the raw byte data on chain fetch at the time Vertex execute the user transformer script, this always have because in the case developer don’t choose IDL they need to write the schema and then using that schema to decode the raw byte data.The return data must be follow the interface ITransformResult where:
delete
, update
or insert
new data into to the table (In here this will action to the table that is choose in the form above)Vertex will action the result transformer in the order so must be specific what thing you want to do first.
Advance
Index with no IDL
I had create an repo that is Campaign - https://github.com/minhbear/campaign-sol: You can clone or create your own program if you want
In the transformer script because I don’t choose IDL so I need to write borsh schema to decode data
Here is the campaign state in the contract
[account]
#[derive(InitSpace)]
pub struct Campaign {
pub campaign_id: u64,
pub advertiser: Pubkey,
pub data: CampaignData,
}
#[derive(
Clone,
InitSpace,
Debug,
Eq,
PartialEq,
anchor_lang::AnchorDeserialize,
anchor_lang::AnchorSerialize
)]
pub struct CampaignData {
#[max_len(50)]
pub name: String,
#[max_len(50)]
pub cta_link: String,
#[max_len(50)]
pub logo: String,
pub start_date: u64,
pub end_date: u64,
pub budget: u64,
pub rate_per_click: u64,
pub clicks: u64,
pub remaining_budget: u64,
pub status: CampaignStatus,
}
#[derive(
InitSpace,
Clone,
Debug,
Eq,
PartialEq,
anchor_lang::AnchorDeserialize,
anchor_lang::AnchorSerialize
)]
pub enum CampaignStatus {
Upcoming,
Ongoing,
Completed,
Cancelled,
}
Inside the Transformer script I need to handle the borsh
const campaignStatusSchema = utils.common.borsh.rustEnum([
utils.common.borsh.struct([], "Upcoming"),
utils.common.borsh.struct([], "Ongoing"),
utils.common.borsh.struct([], "Completed"),
utils.common.borsh.struct([], "Cancelled"),
]);
const borshCampaignDataSchema = utils.common.borsh.struct([
utils.common.borsh.str("name"),
utils.common.borsh.str("ctaLink"),
utils.common.borsh.str("logo"),
utils.common.borsh.u64("startDate"),
utils.common.borsh.u64("endDate"),
utils.common.borsh.u64("budget"),
utils.common.borsh.u64("ratePerClick"),
utils.common.borsh.u64("clicks"),
utils.common.borsh.u64("remainingBudget"),
campaignStatusSchema.replicate("status"),
]);
const borshCampaignSchema = utils.common.borsh.struct([
utils.common.borsh.u64("campaignId"),
utils.common.borsh.publicKey("advertiser"),
borshCampaignDataSchema.replicate("campaignData"),
]);
function execute(context) {
const pdaBuffer = context.pdaBuffer;
const bufferWithDiscriminator = pdaBuffer.subarray(8);
const parser = borshCampaignSchema.decode(bufferWithDiscriminator);
return [{
action: 'INSERT',
data: {
campaign_id: Number(parser.campaignId) * 100,
advertiser: parser.advertiser.toString(),
start_date: new Date(Number(parser.campaignData.startDate) * 1000),
end_date: new Date(Number(parser.campaignData.endDate) * 1000),
budget: Number(parser.campaignData.budget),
rate_per_click: Number(parser.campaignData.ratePerClick),
}
}]
}
To see the tutorial how to use borsh pls follow this: https://solana.com/developers/courses/native-onchain-development/serialize-instruction-data-frontend
Index with IDL
Because you choose IDL when you create an indexer, so the pdaParser
already have the data, you don’t need to handle the borsh, you just get the pdaParser
and handle your logic
Below I have a sample script index reserve USDC/SOL/… in Kamino Lending
function execute(context) {
const pdaParser = context.pdaParser;
const marketPrice = new utils.kamino.Fraction(pdaParser.liquidity.marketPriceSf);
return [{
action: 'INSERT',
data: {
market_price: marketPrice.toDecimal().toString(),
...
}]
}
Libs common
Vertex already provide some libs common when developer index data. Here is the list the libs and how to use
| Utility | Description |
| ----------------- | ------------------------------------------------------------ |
| `utils.common` | The lib common include BN, Decimals, bs58 |
| `utils.kamino.*` | The utils lib contain all the help class/function for Kamino |
| `utils.raydium.*` | The utils lib contain all help class/function for Raydium |
Inside the utils.common already have some libs is traditional like: BN, Decimals, borsh. You just use by write the code like this
new utils.common.BN(...)
new utils.Decimals(...)
const campaignStatusSchema = utils.common.borsh.rustEnum([
utils.common.borsh.struct([], "Upcoming"),
utils.common.borsh.struct([], "Ongoing"),
utils.common.borsh.struct([], "Completed"),
utils.common.borsh.struct([], "Cancelled"),
]);
Several well-known programs in Solana, especially within the DeFi space, utilize complex mathematics to store data. Examples include fixed-point arithmetic in Kamino, liquidity pool math, and tick math in Raydium. Vertex simplifies this process for you, handling these complexities so you can readily access and use the data.
Kamino:
Data store in Kamino some field using Scaling Factor, Fixed Point (that is real value * 2^60) and they call is fraction, Example above is how I convert the market price in scaling factor to the real market price value
For more detail what the helper, common code in Kamino see in our repo (https://github.com/vertex-solana/vertex-indexer/tree/main/src/indexer/utils-transform/kamino)
const marketPrice = new utils.kamino.Fraction(pdaParser.liquidity.marketPriceSf);
Raydium
With CLMM math in Raydium, the hard part is working with Liquidity, sqrt price, tick math, … . We already install that so review what the code we provide for you when working with Raydium in our repo (https://github.com/vertex-solana/vertex-indexer/tree/main/src/indexer/utils-transform/raydium)
We will continue provide more libs for each program in Solana that make your life indexing data is easier 🚀
See our video tutorial: https://x.com/vertex__sol/status/1914717247479931025