How the pool works, in full.

The model, what a default does, the invariants and every parameter of AgentCreditPool — read live from the contract on Robinhood Chain where a value is deploy-time, documented from its own source where it isn't. Full source: contracts/src/AgentCreditPool.sol.

§ 01

Model

One pool of USDG. An agent's line can be backed by up to 20 distinct sponsors at once, each at its own premium, plus any number of seats (see § 03) on top. If a loan defaults, the loss is burned only from the pool-shares of the sponsors who actually backed that loan — never a lender's, and never a sponsor who backed a different agent or joined this one after the default.

backing(r)   = sharesValue(r)                     // r's own pool shares, in USDG
free(r)      = backing(r) - delegatedOut(r) - feeLocked(r)
available(a) = delegatedIn(a) - principalOut(a)    // what's left on agent a's line

vouch(r -> a, amount):   require amount <= free(r)  // + the owner's EIP-712 consent, see below
                         delegatedOut(r) += amount ;  delegatedIn(a) += amount

borrow(a, amount, term): require minLoan <= amount <= available(a)
                         principalOut(a) += amount
                         // amount splits pro-rata across a's CURRENT sponsors, by stake.amount;
                         // each sponsor's own fee share is frozen into loan.shares[] right here
                         feeLocked(sponsor) += its share of the fee

repay(a, principal+fee): fee -> lenders 60% + each sponsor 25% (its own cut) + reserve 15%
lender

Deposits USDG, holds pool shares, earns 60% of every fee. Every line is backed by a sponsor's own locked shares, and a default burns those — not a lender's principal, by construction.

sponsor

Vouches USDG capacity for one agent, from its own deposited pool shares, with the agent owner's signed consent. Earns 25% of that agent's fees; risks exactly what it vouched, only on a loan it actually backed.

agent

An identity (ERC-8004-shaped). Borrows $5–$500 for 1–30 days at 1%/30d within the line its sponsors vouched. Its own owner or configured operatorWallet can borrow; anyone can repay on its behalf.

reserve

Takes 15% of every base fee, paid out immediately on repay — never accrues in the contract itself. No owner, no admin function anywhere in this contract to redirect it.

§ 02

Loan lifecycle and what a default does

Four transitions. The last one is a default, where the backing sponsors pay.

loan.status
Open→Repaidrepay() before markDefault()
Open→DefaulteddueAt + 3d grace passed, anyone calls markDefault()
Repaid agent→Openborrow() again — same line, same sponsors

repay → agent's record grows (loansRepaid, volumeRepaid); the line stays as-is.
default → agent is permanently defaulted, can never borrow again; the owner's default count records it.

require now > loan.defaultableAt          // dueAt + GRACE_PERIOD(3d), fixed at borrow

for each SponsorShare in loan.shares:
  burn ceil[(principal+fee) * totalShares / totalAssets] of that sponsor's own shares
  // if its live stake somehow falls short (should be unreachable — vouch()/
  // borrow() only ever check freeCapacity at that moment), the shortfall
  // becomes explicit totalBadDebt instead of diluting anyone else

for every sponsor of this agent (used by this loan or not):
  release its full remaining committed capacity — the agent can never
  borrow again, so nothing should stay locked against it

agent.defaulted = true ;  agent.delegatedIn = 0
share price: never falls for an uninvolved lender or sponsor
§ 03

Seats

A seat backs an agent with 10 or more repaid loans using a fixed-ratio lock of a separate token (intended to be $AUEVO), on top of the same real-USDG capacity an ordinary vouch requires — never instead of it. The reasoning for that ordering (why burning an unrelated token alone can never protect a lender) is laid out in full on the Seats page. Short version: 50% of the seat's token lock burns on default of the loan it backed; the rest returns, same as an ordinary sponsor's pool-share burn running alongside it.

§ 04

Invariants

Reviewed by hand and exercised by 118 integration-test assertions against a local chain (contracts/test/run-credit.mjs) — not an automated invariant fuzzer, and not a paid, independent, professional audit. Treat these as intended properties the tests check, not a guarantee.

I1 · solvency

totalBadDebt stays 0 and share price never falls, except through the explicit, visible backstop path — no loan loss reaches an uninvolved lender.

I2 · exposure

For every agent, principalOut ≤ delegatedIn. For every sponsor, delegatedOut ≤ its own pool-share value at the moment of any vouch or borrow — checked on-chain, not assumed.

I3 · isolation

A default burns only the shares (and, for a seat, the token lock) of sponsors who backed that specific loan — never a different agent's sponsor, never a sponsor who joined after that loan was drawn.

§ 05

Parameters

The first table is read live from the deployed pool — a real decision made at deploy time, not a code detail. The second is fixed in the contract source itself and identical for any deployment of this version.

minLoan / maxLoan5000000 – 500000000 (raw units)smallest / largest loan
feeBps1% per 30 daysbase fee, pro-rata by term
minRootStake10000000 (raw units)to enroll as a root (sponsor or lender)
asset0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168the stablecoin agents borrow and repay in
pool0xc7a04d94361de7a30d099057c6746217b6aa0d2ethis contract's own address
LENDER_FEE_BPS / SPONSOR_FEE_BPS60% / 25%of each fee; the remainder (15%) goes to the reserve
MAX_PREMIUM_BPS2% per 30 daysceiling a sponsor's own premium can ever reach
GRACE_PERIOD3 daysafter dueAt before markDefault() is allowed
MIN_TERM_DAYS / MAX_TERM_DAYS1 / 30loan duration bounds
MAX_SPONSORS_PER_AGENT20caps the per-agent loop in borrow()/repay()/markDefault() — bounds gas, not a design preference
SEAT_MIN_REPAID_LOANS10repaid loans an agent needs before a seat can back it
SEAT_BURN_BPS50%of a seat's token lock burned on default of the loan it backed
§ 06

Interface

Every write a lender, sponsor, seat-holder or agent can call, plus the views this app's own pages read from.

deposit(uint256 amount)lender
withdraw(uint256 amount)lender
enrollRoot()sponsor/seat-holder
vouch(agentId, amount, premiumBps, maxPremiumBps, nonce, deadline, sig)sponsor
vouchSeat(agentId, amount, premiumBps, maxPremiumBps, nonce, deadline, sig)seat-holder
borrow(agentId, amount, termDays, to)agent (owner/operatorWallet)
repay(loanId)anyone
markDefault(loanId)anyone (permissionless, matches Priors)
sharesValue(address) / freeCapacity(address)view
agentInfo(agentId) / available(agentId)view
sponsorsOf(agentId) / sponsorStakeOf(agentId, sponsor)view
loanInfo(loanId) / loanSharesOf(loanId)view
seatEligible(agentId) / seatTokenRequiredFor(amount)view
isRoot(address) / nextLoanId() / totalBadDebt()view