Skip to main content

Installation

pnpm add @nile-markets/sdk viem
The SDK has zero runtime dependencies. viem is a peer dependency for type compatibility but is not imported by the SDK itself. You need viem (or ethers) separately to interact with the chain.

What the SDK Exports

ABIs

Typed ABI arrays for every protocol contract, ready to use with viem or wagmi:
import {
  positionManagerAbi,
  marginAccountsAbi,
  poolVaultAbi,
  oracleModuleAbi,
  configAbi,
  modeControllerAbi,
  riskManagerAbi,
  settlementEngineAbi,
  mockUsdcAbi,
} from "@nile-markets/sdk";

Constants

Enums and identifiers that match onchain values:
import {
  Side,
  Mode,
  PositionStatus,
  CloseReason,
  FeeType,
  MarginMode,
  PAIR_IDS,
  PAIRS,
  TENORS,
  getPairById,
  getPairByName,
  getTenorBySeconds,
  getTenorByLabel,
} from "@nile-markets/sdk";

// Side enum
Side.LONG   // 0 — trader profits when the base/quote rate rises
Side.SHORT  // 1 — trader profits when the base/quote rate falls

// Tenors are NOT an enum — use the bundled `TENORS` array
TENORS  // [{ seconds: 86400, label: "1D" }, { seconds: 604800, label: "1W" }, ...]
getTenorBySeconds(86400)?.label   // "1D"
getTenorByLabel("1M")?.seconds    // 2592000

// Protocol mode
Mode.NORMAL       // 0 — all operations enabled
Mode.DEGRADED     // 1 — degraded — opens blocked, closes/settlements/liquidations allowed
Mode.REDUCE_ONLY  // 2 — closes only
Mode.PAUSED       // 3 — all operations halted

// Fee type ordinals (match SettlementEngine.FeesCollected)
FeeType.LIQUIDATION_PENALTY  // 0
FeeType.EARLY_TERMINATION    // 1
FeeType.MATURITY             // 2
FeeType.TRADING              // 3

// Pair identifiers (keccak256 hashes)
PAIR_IDS.EUR_USD  // bytes32 hash of "EUR/USD"
PAIR_IDS.USD_JPY  // bytes32 hash of "USD/JPY"

// Bundled pair metadata cache (mirror of contracts/config/sepolia/defaults.json)
PAIRS  // [{ pairId, name: "EUR/USD", base: "EUR", quote: "USD", pythFeedId, ... }, ...]
getPairByName("EUR/USD")?.pairId
getPairById(PAIR_IDS.USD_JPY)?.displayDecimals  // 3
The bundled PAIRS and TENORS caches are sourced from the canonical defaults file and travel with each SDK release. For authoritative state at runtime, prefer the on-chain getters (OracleModule.getAllPairs() and Config.getEnabledTenors()) — they always reflect the live registry.

Labels

Pair-agnostic labels ship from the SDK; pair-aware side labels (e.g. "Long EUR" vs "Short USD") come from @nile-markets/shared. Side labels derive from the pair’s base currency — Pyth-native ordering, so USD/JPY"Long USD", not "Long JPY".
import {
  MODE_LABELS,
  CLOSE_REASON_LABELS,
  POSITION_STATUS_LABELS,
  FEE_TYPE_LABELS,
  MARGIN_MODE_LABELS,
  tenorLabel,
  getPairByName,
} from "@nile-markets/sdk";
import { getSideLabel } from "@nile-markets/shared";

tenorLabel(604800)                                     // "1W"
MODE_LABELS[Mode.NORMAL]                               // "Normal"
CLOSE_REASON_LABELS[CloseReason.LIQUIDATED]            // "Liquidated"
FEE_TYPE_LABELS[FeeType.TRADING]                       // "Trading Fee"
getSideLabel(getPairByName("EUR/USD"), Side.LONG)      // "Long EUR"
getSideLabel(getPairByName("USD/JPY"), Side.SHORT)     // "Short USD"

Setting Up a Client

Create a viem public client to read contract state. For write operations, you also need a wallet client.
import { createPublicClient, createWalletClient, http } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

// Read-only client
const publicClient = createPublicClient({
  chain: sepolia,
  transport: http("YOUR_RPC_URL"),
});

// Write client (for transactions)
const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY");
const walletClient = createWalletClient({
  account,
  chain: sepolia,
  transport: http("YOUR_RPC_URL"),
});
See Contract Addresses for all deployed addresses on Sepolia.

Reading Contract State

Pool State

Query the PoolVault for total deposits and share supply:
import { poolVaultAbi } from "@nile-markets/sdk";
import { formatUnits } from "viem";

const POOL_VAULT = "0x..."; // See Contract Addresses page

const [totalAssets, totalSupply, utilization] = await Promise.all([
  publicClient.readContract({
    address: POOL_VAULT,
    abi: poolVaultAbi,
    functionName: "totalAssets",
  }),
  publicClient.readContract({
    address: POOL_VAULT,
    abi: poolVaultAbi,
    functionName: "totalSupply",
  }),
  publicClient.readContract({
    address: POOL_VAULT,
    abi: poolVaultAbi,
    functionName: "riskCapacityUtilization",
  }),
]);

console.log(`Pool USDC: ${formatUnits(totalAssets, 6)}`);
console.log(`Shares outstanding: ${formatUnits(totalSupply, 6)}`);
console.log(`Share price: ${Number(totalAssets) / Number(totalSupply)}`);
console.log(`Utilization: ${Number(utilization) / 100}%`); // bps → percent

Forward Prices

Read the current EUR/USD forward price for a specific tenor. getForwardByTenor resolves the next fixing timestamp for that tenor automatically:
import { oracleModuleAbi, PAIR_IDS, getTenorByLabel } from "@nile-markets/sdk";
import { formatUnits } from "viem";

const ORACLE_MODULE = "0x..."; // See Contract Addresses page
const tenorSeconds = getTenorByLabel("1W")!.seconds; // 604800

const [fixingTimestamp, forward] = await publicClient.readContract({
  address: ORACLE_MODULE,
  abi: oracleModuleAbi,
  functionName: "getForwardByTenor",
  args: [PAIR_IDS.EUR_USD, tenorSeconds],
});

console.log(`EUR/USD 1W forward: ${formatUnits(forward.forwardPrice, 18)}`);
console.log(`Maturity: ${new Date(Number(fixingTimestamp) * 1000).toISOString()}`);
console.log(`Published at: ${new Date(Number(forward.publishTimestamp) * 1000).toISOString()}`);

Protocol Mode

Check the current operating mode before submitting transactions:
import { modeControllerAbi, Mode } from "@nile-markets/sdk";

const MODE_CONTROLLER = "0x..."; // See Contract Addresses page

const mode = await publicClient.readContract({
  address: MODE_CONTROLLER,
  abi: modeControllerAbi,
  functionName: "currentMode",
});

if (mode !== Mode.NORMAL) {
  console.warn("Protocol is not in NORMAL mode — new positions are disabled");
}

Margin Account Balance

Read a trader’s deposited collateral and available balance:
import { marginAccountsAbi } from "@nile-markets/sdk";
import { formatUnits } from "viem";

const MARGIN_ACCOUNTS = "0x..."; // See Contract Addresses page
const trader = "0xYOUR_ADDRESS";

const [collateral, locked, available] = await Promise.all([
  publicClient.readContract({
    address: MARGIN_ACCOUNTS,
    abi: marginAccountsAbi,
    functionName: "collateralBalance",
    args: [trader],
  }),
  publicClient.readContract({
    address: MARGIN_ACCOUNTS,
    abi: marginAccountsAbi,
    functionName: "imLockedTotal",
    args: [trader],
  }),
  publicClient.readContract({
    address: MARGIN_ACCOUNTS,
    abi: marginAccountsAbi,
    functionName: "availableBalance",
    args: [trader],
  }),
]);

console.log(`Total collateral: ${formatUnits(collateral, 6)} USDC`);
console.log(`Locked in positions: ${formatUnits(locked, 6)} USDC`);
console.log(`Available to trade: ${formatUnits(available, 6)} USDC`);

Writing Transactions

Approve USDC and Open a Position

Opening a position requires two transactions: approve USDC spending, then call openPosition.
import {
  positionManagerAbi,
  mockUsdcAbi,
  Side,
  PAIR_IDS,
  getTenorByLabel,
} from "@nile-markets/sdk";
import { parseUnits } from "viem";

const USDC = "0x...";              // See Contract Addresses page
const POSITION_MANAGER = "0x...";  // See Contract Addresses page

const notional = parseUnits("10000", 6);  // 10,000 USDC notional
const margin = parseUnits("500", 6);      // 500 USDC margin
const tenorSeconds = getTenorByLabel("1W")!.seconds; // 604800

// Step 1: Approve PositionManager to spend your USDC
const approveHash = await walletClient.writeContract({
  address: USDC,
  abi: mockUsdcAbi,
  functionName: "approve",
  args: [POSITION_MANAGER, margin],
});

// Wait for approval to confirm
await publicClient.waitForTransactionReceipt({ hash: approveHash });

// Step 2: Open the position
const openHash = await walletClient.writeContract({
  address: POSITION_MANAGER,
  abi: positionManagerAbi,
  functionName: "openPosition",
  args: [
    {
      pairId: PAIR_IDS.EUR_USD,
      side: Side.LONG,
      notional,
      tenorSeconds,
      margin,
    },
  ],
});

const receipt = await publicClient.waitForTransactionReceipt({ hash: openHash });
console.log(`Position opened in tx: ${receipt.transactionHash}`);

Deposit Margin

Deposit USDC collateral into your MarginAccounts balance:
import { marginAccountsAbi, mockUsdcAbi } from "@nile-markets/sdk";
import { parseUnits } from "viem";

const USDC = "0x...";
const MARGIN_ACCOUNTS = "0x...";

const amount = parseUnits("1000", 6); // 1,000 USDC

// Approve MarginAccounts to pull USDC
const approveHash = await walletClient.writeContract({
  address: USDC,
  abi: mockUsdcAbi,
  functionName: "approve",
  args: [MARGIN_ACCOUNTS, amount],
});
await publicClient.waitForTransactionReceipt({ hash: approveHash });

// Deposit
const depositHash = await walletClient.writeContract({
  address: MARGIN_ACCOUNTS,
  abi: marginAccountsAbi,
  functionName: "deposit",
  args: [amount],
});
await publicClient.waitForTransactionReceipt({ hash: depositHash });

Numeric Precision

All onchain values use fixed-point integers. You need to convert between human-readable numbers and raw contract values.
Value TypeDecimalsRaw ExampleHuman-Readable
USDC amounts (notional, margin, fees)61000000000010,000.00 USDC
Prices (spot, forward, strike)1810800000000000000001.08000 EUR/USD
Basis points (fees, utilization)02002.00%
Use viem’s parseUnits and formatUnits for conversion:
import { parseUnits, formatUnits } from "viem";

// Human → contract
const usdcAmount = parseUnits("500.00", 6);    // 500000000n
const price = parseUnits("1.08500", 18);        // 1085000000000000000n

// Contract → human
const humanUsdc = formatUnits(500000000n, 6);   // "500.0"
const humanPrice = formatUnits(1085000000000000000n, 18); // "1.085"
Never use floating-point arithmetic with contract values. Always operate on bigint values and convert to human-readable strings only for display.

Using with wagmi

If you are building a React frontend with wagmi, the SDK ABIs work directly with wagmi hooks:
import { useReadContract, useWriteContract } from "wagmi";
import { poolVaultAbi } from "@nile-markets/sdk";

function PoolStats({ vaultAddress }: { vaultAddress: `0x${string}` }) {
  const { data: totalAssets } = useReadContract({
    address: vaultAddress,
    abi: poolVaultAbi,
    functionName: "totalAssets",
  });

  return <div>Pool: {totalAssets ? formatUnits(totalAssets, 6) : "..."} USDC</div>;
}

Contract Addresses

Sepolia deployment addresses for all contracts.

Types Reference

Full enum and struct definitions used across the protocol.

Quick Start

End-to-end setup from zero to querying protocol state.

Open a Position

Step-by-step tutorial for your first trade.