Skip to content

Instantly share code, notes, and snippets.

@patcito
Created May 26, 2026 13:09
Show Gist options
  • Select an option

  • Save patcito/d681a9cd7712a53c729369729a989576 to your computer and use it in GitHub Desktop.

Select an option

Save patcito/d681a9cd7712a53c729369729a989576 to your computer and use it in GitHub Desktop.
FlyingTulip leverage executor — PROD HTTP API (Python + TypeScript clients, EIP-712 LeveragedOrder)

FlyingTulip leverage executor — PROD HTTP API

The executor settles a signed LeveragedOrder on the prod LeverageRfqEngine (Sonic mainnet, chainId 146). You don't pass it calldata — you POST a signed order, the executor resolves the DEX route (incl. the ftUSD ↔ X composite via MintAndRedeem + Odos), signs the fill with its own KMS-managed relayer key, and broadcasts it. Engine does not gate by caller — security is the user's EIP-712 signature recovered inside _openLeverageFlash.

Endpoints (PROD)

POST https://api.flyingtulip.com/mm/leverage/orders          # submit
GET  https://api.flyingtulip.com/mm/leverage/orders/<id>     # poll status
GET  https://api.flyingtulip.com/mm/leverage/config          # engine / sessionManager / executor / chain

GET /leverage/config on prod returns:

{
  "chainId": 146,
  "signingChainId": 146,
  "sessionManager": "0x109AE72778a0260571b9767477204F1ce41FBdff",
  "sessionManagerDomainName": "FT SessionManager",
  "leverageRfqEngine": "0x8263a07504d93cB95e0a74f3627bb15faaf140e2",
  "executorAddress": "0xa505815A526f1200c17B7ffaE0067318d734b9d8",
  "mode": "executor"
}
  • leverageRfqEngine is what your script will use as the EIP-712 verifyingContract when signing.
  • executorAddress is the relayer that broadcasts (handy for monitoring; no authz needed by the engine, just FYI).

Order = the same struct the engine's OrderBroadcast emits

action, user, sellToken, buyToken, sellAmount, buyAmount, validTo, feeAmountslippage is buyAmount (the min-out floor; the executor also runs its own oracle-floor check).

Python (recommended — see submit_leverage_order.py)

pip install requests eth-account
USER_PK=0x<key> python submit_leverage_order.py

That script:

  1. Hits /leverage/config to discover the engine + chainId.
  2. EIP-712 signs a LeveragedOrder with domain = LeverageRfqEngine.
  3. POSTs the order + ownerSignature to /leverage/orders.
  4. Polls /leverage/orders/<id> to terminal status (filled | failed | expired | cancelled).

Edit the action, sell_token, buy_token, sell_amount, buy_amount at the top before running.

TypeScript (alternative — see submit-leverage-order.ts)

bun add viem
USER_PK=0x<key> bun submit-leverage-order.ts

Same flow, viem signTypedData.

Body shape (sigType: "eip712", EOA signer)

{
  "sigType": "eip712",
  "orderType": "swap",                 // "open" | "swap" | "close"
  "isOpen": false,
  "user": "0xUSER",
  "order": {
    "action": 2,                       // 0 OPEN | 1 CLOSE | 2 SWAP
    "user": "0xUSER",
    "sellToken": "0xF7D85E…ftUSD",
    "buyToken":  "0xE5DA20…stS",
    "sellAmount": "1000000",
    "buyAmount":  "<min-out>",
    "validTo": 1779999999,
    "feeAmount": "0",
    "kind": 0, "partiallyFillable": false, "sellTokenBalance": 0, "buyTokenBalance": 0
  },
  "ownerSignature": "0xSIG"
}

The signature (0xSIG)

EIP-712 over the order; the domain is the engine:

domain  = { name: "LeverageRfqEngine", version: "1", chainId: 146, verifyingContract: <ENGINE from /config> }
types   = { LeveragedOrder: [action uint8, user address, sellToken address, buyToken address,
                             sellAmount uint256, buyAmount uint256, validTo uint32, feeAmount uint256] }
digest  = keccak256(0x1901 ‖ domainSeparator ‖ structHash)   // recovered signer must == user
  • EOA usersigType:"eip712", ownerSignature (see scripts).
  • Contract user (e.g. a strategy) → sigType:"erc1271"; the contract's isValidSignature() must validate that digest. Body is otherwise identical.

cast signer alternative (after you compute the EIP-712 digest):

cast wallet sign --no-hash <digest> --private-key $PK

Sonic mainnet token + protocol addresses (prod)

Token / contract Address Decimals
USDC 0x29219dd400f2Bf60E5a23d13Be72B486D4038894 6
wS 0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38 18
stS 0xE5DA20F15420aD15DE0fa650600aFc998bbE3955 18
ftUSD (prod) 0xF7D85EC4E7710f71992752eac2111312e73E9C9C 6
LeverageRfqEngine (prod) 0x8263a07504d93cB95e0a74f3627bb15faaf140e2
SessionManager (prod) 0x109AE72778a0260571b9767477204F1ce41FBdff
PositionsManager (prod) 0xbe4050a73a7Fb384c65E885a15C33461A4B20055
MintAndRedeem (prod) 0x0C6f8eC81c3eA5BFf06F6CD0791780f9f050eE31
SwapFiller (FT-owned, prod) 0x621b86771b4d5b4a57fBa9DfCBbAcd89f33aF82a
Executor relayer (FYI) 0xa505815A526f1200c17B7ffaE0067318d734b9d8

Always cross-check against the live registry: https://flyingtulipdotcom.github.io/deployments/prod-sonic.json / prod-sonic-ftusd.toon.

Pre-reqs the user must have done on-chain before submitting

  1. approveBorrow(engine, <token>) on PositionsManager for each token your order touches (allows the engine to borrow on your behalf for OPEN).
  2. approve(<token>, engine, max) for each token you'll sell (lets the engine pull the user's tokens for SWAP / CLOSE).
  3. For session-flow orders: SessionManager.createSession(...) with the executor as the delegate.

(Or just use our examples script's setup step which does all 3 — see leverage-executor/examples/e2e-order-flow.ts.)

Verified end-to-end on prod 2026-05-26

spot-buy-sts SWAP of 0.1 ftUSD → 2.272 stS via the composite ftUSD→USDC→stS route filled in 4 seconds: 0xe2da2ee4…cd54d (block 71422081, gas 1.58M, ~$0.003 of S).

/**
* Submit a LeveragedOrder to the FlyingTulip leverage executor over HTTP — PROD.
*
* bun add viem
* USER_PK=0x<key> bun submit-leverage-order.ts
*
* The order's slippage is the `buyAmount` (minimum-out floor) — the executor
* enforces it on top of its own oracle-floor check.
*
* NOTE: this signs as an EOA (sigType="eip712"). If `user` is a CONTRACT
* (e.g. a strategy), use sigType="erc1271" instead and have the contract's
* isValidSignature() validate the same order digest — body is otherwise identical.
*/
import { http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const API = process.env.EXECUTOR_API ?? "https://api.flyingtulip.com/mm"; // PROD
const PK = (process.env.USER_PK ?? "").replace(/^0x/, "");
if (!PK) throw new Error("set USER_PK=0x...");
const acct = privateKeyToAccount(`0x${PK}`);
// ─── order params — EDIT THESE ──────────────────────────────────────────────
// Sonic mainnet tokens (chain-wide):
const WS = "0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38"; // wS (18dp)
const STS = "0xE5DA20F15420aD15DE0fa650600aFc998bbE3955"; // stS (18dp)
const USDC = "0x29219dd400f2Bf60E5a23d13Be72B486D4038894"; // USDC (6dp)
const FTUSD = "0xF7D85EC4E7710f71992752eac2111312e73E9C9C"; // ftUSD prod (6dp)
const action = 2; // 0 OPEN | 1 CLOSE | 2 SWAP
const sellToken = FTUSD;
const buyToken = STS;
const sellAmount = parseUnits("1", 6); // 1 ftUSD in (6dp)
const buyAmount = 1n; // MIN stS out = your slippage floor — set a real value!
const validTo = Math.floor(Date.now() / 1000) + 3600;
const feeAmount = 0n;
// ────────────────────────────────────────────────────────────────────────────
// 1) discover the engine + signing chain from the executor
const cfg = await (await fetch(`${API}/leverage/config`)).json();
const engine = cfg.leverageRfqEngine as `0x${string}`;
const chainId = cfg.signingChainId as number;
console.log("engine:", engine, "chainId:", chainId, "executor:", cfg.executorAddress);
// 2) EIP-712 sign the LeveragedOrder. domain = the LeverageRfqEngine.
const ownerSignature = await acct.signTypedData({
domain: { name: "LeverageRfqEngine", version: "1", chainId, verifyingContract: engine },
types: {
LeveragedOrder: [
{ name: "action", type: "uint8" },
{ name: "user", type: "address" },
{ name: "sellToken", type: "address" },
{ name: "buyToken", type: "address" },
{ name: "sellAmount", type: "uint256" },
{ name: "buyAmount", type: "uint256" },
{ name: "validTo", type: "uint32" },
{ name: "feeAmount", type: "uint256" },
],
},
primaryType: "LeveragedOrder",
message: {
action, user: acct.address, sellToken, buyToken,
sellAmount, buyAmount, validTo, feeAmount,
},
});
// 3) POST to the executor (via api.flyingtulip.com proxy)
const body = {
sigType: "eip712",
orderType: action === 0 ? "open" : action === 1 ? "close" : "swap",
isOpen: action === 0,
user: acct.address,
order: {
action, user: acct.address, sellToken, buyToken,
sellAmount: sellAmount.toString(), buyAmount: buyAmount.toString(),
validTo, feeAmount: feeAmount.toString(),
kind: 0, partiallyFillable: false, sellTokenBalance: 0, buyTokenBalance: 0,
},
ownerSignature,
};
const resp = await fetch(`${API}/leverage/orders`, {
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body),
});
const res = await resp.json();
console.log("submit:", resp.status, res);
const orderId = res.orderId ?? res.orderID;
if (!orderId) process.exit(1);
// 4) poll to terminal
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 2000));
const d = await (await fetch(`${API}/leverage/orders/${orderId}`)).json();
console.log(`[${i * 2}s] status=${d.status} tx=${d.txHash ?? ""} err=${d.errorMessage ?? ""}`);
if (["filled", "failed", "expired", "cancelled"].includes(d.status)) break;
}
void http; // (imported for convenience if you extend this)
"""
Submit a LeveragedOrder to the FlyingTulip leverage executor over HTTP — PROD.
pip install requests eth-account
USER_PK=0x<key> python submit_leverage_order.py
Slippage is the `buyAmount` (minimum-out floor). The executor verifies the
user's EIP-712 signature, resolves the DEX route (incl. the ftUSD→X composite:
MintAndRedeem ftUSD→USDC, then Odos USDC→X), signs the fill with its own KMS
relayer key, and broadcasts to the engine.
EOA signer (sigType="eip712"). If `user` is a CONTRACT (e.g. a strategy), use
sigType="erc1271" instead and have the contract's isValidSignature() validate
the SAME order digest — body is otherwise identical.
EIP-712 verified to match the engine's on-chain digest exactly (domain =
LeverageRfqEngine / version "1" / chainId 146 / verifyingContract = engine).
"""
import os
import time
import requests
from eth_account import Account
from eth_account.messages import encode_typed_data
API = os.environ.get("EXECUTOR_API", "https://api.flyingtulip.com/mm") # PROD
PK = os.environ["USER_PK"]
acct = Account.from_key(PK)
# ─── order params — EDIT THESE ──────────────────────────────────────────────
# Sonic mainnet tokens (chain-wide):
WS = "0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38" # wS (18dp)
STS = "0xE5DA20F15420aD15DE0fa650600aFc998bbE3955" # stS (18dp)
USDC = "0x29219dd400f2Bf60E5a23d13Be72B486D4038894" # USDC (6dp)
FTUSD = "0xF7D85EC4E7710f71992752eac2111312e73E9C9C"# ftUSD prod (6dp)
action = 2 # 0 OPEN | 1 CLOSE | 2 SWAP
sell_token = FTUSD
buy_token = STS
sell_amount = 1 * 10**6 # 1 ftUSD in (6dp)
buy_amount = 1 # MIN stS out = your slippage floor — set a real value!
valid_to = int(time.time()) + 3600
fee_amount = 0
# ────────────────────────────────────────────────────────────────────────────
# 1) discover engine + signing chain from the executor
cfg = requests.get(f"{API}/leverage/config", timeout=15).json()
engine = cfg["leverageRfqEngine"]
chain_id = cfg["signingChainId"]
print("engine:", engine, "chainId:", chain_id, "executor:", cfg["executorAddress"])
# 2) EIP-712 sign the LeveragedOrder. domain = the LeverageRfqEngine.
typed = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"LeveragedOrder": [
{"name": "action", "type": "uint8"},
{"name": "user", "type": "address"},
{"name": "sellToken", "type": "address"},
{"name": "buyToken", "type": "address"},
{"name": "sellAmount", "type": "uint256"},
{"name": "buyAmount", "type": "uint256"},
{"name": "validTo", "type": "uint32"},
{"name": "feeAmount", "type": "uint256"},
],
},
"primaryType": "LeveragedOrder",
"domain": {"name": "LeverageRfqEngine", "version": "1",
"chainId": chain_id, "verifyingContract": engine},
"message": {
"action": action, "user": acct.address,
"sellToken": sell_token, "buyToken": buy_token,
"sellAmount": sell_amount, "buyAmount": buy_amount,
"validTo": valid_to, "feeAmount": fee_amount,
},
}
signed = Account.sign_message(encode_typed_data(full_message=typed), private_key=PK)
owner_signature = "0x" + signed.signature.hex()
# 3) POST to the executor (via the api.flyingtulip.com proxy)
body = {
"sigType": "eip712",
"orderType": "open" if action == 0 else "close" if action == 1 else "swap",
"isOpen": action == 0,
"user": acct.address,
"order": {
"action": action, "user": acct.address,
"sellToken": sell_token, "buyToken": buy_token,
"sellAmount": str(sell_amount), "buyAmount": str(buy_amount),
"validTo": valid_to, "feeAmount": str(fee_amount),
"kind": 0, "partiallyFillable": False, "sellTokenBalance": 0, "buyTokenBalance": 0,
},
"ownerSignature": owner_signature,
}
resp = requests.post(f"{API}/leverage/orders", json=body, timeout=15)
res = resp.json()
print("submit:", resp.status_code, res)
order_id = res.get("orderId") or res.get("orderID")
if not order_id:
raise SystemExit(1)
# 4) poll to terminal
for i in range(30):
time.sleep(2)
d = requests.get(f"{API}/leverage/orders/{order_id}", timeout=15).json()
print(f"[{i * 2}s] status={d.get('status')} tx={d.get('txHash', '')} err={d.get('errorMessage', '')}")
if d.get("status") in ("filled", "failed", "expired", "cancelled"):
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment