ERC Pool Canister
Ethereum asset custody with gas fee fronting and DEX integration
Responsibilities
- Manage ckERC-20 deposits from users
- Process withdrawals with gas fee fronting
- Convert accumulated fees back to ckETH via DEX
- Handle multi-token support with unified interface
Architecture
Subaccount Architecture
Similar to the BTC Pool, but with Ethereum-specific outflow encoding:
Subaccount Types
| Type | Prefix | Purpose |
|---|---|---|
| Inflow Deposit | 0x1 | User deposits |
| Inflow Repay | 0x2 | Debt repayments |
| ETH Outflow | 0x4 | Ethereum address withdrawals |
| Native Outflow | 0x5 | IC Principal transfers |
| Fee | Internal | Gas fee management |
Ethereum Outflow Subaccounts
Ethereum addresses are exactly 20 bytes, fitting directly in the subaccount:
[0x4, <11 zero bytes>, <20 bytes of Ethereum address>]Example for 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1:
[0x4, 0x0, ..., 0x74, 0x2d, 0x35, 0xCc, ..., 0xbE, 0xb1]FEE_SUBACCOUNT
[0x0, 0x2, <30 zero bytes>]Holds:
- ckETH: For fronting Ethereum gas fees during withdrawals
- Pool tokens (e.g., ckUSDT): Accumulated fees from user withdrawals awaiting swap to ckETH to replenish gas reserves
Gas Fee Fronting
Problem: Burning ckERC-20 tokens requires paying Ethereum gas fees in ETH.
Solution: Protocol fronts ckETH for gas, deducts equivalent amount in pool token from withdrawal.
Fee Calculation Flow
Withdrawal with Fee Fronting
Fee Flow Summary:
User requests: 1000 ckUSDC withdrawal
Gas fee (ETH): 0.001 ckETH
Gas fee (USDC): 2.5 ckUSDC
Ledger fees: ~0.5 ckUSDC
Net withdrawal: 997.5 ckUSDC
Fee to FEE_SUBACCOUNT: 2.5 ckUSDC
ckETH fronted: 0.001 ckETHAutomated Fee Recovery (KongSwap)
The pool must replenish its ckETH reserves used for gas fronting.
Fee Swap Flow
Implementation
Every 5 minutes, the pool checks if accumulated fees should be swapped:
async fn swap_fee() {
// Check FEE_SUBACCOUNT balance
let balance = erc_ledger.balance_of(FEE_SUBACCOUNT).await;
// Only swap if above threshold
if balance <= min_threshold {
return;
}
// Approve DEX
erc_ledger.approve(dex, balance).await;
// Execute swap
dex.swap(SwapArgs {
pay_token: "ckUSDT",
receive_token: "ckETH",
pay_amount: balance,
max_slippage: slippage_tolerance,
}).await;
// ckETH now in FEE_SUBACCOUNT for future gas fronting
}DEX Integration
| Parameter | Value |
|---|---|
| DEX | Configurable |
| Swap Direction | Pool Token → ckETH |
| Trigger | Balance > min_threshold |
| Interval | Every 300 seconds |
| Slippage | Configurable per pool |
Minimum Burn Amount
Each pool defines a min_burn_amount to prevent uneconomical withdrawals:
if net_withdrawal <= metadata.min_burn_amount {
return Err("burn amount is too low");
}This ensures:
- Withdrawal value > gas fees + protocol fees
- Users aren't surprised by high fee ratios
- Pool doesn't process dust withdrawals
Inflow/Outflow Detection
The ERC Pool uses the same timer-based detection as the BTC Pool:
| Task | Interval | Purpose |
|---|---|---|
check_for_new_subaccount_inflows | 60s | Scan for deposits/repayments |
process_treasury_movements | 60s | Detect confirmed deposits |
process_event_queue | 60s | Notify lending canister |
swap_fees | 300s | Convert fees to ckETH |
sync_ledger_fee | 15s | Update fee cache |
Pool Metadata
Each ERC Pool maintains configuration:
PoolMetadata {
ticker: String, // "USDC", "USDT", "ETH"
decimals: u8, // Token decimals
min_burn_amount: Nat, // Minimum withdrawal
cketh_ledger: Principal, // ckETH ledger for gas
// DEX configuration
dex_swap_enabled: bool,
dex_swap_ticker: String,
dex_swap_min_threshold: Nat,
dex_swap_slippage_tolerance: f64,
}Multi-Token Support
The ERC Pool architecture supports multiple tokens with a unified interface:
| Token | Ledger | Minter | Fee Token |
|---|---|---|---|
| ckETH | ckETH Ledger | ckETH Minter | ckETH |
| ckUSDT | ckUSDT Ledger | ckETH Minter | ckETH |
All ERC-20 tokens use the same ckETH minter for withdrawals, with gas paid in ckETH.