Client Setup
Set up GraphQL clients in various languages to interact with Sai Keeper API.
Table of Contents
JavaScript/TypeScript
Apollo Client
Apollo Client is the most popular GraphQL client for JavaScript applications.
Installation
npm install @apollo/client graphql
# For subscriptions
npm install graphql-wsBasic Setup (Queries Only)
import { ApolloClient, InMemoryCache, HttpLink, gql } from '@apollo/client'
const client = new ApolloClient({
link: new HttpLink({
uri: 'https://sai-keeper.testnet-2.nibiru.fi/graphql'
}),
cache: new InMemoryCache()
})
// Example query
const GET_PRICES = gql`
query GetPrices {
oracle {
tokenPricesUsd(limit: 10) {
token { symbol }
priceUsd
}
}
}
`
async function fetchPrices() {
const { data, error } = await client.query({
query: GET_PRICES
})
if (error) {
console.error('Error:', error)
return
}
console.log('Prices:', data.oracle.tokenPricesUsd)
}
fetchPrices()Full Setup (With Subscriptions)
import {
ApolloClient,
InMemoryCache,
HttpLink,
split,
gql
} from '@apollo/client'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { getMainDefinition } from '@apollo/client/utilities'
import { createClient } from 'graphql-ws'
// HTTP link for queries and mutations
const httpLink = new HttpLink({
uri: 'https://sai-keeper.testnet-2.nibiru.fi/graphql'
})
// WebSocket link for subscriptions
const wsLink = new GraphQLWsLink(
createClient({
url: 'wss://sai-keeper.testnet-2.nibiru.fi/graphql',
connectionParams: {
// Add authentication if needed
// authToken: 'your-auth-token'
},
retryAttempts: 5,
shouldRetry: () => true,
on: {
connected: () => console.log('WebSocket connected'),
closed: () => console.log('WebSocket closed'),
error: (error) => console.error('WebSocket error:', error)
}
})
)
// Split traffic between HTTP and WebSocket
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
httpLink
)
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network'
}
}
})
// Example subscription
const WATCH_PRICES = gql`
subscription WatchPrices {
tokenPricesUsd {
token { symbol }
priceUsd
}
}
`
const subscription = client.subscribe({
query: WATCH_PRICES
}).subscribe({
next: ({ data }) => {
console.log('Price update:', data.tokenPricesUsd)
},
error: (error) => {
console.error('Subscription error:', error)
}
})
// Clean up
// subscription.unsubscribe()
export default clientReact Integration
import { ApolloProvider, useQuery, useSubscription, gql } from '@apollo/client'
import client from './apollo-client'
// Wrap your app
function App() {
return (
<ApolloProvider client={client}>
<Dashboard />
</ApolloProvider>
)
}
// Use queries in components
function Dashboard() {
const GET_TRADES = gql`
query GetTrades($trader: String!) {
perp {
trades(where: { trader: $trader, isOpen: true }) {
id
isLong
leverage
state { pnlPct }
}
}
}
`
const { loading, error, data } = useQuery(GET_TRADES, {
variables: { trader: 'nibi1abc...' }
})
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
{data.perp.trades.map(trade => (
<div key={trade.id}>
Trade #{trade.id} - PnL: {(trade.state.pnlPct * 100).toFixed(2)}%
</div>
))}
</div>
)
}
// Use subscriptions in components
function PriceTicker() {
const WATCH_PRICES = gql`
subscription {
tokenPricesUsd {
token { symbol }
priceUsd
}
}
`
const { data, loading } = useSubscription(WATCH_PRICES)
if (loading) return <div>Connecting...</div>
return (
<div>
{data.tokenPricesUsd.map(item => (
<div key={item.token.symbol}>
{item.token.symbol}: ${item.priceUsd.toFixed(2)}
</div>
))}
</div>
)
}urql
urql is a lightweight alternative to Apollo Client.
Installation
npm install urql graphql
# For subscriptions
npm install graphql-wsSetup
import { createClient, fetchExchange, subscriptionExchange } from 'urql'
import { createClient as createWSClient } from 'graphql-ws'
const wsClient = createWSClient({
url: 'wss://sai-keeper.testnet-2.nibiru.fi/graphql'
})
const client = createClient({
url: 'https://sai-keeper.testnet-2.nibiru.fi/graphql',
exchanges: [
fetchExchange,
subscriptionExchange({
forwardSubscription: (operation) => ({
subscribe: (sink) => ({
unsubscribe: wsClient.subscribe(operation, sink)
})
})
})
]
})
export default clientReact Integration
import { Provider, useQuery, useSubscription } from 'urql'
import client from './urql-client'
function App() {
return (
<Provider value={client}>
<Dashboard />
</Provider>
)
}
function Dashboard() {
const QUERY = `
query GetTrades($trader: String!) {
perp {
trades(where: { trader: $trader, isOpen: true }) {
id
isLong
leverage
}
}
}
`
const [result] = useQuery({
query: QUERY,
variables: { trader: 'nibi1abc...' }
})
const { data, fetching, error } = result
if (fetching) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
{data.perp.trades.map(trade => (
<div key={trade.id}>Trade #{trade.id}</div>
))}
</div>
)
}GraphQL Request
Lightweight library for simple queries (no caching or subscriptions).
Installation
npm install graphql-request graphqlUsage
import { GraphQLClient, gql } from 'graphql-request'
const client = new GraphQLClient(
'https://sai-keeper.testnet-2.nibiru.fi/graphql'
)
const GET_TRADES = gql`
query GetTrades($trader: String!) {
perp {
trades(where: { trader: $trader, isOpen: true }) {
id
isLong
leverage
}
}
}
`
async function fetchTrades(trader: string) {
try {
const data = await client.request(GET_TRADES, { trader })
console.log('Trades:', data.perp.trades)
return data.perp.trades
} catch (error) {
console.error('Error fetching trades:', error)
throw error
}
}
fetchTrades('nibi1abc...')Python
gql Library
Full-featured GraphQL client for Python.
Installation
pip install gql[all]Setup
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.websockets import WebsocketsTransport
import asyncio
# HTTP Transport for queries
http_transport = AIOHTTPTransport(
url="https://sai-keeper.testnet-2.nibiru.fi/graphql"
)
# Create client
client = Client(
transport=http_transport,
fetch_schema_from_transport=True
)
# Example query
async def get_trades(trader_address):
query = gql("""
query GetTrades($trader: String!) {
perp {
trades(where: { trader: $trader, isOpen: true }) {
id
isLong
leverage
state {
pnlPct
liquidationPrice
}
perpBorrowing {
baseToken {
symbol
}
}
}
}
}
""")
params = {"trader": trader_address}
async with Client(transport=http_transport) as session:
result = await session.execute(query, variable_values=params)
return result['perp']['trades']
# Run the query
trades = asyncio.run(get_trades("nibi1abc..."))
for trade in trades:
pnl_pct = trade['state']['pnlPct'] * 100
print(f"Trade #{trade['id']}: {trade['perpBorrowing']['baseToken']['symbol']} {trade['leverage']}x - PnL: {pnl_pct:.2f}%")With Subscriptions
from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport
import asyncio
# WebSocket transport for subscriptions
ws_transport = WebsocketsTransport(
url="wss://sai-keeper.testnet-2.nibiru.fi/graphql"
)
async def watch_prices():
subscription = gql("""
subscription WatchPrices {
tokenPricesUsd {
token {
symbol
}
priceUsd
}
}
""")
async with Client(transport=ws_transport) as session:
async for result in session.subscribe(subscription):
prices = result['tokenPricesUsd']
for item in prices:
print(f"{item['token']['symbol']}: ${item['priceUsd']:.2f}")
# Run subscription
asyncio.run(watch_prices())Synchronous Version
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
# Synchronous transport
transport = RequestsHTTPTransport(
url="https://sai-keeper.testnet-2.nibiru.fi/graphql",
verify=True,
retries=3
)
client = Client(transport=transport, fetch_schema_from_transport=True)
# Synchronous query
def get_token_prices():
query = gql("""
query GetPrices {
oracle {
tokenPricesUsd(limit: 10) {
token {
symbol
}
priceUsd
}
}
}
""")
result = client.execute(query)
return result['oracle']['tokenPricesUsd']
prices = get_token_prices()
for item in prices:
print(f"{item['token']['symbol']}: ${item['priceUsd']:.2f}")Rust
graphql_client
Type-safe GraphQL client for Rust.
Installation
Add to Cargo.toml:
[dependencies]
graphql_client = "0.13"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"Setup
use graphql_client::{GraphQLQuery, Response};
use reqwest;
// Define the query
#[derive(GraphQLQuery)]
#[graphql(
schema_path = "schema.graphql",
query_path = "queries/get_trades.graphql",
response_derives = "Debug"
)]
pub struct GetTrades;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let variables = get_trades::Variables {
trader: "nibi1abc...".to_string(),
};
let request_body = GetTrades::build_query(variables);
let res = client
.post("https://sai-keeper.testnet-2.nibiru.fi/graphql")
.json(&request_body)
.send()
.await?;
let response_body: Response<get_trades::ResponseData> = res.json().await?;
if let Some(data) = response_body.data {
for trade in data.perp.trades {
println!("Trade #{}: {}x leverage", trade.id, trade.leverage);
}
}
if let Some(errors) = response_body.errors {
for error in errors {
eprintln!("Error: {}", error.message);
}
}
Ok(())
}Without Code Generation
use reqwest;
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Serialize, Deserialize)]
struct GraphQLRequest {
query: String,
variables: serde_json::Value,
}
#[derive(Debug, Deserialize)]
struct GraphQLResponse<T> {
data: Option<T>,
errors: Option<Vec<GraphQLError>>,
}
#[derive(Debug, Deserialize)]
struct GraphQLError {
message: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let query = r#"
query GetTrades($trader: String!) {
perp {
trades(where: { trader: $trader, isOpen: true }) {
id
isLong
leverage
}
}
}
"#;
let variables = json!({
"trader": "nibi1abc..."
});
let request = GraphQLRequest {
query: query.to_string(),
variables,
};
let response = client
.post("https://sai-keeper.testnet-2.nibiru.fi/graphql")
.json(&request)
.send()
.await?
.json::<serde_json::Value>()
.await?;
println!("Response: {:#?}", response);
Ok(())
}Go
graphql Package
Installation
go get github.com/machinebox/graphqlSetup
package main
import (
"context"
"fmt"
"log"
"github.com/machinebox/graphql"
)
type Trade struct {
ID int `json:"id"`
IsLong bool `json:"isLong"`
Leverage float64 `json:"leverage"`
}
type PerpResponse struct {
Trades []Trade `json:"trades"`
}
type Response struct {
Perp PerpResponse `json:"perp"`
}
func main() {
// Create client
client := graphql.NewClient("https://sai-keeper.testnet-2.nibiru.fi/graphql")
// Define query
req := graphql.NewRequest(`
query GetTrades($trader: String!) {
perp {
trades(where: { trader: $trader, isOpen: true }) {
id
isLong
leverage
}
}
}
`)
// Set variables
req.Var("trader", "nibi1abc...")
// Execute query
ctx := context.Background()
var response Response
if err := client.Run(ctx, req, &response); err != nil {
log.Fatal(err)
}
// Process response
for _, trade := range response.Perp.Trades {
fmt.Printf("Trade #%d: %fx leverage\n", trade.ID, trade.Leverage)
}
}With Error Handling
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/machinebox/graphql"
)
func fetchTrades(trader string) error {
client := graphql.NewClient("https://sai-keeper.testnet-2.nibiru.fi/graphql")
req := graphql.NewRequest(`
query GetTrades($trader: String!) {
perp {
trades(where: { trader: $trader, isOpen: true }) {
id
isLong
leverage
}
}
}
`)
req.Var("trader", trader)
// Add timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var response map[string]interface{}
if err := client.Run(ctx, req, &response); err != nil {
return fmt.Errorf("query failed: %w", err)
}
// Check for data
perp, ok := response["perp"].(map[string]interface{})
if !ok {
return fmt.Errorf("invalid response structure")
}
trades, ok := perp["trades"].([]interface{})
if !ok {
return fmt.Errorf("invalid trades structure")
}
fmt.Printf("Found %d open trades\n", len(trades))
return nil
}
func main() {
if err := fetchTrades("nibi1abc..."); err != nil {
log.Fatal(err)
}
}Environment Variables
For all languages, consider using environment variables for configuration:
# .env file
SAI_KEEPER_HTTP_URL=https://sai-keeper.testnet-2.nibiru.fi/graphql
SAI_KEEPER_WS_URL=wss://sai-keeper.testnet-2.nibiru.fi/graphqlJavaScript:
const httpUrl = process.env.SAI_KEEPER_HTTP_URL
const wsUrl = process.env.SAI_KEEPER_WS_URLPython:
import os
http_url = os.getenv('SAI_KEEPER_HTTP_URL')
ws_url = os.getenv('SAI_KEEPER_WS_URL')Rust:
use std::env;
let http_url = env::var("SAI_KEEPER_HTTP_URL").unwrap();Go:
import "os"
httpUrl := os.Getenv("SAI_KEEPER_HTTP_URL")Next: Filters and Pagination | Previous | Back to Introduction
Last updated