aere-docs/AERE-ERC7683-DESTINATION.md
Aere Network e4cead319d Initial public release
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.
2026-07-20 01:01:36 +03:00

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.