Lucas de Almeida

FoxFi

An intent-based swap where solvers compete to fill a user's order.

Context
Self-study, 2025
Stack
Rust, Anchor
Links
codedemo

The problem

A user who swaps through a router picks the route and eats whatever MEV comes with it. With intents, the user says what they want ("at least X of token B for Y of token A, before T") and solvers compete to fill it.

How it works

user ── create_intent(in, min_out, expiry) ──► Intent PDA, input locked in a vault
solver ── register_solver(stake) ──► Solver PDA with reputation
solver ── submit_solution(out) ──► best output wins
winner ── execute_settlement ──► output to user, fees to protocol and solver
user ── cancel_intent (before execution) ──► input refunded

Fees are 0.15% total: 0.05% protocol and 0.10% to the solver, with a reputation score updated on every settlement.

Design decisions

  • Solvers stake to play. A minimum stake makes spamming fake solutions costly, and the stake is what a future slashing rule would take.
  • Only the winning solver settles. execute_settlement checks the signer against intent.winning_solver.
  • Expiry is enforced on-chain. Intents can't settle or be cancelled once they expire, using the Clock sysvar.

What could go wrong

When I came back to this project, it looked finished but didn't work. The first bid closed the auction, so there was no competition. Settlement paid the user their minimum (minus fees) instead of the winning quote, never released the locked input to the solver, and overflowed the BPF stack, so it couldn't run at all. The original tests never initialised the vault and didn't pass either.

VectorStatus
No real auctionFixed: bids accepted for 20 seconds; only strictly better quotes take the lead
User paid the minimum, not the quoteFixed: the user receives the winning quote minus a 0.05% fee, never below their minimum
Input never releasedFixed: the solver pays and receives the input in the same transaction
Solver pays someone elseFixed: settlement is bound to the intent's user and both mints
Tokens stuck after expiryFixed: refund before any bid, or after expiry if the winner didn't settle (the winner loses reputation)
Fake stakeFixed: the stake moves into the solver's account and comes back on exit
Front-running between solversOpen: bids are public; a commit-reveal round would hide them

What I learned

A complete-looking instruction list says nothing about whether the protocol works. Writing the tests as a user and as a competing solver is what showed that none of the economics held up.

Where it stands

A self-study project, deployed to Solana devnet with a rewritten test suite (7 tests). The demo has a faucet for two worthless test tokens; the user's wallet pays the fees and account rent, and the faucet only co-signs as mint authority, so scripted requests can't drain it.