Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
189 lines
9.8 KiB
Markdown
189 lines
9.8 KiB
Markdown
# Aere Network ERC-7683 Destination Settler
|
|
|
|
The receiving side of cross-chain intents on Aere Network. This document explains how
|
|
the origin and destination halves compose, the solver flow, the repayment proof path,
|
|
and the honest status of what is on-chain today versus what needs the off-chain solver
|
|
and finality prover.
|
|
|
|
Contract: `aerenew/contracts/contracts/intents/AereDestinationSettler.sol`
|
|
Interface: ERC-7683 `IDestinationSettler` (`contracts/intents/IERC7683.sol`)
|
|
Test: `aerenew/contracts/test/destination-settler.test.js` (4 passing)
|
|
|
|
## Why a destination settler at all
|
|
|
|
Aere Network already ships the ORIGIN side of ERC-7683: `AereSpokePool`
|
|
(`contracts/intents/AereSpokePool.sol`) is an `IOriginSettler`. A user locks input
|
|
tokens on the origin chain and emits an `Open` event that declares the OUTPUT leg they
|
|
want on the destination chain (token, amount, recipient). What was missing is the
|
|
DESTINATION half: the contract a solver calls to actually deliver that output and to
|
|
record the delivery so the solver can be repaid. `AereDestinationSettler` is that half.
|
|
|
|
An intent design like this removes the need to bootstrap bridge liquidity. A classic
|
|
lock-and-mint bridge must pre-fund destination-side liquidity before it can serve a
|
|
single transfer. An intent bridge does not: the SOLVER fronts their own liquidity to
|
|
fill the order, and is repaid the locked input on the origin chain after the fill is
|
|
proven final. Aere Network therefore never has to seed or custody bridge liquidity; a
|
|
competitive market of solvers supplies it per intent.
|
|
|
|
## How origin and destination compose
|
|
|
|
```
|
|
ORIGIN CHAIN AERE NETWORK (destination, chain 2800)
|
|
------------ --------------------------------------
|
|
User -> AereSpokePool.open()
|
|
locks input, emits Open
|
|
(declares output leg) ........> Solver reads Open, calls
|
|
AereDestinationSettler.fill(orderId,
|
|
originData, fillerData)
|
|
- delivers output to recipient
|
|
- records the fill
|
|
- emits Filled
|
|
(fill is included in an Aere block)
|
|
|
|
|
Aere QBFT finalizes the block
|
|
|
|
|
Origin-side verifier <........ SP1 proof: "this Filled log was included
|
|
(AereOutbound- in a QBFT-final Aere block", validator-set
|
|
VerifierV2 pattern) anchored (off-chain prover + on-chain verify)
|
|
repays the solver the
|
|
locked input
|
|
```
|
|
|
|
`originData` is the origin settler's declared output leg. `AereSpokePool` builds it (see
|
|
`_buildResolved`) as:
|
|
|
|
```solidity
|
|
abi.encode(bytes32 orderId, address outputToken, uint256 outputAmount, bytes32 recipient)
|
|
```
|
|
|
|
`fillerData` is the solver's actual delivery plus their origin-chain repayment address:
|
|
|
|
```solidity
|
|
abi.encode(address deliveredToken, uint256 deliveredAmount, address deliveredRecipient, bytes32 repaymentAddress)
|
|
```
|
|
|
|
## The solver flow
|
|
|
|
1. LOCK on origin. The user calls `AereSpokePool.open()` (or the gasless `openFor` /
|
|
`openForPQC`), locking input and emitting `Open` with the declared output leg.
|
|
2. FILL on destination. A solver calls `AereDestinationSettler.fill(orderId, originData,
|
|
fillerData)` on Aere Network. The settler pulls the output token from the solver and
|
|
delivers it straight to the recipient (it never custodies the output), records the
|
|
fill, and emits `Filled`.
|
|
3. PROVE and REPAY on origin. Once the fill's block is QBFT-final on Aere, an SP1 proof
|
|
of finality plus log inclusion is generated off-chain and submitted to an origin-side
|
|
verifier, which repays the solver the locked input. No trusted relayer is required;
|
|
the proof is validator-set anchored.
|
|
|
|
## On-chain fail-closed guarantees
|
|
|
|
`fill(orderId, originData, fillerData)` reverts, and records nothing, when:
|
|
|
|
| Condition | Error |
|
|
| --- | --- |
|
|
| `orderId` argument does not equal the id inside `originData` | `OrderIdMismatch` |
|
|
| Declared recipient is zero | `ZeroRecipient` |
|
|
| Declared output token is zero, or declared amount is zero | `ZeroAddress` / `ZeroAmount` |
|
|
| Order already filled once | `AlreadyFilled` |
|
|
| Delivered token or recipient does not match the declared output, or delivered amount is short | `OutputMismatch` |
|
|
|
|
A fill that delivers the wrong token, to the wrong recipient, or short can never be
|
|
recorded, and an order can be filled at most once, so a solver can never be repaid twice
|
|
for a single intent. Delivery is atomic: the output is pulled from the solver and sent
|
|
to the recipient with OpenZeppelin `SafeERC20` in the same call, after the record is
|
|
written (checks, effects, interactions; `nonReentrant`).
|
|
|
|
After a successful fill the settler exposes:
|
|
|
|
- `fills(orderId)` / `getFill(orderId)`: the full fill record (filler, output token,
|
|
amount, recipient, timestamp, block, repayment address).
|
|
- `isFilled(orderId)`: whether the order has been filled.
|
|
- `fillCommitment(orderId)`: the canonical repayment commitment (see below).
|
|
|
|
## Repayment proof path (wiring to Aere's QBFT finality)
|
|
|
|
The origin chain must learn "this exact fill was included in a block that is FINAL on
|
|
Aere Network" without trusting a relayer. Aere Network already has the machinery, and
|
|
the settler is built to plug into it.
|
|
|
|
1. FINALITY prover. `contracts/interop/AereZkQbftLightClient.sol` (live on consuming
|
|
chains at `0xCaDA54FA...6488`) proves, in the SP1 zkVM (guest
|
|
`qbft-lightclient-guest`), that one Aere header is QBFT-final: it decodes the Besu
|
|
QBFT `extraData`, recomputes both Besu hash pre-images, ecrecovers each committed
|
|
seal, and asserts the distinct-validator count reaches the QBFT quorum `ceil(2N/3)`
|
|
(5-of-7 for Aere's live N=7 set), binding to an IMMUTABLE validator-set anchor. The
|
|
settler declares this interface as `IAereQbftFinalityLightClient` for reference. This
|
|
prover runs on the consuming/origin chain, not on Aere Network (Aere is native there),
|
|
so the settler does not call it on-chain.
|
|
2. INCLUSION plus delivery. `contracts/interop/AereOutboundVerifierV2.sol` is the
|
|
origin-side pattern that CONSUMES such a proof. It accepts an SP1 proof that a
|
|
specific Aere log was included in a QBFT-final Aere block (finality plus receipt
|
|
inclusion, one guest), binds the proof's committed `validatorSetRoot` to its own
|
|
immutable anchor, enforces exactly-once replay, and only then acts. An origin-side
|
|
verifier built on this pattern trusts THIS settler as the source address and
|
|
`FILLED_EVENT_SIG` as the event, re-derives `fillCommitment` from the proven log, and
|
|
repays the solver.
|
|
|
|
The on-chain surface the settler must provide for that path is the `Filled` event, the
|
|
persisted `FillRecord`, and the `fillCommitment` binder. It provides all three.
|
|
|
|
`fillCommitment(orderId)` binds:
|
|
|
|
```solidity
|
|
keccak256(abi.encode(
|
|
AERE_CHAIN_ID, // 2800, matching AereOutboundVerifierV2.AERE_CHAIN_ID
|
|
address(this), // this settler
|
|
orderId,
|
|
filler, // solver, repaid on the origin chain
|
|
outputToken,
|
|
outputAmount,
|
|
recipient,
|
|
repaymentAddress // solver's origin-chain repayment address
|
|
))
|
|
```
|
|
|
|
### Proof shape
|
|
|
|
The origin-side proof is exactly the shape `AereOutboundVerifierV2` already documents: an
|
|
SP1 Groth16 proof over Aere QBFT finality plus log inclusion, with the guest committing
|
|
`validatorSetRoot` into its public values and the on-chain verifier binding that root to
|
|
an immutable anchor. Public values are the 288-byte (9 word) `abi.encode` of `(uint64
|
|
chainId, uint64 blockNumber, bytes32 blockHash, bytes32 root, uint8 claimKind, bytes32
|
|
commitment, bytes32 validatorSetRoot, bool finalized, bool falconVerified)`. The pure
|
|
finality proof consumed by `AereZkQbftLightClient` is the narrower 192-byte (6 word)
|
|
form. The exact receipt-trie inclusion encoding for this settler's `Filled` log is
|
|
[VERIFY: confirm against the final AereOutboundVerifierV2 guest].
|
|
|
|
## Honest status
|
|
|
|
ON-CHAIN and tested today (`test/destination-settler.test.js`, 4 passing on Hardhat):
|
|
|
|
- A valid fill delivers the output to the recipient, records the fill, emits `Filled`,
|
|
and exposes the record plus `fillCommitment`.
|
|
- A duplicate fill reverts (`AlreadyFilled`).
|
|
- A mismatched-output fill reverts (`OutputMismatch`), tested for short amount, wrong
|
|
recipient, and wrong token.
|
|
- A zero-recipient fill reverts (`ZeroRecipient`).
|
|
|
|
NOT on one chain, marked [MEASURE]:
|
|
|
|
- The end-to-end cross-chain flow (off-chain SP1 prover run over the
|
|
`qbft-lightclient-guest` ELF, plus the origin-chain verifier deployment that repays the
|
|
solver) is NOT exercised by this contract or its unit test. It needs the separate
|
|
off-chain prover and a second chain. This is a settlement / interop composition, not a
|
|
claim of a live cross-chain transfer.
|
|
- The exact receipt-trie inclusion encoding is [VERIFY: confirm against the final
|
|
AereOutboundVerifierV2 guest].
|
|
|
|
SCOPE. This is a settlement / interop-layer contract. It does not touch, gate, or change
|
|
Aere Network consensus, which remains classical secp256k1 ECDSA QBFT. The finality proof
|
|
above is a proof ABOUT that QBFT finality, verified classically today. That is the
|
|
honest and intended posture, not a post-quantum consensus claim. The `falconVerified`
|
|
slot in the proof public values is recorded and required to be `false` on mainnet.
|
|
|
|
Repayment security reduces to the honesty of at least `2f+1` of Aere Network's QBFT
|
|
validators (5-of-7 today, all Foundation-operated: an operator assumption, not an
|
|
economic one) plus a sound SP1 / Groth16 verifier and the correct validator-set anchor.
|
|
Recording a fill here is permissionless and does not, by itself, move any origin funds;
|
|
repayment happens only on the origin chain and only behind the proof.
|