DomFiDomFi

Integration Guide

Architecture, trade lifecycle, and code examples for integrating with DomFi contracts on Base L2.

Network

PropertyValue
ChainBase Mainnet
Chain ID8453
CollateralUSDC (6 decimals)
Price precision18 decimals
Leverage precision2 decimals (100 = 1x)

Architecture

16 contracts on Base. Every contract discovers its dependencies through DomfiRegistry, a central lookup table. Integrators only need the registry address. Call registry.getContractAddress("DomfiTrading") to find everything else.

The system follows a request-fulfill pattern. When a user opens or closes a trade, the protocol queues a price request and waits for an off-chain bot to submit a signed price. Two-step flow ensures price integrity.

TRADE EXECUTION FLOW
TRADER
ON-CHAIN
OFF-CHAIN
01SUBMIT ORDERTRADERMarketOpenOrderInitiated / MarketCloseOrderInitiated
openTrade() / closeTradeMarket() → DomfiTrading
Trader calls openTrade() or closeTradeMarket() on DomfiTrading. Opens require pair index, collateral, leverage, direction, and USDC approval to DomfiTradingStorage. Closes require pair index, position index, and close percentage. Both require ETH oracle fee as msg.value.

Automated Actions

The bot infrastructure handles liquidations, stop-loss, and take-profit execution through DomfiTradesUpKeep. No user interaction required.

Code Examples

Reading Oracle Prices

// script/ReadPrice.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script, console} from "forge-std/Script.sol";
import {IDomfiRegistry} from "../src/interfaces/IDomfiRegistry.sol";
import {IDomfiOracle} from "../src/interfaces/IDomfiOracle.sol";

contract ReadPrice is Script {
    address constant REGISTRY = 0x...; // DomfiRegistry on Base

    function run() external view {
        IDomfiRegistry registry = IDomfiRegistry(REGISTRY);
        IDomfiOracle oracle = IDomfiOracle(
            registry.getContractAddress("DomfiOracle")
        );

        uint256 btcdomPrice = oracle.getPrice(0); // pair index 0 = BTCDOM
        console.log("BTCDOM price:", btcdomPrice);
    }
}
forge script script/ReadPrice.s.sol --rpc-url base
import { ethers } from "hardhat";

const REGISTRY_ADDRESS = "0x..."; // DomfiRegistry on Base

const registry = await ethers.getContractAt("IDomfiRegistry", REGISTRY_ADDRESS);
const oracleAddr = await registry.getContractAddress("DomfiOracle");
const oracle = await ethers.getContractAt("IDomfiOracle", oracleAddr);

const btcdomPrice = await oracle.getPrice(0); // pair index 0 = BTCDOM
console.log("BTCDOM price:", btcdomPrice);
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';

const client = createPublicClient({ chain: base, transport: http() });

const price = await client.readContract({
  address: ORACLE_ADDRESS,
  abi: domfiOracleAbi,
  functionName: 'getPrice',
  args: [0n], // BTCDOM
});
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://mainnet.base.org"))

registry = w3.eth.contract(address=REGISTRY_ADDRESS, abi=registry_abi)
oracle_addr = registry.functions.getContractAddress("DomfiOracle").call()
oracle = w3.eth.contract(address=oracle_addr, abi=oracle_abi)

btcdom_price = oracle.functions.getPrice(0).call()  # pair index 0 = BTCDOM
print(f"BTCDOM price: {btcdom_price}")

Opening a Trade

// script/OpenTrade.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDomfiRegistry} from "../src/interfaces/IDomfiRegistry.sol";
import {IDomfiTrading} from "../src/interfaces/IDomfiTrading.sol";

contract OpenTrade is Script {
    address constant REGISTRY = 0x...; // DomfiRegistry on Base
    address constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;

    function run() external {
        vm.startBroadcast();

        IDomfiRegistry registry = IDomfiRegistry(REGISTRY);
        IDomfiTrading trading = IDomfiTrading(
            registry.getContractAddress("DomfiTrading")
        );
        address tradingStorage = registry.getContractAddress("DomfiTradingStorage");

        // Approve USDC to TradingStorage (not Trading)
        IERC20(USDC).approve(tradingStorage, 100e6);

        // Open a 50x long on BTCDOM
        trading.openTrade{value: 0.00003 ether}(
            IDomfiTrading.Trade({
                pairIndex: 0,        // BTCDOM
                collateral: 100e6,   // 100 USDC (6 decimals)
                leverage: 5000,      // 50x (2 decimal precision)
                buy: true,           // long
                tp: 0,               // no take profit
                sl: 0                // no stop loss
            }),
            0,  // market order
            50  // 0.5% slippage
        );

        vm.stopBroadcast();
    }
}
forge script script/OpenTrade.s.sol --rpc-url base --broadcast
import { ethers } from "hardhat";

const REGISTRY_ADDRESS = "0x..."; // DomfiRegistry on Base
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

const [signer] = await ethers.getSigners();
const registry = await ethers.getContractAt("IDomfiRegistry", REGISTRY_ADDRESS);
const tradingAddr = await registry.getContractAddress("DomfiTrading");
const tradingStorageAddr = await registry.getContractAddress("DomfiTradingStorage");

const usdc = await ethers.getContractAt("IERC20", USDC);
const trading = await ethers.getContractAt("IDomfiTrading", tradingAddr);

// Approve USDC to TradingStorage (not Trading)
await usdc.approve(tradingStorageAddr, ethers.parseUnits("100", 6));

// Open a 50x long on BTCDOM
await trading.openTrade(
  {
    trader: signer.address,
    pairIndex: 0,
    index: 0,
    collateral: ethers.parseUnits("100", 6),
    openPrice: 0n,
    buy: true,
    leverage: 5000,
    tp: 0n,
    sl: 0n,
  },
  0,  // market order
  50, // 0.5% slippage
  { value: ethers.parseEther("0.00003") }, // oracle fee
);
import { createWalletClient, http, parseUnits, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http('https://mainnet.base.org'),
});

// Approve USDC to TradingStorage (not Trading)
await walletClient.writeContract({
  address: USDC_ADDRESS,
  abi: erc20Abi,
  functionName: 'approve',
  args: [TRADING_STORAGE_ADDRESS, parseUnits('100', 6)],
});

// Open a 50x long on BTCDOM
await walletClient.writeContract({
  address: TRADING_ADDRESS,
  abi: tradingAbi,
  functionName: 'openTrade',
  args: [
    {
      trader: account.address,
      pairIndex: 0,
      index: 0,
      collateral: parseUnits('100', 6),
      openPrice: 0n,
      buy: true,
      leverage: 5000,
      tp: 0n,
      sl: 0n,
    },
    0,  // market order
    50, // 0.5% slippage
  ],
  value: parseEther('0.00003'), // oracle fee
});
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://mainnet.base.org"))
account = w3.eth.account.from_key("0xYOUR_PRIVATE_KEY")

registry = w3.eth.contract(address=REGISTRY_ADDRESS, abi=registry_abi)
trading_addr = registry.functions.getContractAddress("DomfiTrading").call()
storage_addr = registry.functions.getContractAddress("DomfiTradingStorage").call()

usdc = w3.eth.contract(address=USDC_ADDRESS, abi=erc20_abi)
trading = w3.eth.contract(address=trading_addr, abi=trading_abi)

# Approve USDC to TradingStorage (not Trading)
approve_tx = usdc.functions.approve(
    storage_addr, 100 * 10**6
).build_transaction({"from": account.address, "nonce": w3.eth.get_transaction_count(account.address)})
w3.eth.send_raw_transaction(w3.eth.account.sign_transaction(approve_tx, account.key).raw_transaction)

# Open a 50x long on BTCDOM
trade_tx = trading.functions.openTrade(
    (
        account.address,  # trader
        0,                # pairIndex — BTCDOM
        0,                # index
        100 * 10**6,      # collateral — 100 USDC
        0,                # openPrice
        True,             # buy — long
        5000,             # leverage — 50x
        0,                # tp
        0,                # sl
    ),
    0,   # market order
    50,  # 0.5% slippage
).build_transaction({
    "from": account.address,
    "value": w3.to_wei(0.00003, "ether"),  # oracle fee
    "nonce": w3.eth.get_transaction_count(account.address),
})
w3.eth.send_raw_transaction(w3.eth.account.sign_transaction(trade_tx, account.key).raw_transaction)

Vault Deposit (ERC-4626)

// script/VaultDeposit.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDomfiRegistry} from "../src/interfaces/IDomfiRegistry.sol";
import {IDomfiVault} from "../src/interfaces/IDomfiVault.sol";

contract VaultDeposit is Script {
    address constant REGISTRY = 0x...; // DomfiRegistry on Base
    address constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;

    function run() external {
        vm.startBroadcast();

        IDomfiRegistry registry = IDomfiRegistry(REGISTRY);
        IDomfiVault vault = IDomfiVault(
            registry.getContractAddress("DomfiVault")
        );

        uint256 amount = 1000e6; // 1000 USDC

        IERC20(USDC).approve(address(vault), amount);
        uint256 shares = vault.deposit(amount, msg.sender);

        vm.stopBroadcast();
    }
}
forge script script/VaultDeposit.s.sol --rpc-url base --broadcast
import { ethers } from "hardhat";

const REGISTRY_ADDRESS = "0x..."; // DomfiRegistry on Base
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

const [signer] = await ethers.getSigners();
const registry = await ethers.getContractAt("IDomfiRegistry", REGISTRY_ADDRESS);
const vaultAddr = await registry.getContractAddress("DomfiVault");

const usdc = await ethers.getContractAt("IERC20", USDC);
const vault = await ethers.getContractAt("IDomfiVault", vaultAddr);

const amount = ethers.parseUnits("1000", 6); // 1000 USDC

// Approve USDC to Vault
await usdc.approve(vaultAddr, amount);

// Deposit
const shares = await vault.deposit(amount, signer.address);
console.log("Shares received:", shares);
import { createWalletClient, http, parseUnits } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http('https://mainnet.base.org'),
});

// Approve USDC to Vault
await walletClient.writeContract({
  address: USDC_ADDRESS,
  abi: erc20Abi,
  functionName: 'approve',
  args: [VAULT_ADDRESS, parseUnits('1000', 6)],
});

// Deposit 1000 USDC
await walletClient.writeContract({
  address: VAULT_ADDRESS,
  abi: vaultAbi,
  functionName: 'deposit',
  args: [parseUnits('1000', 6), account.address],
});
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://mainnet.base.org"))
account = w3.eth.account.from_key("0xYOUR_PRIVATE_KEY")

registry = w3.eth.contract(address=REGISTRY_ADDRESS, abi=registry_abi)
vault_addr = registry.functions.getContractAddress("DomfiVault").call()

usdc = w3.eth.contract(address=USDC_ADDRESS, abi=erc20_abi)
vault = w3.eth.contract(address=vault_addr, abi=vault_abi)

amount = 1000 * 10**6  # 1000 USDC

# Approve USDC to Vault
approve_tx = usdc.functions.approve(
    vault_addr, amount
).build_transaction({"from": account.address, "nonce": w3.eth.get_transaction_count(account.address)})
w3.eth.send_raw_transaction(w3.eth.account.sign_transaction(approve_tx, account.key).raw_transaction)

# Deposit
deposit_tx = vault.functions.deposit(
    amount, account.address
).build_transaction({"from": account.address, "nonce": w3.eth.get_transaction_count(account.address)})
w3.eth.send_raw_transaction(w3.eth.account.sign_transaction(deposit_tx, account.key).raw_transaction)

These examples show the pattern. Verify exact function signatures against deployed contract ABIs on BaseScan.

Pair Indices

IndexPairMax LeverageMaker FeeTaker Fee
0BTCDOM250x0.05%0.1%
1ETHDOM250x0.05%0.1%
2USDTDOM250x0.05%0.1%
3BNBDOM250x0.05%0.1%
4SOLDOM250x0.05%0.1%

Key Events

The 5 events most integrations listen for. See Contract Reference — Events for the full listing across all contracts.

EventContractMeaning
MarketOpenOrderInitiatedDomfiTradingUser submitted open order
MarketOpenExecutedDomfiTradingCallbacksTrade opened successfully
MarketCloseOrderInitiatedDomfiTradingUser submitted close order
MarketCloseExecutedDomfiTradingCallbacksTrade closed, PnL settled
MarketOpenCanceledDomfiTradingCallbacksOpen order rejected (slippage, OI cap, etc.)

Error Handling

See Contract Reference — Errors for the full error listing across all contracts.

Common Reverts

ErrorContractCauseResolution
InsufficientAllowanceDomfiTradingStorageUSDC not approved or approved to wrong addressApprove USDC to DomfiTradingStorage, not DomfiTrading
MaxOpenInterestExceededDomfiPairInfosTrade would exceed $10M OI cap per direction per pairReduce position size or wait for OI to decrease
SlippageExceededDomfiTradingCallbacksOracle price moved beyond your slippage tolerance between submission and fulfillmentIncrease slippage parameter or retry
InsufficientCollateralDomfiTradingCallbacksCollateral too low after fees for the requested leverageIncrease collateral or reduce leverage
MaxLeverageExceededDomfiTradingLeverage exceeds 250x (25000 in 2-decimal precision)Use leverage value ≤ 25000
TpTooHighDomfiTradingTake-profit exceeds 900% capSet TP ≤ 900% of collateral
OraclePriceStaleDomfiPriceRouterOracle price too old for executionRetry — the publisher bot will submit a fresh price
InsufficientValueDomfiTradingETH sent as msg.value is less than the oracle feeSend at least ~0.00003 ETH as value

Handling Asynchronous Execution

The request-fulfill pattern means openTrade() does not return trade data directly. To confirm execution:

  1. Watch events: Listen for MarketOpenExecuted from DomfiTradingCallbacks filtered by your trader address.
  2. Poll storage: Call DomfiTradingStorage.openTrades(trader, index) to check if the position exists.
  3. Handle failure: If the oracle bot rejects the price or the callback reverts, MarketOpenCanceled is emitted instead. Your collateral is returned automatically.

Foundry scripts are one-shot — use cast to poll or watch events from the CLI:

# Poll for MarketOpenExecuted events
cast logs \
  --from-block latest \
  --address $TRADING_CALLBACKS_ADDRESS \
  "MarketOpenExecuted(address,uint256,uint256)" \
  --rpc-url base

# Check if position exists
cast call $TRADING_STORAGE_ADDRESS \
  "openTrades(address,uint256)(tuple)" \
  $TRADER_ADDRESS 0 \
  --rpc-url base
import { ethers } from "hardhat";

const callbacks = await ethers.getContractAt(
  "IDomfiTradingCallbacks",
  TRADING_CALLBACKS_ADDRESS,
);

// Watch for trade execution
callbacks.on(
  callbacks.filters.MarketOpenExecuted(signer.address),
  (trader, pairIndex, index, event) => {
    console.log("Trade executed:", { trader, pairIndex, index });
    callbacks.removeAllListeners("MarketOpenExecuted");
  },
);
// Watch for trade execution
const unwatch = client.watchContractEvent({
  address: TRADING_CALLBACKS_ADDRESS,
  abi: tradingCallbacksAbi,
  eventName: 'MarketOpenExecuted',
  args: { trader: account.address },
  onLogs: (logs) => {
    console.log('Trade executed:', logs[0].args);
    unwatch();
  },
});
import time

callbacks = w3.eth.contract(
    address=TRADING_CALLBACKS_ADDRESS, abi=callbacks_abi
)

# Poll for MarketOpenExecuted events
event_filter = callbacks.events.MarketOpenExecuted.create_filter(
    from_block="latest",
    argument_filters={"trader": account.address},
)

while True:
    entries = event_filter.get_new_entries()
    if entries:
        print(f"Trade executed: {entries[0].args}")
        break
    time.sleep(2)

See Also