Shielded Borrowing & Lending

Architecture & Prototype Ideas

Note: produced by myself, Gemini & Claude.
cc: @maurice @graphomath @Michael @ArtemG

  • Product: shielded privacy wrapper over Aave v3 that gives each user their own EIP-1167 sub-account proxy, inheriting Aave’s liquidity, interest accrual, and native liquidator network.
  • Note: this design addresses both lending and borrowing.

The problem

DeFi lending runs on Aave — billions in liquidity, mature risk parameters, a proven liquidator network — but every position, balance, and liquidation is publicly visible and permanently attached to the user’s address. For enterprises, treasuries, funds, and privacy-conscious users, this transparency is disqualifying.

Three naive approaches fail:

  1. Rebuild Aave as a native shielded money market — years of engineering, giant audit bill, impossible to bootstrap liquidity from zero.
  2. Wrap Aave as a shared pool (Aztec Connect model) — works for deposits but catastrophically socializes liquidation risk; one user underwater liquidates everyone. This killed Aztec Connect’s borrowing story.
  3. Hide collateral, debt, and identity all at once — no protocol has shipped this. Liquidators can’t act on state they can’t see; borrowers don’t self-report when underwater.

Plus: showing the user a $-denominated portfolio view when balances are encrypted notes is non-trivial in any native design.

The solution

A thin wrapper over Aave v3 where each shielded user gets their own EIP-1167 CREATE2 minimal proxy — the exact pattern Instadapp DSA uses in production. Each proxy is an independent Aave borrower with its own health factor, owned by a singleton Forwarder contract that only executes proxy calls when unlocked by a ZK proof from the Anoma Protocol Adapter. The shielded layer’s sole job is hiding the link between user identity and proxy address; everything else — rates, liquidations, accrual, oracles — is delegated to Aave.

Why this solves each problem

Problem How the design solves it
Rebuilding Aave is infeasible Wrap it. Day-one liquidity and rates are Aave’s.
Aztec Connect’s socialized liquidation Each user has their own Aave address (the proxy). Alice’s liquidation has zero effect on Bob.
Liquidating hidden positions is unsolved Don’t solve it. Aave’s existing liquidator bots liquidate each proxy natively — they don’t know it’s shielded-owned. Post-liquidation residue stays claimable.
Interest accrual in shielded notes Aave’s liquidityIndex / variableBorrowIndex do the work. The note only commits to a proxy address.
Portfolio view from encrypted state Client-side: decrypt notes → list proxy addresses → call aavePool.getUserAccountData(proxy) and aToken.balanceOf(proxy). Aave multiplies by the index internally.
Audit and bootstrap cost ~300 lines of Solidity + one ZK circuit. Weeks, not months.
Borrowing “different” from lending It isn’t. Same proxy, same resource kind, same action structure.

Architecture

┌─────────────────────────────────────────────────────────────┐
│              ANOMA EVM PROTOCOL ADAPTER                     │
│     (verifies ZK proof, then delivers the carrier call)     │
└──────────────────────────┬──────────────────────────────────┘
                           │ onlyAdapter
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                 FORWARDER  (the Guardian)                   │
│                                                             │
│   • Holds NO user funds                                     │
│   • Holds NO user→proxy mappings                            │
│   • Factory: CREATE2-deploys proxies with deterministic salt│
│   • Router: dispatches whitelisted selectors to the proxy   │
└──────────────────────────┬──────────────────────────────────┘
                           │ proxy.delegatecall(impl, selector)
                           ▼
┌─────────────────────────────────────────────────────────────┐
│            SubAccount Proxy (per shielded user)             │
│                 45-byte EIP-1167 clone                      │
│                                                             │
│   HOLDS THE STATE:                                          │
│     • aToken balances (Aave supply position)                │
│     • variableDebtToken balances (Aave debt)                │
│     • Aave userConfiguration bitmap, eMode                  │
│   delegatecall-forwards every call to the Implementation    │
└──────────────────────────┬──────────────────────────────────┘
                           │ delegatecall
                           ▼
┌─────────────────────────────────────────────────────────────┐
│             SubAccount Implementation (singleton)           │
│                                                             │
│   HOLDS THE LOGIC:                                          │
│     supply / withdraw / borrow / repay / setEMode / sweep   │
│     onlyForwarder guard on every entry point                │
│   HOLDS NO STATE (delegatecall stores in the proxy's slots) │
└──────────────────────────┬──────────────────────────────────┘
                           │ pool.supply / pool.borrow / …
                           ▼
                      ┌──────────┐
                      │ Aave v3  │
                      └──────────┘

  • Forwarder — Singleton per chain. Holds no user funds and no user mappings. Only callable by the Anoma Protocol Adapter after a ZK proof has been verified. Factory (CREATE2-deploys proxies) and router (dispatches whitelisted selectors).
  • SubAccount proxy — 45-byte EIP-1167 clone, one per shielded position, owned by the Forwarder. Holds the Aave state: aTokens, variableDebtTokens, userConfiguration, eMode. Deployed lazily on first use (~66k gas).
  • Implementation — Singleton logic contract with whitelisted Aave selectors (supply, withdraw, borrow, repay, setEMode, sweep), guarded by onlyForwarder. Holds no state (delegatecall semantics).
  • PositionNote resource — The only ARM resource kind. value_ref = (proxy_address, user_nonce, position_version) where proxy_address = CREATE2(H(user_nonce || position_version), forwarder). The user_nonce must be 256-bit high-entropy (SDK-enforced) to defeat active probing.

Threat-model win: the Forwarder has nothing worth compromising. The user-to-proxy mapping lives only inside the shielded commitment tree, accessible via ZK proof.

Flows

Supply. ARM action carries the input underlying (shielded or public ERC-20) + a calldata-carrier resource targeting Forwarder.executeSupply(salt, pool, asset, amount) + a new or reused PositionNote. The Forwarder lazy-deploys the proxy, funds it, calls Pool.supply(). The proxy now holds scaled aTokens; interest accrues via Aave’s public index with no further user action.

Borrow. Same proxy, same PositionNote. Action carries Forwarder.executeBorrow(...). Forwarder calls proxy.borrow() → Aave checks the proxy’s health factor and mints variable debt tokens. If undercollateralized, Aave reverts, and the whole ARM action reverts. Safety is enforced by Aave’s checks, not re-proved in ZK.

Repay. Similar structure with executeRepay. Full exit composes Repay + Withdraw in one ARM transaction, then nullifies the PositionNote.

Liquidation

Handled entirely by Aave. When a proxy’s HF drops below 1, Aave’s existing liquidator bots call Pool.liquidationCall(..., user=proxy, ...) and seize collateral as they would for any Aave user. The shielded layer never participates. The PositionNote remains valid; its value_ref still points to the same proxy, now with reduced balances, and the residue is sweepable.

The residue sweep is the single worst linkability vector. A public LiquidationCall followed by a shielded sweep in the next few blocks is a near-1:1 correlation. Mandatory client SDK mitigations:

  1. Sweep Delay — randomized hours-to-days before a residue sweep is allowed.
  2. Threshold Batcher — only release the sweep after N (≥50) unrelated shielded actions have posted.
  3. Noise sweeps — protocol-owned sub-accounts perform regular no-op sweep-shaped actions so every real sweep is statistically indistinguishable from a protocol one.

Portfolio view

Pure client-side, zero state reconciliation:

  1. Decrypt notes from the Adapter’s commitment tree.
  2. Extract the proxy addresses.
  3. For each proxy: aavePool.getUserAccountData(proxy) returns collateral, debt, HF, LTV in USD. Per-asset breakdowns via aToken.balanceOf(proxy) (Aave already multiplies by liquidityIndex internally).
  4. Historical P&L reconstructed from Aave’s public event log filtered on user=proxy.

RPC-level clustering is a silent leak: N sequential queries for N proxies from one IP via one provider let the RPC cluster those proxies together. The SDK must default to split queries across multiple unrelated providers, with optional Tor / mixnet / VPN / local-node transport.

Privacy properties

  • Hidden: user identity, the link between an EOA and their proxies, the link between multiple proxies owned by the same user.
  • Visible: each proxy’s Aave state (the Railgun Access Card model — private owner, public position), total proxy count, action timing.
  • Anonymity set: grows with adoption. Seed with protocol-owned proxies at launch.

Opt-in compliance via voluntary viewing key export. The SDK ships with a “Generate Disclosure” feature using the user’s own viewing key to produce a signed JSON history for tax/audit use. The protocol never holds viewing keys. This frames the product as a privacy layer, not a mixer, and defuses the most common regulatory objection.

Key leaks to mitigate: small launch anonymity set, sweep-after-liquidation timing, salt active probing, RPC clustering, gas payer linkage, withdrawal destination correlation.

Prototype plan

Locked-in decisions: Base first · USDC supply + WETH collateral → USDC borrow · Aave v3 only · keep $500 Adapter beta cap (market as “Safety-First Beta”) · opt-in compliance via viewing key.

  • M0 — Spec lock with Anoma team; confirm Adapter constraints; sign off on proxy upgradability (recommendation: beacon proxy for bug-patchability at ~+3k gas/call vs immutable EIP-1167).
  • M1 — Solidity contracts (Forwarder + SubAccount + Implementation). Foundry fork-test against live Aave on Base including real liquidation. Solidity audit.
  • M2PositionNote resource logic circuit (Cairo/Noir). ZK-specialist audit.
  • M3 — Integration on Base Sepolia against the live Protocol Adapter. End-to-end CLI.
  • M4 — TypeScript portfolio client with all privacy hygiene features mandatory: CSPRNG nonce generation, split-RPC router, Sweep Delay + Threshold Batcher, Generate Disclosure.
  • M5 — Closed beta on Base mainnet. Seed anonymity set with protocol-owned sub-accounts and decoy proxies. Target: 50+ positions, 30+ days, no incidents.
  • M6 — Arbitrum + Aave v4 support when v4 lands on L2s.
  • M7 — Compound III as a second underlying.

Out of scope: cross-chain atomic borrowing (wait for Anoma cross-controller atomicity), native rate curves, shielded position shape (Ferveo V2), own liquidator network.

Open before M1: proxy upgradability sign-off, Solidity + ZK audit firm selection, formal Anoma team sync, beta whitelist sourcing.

Sources

Aave v3/v4 docs, Compound III docs, EIP-1167, OpenZeppelin Clones, Instadapp DSA, Anoma Resource Machine spec (specs.anoma.net), EVM Protocol Adapter, AnomaPay, Penumbra shielded staking (the exchange-rate-via-public-index pattern, now delegated to Aave), Aztec Connect sunset post-mortem, Railgun Access Cards.

3 Likes