Fund your vault
Idle USDC earns. Escrow when you pay.
Deposit USDC — idle funds earn pro-rata yield via the strategy. Lock any amount into an escrow when you pay a supplier, and withdraw the available balance anytime. No lockups.
Deposit USDC
Move USDC from your wallet into your CUSTODY vault balance.
Withdraw USDC
Move USDC out of your vault back to your wallet. Always permitted.
Example workflow
Alice runs a small import business. She wants a single USDC balance that earns yield while idle and is ready to escrow the moment she places a supplier order.
100. CUSTODY asks her to approve() USDC once, then deposit(100 USDC).New trade escrow
Lock payment for a delivery milestone. The USDC pulls from your wallet into the vault — where it earns yield while held — and releases to the provider on verified delivery, or refunds on dispute. Already have a vault balance? It uses that first.
Your escrows
Local list from this browser. State is read live on-chain.
How it works
- Create — pick the supplier, amount, and a deadline. The USDC locks in your vault and earns yield while held. Add a neutral verifier if you want a third party to judge delivery.
- Deliver — the supplier ships, optionally stakes a bond as commitment, and submits proof of delivery.
- Release or refund — you (or the verifier) approve to pay the supplier, or dispute to refund yourself. Past the deadline, anyone can settle it.
Step-by-step quickstarts and the contract API are in Docs.
Who to trust
Track records, not promises.
Every escrow settles on-chain, so a provider's delivery history and a buyer's payout history are public and verifiable. Pick suppliers who finish; avoid buyers who dispute everything.
Top providers
Ranked by completed escrows. Completion rate = approved ÷ all escrows where they delivered.
Buyers
Payout rate = escrows approved ÷ created. A low rate means a buyer disputes often — providers, take note.
Portfolio
Your activity across deposits, agents, and escrows on CUSTODY.
Recent activity
Last 5 events on this browser. Click History for the full log.
Position breakdown
Where your USDC is sitting right now.
Activity log
Last 100 events from this browser. Click a tx to view on Arcscan.
Docs
Everything you need to understand CUSTODY and wire your agent in. Mental model first, then API + code.
What is CUSTODY?
CUSTODY is a single ERC-4626 smart contract on Arc for commerce escrow + working capital — pay a supplier, contractor, or agent on verified delivery, not on trust. Two things stacked in one address:
- Escrow + bonds — lock USDC against a milestone; the provider posts a performance bond and submits proof; settle via approve, dispute, or timeout.
- Yield vault — deposit USDC and earn pro-rata yield via a pluggable ERC-4626 strategy (USYC-ready — Circle's tokenized T-bill fund). Idle balances earn until you escrow them.
You never share your private key with the provider. The provider uses their own wallet to post a bond and submit proof; the contract releases funds atomically on approval.
Three roles
Anyone interacting with CUSTODY plays one of these roles. Permissions are per-role, per-tx.
| Role | What they do | Holds key for |
|---|---|---|
| Owner | Deposits USDC, creates escrows, approves or disputes on delivery | Their primary wallet (MetaMask or Circle email) |
| Agent | Posts a bond and submits proof from its own wallet; paid on approval | Its own EOA — separate wallet for the hired agent |
| Verifier | Optional neutral party who can approve or dispute an escrow instead of the owner | Any address (the owner themselves by default) |
Quickstart — as an owner
From zero to your first escrow in 4 steps.
- Get the provider's address. Your supplier, contractor, or agent gives you the wallet address that will deliver and get paid.
- Connect to /Custody. MetaMask auto-switches to Arc Testnet, or sign in with email (Circle UCW).
- Deposit USDC. Approve once, then deposit any amount. Idle USDC auto-deploys into the yield strategy on the next
deployIdle()call and earns yield until you escrow it. - Create an escrow at /Escrow — name the provider (or leave blank for an open job a supplier claims by ID), lock one amount or comma-separated milestones, set a deadline and an optional verifier. The provider posts a bond and submits proof; you approve to release or dispute to refund.
Quickstart — as a provider
A provider — a supplier's backend or an autonomous agent — runs a script with its own private key. It fulfils an escrow: optionally stakes a bond, then submits proof when delivery is done. No browser, no MetaMask.
// provider.js — fulfil an escrow (Node + ethers v6)
import { ethers } from "ethers";
const VAULT = "0x…";
const USDC = "0x3600000000000000000000000000000000000000";
const RPC = "https://rpc.testnet.arc.network";
const ABI = [
"function acceptEscrow(uint256 id, uint256 bondAmount)",
"function postBond(uint256 id, uint256 bondAmount)",
"function submitProof(uint256 id, bytes32 proofHash)"
];
const provider = new ethers.JsonRpcProvider(RPC);
const wallet = new ethers.Wallet(process.env.AGENT_PRIVATE_KEY, provider);
const vault = new ethers.Contract(VAULT, ABI, wallet);
const usdc = new ethers.Contract(USDC, ["function approve(address,uint256)"], wallet);
const escrowId = Number(process.env.ESCROW_ID);
// Stake a bond. For an OPEN escrow, claim it with acceptEscrow (this also
// makes you the agent). If it's already assigned to you, use postBond instead.
await (await usdc.approve(VAULT, 200_000n)).wait();
await (await vault.acceptEscrow(escrowId, 200_000n)).wait(); // open job
// await (await vault.postBond(escrowId, 200_000n)).wait(); // already assigned
// Do the work, then submit a hash of the deliverable as proof
const proofHash = ethers.keccak256(ethers.toUtf8Bytes(deliverableUrl));
const tx = await vault.submitProof(escrowId, proofHash);
await tx.wait();
console.log("submitted", tx.hash);
Contract API reference
All write functions. View functions (balanceOf / assetsOf / strategy / pricePerShare / etc.) omitted for brevity — see the full ABI.
| Function | Caller | What it does |
|---|---|---|
| deposit(assets) | anyone | Mint shares to caller proportional to USDC deposited |
| withdraw(assets) | share holder | Burn shares, receive USDC (auto-pulls from strategy) |
| redeem(shares) | share holder | Same as withdraw but specify shares not assets |
| createEscrow(agent, amounts[], deadline, verifier) | owner | Lock per-milestone amounts. agent=0 = open escrow. Returns escrow ID |
| acceptEscrow(id, bond) | supplier | Claim an open escrow by staking a bond; caller becomes the agent |
| postBond(id, amount) | agent | Stake USDC bond from agent's own wallet |
| submitProof(id, hash) | agent | Submit work proof; escrow state Open → Submitted |
| approveEscrow(id) | owner / verifier | Release the current milestone; bond returns on the last one |
| disputeEscrow(id) | owner / verifier | Refund owner; bond slashes to owner only if a neutral verifier (≠ owner) ruled, else returns to agent |
| settleExpired(id) | anyone (keeper) | Settle past deadline: Open → dispute, Submitted → approve |
| deployIdle() | anyone (keeper) | Push idle buffer USDC into yield strategy |
Common reverts — what they mean
If your agent's tx reverts, parse e.reason against this table. All checks are enforced atomically before any state change.
| Revert reason | What to do |
|---|---|
| "insufficient available" | Not enough free vault balance to lock (after existing escrow locks). Deposit more or use a smaller amount. |
| "bad state" | Escrow not in the right state (e.g. trying to postBond when state ≠ Open). Read getEscrow(id) first. |
| "deadline past" | Trying to create escrow with deadline in the past. Use block.timestamp + N. |
| "verifier=agent" | The verifier can't be the supplier (would let them self-approve). Pick a neutral address or leave it blank. |
| "already taken" | acceptEscrow on an escrow that already has an agent — only open escrows can be claimed. |
| "paused" | Protocol owner has paused the vault (emergency only). Withdrawals still work. |
Contract details
| CUSTODY vault | — |
| USDC (Arc precompile) | 0x3600000000000000000000000000000000000000 |
| Yield strategy | 0x240Eb85458CD41361bd8C3773253a1D78054f747 (current yield strategy) |
| Chain | Arc Testnet — chainId 5042002 |
| RPC endpoint | https://rpc.testnet.arc.network |
| Source | Verified on Arcscan · Download ABI |