Skip to main content
The same protocol is deployed on both testnets with identical contract bytecode and identical parameters. Switch tabs to view per-chain addresses.
chainId: 11155111 · Block explorer: sepolia.etherscan.io
ContractAddressDescription
Protocol0xC20E775e274314570DAa1d50a191750753F686dARoot registry for all contract addresses and role assignments
Config0x8653f27FB34bF845c7C16Dca07DFf7A0cd2A6557Configurable parameters (margin factors, fee rates, tenors, risk caps)
OracleModule0x1f78995b606CCF768E0a532Be8A1AB6eDf3Cd4D5Pyth spot prices + publisher-pushed forward prices with safeguards
ModeController0x2b7775De7a4696f05D875BDE8f983303A3d49582Operating mode state machine (NORMAL / DEGRADED / REDUCE_ONLY / PAUSED)
RiskManager0xd82515b62501F011DA2BaFA877B57f8713dc20EePer-position, per-account, and pool-level exposure caps and validation
MarginAccounts0x12c310A5A5B5771459ff96979931CddE75A6D935Collateral deposits, margin locking, and PnL settlement
PoolVault0x61208f0337eE40278b0cbc55daEE7A37Fa180618ERC-4626 liquidity pool for LP deposits and share token management
PositionManager0x8d81E9f1D0F0d1BdAAc0a91AFc2976522429A0cdPosition lifecycle: open, increase, reduce, close, margin management
SettlementEngine0x86f9339EC3Ca09aa51E1862911678C696eC09470Settlement at maturity, liquidation, and early termination processing
MockUSDC0x319FeC6Cc374922A183A354a41E89b7A313EE547Test USDC token (6 decimals) with public mint faucet function
Start block: 10391511 — used as the startBlock in the subgraph and event scanning.
Bytecode is identical on both chains. Only the addresses, the Pyth contract that OracleModule wraps, and the per-chain startBlock differ.

External Dependencies

The protocol integrates with external infrastructure that is not operated by the Nile Markets team. Pyth Network is deployed on every chain at a chain-specific address; price feed IDs are global.
DependencyIdentifierPurpose
Pyth EUR/USD Price Feed0xa995d00bb36a63cef7fd2c287dc105fc8f3d93779f062f09551b0af3e81ec30bReal-time EUR/USD spot price used for fixing prices and oracle health checks
Pyth USD/JPY Price Feed0xef2c98c804ba503c6a707e38be4dfbb16683775f195b091252bf24693042fd52Real-time USD/JPY spot price
Pyth Networkpyth.networkDecentralized oracle network providing the underlying price feed (per-chain contract address — see Pyth EVM addresses)
The Pyth price feed IDs are bytes32 identifiers, not Ethereum addresses. They are used by the publisher service to fetch prices from the Pyth Hermes API and by the OracleModule to verify onchain price updates. The same feed IDs are used on every supported chain.

Block Explorer

Contracts are verified on each chain’s block explorer.
To view any contract, use the appropriate explorer base URL:
  • Ethereum Sepolia: https://sepolia.etherscan.io/address/<CONTRACT_ADDRESS>
  • Arbitrum Sepolia: https://sepolia.arbiscan.io/address/<CONTRACT_ADDRESS>
For example, the Sepolia PositionManager: https://sepolia.etherscan.io/address/0x8d81E9f1D0F0d1BdAAc0a91AFc2976522429A0cd

Contract Architecture

The contracts are organized into shared infrastructure (singletons) and per-pool instances:
Protocol (Root Registry)
+-- Shared Infrastructure
|   +-- Config             -- All configurable parameters
|   +-- OracleModule       -- Pyth spot + publisher-pushed forwards
|   +-- ModeController     -- Operating mode state machine
|   +-- RiskManager        -- Risk caps and validation
+-- Pool 0 (USDC)
    +-- MarginAccounts     -- Collateral and margin management
    +-- PoolVault          -- ERC-4626 liquidity pool
    +-- PositionManager    -- Position lifecycle
    +-- SettlementEngine   -- Settlement, liquidation, termination
All contracts are non-upgradeable (no proxy pattern). Parameters are configurable via the Config contract, but contract logic is immutable. Logic changes require a full redeployment.

Loading Addresses in Code

TypeScript (Frontend / SDK)

The frontend loads addresses from the deployment JSON file via the getAddress helper, which resolves the correct address based on the connected chain ID:
import { getAddress } from "@/config/addresses";
import { useChainId } from "wagmi";
import { positionManagerAbi } from "@nile-markets/sdk";

const chainId = useChainId();
const positionManager = getAddress(chainId, "positionManager");
Address resolution by chain:
  • Chain ID 11155111 (Ethereum Sepolia) loads from deployments/sepolia/addresses.json
  • Chain ID 421614 (Arbitrum Sepolia) loads from deployments/arbitrumSepolia/addresses.json
  • Chain ID 31337 (Anvil) loads from deployments/anvil/addresses.json

TypeScript (Standalone / Node.js)

For scripts or services that do not use wagmi, reference addresses directly:
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import { poolVaultAbi } from "@nile-markets/sdk";

const client = createPublicClient({
  chain: sepolia,
  transport: http("YOUR_RPC_URL"),
});

const POOL_VAULT = "0x61208f0337eE40278b0cbc55daEE7A37Fa180618";

const totalAssets = await client.readContract({
  address: POOL_VAULT,
  abi: poolVaultAbi,
  functionName: "totalAssets",
});

Rust (Keeper / Publisher)

Rust services receive contract addresses via environment variables or CLI arguments:
use alloy::primitives::Address;
use fx_contracts::generated::settlement_engine::SettlementEngine;

let address: Address = std::env::var("SETTLEMENT_ENGINE_ADDRESS")
    .expect("SETTLEMENT_ENGINE_ADDRESS required")
    .parse()
    .expect("Invalid address");

let contract = SettlementEngine::new(address, &provider);
For the EUR/USD pair ID constant (pre-computed keccak256("EUR/USD")):
use fx_contracts::EUR_USD_PAIR_ID;
let pair_id = EUR_USD_PAIR_ID;

Subgraph

The subgraph references contract addresses in subgraph.yaml data sources:
dataSources:
  - kind: ethereum
    name: PositionManager
    source:
      address: "0x8d81E9f1D0F0d1BdAAc0a91AFc2976522429A0cd"
      abi: PositionManager
      startBlock: 10391511
    mapping:
      # ...handler configuration

Deployment JSON

The canonical source of truth for addresses is the deployment JSON file generated by the deploy script:
{
  "config": "0x8653f27FB34bF845c7C16Dca07DFf7A0cd2A6557",
  "marginAccounts": "0x12c310A5A5B5771459ff96979931CddE75A6D935",
  "modeController": "0x2b7775De7a4696f05D875BDE8f983303A3d49582",
  "oracleModule": "0x1f78995b606CCF768E0a532Be8A1AB6eDf3Cd4D5",
  "poolVault": "0x61208f0337eE40278b0cbc55daEE7A37Fa180618",
  "positionManager": "0x8d81E9f1D0F0d1BdAAc0a91AFc2976522429A0cd",
  "protocol": "0xC20E775e274314570DAa1d50a191750753F686dA",
  "riskManager": "0xd82515b62501F011DA2BaFA877B57f8713dc20Ee",
  "settlementEngine": "0x86f9339EC3Ca09aa51E1862911678C696eC09470",
  "startBlock": 10391511,
  "usdc": "0x319FeC6Cc374922A183A354a41E89b7A313EE547"
}
Contract addresses should never be stored in environment variables. They are always loaded from the deployment JSON file, which is generated by the deploy script and committed to the repository. This ensures a single source of truth that stays in sync across all consumers (frontend, subgraph, tests).