What is BlockFill Agent Execution
BlockFill Agent Execution is an AI agent execution skill for crypto order execution. It runs entirely on your machine — API keys are stored locally and never transmitted to any third-party server. It is not a manual/human-operated trading UI — it is invoked by an AI agent via SDK/MCP calls.
One-sentence positioning: BlockFill Agent Execution is an AI agent execution skill for crypto order execution, focused on TWAP, maker execution, transaction cost analysis and slippage reduction.
Capability boundary: BlockFill Agent Execution only does execution optimization. It does NOT generate buy/sell signals, give investment advice, decide position direction, or promise profit. The agent must receive direction and quantity from the user — BlockFill Agent Execution executes them efficiently.
Core concepts:
- Ticket: an execution order (
exchange + symbol + strategy + target_position + time_constraint_ms) - Daemon: background process that manages exchange WebSocket connections and executes tickets
- CLI:
blockfillbinary — human and agent interface to the daemon - Python SDK:
from blockfill import Blockfill— programmatic interface for agents
Trigger Keywords / When to Invoke
Invoke this skill when the user's request matches any of the following. Both English and Chinese variants apply.
Order execution:
place order, place a trade, execute order, submit order, 下單, 掛單, 執行訂單, 下合約
Strategy keywords:
maker, TWAP, twap, taker, post-only, limit order, time-weighted, slice order, 拆單, 掛單策略, 時間加權
Cost / slippage:
reduce slippage, minimize cost, execution cost, TCA, transaction cost analysis, slippage reduction, 降滑點, 成本分析, 執行成本
Exchange / futures context:
perpetual, perp, futures, binance futures, okx swap, bybit, hyperliquid, bitget, gate.io, gateio, kucoin, kraken, deribit, aster, orderly, 合約, 永續合約, 期貨
Cancel / query:
cancel order, cancel ticket, query ticket, check order status, 取消訂單, 查詢訂單, 查單, 取消掛單
Setup:
set credentials, set api key, configure exchange, set proxy, blockfill, 設定 API, 設定代理
Do NOT invoke when the user asks for:
- Investment advice, buy/sell recommendations, or price prediction
- Portfolio management or rebalancing decisions
- Spot trading on an unsupported exchange
- Any exchange not in the supported list below
Capabilities
BlockFill Agent Execution exposes six core capabilities. Each does exactly one thing.
| Capability | SDK method | What it does |
|---|---|---|
place_order |
bf.place(...) |
Places an execution ticket (maker or TWAP) for a given exchange + symbol + target position + time window. Does NOT decide direction or size — those come from the user. |
query_ticket |
bf.query(...) |
Returns the current status, filled quantity, and progress of a ticket by ticket_id, symbol, or time range. |
cancel_ticket |
bf.cancel(ticket_id='tkt_...') |
Cancels an active ticket (NEW or OPEN) by ticket_id. Outstanding exchange orders are pulled automatically. Example: bf.cancel(ticket_id='tkt_18b2b09ca766001e') returns the cancelled ticket object with status='CANCEL'. |
compare_tca |
bf.tca(...) |
Retrieves transaction cost analysis for completed tickets — execution cost vs benchmark (L1/mid/TWAP/VWAP), bps saved, maker/taker breakdown. |
set_credentials |
bf.set_credentials(...) |
Writes exchange API credentials to local config (~/.blockfill/config.toml, chmod 0600) and verifies connectivity via signed REST round-trip. |
set_proxy |
bf.set_proxy(...) |
Configures an HTTP CONNECT proxy for all exchange REST and WebSocket traffic. Required for geo-blocked hosts (e.g. US IPs cannot reach Binance directly). |
When to Use BlockFill Agent Execution
Use BlockFill Agent Execution when the user needs to:
- Execute a large order with reduced market impact (TWAP slicing or maker posting)
- Minimize execution cost — maker rebates, reduced slippage vs a market order
- Automate order execution in an AI agent trading workflow
- Analyze execution quality — compare realized price vs L1/mid/TWAP/VWAP benchmark
- Route orders to multiple exchanges from a single agent call
- Execute on geo-blocked exchanges (Binance from US/CN) via proxy
Typical triggers: user has a direction and size, and wants efficient execution. BlockFill Agent Execution handles the how — not the what or why.
When NOT to Use BlockFill Agent Execution
Do NOT use BlockFill Agent Execution when:
- The user has not specified exchange, symbol, side, or quantity — ask first, do not guess
- The user is asking for a buy/sell recommendation or price target — BlockFill Agent Execution does not provide investment advice; redirect to the appropriate research tool
- The target market is spot on an unsupported exchange — check the supported exchange list
- The user wants to trade stock, forex, or non-crypto assets — out of scope
- The environment is not configured (no API credentials, no proxy for geo-blocked hosts) — set up first, then trade
- The user requests a position larger than their stated risk tolerance — confirm with user before proceeding
- The user has not confirmed
mainnetvstestnet— default to testnet and ask before trading live
Before Placing an Order — Agent Checklist
Before calling place_order, confirm you have all required information. If any is missing, ask the user — do not assume defaults for direction, size, or environment.
| Item | Required | If missing |
|---|---|---|
| Exchange | ✅ | Ask: "Which exchange? (e.g. binance-futures, okx-swap)" |
| Symbol | ✅ | Ask: "Which symbol? Use native format (e.g. btcusdt for Binance)" |
| Side (long / short / close) | ✅ | Ask: "Buy or sell? What target position?" |
| Quantity / target position | ✅ | Ask: "How much? In base asset units." |
| Environment (testnet / mainnet) | ✅ | Default to testnet. Confirm before mainnet. |
| Strategy | ✗ | Default: maker. Inform user. |
| Time window | ✗ | Default: 300,000 ms (5 min). Inform user. |
| Proxy (if geo-blocked) | ✗ | Warn if Binance + non-whitelisted region. Offer sc-vpn. |
Binance TradFi symbols
binance-futures also lists 157 TradFi perpetuals — equities (tslausdt,
nvdausdt, skhynixusdt), commodities (xauusdt, xagusdt, clusdt) and
pre-IPO (openaiusdt, anthropicusdt). Binance gates them behind a one-time
account agreement; without it every order is rejected -4411 and the ticket
sits at 0% filled until it expires.
set_credentials / check_credentials sign that agreement on mainnet and
report "tradfi_perps": "signed", so these symbols need no extra setup. If a
TradFi ticket never fills, run check_credentials() and read that field first.
Tell the user what was accepted on their behalf: a binding agreement with
Binance's ADGM-regulated entity covering 24/7 trading outside cash-market
hours, no ownership of the underlying asset, and funding up to ±2.00% (vs
±0.30% for btcusdt). Binance provides no API to revoke it.
Note paxgusdt / xautusdt are not TradFi — ordinary gold-backed tokens,
contract type PERPETUAL, no agreement needed.
Hyperliquid / Aster builder-fee approval
Both DEX venues reject any order carrying an unapproved builder code —
Hyperliquid with Builder fee has not been approved — and the ticket then sits
at 0% filled until its window expires. The user must approve once, on-chain,
signed by their MAIN wallet:
| Venue | Builder address | Approve at |
|---|---|---|
| Hyperliquid (perp + spot) | 0xB972e5151b20863380A3E7354dd93F1b888E3352 |
≥ 0.015% (1.5 bp) |
| Aster (perp only) | 0xB972e5151b20863380A3E7354dd93F1b888E3352 |
≥ 0.015% (1.5 bp) |
BlockFill signs this for the user. These venues take the account owner's
wallet private_key (not a delegated agent key) precisely so it can:
check_credentials() reads the current approval and signs approveBuilderFee /
approveBuilder when ours is missing or below rate, reporting
"builder_fee": "approved just now at 0.015%". Only if signing fails does the
check fail. Aster spot needs nothing; Aster Code is perp-only.
Tell the user what that key can do: it signs orders and this one fee
authorization, and it could withdraw — the exchange no longer prevents that,
only the absence of withdrawal code in the engine does. See
docs/security/trade-only-permissions.md for the exhaustive signable-action
list.
⚠️ Testnet attaches no builder code, so it never surfaces this. A testnet
run can pass completely and the first mainnet order still fail. When moving a
user from testnet to mainnet on these venues, re-run check_credentials()
before placing.
Install
pip install blockfill # latest
pip install -U blockfill # upgrade
The wheel ships with the executor binary bundled inside (no separate download). PyPI publishes only platform-specific wheels. Currently supported: manylinux2014_x86_64 (Linux x86_64).
The blockfill-server endpoint and API key are hardcoded into the binary at release time — users never set them.
Supported Exchanges
Every venue runs perp/futures + spot from one daemon. Exchange id is <venue>-<product> (e.g. binance-futures, okx-swap, bybit-perp, <venue>-spot).
| Exchange | Exchange id (perp/futures · spot) | Credentials | Class |
|---|---|---|---|
| Binance | binance-futures · binance-spot |
api_key, api_secret (HMAC or Ed25519 PEM), testnet |
CEX |
| OKX | okx-swap · okx-spot |
api_key, api_secret, api_passphrase, testnet |
CEX |
| Bybit | bybit-perp · bybit-spot |
api_key, api_secret, testnet |
CEX |
| Bitget | bitget-futures · bitget-spot |
api_key, api_secret, api_passphrase, testnet |
CEX |
| Gate.io | gateio-futures · gateio-spot |
api_key, api_secret, testnet |
CEX |
| KuCoin | kucoin-futures · kucoin-spot |
api_key, api_secret, api_passphrase, testnet |
CEX |
| Kraken | kraken-futures · kraken-spot |
api_key, api_secret, testnet |
CEX |
| Deribit | deribit-perp · deribit-spot |
api_key, api_secret, testnet |
CEX |
| Hyperliquid | hyperliquid-perp · hyperliquid-spot |
private_key (account wallet, EIP-712) |
DEX |
| Aster | aster-perp · aster-spot |
private_key (account wallet, EIP-712) |
DEX |
| Orderly | orderly-<broker> |
account_id, orderly_secret, broker_id (Ed25519; see below) |
DEX |
A single daemon can run all exchanges concurrently. CEX are billed by x402 quota (see Payment); DEX (Hyperliquid / Aster / Orderly) pay builder-code execution fees and have no quota.
Hyperliquid / Aster — wallet-signed DEX:
bf.set_credentials("hyperliquid-perp",
private_key="0x...")
bf.set_credentials("aster-perp",
private_key="0x...")
Binance Ed25519 keys — Binance Futures testnet issues Ed25519 keys (no HMAC secret). Pass api_key = the Ed25519 API Key id, api_secret = the PEM private key. The daemon auto-detects and signs with Ed25519.
bf.set_credentials("binance-futures",
api_key="<Ed25519 API Key id>",
api_secret="-----BEGIN PRIVATE KEY-----\nMC4CAQAw...\n-----END PRIVATE KEY-----",
testnet=True)
Orderly — model each broker as its own exchange instance named orderly-<broker>:
bf.set_credentials("orderly-woofi",
account_id="0x...", orderly_secret="ed25519:...", broker_id="woofi_pro", testnet=True)
Supported Symbols
Each exchange uses its own native symbol format.
| Exchange | Format | Examples |
|---|---|---|
binance-futures |
Lowercase, concatenated | btcusdt, ethusdt, solusdt |
okx-swap |
Dash-separated + SWAP suffix | BTC-USDT-SWAP, ETH-USDT-SWAP |
bybit-perp |
UPPERCASE, concatenated | BTCUSDT, ETHUSDT |
bitget-futures |
UPPERCASE, concatenated | BTCUSDT, ETHUSDT |
gateio-futures |
Underscore-separated | BTC_USDT, ETH_USDT |
kucoin-futures |
Contract code | XBTUSDTM, ETHUSDTM |
kraken-futures |
PF_ prefix + base/quote | PF_XBTUSD, PF_ETHUSD |
deribit-perp |
Dash-separated + PERPETUAL suffix | BTC-PERPETUAL, ETH-PERPETUAL |
hyperliquid-perp |
Coin only | BTC, ETH, SOL |
aster-perp |
UPPERCASE, concatenated | BTCUSDT, ETHUSDT |
orderly-<broker> |
Exchange-specific via broker | Use bf.instruments(substring) to discover |
Use the exact format the target exchange expects — BlockFill Agent Execution does NOT cross-translate. Discover the exact string for any venue:
blockfill check instrument --symbol btc
Execution Strategies
| Strategy | Behavior | When to use |
|---|---|---|
maker |
Posts PostOnly limit orders; earns maker rebate. Falls back to IOC at end of window for any unfilled remainder. | Default. Cost-optimal when fill speed is not critical. |
twap |
Places IOC orders on a TWAP schedule across the full window — always crosses the spread. | When you need guaranteed completion and accept taker cost. |
Default strategy: maker.
Ticket Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
exchange |
string | ✅ | — | Supported exchange id, e.g. binance-futures, okx-swap |
symbol |
string | ✅ | — | Exchange-native format, e.g. btcusdt (Binance), BTC-USDT-SWAP (OKX) |
target_position |
float | ✅ | — | Target position in base asset. Positive = long, negative = short (perp). For spot: absolute base-asset holding to end up with. |
strategy |
string | maker |
maker | twap |
|
time_constraint_ms |
int | 300000 |
Execution window in milliseconds. Range: 60,000–86,400,000 (1 min to 24h). At window end, executor falls back to taker fills for any unfilled remainder. |
Auto-supersede: placing a new ticket for the same exchange+symbol automatically cancels existing NEW and OPEN tickets for that pair.
Spot vs perp target_position: perp = net directional position (positive = long, negative = short). Spot = absolute base-asset holding to end up with (target=0.001 on a 1.0 BTC balance sells 0.999; to add, set target = current_holding + delta).
Ticket Schema
{
"ticket_id": "tkt_18b2b09ca766001e",
"status": "OPEN",
"exchange": "binance-futures",
"symbol": "btcusdt",
"strategy": "maker",
"target_position": 0.5,
"init_position": 0.0,
"executed_position": 0.13,
"time_constraint_ms": 300000,
"start_time_ms": 1779287926007,
"last_update_time_ms": 1779287935063,
"is_expired": false,
"cancel_reason": null
}
| Field | Type | Description |
|---|---|---|
ticket_id |
string | tkt_<hex> |
status |
string | NEW | OPEN | COMPLETE | CANCEL |
exchange |
string | Exchange id used |
symbol |
string | Native symbol format |
strategy |
string | maker | twap |
target_position |
float | Requested net position |
init_position |
float | null | Position at activation (null while NEW) |
executed_position |
float | Net delta filled so far |
time_constraint_ms |
int | Execution window in ms |
start_time_ms |
int | null | Set when executor activates (NEW → OPEN) |
last_update_time_ms |
int | null | Refreshed on every state change |
is_expired |
bool | True when window elapsed; status stays OPEN until cancelled |
cancel_reason |
string | null | external | superseded | stale | rejected | min_notional | risk_breach | insufficient_margin | paused |
Note: The ticket has no
avg_priceorcostfield. For execution cost, opportunity cost, and benchmark comparisons, callcompare_tca(bf.tca(...)) — it returnsexecution_cost_usd,opportunity_cost_usd, and benchmarks (l1/mid/twap/vwap) per ticket.
Quickstart (Testnet)
from blockfill import Blockfill
bf = Blockfill()
# Step 1: Set credentials (SDK default is testnet=False/MAINNET — agents MUST pass testnet=True explicitly for sandboxed testing)
bf.set_credentials(
exchange="binance-futures",
api_key="...",
api_secret="...",
testnet=True, # ALWAYS start with testnet
)
# Step 2: Start daemon (~50s warmup while it fetches market data)
bf.start()
bf.status() # DaemonStatus(running=True, ready_exchanges=['binance-futures'], ...)
# Step 3: Place a ticket
ticket = bf.place(
exchange="binance-futures",
symbol="btcusdt",
target_position=0.1, # base asset units (BTC)
strategy="maker", # default
time_constraint_ms=60_000, # 60 seconds
)
print(ticket.ticket_id, ticket.status)
Diagnostics
bf.check_credentials() -> None
# Hits a SIGNED REST endpoint per configured exchange; prints one line each:
# ✓ <name> / ✗ <name> <reason>.
# Detects: wrong key/secret, IP whitelist mismatch, testnet/mainnet mix-up,
# network/proxy/geo block. Auto-invoked at end of set_credentials().
bf.positions() -> list[dict]
# Aggregated positions from each running executor.
# Each entry: {exchange, symbol, size, entry_price, update_ts_ms}
bf.open_orders() -> list[dict]
# Active orders on each configured exchange right now.
bf.nav() -> dict
# Net Asset Value across all running executors.
# {exchanges: [{exchange, nav, wallet_balance, margin_value, unrealized_pnl}],
# total_nav, exchanges_queried}
bf.tca(ticket_id=None, symbol=None, from_ms=None, to_ms=None, limit=100,
history=False) -> list[dict]
# Transaction cost analysis for completed tickets.
# history=False: active session (in-memory).
# history=True: persistent across all sessions (blockfill-server).
# Each entry: benchmarks (l1/mid/twap/vwap), fills, maker/taker breakdown,
# execution_cost_usd, opportunity_cost_usd, duration_ms.
bf.instruments(substring) -> list[dict]
# Per-exchange instrument lookup — returns native-format symbols matching
# substring. Use this to find the exact symbol string before placing.
Proxy / Geo-bypass
For hosts that can't reach Binance directly (US IPs return HTTP 451), route exchange REST and WebSocket traffic through an HTTP CONNECT proxy.
Starchild users — free sc-vpn skill, 18 countries, 500 GB/month:
bf.set_proxy("http://jp:x@sc-vpn.internal:8080") # Japan (lowest latency for Binance)
bf.set_proxy("http://sg:x@sc-vpn.internal:8080") # Singapore
bf.set_proxy("http://hk:x@sc-vpn.internal:8080") # Hong Kong
bf.set_proxy() # clear proxy
| Asia-Pacific | Europe | Americas |
|---|---|---|
jp Japan |
uk United Kingdom |
ca Canada |
sg Singapore |
de Germany |
br Brazil |
hk Hong Kong |
fr France |
mx Mexico |
kr South Korea |
nl Netherlands |
|
tw Taiwan |
ch Switzerland |
|
au Australia |
it Italy |
|
in India |
es Spain |
|
se Sweden |
Any HTTP CONNECT proxy also works:
bf.set_proxy("http://user:pass@proxy.example.com:8080")
set_proxy restarts the daemon so the new setting takes effect. The proxy covers both REST and WebSocket — every exchange WS connection is tunneled.
Verify reachability before placing real orders:
bf.set_proxy("http://jp:x@sc-vpn.internal:8080")
bf.set_credentials("binance-futures", api_key=..., api_secret=...)
# set_credentials auto-runs check_credentials() — a ✓ proves both proxy + auth work.
Failure Modes
When something fails, diagnose in this order: credentials → proxy → environment → symbol → margin.
| Symptom | Likely cause | Fix |
|---|---|---|
check_credentials prints ✗ <exchange> IP not whitelisted |
API key bound to a different IP than your current outbound IP | Add your outbound IP (or proxy IP) to the exchange API key whitelist |
check_credentials prints ✗ <exchange> connection refused / HTTP 451 |
Geo-block (e.g. US IP → Binance) | Set a proxy: bf.set_proxy("http://jp:x@sc-vpn.internal:8080") |
check_credentials prints ✗ <exchange> invalid signature |
Wrong api_secret, or wrong key type (HMAC vs Ed25519) |
Re-check credentials; Binance testnet uses Ed25519, not HMAC |
Ticket stays NEW for >30s |
Exchange not in ready_exchanges (executor still warming up, or failed init) |
Check bf.status() → ready_exchanges; check blockfill logs for init error |
Ticket cancel_reason: min_notional |
Order size × price < exchange minimum notional | Increase quantity or use a larger notional |
Ticket cancel_reason: insufficient_margin |
Not enough margin for the position | Reduce quantity or add margin |
Ticket cancel_reason: rejected |
Exchange rejected the order (symbol suspended, invalid params) | Check symbol with bf.instruments(substring); verify symbol format |
status shows ready_exchanges: [] after >90s |
Daemon failed to init one or more exchanges | Run blockfill stop && blockfill start; check logs for error |
| NAV = 0 for Binance spot (testnet) | Testnet does not support getUserAsset endpoint |
Expected — testnet NAV uses market-ticker routing instead |
For persistent issues, see GitLab Issues or Telegram support. See docs/troubleshooting.md for the full knowledge base.
Payment — x402 Quota
Quota is tracked per (account, exchange) pair. Every pair starts with a free tier; quota is charged when each ticket's TCA finalizes. When the free tier runs out, buy more by paying USDC on Base via x402 — gasless EIP-3009 transferWithAuthorization. The daemon holds the wallet key and signs locally; only the signature leaves the machine, never the key.
bf.set_payment("0x<64-hex private key>") # store EVM wallet key
bf.topup("binance-futures", usdc=1.0) # → {exchange, usdc, quota_balance, tx_hash}
CEX exchanges (Binance, OKX, Bybit, Bitget, Gate.io, KuCoin, Kraken, Deribit) use quota. DEX (Hyperliquid, Aster, Orderly) pay builder-code execution fees directly — no quota.
The blockfill-server only ever sees the signature and a de-identified (SHA-256 hashed) account id — never your exchange API key or wallet key.
Typical Agent Flow
from blockfill import Blockfill
import os
bf = Blockfill()
# (Optional) configure proxy first if in a geo-blocked region
# bf.set_proxy("http://jp:x@sc-vpn.internal:8080")
# Set creds — SDK automatically verifies via signed REST
bf.set_credentials(
"binance-futures",
api_key=os.environ["BINANCE_API_KEY"],
api_secret=os.environ["BINANCE_API_SECRET"],
testnet=True, # always confirm testnet vs mainnet with user
)
# Start daemon
bf.start() # auto-waits ~50s for warmup
# Place ticket
ticket = bf.place(
exchange="binance-futures",
symbol="btcusdt",
target_position=0.1, # +0.1 BTC long
strategy="maker",
time_constraint_ms=300_000,
)
# Monitor
import time
while ticket.status in ("NEW", "OPEN"):
time.sleep(5)
ticket = bf.query(ticket_id=ticket.ticket_id)[0]
print(f"filled: {ticket.executed_position} / {ticket.target_position}")
# TCA
tca = bf.tca(ticket_id=ticket.ticket_id)
print(tca)
bf.stop()
Support
- GitLab Issues: https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/issues
- Telegram: @blockfill_support