---
title: "Ring Oracle"
description: "One server-only rate facade for native-token desk quotes, Chainlink bridges, fiat FX, credit accounting, and store presentment"
locale: "en"
---
# Ring Oracle

Ring Oracle is the server-only rate facade at `@/lib/ring-oracle`. It gives payment, wallet, membership, public-pool, and store code one vocabulary for project value instead of hard-coding token or fiat symbols.

> **Info**
> Use **Founder** / **Developer** in the docs sidebar to filter this page. Founders see the operator journey and checkout behavior; developers see verified modules, routes, payloads, and smoke commands.

## Three denominations, no symbol aliases

| Denomination | Meaning | Configured identity |
|---|---|---|
| `credit_balance` | Project-owned ledger unit | `credit.creditBalanceUnitLabel` and `credit.creditBalanceUnitToMainCurrency` |
| `native_token` | Clone-native token | `tokens.nativeToken.symbol` |
| `main_currency` | Project settlement fiat | `store.mainCurrency` |

`ValueDenomination` contains exactly those three values. Currency symbols such as `RING`, `USD`, or `UAH` are display/config values, not denomination IDs.

  `GET` and `POST /api/prices/conversion` accept only `native_token` and `main_currency`. `credit_balance` uses the credit accounting surface, and legacy `RING` / `USD` request aliases return HTTP 400.

### For founders

## Why this matters for your clone

  
- **[Consistent money language](/docs/features/payments.md)** — Wallet, checkout, membership, and community funding resolve project value through the same denomination model.

  
- **[Operational FX feed](/docs/features/payment-conductor.md)** — ProcessConductor refreshes fiat presentment rates while manual overrides remain available in clone config.

  
- **[Truthful checkout](/docs/features/store.md)** — The store separates what a buyer sees from the fiat currency a card or PayPal gateway charges.

## Operator journey

### 1. Choose the clone’s money identities

Set the project main currency, supported fiat presentment currencies, supported crypto symbols, native token, and credit accounting rate in `ring-config.json`. Ring Oracle reads those project identities; product code does not need a `RING/USD` special case.

### 2. Select the FX feed

`ring-config.fx` resolves providers in this order:

1. `fx.byMainCurrency[store.mainCurrency]`
2. NBU when the main currency is `UAH`
3. `fx.default` for other main currencies

The shipped default uses NBU for UAH and `open_er_api` otherwise. `fx.manualOverrides` wins over both static rates and the live overlay.

### 3. Operate desk and FX rates

Open `/admin/web3/settings` as a superadmin to update the native-token desk rate, inspect the resolved fiat FX provider and last fetch time, or force an FX refresh. The UI reads `GET /api/admin/web3/settings` and `GET /api/admin/fx`; it writes with the corresponding `POST` routes.

### 4. Schedule the feed route

Production schedulers call `GET /api/cron/fx-feed-refresh`. Set `CRON_SECRET` and send it as a Bearer token. The route records the run through ProcessConductor under pipeline ID `fx-feed-refresh`; feed staleness still follows the configured `refreshHours`.

### 5. Understand buyer presentment

The left navigation rail switches between `main_currency` (the buyer’s last fiat preference) and `native_token` only. The checkout droplist follows that mode: fiat lists configured `SupportedCurrencies`; native lists configured `SupportedCrypto`.

The review step converts product lines and totals through `convertPrice` / `displayPrice`. Card and PayPal remain fiat charges: checkout carries a fiat `paymentCurrency`, and the server recomputes the charge from the main-currency order total before calling PaymentConductor.

> **Tip**
> A crypto amount in the checkout review is presentment, not proof that the card gateway will charge crypto. The payment rail and `paymentCurrency` decide settlement.

### For developers

## Architecture

| Surface | Verified path | Responsibility |
|---|---|---|
| Facade | `lib/ring-oracle/index.ts` | Server-only export surface for every rate family |
| Denominations | `lib/value-denomination.ts` | Exact `ValueDenomination` triad and validation |
| Desk oracle | `features/wallet/services/native-token-oracle.ts` | Native ↔ main rate, signed quotes, audit log |
| Chainlink bridge | `features/wallet/services/native-token-chainlink-oracle.ts` | Allowlisted external EVM token feeds |
| Fiat FX | `lib/fx/fx-feed-service.ts`, `lib/fx/fx-rates-overlay.ts` | Provider resolution, persistent cache, live overlay |
| Credit accounting | `lib/payments/credit-balance.ts` | Credit unit ↔ main accounting helpers |
| FX pipeline | `lib/processes/fx/fx-feed-refresh.ts`, `lib/processes/registry.ts` | ProcessConductor handler, ID, and cron path |
| Conversion API | `app/api/prices/conversion/route.ts` | Public native ↔ main rate and conversion contract |
| SSR hydrate | `app/layout.tsx` | `AuthenticatedAppShell` warms and reads rates |
| Client provider | `components/providers/app-client-shell.tsx` | Passes `initialExchangeRates` to store currency context |
| Store currency context | `features/store/currency-context.tsx` | Display mode, conversion, formatting, preferences |
| Checkout presentment | `features/store/components/checkout/prebilling-page.tsx` | Fiat/crypto droplist and fiat `paymentCurrency` |
| Checkout charge | `app/_actions/store-checkout-payment.ts` | Server-side fiat recomputation for card/PayPal |

### Facade rule

Server code imports rates from the facade, not from implementation modules:

{`import {
  ensureFxFeedFresh,
  getExchangeRates,
  getMainCurrencySymbol,
  getNativeTokenToMainCurrencyRate,
} from '@/lib/ring-oracle'

await ensureFxFeedFresh()
const rates = getExchangeRates()
const desk = await getNativeTokenToMainCurrencyRate()`}

`@/lib/ring-oracle` imports `server-only`. Client components receive serializable rate data through `AuthenticatedAppShell`, `AppClientShell`, or the client-safe `getLiveExchangeRates` server action.

## FX refresh flow

### Configure provider resolution

{`{
  "fx": {
    "byMainCurrency": {
      "UAH": {
        "provider": "nbu",
        "enabled": true,
        "refreshHours": 24
      }
    },
    "default": {
      "provider": "open_er_api",
      "enabled": true,
      "refreshHours": 24
    },
    "manualOverrides": {}
  }
}`}

Provider IDs are `nbu`, `open_er_api`, and `frankfurter`. A hard guard replaces NBU with `open_er_api` whenever the main currency is not UAH.

### Protect and invoke the cron route

{`CRON_SECRET=replace-with-a-long-random-secret

curl \
  -H "Authorization: Bearer $CRON_SECRET" \
  https://your-ring.example/api/cron/fx-feed-refresh`}

The authorization check is conditional on `CRON_SECRET`, so production deployments must configure it. The response includes ProcessConductor `runId` plus refresh metadata; a disabled feed reports a successful skipped run. Rates persist under `platform_settings` id `fx_feed` (same hybrid JSONB pattern as the desk oracle). Ship `k8s/cronjob-fx-feed-refresh.yaml` for hourly cluster refresh.

### Inspect or force refresh as an admin

`GET /api/admin/fx` returns resolved feed configuration, overrides, fetch time, a rate sample, and resolved rates for platform admins. `POST /api/admin/fx` accepts `{ "force": true }` and refreshes the feed. Desk-rate reads and writes remain on the superadmin-only `/api/admin/web3/settings` route.

## Conversion API contract

### Read supported pairs

`GET /api/prices/conversion` returns `denominations: ["native_token", "main_currency"]`, both direction pairs, rates and inverse rates, source metadata, and limits from `0.000001` through `1000000`.

### Convert an amount

{`curl -X POST https://your-ring.example/api/prices/conversion \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "25",
    "from": "main_currency",
    "to": "native_token"
  }'`}

`amount` must be a positive decimal string, and `from` must differ from `to`. The response includes denomination IDs, configured display currencies, source amount, converted amount, exchange rate, timestamp, confidence, zero conversion fee, and source metadata.

  `{ "from": "RING", "to": "USD" }` is invalid even when those are the clone’s configured symbols. Send denomination IDs, not symbols.

## SSR and checkout boundaries

### Hydrate rates on the server

`AuthenticatedAppShell` calls `ensureFxFeedFresh()`, then `getExchangeRates()`. If refresh fails, the shell leaves `initialExchangeRates` null and client code falls back to static `ring-config` rates.

### Seed and refresh the client provider

`AppClientShell` passes the serializable rates to `StorePaymentMethodsProvider`. The provider starts from that SSR seed, then calls `getLiveExchangeRates()` after hydration to keep browser conversion aligned with server checkout.

### Keep display and charge currencies separate

`StorePaymentMethodsProvider.displayMode` is only `main_currency | native_token`. `PrebillingPage` derives the droplist from `getSupportedCurrencies()` or `getSupportedCrypto()`, while preserving a fiat `paymentCurrency` for card and PayPal. `submitStoreCheckoutPayment` validates that fiat against the supported presentment pool and recomputes the amount server-side.

## Smoke verification

Run the shipped `smk46_` smoke from `ring-platform.org`:

{`NODE_OPTIONS="--conditions=react-server" \
DB_BACKEND_MODE=k8s-postgres-fcm \
npx tsx scripts/smoke-ring-oracle-fx.cts

# Include public API and cron probes:
SMOKE_BASE_URL=http://localhost:3000 \
CRON_SECRET=replace-with-the-local-secret \
npx tsx scripts/smoke-ring-oracle-fx.cts`}

The smoke verifies pipeline registration, provider resolution and refresh, the native ↔ main service round-trip, optional conversion `GET` / `POST` contracts, HTTP 400 for legacy `RING` / `USD`, and cron response behavior.

## Related documentation

  
- [features/payment-conductor](/docs/features/payment-conductor.md) — Depends-on: PaymentConductor consumes Oracle rates when a rail crosses denominations.

  
- [features/store](/docs/features/store.md) — Same-workflow: catalog display, checkout presentment, and server-side charge creation.

  
- [features/wallet](/docs/features/wallet.md) — Deep-dive: Token Desk quotes, credit accounting, and native-token balances.
