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

# CurrencyRegistry

> The owner-managed table that decides which ERC-20 stands in for each currency, holds the active oracle and deploys synthetic token and vault pairs.

`CurrencyRegistry` answers one question for the factory and the router: for currency code `X`, which token is the quote asset, how many decimals does it have, is it a real stablecoin or a synthetic, where is its vault, and may new launches use it. Changes only affect launches created afterwards; an existing curve keeps its quote asset forever.

Source: `contracts/src/CurrencyRegistry.sol` (`Ownable2Step`). Interface: `contracts/src/interfaces/ICurrencyRegistry.sol`. Addresses: [Addresses](/protocol/addresses).

## Configuration record

```solidity theme={"system"}
struct CurrencyConfig {
    address quoteAsset;   // ERC-20 curves for this code are quoted in
    uint8   quoteDecimals; // read from the token at registration
    uint8   tier;          // 1 = real fiat-backed stablecoin, 2 = synthetic backed by an FxVault
    address vault;         // FxVault for tier 2, zero otherwise
    bool    enabled;       // new launches allowed
}
```

The constructor registers `"USD"` (the constant `USD = "USD"`) as tier 1 with `usdc` as its quote asset and records the oracle. `usdc` is immutable; the oracle can be replaced.

## Views

| Function                       | Returns          | Notes                                                                                                                                                      |
| ------------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get(bytes32 code)`            | `CurrencyConfig` | Zeroed struct for an unknown code (`quoteAsset == 0x0`).                                                                                                   |
| `requireEnabled(bytes32 code)` | `CurrencyConfig` | Reverts `UnknownCurrency(code)` or `CurrencyDisabled(code)`. The factory calls this on every `createLaunch`.                                               |
| `allCodes()`                   | `bytes32[]`      | Every registered code in registration order: `USD` first, then EUR when registered, then the synthetics in the order of `contracts/script/Currencies.sol`. |
| `codeOfAsset(address asset)`   | `bytes32`        | Reverse lookup from a quote asset (dollar, EURC or a `SynthToken`) to its code; zero for unknown assets.                                                   |
| `oracle()`                     | `IFxOracle`      | The oracle the factory sizes curves with and the router pays update fees to.                                                                               |
| `usdc()`                       | `address`        | The settlement dollar (USDG on Robinhood Chain, USDC on Arc).                                                                                              |
| `USD()`                        | `bytes32`        | The constant `"USD"`.                                                                                                                                      |

```solidity theme={"system"}
function get(bytes32 code) external view returns (CurrencyConfig memory);
function requireEnabled(bytes32 code) external view returns (CurrencyConfig memory);
function allCodes() external view returns (bytes32[] memory);
function codeOfAsset(address asset) external view returns (bytes32);
function oracle() external view returns (IFxOracle);
function usdc() external view returns (address);
```

Codes are ASCII right-padded to 32 bytes: `"INR"` is `0x494e520000000000000000000000000000000000000000000000000000000000`. In viem: `stringToHex("INR", { size: 32 })`; with Foundry: `cast --format-bytes32-string INR`.

## Owner functions

```solidity theme={"system"}
function setOracle(IFxOracle oracle_) external onlyOwner;
function setRealCurrency(bytes32 code, address asset, bool enabled) external onlyOwner;
function createSynthetic(bytes32 code, string calldata name, string calldata symbol, FxVault.Params calldata p)
    external onlyOwner returns (address synth, address vault);
function setEnabled(bytes32 code, bool enabled) external onlyOwner;
```

**`setOracle`** replaces the registry's oracle (`ZeroAddress` for `0x0`). It changes what the factory uses to size new curves and what the router charges as an update fee. It does not change the oracle of any existing vault: each `FxVault` stores its oracle as an immutable at creation. See [Oracles](/protocol/contracts/oracles).

**`setRealCurrency`** registers, or re-registers, a tier-1 stablecoin for a code. The oracle must support the code (`NotSupportedByOracle`), the asset must be non-zero and have at most 18 decimals (`DecimalsTooHigh`). Calling it for a code that already exists overwrites the record: this is how a currency moves from tier 2 to tier 1 when a real stablecoin lands, without touching launches already quoted in the synthetic. The reverse lookup for the new asset is set; the old synthetic keeps its own reverse entry.

**`createSynthetic`** deploys a `SynthToken(name, symbol)` and an `FxVault` for the code, wires them (`setVault`), registers the pair as tier 2 with 18 decimals and enables it. The vault is owned by the registry's current owner, uses the registry's dollar and current oracle, and is named `"<name> Underwriter"` / `"uw-<symbol>"` for its share token. Reverts `NotSupportedByOracle(code)` if the oracle does not list the code and `AlreadyExists(code)` if the code already has a quote asset. A code can therefore never get a second synthetic.

**`setEnabled`** toggles whether new launches may use a code (`UnknownCurrency` for unregistered codes). Disabling a currency stops `createLaunch` for it; trading, minting and redeeming continue.

The owner cannot remove a code, change a synthetic's vault, or edit a vault's parameters from here (vault parameters are set on the vault itself; the registry owner is also each vault's owner at creation).

## Events

| Event                                                                                            | Emitted by                                                            |
| ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| `OracleSet(address oracle)`                                                                      | `setOracle`                                                           |
| `CurrencySet(bytes32 indexed code, address quoteAsset, uint8 tier, address vault, bool enabled)` | constructor (USD), `setRealCurrency`, `createSynthetic`, `setEnabled` |
| `SyntheticCreated(bytes32 indexed code, address synth, address vault)`                           | `createSynthetic` (before the matching `CurrencySet`)                 |

The indexer seeds its `currency` table from `allCodes()` + `get(code)` and keeps it in sync from `CurrencySet` (see [Currencies query](/api/queries/currencies)).

## Errors

| Error                                | When                                                                                 |
| ------------------------------------ | ------------------------------------------------------------------------------------ |
| `NotSupportedByOracle(bytes32 code)` | `setRealCurrency` or `createSynthetic` for a code the oracle's `isSupported` rejects |
| `DecimalsTooHigh()`                  | `setRealCurrency` with a token above 18 decimals                                     |
| `UnknownCurrency(bytes32 code)`      | `requireEnabled` or `setEnabled` for an unregistered code                            |
| `CurrencyDisabled(bytes32 code)`     | `requireEnabled` for a disabled code                                                 |
| `AlreadyExists(bytes32 code)`        | `createSynthetic` for a code that already has a quote asset                          |
| `ZeroAddress()`                      | zero oracle, dollar or asset                                                         |

## How tiers are decided

Tier is a property of the registration, not of the currency. The deploy script registers `USD` (always), `EUR` when the chain has EURC and the script is told about it, and the sixty synthetics from `Currencies.sol` with the vault parameters in [FxVault](/protocol/contracts/fx-vault). Tier 1 with `quoteAsset == usdc()` is the USD case the router and factory special-case; tier 1 with any other asset (EURC) can only be traded and launched with that asset directly; tier 2 goes through the vault. Any ISO currency that is not registered can still be a display currency in the app, converted off-chain.

## Reading the registry

<CodeGroup>
  ```ts viem theme={"system"}
  import { createPublicClient, http, stringToHex, hexToString } from "viem";
  import { currencyRegistryAbi } from "@launchpad/abis";

  const registry = "0xc3A0ac0eF92392A37287AfE99D401679BA00988a"; // Robinhood Chain
  const client = createPublicClient({ transport: http("https://rpc.mainnet.chain.robinhood.com") });

  const codes = await client.readContract({ address: registry, abi: currencyRegistryAbi, functionName: "allCodes" });
  for (const code of codes) {
    const c = await client.readContract({ address: registry, abi: currencyRegistryAbi, functionName: "get", args: [code] });
    console.log(hexToString(code, { size: 32 }), c.tier === 1 ? "real" : "synthetic", c.quoteAsset, c.vault, c.enabled);
  }

  const inr = await client.readContract({
    address: registry, abi: currencyRegistryAbi, functionName: "requireEnabled", args: [stringToHex("INR", { size: 32 })],
  });
  ```

  ```bash cast theme={"system"}
  REGISTRY=0xc3A0ac0eF92392A37287AfE99D401679BA00988a
  RPC=https://rpc.mainnet.chain.robinhood.com
  cast call $REGISTRY "allCodes()(bytes32[])" --rpc-url $RPC
  cast call $REGISTRY "get(bytes32)((address,uint8,uint8,address,bool))" $(cast --format-bytes32-string INR) --rpc-url $RPC
  cast call $REGISTRY "oracle()(address)" --rpc-url $RPC
  ```
</CodeGroup>
