# Oracle API

Query and subscribe to token price feeds and metadata.

## Table of Contents

* [Queries](#queries)
  * [token](#token)
  * [tokens](#tokens)
  * [tokenPricesUsd](#tokenpricesusd)
* [Subscriptions](#subscriptions)
* [Types Reference](#types-reference)

## Queries

### token

Get details for a specific token by ID.

**Signature:**

```graphql
token(id: Int!): Token!
```

**Parameters:**

* `id`: Token ID (integer)

**Returns:** `Token` object

**Example:**

```graphql
query GetToken {
  oracle {
    token(id: 1) {
      id
      symbol
      name
      logoUrl
      tradingViewSymbol
      permissionGroup
    }
  }
}
```

**Use Cases:**

* Get token metadata
* Display token information
* Resolve token ID to symbol

***

### tokens

List all available tokens with filtering and ordering.

**Signature:**

```graphql
tokens(
  where: TokensFilter
  limit: Int
  order_by: TokensOrder
  order_desc: Boolean
): [Token!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `name`: Filter by token name (string match)
  * `permissionGroup`: Filter by permission group
* `limit`: Max results
* `order_by`: Sort by `id`, `name`, or `permission_group`
* `order_desc`: Sort descending

**Returns:** Array of `Token` objects

**Example - List All Tokens:**

```graphql
query ListAllTokens {
  oracle {
    tokens(order_by: name) {
      id
      symbol
      name
      logoUrl
      tradingViewSymbol
    }
  }
}
```

**Example - Search by Name:**

```graphql
query SearchToken($searchTerm: String!) {
  oracle {
    tokens(where: { name: $searchTerm }) {
      id
      symbol
      name
    }
  }
}
```

**Example - Filter by Permission Group:**

```graphql
query GetPermittedTokens {
  oracle {
    tokens(where: { permissionGroup: 1 }) {
      id
      symbol
      name
    }
  }
}
```

**Use Cases:**

* Build token selector dropdown
* Search tokens by name
* List tradeable assets
* Filter by permissions

***

### tokenPricesUsd query

Get current USD prices for tokens.

**Signature:**

```graphql
tokenPricesUsd(
  where: TokenPriceUsdFilter
  limit: Int
  order_by: TokenPriceUsdOrder
  order_desc: Boolean
): [TokenPricesUsd!]!
```

**Parameters:**

* `where`: Filter object (optional)
  * `tokenId`: Filter by specific token ID
* `limit`: Max results
* `order_by`: Sort by `oracle_token_id`
* `order_desc`: Sort descending

**Returns:** Array of `TokenPricesUsd` objects

**Example - Get All Prices:**

```graphql
query GetAllPrices {
  oracle {
    tokenPricesUsd {
      token {
        id
        symbol
        name
      }
      priceUsd
      lastUpdatedBlock {
        block
        block_ts
      }
    }
  }
}
```

**Example - Get Specific Token Price:**

```graphql
query GetBTCPrice {
  oracle {
    tokenPricesUsd(where: { tokenId: 1 }) {
      token {
        symbol
      }
      priceUsd
      lastUpdatedBlock {
        block
        block_ts
      }
    }
  }
}
```

**Example - Get Multiple Prices:**

```graphql
query GetPrices {
  btc: oracle {
    tokenPricesUsd(where: { tokenId: 1 }) {
      priceUsd
      lastUpdatedBlock { block_ts }
    }
  }
  eth: oracle {
    tokenPricesUsd(where: { tokenId: 2 }) {
      priceUsd
      lastUpdatedBlock { block_ts }
    }
  }
}
```

**Key Fields:**

* `priceUsd`: Current price in USD (float)
* `lastUpdatedBlock`: Block info for last price update

**Use Cases:**

* Display token prices
* Calculate position values
* Monitor price changes
* Verify price freshness

***

## Subscriptions

Subscribe to real-time price updates.

### tokenPricesUsd

Subscribe to price updates for specific or all tokens.

**Signature:**

```graphql
subscription {
  tokenPricesUsd(where: SubTokenPriceUsdFilter): [TokenPricesUsd!]!
}
```

**Example - Subscribe to All Prices:**

```graphql
subscription WatchAllPrices {
  tokenPricesUsd {
    token {
      id
      symbol
    }
    priceUsd
    lastUpdatedBlock {
      block
      block_ts
    }
  }
}
```

**Example - Subscribe to Specific Token:**

```graphql
subscription WatchBTCPrice {
  tokenPricesUsd(where: { tokenId: 1 }) {
    token {
      symbol
    }
    priceUsd
    lastUpdatedBlock {
      block_ts
    }
  }
}
```

**Use Cases:**

* Real-time price tickers
* Live trading dashboards
* Price alert systems
* Chart updates

***

### userBalances

Subscribe to user balance updates across all tokens.

**Signature:**

```graphql
subscription {
  userBalances(where: SubUserBalancesFilter!): [Balance!]!
}
```

**Example:**

```graphql
subscription WatchUserBalances($user: String!) {
  userBalances(where: { user: $user }) {
    amount
    token_info {
      symbol
      decimals
      bank_denom
      erc20_contract_address
      logo
      name
      type
    }
  }
}
```

**Use Cases:**

* Track wallet balances
* Monitor account changes
* Real-time portfolio updates
* Display available funds

***

## Types Reference

### Token

```graphql
type Token {
  id: Int!
  symbol: String!
  name: String!
  logoUrl: String
  tradingViewSymbol: String
  permissionGroup: Int!
}
```

**Field Details:**

* `id`: Unique token identifier
* `symbol`: Token ticker (e.g., "BTC", "ETH")
* `name`: Full token name (e.g., "Bitcoin", "Ethereum")
* `logoUrl`: URL to token logo image
* `tradingViewSymbol`: Symbol for TradingView charts
* `permissionGroup`: Access/trading permission level

***

### TokenPricesUsd

```graphql
type TokenPricesUsd {
  token: Token
  priceUsd: Float!
  lastUpdatedBlock: Block!
}
```

**Field Details:**

* `token`: Associated token metadata
* `priceUsd`: Current USD price
* `lastUpdatedBlock`: Last price update info
  * `block`: Block height
  * `block_ts`: Timestamp (RFC3339 format)

***

### Balance

```graphql
type Balance {
  amount: String!
  token_info: TokenInfo!
}
```

***

### TokenInfo

Detailed token information for balances.

```graphql
type TokenInfo {
  bank_denom: String!
  symbol: String!
  name: String!
  decimals: Int!
  logo: String
  type: TokenType!
  erc20_contract_address: String!
}
```

**TokenType Enum:**

* `bank`: Native Cosmos SDK token
* `erc20`: ERC20 token on EVM

***

## Working with Prices

### Price Freshness

Always check when price was last updated:

```javascript
const now = Date.now()
const lastUpdate = new Date(lastUpdatedBlock.block_ts).getTime()
const ageSeconds = (now - lastUpdate) / 1000

if (ageSeconds > 60) {
  console.warn('Price may be stale')
}
```

### Price Formatting

```javascript
// Format price with appropriate decimals
const formatPrice = (priceUsd) => {
  if (priceUsd >= 1000) {
    return priceUsd.toFixed(2)
  } else if (priceUsd >= 1) {
    return priceUsd.toFixed(4)
  } else {
    return priceUsd.toFixed(6)
  }
}
```

### Calculate Position Value

```javascript
// Calculate position value in USD
const positionValueUsd = (collateralAmount, priceUsd, decimals) => {
  const actualAmount = collateralAmount / (10 ** decimals)
  return actualAmount * priceUsd
}
```

### Balance Conversion

```javascript
// Convert balance amount string to number
const getBalanceValue = (balance) => {
  const amount = parseFloat(balance.amount)
  return amount / (10 ** balance.token_info.decimals)
}
```

***

## Best Practices

### Caching Prices

```javascript
// Cache prices for short periods to reduce queries
const priceCache = new Map()
const CACHE_TTL = 5000 // 5 seconds

const getCachedPrice = async (tokenId) => {
  const cached = priceCache.get(tokenId)
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.price
  }
  
  const price = await fetchPrice(tokenId)
  priceCache.set(tokenId, { price, timestamp: Date.now() })
  return price
}
```

### Handling Multiple Tokens

```javascript
// Batch query multiple prices efficiently
const query = `
  query GetMultiplePrices($tokenIds: [Int!]!) {
    oracle {
      prices: tokenPricesUsd(limit: 100) {
        token { id symbol }
        priceUsd
      }
    }
  }
`

// Filter client-side for needed tokens
const relevantPrices = data.oracle.prices.filter(p => 
  tokenIds.includes(p.token.id)
)
```

### Subscription Management

```javascript
// Manage price subscription lifecycle
let priceSubscription

const subscribeToPrices = (tokenIds) => {
  // Unsubscribe from previous
  if (priceSubscription) {
    priceSubscription.unsubscribe()
  }
  
  // Subscribe to new set
  priceSubscription = client.subscribe({
    query: PRICE_SUBSCRIPTION,
    variables: { tokenIds }
  }).subscribe({
    next: (data) => updatePrices(data),
    error: (err) => console.error(err)
  })
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sai.fun/for-devs/sai-keeper/api-oracle.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
