A loan quote rate combines four components. The first three are loan-agnostic (snapshot of the Agent-SOFR oracle state); the fourth is the matcher's flat fee.
Two of these terms (base anchor, regime premium) are identical to what the Agent-SOFR oracle publishes — they're not loan-specific. The variance premium is loan-specific because it depends on the loan's LTV and duration.
Same Black-Cox first-passage formula as agent-sofr-v1 §3, but with the loan's actual LTV and duration_sec plugged in (instead of the generic 80% / 1h used for the headline rate).
The matcher's compute_collateral mode does the inverse: given a target rate, find the LTV that produces it. Bisection on LTV against the equation above, clamped at the regime's max LTV cap.
Even when the math allows higher LTV, we cap by regime. Caps are tightened from initial v1 values after audit round 1 — a 3% absolute buffer below the contract's 95% liquidation threshold gives the matching engine wiggle room without ever generating quotes the contract would reject at origination.
| Regime | Max LTV (cap) | Aave static | Capital efficiency Δ |
|---|---|---|---|
| RESTING | 92% | 80% | +12% |
| LOW | 90% | 80% | +10% |
| NORMAL | 85% | 80% | +5% |
| ELEVATED | 80% | 80% | 0% (parity) |
| HIGH | 70% | 80% | −10% (safer for lenders) |
| EXTREME | 55% | 80% | −25% (matching paused) |
Weighted-average gain (using time-share weights from agent-sofr-v1): ~8% capital efficiency improvement in calm markets, with materially better lender protection in shocks. Aave can't adapt because LTV requires a multi-week governance vote.
The matcher signs every quote with an EIP-712 typed-data signature. The contract's originate() verifies the signature with ECDSA.recover against the configured oracleSigner address. A quote is non-replayable across contract versions because the EIP-712 domain includes the version string.
EIP712Domain: name: "InterAgentRepo" version: "4" // V4 is the active deployment chainId: 8453 // Base mainnet verifyingContract: "0x9d3b61d13a839968ffad94a0eedf73153c2fb31c" Quote: borrower: address lender: address principalToken: address // e.g. USDC principalAmount: uint256 // in native ERC-20 units collateralToken: address // e.g. WETH collateralAmount: uint256 expiryTimestamp: uint256 // loan must be repaid before this rateBps: uint256 // the rate computed above nonce: bytes32 // = loanId on chain
The nonce doubles as the loanId in the contract's loans mapping — the matcher generates it from a hash of (borrower, lender, expiryTimestamp, principalAmount) so it's deterministic but unique per (counterparty, terms) tuple.
Once both counterparties have approved their respective ERC-20s (USDC for lender, WETH for borrower), either party can submit the signed quote to originate(). The contract:
oracleSignerexpiryTimestamp > block.timestamprateBps ≤ ceiling (R1-#3 post-audit)LoanOriginated eventFrom that point, three terminal states are possible:
| Terminal state | Trigger | Outcome |
|---|---|---|
| Repaid happy path | Borrower calls repay(loanId) before expiry |
Principal + interest → lender. Full collateral → borrower. |
| Liquidated | Anyone calls liquidate(loanId) when on-chain LTV ≥ 95% (post-grace-period of 60s) |
Aave-style split (see §6). Liquidator gets 3% bounty. |
| Defaulted | Anyone calls defaultLoan(loanId) after expiryTimestamp without repayment |
Aave-style split (see §6). Caller gets 3% bounty. |
When a loan terminates via liquidation or expiry-default, the collateral is split four ways (introduced in audit R1-#4):
| Recipient | Share | Purpose |
|---|---|---|
| Liquidator / triggerer | 3% of collateral | Bounty incentivising decentralised liquidator competition |
| Insurance pool | 1% of collateral | Cross-loan loss buffer (separate contract address, rotatable) |
| Lender | debt-equivalent | principalAmount + accrued interest, denominated in collateral at Chainlink price |
| Borrower | remaining excess | If collateral > debt + fees, excess refunded to borrower |
Why this matters: in earlier versions (V1/V2), the entire collateral went to the lender on default. That was an over-pay to lenders that disincentivised borrowing. The Aave-style split (R1-#4 fix in V3+) makes the contract fair: lenders are made whole, the system gets a small reserve buffer, and the borrower gets back whatever's left after paying their debt.
The contract enforces these properties at the bytecode level. They're not just convention — every originate() reverts unless all hold:
| Invariant | Enforced where | Audit finding addressed |
|---|---|---|
| Initial LTV ≤ 93% | originate() | R1-#1 |
| Loan duration ≥ 120s | originate() | R1-#2 |
| rateBps ≤ sanity ceiling | originate() | R1-#3 |
| Aave-style default split (not lender-takes-all) | defaultLoan() / liquidate() | R1-#4 |
| repay() is NOT pausable (owner can't grief borrower into default) | Pausable mixin scope | R2-#2 |
| Chainlink price feed staleness ≤ 1h | liquidate() | R1-#5 (heartbeat check) |
| Grace period 60s after origination before liquidate allowed | liquidate() | (anti-flash-loan) |
| EIP-712 domain version = "4" | originate() sig verify | Non-replayable across V1/V2/V3/V4 |
Full audit reports (round 1, 2, 3): github.com/regimeshift-xyz/regimeshift-clearinghouse/tree/main/audit. Outstanding findings: 0. Retired contracts (V1/V2/V3): oracleSigner rotated to 0x...dEaD on-chain.