UnitFlow LogoUnitFlow Docs

Developer Reference

This page covers all contract addresses, function signatures, ABI fragments, React hooks, and integration patterns for building on top of UnitFlow Lending.

Contract Addresses (Arc Testnet — chainId 5042002)

ContractAddressRole
LendingPool0xb6cBD47e67DeF8270916A9F756526015Fabb2f1BCore pool — all user interactions
LiquidationEngine0xc3f1024ff9ff178D7B321a28966F4bBCa7db3788Liquidations & health factor reads
InterestRateModel0x07AeC51d8448b4a6560ec8cB185ce396ACD2E1B9Rate calculation (read-only)
PriceOracle0x5bD0c70dDD1cC439E613dEF1Da7F06E1a24DB0078-decimal USD prices
FeeDistributor0x4fF873fAE73a5FcDa5De06E63Ef22c32207e4F0cProtocol fee routing
Configurator0xAa7cefeb7FD04dF05CDA389bD1fc4097BDB0b61aAdmin — risk parameters
uUSDC0xbab299155CDC34b790e2d96DdadC3Ac64708C003Supply token for USDC
uEURC0x6bdaa941dE3D01b97B6010b2e5eFd6439ec8D2c6Supply token for EURC
dUSDC0xD21Fe44773107824cee0E36F6B38A072A02024F5Debt token for USDC
dEURC0x169138F1551B2D4c78240e67fD0B9075d2366788Debt token for EURC
USDC0x3600000000000000000000000000000000000000Underlying
EURC0x89b50855aa3be2f677cd6303cec089b5f319d72aUnderlying

Precision Constants

WAD = 10n ** 18n   // health factor, share ratios
RAY = 10n ** 27n   // interest indices, rates
// Token decimals: 6 (USDC & EURC)
// Oracle price decimals: 8

LendingPool ABI — Key Functions

Write Functions

// Supply assets into the pool
supply(
  asset:       address,   // underlying token address
  amount:      uint256,   // token amount (6 decimals)
  onBehalfOf:  address    // recipient of uTokens
) external

// Withdraw supplied assets
withdraw(
  asset:   address,
  amount:  uint256,   // pass type(uint256).max to withdraw all
  to:      address
) external returns (uint256 withdrawn)

// Borrow against collateral
borrow(
  asset:       address,
  amount:      uint256,
  onBehalfOf:  address
) external

// Repay borrowed debt
repay(
  asset:       address,
  amount:      uint256,   // pass type(uint256).max to repay all
  onBehalfOf:  address
) external returns (uint256 repaid)

Read Functions

// Account summary — all values 8-decimal USD
getUserAccountData(user: address) external view returns (
  totalCollateralUSD:  uint256,
  totalDebtUSD:        uint256,
  healthFactor:        uint256,   // WAD-scaled
  availableBorrowsUSD: uint256
)

// Full reserve state for an asset
reserves(asset: address) external view returns (
  uToken:               address,
  debtToken:            address,
  underlyingAsset:      address,
  liquidityIndex:       uint256,   // RAY
  variableBorrowIndex:  uint256,   // RAY
  currentLiquidityRate: uint256,   // RAY per-second
  currentBorrowRate:    uint256,   // RAY per-second
  totalLiquidity:       uint256,   // 6-decimal tokens
  totalBorrows:         uint256,   // 6-decimal tokens
  reserveBalance:       uint256,
  lastUpdateTimestamp:  uint256,
  active:               bool,
  frozen:               bool
)

// Utilisation ratio for an asset
getUtilizationRate(asset: address) external view returns (uint256)  // RAY

// Per-user, per-asset collateral balance
userCollateral(user: address, asset: address) external view returns (uint256)

LiquidationEngine ABI

// Returns WAD-scaled health factor for any address
getHealthFactor(user: address) external view returns (uint256)

// Execute a liquidation
liquidate(
  borrower:        address,
  collateralAsset: address,   // asset to receive
  debtAsset:       address,   // asset to repay
  debtToCover:     uint256    // ≤ 50% of outstanding debt
) external

uToken / dToken ABI

// Standard ERC-20 — balanceOf returns scaled amount
balanceOf(account: address) external view returns (uint256)

// dTokens are non-transferable (transfer() reverts)

React Hooks

All hooks live under src/hooks/lending/ and are re-exported from src/hooks/lending/index.ts.

useLendingPool()

import { useLendingPool } from '@/hooks/lending'

const { reserves, totalTvlUsd, isLoading, isError, refetch } = useLendingPool()

// reserves: ReserveInfo[]
interface ReserveInfo {
  symbol:                  'USDC' | 'EURC'
  totalLiquidity:          bigint
  totalBorrows:            bigint
  totalLiquidityFormatted: string   // human-readable
  totalBorrowsFormatted:   string
  supplyApy:               number   // percent, e.g. 4.25
  borrowApy:               number
  utilizationRate:         number   // 0–100
  liquidityIndex:          bigint   // RAY
  borrowIndex:             bigint   // RAY
  active:                  boolean
}

Fetches both reserves in a single multicall. Refreshes every 15 seconds. Converts RAY per-second rates to annual percentages using rate × 31_536_000 / RAY × 100.

useUserPosition(address)

import { useUserPosition } from '@/hooks/lending'

const position = useUserPosition(address)

interface UserPositionData {
  totalCollateralUsd:    number
  totalDebtUsd:          number
  healthFactor:          bigint   // WAD
  healthFactorDisplay:   string   // "1.45" or "∞"
  availableBorrowsUsd:   number
  borrowingPowerUsedPct: number   // 0–100
  netWorthUsd:           number
  positions:             AssetPosition[]
  isLoading:             boolean
  isError:               boolean
  refetch:               () => void
}

interface AssetPosition {
  symbol:            'USDC' | 'EURC'
  supplied:          bigint
  suppliedFormatted: string
  borrowed:          bigint
  borrowedFormatted: string
}

Batches getUserAccountData, both reserves calls, and both uToken balanceOf calls into a single multicall. Refreshes every 12 seconds. Pass undefined when no wallet is connected — the hook returns zeroed data without making any RPC calls.

useSupply(assetSymbol, amount)

import { useSupply } from '@/hooks/lending'

const { write, state, isPending, isSuccess, isError, needsApproval, reset } =
  useSupply('USDC', '100')

// write()  →  approve (if needed) then supply
// state.step: 'idle' | 'approving' | 'supplying' | 'success' | 'error'
// state.txHash: string | undefined
// needsApproval: boolean  (current allowance < amount)

useBorrow(assetSymbol, amount)

const { write, state, isPending, isSuccess, isError, reset } =
  useBorrow('EURC', '50')

// state.step: 'idle' | 'borrowing' | 'success' | 'error'
// No approval needed — pool sends tokens to you

useRepay(assetSymbol, amount)

const { write, state, isPending, isSuccess, isError, needsApproval, reset } =
  useRepay('USDC', 'max')   // pass 'max' to repay full debt

// 'max' maps to type(uint256).max on-chain
// state.step: 'idle' | 'approving' | 'repaying' | 'success' | 'error'

useWithdraw(assetSymbol, amount)

const { write, state, isPending, isSuccess, isError, reset } =
  useWithdraw('USDC', 'max')   // 'max' → type(uint256).max

// state.step: 'idle' | 'withdrawing' | 'success' | 'error'
// No approval needed — pool burns your uTokens

useLiquidatablePositions()

const { positions, isLoading } = useLiquidatablePositions()

interface LiquidatablePosition {
  borrower:            `0x${string}`
  healthFactor:        bigint
  healthFactorDisplay: string
  collateralAsset:     'USDC' | 'EURC'
  collateralAmount:    bigint
  collateralFormatted: string
  debtAsset:           'USDC' | 'EURC'
  debtAmount:          bigint
  debtFormatted:       string
  debtToCover:         bigint   // 50% of debt
}

Scans Supply events from the last 50,000 blocks to discover borrowers, then filters to HF < 1.2 via a batched multicall. Refreshes every 12 seconds.

useLiquidate(borrower, collateralAsset, debtAsset, debtToCover)

const { write, state, isPending, isSuccess, isError, reset } = useLiquidate(
  borrower,        // 0x...
  collateralAsset, // underlying address (not uToken)
  debtAsset,       // underlying address
  debtToCover      // bigint — 50% of debt
)

// state.step: 'idle' | 'approving' | 'liquidating' | 'success' | 'error'

Configuration File

// src/lib/contracts/lending/config.ts

export const LENDING_CHAIN_ID = 5042002

export const LENDING_ASSETS: Record<'USDC' | 'EURC', {
  symbol:        'USDC' | 'EURC'
  decimals:      number          // 6
  underlying:    `0x${string}`
  uToken:        `0x${string}`
  dToken:        `0x${string}`
  fallbackPrice: bigint          // 8-decimal USD
}>

Integration Example — Read Pool State

import { createPublicClient, http } from 'viem'
import { LENDING_POOL_ABI } from '@/lib/contracts/lending/abis'

const client = createPublicClient({
  chain: arcTestnet,
  transport: http('https://rpc.arc-testnet.io'),
})

const [usdcReserve, eurcReserve] = await client.multicall({
  contracts: [
    {
      address: '0xb6cBD47e67DeF8270916A9F756526015Fabb2f1B',
      abi: LENDING_POOL_ABI,
      functionName: 'reserves',
      args: ['0x3600000000000000000000000000000000000000'],
    },
    {
      address: '0xb6cBD47e67DeF8270916A9F756526015Fabb2f1B',
      abi: LENDING_POOL_ABI,
      functionName: 'reserves',
      args: ['0x89b50855aa3be2f677cd6303cec089b5f319d72a'],
    },
  ],
})

// Convert RAY per-second rate to APR%
const SECONDS_PER_YEAR = 31_536_000n
const RAY = 10n ** 27n
const supplyApr = Number(usdcReserve.result[5] * SECONDS_PER_YEAR * 10_000n / RAY) / 100

Events

// LendingPool emits:
event Supply(
  address indexed asset,
  address indexed user,
  uint256 amount,
  uint256 index
)

event Withdraw(
  address indexed asset,
  address indexed user,
  address indexed to,
  uint256 amount
)

event Borrow(
  address indexed asset,
  address indexed user,
  uint256 amount,
  uint256 borrowRate,
  uint256 index
)

event Repay(
  address indexed asset,
  address indexed user,
  address indexed repayer,
  uint256 amount
)

// LiquidationEngine emits:
event Liquidation(
  address indexed collateralAsset,
  address indexed debtAsset,
  address indexed borrower,
  uint256 debtToCover,
  uint256 collateralSeized,
  address liquidator
)