RFC-7: Mezo bridge intents#701
Conversation
Introduces an intent layer on top of the existing bridge so a single user action can express "bridge these assets and do X with them on the other side". The design follows the shape of ERC-7683 so generic third-party fill infrastructure can plug in without protocol-specific code. Covers the symmetric Mezo<->Ethereum design: bridge-with-payload entry points on MezoBridge that carry an opaque payload, a companion PayloadRecorded event, and a registry-driven bridge-hook mechanism that fires on allowlisted recipients after each transfer completes - kept orthogonal to the intent layer. Per-protocol Resolvers act as recipient, hook target, ERC-7683 resolve() surface, custodian, and executor for their Orders, with no central settler or shared custodian. Solvers are untrusted, permissionless off-chain actors that watch bridge events and submit fills. Includes a worked remote-borrow example showing how a generic Solver discovers and fills an Order without protocol-specific knowledge. Also captures the rollout shape on a live chain (coordinated halt via the Upgrade primitive paired with the MezoBridge proxy upgrade, additive WithPayload events) and additional considerations left open for later: optimistic fill, gasless bridge-with-payload, a public discovery layer, and Morpho V2 cross-chain markets.
| AssetsBridge.bridgeOutWithPayload( | ||
| BTC, // token | ||
| btcAmount, // amount | ||
| ETHEREUM, // chain |
There was a problem hiding this comment.
Do we need the target chain argument?
bridgeOutWithPayload will only work when bridging to Ethereum as the provided recepient (resolver) must be a smart contract. Unless we want to keep the target chain for the sake of completeness (ordinary bridgeOut has target chain) and revert if Bitcoin is provided here.
There was a problem hiding this comment.
Kept the chain argument for parity with bridgeOut and made it revert for any non-Ethereum target. f74d4f4
| IPool(AAVE_POOL).supply(TBTC, e.amount, address(this), 0); | ||
| IPool(AAVE_POOL).borrow(USDC, p.usdcToBorrow, 2, 0, address(this)); | ||
|
|
||
| IERC20(USDC).transfer(msg.sender, p.solverFee); |
There was a problem hiding this comment.
Nitpick: this should probably be
| IERC20(USDC).transfer(msg.sender, p.solverFee); | |
| IERC20(USDC).transfer(solverPayoutAddress, p.solverFee); |
where solverPayoutAddress is the address that gets the solver's payment (fee for calling fill()). It should be passed as an argument to fill().
The resolve() function lets the solver choose the payment recipient:
bytes memory varRecipient = abi.encodeWithSignature("PaymentRecipient()");
There was a problem hiding this comment.
fill now takes the payout address and pays it, and resolve threads variable 0 (PaymentRecipient) into the step. 1a39b45
| 2. Looks up the matching `PayloadRecorded(payloadHash, payload)` on the | ||
| origin chain to get the plain payload bytes. | ||
|
|
||
| `payloadHash` is **not** guaranteed unique across pending entries by the |
There was a problem hiding this comment.
We probably should add the sequence number when emitting PayloadRecorded events:
PayloadRecorded(uint256 indexed sequence, bytes32 indexed payloadHash, bytes payload)
There was a problem hiding this comment.
Added sequence as an indexed field and re-keyed the generic discovery steps on it. The worked example stays on payloadHash since it enforces uniqueness itself. e420d78
|
I haven't had a chance to look into the proposal yet, but flagging that we should look at Morpho Midnight and ensure the approach is compatible. Midnight is architecturally an intent protocol already - the offer happens off-chain with on-chain settlement, separating the expression of intent from sourcing capital. We can't use the existing Morpho Blue as-is for remote-sourcing of capital - the liquidity needs to be there. Morpho Midnight solves the liquidity fragmentation within a single chain but it's a very similar async pattern to what we are trying to build to source the capital from the outside. |
The payload variant delivers funds to a Resolver, which is necessarily an EVM contract, so Ethereum is the only valid target chain. Document that the method keeps the chain argument for parity with bridgeOut and reverts when any other target (such as Bitcoin) is passed, rather than dropping the argument.
The resolved order advertises a PaymentRecipient variable so a Solver can direct its fee to an address other than the submitting account, but the example's fill paid msg.sender and never threaded that variable into the step call. Pass the payout address into fill and transfer the fee to it, so the variable the order exposes is the one that gets paid.
A payload hash is not unique across bridge entries, so correlating an order by hash is ambiguous when two transfers carry the same payload. Add the monotonic bridge sequence as an indexed field on PayloadRecorded and key the generic order-discovery steps on it, since it is the only bridge-guaranteed unique identifier. The worked example keeps hash-based keying because it enforces payload uniqueness itself.
The bridge operates on registry-allowlisted hook targets and has no notion of Resolvers, so the payload variant is not restricted to Resolver recipients. State the actual constraint: a payload is only actionable where the recipient is a contract that can receive the bridge hook, which means an EVM chain, so Ethereum is the only valid target.
|
I dug into Midnight a bit, and I don't think it competes with what RFC-7 does - they sit at different levels. RFC-7 is really just a transport intent: bridge these assets and do something with them on the other side. It has no idea what lending is and doesn't care where the liquidity comes from. Midnight is the lending intent itself - off-chain offers that don't tie up capital, settled on-chain, with liquidity pulled in only at settlement. So Midnight fits inside an RFC-7 intent rather than sitting next to it. The pieces line up well. A Midnight-backed Resolver receives the bridged collateral, the Solver plays the Midnight taker - finding lender offers off-chain and handing them to the Resolver to settle the loan - and then the borrowed funds bridge back. Those offers are just the kind of off-chain input a Solver already has to go discover, so there's nothing new on the bridge side; Midnight is one more Resolver. The only real shift is that the user expresses terms (a max rate, a term) instead of a fixed borrow amount, since the rate falls out of whatever offers are around at settlement. And the "liquidity has to be there" worry mostly goes away - RFC-7 sources it at fill time, which is exactly how Midnight works. |
Closes: MEZO-4500
Introduction
The Mezo bridge today moves BTC and ERC20 assets between Ethereum and Mezo as plain value transfers — the user picks an asset, an amount, and a recipient, the bridge mints or releases on the other side, and the flow ends there. A growing class of cross-chain use cases needs more than that: a Mezo user may want to deposit BTC as collateral on an Ethereum lending market and receive the borrowed stablecoin back on Mezo in a single user-facing action, without ever custodying funds on Ethereum. Today those flows require either a custom bridge integration per protocol or a series of manual steps from the user. This PR adds RFC-7, a design proposal for an intent layer on top of the existing bridge so a single user action can declaratively express "bridge these assets and do X with them on the other side". The design follows the shape of ERC-7683 so generic, permissionless third-party fill infrastructure can plug in without protocol-specific code. This is a documentation-only proposal; no protocol code ships in this PR.
Changes
docs/rfc/rfc-7.mdA new RFC covering the symmetric Mezo↔Ethereum intent design:
MezoBridgethat carry an opaque payload alongside a transfer, plus a companionPayloadRecordedevent for observability.resolve()surface, custodian, and executor for their own Orders, with no central settler or shared custodian.MezoBridgeproxy upgrade, and additiveWithPayloadevents.Testing
Documentation-only change — no executable code ships in this PR.
markdownlint docs/rfc/rfc-7.md --config .markdownlint.ymlpasses.Author's checklist
docs/) or specification (x/<module>/spec/)Assigneesfieldmezod-developersin theReviewersfield and notified them on DiscordReviewer's checklist