Skip to content

RFC-7: Mezo bridge intents#701

Open
lukasz-zimnoch wants to merge 5 commits into
mainfrom
rfc-7
Open

RFC-7: Mezo bridge intents#701
lukasz-zimnoch wants to merge 5 commits into
mainfrom
rfc-7

Conversation

@lukasz-zimnoch

@lukasz-zimnoch lukasz-zimnoch commented May 29, 2026

Copy link
Copy Markdown
Collaborator

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.md

A new RFC covering the symmetric Mezo↔Ethereum intent design:

  • Bridge-with-payload entry points on MezoBridge that carry an opaque payload alongside a transfer, plus a companion PayloadRecorded event for observability.
  • A registry-driven bridge-hook mechanism that fires on allowlisted recipients after each transfer completes, kept orthogonal to the intent layer.
  • Per-protocol Resolvers that act as recipient, hook target, ERC-7683 resolve() surface, custodian, and executor for their own Orders, with no central settler or shared custodian.
  • Untrusted, permissionless Solvers that watch bridge events and submit fills, plus a worked remote-borrow example showing how a generic Solver discovers and fills an Order without protocol-specific knowledge.
  • The rollout shape on a live chain: a coordinated halt via the Upgrade primitive paired with the MezoBridge proxy upgrade, and additive WithPayload events.
  • Open considerations left for later: optimistic fill, gasless bridge-with-payload, a public discovery layer, and Morpho V2 cross-chain markets.

Testing

Documentation-only change — no executable code ships in this PR. markdownlint docs/rfc/rfc-7.md --config .markdownlint.yml passes.


Author's checklist

  • Provided the appropriate description of the pull request
  • Updated relevant unit and integration tests
  • Updated relevant documentation (docs/) or specification (x/<module>/spec/)
  • Assigned myself in the Assignees field
  • Assigned mezod-developers in the Reviewers field and notified them on Discord

Reviewer's checklist

  • Confirmed all author's checklist items have been addressed
  • Considered security implications of the code changes
  • Considered performance implications of the code changes
  • Tested the changes and summarized covered scenarios and results in a comment

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.
@linear-code

linear-code Bot commented May 29, 2026

Copy link
Copy Markdown

MEZO-4500

@lukasz-zimnoch lukasz-zimnoch self-assigned this May 29, 2026
@lukasz-zimnoch lukasz-zimnoch changed the title Add RFC-7: Mezo bridge intents RFC-7: Mezo bridge intents May 29, 2026
@lukasz-zimnoch lukasz-zimnoch marked this pull request as ready for review May 29, 2026 12:50
@lukasz-zimnoch lukasz-zimnoch requested a review from a team May 29, 2026 12:53
Comment thread docs/rfc/rfc-7.md
AssetsBridge.bridgeOutWithPayload(
BTC, // token
btcAmount, // amount
ETHEREUM, // chain

@tomaszslabon tomaszslabon Jun 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@lukasz-zimnoch lukasz-zimnoch Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept the chain argument for parity with bridgeOut and made it revert for any non-Ethereum target. f74d4f4

Comment thread docs/rfc/rfc-7.md Outdated
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);

@tomaszslabon tomaszslabon Jun 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: this should probably be

Suggested change
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()");

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fill now takes the payout address and pays it, and resolve threads variable 0 (PaymentRecipient) into the step. 1a39b45

Comment thread docs/rfc/rfc-7.md
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should add the sequence number when emitting PayloadRecorded events:

PayloadRecorded(uint256 indexed sequence, bytes32 indexed payloadHash, bytes payload)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@pdyraga

pdyraga commented Jun 8, 2026

Copy link
Copy Markdown
Member

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.
@lukasz-zimnoch

lukasz-zimnoch commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants