CUSTODY
● Yield-bearing vault

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.

Total Value Locked
$USDC
Vault buffer + strategy position
Current APY (net)
%
Loading strategy…
Your Position
$USDC
Your share value (principal + yield earned)

Deposit USDC

Move USDC from your wallet into your CUSTODY vault balance.

USDC

Withdraw USDC

Move USDC out of your vault back to your wallet. Always permitted.

USDC

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.

1
Alice's wallet holds 500 USDC. She opens CUSTODY, clicks Deposit, enters 100. CUSTODY asks her to approve() USDC once, then deposit(100 USDC).
2
Idle USDC auto-deploys into the yield strategy and starts earning. Vault = 100 USDC, Wallet = 400 USDC — the vault balance is what she can escrow against.
3
When she places an order she opens an escrow from this balance (Escrow tab). Anything not locked keeps earning. To pull funds out she clicks Withdraw — one tx, no lockup.
Outcome — One yield-bearing balance that doubles as escrow collateral. Only Alice can move money in or out at this layer.

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.

Who gets paid on verified delivery. Leave blank = open escrow: share the ID and your chosen supplier claims it (stakes a bond) on the “I'm the supplier” tab.
USDC
Comma-separate to release in stages — each milestone approved on its own.
A neutral party (e.g. an inspector) who can approve or dispute. Blank = you decide.

Your escrows

Local list from this browser. State is read live on-chain.

No escrows created yet on this browser.

How it works

  1. 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.
  2. Deliver — the supplier ships, optionally stakes a bond as commitment, and submits proof of delivery.
  3. 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.

● On-chain reputation

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.

Loading on-chain reputation…

Buyers

Payout rate = escrows approved ÷ created. A low rate means a buyer disputes often — providers, take note.

Loading…

Portfolio

Your activity across deposits, agents, and escrows on CUSTODY.

Vault USDC
$
Locked in escrow
$
Net APY
~7.2%
Wallet USDC
$

Recent activity

Last 5 events on this browser. Click History for the full log.

No activity yet.

Position breakdown

Where your USDC is sitting right now.

Available in vault
Locked in escrows (Open + Submitted)
Total controlled by CUSTODY
USDC in your wallet (outside vault)

Activity log

Last 100 events from this browser. Click a tx to view on Arcscan.

No history yet.

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.

RoleWhat they doHolds 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.

  1. Get the provider's address. Your supplier, contractor, or agent gives you the wallet address that will deliver and get paid.
  2. Connect to /Custody. MetaMask auto-switches to Arc Testnet, or sign in with email (Circle UCW).
  3. 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.
  4. 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.

FunctionCallerWhat it does
deposit(assets)anyoneMint shares to caller proportional to USDC deposited
withdraw(assets)share holderBurn shares, receive USDC (auto-pulls from strategy)
redeem(shares)share holderSame as withdraw but specify shares not assets
createEscrow(agent, amounts[], deadline, verifier)ownerLock per-milestone amounts. agent=0 = open escrow. Returns escrow ID
acceptEscrow(id, bond)supplierClaim an open escrow by staking a bond; caller becomes the agent
postBond(id, amount)agentStake USDC bond from agent's own wallet
submitProof(id, hash)agentSubmit work proof; escrow state Open → Submitted
approveEscrow(id)owner / verifierRelease the current milestone; bond returns on the last one
disputeEscrow(id)owner / verifierRefund 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 reasonWhat 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 strategy0x240Eb85458CD41361bd8C3773253a1D78054f747 (current yield strategy)
ChainArc Testnet — chainId 5042002
RPC endpointhttps://rpc.testnet.arc.network
SourceVerified on Arcscan · Download ABI