Client Setup Guides
Set up GraphQL clients in various languages to interact with Sai Keeper API.
Table of Contents
Section titled “Table of Contents”JavaScript/TypeScript
Section titled “JavaScript/TypeScript”Apollo Client
Section titled “Apollo Client”Apollo Client is the most popular GraphQL client for JavaScript applications.
Installation
Section titled “Installation”npm install @apollo/client graphql# For subscriptionsnpm install graphql-wsBasic Setup (Queries Only)
Section titled “Basic 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 queryconst 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)
Section titled “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 mutationsconst httpLink = new HttpLink({ uri: 'https://sai-keeper.testnet-2.nibiru.fi/graphql'})
// WebSocket link for subscriptionsconst 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 WebSocketconst 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 subscriptionconst 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
Section titled “React Integration”import { ApolloProvider, useQuery, useSubscription, gql } from '@apollo/client'import client from './apollo-client'
// Wrap your appfunction App() { return ( <ApolloProvider client={client}> <Dashboard /> </ApolloProvider> )}
// Use queries in componentsfunction 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 componentsfunction 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 is a lightweight alternative to Apollo Client.
Installation
Section titled “Installation”npm install urql graphql# For subscriptionsnpm install graphql-wsimport { 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
Section titled “React 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
Section titled “GraphQL Request”Lightweight library for simple queries (no caching or subscriptions).
Installation
Section titled “Installation”npm install graphql-request graphqlimport { 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
Section titled “Python”gql Library
Section titled “gql Library”Full-featured GraphQL client for Python.
Installation
Section titled “Installation”pip install gql[all]from gql import gql, Clientfrom gql.transport.aiohttp import AIOHTTPTransportfrom gql.transport.websockets import WebsocketsTransportimport asyncio
# HTTP Transport for querieshttp_transport = AIOHTTPTransport( url="https://sai-keeper.testnet-2.nibiru.fi/graphql")
# Create clientclient = Client( transport=http_transport, fetch_schema_from_transport=True)
# Example queryasync 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 querytrades = 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
Section titled “With Subscriptions”from gql import gql, Clientfrom gql.transport.websockets import WebsocketsTransportimport asyncio
# WebSocket transport for subscriptionsws_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 subscriptionasyncio.run(watch_prices())Synchronous Version
Section titled “Synchronous Version”from gql import gql, Clientfrom gql.transport.requests import RequestsHTTPTransport
# Synchronous transporttransport = RequestsHTTPTransport( url="https://sai-keeper.testnet-2.nibiru.fi/graphql", verify=True, retries=3)
client = Client(transport=transport, fetch_schema_from_transport=True)
# Synchronous querydef 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}")graphql_client
Section titled “graphql_client”Type-safe GraphQL client for Rust.
Installation
Section titled “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"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
Section titled “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(())}graphql Package
Section titled “graphql Package”Installation
Section titled “Installation”go get github.com/machinebox/graphqlpackage 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
Section titled “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
Section titled “Environment Variables”For all languages, consider using environment variables for configuration:
# .env fileSAI_KEEPER_HTTP_URL=https://sai-keeper.testnet-2.nibiru.fi/graphqlSAI_KEEPER_WS_URL=wss://sai-keeper.testnet-2.nibiru.fi/graphqlJavaScript:
const httpUrl = process.env.SAI_KEEPER_HTTP_URLconst wsUrl = process.env.SAI_KEEPER_WS_URLPython:
import oshttp_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")