> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goldsky.com/llms.txt
> Use this file to discover all available pages before exploring further.

# On-chain FX & multi-currency corridors

> Index Circle StableFX quotes and settlements on Arc with Turbo, and publish FX rates and exposure controls with Compose.

The first wave of stablecoins was dollar-only. The next is multi-currency. [Circle's Arc](/chains/arc) ships with [Circle StableFX](https://www.circle.com/blog/introducing-circle-stablefx-and-circle-partner-stablecoins), an institutional-grade **on-chain FX engine** - RFQ price discovery and 24/7 payment-versus-payment settlement across USDC, EURC, and regional partner stablecoins.

On-chain FX gives a treasury something it has never had: every quote and every fill, for every currency pair, as a queryable event stream. This guide turns that stream into FX analytics, a published rate oracle, and exposure controls.

## What you'll build

```mermaid theme={null}
flowchart LR
    A[Circle StableFX on Arc] -->|quotes + settlements| B[Turbo pipeline]
    B --> C[(FX warehouse)]
    C --> D[Volume, VWAP, spreads,<br/>corridor analytics]
    C --> E[Compose: publish FX rate oracle]
    C --> F[Compose: exposure / limit monitor]
```

## Step 1: Index quotes and settlements

StableFX settlements are onchain events. Decode them from Arc's `raw_logs` with the StableFX ABI, extracting the currency pair, the amounts on each leg, and the counterparties.

```yaml stablefx-settlements.yaml expandable theme={null}
name: stablefx-settlements
resource_size: s

sources:
  arc_logs:
    type: dataset
    dataset_name: arc_testnet.raw_logs
    version: 1.0.0
    start_at: latest
    filter: address = lower('0xSTABLEFX_SETTLEMENT')

transforms:
  decoded:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        _gs_log_decode(
          _gs_fetch_abi('https://raw.githubusercontent.com/your-org/abis/main/stablefx.json', 'raw'),
          topics,
          data
        ) AS decoded,
        block_timestamp,
        transaction_hash,
        _gs_op
      FROM arc_logs

  fx_settlements:
    type: sql
    primary_key: id
    sql: |
      SELECT
        id,
        decoded.event_params[1] AS base_currency,      -- e.g. USDC
        decoded.event_params[2] AS quote_currency,     -- e.g. EURC
        CAST(decoded.event_params[3] AS DECIMAL(38, 0)) AS base_amount,
        CAST(decoded.event_params[4] AS DECIMAL(38, 0)) AS quote_amount,
        decoded.event_params[5] AS maker,
        decoded.event_params[6] AS taker,
        to_timestamp(block_timestamp) AS settled_at,
        transaction_hash,
        _gs_op
      FROM decoded
      WHERE decoded IS NOT NULL
        AND decoded.event_signature = 'Settlement'

sinks:
  fx_warehouse:
    type: clickhouse
    from: fx_settlements
    table: fx_settlements
    secret_name: MY_CLICKHOUSE
    primary_key: id
```

<Warning>
  StableFX is new, and its published interface is the source of truth for event names and field order. Fetch the ABI with `_gs_fetch_abi` and confirm the `Settlement` event signature and `event_params` positions against Circle's StableFX contracts before relying on the extraction above.
</Warning>

## Step 2: Compute FX analytics

With settlements in your warehouse, the desk's core metrics are SQL:

```sql theme={null}
-- Volume-weighted rate and spread per pair, last hour
SELECT
  base_currency,
  quote_currency,
  sum(quote_amount) / sum(base_amount)              AS vwap_rate,
  count()                                            AS fills,
  sum(base_amount)                                   AS base_volume
FROM fx_settlements
WHERE settled_at > now() - INTERVAL 1 HOUR
GROUP BY base_currency, quote_currency;
```

From the same table you get **corridor flows** (net USD→EUR movement), **realized spread** versus a reference rate, and **maker/taker league tables**. Maintain a live last-rate per pair with the [PostgreSQL aggregate sink](/turbo-pipelines/sinks/postgres-aggregate).

## Step 3: Publish an FX rate oracle

Other contracts and apps need the rate you just computed. A scheduled [Compose](/compose/introduction) task reads the VWAP and publishes it on-chain - the same mechanism as the [NAV oracle](/compose/guides/build-a-nav-oracle), pointed at an FX pair.

```typescript src/tasks/publish-fx-rate.ts theme={null}
import type { TaskContext, Chain } from "compose";

// Publish to the same network your StableFX pipeline reads from (Arc). Arc
// isn't a built-in Edge RPC chain yet, so define it as a custom chain; swap for
// evm.chains.<name> once it's available on Edge. Note Arc pays gas in USDC.
const ARC_CHAIN: Chain = {
  id: 0, // Arc testnet chain ID
  name: "Arc Testnet",
  testnet: true,
  nativeCurrency: { name: "USD Coin", symbol: "USDC", decimals: 6 },
  rpcUrls: {
    default: { http: ["https://<arc-testnet-rpc>"] },
    public: { http: ["https://<arc-testnet-rpc>"] },
  },
  blockExplorers: { default: { name: "Explorer", url: "https://<explorer>" } },
};

export async function main(context: TaskContext) {
  const { fetch, evm } = context;

  const rate = await fetch<{ pair: string; vwap: number; asOf: string }>(
    "https://fx.example.com/vwap?pair=USDC-EURC",
  );

  const wallet = await evm.wallet({ name: "fx-oracle", sponsorGas: true });
  await wallet.writeContract(
    ARC_CHAIN,
    "0xFX_ORACLE",
    "updateRate(bytes32,uint256,uint64)",
    [
      "0x555344432d45555243", // "USDC-EURC"
      BigInt(Math.round(rate.vwap * 1e18)),
      BigInt(Math.floor(new Date(rate.asOf).getTime() / 1000)),
    ],
  );

  return { pair: rate.pair, vwap: rate.vwap };
}
```

## Step 4: Monitor exposure and enforce limits

FX desks live and die by position limits. Because you have every fill, net exposure per currency is a running sum - and a [Compose](/compose/introduction) task can watch it and act (alert, hedge, or halt quoting) when a limit is breached. Compose's [durable execution](/compose/introduction) guarantees the control fires, and every decision is [traced](/compose/debugging) for the desk's risk review - a live version of the [circuit-breaker pattern](/solutions/treasury-and-reserves#circuit-breaker-pattern).

## Business outcomes

* **Every quote and fill is data** - true VWAP, realized spreads, and corridor flows, not a dealer's end-of-day summary.
* **Multi-currency treasury** - real-time positions across USD, EUR, and regional stablecoins in one warehouse.
* **A rate you can publish and defend** - on-chain FX oracle sourced from actual settlements, with an audit trail.
* **Automated limit enforcement** - exposure breaches trigger controls in seconds.

## Resources

* [Circle StableFX](https://www.circle.com/blog/introducing-circle-stablefx-and-circle-partner-stablecoins)
* [Build a multi-chain NAV oracle](/compose/guides/build-a-nav-oracle)
* [ClickHouse sink](/turbo-pipelines/sinks/clickhouse)

Can't find what you're looking for? Reach out to us at [support@goldsky.com](mailto:support@goldsky.com) for help.
