Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,19 @@ RPC_URL=
STARTING_BLOCK_LST=1359445
# Number of batches of 1000 you wanna process
NUM_BATCHES=2

# --- Vesu Extended GraphQL: optional capital baselines (not indexed; added to aggregates in vesuExtendedUsdcSummary) ---
# Per-strategy JSON (keys = strategy_id). Overrides globals below when set for that strategy.
# Amounts: vaUsdcRaw / walletUsdcRaw = USDC raw integer (6 decimals, same units as on-chain Transfer amount).
# extendedFreeQuantized = Extended core deposit quanta (same unit as indexed quantized_amount).
# VESU_EXTENDED_CAPITAL_BASELINES={"extended_usdc_test":{"vaUsdcRaw":"1000000","walletUsdcRaw":"500000","extendedFreeQuantized":"0"}}
# Globals (single-strategy deploys) if JSON key missing:
# VESU_EXTENDED_INITIAL_VA_USDC_RAW=0
# VESU_EXTENDED_INITIAL_WALLET_USDC_RAW=0
# VESU_EXTENDED_INITIAL_EXTENDED_FREE_QUANTIZED=0
# Optional GraphQL args on vesuExtendedUsdcSummary override env for that request: initial_va_usdc_raw, initial_wallet_usdc_raw, initial_extended_free_quantized

# --- poll-extended-trades (npm run poll-extended-trades) ---
# EXTENDED_BACKEND_URL_READ=
# EXTENDED_TRADES_START_TIME=2025-03-01T00:00:00.000Z
# Or unix seconds / ms; or EXTENDED_TRADES_START_TIME_MS=1740787200000 — trades strictly before cutoff are not upserted
214 changes: 171 additions & 43 deletions indexers/drizzle/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,6 @@ export const position_updated = pgTable('position_updated', {
.on(position_updated.block_number, position_updated.tx_index, position_updated.event_index)
}));

export const strategy_metadata = pgTable('strategy_metadata', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
strategy_address: text('strategy_address').notNull().unique(),
strategy_name: text('strategy_name').notNull(),
quote_asset: text('quote_asset').notNull()
}, (strategy_metadata) => ({
'strategy_metadata_id': uniqueIndex('strategy_metadata_id')
.on(strategy_metadata.strategy_address)
}));

export const raw_price_events = pgTable('raw_price_events', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
block_number: integer('block_number').notNull(),
Expand All @@ -115,40 +105,15 @@ export const raw_price_events = pgTable('raw_price_events', {
.on(raw_price_events.block_number, raw_price_events.tx_index, raw_price_events.event_index)
}));

export const prices = pgTable('prices', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
asset: text('asset').notNull(),
price: doublePrecision('price').notNull(),
timestamp: integer('timestamp').notNull(),
block_number: integer('block_number').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
}, (prices) => ({
'price_id': uniqueIndex('price_id')
.on(prices.asset, prices.timestamp)
}));

export const token_metadata = pgTable('token_metadata', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
address: text('address').notNull().unique(),
name: text('name').notNull(),
symbol: text('symbol').notNull(),
decimals: integer('decimals').notNull(),
pragma_pair_id: text('pragma_pair_id').notNull(),
pragma_decimals: integer('pragma_decimals').notNull()
}, (token_metadata) => ({
'token_metadata_id': uniqueIndex('token_metadata_id')
.on(token_metadata.address)
}));

export const svk_alt_redemptions_subscribed = pgTable('svk_alt_redemptions_subscribed', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
block_number: integer('block_number').notNull(),
tx_index: integer('tx_index').notNull(),
event_index: integer('event_index').notNull(),
tx_hash: text('tx_hash').notNull(),
contract_address: text('contract_address').notNull(),
new_nft_id: text('new_nft_id').notNull(),
old_nft_id: text('old_nft_id').notNull(),
new_nft_id: integer('new_nft_id').notNull(),
old_nft_id: integer('old_nft_id').notNull(),
receiver: text('receiver').notNull(),
timestamp: integer('timestamp').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
Expand All @@ -164,10 +129,10 @@ export const svk_alt_redemptions_claimed = pgTable('svk_alt_redemptions_claimed'
event_index: integer('event_index').notNull(),
tx_hash: text('tx_hash').notNull(),
contract_address: text('contract_address').notNull(),
new_nft_id: text('new_nft_id').notNull(),
old_nft_id: text('old_nft_id').notNull(),
new_nft_id: integer('new_nft_id').notNull(),
old_nft_id: integer('old_nft_id').notNull(),
receivable: text('receivable').notNull(),
swap_id: text('swap_id').notNull(),
swap_id: integer('swap_id').notNull(),
timestamp: integer('timestamp').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
}, (svk_alt_redemptions_claimed) => ({
Expand All @@ -182,8 +147,8 @@ export const svk_alt_redemptions_unsubscribed = pgTable('svk_alt_redemptions_uns
event_index: integer('event_index').notNull(),
tx_hash: text('tx_hash').notNull(),
contract_address: text('contract_address').notNull(),
new_nft_id: text('new_nft_id').notNull(),
old_nft_id: text('old_nft_id').notNull(),
new_nft_id: integer('new_nft_id').notNull(),
old_nft_id: integer('old_nft_id').notNull(),
owner: text('owner').notNull(),
is_old_nft_returned: boolean('is_old_nft_returned').notNull(),
is_original_assets_returned: boolean('is_original_assets_returned').notNull(),
Expand All @@ -195,6 +160,46 @@ export const svk_alt_redemptions_unsubscribed = pgTable('svk_alt_redemptions_uns
.on(svk_alt_redemptions_unsubscribed.block_number, svk_alt_redemptions_unsubscribed.tx_index, svk_alt_redemptions_unsubscribed.event_index)
}));

export const strategy_metadata = pgTable('strategy_metadata', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
strategy_address: text('strategy_address').notNull().unique(),
strategy_name: text('strategy_name').notNull(),
quote_asset: text('quote_asset').notNull()
}, (strategy_metadata) => ({
'strategy_metadata_id': uniqueIndex('strategy_metadata_id')
.on(strategy_metadata.strategy_address)
}));

export const prices = pgTable('prices', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
asset: text('asset').notNull(),
price: doublePrecision('price').notNull(),
timestamp: integer('timestamp').notNull(),
block_number: integer('block_number').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
}, (prices) => ({
'price_id': uniqueIndex('price_id')
.on(prices.asset, prices.timestamp)
}));

export const token_metadata = pgTable('token_metadata', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
address: text('address').notNull().unique(),
name: text('name').notNull(),
symbol: text('symbol').notNull(),
decimals: integer('decimals').notNull(),
pragma_pair_id: text('pragma_pair_id').notNull(),
pragma_decimals: integer('pragma_decimals').notNull()
}, (token_metadata) => ({
'token_metadata_id': uniqueIndex('token_metadata_id')
.on(token_metadata.address)
}));

export const lst_price_sync_progress = pgTable('lst_price_sync_progress', {
id: text('id').notNull().primaryKey().default("lst_price_sync"),
last_processed_block: integer('last_processed_block')
});

export const svk_alt_redemptions = pgTable('svk_alt_redemptions', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
block_number: integer('block_number').notNull(),
Expand All @@ -218,4 +223,127 @@ export const svk_alt_redemptions = pgTable('svk_alt_redemptions', {
}, (svk_alt_redemptions) => ({
'redemption_unique': uniqueIndex('redemption_unique')
.on(svk_alt_redemptions.contract_address, svk_alt_redemptions.old_nft_id)
}));
}));

export const strategy_apy = pgTable('strategy_apy', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
strategy_id: text('strategy_id').notNull(),
strategy_address: text('strategy_address').notNull(),
net_apy: doublePrecision('net_apy'),
timestamp: integer('timestamp').notNull(),
block_number: integer('block_number')
}, (strategy_apy) => ({
'strategy_apy_unique': uniqueIndex('strategy_apy_unique')
.on(strategy_apy.strategy_id, strategy_apy.timestamp)
}));

export const vesu_extended_usdc_transfers = pgTable('vesu_extended_usdc_transfers', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
block_number: integer('block_number').notNull(),
tx_index: integer('tx_index').notNull(),
event_index: integer('event_index').notNull(),
tx_hash: text('tx_hash').notNull(),
strategy_id: text('strategy_id').notNull(),
flow_type: text('flow_type').notNull(),
from_address: text('from_address').notNull(),
to_address: text('to_address').notNull(),
amount: text('amount').notNull(),
timestamp: integer('timestamp').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
}, (vesu_extended_usdc_transfers) => ({
'vesu_ext_usdc_transfer_event_id': uniqueIndex('vesu_ext_usdc_transfer_event_id')
.on(vesu_extended_usdc_transfers.block_number, vesu_extended_usdc_transfers.tx_index, vesu_extended_usdc_transfers.event_index)
}));

export const vesu_extended_modify_position = pgTable('vesu_extended_modify_position', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
block_number: integer('block_number').notNull(),
tx_index: integer('tx_index').notNull(),
event_index: integer('event_index').notNull(),
tx_hash: text('tx_hash').notNull(),
strategy_id: text('strategy_id').notNull(),
pool_contract: text('pool_contract').notNull(),
collateral_asset: text('collateral_asset').notNull(),
debt_asset: text('debt_asset').notNull(),
user_address: text('user_address').notNull(),
collateral_delta: text('collateral_delta').notNull(),
collateral_shares_delta: text('collateral_shares_delta').notNull(),
debt_delta: text('debt_delta').notNull(),
nominal_debt_delta: text('nominal_debt_delta').notNull(),
timestamp: integer('timestamp').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
}, (vesu_extended_modify_position) => ({
'vesu_ext_modify_event_id': uniqueIndex('vesu_ext_modify_event_id')
.on(vesu_extended_modify_position.block_number, vesu_extended_modify_position.tx_index, vesu_extended_modify_position.event_index)
}));

export const vesu_extended_multiply_lever = pgTable('vesu_extended_multiply_lever', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
block_number: integer('block_number').notNull(),
tx_index: integer('tx_index').notNull(),
event_index: integer('event_index').notNull(),
tx_hash: text('tx_hash').notNull(),
strategy_id: text('strategy_id').notNull(),
lever_kind: text('lever_kind').notNull(),
pool_id: text('pool_id').notNull(),
collateral_asset: text('collateral_asset').notNull(),
debt_asset: text('debt_asset').notNull(),
user_address: text('user_address').notNull(),
margin: text('margin').notNull(),
collateral_delta: text('collateral_delta').notNull(),
debt_delta: text('debt_delta').notNull(),
timestamp: integer('timestamp').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
}, (t) => ({
'vesu_ext_multiply_lever_event_id': uniqueIndex('vesu_ext_multiply_lever_event_id')
.on(t.block_number, t.tx_index, t.event_index)
}));

export const vesu_extended_ekubo_swapped = pgTable('vesu_extended_ekubo_swapped', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
block_number: integer('block_number').notNull(),
tx_index: integer('tx_index').notNull(),
event_index: integer('event_index').notNull(),
tx_hash: text('tx_hash').notNull(),
strategy_id: text('strategy_id').notNull(),
locker: text('locker').notNull(),
token0: text('token0').notNull(),
token1: text('token1').notNull(),
fee: text('fee').notNull(),
tick_spacing: text('tick_spacing').notNull(),
extension: text('extension').notNull(),
swap_amount_signed: text('swap_amount_signed').notNull(),
is_token1: text('is_token1').notNull(),
sqrt_ratio_limit: text('sqrt_ratio_limit').notNull(),
skip_ahead: text('skip_ahead').notNull(),
delta0_signed: text('delta0_signed').notNull(),
delta1_signed: text('delta1_signed').notNull(),
sqrt_ratio_after: text('sqrt_ratio_after').notNull(),
tick_after_signed: text('tick_after_signed').notNull(),
liquidity_after: text('liquidity_after').notNull(),
timestamp: integer('timestamp').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
}, (t) => ({
'vesu_ext_ekubo_swap_event_id': uniqueIndex('vesu_ext_ekubo_swap_event_id')
.on(t.block_number, t.tx_index, t.event_index)
}));

export const vesu_extended_core_deposits = pgTable('vesu_extended_core_deposits', {
id: text('id').notNull().primaryKey().default(sql`gen_random_uuid()`),
block_number: integer('block_number').notNull(),
tx_index: integer('tx_index').notNull(),
event_index: integer('event_index').notNull(),
tx_hash: text('tx_hash').notNull(),
strategy_id: text('strategy_id').notNull(),
vault_id_key: text('vault_id_key').notNull(),
va_address: text('va_address').notNull(),
collateral_id: text('collateral_id').notNull(),
quantized_amount: text('quantized_amount').notNull(),
unquantized_amount: text('unquantized_amount').notNull(),
salt: text('salt').notNull(),
timestamp: integer('timestamp').notNull(),
cursor: bigint('_cursor', { mode: 'bigint' })
}, (vesu_extended_core_deposits) => ({
'vesu_ext_core_dep_event_id': uniqueIndex('vesu_ext_core_dep_event_id')
.on(vesu_extended_core_deposits.block_number, vesu_extended_core_deposits.tx_index, vesu_extended_core_deposits.event_index)
}));
56 changes: 37 additions & 19 deletions indexers/utils/common_transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,27 +206,45 @@ export async function processEvent(
}

// Parse data
let indexAdjustment = 0;
for (let index = 0; index < event.data.length; index++) {
let dataField: any = event.data[index];
const field = config.dataFields[index - indexAdjustment];
if (config.dataFields[index - indexAdjustment].type == "u256") {
// if next event data is not 0, throw an error
dataField = uint256
.uint256ToBN({
low: event.data[index],
high: event.data[index + 1],
})
.toString();
// allows u to skip this index for the data type
indexAdjustment++;
index++;
}
if (!config.skipDataFieldParsing) {
let indexAdjustment = 0;
for (let index = 0; index < event.data.length; index++) {
let dataField: any = event.data[index];
const field = config.dataFields[index - indexAdjustment];
if (!field) {
throw new Error(
`${config.tableName}: event data length ${event.data.length} exceeds dataFields schema`,
);
}
if (field.type === "i257") {
const low = BigInt(event.data[index]);
const high = BigInt(event.data[index + 1]);
const isNegative = BigInt(event.data[index + 2]) !== 0n;
const abs = low + (high << 128n);
const signed = isNegative ? -abs : abs;
if (field.sqlType !== "skip") {
result[field.name] = signed.toString();
}
indexAdjustment += 2;
index += 2;
continue;
}
if (config.dataFields[index - indexAdjustment].type == "u256") {
dataField = uint256
.uint256ToBN({
low: event.data[index],
high: event.data[index + 1],
})
.toString();
indexAdjustment++;
index++;
}

if (field.sqlType == "skip") {
continue;
if (field.sqlType == "skip") {
continue;
}
result[field.name] = convertToSqlFormat(dataField, field);
}
result[field.name] = convertToSqlFormat(dataField, field);
}

// Add additional fields
Expand Down
5 changes: 5 additions & 0 deletions indexers/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { onEventEkuboVault } from "./ekubo_vault";
import { CONFIG_INVESTMENT_FLOWS_ERC4626, EKUBO_VAULT_CONTRACTS } from "./configs/investment_flows_erc4626";
import { CONFIG_INVESTMENT_FLOWS_STARKNET_VAULT_KIT } from "./configs/investment_flows_starknet_vault_kit";
import { CONFIG_PRAGMA_PRICE } from "./configs/pragma_price";
import { CONFIG_VESU_EXTENDED_VAULT_OPS } from "./configs/vesu_extended_vault_ops";

export interface EventField {
name: string;
/** u256 = two felts (low, high); i257 = alexandria i257 (u256 abs low+high, then is_negative felt) */
type: string;
sqlType: string;
}
Expand Down Expand Up @@ -51,6 +53,8 @@ export interface EventConfig {
dataFields: EventField[];
additionalFields: AdditionalField[];
includeReceipt?: boolean;
/** When true, skip declarative data parsing; onEvent must populate from event.data. */
skipDataFieldParsing?: boolean;
onEvent?: OnEvent;
}

Expand All @@ -68,6 +72,7 @@ export const CONFIG: EventConfig[] = [
...CONFIG_PRAGMA_PRICE,
...CONFIG_INVESTMENT_FLOWS_ERC4626,
...CONFIG_INVESTMENT_FLOWS_STARKNET_VAULT_KIT,
...CONFIG_VESU_EXTENDED_VAULT_OPS,

{
tableName: "harvests",
Expand Down
Loading