Examples: Queries
Practical examples for common query patterns using the Sai Keeper API.
Table of Contents
Setup
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
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
}
}
}
}
`
// Usage
const { data } = await client.query({
query: GET_OPEN_POSITIONS,
variables: { trader: 'nibi1abc...' }
})
// Process results
data.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
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
}
}
}
}
}
}
`
// Usage
const { data } = await client.query({
query: GET_TRADE_HISTORY,
variables: { trader: 'nibi1abc...', limit: 50 }
})
// Calculate total realized PnL
const 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
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
}
}
}
`
// Usage
const { data } = await client.query({
query: GET_MARKET_INFO,
variables: { collateralId: 1, marketId: 1 }
})
const market = data.perp.borrowing
// Calculate funding rate APR
const hourlyRateLong = market.feesPerHourLong
const 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
const GET_ALL_MARKETS = gql`
query GetAllMarkets {
perp {
borrowings(order_by: base_token_name) {
marketId
baseToken {
symbol
name
logoUrl
tradingViewSymbol
}
collateralToken {
symbol
}
visible
}
}
}
`
// Usage
const { data } = await client.query({
query: GET_ALL_MARKETS
})
// Filter only visible markets
const 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
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
}
}
`
// Usage
const { 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
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
}
}
}
}
`
// Usage
const { data } = await client.query({
query: GET_USER_LP_POSITIONS,
variables: { user: 'nibi1abc...' }
})
// Calculate total portfolio value
let 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 withdrawals
data.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
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
}
}
}
}
}
`
// Usage
const { 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
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
}
}
}
}
`
// Usage
const { data } = await client.query({
query: GET_TOKEN_PRICES
})
// Create price map
const 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
const GET_TOKEN_PRICE = gql`
query GetTokenPrice($tokenId: Int!) {
oracle {
tokenPricesUsd(where: { tokenId: $tokenId }) {
token {
symbol
name
}
priceUsd
lastUpdatedBlock {
block
block_ts
}
}
}
}
`
// Usage
const { 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
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
}
}
}
`
// Usage
const { data } = await client.query({
query: GET_USER_FEES,
variables: { trader: 'nibi1abc...', limit: 50 }
})
// Calculate total fees paid
const 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
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 days
const 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
const GET_DAILY_STATS = gql`
query GetDailyStats($limit: Int!) {
fee {
feeDailyStats(
filter: { protocolWide: true }
limit: $limit
) {
date
collateralDenom
openingFeeTotal
closingFeeTotal
openingFeeCount
closingFeeCount
totalBadDebt
}
}
}
`
// Usage
const { data } = await client.query({
query: GET_DAILY_STATS,
variables: { limit: 30 }
})
// Create time series for charting
const 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
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 query
const { 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
// Fetch all trades with pagination
async 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
}
// Usage
const allTrades = await fetchAllTrades('nibi1abc...')
console.log(`Total trades: ${allTrades.length}`)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}`)
}
// Usage
try {
const data = await queryWithRetry(GET_OPEN_POSITIONS, { trader: 'nibi1abc...' })
console.log('Success:', data)
} catch (error) {
console.error('Failed:', error.message)
}Next: Subscription Examples | Previous | Back to Introduction
Last updated