For the complete documentation index, see llms.txt. This page is also available as Markdown.

FlapVaultEx

Overview

FlapVaultEx is an asynchronous, intent-based settlement layer that lets a vault swap any token for any other token without knowing anything about DEX routing. The vault submits an intent — "sell this much of inputToken, I want outputToken" — and Flap's off-chain executor sources the best route across DEX aggregators and settles it back on-chain.

It bridges on-chain custody with off-chain routing:

  • The contract custodies the input and records a PENDING request.

  • The off-chain executor finds the best route and calls fulfillSwap with the route calldata.

  • The contract measures the real output by balance delta, enforces an on-chain minimum, takes the fee, and delivers the result straight back to the vault via a callback — atomically.

There are no signatures and no routing logic in the vault. Routes are bound by on-chain whitelists, output is verified by balance delta, and the executor can only settle — never redirect — the funds.

Deployed addresses:

Network
FlapVaultEx (proxy)
Max Callback Gas

BNB Chain Mainnet

0x000048Df69f3d2af22Df6DC2d3B01FB592b90000

200,000

Primary use cases:

  • Converting tax/fee revenue held in an arbitrary token into a stablecoin or a target dividend asset

  • Consolidating multiple fee tokens into one

  • Rebalancing a vault's holdings without building or auditing DEX routing

How it works

Step by step:

  1. The vault calls requestSwap() with {inputToken, outputToken, inputAmount}, transferring the input ERC-20 into FlapVaultEx via approval.

  2. FlapVaultEx records the request as PENDING and emits FlapVaultExRequested.

  3. The off-chain executor indexes the event, quotes across DEX aggregators, and picks the best route.

  4. The executor calls fulfillSwap() with the winning route (target, allowanceTarget, data) and a minOutputAmount floor.

  5. FlapVaultEx approves exactly the input amount, performs the router call, resets the approval, and measures the received output by balance delta.

  6. If the output is at least minOutputAmount, it takes the fee and delivers the net output to the vault, then calls onFlapVaultExSettled(...). If anything fails, the whole call reverts.


Request lifecycle

Status
Value
Meaning

NONE

0

No such request.

PENDING

1

Input custodied; awaiting settlement or a refund.

FULFILLED

2

Swapped; output delivered to the vault.

REFUNDED

3

Input returned to the vault (fee taken from the input side).

FAILED

4

A failed batch item whose automatic refund could not be delivered; retryable via refundSwap.

Requests never expire — there is no deadline. A backlogged order can always be settled later, so a vault is never punished because gas was high or a route was temporarily unavailable.


Fees

FlapVaultEx charges a small protocol fee, currently 0.5% (50 bps). The fee exists primarily to discourage abuse — creating requests only to immediately refund them for free, or spamming requests the executor has to process.

How the fee is taken:

  • It is charged on the quote-token side: if the input is a configured quote token (e.g. USDT/USDC/WBNB) and the output is not, it's taken from the input; if the output is the quote, from the output; if both or neither, it falls back to the admin-configured side.

  • The rate is snapshotted at requestSwap. Settlement always uses the snapshotted rate, so a fee change never affects an in-flight request.

  • A refund also takes the snapshotted feeBps, always from the input side (a refund has no output) — so a request → refund round trip is not free.


Callback gas limit

Callbacks run with a bounded gas budget — 200,000 on the current deployment — so settlement can never be griefed by an expensive callback. Keep onFlapVaultExSettled cheap; it should record the result, not do heavy work. Read the live value via maxCallbackGas().


How to integrate

Step 1 — Import the interfaces

Step 2 — Implement IFlapVaultExConsumer

Your vault must implement onFlapVaultExSettled. It is called for both outcomes, so branch on status:

  • status == 2 (FULFILLED): the output token was delivered, net of the fee.

  • status == 3 (REFUNDED): the input token was returned, net of the input-side fee.

In both cases the funds are already in your vault when the callback runs. Validate the caller, then do whatever your accounting needs.

Step 3 — Trigger a swap

Do not let the caller pick the tokens or amount. Fix the parameters at initialization (like the other vault examples in this repo) and expose a no-argument swap() that just swaps what the vault actually holds. Revenue (tax payouts, direct transfers) accumulates in the vault, so read the live balance — balanceOf(address(this)) — and only create a request once it is worth it.

Keep the returned requestId (or index it from FlapVaultExRequested) so you can correlate the callback.

Example vault

Notes:

  • Tokens and the swap threshold are fixed at initialization; swap() takes no arguments and uses the live balanceOf(address(this)), so a caller can never steer the vault into selling the wrong asset or amount.

  • No-arg swap() is safe to leave callable by anyone (it only touches the vault's own revenue) — restrict it with an owner/operator check if your design needs to.

  • Approve FlapVaultEx for exactly the amount you swap (it pulls exactly that; fee-on-transfer inputs are rejected).

  • Branch on status — the same callback fires whether the swap settled or the input was refunded — and gate it on msg.sender == flapVaultEx plus your own request-id tracking.


Native (BNB/ETH) input and output

FlapVaultEx only swaps ERC-20 tokens — native is disabled. So if your vault wants to accept or pay out native BNB, wrap it into WBNB yourself (and unwrap on the way out):

  • Native input (sell native): wrap the vault's native balance into WBNB with deposit{value: amount}(), then request a swap from WBNB to your target token.

  • Native output (buy native): request a swap from your token to WBNB, then unwrap the WBNB you receive in the callback back into BNB with withdraw(amount).

Native input — sell native for an ERC-20

Vault receives native, wraps it, then requests WBNB → outputToken:

On the refund path the input comes back as WBNB; call IWETH(WBNB).withdraw(amount) if the vault wants native again.

Native output — buy native with an ERC-20

Vault requests inputToken → WBNB, then unwraps the delivered amount in the callback:


Reference

Full interface

Last updated