Pricing API
The Pricing API provides fiat price conversion for assets.
JSON-RPC namespace: pricing
Endpoints
Get Asset Fiat Price
Get the current fiat price of an asset on a specific network.
Method: GetAssetFiatPrice
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network where asset exists |
asset_id | string | YES | Asset identifier |
fiat_currency | FiatCurrency | YES | Target fiat currency |
FiatCurrency Enum:
| Value | Description |
|---|---|
USD | US Dollar |
EUR | Euro |
GBP | British Pound |
JPY | Japanese Yen |
CNY | Chinese Yuan |
AUD | Australian Dollar |
CAD | Canadian Dollar |
Response:
| Field | Type | Description |
|---|---|---|
price | DecimalString | Price in specified fiat currency |
Example Request:
Rust
use hydra_client::pricing::{PricingServiceClient, GetAssetFiatPriceRequest, FiatCurrency};
use hydra_client::common::Network;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = PricingServiceClient::connect("http://localhost:5001").await?;
let request = GetAssetFiatPriceRequest {
network: Some(Network {
protocol: Protocol::Bitcoin as i32,
id: "0a03cf40".to_string(),
}),
asset_id: "0x0000000000000000000000000000000000000000000000000000000000000000".to_string(),
fiat_currency: FiatCurrency::Usd as i32,
};
let response = client.get_asset_fiat_price(request).await?;
let price = response.into_inner().price;
println!("BTC Price: ${}", price);
Ok(())
}
Go
package main
import (
"context"
"fmt"
"log"
pb "github.com/hydra/api/proto"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:5001", grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewPricingServiceClient(conn)
request := &pb.GetAssetFiatPriceRequest{
Network: &pb.Network{
Protocol: pb.Protocol_PROTOCOL_BITCOIN,
Id: "0a03cf40",
},
AssetId: "0x0000000000000000000000000000000000000000000000000000000000000000",
FiatCurrency: pb.FiatCurrency_USD,
}
response, err := client.GetAssetFiatPrice(context.Background(), request)
if err != nil {
log.Fatalf("Failed to get price: %v", err)
}
fmt.Printf("BTC Price: $%s\n", response.Price)
}
TypeScript
import { PricingServiceClient } from './proto/PricingServiceClientPb'
import { GetAssetFiatPriceRequest, FiatCurrency } from './proto/pricing_pb'
const client = new PricingServiceClient('http://localhost:5001')
const request = new GetAssetFiatPriceRequest()
request.setNetwork({ protocol: 1, id: '0a03cf40' })
request.setAssetId('0x0000000000000000000000000000000000000000000000000000000000000000')
request.setFiatCurrency(FiatCurrency.USD)
const response = await client.getAssetFiatPrice(request, {})
const price = response.getPrice()
console.log(`BTC Price: $${price}`)
Example Response:
{
"price": "98450.75"
}
Get Asset Fiat Price By Symbol
Returns the fiat price of an asset by its ticker symbol. Does not require specifying a network — useful for general price lookups (e.g. BTC, ETH, USDC).
Method: GetAssetFiatPriceBySymbol
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
asset_symbol | string | YES | Asset ticker symbol (e.g. BTC, ETH, USDC) |
fiat_currency | FiatCurrency | YES | Target fiat currency for the price |
Response:
| Field | Type | Description |
|---|---|---|
price | DecimalString (optional) | Fiat price, or empty if not available |
Example Request:
import { GetAssetFiatPriceBySymbolRequest } from './proto/pricing_pb'
const request = new GetAssetFiatPriceBySymbolRequest()
request.setAssetSymbol('BTC')
request.setFiatCurrency(0) // USD
const response = await client.getAssetFiatPriceBySymbol(request, {})
const price = response.getPrice()
if (price) {
console.log('BTC price (USD):', price.getValue())
} else {
console.log('Price not available')
}
Example Response:
{ "price": "67234.50" }
Tip: prefer
GetAssetFiatPrice(network + asset_id) when you already know which network the asset lives on — it's unambiguous. UseGetAssetFiatPriceBySymbolfor generic price displays where the user just wants to know "what's BTC worth?".
Common Use Cases
Get multiple asset prices
Rust
use hydra_client::pricing::{PricingServiceClient, GetAssetFiatPriceRequest, FiatCurrency};
use hydra_client::common::Network;
use std::collections::HashMap;
async fn get_asset_prices(
client: &mut PricingServiceClient<tonic::transport::Channel>,
network: Network,
asset_ids: Vec<String>,
fiat_currency: FiatCurrency,
) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let mut prices = HashMap::new();
for asset_id in asset_ids {
let request = GetAssetFiatPriceRequest {
network: Some(network.clone()),
asset_id: asset_id.clone(),
fiat_currency: fiat_currency as i32,
};
let response = client.get_asset_fiat_price(request).await?;
prices.insert(asset_id, response.into_inner().price);
}
Ok(prices)
}
// Usage
let prices = get_asset_prices(
&mut client,
bitcoin_network,
vec!["0x0000000000000000000000000000000000000000000000000000000000000000".to_string(), "RGB:asset123".to_string()],
FiatCurrency::Usd,
).await?;
println!("BTC: {}", prices.get("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap());
println!("RGB Asset: {}", prices.get("RGB:asset123").unwrap());
Go
package main
import (
"context"
"fmt"
pb "github.com/hydra/api/proto"
)
func getAssetPrices(
client pb.PricingServiceClient,
network *pb.Network,
assetIds []string,
fiatCurrency pb.FiatCurrency,
) (map[string]string, error) {
prices := make(map[string]string)
for _, assetId := range assetIds {
request := &pb.GetAssetFiatPriceRequest{
Network: network,
AssetId: assetId,
FiatCurrency: fiatCurrency,
}
response, err := client.GetAssetFiatPrice(context.Background(), request)
if err != nil {
return nil, err
}
prices[assetId] = response.Price
}
return prices, nil
}
// Usage
prices, err := getAssetPrices(
client,
bitcoinNetwork,
[]string{"0x0000000000000000000000000000000000000000000000000000000000000000", "RGB:asset123"},
pb.FiatCurrency_USD,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("BTC: %s\n", prices["0x0000000000000000000000000000000000000000000000000000000000000000"])
fmt.Printf("RGB Asset: %s\n", prices["RGB:asset123"])
TypeScript
async function getAssetPrices(
client: PricingServiceClient,
network: Network,
assetIds: string[],
fiatCurrency: FiatCurrency
): Promise<Map<string, string>> {
const prices = new Map<string, string>()
for (const assetId of assetIds) {
const request = new GetAssetFiatPriceRequest()
request.setNetwork(network)
request.setAssetId(assetId)
request.setFiatCurrency(fiatCurrency)
const response = await client.getAssetFiatPrice(request, {})
prices.set(assetId, response.getPrice())
}
return prices
}
// Usage
const prices = await getAssetPrices(
client,
bitcoinNetwork,
['0x0000000000000000000000000000000000000000000000000000000000000000', 'RGB:asset123'],
FiatCurrency.USD
)
console.log('BTC:', prices.get('0x0000000000000000000000000000000000000000000000000000000000000000'))
console.log('RGB Asset:', prices.get('RGB:asset123'))
Convert crypto amount to fiat
Rust
use hydra_client::pricing::{PricingServiceClient, GetAssetFiatPriceRequest, FiatCurrency};
use hydra_client::common::Network;
async fn convert_to_fiat(
client: &mut PricingServiceClient<tonic::transport::Channel>,
network: Network,
asset_id: String,
amount: String,
fiat_currency: FiatCurrency,
) -> Result<String, Box<dyn std::error::Error>> {
let request = GetAssetFiatPriceRequest {
network: Some(network),
asset_id,
fiat_currency: fiat_currency as i32,
};
let response = client.get_asset_fiat_price(request).await?;
let price: f64 = response.into_inner().price.parse()?;
let asset_amount: f64 = amount.parse()?;
// Assuming amount is in base units (e.g., satoshis for BTC)
// Convert to main unit (BTC = 8 decimals)
let btc_amount = asset_amount / 100_000_000.0;
let fiat_value = btc_amount * price;
Ok(format!("{:.2}", fiat_value))
}
// Usage
let fiat_value = convert_to_fiat(
&mut client,
bitcoin_network,
"0x0000000000000000000000000000000000000000000000000000000000000000".to_string(),
"100000000".to_string(), // 1 BTC in satoshis
FiatCurrency::Usd,
).await?;
println!("Value: ${}", fiat_value);
Go
package main
import (
"context"
"fmt"
"strconv"
pb "github.com/hydra/api/proto"
)
func convertToFiat(
client pb.PricingServiceClient,
network *pb.Network,
assetId string,
amount string,
fiatCurrency pb.FiatCurrency,
) (string, error) {
request := &pb.GetAssetFiatPriceRequest{
Network: network,
AssetId: assetId,
FiatCurrency: fiatCurrency,
}
response, err := client.GetAssetFiatPrice(context.Background(), request)
if err != nil {
return "", err
}
price, err := strconv.ParseFloat(response.Price, 64)
if err != nil {
return "", err
}
assetAmount, err := strconv.ParseFloat(amount, 64)
if err != nil {
return "", err
}
// Assuming amount is in base units (e.g., satoshis for BTC)
// Convert to main unit (BTC = 8 decimals)
btcAmount := assetAmount / 100000000
fiatValue := btcAmount * price
return fmt.Sprintf("%.2f", fiatValue), nil
}
// Usage
fiatValue, err := convertToFiat(
client,
bitcoinNetwork,
"0x0000000000000000000000000000000000000000000000000000000000000000",
"100000000", // 1 BTC in satoshis
pb.FiatCurrency_USD,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Value: $%s\n", fiatValue)
TypeScript
async function convertToFiat(
client: PricingServiceClient,
network: Network,
assetId: string,
amount: string,
fiatCurrency: FiatCurrency
): Promise<string> {
const request = new GetAssetFiatPriceRequest()
request.setNetwork(network)
request.setAssetId(assetId)
request.setFiatCurrency(fiatCurrency)
const response = await client.getAssetFiatPrice(request, {})
const price = parseFloat(response.getPrice())
const assetAmount = parseFloat(amount)
// Assuming amount is in base units (e.g., satoshis for BTC)
// Convert to main unit (BTC = 8 decimals)
const btcAmount = assetAmount / 100000000
const fiatValue = btcAmount * price
return fiatValue.toFixed(2)
}
// Usage
const fiatValue = await convertToFiat(
client,
bitcoinNetwork,
'0x0000000000000000000000000000000000000000000000000000000000000000',
'100000000', // 1 BTC in satoshis
FiatCurrency.USD
)
console.log(`Value: $${fiatValue}`)
Price ticker with refresh
Rust
use hydra_client::pricing::{PricingServiceClient, GetAssetFiatPriceRequest, FiatCurrency};
use hydra_client::common::Network;
use tokio::time::{interval, Duration};
use std::sync::Arc;
use tokio::sync::Mutex;
struct PriceTicker {
client: Arc<Mutex<PricingServiceClient<tonic::transport::Channel>>>,
network: Network,
asset_id: String,
fiat_currency: FiatCurrency,
}
impl PriceTicker {
fn new(
client: Arc<Mutex<PricingServiceClient<tonic::transport::Channel>>>,
network: Network,
asset_id: String,
fiat_currency: FiatCurrency,
) -> Self {
Self {
client,
network,
asset_id,
fiat_currency,
}
}
async fn get_price(&self) -> Result<String, Box<dyn std::error::Error>> {
let request = GetAssetFiatPriceRequest {
network: Some(self.network.clone()),
asset_id: self.asset_id.clone(),
fiat_currency: self.fiat_currency as i32,
};
let mut client = self.client.lock().await;
let response = client.get_asset_fiat_price(request).await?;
Ok(response.into_inner().price)
}
async fn start<F>(&self, interval_ms: u64, callback: F)
where
F: Fn(String) + Send + 'static,
{
let mut ticker = interval(Duration::from_millis(interval_ms));
loop {
ticker.tick().await;
match self.get_price().await {
Ok(price) => callback(price),
Err(e) => eprintln!("Failed to fetch price: {}", e),
}
}
}
}
// Usage
let ticker = PriceTicker::new(
Arc::new(Mutex::new(client)),
bitcoin_network,
"0x0000000000000000000000000000000000000000000000000000000000000000".to_string(),
FiatCurrency::Usd,
);
tokio::spawn(async move {
ticker.start(30000, |price| {
println!("BTC/USD: ${}", price);
}).await;
});
// Stop after 5 minutes
tokio::time::sleep(Duration::from_secs(300)).await;
Go
package main
import (
"context"
"fmt"
"log"
"time"
pb "github.com/hydra/api/proto"
)
type PriceTicker struct {
client pb.PricingServiceClient
network *pb.Network
assetId string
fiatCurrency pb.FiatCurrency
stopChan chan bool
}
func NewPriceTicker(
client pb.PricingServiceClient,
network *pb.Network,
assetId string,
fiatCurrency pb.FiatCurrency,
) *PriceTicker {
return &PriceTicker{
client: client,
network: network,
assetId: assetId,
fiatCurrency: fiatCurrency,
stopChan: make(chan bool),
}
}
func (pt *PriceTicker) GetPrice() (string, error) {
request := &pb.GetAssetFiatPriceRequest{
Network: pt.network,
AssetId: pt.assetId,
FiatCurrency: pt.fiatCurrency,
}
response, err := pt.client.GetAssetFiatPrice(context.Background(), request)
if err != nil {
return "", err
}
return response.Price, nil
}
func (pt *PriceTicker) Start(intervalMs int, callback func(string)) {
ticker := time.NewTicker(time.Duration(intervalMs) * time.Millisecond)
go func() {
for {
select {
case <-ticker.C:
price, err := pt.GetPrice()
if err != nil {
log.Printf("Failed to fetch price: %v", err)
} else {
callback(price)
}
case <-pt.stopChan:
ticker.Stop()
return
}
}
}()
}
func (pt *PriceTicker) Stop() {
pt.stopChan <- true
}
// Usage
ticker := NewPriceTicker(
client,
bitcoinNetwork,
"0x0000000000000000000000000000000000000000000000000000000000000000",
pb.FiatCurrency_USD,
)
ticker.Start(30000, func(price string) {
fmt.Printf("BTC/USD: $%s\n", price)
})
// Stop after 5 minutes
time.Sleep(5 * time.Minute)
ticker.Stop()
TypeScript
class PriceTicker {
private client: PricingServiceClient
private network: Network
private assetId: string
private fiatCurrency: FiatCurrency
private interval: NodeJS.Timeout | null = null
constructor(
client: PricingServiceClient,
network: Network,
assetId: string,
fiatCurrency: FiatCurrency
) {
this.client = client
this.network = network
this.assetId = assetId
this.fiatCurrency = fiatCurrency
}
async getPrice(): Promise<string> {
const request = new GetAssetFiatPriceRequest()
request.setNetwork(this.network)
request.setAssetId(this.assetId)
request.setFiatCurrency(this.fiatCurrency)
const response = await this.client.getAssetFiatPrice(request, {})
return response.getPrice()
}
start(intervalMs: number, callback: (price: string) => void) {
this.interval = setInterval(async () => {
try {
const price = await this.getPrice()
callback(price)
} catch (error) {
console.error('Failed to fetch price:', error)
}
}, intervalMs)
}
stop() {
if (this.interval) {
clearInterval(this.interval)
this.interval = null
}
}
}
// Usage
const ticker = new PriceTicker(
client,
bitcoinNetwork,
'0x0000000000000000000000000000000000000000000000000000000000000000',
FiatCurrency.USD
)
ticker.start(30000, (price) => {
console.log(`BTC/USD: $${price}`)
})
// Stop after 5 minutes
setTimeout(() => ticker.stop(), 300000)
Calculate portfolio value
Rust
use hydra_client::pricing::{PricingServiceClient, GetAssetFiatPriceRequest, FiatCurrency};
use hydra_client::wallet::{WalletServiceClient, GetBalancesRequest};
use hydra_client::common::Network;
async fn calculate_portfolio_value(
pricing_client: &mut PricingServiceClient<tonic::transport::Channel>,
wallet_client: &mut WalletServiceClient<tonic::transport::Channel>,
network: Network,
fiat_currency: FiatCurrency,
) -> Result<String, Box<dyn std::error::Error>> {
// Get all balances
let balance_req = GetBalancesRequest {
network: Some(network.clone()),
};
let balance_resp = wallet_client.get_balances(balance_req).await?;
let balances = balance_resp.into_inner().balances;
let mut total_value = 0.0;
// Iterate through each asset
for (asset_id, balance) in balances {
// Get asset price
let price_req = GetAssetFiatPriceRequest {
network: Some(network.clone()),
asset_id: asset_id.clone(),
fiat_currency: fiat_currency as i32,
};
match pricing_client.get_asset_fiat_price(price_req).await {
Ok(price_resp) => {
let price: f64 = price_resp.into_inner().price.parse()?;
// Get total balance (onchain + offchain)
let onchain_confirmed: f64 = balance.onchain
.as_ref()
.and_then(|o| o.confirmed.parse().ok())
.unwrap_or(0.0);
let offchain_local: f64 = balance.offchain
.as_ref()
.and_then(|o| o.local.parse().ok())
.unwrap_or(0.0);
let total_amount = (onchain_confirmed + offchain_local) / 100_000_000.0; // Convert to BTC
total_value += total_amount * price;
}
Err(_) => {
eprintln!("No price available for {}", asset_id);
}
}
}
Ok(format!("{:.2}", total_value))
}
// Usage
let portfolio_value = calculate_portfolio_value(
&mut pricing_client,
&mut wallet_client,
bitcoin_network,
FiatCurrency::Usd,
).await?;
println!("Total Portfolio Value: ${}", portfolio_value);
Go
package main
import (
"context"
"fmt"
"log"
"strconv"
pb "github.com/hydra/api/proto"
)
func calculatePortfolioValue(
pricingClient pb.PricingServiceClient,
walletClient pb.WalletServiceClient,
network *pb.Network,
fiatCurrency pb.FiatCurrency,
) (string, error) {
// Get all balances
balanceReq := &pb.GetBalancesRequest{
Network: network,
}
balanceResp, err := walletClient.GetBalances(context.Background(), balanceReq)
if err != nil {
return "", err
}
balances := balanceResp.Balances
totalValue := 0.0
// Iterate through each asset
for assetId, balance := range balances {
// Get asset price
priceReq := &pb.GetAssetFiatPriceRequest{
Network: network,
AssetId: assetId,
FiatCurrency: fiatCurrency,
}
priceResp, err := pricingClient.GetAssetFiatPrice(context.Background(), priceReq)
if err != nil {
log.Printf("No price available for %s", assetId)
continue
}
price, err := strconv.ParseFloat(priceResp.Price, 64)
if err != nil {
continue
}
// Get total balance (onchain + offchain)
onchainConfirmed := 0.0
if balance.Onchain != nil {
onchainConfirmed, _ = strconv.ParseFloat(balance.Onchain.Confirmed, 64)
}
offchainLocal := 0.0
if balance.Offchain != nil {
offchainLocal, _ = strconv.ParseFloat(balance.Offchain.Local, 64)
}
totalAmount := (onchainConfirmed + offchainLocal) / 100000000 // Convert to BTC
totalValue += totalAmount * price
}
return fmt.Sprintf("%.2f", totalValue), nil
}
// Usage
portfolioValue, err := calculatePortfolioValue(
pricingClient,
walletClient,
bitcoinNetwork,
pb.FiatCurrency_USD,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total Portfolio Value: $%s\n", portfolioValue)
TypeScript
async function calculatePortfolioValue(
pricingClient: PricingServiceClient,
walletClient: WalletServiceClient,
network: Network,
fiatCurrency: FiatCurrency
): Promise<string> {
// Get all balances
const balanceReq = new GetBalancesRequest()
balanceReq.setNetwork(network)
const balanceResp = await walletClient.getBalances(balanceReq, {})
const balances = balanceResp.getBalancesMap()
let totalValue = 0
// Iterate through each asset
for (const [assetId, balance] of balances.entries()) {
// Get asset price
const priceReq = new GetAssetFiatPriceRequest()
priceReq.setNetwork(network)
priceReq.setAssetId(assetId)
priceReq.setFiatCurrency(fiatCurrency)
try {
const priceResp = await pricingClient.getAssetFiatPrice(priceReq, {})
const price = parseFloat(priceResp.getPrice())
// Get total balance (onchain + offchain)
const onchainConfirmed = parseFloat(balance.getOnchain()?.getConfirmed() || '0')
const offchainLocal = parseFloat(balance.getOffchain()?.getLocal() || '0')
const totalAmount = (onchainConfirmed + offchainLocal) / 100000000 // Convert to BTC
totalValue += totalAmount * price
} catch (error) {
console.warn(`No price available for ${assetId}`)
}
}
return totalValue.toFixed(2)
}
// Usage
const portfolioValue = await calculatePortfolioValue(
pricingClient,
walletClient,
bitcoinNetwork,
FiatCurrency.USD
)
console.log(`Total Portfolio Value: $${portfolioValue}`)
Supported Fiat Currencies
| Currency | Code | Symbol |
|---|---|---|
| US Dollar | USD | $ |
| Euro | EUR | € |
| British Pound | GBP | £ |
| Japanese Yen | JPY | ¥ |
| Chinese Yuan | CNY | ¥ |
| Australian Dollar | AUD | A$ |
| Canadian Dollar | CAD | C$ |
Price Data Sources
Prices come from the price oracle configured in config.yaml (pricing_config.price_oracle_url), which refreshes them in the background. Hydra App does not poll an exchange itself — it asks the oracle.
How an asset is looked up
(2026-09-03) The oracle is asked for that specific deployment of the asset — this contract, on this network. Only when the oracle does not list the asset by deployment is it asked for by the symbol the node holds it under (which is the declared symbol when the operator set one, otherwise the chain's).
Looking up by deployment first is what keeps a bridged token priced as itself rather than as whatever else shares its ticker.
Staleness
(2026-09-03) A price the oracle flags as stale — not refreshed within its maximum age — is reported as unavailable rather than served.
Handle "no price" as a normal outcome, not an error. A quiet asset, an oracle hiccup, or a newly listed token will all produce it. Anything that must not block on pricing (order sizing, balance display) needs a path that works without a fiat figure. Serving a stale price is the failure mode this deliberately avoids.
Best Practices
- Cache prices - Don't query too frequently, cache for 10-30 seconds
- Handle missing prices - Not all assets have fiat pricing
- Use appropriate decimals - Account for asset decimals when converting
- Batch requests - If checking multiple assets, add delay between calls
- Display update time - Show users when price was last updated
Error Handling
| Error Code | Description | Solution |
|---|---|---|
INVALID_ARGUMENT | Invalid asset_id or network | Verify asset exists on network |
NOT_FOUND | No price available for asset | The oracle does not list this deployment or its symbol, or the price it holds is stale. Expected for illiquid and newly listed assets — degrade gracefully rather than retrying hard |
UNAVAILABLE | Price service temporarily down | Retry with exponential backoff |
Decimal Precision
Prices are returned as decimal strings with appropriate precision:
- BTC/USD: 2 decimal places (e.g., "98450.75")
- ETH/USD: 2 decimal places (e.g., "3250.50")
- Small assets: May have more decimals for accuracy
Always parse as float or decimal to maintain precision.