# ERC-8465: Credit-Matched Liquidity

> Robinhood Chain testnet (46630). Test tokens, no value. Reference contracts not audited. These are drafts under review, not standards.

A draft under review on ethereum/ERCs. A lender funds the quote side of a holder's pool position in an escrow, repaid from fees first, unwound on an oracle price.

Status: Testnet only.

- Pull request: https://github.com/ethereum/ERCs/pull/2109
- Text pinned at the pull request's head commit 8940a9da9578026fd578b5d3a4c13ca57bdfdb89 (ERCS/erc-8465.md, CC0): https://github.com/priors-agents/ERCs/blob/8940a9da9578026fd578b5d3a4c13ca57bdfdb89/ERCS/erc-8465.md
- Discussion: https://ethereum-magicians.org/t/erc-8465-credit-matched-liquidity/30039
- Reference implementation: https://github.com/priors-agents/erc-credit-matched-liquidity/tree/2f1d8642c88ce71a00cfbc373b24ebe29c1daf28 (commit 2f1d8642c88ce71a00cfbc373b24ebe29c1daf28)
- This page: https://lab.priors.trade/erc/8465

## Abstract

This proposal defines an interface for a contract that lends an [ERC-20](https://eips.ethereum.org/EIPS/eip-20) quote token into a concentrated-liquidity position that a holder pairs with base tokens it owns. The position, an [ERC-721](https://eips.ethereum.org/EIPS/eip-721) token of a position manager, stays in the contract's escrow: the holder cannot withdraw it. Fees in the quote token pay the lender first. The holder closes the position by repaying what it owes and gets the position token back. While anything is owed, anyone may unwind the position once its value, taken from the price feed the contract names for the pool and never from the pool's own price, no longer covers the loan by the margin fixed at opening, or once it is due. A price set while the base token was not trading is never used. The unwind settles at the feed's price without a swap, pays the lender's base tokens to a fallback address the lender names, and leaves the holder's part in the position token, so a token whose transfer registry blocks an address cannot strand a position. When, past the due date, no price has come for a delay the lender lists, at least 14 days, or once a day has passed since the base token stopped trading for good, anyone may instead hand the position over without a price: the lender takes the quote, up to what it is owed, and the position token too if that quote falls short.

## Specification headings

1. Definitions
2. Interface
3. Tokens, pools and prices
4. Opening
5. Escrow and terms
6. Health
7. Fees
8. Closing
9. Unwinding
10. Hand-over

## At Priors

Nothing at Priors runs this draft. Its reference contracts go on the testnet only.

Its reference's README: “Not audited. Never deploy without an external audit.”

## Try it on testnet (46630)

Every action is prepared as an unsigned transaction { chainId: 46630, to, data, value }, signed by your own wallet or testnet key. Test tokens have no value.

Contracts:

- testDollar: 0x64A419191369E6a79d562A65336073ec1bfbf4BD (https://explorer.testnet.chain.robinhood.com/address/0x64A419191369E6a79d562A65336073ec1bfbf4BD)

Actions:

- `fund`: Fund your escrow with test dollars (as its lender). Who signs: the lender Params: `contract` (Your own CreditMatchedLiquidity's address, address), `amount` (How many test dollars, amount).
- `mint_base`: Mint your mock base token. Who signs: anyone (at most a million whole tokens a call) Params: `base` (Your mock base token (LabCmlToken), address), `to` (Who receives it, address), `amount` (How many, in base units (18 decimals: 1000000000000000000 is one), uint).
- `set_mock_pool`: Set a pool on your mock position manager. Who signs: the manager's owner (who deployed it) Params: `manager` (Your mock position manager (LabCmlPositionManager), address), `pool` (The pool's address (any address you choose names it), address), `base` (Its base token (your LabCmlToken), address), `quote` (The quote token (empty: the test dollar), address, optional, default (the lab's)), `price` (The pool's price: quote units per 1e18 base units (1000000 is one test dollar a whole base token), uint).
- `set_mock_price`: Set your mock price feed. Who signs: the feed's owner (who deployed it) Params: `feed` (Your mock price feed (LabCmlPriceFeed), address), `answer` (The answer (in the feed's decimals: 100000000 is 1.00 with 8), int), `updated_at` (Updated at (unix seconds; +0 is now), time, optional, default +0).
- `list_pool`: List a pool with its terms (as the lender). Who signs: the lender Params: `contract` (Your own CreditMatchedLiquidity's address, address), `pool` (The pool, address), `base` (Its base token, address), `price_feed` (The base's price feed, address), `status_source` (Where trading status is read (empty: trades continuously), address, optional, default 0x0000000000000000000000000000000000000000), `max_price_age` (Oldest usable price, seconds, uint, optional, default 3600), `limit_bps` (Quote limit, bps of the base's value, uint, optional, default 5000), `unwind_bps` (Unwindable below, bps of what is owed, uint, optional, default 11000), `gap_bps` (Price fall a new position must survive, bps, uint, optional, default 2000), `charge_bps` (The lender's charge, bps, uint, optional, default 500), `term` (Term, seconds, uint, optional, default 2592000), `handover_delay` (Hand-over delay, seconds (at least 14 days), uint, optional, default 1209600), `base_decimals` (The base's decimals, uint, optional, default 18).
- `approve_base`: Let the escrow take your base token. Who signs: the holder Params: `contract` (Your own CreditMatchedLiquidity's address, address), `base` (The base token, address), `amount` (How many, in base units, uint).
- `open`: Open a position: your base, the lender's quote. Who signs: the holder (the identity's owner when one is named) Params: `contract` (Your own CreditMatchedLiquidity's address, address), `pool` (The pool, address), `base_in` (Your base, in base units, uint), `quote_to_borrow` (The quote side to borrow, in test dollars, amount), `tick_lower` (Lower tick, int, optional, default -887220), `tick_upper` (Upper tick, int, optional, default 887220), `agent_id` (Your identity (empty: none), agentId, optional, default 0).
- `apply_fees`: Apply a position's fees (the lender first). Who signs: anyone Params: `contract` (Your own CreditMatchedLiquidity's address, address), `position_id` (The position, uint).
- `unwind`: Unwind a position on the oracle price. Who signs: anyone, once it is unwindable Params: `contract` (Your own CreditMatchedLiquidity's address, address), `position_id` (The position, uint).
- `approve_repay`: Let the escrow take what a position owes (before close). Who signs: the holder Params: `contract` (Your own CreditMatchedLiquidity's address, address), `amount` (How many test dollars (what the position read says it owes, or more), amount).
- `close`: Close a position (repay and take it back). Who signs: the holder Params: `contract` (Your own CreditMatchedLiquidity's address, address), `position_id` (The position, uint).
- `withdraw`: Withdraw unlent test dollars (as the lender). Who signs: the lender Params: `contract` (Your own CreditMatchedLiquidity's address, address), `to` (Where they go, address), `amount` (How many test dollars, amount).
- `set_lender_fallback`: Set the lender's fallback address. Who signs: the lender Params: `contract` (Your own CreditMatchedLiquidity's address, address), `fallback` (The fallback, address).

Reads:

- `position`: A position and its health. Params: `contract` (Your own CreditMatchedLiquidity's address, address), `position_id` (The position, uint).
- `quote_limit`: The most quote a position may borrow for a base amount. Params: `contract` (Your own CreditMatchedLiquidity's address, address), `pool` (The pool, address), `base_in` (Base, in base units, uint).
- `terms`: A pool's terms. Params: `contract` (Your own CreditMatchedLiquidity's address, address), `pool` (The pool, address).

Deploy your own:

- `base_token`: Your mock base token (LabCmlToken: the reference's MockToken, its setters yours alone). Params: `decimals` (Its decimals, uint, optional, default 18).
- `price_feed`: Your mock price feed (LabCmlPriceFeed: the reference's MockPriceFeed, set by you alone). Params: `decimals` (Its decimals, uint, optional, default 8), `answer` (Its first answer (100000000 is 1.00 with 8 decimals), int).
- `position_manager`: Your mock position manager (LabCmlPositionManager: the reference's MockPositionManager, its pools and fees set by you alone). No params.
- `escrow`: Your own CreditMatchedLiquidity: you are its lender. Params: `lender` (The lender (you), address), `manager` (Your mock position manager (LabCmlPositionManager), address), `fallback` (The lender's fallback address, address), `quote` (The quote token (empty: the test dollar), address, optional, default (the lab's)), `registry` (The identity registry (empty: the testnet ERC-8004 one), address, optional, default (the lab's)).

## With an agent

- MCP (Streamable HTTP): https://lab.priors.trade/mcp. Tools for this draft: `lab_erc(8465)`, `lab_setup(address)`, `lab_read(8465, read, params)`, `lab_prepare(8465, action, params)`, `lab_deploy_prepare(8465, deploy, params)`, `lab_check_contract(8465, chain, address)`, `lab_feedback_draft(8465, note, txHash?)`.
- HTTP: https://lab.priors.trade/openapi.json describes GET https://lab.priors.trade/api/lab/erc/8465, /api/lab/read/8465/{read}, /api/lab/prepare/8465/{action}, /api/lab/deploy/8465/{deploy}, /api/lab/check/8465/{chain}/{address} and /api/lab/setup/{address}.

A prompt to give your agent:

```text
Use the Priors lab on Robinhood Chain testnet (chain id 46630), where every token is a test token of no value. Its MCP server is https://lab.priors.trade/mcp and this draft's page is https://lab.priors.trade/erc/8465.md. Try ERC-8465, Credit-Matched Liquidity (a draft under review). Start with lab_setup for my testnet address to see my test ETH, test dollars and identities, then fund your escrow with test dollars (as its lender), then mint your mock base token, then set a pool on your mock position manager, then set your mock price feed, then list a pool with its terms (as the lender), then let the escrow take your base token with lab_prepare(8465, <action>, params). Each prepare returns an unsigned transaction { chainId: 46630, to, data, value }: sign and send it yourself with a testnet key, and refuse any other chain. Never ask me for a key. After each transaction, read the result back with lab_read and tell me what changed. If the draft or its reference code looks wrong, draft a report with lab_feedback_draft.
```

## Check a contract

Any address on testnet or mainnet: supportsInterface(0x5ff5a6f0) and the views, read only (`lab_check_contract`, or GET https://lab.priors.trade/api/lab/check/8465/{chain}/{address}).

## Feedback

The discussion of the text belongs in its thread: https://ethereum-magicians.org/t/erc-8465-credit-matched-liquidity/30039. A bug in the reference implementation goes to its repository's issues: https://github.com/priors-agents/erc-credit-matched-liquidity/issues (a new one: https://github.com/priors-agents/erc-credit-matched-liquidity/issues/new). The text under review: https://github.com/ethereum/ERCs/pull/2109/files. `lab_feedback_draft` writes the report; the lab posts nothing for anyone.
