Intents & Batching

Paragon Flow upgrades swapping from “send a transaction and hope” to sign an intent, batch with others, settle with best execution, and share the surplus. It’s the engine behind Paragon Shield’s.

TL;DR

  • You sign an EIP-712 Intent (not a transaction).

  • A Batcher groups intents for a short window and asks Solvers to propose a settlement.

  • The best proposal is executed on-chain through FlowSettlement, which:

    • enforces your bounds (minOut, deadline, receiver),

    • computes surplus = amountOut − minOut,

    • splits surplus to Trader / LPs used / stXPGN lockers,

    • emits auditable events (Proof-of-Best-Execution).

If no solver can beat your bounds, the batch reverts for you (or, if you opted in, falls back to a normal router swap).


Why intents?

  • MEV resistance: batched clearing + private relay kills most sandwich vectors.

  • Better pricing: solvers can route multi-hop across multiple venues and cross-match flows.

  • Programmable refunds: surplus is shared to the right parties automatically.


Key actors

  • Trader: signs an Intent.

  • Batcher/Relay: groups intents, broadcasts to whitelisted solvers.

  • Solver(s): search for best settlement across Paragon pools (+ external DEXes later).

  • FlowSettlement: on-chain verifier/executor; distributes surplus to vaults.


The Intent (EIP-712)

// EIP-712 domain
const domain = {
  name: "ParagonFlow",
  version: "1",
  chainId, // BNB Testnet during beta
  verifyingContract: FLOW_SETTLEMENT_ADDRESS,
} as const;

// EIP-712 types
const types = {
  Intent: [
    { name: "sellToken", type: "address" },
    { name: "buyToken",  type: "address" },
    { name: "amountIn",  type: "uint256" },
    { name: "minOut",    type: "uint256" },
    { name: "deadline",  type: "uint256" },
    { name: "receiver",  type: "address" },
    { name: "prefs",     type: "uint256" }, // bitfield (privacy, fallback, slippage mode)
    { name: "nonce",     type: "uint256" },
  ],
} as const;

// Example value
const value = {
  sellToken, buyToken,
  amountIn,          // exact-in
  minOut,            // bound after fees
  deadline,          // block timestamp bound
  receiver: user,
  prefs,             // see "Preferences bitfield" below
  nonce,             // wallet-defined
};

Preferences bitfield (prefs)

  • 0..1 privacy: 0=Lite, 1=Private, 2=Stealth

  • 2 fallback: 0=No fallback (revert), 1=Fallback to router if solver fails

  • 3 cashback mode: 0=claim to wallet, 1=auto-stake to stXPGN

  • 4..7 reserved

You can cancel any outstanding intent by incrementing your nonce via FlowSettlement.cancel(nonce) or signing a replacement with a higher nonce.


Batching windows

  • Window: ~1 block on BNB (testnet setting), adaptive later.

  • Priority: FIFO within the same gas price band.

  • Max batch size: capped to keep gas predictable (configurable).

  • Liveness: if no valid solver submission before window end → optional fallback or revert.


Auction & scoring

Solvers submit a Settlement:

{ routeData, expectedOut, gasEstimate, lpTouchSet, proof }

Score = (expectedOut − minOut) − gasCostEquivalent − penalties

  • expectedOut − minOut → higher is better (more surplus).

  • gasCostEquivalent → convert gas to output token via oracle.

  • penalties → for unhealthy routes (too many hops, stale pools, unsafe external venues).

  • Tie-breakers: fewer hops → earlier timestamp → lower variance in sim.

Batcher picks the top-score, builds a bundle, and sends it to FlowSettlement.execute.


On-chain settlement (Solidity surface)

interface IFlowSettlement {
  struct Intent {
    address sellToken;
    address buyToken;
    uint256 amountIn;
    uint256 minOut;
    uint256 deadline;
    address receiver;
    uint256 prefs;
    uint256 nonce;
  }

  function execute(
    Intent calldata intent,
    bytes calldata signature,
    bytes calldata routeData // ABI-encoded hops & venues
  ) external returns (uint256 amountOut);

  function cancel(uint256 newNonce) external;

  event ShieldSubmitted(address indexed user, bytes32 intentHash);
  event ShieldSettled(
    address indexed user,
    bytes32 indexed intentHash,
    uint256 amountOut,
    uint256 surplus,        // amountOut - minOut (>=0)
    uint256 traderRebate,   // default 60%
    uint256 lpRebate,       // default 30%
    uint256 lockerCut       // default 10% -> stXPGN
  );
}

Settlement checks

  • signature & domain match

  • block.timestamp ≤ deadline

  • user allowance/transferFrom succeeds for amountIn

  • Router performs the routeData swaps atomically

  • amountOut ≥ minOut else revert (or fallback if allowed)


Surplus & payouts

  • Surplus: surplus = amountOut − minOut (0 if negative).

  • Splits (default):

    • 60% → Trader (instant claim or auto-stake to stXPGN)

    • 30%LPRebateVault (credited to pairs actually touched by the route)

    • 10%LockerVault (stXPGN / veFlow lockers)

  • Protocol can take a small cut (0–5%) of surplus before splits (DAO-governed).

LPRebateVault accounting

  • We snapshot pair, blockNumber, usedLiquidity for the settlement.

  • LPs claim proportionally to the liquidity they had in that block.


External venues (phased)

  • Phase 0 (testnet): Paragon pools only.

  • Phase 1: add whitelisted Pancake/Thena routes with per-venue caps and slashing bonds for executors.

  • Phase 2: open allow-list, higher caps, cross-DEX batch netting.


Fallbacks & failures

  • Revert (default) if no solver produces amountOut ≥ minOut.

  • Fallback to Router if the prefs bit is set. UI shows: “Shield unavailable, executed via Router.”

  • Partial batch failure: single intent revert doesn’t affect others; we isolate by executing intents independently (no shared state assumptions).


Security model

  • Permissions: FlowSettlement is upgrade-gated by timelock + multisig.

  • Solver bonds: executors post bonds and can be slashed (mis-pricing, invalid routing, griefing).

  • Replay protection: per-user nonces + domain separation.

  • Oracle usage: only for gas costing and sanity checks; user bound (minOut) is the real guard.

  • Emergency: Shield can be paused without affecting classic swaps.


Relay API (testnet draft)

Submit intent

POST {RELAY_URL}/v1/intent
{
  "intent": { ...EIP712 value... },
  "signature": "0x...",
  "hint": { "privacy": "lite|private|stealth", "fallback": true|false }
}
→ { "intentHash": "0x...", "batchEta": 1730000000 }

Status

GET {RELAY_URL}/v1/intent/{intentHash}
→ { "state": "pending|settled|failed|reverted", "txHash": "0x...", "savingsPct": "0.42" }

WebSocket feed wss://.../v1/streamShieldSubmitted / ShieldSettled notifications.


Frontend integration (quick guide)

  1. Prepare intent & sign

import { signTypedData } from "viem/actions";
const signature = await signTypedData(wallet, { domain, types, primaryType: "Intent", message: value });
  1. POST to relay (or call execute directly if you run a solver).

  2. Show live status (pending → settled).

    • If settled, extract surplus + rebate amounts from event or status and surface “You saved X%”.

  3. Claims

    • Trader: call TraderRebateVault.claim(user) if not auto-staked.

    • LPs: show accrued Flow earnings on the pool page (per-block snapshots).


Testnet parameters (current)

  • Chain: BNB Testnet

  • Batch window: ~1 block (adaptive 1–2 blocks if congestion)

  • Min trade size: none (UI may nudge > $5)

  • Max hops: 4 (Paragon pools only)

  • Surplus split: 60/30/10 (TBD via DAO for mainnet)

  • Contracts:

    • FlowSettlement: FLOW_SETTLEMENT_ADDRESS

    • TraderRebateVault: TRADER_REBATE_VAULT

    • LPRebateVault: LP_REBATE_VAULT

    • LockerVault: LOCKER_VAULT


Developer FAQs

Q: Can I cancel/replace an intent? Yes. Call cancel(newNonce) or sign a newer intent with a higher nonce.

Q: Does batching increase gas? Settlement for a batch is similar to one swap with comparable hops. Slightly higher overhead is typically offset by better price (surplus).

Q: How are LP rebates mapped to pairs? Solvers must provide lpTouchSet per hop; settlement verifies via router events and credits the vault.

Q: Can I force a public (non-Shield) swap? Yes—use classic Router or set prefs.fallback=true and toggle Shield off in the UI.


Roadmap hooks

  • Limit/RFQ intents

  • Time-weighted auctions for large orders

  • Cross-DEX netting (match internal flows to reduce market impact)

  • veFlow voting to steer surplus weights and utilization emissions

Last updated