External Developer Guide
Cronos Oracle Hub
Subscription / Paid Access

Integrating Cronos Oracle Hub from External Projects

This guide is written only for external projects and developers who are not owner and not authorized on the Hub. It is aimed at teams that will normally integrate through subscription or paid requests, and only in rare cases be whitelisted for trials or special agreements.

The intended integration target is the CronosOracleHub smart contract. External projects do not integrate directly with the adapter.
The adapter addresses are still useful for transparency and verification, but each adapter pricing entrypoint is protected by onlyAggregator. That means your wallet, frontend, EOA, or contract cannot call adapter pricing directly.

Contents

  1. Deployment reference
  2. Which function should you use?
  3. Access model for external users
  4. ABI fragments you actually need
  5. Frontend / script examples
  6. Solidity contract examples
  7. Returned struct format
  8. Important rules and gotchas
  9. FAQ

1. Deployment reference

Component Address Why it matters
CronosOracleHub 0xcCf26F02C708bc36Cb295783EE85a296a733FfC9 Main integration contract. This is the contract your project calls.
CronosOracleHubV2PairAdapter 0x8F99459f41000Db54CAb2394dcbB22AEF285B23b Reference only. External projects do not call this directly.
CronosOracleHubV3PairAdapter 0x32D217bA5f75d20eBdd4dE6Aaa3dA9eC530eBdAF Reference only. External projects do not call this directly.
Payment token - USDC 0xc21223249CA28397B4B6541dfFaEcC539BfF0c59 The token used for request fees and subscription payments.
WCRO 0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23 Used internally when pricing native CRO as address(0).

What external projects should call

  • getUsdPrices(address[] tokens) for the paid transaction path.
  • getPricesInToken(address[] tokens, address quoteToken) for the paid transaction path.
  • getUsdPricesView(address[] tokens) only if the caller has free access or access by subscription.
  • getPricesInTokenView(address[] tokens, address quoteToken) only if the caller has free access or access by subscription.
  • buySubscription(uint256 planIndex) to subscribe the caller itself.
  • subscribeFor(address target, uint256 planIndex) to subscribe another address, including a contract address.

2. Which function should you use?

Your situation Recommended function Why
Your wallet or frontend wants prices and you are not subscribed getUsdPrices or getPricesInToken These paid functions can pull the request fee from the caller after ERC-20 approval.
Your project contract is subscribed or whitelisted getUsdPricesView or getPricesInTokenView These are the cleanest functions for on-chain integration because they avoid per-call token transfers.
You want to value assets in USD getUsdPrices or getUsdPricesView Hub returns USD prices scaled to 1e18.
You want token-in-token quotes getPricesInToken or getPricesInTokenView Hub derives token-in-quote prices through USD internally.
For most smart-contract integrations, subscription is the preferred model. A subscribed contract can use the free view path and avoids operational friction around ERC-20 approvals.

3. Access model for external users

Paid request path

Use getUsdPrices or getPricesInToken.

  • The function is not a Solidity view function.
  • If the caller does not have free access, the Hub charges:
requiredFee = flatRequestFee + (perTokenRequestFee * tokenCount)
  • The caller must have enough payment tokens.
  • The caller must approve the Hub first.

Free view path

Use getUsdPricesView or getPricesInTokenView.

  • Only available to callers with free access.
  • Free access includes active subscribers and whitelisted addresses.
  • Owner and authorized addresses also qualify, but that is outside the scope of this guide.

Whitelisting

Whitelisting is usually reserved for the Hub owner's own projects or a temporary free trial.

  • It is controlled by Hub admins.
  • External projects should not assume whitelist access will exist forever.
  • For long-term integration, subscription is usually the safer expectation.
  • Special agreements are a possibility.

How subscription works

  • Subscriptions are plan-based.
  • Each plan is selected by planIndex, not by duration input from the caller.
  • You can inspect plans with getSubscriptionPlanCount() and getSubscriptionPlanAt(index).
  • If a subscription is still active and another plan is bought, the new duration is added after the current expiry.
  • You can subscribe an EOA or a contract address using subscribeFor(target, planIndex).

Live on-chain pricing

These values are read directly from the Hub contract when this HTML is opened in a wallet-enabled browser. Replace the placeholder Hub address below before distributing the guide.

Source functions: getPaymentConfig(), getRequiredRequestFee(tokenCount), getSubscriptionPlanCount(), and getSubscriptionPlanAt(index).
Flat paid request fee Loading
Base fee charged per paid request.
Per-token request fee Loading
Added for each token in the request.
Example required fee Loading
Current cost for a 3-token paid request.
Payment token [Payment token address]
Read from getPaymentConfig().
Subscription plans currently configured on-chain
Loading plan list...
Waiting to read contract pricing data.

4. ABI fragments you actually need

CronosOracleHub ABI — core external-project fragment

[
  {
    "inputs": [{"internalType":"address[]","name":"tokens","type":"address[]"}],
    "name": "getUsdPrices",
    "outputs": [{
      "components": [
        {"internalType":"uint256","name":"priceE18","type":"uint256"},
        {"internalType":"bool","name":"valid","type":"bool"},
        {"internalType":"uint8","name":"usedSources","type":"uint8"}
      ],
      "internalType":"struct CronosOracleHub.PriceResult[]",
      "name":"results",
      "type":"tuple[]"
    }],
    "stateMutability":"nonpayable",
    "type":"function"
  },
  {
    "inputs": [
      {"internalType":"address[]","name":"tokens","type":"address[]"},
      {"internalType":"address","name":"quoteToken","type":"address"}
    ],
    "name": "getPricesInToken",
    "outputs": [{
      "components": [
        {"internalType":"uint256","name":"priceE18","type":"uint256"},
        {"internalType":"bool","name":"valid","type":"bool"},
        {"internalType":"uint8","name":"usedSources","type":"uint8"}
      ],
      "internalType":"struct CronosOracleHub.PriceResult[]",
      "name":"results",
      "type":"tuple[]"
    }],
    "stateMutability":"nonpayable",
    "type":"function"
  },
  {
    "inputs": [{"internalType":"address[]","name":"tokens","type":"address[]"}],
    "name": "getUsdPricesView",
    "outputs": [{
      "components": [
        {"internalType":"uint256","name":"priceE18","type":"uint256"},
        {"internalType":"bool","name":"valid","type":"bool"},
        {"internalType":"uint8","name":"usedSources","type":"uint8"}
      ],
      "internalType":"struct CronosOracleHub.PriceResult[]",
      "name":"results",
      "type":"tuple[]"
    }],
    "stateMutability":"view",
    "type":"function"
  },
  {
    "inputs": [
      {"internalType":"address[]","name":"tokens","type":"address[]"},
      {"internalType":"address","name":"quoteToken","type":"address"}
    ],
    "name": "getPricesInTokenView",
    "outputs": [{
      "components": [
        {"internalType":"uint256","name":"priceE18","type":"uint256"},
        {"internalType":"bool","name":"valid","type":"bool"},
        {"internalType":"uint8","name":"usedSources","type":"uint8"}
      ],
      "internalType":"struct CronosOracleHub.PriceResult[]",
      "name":"results",
      "type":"tuple[]"
    }],
    "stateMutability":"view",
    "type":"function"
  },
  {
    "inputs": [{"internalType":"uint256","name":"planIndex","type":"uint256"}],
    "name": "buySubscription",
    "outputs": [],
    "stateMutability":"nonpayable",
    "type":"function"
  },
  {
    "inputs": [
      {"internalType":"address","name":"target","type":"address"},
      {"internalType":"uint256","name":"planIndex","type":"uint256"}
    ],
    "name": "subscribeFor",
    "outputs": [],
    "stateMutability":"nonpayable",
    "type":"function"
  },
  {
    "inputs": [],
    "name": "getSubscriptionPlanCount",
    "outputs": [{"internalType":"uint256","name":"count","type":"uint256"}],
    "stateMutability":"view",
    "type":"function"
  },
  {
    "inputs": [{"internalType":"uint256","name":"index","type":"uint256"}],
    "name": "getSubscriptionPlanAt",
    "outputs": [
      {"internalType":"uint64","name":"durationSeconds","type":"uint64"},
      {"internalType":"uint256","name":"price","type":"uint256"}
    ],
    "stateMutability":"view",
    "type":"function"
  },
  {
    "inputs": [{"internalType":"address","name":"subscriber","type":"address"}],
    "name": "getSubscriptionDetails",
    "outputs": [
      {"internalType":"uint64","name":"expiry","type":"uint64"},
      {"internalType":"bool","name":"active","type":"bool"},
      {"internalType":"uint256","name":"remainingSeconds","type":"uint256"}
    ],
    "stateMutability":"view",
    "type":"function"
  },
  {
    "inputs": [{"internalType":"address","name":"user","type":"address"}],
    "name": "getAccessDetails",
    "outputs": [
      {"internalType":"bool","name":"whitelisted","type":"bool"},
      {"internalType":"bool","name":"subscribed","type":"bool"},
      {"internalType":"bool","name":"hasFreeAccess","type":"bool"}
    ],
    "stateMutability":"view",
    "type":"function"
  },
  {
    "inputs": [],
    "name": "getPaymentConfig",
    "outputs": [
      {"internalType":"address","name":"paymentToken_","type":"address"},
      {"internalType":"uint256","name":"flatRequestFee_","type":"uint256"},
      {"internalType":"uint256","name":"perTokenRequestFee_","type":"uint256"}
    ],
    "stateMutability":"view",
    "type":"function"
  },
  {
    "inputs": [{"internalType":"uint256","name":"tokenCount","type":"uint256"}],
    "name": "getRequiredRequestFee",
    "outputs": [{"internalType":"uint256","name":"requiredFee","type":"uint256"}],
    "stateMutability":"view",
    "type":"function"
  }
]

Minimal ERC-20 ABI fragment for payment token approval

[
  {
    "inputs": [
      {"internalType":"address","name":"spender","type":"address"},
      {"internalType":"uint256","name":"amount","type":"uint256"}
    ],
    "name":"approve",
    "outputs":[{"internalType":"bool","name":"","type":"bool"}],
    "stateMutability":"nonpayable",
    "type":"function"
  }
]

Adapter ABI reference

[
  {
    "inputs": [{"internalType":"address[]","name":"tokens","type":"address[]"}],
    "name":"getUsdPrices",
    "outputs":[{
      "components":[
        {"internalType":"uint256","name":"priceE18","type":"uint256"},
        {"internalType":"uint256","name":"liquidityUsdE18","type":"uint256"},
        {"internalType":"bool","name":"valid","type":"bool"}
      ],
      "internalType":"struct IPriceAdapter.AdapterPrice[]",
      "name":"prices",
      "type":"tuple[]"
    }],
    "stateMutability":"view",
    "type":"function"
  }
]
External projects should treat this as documentation only. The adapter pricing function is protected by onlyAggregator, so direct external adapter pricing calls will revert unless the caller is the configured Hub, owner, or an authorized address on the adapter.

5. Frontend / script examples (ethers.js)

Example A — inspect plan list

import { ethers } from "ethers";

const HUB = "[CronosOracleHub address]";
const hub = new ethers.Contract(HUB, HUB_ABI, signerOrProvider);

const count = Number(await hub.getSubscriptionPlanCount());
for (let i = 0; i < count; i++) {
  const [durationSeconds, price] = await hub.getSubscriptionPlanAt(i);
  console.log({
    planIndex: i,
    durationSeconds: Number(durationSeconds),
    price: price.toString()
  });
}

Example B — subscribe the connected wallet

const HUB = "[CronosOracleHub address]";
const PAYMENT_TOKEN = "[Payment token address]";
const PLAN_INDEX = 0;

const hub = new ethers.Contract(HUB, HUB_ABI, signer);
const token = new ethers.Contract(PAYMENT_TOKEN, ERC20_ABI, signer);

const [, price] = await hub.getSubscriptionPlanAt(PLAN_INDEX);

const approveTx = await token.approve(HUB, price);
await approveTx.wait();

const subscribeTx = await hub.buySubscription(PLAN_INDEX);
await subscribeTx.wait();

console.log("Subscription bought for connected wallet");

Example C — subscribe a project contract

const HUB = "[CronosOracleHub address]";
const PAYMENT_TOKEN = "[Payment token address]";
const PROJECT_CONTRACT = "[Your contract address]";
const PLAN_INDEX = 1;

const hub = new ethers.Contract(HUB, HUB_ABI, signer);
const token = new ethers.Contract(PAYMENT_TOKEN, ERC20_ABI, signer);

const [, price] = await hub.getSubscriptionPlanAt(PLAN_INDEX);

await (await token.approve(HUB, price)).wait();
await (await hub.subscribeFor(PROJECT_CONTRACT, PLAN_INDEX)).wait();

const details = await hub.getSubscriptionDetails(PROJECT_CONTRACT);
console.log({
  expiry: details.expiry?.toString?.() ?? details[0].toString(),
  active: details.active ?? details[1],
  remainingSeconds: (details.remainingSeconds ?? details[2]).toString()
});
This is the recommended pattern for protocol integrations. The payer can be an EOA, while the subscribed caller is the project contract that later queries the Hub.

Example D — paid wallet request for USD prices

const HUB = "[CronosOracleHub address]";
const PAYMENT_TOKEN = "[Payment token address]";

const TOKENS = [
  "0x0000000000000000000000000000000000000000", // native CRO
  "0xTokenA",
  "0xTokenB"
];

const hub = new ethers.Contract(HUB, HUB_ABI, signer);
const token = new ethers.Contract(PAYMENT_TOKEN, ERC20_ABI, signer);

const requiredFee = await hub.getRequiredRequestFee(TOKENS.length);
await (await token.approve(HUB, requiredFee)).wait();

const tx = await hub.getUsdPrices(TOKENS);
const receipt = await tx.wait();
console.log("Paid price request executed", receipt.hash);

// To read returned values off-chain without sending a paid tx, simulate with callStatic:
const results = await hub.callStatic.getUsdPrices(TOKENS);
console.log(results.map(r => ({
  priceE18: r.priceE18.toString(),
  valid: r.valid,
  usedSources: Number(r.usedSources)
})));
The actual function is nonpayable and charges the caller. For frontend usage, you will often first approve, then either execute the paid request on-chain or simulate with callStatic for UI display.

Example E — subscribed or whitelisted caller using free view path

const HUB = "[CronosOracleHub address]";
const hub = new ethers.Contract(HUB, HUB_ABI, providerOrSigner);

const access = await hub.getAccessDetails("[Caller address]");
if (!(access.hasFreeAccess ?? access[2])) {
  throw new Error("Caller does not have free access");
}

const results = await hub.getUsdPricesView([
  "0x0000000000000000000000000000000000000000",
  "0xTokenA"
]);

console.log(results.map(r => ({
  priceE18: r.priceE18.toString(),
  valid: r.valid,
  usedSources: Number(r.usedSources)
})));

Example F — token-in-token pricing

const HUB = "[CronosOracleHub address]";
const QUOTE_TOKEN = "0xUSDCorOtherQuoteToken";

const results = await hub.getPricesInTokenView(
  ["0xTokenA", "0xTokenB"],
  QUOTE_TOKEN
);

for (const r of results) {
  console.log({
    priceInQuoteE18: r.priceE18.toString(),
    valid: r.valid,
    usedSources: Number(r.usedSources)
  });
}

6. Solidity contract examples

Minimal interface for external contracts

interface ICronosOracleHub {
    struct PriceResult {
        uint256 priceE18;
        bool valid;
        uint8 usedSources;
    }

    function getUsdPricesView(address[] calldata tokens)
        external
        view
        returns (PriceResult[] memory results);

    function getPricesInTokenView(address[] calldata tokens, address quoteToken)
        external
        view
        returns (PriceResult[] memory results);

    function getUsdPrices(address[] calldata tokens)
        external
        returns (PriceResult[] memory results);

    function getPricesInToken(address[] calldata tokens, address quoteToken)
        external
        returns (PriceResult[] memory results);

    function getAccessDetails(address user)
        external
        view
        returns (bool whitelisted, bool subscribed, bool hasFreeAccess);
}

Recommended pattern — subscribed contract using free view path

contract MyProtocol {
    ICronosOracleHub public immutable hub;

    constructor(address hub_) {
        hub = ICronosOracleHub(hub_);
    }

    function getTokenUsd(address token) external view returns (uint256 priceE18, bool valid) {
        address[] memory tokens = new address[](1);
        tokens[0] = token;

        ICronosOracleHub.PriceResult[] memory results = hub.getUsdPricesView(tokens);
        return (results[0].priceE18, results[0].valid);
    }
}
This is the cleanest on-chain integration style when your contract has active subscription access or is whitelisted.

Paid contract path — possible, but operationally heavier

interface IERC20ApproveOnly {
    function approve(address spender, uint256 amount) external returns (bool);
}

contract MyPaidProtocol {
    ICronosOracleHub public immutable hub;
    IERC20ApproveOnly public immutable paymentToken;

    constructor(address hub_, address paymentToken_) {
        hub = ICronosOracleHub(hub_);
        paymentToken = IERC20ApproveOnly(paymentToken_);
    }

    function paidRead(address token, uint256 feeAmount)
        external
        returns (uint256 priceE18, bool valid)
    {
        paymentToken.approve(address(hub), feeAmount);

        address[] memory tokens = new address[](1);
        tokens[0] = token;

        ICronosOracleHub.PriceResult[] memory results = hub.getUsdPrices(tokens);
        return (results[0].priceE18, results[0].valid);
    }
}
This path only works if the contract itself holds payment tokens and actively approves the Hub. That is why subscriptions are normally preferable for protocol-level integrations.

7. Returned struct format

Field Type Meaning
priceE18 uint256 Price scaled to 1e18. Example: 1.25 USD = 1250000000000000000.
valid bool true if the Hub could produce a valid aggregated price for that token.
usedSources uint8 How many adapter quotes survived filtering and were actually used in the final result.
Always check valid before trusting priceE18. A zero or default-looking number should not be trusted unless valid == true.

8. Important rules and gotchas

  • Use the Hub, not the adapter. The adapter pricing entrypoint is restricted.
  • Native CRO is represented as address(0). The Hub internally normalizes that to WCRO.
  • Request fees are paid in the payment token's smallest units. For a 6-decimal token like USDC, 1000000 means 1.0.
  • View path is not public-for-everyone. It is a free-access path, not an unrestricted public path.
  • Subscribed addresses count as free-access callers. This includes subscribed contracts.
  • For protocol integrations, subscribe the contract address that will call the Hub.
  • Do not assume trial whitelist access is permanent. Build for subscription unless explicitly agreed otherwise.
  • Adapter hardening means runtime pricing is more robust. Misconfigured or broken pair routes are intended to be filtered or rejected without breaking normal price consumption through the Hub.

9. FAQ

External projects should integrate with CronosOracleHub, not directly with the adapter. The adapter pricing entrypoint is restricted and is intended to be called by the Hub.

The normal long-term model is subscription. External users can also use the paid request path, but subscriptions are usually cleaner for repeated usage and for protocol integrations.

Use the paid request path when the caller does not have free access and you want to fetch prices anyway. In that case the caller needs sufficient payment tokens and must approve the Hub first.

Use the free view path when the caller has free access, for example because it is actively subscribed or whitelisted. This is usually the preferred pattern for project contracts.

You need enough ERC-20 allowance for the Hub to collect the required fee. Whether you approve each time or approve a larger amount is your own operational choice and risk decision.

Yes. A payer can subscribe a target address using subscribeFor(target, planIndex). That target can be a smart contract address, which is the recommended setup for protocol integrations.

The new plan duration is added after the current subscription expiry. It does not overwrite the active remaining period.

Native CRO is represented as address(0). The Hub internally normalizes that to WCRO where required.

priceE18 is the returned price scaled to 1e18. For example, a USD price of 1.25 would be returned as 1250000000000000000.

No. Always check the valid field first. You should not rely on a returned number unless the result is marked valid.

It tells you how many adapter quotes survived filtering and were actually used in the final aggregated result.

No. External projects should treat whitelisting as temporary unless explicitly agreed otherwise. For long-term integrations, build around subscription access.

No. Paid request fees and blockchain gas costs are separate. The request fee is collected in the configured payment token, while gas is paid to the network.

Yes. Use getPricesInToken or getPricesInTokenView with the quote token address when you want token-in-token pricing instead of USD pricing.

Yes, token support can be requested. Contact Swerfer via X or Telegram using the links in the footer and send at least the following:

  • the token address
  • the LP pair address

Providing both addresses helps evaluate whether the token can be added cleanly to the pricing configuration.