This proposal defines an interface for a contract that lends an ERC-20 quote token into a concentrated-liquidity position that a holder pairs with base tokens it owns. The position, an ERC-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.
ERC-8465 · draft under review
Credit-Matched Liquidity
A lender funds the quote side of a holder's pool position in an escrow, repaid from fees first, unwound on an oracle price.
StatusTestnet only
Guided in the app: Credit-matched liquidity.
- Pull request
- ethereum/ERCs #2109 (the text under review)
- Text pinned at
ERCS/erc-8465.mdat 8940a9d
8940a9da9578026fd578b5d3a4c13ca57bdfdb89- Discussion
- Its thread on Ethereum Magicians
- Reference
- priors-agents/erc-credit-matched-liquidity at 2f1d864
2f1d8642c88ce71a00cfbc373b24ebe29c1daf28 - Markdown
- /erc/8465.md
What it is
Specification headings
- Definitions
- Interface
- Tokens, pools and prices
- Opening
- Escrow and terms
- Health
- Fees
- Closing
- Unwinding
- Hand-over
The abstract and headings above are the text at 8940a9d, CC0. Read the full text at that commit.
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 Robinhood Chain testnet (46630), with the reference contracts at their pinned commits and test tokens of no value. Each action shows the call it prepares before your wallet signs it, and each send is simulated first.
Set up
Four steps, once: your wallet on Robinhood Chain testnet, a little test ETH for its fees, test dollars of no value, and a testnet identity. The forms below fill from them.
- 1
Wallet
Not connected. Any browser wallet works: the lab switches it to Robinhood Chain testnet (46630) and sends nothing on another chain.
- 2
Test ETH
For the testnet's network fees. The lab's drip sends a little to a connected wallet once a day.
- 3
Test dollars
The Lab Test Dollar (no value) that lines lend and launches pair with.
- 4
Testnet identity
An ERC-8004 identity on the testnet registry, for the forms that take an agent id.
The testnet contracts
| Contract | Address on testnet (46630) |
|---|---|
| The lab's test dollar (LabTestDollar) | 0x64A419191369E6a79d562A65336073ec1bfbf4BD |
Not audited. Never deploy without an external audit. Deploy your own only, on testnet: your mock base token, price feed and position manager (no one else can set them) and your escrow over them, in Deploy your own; then the actions here.
Actions
Reads
Deploy your own
Deploy the reference contract from its pinned commit with your own wallet, on the testnet: you become its lender, launchpad or backer. Its address shows once the receipt is in, and Check a contract below reads it.
Its reference's README: “Not audited. Never deploy without an external audit.”
Not audited. Never deploy without an external audit. Deploy your own only, on testnet: your mock base token, price feed and position manager (no one else can set them) and your escrow over them, in Deploy your own; then the actions here.
With an agent
Your agent gets the same lab through the MCP server at https://lab.priors.trade/mcp or the HTTP API described by /openapi.json. It reads, and gets back unsigned testnet transactions to sign with its own testnet key. The lab holds no key of its own for you and never asks for one.
MCP tools for ERC-8465
lab_erc(8465)- What the draft does on the lab, its contracts and an agent's next calls.
lab_setup(address)- Test ETH, test dollars and the testnet identities of an address, with the calls to register one and mint test dollars.
lab_read(8465, "position", { contract, position_id })- A position and its health.
lab_read(8465, "quote_limit", { contract, pool, base_in })- The most quote a position may borrow for a base amount.
lab_read(8465, "terms", { contract, pool })- A pool's terms.
lab_prepare(8465, "fund", { contract, amount })- Fund your escrow with test dollars (as its lender). Who signs: the lender
lab_prepare(8465, "mint_base", { base, to, amount })- Mint your mock base token. Who signs: anyone (at most a million whole tokens a call)
lab_prepare(8465, "set_mock_pool", { manager, pool, base, quote, price })- Set a pool on your mock position manager. Who signs: the manager's owner (who deployed it)
lab_prepare(8465, "set_mock_price", { feed, answer, updated_at })- Set your mock price feed. Who signs: the feed's owner (who deployed it)
lab_prepare(8465, "list_pool", { contract, pool, base, price_feed, status_source, max_price_age, limit_bps, unwind_bps, gap_bps, charge_bps, term, handover_delay, base_decimals })- List a pool with its terms (as the lender). Who signs: the lender
lab_prepare(8465, "approve_base", { contract, base, amount })- Let the escrow take your base token. Who signs: the holder
lab_prepare(8465, "open", { contract, pool, base_in, quote_to_borrow, tick_lower, tick_upper, agent_id })- Open a position: your base, the lender's quote. Who signs: the holder (the identity's owner when one is named)
lab_prepare(8465, "apply_fees", { contract, position_id })- Apply a position's fees (the lender first). Who signs: anyone
lab_prepare(8465, "unwind", { contract, position_id })- Unwind a position on the oracle price. Who signs: anyone, once it is unwindable
lab_prepare(8465, "approve_repay", { contract, amount })- Let the escrow take what a position owes (before close). Who signs: the holder
lab_prepare(8465, "close", { contract, position_id })- Close a position (repay and take it back). Who signs: the holder
lab_prepare(8465, "withdraw", { contract, to, amount })- Withdraw unlent test dollars (as the lender). Who signs: the lender
lab_prepare(8465, "set_lender_fallback", { contract, fallback })- Set the lender's fallback address. Who signs: the lender
lab_deploy_prepare(8465, "base_token", { decimals })- Your mock base token (LabCmlToken: the reference's MockToken, its setters yours alone).
lab_deploy_prepare(8465, "price_feed", { decimals, answer })- Your mock price feed (LabCmlPriceFeed: the reference's MockPriceFeed, set by you alone).
lab_deploy_prepare(8465, "position_manager", { })- Your mock position manager (LabCmlPositionManager: the reference's MockPositionManager, its pools and fees set by you alone).
lab_deploy_prepare(8465, "escrow", { lender, manager, fallback, quote, registry })- Your own CreditMatchedLiquidity: you are its lender.
lab_check_contract(8465, chain, address)- The conformance read of any contract, on testnet or mainnet: supportsInterface and the views.
lab_feedback_draft(8465, note, txHash?)- The report text and the thread and issue links. It posts nothing.
HTTP
GET https://lab.priors.trade/api/lab/erc/8465GET https://lab.priors.trade/api/lab/read/8465/position?contract=<contract>&position_id=<position_id>GET https://lab.priors.trade/api/lab/read/8465/quote_limit?contract=<contract>&pool=<pool>&base_in=<base_in>GET https://lab.priors.trade/api/lab/read/8465/terms?contract=<contract>&pool=<pool>GET https://lab.priors.trade/api/lab/prepare/8465/fund?contract=<contract>&amount=<amount>GET https://lab.priors.trade/api/lab/prepare/8465/mint_base?base=<base>&to=<to>&amount=<amount>GET https://lab.priors.trade/api/lab/prepare/8465/set_mock_pool?manager=<manager>&pool=<pool>&base=<base>"e=<quote>&price=<price>GET https://lab.priors.trade/api/lab/prepare/8465/set_mock_price?feed=<feed>&answer=<answer>&updated_at=<updated_at>GET https://lab.priors.trade/api/lab/prepare/8465/list_pool?contract=<contract>&pool=<pool>&base=<base>&price_feed=<price_feed>&status_source=<status_source>&max_price_age=<max_price_age>&limit_bps=<limit_bps>&unwind_bps=<unwind_bps>&gap_bps=<gap_bps>&charge_bps=<charge_bps>&term=<term>&handover_delay=<handover_delay>&base_decimals=<base_decimals>GET https://lab.priors.trade/api/lab/prepare/8465/approve_base?contract=<contract>&base=<base>&amount=<amount>GET https://lab.priors.trade/api/lab/prepare/8465/open?contract=<contract>&pool=<pool>&base_in=<base_in>"e_to_borrow=<quote_to_borrow>&tick_lower=<tick_lower>&tick_upper=<tick_upper>&agent_id=<agent_id>GET https://lab.priors.trade/api/lab/prepare/8465/apply_fees?contract=<contract>&position_id=<position_id>GET https://lab.priors.trade/api/lab/prepare/8465/unwind?contract=<contract>&position_id=<position_id>GET https://lab.priors.trade/api/lab/prepare/8465/approve_repay?contract=<contract>&amount=<amount>GET https://lab.priors.trade/api/lab/prepare/8465/close?contract=<contract>&position_id=<position_id>GET https://lab.priors.trade/api/lab/prepare/8465/withdraw?contract=<contract>&to=<to>&amount=<amount>GET https://lab.priors.trade/api/lab/prepare/8465/set_lender_fallback?contract=<contract>&fallback=<fallback>GET https://lab.priors.trade/api/lab/deploy/8465/base_token?decimals=<decimals>GET https://lab.priors.trade/api/lab/deploy/8465/price_feed?decimals=<decimals>&answer=<answer>GET https://lab.priors.trade/api/lab/deploy/8465/position_managerGET https://lab.priors.trade/api/lab/deploy/8465/escrow?lender=<lender>&manager=<manager>&fallback=<fallback>"e=<quote>®istry=<registry>GET https://lab.priors.trade/api/lab/check/8465/<chain>/<address>GET https://lab.priors.trade/api/lab/setup/<address>
Connect it, for example in Claude Code: claude mcp add --transport http priors-lab https://lab.priors.trade/mcp
A prompt to copy
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
Paste any contract on either chain: the lab reads supportsInterface for ICreditMatchedLiquidity 0x5ff5a6f0 (computed from the pinned interface) and ERC-165's own, then calls each view. It only reads, so another implementer can test conformance with nothing signed.
Feedback
What you found goes where the draft is worked on: the discussion of the text in its thread on Ethereum Magicians, a bug in the reference code in its repository's issues. You post it under your own account. The lab posts nothing for anyone.