Advanced Usage Guide
This guide covers advanced topics and strategies for working with the Kodiak Islands subgraph, including complex querying patterns, integration strategies, performance optimization, and custom analytics.
Complex Querying Patterns
Fragment Reuse
For complex applications that frequently query similar fields, use GraphQL fragments to reduce duplication:
fragment VaultBasicInfo on KodiakVault {
id
name
symbol
totalValueLockedUSD
apr {
averageApr
}
}
fragment VaultDetailedInfo on KodiakVault {
...VaultBasicInfo
inputToken {
symbol
decimals
}
outputToken {
symbol
decimals
}
pricePerShare
_token0Amount
_token1Amount
_token0AmountUSD
_token1AmountUSD
}
query {
topVaults: kodiakVaults(
first: 5
orderBy: totalValueLockedUSD
orderDirection: desc
) {
...VaultBasicInfo
}
specificVault: kodiakVault(id: "0x1234567890abcdef") {
...VaultDetailedInfo
}
}
Dynamic Querying with Variables
Use GraphQL variables for dynamic queries:
query GetVaultPerformance($vaultId: ID!, $startTime: BigInt!, $endTime: BigInt!) {
snapshots: kodiakVaultDailySnapshots(
where: {
vault: $vaultId
timestamp_gte: $startTime
timestamp_lte: $endTime
}
orderBy: timestamp
) {
timestamp
totalValueLockedUSD
dailyTotalFeesUSD
apr
}
}
This query can be executed with variables:
{
"vaultId": "0x1234567890abcdef",
"startTime": "1640995200",
"endTime": "1672531200"
}
Advanced Filtering Combinations
Combine multiple filters for complex data selection:
query HighPerformingVaults {
kodiakVaultDailySnapshots(
where: {
apr_gt: "20" # APR > 20%
totalValueLockedUSD_gt: "500000" # TVL > $500,000
dailyTotalFeesUSD_gt: "1000" # Daily fees > $1,000
timestamp_gt: "1648771200" # After April 1, 2022
}
orderBy: apr
orderDirection: desc
) {
vault {
id
name
}
timestamp
apr
totalValueLockedUSD
dailyTotalFeesUSD
}
}
Time Series Analysis
Calculating Period-over-Period Changes
To calculate weekly changes in vault performance:
query WeeklyPerformanceChange($vaultId: ID!) {
thisWeek: kodiakVaultDailySnapshots(
first: 7
orderBy: timestamp
orderDirection: desc
where: { vault: $vaultId }
) {
timestamp
totalValueLockedUSD
dailyTotalFeesUSD
volumeUSD
apr
}
previousWeek: kodiakVaultDailySnapshots(
first: 7
skip: 7
orderBy: timestamp
orderDirection: desc
where: { vault: $vaultId }
) {
timestamp
totalValueLockedUSD
dailyTotalFeesUSD
volumeUSD
apr
}
}
With this data, you can calculate week-over-week changes for key metrics in your application:
function calculateChangePercentage(current: number, previous: number): number {
if (previous === 0) return 0;
return ((current - previous) / previous) * 100;
}
// Calculate aggregate values for each week
const thisWeekTVL = average(thisWeekData.map(d => parseFloat(d.totalValueLockedUSD)));
const prevWeekTVL = average(previousWeekData.map(d => parseFloat(d.totalValueLockedUSD)));
const thisWeekFees = sum(thisWeekData.map(d => parseFloat(d.dailyTotalFeesUSD)));
const prevWeekFees = sum(previousWeekData.map(d => parseFloat(d.dailyTotalFeesUSD)));
// Calculate percentage changes
const tvlChange = calculateChangePercentage(thisWeekTVL, prevWeekTVL);
const feesChange = calculateChangePercentage(thisWeekFees, prevWeekFees);
Analyzing APR Trends
Track APR trends over longer periods to identify patterns:
query AprTrends($vaultId: ID!) {
daily: kodiakVaultDailySnapshots(
first: 90 # Last 90 days
orderBy: timestamp
orderDirection: desc
where: { vault: $vaultId }
) {
timestamp
apr
totalValueLockedUSD
}
}
You can use this data to:
Calculate moving averages (7-day, 30-day)
Identify seasonal patterns
Correlate APR changes with TVL or other metrics
Build predictive models for APR forecasting
Integration Strategies
Real-time Data Updates
For applications requiring real-time data, implement a polling strategy:
// Fetch latest vault data every 60 seconds
function setupPolling(vaultId: string) {
const query = `
query GetVaultData($id: ID!) {
kodiakVault(id: $id) {
totalValueLockedUSD
pricePerShare
apr {
averageApr
}
}
}
`;
setInterval(() => {
executeQuery(query, { id: vaultId })
.then(data => updateUI(data.kodiakVault))
.catch(error => handleError(error));
}, 60000);
}
Combining with On-chain Data
For some advanced use cases, you may need to combine subgraph data with direct on-chain calls:
async function getCompleteVaultData(vaultId: string) {
// Get historical and aggregated data from subgraph
const subgraphData = await fetchFromSubgraph(vaultId);
// Get real-time position data from the blockchain
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const vaultContract = new ethers.Contract(vaultId, VAULT_ABI, provider);
const currentPosition = await vaultContract.getCurrentPosition();
// Combine the data
return {
...subgraphData,
currentPosition: {
lowerTick: currentPosition.lowerTick.toString(),
upperTick: currentPosition.upperTick.toString(),
liquidity: currentPosition.liquidity.toString()
}
};
}
Building Custom Analytics
Create custom analytics by combining multiple queries:
async function vaultPerformanceAnalytics(vaultId: string) {
// Fetch basic vault info
const vaultInfo = await fetchVaultInfo(vaultId);
// Fetch historical data
const dailyData = await fetchDailySnapshots(vaultId, 90); // Last 90 days
// Fetch user activity
const deposits = await fetchDeposits(vaultId);
const withdrawals = await fetchWithdrawals(vaultId);
// Calculate custom metrics
const userRetentionRate = calculateUserRetention(deposits, withdrawals);
const volatilityScore = calculateVolatility(dailyData.map(d => d.apr));
const performanceScore = calculatePerformanceScore(
vaultInfo.apr.averageApr,
volatilityScore,
userRetentionRate
);
return {
vaultInfo,
userRetentionRate,
volatilityScore,
performanceScore,
dailyTrends: processDailyTrends(dailyData)
};
}
Performance Optimization
Query Optimization
Optimize your queries to reduce response time and load on the subgraph:
Select only necessary fields:
# Instead of this { kodiakVaults { id name symbol # ... many more fields } } # Do this { kodiakVaults { id name totalValueLockedUSD # Only the fields you need } }
Limit result sizes:
{ kodiakVaults(first: 20) { # fields } }
Use efficient filtering:
# Instead of fetching all and filtering client-side { kodiakVaults(where: { totalValueLockedUSD_gt: "1000000" }) { # fields } }
Client-Side Caching
Implement caching to reduce redundant queries:
// Simple in-memory cache example
const cache = new Map();
async function fetchWithCache(query, variables, maxAge = 60000) {
const cacheKey = JSON.stringify({ query, variables });
if (cache.has(cacheKey)) {
const { data, timestamp } = cache.get(cacheKey);
if (Date.now() - timestamp < maxAge) {
return data;
}
}
const result = await executeQuery(query, variables);
cache.set(cacheKey, {
data: result,
timestamp: Date.now()
});
return result;
}
Batching Queries
For applications that need multiple related pieces of data, batch your queries:
query BatchedData($vaultId: ID!) {
vault: kodiakVault(id: $vaultId) {
id
name
totalValueLockedUSD
apr {
averageApr
}
}
recentDeposits: kodiakDeposits(
first: 5,
orderBy: timestamp,
orderDirection: desc,
where: { vault: $vaultId }
) {
timestamp
amountUSD
}
dailyStats: kodiakVaultDailySnapshots(
first: 7,
orderBy: timestamp,
orderDirection: desc,
where: { vault: $vaultId }
) {
timestamp
dailyTotalFeesUSD
}
}
Advanced Use Cases
Strategy Analysis
Analyze the effectiveness of different vault strategies by tracking changes to tick ranges:
query StrategyEffectiveness($vaultId: ID!) {
# Get all snapshots and process client-side
snapshots: kodiakVaultDailySnapshots(
orderBy: timestamp
where: { vault: $vaultId }
) {
timestamp
lowerTick
upperTick
# If tickChanges is available in your snapshots, include it
tickChanges {
timestamp
lowerTick
upperTick
}
dailyTotalFeesUSD
apr
}
}
This data can be used to:
Compare APR before and after strategy changes
Identify optimal tick ranges for different market conditions
Evaluate the effectiveness of strategy adjustments
Portfolio Analysis
For users with positions across multiple vaults, build portfolio analytics:
query UserPortfolio($userAddress: String!) {
# Get all user deposits
deposits: kodiakDeposits(
where: { from: $userAddress }
orderBy: timestamp
) {
vault {
id
name
_token0 {
symbol
}
_token1 {
symbol
}
}
timestamp
amount
amountUSD
}
# Get all user withdrawals
withdrawals: kodiakWithdraws(
where: { to: $userAddress }
orderBy: timestamp
) {
vault {
id
name
}
timestamp
amount
amountUSD
}
}
With this data, you can:
Calculate user's net position in each vault
Estimate their portfolio value over time
Calculate portfolio-wide performance metrics
Vault Comparison Tool
Build a tool to compare performance across vaults:
query CompareVaults($vaultIds: [ID!]!) {
vaults: kodiakVaults(
where: { id_in: $vaultIds }
) {
id
name
_token0 {
symbol
}
_token1 {
symbol
}
totalValueLockedUSD
apr {
averageApr
}
weeklyFeesEarnedUSD
}
# Get daily performance for each vault (last 30 days)
dailyPerformance: kodiakVaultDailySnapshots(
first: 30
orderBy: timestamp
orderDirection: desc
where: { vault_in: $vaultIds }
) {
vault {
id
}
timestamp
apr
dailyTotalFeesUSD
totalValueLockedUSD
}
}
Working with Time Series Data
When working with time series data from the Kodiak Islands subgraph, consider these strategies:
Handling Missing Data Points: Daily and hourly snapshots may have missing data points. Implement interpolation strategies in your application to handle these gaps.
Normalizing Timestamps: Convert Unix timestamps to your local timezone for display:
function formatTimestamp(unixTimestamp) { return new Date(unixTimestamp * 1000).toLocaleString(); }
Grouping Data: For longer time ranges, group data into periods (weekly, monthly):
function groupDataByWeek(dailyData) { const weeklyData = {}; dailyData.forEach(day => { const weekNumber = getWeekNumber(day.timestamp); if (!weeklyData[weekNumber]) { weeklyData[weekNumber] = { totalFeesUSD: 0, avgTVL: 0, daysCount: 0 }; } weeklyData[weekNumber].totalFeesUSD += parseFloat(day.dailyTotalFeesUSD); weeklyData[weekNumber].avgTVL += parseFloat(day.totalValueLockedUSD); weeklyData[weekNumber].daysCount += 1; }); // Calculate averages Object.keys(weeklyData).forEach(week => { weeklyData[week].avgTVL /= weeklyData[week].daysCount; }); return weeklyData; }
Conclusion
This advanced guide has covered complex querying patterns, integration strategies, performance optimization, and various use cases for the Kodiak Islands subgraph. By leveraging these techniques, you can build sophisticated applications that provide powerful insights into the Kodiak Islands protocol and its vaults.
Remember that subgraph queries can be resource-intensive, so always optimize your queries and implement appropriate caching strategies. For very large-scale applications, consider implementing additional middleware to handle complex data processing and caching.
For any questions or issues with the subgraph, refer to the official Kodiak Islands documentation or contact the protocol team for support.
Last updated