A collateralized lending platform built on Arc Testnet. Users connect a wallet, deposit cirBTC as collateral, and borrow USDC against it. The protocol is governed by a configurable collateral factor — by default users can borrow up to 50% of their deposited collateral value.
- Prerequisites
- Getting Started
- How It Works
- Contract Overview
- Loan Lifecycle
- Environment Variables
- Project Structure
- Node.js v18+ - Install via nvm
- A wallet - either:
- MetaMask (or any injected EVM wallet) - connected to Arc Testnet (Chain ID
5042002), or - Circle Passkey Wallet - browser-based biometric authentication via WebAuthn (no extension needed). Requires a Circle developer account for the client key and URL.
Emulating passkeys in the browser: If your browser does not support hardware passkeys (or you're developing without a biometric device), you can enable a virtual authenticator in DevTools:
- Open Developer Tools (F12)
- Go to the WebAuthn tab (in Chromium-based browsers)
- Check "Enable virtual authenticator environment"
- Configure the virtual authenticator:
- Protocol:
ctap2 - Transport:
internal - Supports resident keys:
Yes - Supports user verification:
Yes - Supports large blob:
No
This creates a software-based authenticator that emulates biometric authentication, allowing passkey registration and login to work without physical hardware.
- MetaMask (or any injected EVM wallet) - connected to Arc Testnet (Chain ID
- Arc Testnet USDC - used for gas fees only. Obtain from the Circle faucet (20 USDC per request, every 2 hours per address).
- cirBTC - the collateral token for this protocol. Circle's wrapped Bitcoin on Arc Testnet (
0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF).⚠️ cirBTC has no public faucet yet. As of this writing, cirBTC is a "coming soon" product from Circle and is not distributed byfaucet.circle.comor the developer-console faucet (both of which only dispense USDC/EURC/native gas tokens). The contract on Arc Testnet is Circle's standardFiatTokenProxy, which means only Circle-whitelisted minter addresses can mint — there is no self-serve on-chain faucet.To obtain cirBTC for testing, you currently need to either:
- Request an allocation through Circle's developer Discord / partner channels, or
- Receive a transfer from a wallet that already holds cirBTC (e.g. a team demo wallet).
The mock USDC loan token deployed by this repo remains freely mintable via the in-app faucet button, so the rest of the flow is testable once you have any non-zero cirBTC balance.
- Clone the repository and install dependencies:
git clone <repo-url>
cd arc-borrow-lend
npm install- Set up environment variables:
cp .env.example .env.localEdit .env.local and fill in your deployer private key and (optionally) Circle credentials (see Environment Variables). The Arc Testnet RPC URL defaults to https://rpc.testnet.arc.network.
Note: The public RPC rate-limits (HTTP 429) under the app's polling — balances and buttons can stall. For demos or heavy use, set
NEXT_PUBLIC_RPC_URLto a dedicated provider (e.g. an Alchemy Arc Testnet endpoint). Note: Deployment requires a non-custodial wallet (e.g. MetaMask) whose private key you can export. Custodial wallets like Circle passkey wallets do not expose private keys and cannot be used for deployment. The Circle wallet integration is for end users interacting with the deployed contracts via the frontend.
- Compile and deploy the smart contracts:
npm run compile
npm run deploy:lendingThe deploy script:
- Uses the existing cirBTC token (
0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF) as collateral — no deployment needed - Deploys a mock USDC (TestnetERC20, 8 decimals) and mints 100,000 USDC to the deployer
- Deploys the
LendingBorrowingcontract with cirBTC as collateral, USDC as the lending token, and a 50% collateral factor - Funds the lending pool with 50,000 USDC
- Writes all deployed addresses to
.env.local - Start the development server:
npm run devThe app will be available at http://localhost:3000.
- Built with Next.js App Router and wagmi + viem for wallet interactions
- Dual wallet support: users can connect via MetaMask (injected wallet) or a Circle passkey wallet (WebAuthn biometric authentication with smart account abstraction via
[@circle-fin/modular-wallets-core](https://www.npmjs.com/package/@circle-fin/modular-wallets-core)). A unifieduseContractWritehook abstracts the differences so all lending actions work identically with either wallet type. - Collateral: cirBTC (Circle's wrapped Bitcoin on Arc Testnet, 8 decimals). Users deposit cirBTC to unlock borrowing capacity.
- Lending token: Mock USDC (8 decimals) - freely mintable via the UI faucet button. Users borrow USDC from the pool against their cirBTC collateral.
- Collateral factor: set at deployment (default 50%). Users can borrow up to
collateralFactor%of their deposited cirBTC value in USDC. Both tokens use 8 decimals so the percentage math is exact with no decimal conversion. - Only one active loan per user at a time. Collateral is locked while a loan is outstanding and released upon full repayment.
- Styled with Tailwind CSS and components from shadcn/ui
- Arc Testnet uses USDC as native gas — no separate ETH needed for transaction fees.
A simple collateralized lending protocol with the following interface:
| Function | Who | Description |
|---|---|---|
depositCollateral(amount) |
User | Deposit cirBTC as collateral (requires prior ERC20 approval) |
takeLoan(amount) |
User | Borrow USDC up to collateralFactor% of deposited collateral |
repayLoan(amount) |
User | Repay USDC loan (partially or fully) |
withdrawCollateral(amount) |
User | Withdraw cirBTC not locked by an active loan |
fundPool(amount) |
Owner | Fund the lending pool with USDC |
setCollateralFactor(factor) |
Owner | Update the collateral factor (1–100) |
View helpers:
| Function | Returns |
|---|---|
maxBorrow(user) |
Maximum USDC the user can currently borrow |
availableCollateral(user) |
cirBTC available to withdraw (not locked by a loan) |
poolLiquidity() |
Current USDC balance in the lending pool |
| Step | Action | Description |
|---|---|---|
| 1 | Get cirBTC | Obtain cirBTC on Arc Testnet to use as collateral (see Prerequisites — no public faucet; distribution is via Circle Discord or a team demo wallet) |
| 2 | Deposit | Approve and deposit cirBTC as collateral |
| 3 | Borrow | Take a USDC loan up to the collateral factor limit |
| 4 | Repay | Approve USDC and repay any portion of the outstanding loan |
| 5 | Withdraw | Once fully repaid, withdraw cirBTC collateral |
Collateral is locked for the duration of an active loan and cannot be withdrawn until the loan is fully repaid.
All environment variables live in .env.local. The deploy script automatically writes contract addresses after a successful deployment.
| Variable | Purpose |
|---|---|
PRIVATE_KEY |
Deployer wallet private key |
NEXT_PUBLIC_RPC_URL |
Alchemy RPC URL (used by both Hardhat and the frontend) |
NEXT_PUBLIC_CIRBTC_ADDRESS |
cirBTC token address (hardcoded; auto-written by deploy script) |
NEXT_PUBLIC_USDC_ADDRESS |
Deployed mock USDC address (auto-written by deploy script) |
NEXT_PUBLIC_LENDING_ADDRESS |
Deployed LendingBorrowing contract address (auto-written) |
NEXT_PUBLIC_CIRCLE_CLIENT_KEY |
Circle modular wallets client key (for passkey wallet) |
NEXT_PUBLIC_CIRCLE_CLIENT_URL |
Circle modular wallets API URL (for passkey wallet) |
NEXT_PUBLIC_EXPLORER_URL |
Block explorer base URL used for transaction links (optional) |
NEXT_PUBLIC_SUPABASE_URL |
Local Supabase API URL (default http://127.0.0.1:54321) |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY |
Local Supabase publishable key (printed by npm run db:start; formerly "anon key") |
Transaction history is persisted in a locally self-hosted Supabase instance. Docker is required.
npm run db:start # boots Postgres + Studio + REST
npm run db:status # prints API URL, anon key, Studio URL
npm run db:stop
npm run db:reset # re-run migrations, wipe dataOn first run, copy the printed Project URL into NEXT_PUBLIC_SUPABASE_URL and the Publishable key into NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY in .env.local. (Supabase recently renamed anon → publishable and service_role → secret; only the publishable key is needed here.) The initial migration (supabase/migrations/*_init_transactions.sql) creates the transactions table used by the history panel on the dashboard.
arc-borrow-lend/
├── app/ # Next.js App Router pages
│ ├── layout.tsx
│ ├── page.tsx # Main lending & borrowing UI
│ ├── providers.tsx
│ └── globals.css
├── components/ # React components
│ ├── trading/
│ │ └── TxStatus.tsx # Transaction status indicator
│ ├── wallet/ # Wallet connection components
│ │ ├── ConnectDialog.tsx # Wallet selection dialog
│ │ ├── ConnectWallet.tsx # Wallet connect button + balances
│ │ └── CopyableText.tsx # Click-to-copy address display
│ └── ui/ # shadcn/ui primitives (button, card, input, tabs, etc.)
├── contracts/ # Solidity smart contracts
│ ├── LendingBorrowing.sol # Collateralized lending protocol
│ └── TestnetERC20.sol # Mintable ERC20 for testnet (deployed as USDC)
├── contexts/
│ └── WalletContext.tsx # Dual-wallet state (MetaMask + Circle passkey)
├── hooks/
│ ├── useContractWrite.ts # Unified write hook for both wallet types
│ └── lending/ # Lending hook modules
│ ├── index.ts
│ ├── useLendingState.ts # Pool and user position state
│ └── useLendingActions.ts # Deposit, withdraw, borrow, repay actions
├── lib/
│ ├── contracts/ # Contract definitions
│ │ ├── addresses.ts # Deployed contract addresses from env vars
│ │ └── abis/ # ABI definitions
│ ├── circle.ts # Circle passkey transport & client helpers
│ ├── errors.ts # Error parsing & user-friendly messages
│ ├── wagmi.ts # Wagmi + Arc Testnet chain config
│ └── utils.ts # Utility functions
├── public/ # Static assets
├── scripts/
│ └── deploy-lending.ts # Deploys mock USDC + LendingBorrowing, funds pool
├── hardhat.config.ts # Arc Testnet network & compiler settings
├── next.config.ts # Next.js configuration
├── package.json # All dependencies & scripts
└── .env.example # Environment variable template
This sample application:
- Targets Arc Testnet only
- Mock USDC is freely mintable — not suitable for production use without replacing with a real token
- cirBTC is an existing token on Arc Testnet and is not mintable via the app
- No interest rate model — this is an interest-free protocol for demonstration purposes
- Is not intended for production use without modification
