Integration Guide
Architecture, trade lifecycle, and code examples for integrating with DomFi contracts on Base L2.
Network
| Property | Value |
|---|---|
| Chain | Base Mainnet |
| Chain ID | 8453 |
| Collateral | USDC (6 decimals) |
| Price precision | 18 decimals |
| Leverage precision | 2 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.
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 baseimport { 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 --broadcastimport { 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 --broadcastimport { 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
| Index | Pair | Max Leverage | Maker Fee | Taker Fee |
|---|---|---|---|---|
| 0 | BTCDOM | 250x | 0.05% | 0.1% |
| 1 | ETHDOM | 250x | 0.05% | 0.1% |
| 2 | USDTDOM | 250x | 0.05% | 0.1% |
| 3 | BNBDOM | 250x | 0.05% | 0.1% |
| 4 | SOLDOM | 250x | 0.05% | 0.1% |
Key Events
The 5 events most integrations listen for. See Contract Reference — Events for the full listing across all contracts.
| Event | Contract | Meaning |
|---|---|---|
MarketOpenOrderInitiated | DomfiTrading | User submitted open order |
MarketOpenExecuted | DomfiTradingCallbacks | Trade opened successfully |
MarketCloseOrderInitiated | DomfiTrading | User submitted close order |
MarketCloseExecuted | DomfiTradingCallbacks | Trade closed, PnL settled |
MarketOpenCanceled | DomfiTradingCallbacks | Open order rejected (slippage, OI cap, etc.) |
Error Handling
See Contract Reference — Errors for the full error listing across all contracts.
Common Reverts
| Error | Contract | Cause | Resolution |
|---|---|---|---|
InsufficientAllowance | DomfiTradingStorage | USDC not approved or approved to wrong address | Approve USDC to DomfiTradingStorage, not DomfiTrading |
MaxOpenInterestExceeded | DomfiPairInfos | Trade would exceed $10M OI cap per direction per pair | Reduce position size or wait for OI to decrease |
SlippageExceeded | DomfiTradingCallbacks | Oracle price moved beyond your slippage tolerance between submission and fulfillment | Increase slippage parameter or retry |
InsufficientCollateral | DomfiTradingCallbacks | Collateral too low after fees for the requested leverage | Increase collateral or reduce leverage |
MaxLeverageExceeded | DomfiTrading | Leverage exceeds 250x (25000 in 2-decimal precision) | Use leverage value ≤ 25000 |
TpTooHigh | DomfiTrading | Take-profit exceeds 900% cap | Set TP ≤ 900% of collateral |
OraclePriceStale | DomfiPriceRouter | Oracle price too old for execution | Retry — the publisher bot will submit a fresh price |
InsufficientValue | DomfiTrading | ETH sent as msg.value is less than the oracle fee | Send 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:
- Watch events: Listen for
MarketOpenExecutedfrom DomfiTradingCallbacks filtered by your trader address. - Poll storage: Call
DomfiTradingStorage.openTrades(trader, index)to check if the position exists. - Handle failure: If the oracle bot rejects the price or the callback reverts,
MarketOpenCanceledis 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 baseimport { 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
- Contract Reference for the full inventory with BaseScan links
- Contract Addresses for deployed proxy and implementation addresses