The published line and the local line of this repository had no common ancestor: the public one carried the hygiene pass (no host names, no internal paths), the local one carried a month of corrections that never shipped. This commit ports the local work onto the public line, keeping the public hygiene wording wherever the two touched the same sentence, and keeping the public version of AERE-CROSS-CLIENT-DETERMINISM.md entirely. Carried: LICENSE/LICENSING corrections, VERIFY-POLICY.md, CITATIONS-UNRESOLVED.md remeasured 2026-08-11, the 'audited' adjective removed from next to Bouncy Castle, citation paths rewritten to published form, AIP-8, the QA consolidation report, the second EIP validation pass, fork-height corrections, the AereSink / threshold-factory correction, the forge test floor, and the architecture-map updates.
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: `aere-contracts/contracts/intents/AereDestinationSettler.sol`
|
|
Interface: ERC-7683 `IDestinationSettler` (`aere-contracts/contracts/intents/IERC7683.sol`)
|
|
Test: `aere-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`
|
|
(`aere-contracts/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. `aere-contracts/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. `aere-contracts/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 (`aere-contracts/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.
|