Skip to main content
This guide walks through submitting a TWAP order to the Tachyon TWAP service. The service is separate from the relayer, it has its own HTTP API and its own ECIES public key.
The TypeScript SDK is WIP. The flow below shows the raw steps you can implement today; the SDK will wrap them.

Prerequisites

  • A wallet on the source chain (any wallet that can sign EIP-191 personal_sign messages)
  • The source asset approved to the TWAP service’s TEE address
  • An ECIES library (e.g., ecies for Node, matching the Go ecies/go/v2 format the service uses)

1. Fetch the TWAP service’s ECIES public key

const res = await fetch(`${TWAP_URL}/ecies-pubkey`);
const { publicKey } = await res.json(); // hex-encoded uncompressed pubkey
This pubkey is distinct from the relayer’s ECIES pubkey. Orders must be encrypted to this key so only the TWAP service (inside the TEE) can decrypt them.

2. Fetch the TEE wallet address (for approval)

const addrRes = await fetch(`${TWAP_URL}/address`);
const { address: teeAddress } = await addrRes.json();
The user must approve their source asset to this address so the service can pull funds on order creation.

3. Build the order

const order = {
  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 between tranches
  startTime: Math.floor(Date.now() / 1000),
  endTime:   Math.floor(Date.now() / 1000) + 4 * 3600,
  recipients: ["0xRecipient..."],
  amounts:    ["999000000000"],      // min-out on destination (≈1% slippage)
};
Constraints enforced by the service:
  • period ≥ 5 seconds
  • endTime > startTime
  • recipients.length === amounts.length, both > 0
  • amountPerPeriod > 0
  • totalAmountInamountPerPeriod
  • chainIn / chainOut must match the service’s configured chain names

4. Encrypt the order

Serialize to JSON and encrypt with the service’s ECIES pubkey:
const plaintext = Buffer.from(JSON.stringify(order));
const encrypted = await eciesEncrypt(publicKey, plaintext); // library-specific
const encryptedHex = encrypted.toString("hex");

5. Sign the matching personal-sign message

Pick any canonical message, the service verifies that the personal-sign recovery matches order.user. A common pattern:
const message = `TACHYON_TWAP:${order.user}:${order.startTime}`;
const signature = await wallet.signMessage(message);

6. Submit

const createRes = await fetch(`${TWAP_URL}/orders`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    encrypted: encryptedHex,
    signature,
    message,
  }),
});

const { orderId, order: created } = await createRes.json();
The service:
  1. Verifies the signature recovers to order.user
  2. Decrypts the order
  3. Pulls totalAmountIn in a single on-chain transferFrom from order.user to its TEE wallet
  4. Schedules the first tranche (either immediately if startTime has passed, or at startTime)

7. Poll order status

const res = await fetch(`${TWAP_URL}/orders/${orderId}`);
const { order } = await res.json();

console.log(order.status);          // "active" | "completed" | "cancelled"
console.log(order.tranchesFilled, "/", order.totalTranches);
console.log(order.amountSpent);
console.log(order.intentIds);       // BridgeIntent IDs, one per fired tranche
Each tranche is a BridgeIntent on the source chain bridge contract. Track individual tranche settlement through the relayer’s GET /intent-details/:intentId using each intentId from order.intentIds.

8. Cancel (optional)

await fetch(`${TWAP_URL}/orders/${orderId}/cancel`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ signature, message }), // same recovery requirement
});
No further tranches fire. Tranches already fired continue to their own BridgeIntent settlement; they are not reversed.

Solver reward and expected output

Per tranche, the service:
  • Deducts 5% of amountPerPeriod as the solver reward (included as reward on the BridgeIntent)
  • Sets expectedAmountB on the BridgeIntent to the sum of order.amounts[] (your minimum acceptable output on destination)
  • Sets auction duration to period / 2 (minimum 20 seconds)
These are not currently configurable per-order.

Recipients on destination

Recipients are stored encrypted in the relayer via POST /store-recipients after each createIntent call. The winning solver fetches them via GET /get-recipients/:intentId (which only returns the decrypted list to the auction winner) and delivers to those addresses on the destination chain.

Open the hosted TWAP app

Walk through this flow without writing code.