Skip to main content
Private TWAP lets you execute a large position over a defined window without telegraphing the size, schedule, or destination. The TWAP service is a separate Tachyon product running in a TEE with its own HTTP API and its own ECIES public key for order encryption.
Live on testnet. Mainnet coming soon.

Why this is hard on public DEXes

On a public AMM, a TWAP is observable, order size, schedule, and direction leak the moment the first tranche hits the chain. Bots front-run subsequent tranches; market makers widen spreads against the predictable flow. Private TWAP removes the leak. The user signs an order, encrypts it to the TWAP service’s ECIES public key, and submits. The service pulls all funds upfront in a single transferFrom (on-chain observers see one lump transfer, not a TWAP pattern). It then drips tranches on the user-defined schedule, creating a BridgeIntent on the source-chain bridge contract for each tranche. Solvers fulfill each tranche independently; destination deliveries land on stealth addresses.

How it works (integrator view)

  1. Fetch the TWAP service’s ECIES public key (one-time setup)
  2. Build a signed order with total amount, per-period amount, period length, start/end time, recipient(s), and source/dest chain + asset
  3. Encrypt the order with the TWAP ECIES pubkey
  4. Sign a personal-sign message proving ownership
  5. POST to the TWAP service, it decrypts, verifies the signature matches the user field, pulls funds, and schedules the first tranche

What’s confidential

  • Total order size, not deducible from individual tranche amounts, since observers can’t link tranches to each other
  • Schedule beyond the current tranche, remaining tranches aren’t telegraphed
  • Per-recipient amounts, split between recipients is stored encrypted in the relayer
  • Destination addresses, each tranche settles to a fresh stealth address on the destination chain
What’s necessarily visible: the initial transferFrom from user to the TEE wallet on the source chain (a single on-chain transfer). This hides the TWAP pattern but does reveal that the user handed off some amount to the TEE at one point.

Order parameters

The parent order shape (TWAPOrderParams):
interface TWAPOrderParams {
  user: string;            // signer address (must match personal-sign recovery)
  assetIn: string;         // ERC-20 address on sourceChain
  assetOut: string;        // ERC-20 address on destChain
  chainIn: string;         // human name, e.g. "base_mainnet"
  chainOut: string;        // must match destChainId's configured name
  sourceChainId: number;   // numeric chain ID
  destChainId: number;     // numeric chain ID
  totalAmountIn: string;   // full amount pulled upfront, decimal string (base units)
  amountPerPeriod: string; // per-tranche amount, decimal string
  period: number;          // seconds between tranches (minimum 5)
  startTime: number;       // unix seconds
  endTime: number;         // unix seconds, must be > startTime
  recipients: string[];    // destination addresses, any length > 0
  amounts: string[];       // per-recipient amounts on destination, same length as recipients
  permitSignature?: string;
}
Derived fields set by the service:
  • totalTranches = totalAmountIn / amountPerPeriod (integer division, minimum 1)
  • Per-tranche solver reward: 5% of amountPerPeriod, deducted from the tranche
  • Per-tranche auction duration: period / 2 (minimum 20 seconds)
  • expectedAmountB (min-output on destination): sum of amounts[]

Slicing strategy

Today, TWAP is fixed-time / fixed-size: you specify amountPerPeriod and period. The service divides totalAmountIn into equal tranches and fires one every period seconds until either totalAmountIn is spent or endTime passes, whichever comes first. Variable-cadence and variable-size strategies are not currently supported.

Integration shape (SDK, planned)

const { twapId } = await tachyon.twap.create({
  user: userAddress,
  assetIn: USDC_BASE,
  assetOut: USDC_HORIZEN,
  chainIn: "base_mainnet",
  chainOut: "horizen_mainnet",
  sourceChainId: 8453,
  destChainId: 26514,
  totalAmountIn: "1000000000000",  // 1M USDC (6 decimals)
  amountPerPeriod: "50000000000",  // 50k per tranche → 20 tranches
  period: 600,                     // 10 minutes
  startTime: Math.floor(Date.now() / 1000),
  endTime: Math.floor(Date.now() / 1000) + 4 * 3600,
  recipients: ["0xRecipient..."],
  amounts: ["999000000000"],       // 1% slippage allowance
});

tachyon.twap.subscribe(twapId, (event) => {
  if (event.type === "tranche.created") console.log(event.intentId);
  if (event.type === "twap.completed")  console.log("done");
});
The SDK wraps the TWAP service’s ECIES encryption, personal-sign, and HTTP call, you supply plaintext params and a wallet.

Cancellation

You can cancel an active order at any time. Already-fired tranches continue to their own settlement (they were independent BridgeIntents), but no further tranches are scheduled. Funds not yet dripped remain in the TEE’s custody and can be returned or carried forward per your cancellation flow.

Try it

Open the hosted TWAP app

Place a TWAP without writing code.

TWAP integration guide

Step-by-step integration using the raw TWAP service API.