Query Examples
此内容尚不支持你的语言。
Practical examples for common query patterns using the Sai Keeper API.
Table of Contents
Section titled “Table of Contents”These examples use Apollo Client, but the queries work with any GraphQL client.
import { ApolloClient, InMemoryCache, gql } from '@apollo/client'
const client = new ApolloClient({ uri: 'https://sai-keeper.testnet-2.nibiru.fi/graphql', cache: new InMemoryCache()})Perp Queries
Section titled “Perp Queries”Example 1: Get All Open Positions for a Trader
Section titled “Example 1: Get All Open Positions for a Trader”const GET_OPEN_POSITIONS = gql` query GetOpenPositions($trader: String!) { perp { trades( where: { trader: $trader isOpen: true } order_desc: true ) { id isLong leverage collateralAmount openPrice sl tp perpBorrowing { baseToken { symbol name logoUrl } collateralToken { symbol decimals } marketId } openBlock { block_ts } state { pnlCollateral pnlPct pnlCollateralAfterFees liquidationPrice positionValue borrowingFeeCollateral } } } }`
// Usageconst { data } = await client.query({ query: GET_OPEN_POSITIONS, variables: { trader: 'nibi1abc...' }})
// Process resultsdata.perp.trades.forEach(trade => { const token = trade.perpBorrowing.baseToken const decimals = trade.perpBorrowing.collateralToken.decimals
console.log(`${token.symbol} ${trade.isLong ? 'LONG' : 'SHORT'}`) console.log(`Leverage: ${trade.leverage}x`) console.log(`PnL: ${(trade.state.pnlPct * 100).toFixed(2)}%`) console.log(`Liquidation: $${trade.state.liquidationPrice.toFixed(2)}`)})Example 2: Get Trade History with Realized PnL
Section titled “Example 2: Get Trade History with Realized PnL”const GET_TRADE_HISTORY = gql` query GetTradeHistory($trader: String!, $limit: Int!) { perp { tradeHistory( where: { trader: $trader } limit: $limit order_by: sequence order_desc: true ) { id tradeChangeType realizedPnlCollateral realizedPnlPct block { block block_ts } trade { id isLong leverage openPrice closePrice perpBorrowing { baseToken { symbol } collateralToken { symbol decimals } } } } } }`
// Usageconst { data } = await client.query({ query: GET_TRADE_HISTORY, variables: { trader: 'nibi1abc...', limit: 50 }})
// Calculate total realized PnLconst totalPnL = data.perp.tradeHistory .filter(h => h.realizedPnlCollateral !== null) .reduce((sum, h) => sum + h.realizedPnlCollateral, 0)
console.log(`Total Realized PnL: ${totalPnL}`)Example 3: Get Market Information and Funding Rates
Section titled “Example 3: Get Market Information and Funding Rates”const GET_MARKET_INFO = gql` query GetMarketInfo($collateralId: Int!, $marketId: Int!) { perp { borrowing(collateralId: $collateralId, marketId: $marketId) { marketId baseToken { symbol name logoUrl } quoteToken { symbol } collateralToken { symbol decimals } price oiLong oiShort oiMax feesPerHourLong feesPerHourShort openFeePct closeFeePct maxLeverage minLeverage minPositionSizeUSD } } }`
// Usageconst { data } = await client.query({ query: GET_MARKET_INFO, variables: { collateralId: 1, marketId: 1 }})
const market = data.perp.borrowing
// Calculate funding rate APRconst hourlyRateLong = market.feesPerHourLongconst aprLong = hourlyRateLong * 24 * 365 * 100
console.log(`${market.baseToken.symbol} Market`)console.log(`Price: $${market.price}`)console.log(`OI Long: ${market.oiLong} | Short: ${market.oiShort}`)console.log(`Max Leverage: ${market.maxLeverage}x`)console.log(`Funding APR Long: ${aprLong.toFixed(2)}%`)Example 4: List All Available Markets
Section titled “Example 4: List All Available Markets”const GET_ALL_MARKETS = gql` query GetAllMarkets { perp { borrowings(order_by: base_token_name) { marketId baseToken { symbol name logoUrl tradingViewSymbol } collateralToken { symbol } visible } } }`
// Usageconst { data } = await client.query({ query: GET_ALL_MARKETS})
// Filter only visible marketsconst visibleMarkets = data.perp.borrowings.filter(m => m.visible)
console.log(`Available markets: ${visibleMarkets.length}`)visibleMarkets.forEach(m => { console.log(`${m.baseToken.symbol}-${m.collateralToken.symbol} (ID: ${m.marketId})`)})LP Queries
Section titled “LP Queries”Example 5: Get All Vaults with Metrics
Section titled “Example 5: Get All Vaults with Metrics”const GET_ALL_VAULTS = gql` query GetAllVaults { lp { vaults { address collateralToken { symbol name logoUrl decimals } tvl sharePrice apy availableAssets currentEpoch epochStart sharesDenom collateralERC20 sharesERC20 revenueInfo { RevenueCumulative NetProfit TraderLosses CurrentEpochPositiveOpenPnl Liabilities } } epochDurationDays } }`
// Usageconst { data } = await client.query({ query: GET_ALL_VAULTS})
data.lp.vaults.forEach(vault => { const decimals = vault.collateralToken.decimals const tvlFormatted = vault.tvl / (10 ** decimals)
console.log(`${vault.collateralToken.symbol} Vault`) console.log(`TVL: ${tvlFormatted.toLocaleString()} ${vault.collateralToken.symbol}`) console.log(`APY: ${vault.apy?.toFixed(2) || 'N/A'}%`) console.log(`Share Price: ${vault.sharePrice}`) console.log(`Epoch: ${vault.currentEpoch}`)})Example 6: Get User LP Positions
Section titled “Example 6: Get User LP Positions”const GET_USER_LP_POSITIONS = gql` query GetUserLPPositions($user: String!) { lp { deposits(where: { depositor: $user }) { depositor shares vault { address collateralToken { symbol decimals } sharePrice apy currentEpoch } } withdrawRequests(where: { depositor: $user }) { depositor shares status unlockEpoch autoRedeem vault { address collateralToken { symbol decimals } sharePrice currentEpoch } } } }`
// Usageconst { data } = await client.query({ query: GET_USER_LP_POSITIONS, variables: { user: 'nibi1abc...' }})
// Calculate total portfolio valuelet totalValue = 0
data.lp.deposits.forEach(deposit => { const decimals = deposit.vault.collateralToken.decimals const value = (deposit.shares * deposit.vault.sharePrice) / (10 ** decimals) totalValue += value
console.log(`${deposit.vault.collateralToken.symbol} Vault`) console.log(`Shares: ${deposit.shares}`) console.log(`Value: ${value.toFixed(2)}`) console.log(`APY: ${deposit.vault.apy?.toFixed(2) || 'N/A'}%`)})
// Check pending withdrawalsdata.lp.withdrawRequests.forEach(request => { const isReady = request.vault.currentEpoch >= request.unlockEpoch const epochsRemaining = Math.max(0, request.unlockEpoch - request.vault.currentEpoch)
console.log(`\nPending Withdrawal: ${request.vault.collateralToken.symbol}`) console.log(`Status: ${isReady ? 'Ready' : `${epochsRemaining} epochs remaining`}`) console.log(`Auto-redeem: ${request.autoRedeem}`)})
console.log(`\nTotal LP Value: ${totalValue.toFixed(2)}`)Example 7: Get Vault Deposit/Withdrawal History
Section titled “Example 7: Get Vault Deposit/Withdrawal History”const GET_VAULT_HISTORY = gql` query GetVaultHistory($vaultAddress: String!, $limit: Int!) { lp { depositHistory( where: { vault: $vaultAddress } limit: $limit order_by: sequence order_desc: true ) { id depositor amount shares isWithdraw block { block block_ts } vault { collateralToken { symbol decimals } } } } }`
// Usageconst { data } = await client.query({ query: GET_VAULT_HISTORY, variables: { vaultAddress: 'nibi1vault...', limit: 100 }})
data.lp.depositHistory.forEach(event => { const decimals = event.vault.collateralToken.decimals const amount = event.amount / (10 ** decimals) const action = event.isWithdraw ? 'Withdrew' : 'Deposited' const date = new Date(event.block.block_ts).toLocaleDateString()
console.log(`${date}: ${event.depositor} ${action} ${amount.toFixed(2)}`)})Oracle Queries
Section titled “Oracle Queries”Example 8: Get Current Token Prices
Section titled “Example 8: Get Current Token Prices”const GET_TOKEN_PRICES = gql` query GetTokenPrices { oracle { tokenPricesUsd(limit: 100) { token { id symbol name logoUrl } priceUsd lastUpdatedBlock { block block_ts } } } }`
// Usageconst { data } = await client.query({ query: GET_TOKEN_PRICES})
// Create price mapconst priceMap = {}data.oracle.tokenPricesUsd.forEach(item => { priceMap[item.token.symbol] = { price: item.priceUsd, lastUpdate: item.lastUpdatedBlock.block_ts }})
console.log(`BTC: $${priceMap['BTC'].price.toFixed(2)}`)console.log(`ETH: $${priceMap['ETH'].price.toFixed(2)}`)Example 9: Get Specific Token Price with Freshness Check
Section titled “Example 9: Get Specific Token Price with Freshness Check”const GET_TOKEN_PRICE = gql` query GetTokenPrice($tokenId: Int!) { oracle { tokenPricesUsd(where: { tokenId: $tokenId }) { token { symbol name } priceUsd lastUpdatedBlock { block block_ts } } } }`
// Usageconst { data } = await client.query({ query: GET_TOKEN_PRICE, variables: { tokenId: 1 }})
const priceData = data.oracle.tokenPricesUsd[0]const lastUpdate = new Date(priceData.lastUpdatedBlock.block_ts)const ageSeconds = (Date.now() - lastUpdate.getTime()) / 1000
console.log(`${priceData.token.symbol}: $${priceData.priceUsd}`)console.log(`Last updated: ${ageSeconds.toFixed(0)}s ago`)
if (ageSeconds > 60) { console.warn('⚠️ Price may be stale')}Fee Queries
Section titled “Fee Queries”Example 10: Get User Fee History
Section titled “Example 10: Get User Fee History”const GET_USER_FEES = gql` query GetUserFees($trader: String!, $limit: Int!) { fee { feeTransactions( filter: { traderAddress: $trader } limit: $limit ) { id feeType totalFeeCharged govFee vaultFee referrerAllocation triggerFee collateralDenom feeMultiplier blockTime tradeId } } }`
// Usageconst { data } = await client.query({ query: GET_USER_FEES, variables: { trader: 'nibi1abc...', limit: 50 }})
// Calculate total fees paidconst totalFees = data.fee.feeTransactions.reduce((sum, fee) => { return sum + fee.totalFeeCharged}, 0)
console.log(`Total fees paid: ${totalFees}`)console.log(`Number of transactions: ${data.fee.feeTransactions.length}`)Example 11: Get Protocol Fee Summary
Section titled “Example 11: Get Protocol Fee Summary”const GET_PROTOCOL_SUMMARY = gql` query GetProtocolSummary($fromDate: Time, $toDate: Time) { fee { protocolFeeSummary(fromDate: $fromDate, toDate: $toDate) { period { fromDate toDate } totalFees totalOpeningFees totalClosingFees totalGovFees totalVaultFees totalReferrerFees totalTriggerFees totalBadDebt openingCount closingCount uniqueTraders avgFeeMultiplier } } }`
// Usage - Get last 30 daysconst thirtyDaysAgo = new Date()thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
const { data } = await client.query({ query: GET_PROTOCOL_SUMMARY, variables: { fromDate: thirtyDaysAgo.toISOString(), toDate: new Date().toISOString() }})
const summary = data.fee.protocolFeeSummary
console.log('Protocol Summary (Last 30 Days)')console.log(`Total Fees: ${summary.totalFees}`)console.log(`Gov Fees: ${summary.totalGovFees}`)console.log(`Vault Fees: ${summary.totalVaultFees}`)console.log(`Unique Traders: ${summary.uniqueTraders}`)console.log(`Avg Fee Multiplier: ${summary.avgFeeMultiplier.toFixed(2)}x`)Example 12: Get Daily Fee Statistics
Section titled “Example 12: Get Daily Fee Statistics”const GET_DAILY_STATS = gql` query GetDailyStats($limit: Int!) { fee { feeDailyStats( filter: { protocolWide: true } limit: $limit ) { date collateralDenom openingFeeTotal closingFeeTotal openingFeeCount closingFeeCount totalBadDebt } } }`
// Usageconst { data } = await client.query({ query: GET_DAILY_STATS, variables: { limit: 30 }})
// Create time series for chartingconst chartData = data.fee.feeDailyStats.map(stat => ({ date: new Date(stat.date).toLocaleDateString(), totalFees: stat.openingFeeTotal + stat.closingFeeTotal, transactions: stat.openingFeeCount + stat.closingFeeCount}))
console.log('Daily Fee Data:', chartData)Advanced Patterns
Section titled “Advanced Patterns”Example 13: Combine Multiple Queries
Section titled “Example 13: Combine Multiple Queries”const GET_DASHBOARD_DATA = gql` query GetDashboardData($trader: String!) { perp { trades(where: { trader: $trader, isOpen: true }) { id isLong leverage state { pnlCollateral pnlPct } perpBorrowing { baseToken { symbol } } } } lp { deposits(where: { depositor: $trader }) { shares vault { sharePrice collateralToken { symbol decimals } } } } fee { traderFeeSummary(traderAddress: $trader) { totalFees openingCount closingCount } } }`
// Usage - Get all user data in one queryconst { data } = await client.query({ query: GET_DASHBOARD_DATA, variables: { trader: 'nibi1abc...' }})
console.log('Open Positions:', data.perp.trades.length)console.log('LP Deposits:', data.lp.deposits.length)console.log('Total Trades:', data.fee.traderFeeSummary.openingCount + data.fee.traderFeeSummary.closingCount)Example 14: Pagination Pattern
Section titled “Example 14: Pagination Pattern”// Fetch all trades with paginationasync function fetchAllTrades(trader) { const QUERY = gql` query GetTrades($trader: String!, $limit: Int!, $offset: Int!) { perp { trades( where: { trader: $trader } limit: $limit offset: $offset ) { id isOpen isLong } } } `
let allTrades = [] let offset = 0 const limit = 100 let hasMore = true
while (hasMore) { const { data } = await client.query({ query: QUERY, variables: { trader, limit, offset } })
const trades = data.perp.trades allTrades = allTrades.concat(trades)
hasMore = trades.length === limit offset += limit
console.log(`Fetched ${allTrades.length} trades...`) }
return allTrades}
// Usageconst allTrades = await fetchAllTrades('nibi1abc...')console.log(`Total trades: ${allTrades.length}`)Example 15: Error Handling Pattern
Section titled “Example 15: Error Handling Pattern”async function queryWithRetry(query, variables, maxRetries = 3) { let lastError
for (let i = 0; i < maxRetries; i++) { try { const { data, errors } = await client.query({ query, variables })
if (errors && errors.length > 0) { throw new Error(errors[0].message) }
return data } catch (error) { lastError = error console.warn(`Query attempt ${i + 1} failed:`, error.message)
if (i < maxRetries - 1) { // Exponential backoff await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))) } } }
throw new Error(`Query failed after ${maxRetries} attempts: ${lastError.message}`)}
// Usagetry { const data = await queryWithRetry(GET_OPEN_POSITIONS, { trader: 'nibi1abc...' }) console.log('Success:', data)} catch (error) { console.error('Failed:', error.message)}