This guide provides comprehensive information for frontend engineers to integrate with the Cap Protocol v4 smart contracts deployed on Arbitrum Sepolia.
- Quick Start
- Contract Addresses
- Integration Scenarios
- Read Methods (View Functions)
- Write Methods (State Changes)
- Trading Flow
- Liquidity Provider Flow
- Error Handling
- Code Examples
const ARBITRUM_SEPOLIA = {
chainId: 421614,
name: 'Arbitrum Sepolia',
currency: 'ETH',
rpcUrls: ['https://arbitrum-sepolia-rpc.publicnode.com'],
blockExplorerUrls: ['https://sepolia.arbiscan.io/'],
}
const CONTRACTS = {
CHAINLINK: '0x422bC676E1b5091448E8f42308017Ae1f146a441',
STORE: '0x3Bc4AdC426b4C6Ec200a33c419a08DD344cBE3d6',
TRADE: '0x55c75ae8658E6fab799E9Fedcd2Bc3A19e695F90',
POOL: '0x10cE03e10897b9bc78977343cA59957DF2772D2F',
CLP: '0xE416450301f4a82cD6d1781EAae572F320Ed2429',
}
Goal: Show real-time prices for ETH and BTC Flow: Chainlink β Price Display Frequency: Every 10-30 seconds
Goal: Allow users to open/close leveraged positions
Flow: User Input β Order Submission β Position Management
User Actions: Submit orders, close positions, view PnL
Goal: Enable LP deposits/withdrawals and show yields Flow: Deposit β Get LP Tokens β View Rewards β Withdraw User Actions: Add/remove liquidity, claim rewards
Goal: Show user's positions, balances, and history Flow: Connect Wallet β Fetch User Data β Display Portfolio Data: Open positions, balance, PnL, order history
Purpose: Get current price from Chainlink oracle
When to use: Real-time price display, order validation
Returns: Price with 8 decimals (e.g., 250000000000 = $2,500.00)
// Get ETH price
const ethPrice = await chainlinkContract.getPrice('0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165')
const formattedPrice = ethPrice / 1e8 // Convert to readable format
Purpose: Get sequencer uptime feed address for L2 monitoring
When to use: System health checks, display network status
Purpose: Get market configuration (leverage, fees, limits)
When to use: Display trading limits, validate order parameters
Returns: Market struct with all trading parameters
// Get ETH-USD market config
const ethMarket = await storeContract.getMarket('ETH-USD')
// ethMarket.maxLeverage, ethMarket.fee, ethMarket.minSize, etc.
Purpose: Get user's USDC balance in the protocol
When to use: Display available trading balance
Returns: Balance in USDC (6 decimals)
Purpose: Get user's open position in a market
When to use: Display current positions, calculate PnL
Returns: Position struct with size, entry price, timestamp
Purpose: Get user's pending orders
When to use: Display order book, show pending orders
Returns: Array of Order structs
Purpose: Get all user positions across markets
When to use: Portfolio overview, position management
Returns: Array of all user positions
Purpose: Get total pool liquidity
When to use: Display total available liquidity
Returns: Pool balance in USDC
Purpose: Get pool utilization percentage
When to use: Show how much liquidity is being used
Returns: Utilization rate (basis points, 10000 = 100%)
Purpose: Get total LP tokens in circulation
When to use: Calculate LP token price, APY calculations
Purpose: Get user's LP token balance
When to use: Display LP holdings, calculate share percentage
submitOrder(address user, string market, bool isLong, uint256 margin, uint256 size, uint256 price, bool isMarket)
Purpose: Submit a new trading order
When to use: User wants to open a leveraged position
Gas: ~200k-300k gas
Prerequisites:
- User must have sufficient balance
- Margin must meet minimum requirements
- Size must be within limits
// Submit long ETH position with 10x leverage
await tradeContract.submitOrder(
userAddress,
'ETH-USD',
true, // isLong
ethers.parseUnits('100', 6), // $100 margin (USDC)
ethers.parseUnits('1000', 6), // $1000 position size
0, // price (0 for market order)
true // isMarket
)
Purpose: Close an existing position (full or partial)
When to use: User wants to take profits or cut losses
Gas: ~150k-250k gas
Purpose: Cancel a pending limit order
When to use: User wants to cancel unfilled order
Gas: ~50k gas
Purpose: Add liquidity to earn LP rewards
When to use: User wants to become a liquidity provider
Gas: ~100k-150k gas
Prerequisites: User must approve USDC spending
// First approve USDC spending
await usdcContract.approve(poolAddress, amount)
// Then deposit
await poolContract.deposit(userAddress, ethers.parseUnits('1000', 6))
Purpose: Remove liquidity and burn LP tokens
When to use: User wants to exit LP position
Gas: ~100k-150k gas
Purpose: Claim accumulated trading fees and rewards
When to use: User wants to harvest LP earnings
Gas: ~80k gas
Purpose: Deposit USDC for trading
When to use: User needs to fund their trading account
Gas: ~80k gas
Prerequisites: USDC approval required
Purpose: Withdraw USDC from trading account
When to use: User wants to remove funds
Gas: ~80k gas
sequenceDiagram
participant User
participant Frontend
participant Store
participant Trade
participant Chainlink
participant Pool
User->>Frontend: Connect Wallet
Frontend->>Store: getBalance(user)
Frontend->>Chainlink: getPrice(feed)
Frontend->>Store: getMarket(symbol)
User->>Frontend: Submit Order
Frontend->>Store: deposit(amount) [if needed]
Frontend->>Trade: submitOrder(...)
Trade->>Chainlink: getPrice(feed)
Trade->>Store: updatePosition(...)
Trade->>Pool: updateBalance(...)
Frontend->>Store: getPosition(user)
Frontend->>User: Show Updated Position
- Price Check: Get current market price from Chainlink
- Balance Check: Verify user has sufficient margin
- Market Validation: Check market is active and within limits
- Parameter Validation: Validate leverage, size, slippage
- Approval Check: Ensure USDC spending is approved
- Submit Transaction: Call
submitOrder
with validated parameters - Monitor Status: Watch for transaction confirmation
- Update UI: Refresh positions and balances
sequenceDiagram
participant User
participant Frontend
participant USDC
participant Pool
participant CLP
User->>Frontend: Click "Add Liquidity"
Frontend->>USDC: allowance(user, pool)
alt Insufficient Allowance
Frontend->>USDC: approve(pool, amount)
end
Frontend->>Pool: deposit(user, amount)
Pool->>CLP: mint(user, shares)
Frontend->>CLP: balanceOf(user)
Frontend->>User: Show LP Tokens Received
- Track LP Tokens: Monitor
balanceOf(user)
for LP tokens - Calculate Share:
userTokens / totalSupply * 100
- Monitor Pool Growth: Track pool balance changes
- Show APY: Calculate based on trading volume and fees
- Harvest Rewards: Allow users to
collectRewards()
try {
await tradeContract.submitOrder(...)
} catch (error) {
if (error.message.includes('!balance')) {
showError('Insufficient balance')
} else if (error.message.includes('!size')) {
showError('Position size too small')
} else if (error.message.includes('!leverage')) {
showError('Leverage exceeds maximum')
}
}
// Check if user is on correct network
if (chainId !== 421614) {
await switchNetwork(ARBITRUM_SEPOLIA)
}
// Handle RPC failures
const providers = [
'https://arbitrum-sepolia-rpc.publicnode.com',
'https://sepolia-rollup.arbitrum.io/rpc'
]
import { ethers } from 'ethers'
class CapProtocolSDK {
constructor(provider, signer) {
this.provider = provider
this.signer = signer
this.contracts = this.initContracts()
}
initContracts() {
return {
chainlink: new ethers.Contract(CONTRACTS.CHAINLINK, chainlinkABI, this.signer),
store: new ethers.Contract(CONTRACTS.STORE, storeABI, this.signer),
trade: new ethers.Contract(CONTRACTS.TRADE, tradeABI, this.signer),
pool: new ethers.Contract(CONTRACTS.POOL, poolABI, this.signer),
clp: new ethers.Contract(CONTRACTS.CLP, clpABI, this.signer)
}
}
// Get real-time prices
async getPrices() {
const [ethPrice, btcPrice] = await Promise.all([
this.contracts.chainlink.getPrice('0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165'),
this.contracts.chainlink.getPrice('0x56a43EB56Da12C0dc1D972ACb089c06a5dEF8e69')
])
return {
ETH: ethPrice / 1e8,
BTC: btcPrice / 1e8
}
}
// Get user portfolio
async getUserPortfolio(address) {
const [balance, ethPosition, btcPosition, lpTokens] = await Promise.all([
this.contracts.store.getBalance(address),
this.contracts.store.getPosition(address, 'ETH-USD'),
this.contracts.store.getPosition(address, 'BTC-USD'),
this.contracts.clp.balanceOf(address)
])
return {
balance: balance / 1e6, // Convert to readable USDC
positions: { ethPosition, btcPosition },
lpTokens: lpTokens / 1e18
}
}
// Submit market order
async submitMarketOrder(market, isLong, marginUSD, leverageX) {
const margin = ethers.parseUnits(marginUSD.toString(), 6)
const size = margin * BigInt(leverageX)
return await this.contracts.trade.submitOrder(
await this.signer.getAddress(),
market,
isLong,
margin,
size,
0, // market price
true // is market order
)
}
// Add liquidity
async addLiquidity(amountUSD) {
const amount = ethers.parseUnits(amountUSD.toString(), 6)
return await this.contracts.pool.deposit(
await this.signer.getAddress(),
amount
)
}
}
import { useContract, useContractRead } from 'wagmi'
export function useCapProtocol() {
// Real-time price hook
const { data: ethPrice } = useContractRead({
address: CONTRACTS.CHAINLINK,
abi: chainlinkABI,
functionName: 'getPrice',
args: ['0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165'],
watch: true,
select: (data) => Number(data) / 1e8
})
// User balance hook
const { data: userBalance } = useContractRead({
address: CONTRACTS.STORE,
abi: storeABI,
functionName: 'getBalance',
args: [address],
watch: true,
select: (data) => Number(data) / 1e6
})
return { ethPrice, userBalance }
}
- Always validate user inputs before sending transactions
- Check allowances before token transfers
- Handle slippage for market orders
- Monitor gas prices on Arbitrum
- Implement timeout handling for pending transactions
- Batch read calls using
multicall
when possible - Cache market configurations (they rarely change)
- Use websockets for real-time price updates
- Implement proper loading states for better UX
- Add transaction status tracking
- Use Arbitrum Sepolia testnet for all development
- Get test ETH from Chainlink Faucet
- Test all error scenarios before mainnet deployment
π Ready to integrate! This guide covers all scenarios for building a complete trading interface with the Cap Protocol v4.