For AI agents: Documentation index at /llms.txt

Skip to content

Fetch exchange rates

The exchange rate canister (XRC) provides cryptocurrency and fiat exchange rates to other canisters. Because the XRC requires cycles attached to every call, you must call it from a canister that has cycles available; the CLI cannot attach cycles to a direct call.

This guide shows how to call the XRC from Rust and Motoko, parse the scaled-integer response, and test from the CLI using the proxy canister pattern.

Call the XRC

The XRC exposes a single method, get_exchange_rate, which takes a base asset, quote asset, and optional timestamp. Every call must include exactly 1 billion cycles; unused cycles are refunded.

In Motoko, declare the XRC actor interface inline and use the (with cycles = amount) syntax to attach cycles. The Candid field class maps to class_ in Motoko because class is a reserved keyword.

import Cycles "mo:core/Cycles";
import Float "mo:core/Float";
import Int "mo:core/Int";
import Nat32 "mo:core/Nat32";
import Nat64 "mo:core/Nat64";
type AssetClass = { #Cryptocurrency; #FiatCurrency };
type Asset = { symbol : Text; class_ : AssetClass };
type GetExchangeRateRequest = {
base_asset : Asset;
quote_asset : Asset;
timestamp : ?Nat64;
};
type ExchangeRateMetadata = {
decimals : Nat32;
base_asset_num_received_rates : Nat64;
base_asset_num_queried_sources : Nat64;
quote_asset_num_received_rates : Nat64;
quote_asset_num_queried_sources : Nat64;
standard_deviation : Nat64;
forex_timestamp : ?Nat64;
};
type ExchangeRate = {
base_asset : Asset;
quote_asset : Asset;
timestamp : Nat64;
rate : Nat64;
metadata : ExchangeRateMetadata;
};
type ExchangeRateError = {
#AnonymousPrincipalNotAllowed;
#Pending;
#CryptoBaseAssetNotFound;
#CryptoQuoteAssetNotFound;
#StablecoinRateNotFound;
#StablecoinRateTooFewRates;
#StablecoinRateZeroRate;
#ForexInvalidTimestamp;
#ForexBaseAssetNotFound;
#ForexQuoteAssetNotFound;
#ForexAssetsNotFound;
#RateLimited;
#NotEnoughCycles;
#FailedToAcceptCycles;
#InconsistentRatesReceived;
#Other : { code : Nat32; description : Text };
};
transient let xrc : actor {
get_exchange_rate : shared GetExchangeRateRequest -> async {
#Ok : ExchangeRate;
#Err : ExchangeRateError;
};
} = actor "uf6dk-hyaaa-aaaaq-qaaaq-cai";
persistent actor {
public func getRate(base : Text, quote : Text) : async ?Float {
let request : GetExchangeRateRequest = {
base_asset = { symbol = base; class_ = #Cryptocurrency };
quote_asset = { symbol = quote; class_ = #FiatCurrency };
timestamp = null;
};
let result = await (with cycles = 1_000_000_000) xrc.get_exchange_rate(request);
switch result {
case (#Ok rate) {
let scale = Float.fromInt(Int.pow(10, Nat32.toNat(rate.metadata.decimals)));
?(Float.fromInt(Nat64.toNat(rate.rate)) / scale)
};
case (#Err err) {
// handle specific errors as needed (see Error handling section below)
null
};
};
};
}

Reading the response

The rate field is a scaled 64-bit integer. The metadata.decimals field tells you the scale factor:

human_readable_price = rate / 10^decimals

For example, if rate = 8_523_450_000 and decimals = 8, the price is 85.2345.

The response also includes useful metadata:

FieldDescription
base_asset_num_queried_sourcesNumber of exchanges queried for the base asset
base_asset_num_received_ratesNumber of exchanges that responded with a valid rate
standard_deviationSpread across received rates (scaled by decimals)
forex_timestampTimestamp of the forex data used, if a fiat asset was involved

A large gap between num_queried_sources and num_received_rates indicates that many exchanges were unavailable, which may affect rate quality.

Requesting historical rates

Pass a Unix timestamp (in seconds) to request a rate for a past minute. Timestamps have 1-minute granularity; seconds within the minute are ignored.

For reliability, use the start of the previous minute rather than the current minute, because some exchanges may not yet have published data for the current interval:

let oneMinuteAgo : Nat64 = (Nat64.fromNat(Int.abs(Time.now())) / 1_000_000_000) - 60;
let request : GetExchangeRateRequest = {
base_asset = { symbol = "ICP"; class_ = #Cryptocurrency };
quote_asset = { symbol = "USD"; class_ = #FiatCurrency };
timestamp = ?oneMinuteAgo;
};

Error handling

The most important errors to handle explicitly:

ErrorCauseAction
NotEnoughCyclesFewer than 1B cycles attachedEnsure the caller provides sufficient cycles
PendingXRC is already retrieving a rate for this assetRetry after a short delay
RateLimitedToo many concurrent requests from non-CMC callersRetry with backoff
CryptoBaseAssetNotFound / CryptoQuoteAssetNotFoundExchange returned no data for the assetCheck the symbol and try again
InconsistentRatesReceivedRates across exchanges diverged too widelyThe XRC refuses to return an unreliable rate; retry later
ForexInvalidTimestampRequested timestamp is outside the available forex windowUse a more recent timestamp

Testing from the CLI

The XRC requires cycles attached to the call, so you cannot call it directly from the CLI on mainnet. To test the integration from the terminal, use the proxy canister pattern: deploy a proxy canister that forwards the call with cycles attached.

On a local replica, note that the XRC fetches from live external exchanges via HTTPS outcalls, so local testing requires a connection to the internet and a subnet configured as type system.

Next steps