Skip to main content

BlockFill Agent Execution

BlockFill Agent Execution is an AI agent execution skill for crypto order execution, focused on TWAP, maker execution, transaction cost analysis and slippage reduction.


For AI Agents

BlockFill Agent Execution converts a trading intent (exchange + symbol + direction + size) into efficiently executed crypto orders, using maker (PostOnly resting) or TWAP (taker-sliced) strategies across the 11 documented CEX and DEX venues — Binance, OKX, Bybit, Bitget, Gate.io, KuCoin, Kraken, Deribit, Hyperliquid, Aster, and Orderly. It runs entirely on the user's machine: exchange API keys are stored locally in ~/.blockfill/config.toml and never transmitted to any third party. BlockFill's capability boundary is execution optimization only — it does NOT generate buy/sell signals, give investment advice, decide position direction, or make portfolio management decisions; the agent must receive direction and quantity from the user and pass them to BlockFill for efficient execution.

Read SKILL.md for complete invocation details.

Agent policy: default to testnet unless the user explicitly confirms mainnet (SDK default is mainnet).


What BlockFill Does

BlockFill Agent Execution is an execution-optimization tool only. Given an exchange, symbol, direction, and size that the user (or the agent, on the user's explicit instruction) has already decided on, BlockFill works that order over time using maker or taker strategies to reduce slippage and market impact.

BlockFill is not:

  • A signal generator — it does not analyze markets, generate buy/sell signals, or predict price direction
  • Investment advice — it does not recommend trades, position sizes, or portfolio allocations
  • An auto-arbitrage or auto-trading strategy — it does not decide when to trade, what to trade, or how much; it only executes what it is told

When to Use BlockFill

Use BlockFill when the user needs to:

  • Execute a large crypto order with reduced market impact — TWAP slicing or maker posting prevents moving the market against the user on entry or exit
  • Reduce execution slippage — maker rebates and passive resting orders cut realized cost vs a naive market order
  • Run a taker or maker strategy — time-slice a large order over minutes or hours, or post limit orders that earn rebates and fill passively
  • Compare transaction cost analysis (TCA) — retrieve realized fill price vs L1/mid/TWAP/VWAP benchmark to evaluate execution quality across tickets
  • Automate order execution in an AI agent trading workflow — agents can place, monitor, and cancel tickets programmatically via the Python SDK or MCP tools

When NOT to Use BlockFill

Do NOT use BlockFill when:

  • The user is asking for a buy/sell recommendation, price prediction, or investment advice — BlockFill does not provide these; redirect to the appropriate research or analysis tool
  • The target exchange is not in the supported list — check the Supported venues table before attempting to configure; unsupported venues cannot be added at runtime
  • Exchange API credentials are not yet configured — call bf.set_credentials(...) and verify with bf.check_credentials() before placing any order
  • The user has not confirmed mainnet — the SDK default is mainnet (testnet=False); as agent policy, default to testnet (testnet=True) and only switch to mainnet after the user explicitly confirms
  • The user wants spot trading on an exchange not in the supported list — stocks, forex, and non-crypto assets are out of scope entirely
  • The user wants BlockFill to promise or guarantee profit — BlockFill only executes the size/direction the user specifies; it makes no performance claims

Required Parameters

Before placing any order, confirm all required parameters are available. If any required field is missing, ask the user — do not assume defaults for direction, size, or environment.

Parameter Required Default Description
exchange Exchange id in <venue>-<product> format, e.g. binance-futures, okx-swap, bybit-perp
symbol Native symbol format per exchange, e.g. btcusdt for Binance, BTC-USDT-SWAP for OKX
target_position Target position in base asset units; positive = long, negative = short (perp); absolute holding for spot
side implied Implied from the sign of target_position — do not pass separately; positive means buy/long, negative means sell/short
strategy no maker Execution strategy: maker (PostOnly resting + IOC fallback) or taker (IOC slices on a TWAP schedule). twap is accepted as the former name for taker.
time_constraint_ms no 300000 Execution window in milliseconds (5 minutes). Range: 60,000–86,400,000 (1 min to 24 h)
testnet no False (mainnet) Whether to trade on testnet. SDK default is mainnet — as agent policy, explicitly pass testnet=True unless the user has explicitly confirmed mainnet

Safety Checklist Before Trading

Run through this before placing any order — especially before the first mainnet order. Each line is a check plus why it matters; follow the link when a check fails.

  • Testnet unless confirmed — SDK default is testnet=False (mainnet); as agent policy, explicitly pass testnet=True unless the user has explicitly confirmed mainnet. Prevents accidental real-money orders.
  • Credentials verifiedbf.check_credentials() returns for the target exchange (auth + IP whitelist + network reachability in one signed round-trip). → docs/troubleshooting.md
  • IP whitelisted — the outbound IP (or the proxy's IP) is on the exchange API key's whitelist. Avoids -2015 / IP not whitelisted rejections. → docs/troubleshooting.md
  • Proxy working (if geo-blocked) — for US/CN → Binance and similar, bf.status().proxy is set and check_credentials passes through it. → docs/proxy-setup.md
  • Exchange ready — the venue appears in bf.status().ready_exchanges before place. Prevents "not ready — warming up" rejects.
  • Notional within limit — order size × price is above the exchange minimum and within the size the user intended. Avoids cancel_reason: min_notional and oversized fills.
  • Sufficient marginbf.nav() shows enough margin for the target position. Avoids cancel_reason: insufficient_margin.

BlockFill Agent Execution executes only what the user specifies — it never chooses direction, size, or whether to trade. For the full diagnostic order (credentials → proxy → environment → symbol → margin), see docs/troubleshooting.md.


Quick Example Prompts

Copy these prompts to test BlockFill with an AI agent:

Buy 0.01 BTC perpetual on Binance futures testnet. Use default maker strategy over 5 minutes.
Place a maker order: sell 500 USDT worth of ETH on OKX swap testnet, target position -0.15 ETH, 10-minute window.
Execute a time-sliced order on binance-futures testnet: buy 0.1 BTCUSDT perpetual over 15 minutes using taker strategy.
Show me the TCA for my last 5 completed tickets on binance-futures — compare realized fill price vs TWAP and mid benchmarks.
Cancel all active orders on binance-futures testnet.
Place a live (mainnet) maker order for 0.005 BTC on binance-futures — I've already confirmed this is not a test.

Capabilities

Capability What it does Docs
place_order Places an execution ticket (maker or taker) for a given exchange, symbol, target position, and time window api-reference.md
query_ticket Returns current status, fill progress, and metadata for tickets filtered by id, symbol, or time range api-reference.md
cancel_ticket Cancels an active NEW or OPEN ticket; outstanding exchange orders are pulled automatically api-reference.md
compare_tca Retrieves transaction cost analysis for completed tickets — realized price vs L1/mid/TWAP/VWAP benchmark, bps saved, maker/taker breakdown api-reference.md
set_credentials Writes exchange API credentials to local config (~/.blockfill/config.toml, chmod 0600) and verifies connectivity via signed REST round-trip api-reference.md
set_proxy Configures an HTTP CONNECT proxy for exchange REST traffic (WebSocket proxying not yet supported); required for geo-blocked hosts (e.g. US IPs cannot reach Binance directly) api-reference.md

BlockFill Agent Execution is an AI-agent-ready Python SDK and local execution engine for crypto perpetual-futures and spot order execution. It enables AI agents, trading systems, and developers to execute large orders through TWAP and maker-style strategies across 11 documented CEX and DEX venues (Binance, OKX, Bybit, Bitget, Gate.io, KuCoin, Kraken, Deribit, Hyperliquid, Aster, Orderly), while keeping exchange API keys on the user's own machine.

You declare a target position; BlockFill works the order over a time window using maker (PostOnly resting + IOC fallback) or twap (taker-sliced) strategies, cutting execution slippage vs. naïve market orders.

One Python API, one local daemon, 11 documented venues across spot + perp (see Supported venues), one bf.place(...) call per target position.

Supported venues

The agent exchange id is <venue>-<product> — pass it to set_credentials(...) and use it everywhere (bf.place, bf.quota, …).

Exchange Exchange id (perp/futures · spot) Quote (spot · perp)
Binance binance-futures · binance-spot usdt, usdc, fdusd, usd · usdt, usdc
OKX okx-swap · okx-spot usdt, usdc, usd · usdt
Bybit bybit-perp · bybit-spot usdt, usdc, usd · usdt, usdc
Bitget bitget-futures · bitget-spot usdt, usdc, usd · usdt, usdc
Gate.io gateio-futures · gateio-spot usdt, usdc · usdt
KuCoin kucoin-futures · kucoin-spot usdt, usdc · usdt, usdc
Kraken kraken-futures · kraken-spot usd, usdt, usdc · usd
Deribit deribit-perp · deribit-spot usdc, usdt · usdc
Hyperliquid hyperliquid-perp · hyperliquid-spot usdc · usdc
Aster aster-perp · aster-spot usdt · usdt
Orderly orderly-<broker_id> (WOOFi Pro, …) — · usdc

CEX (Binance, OKX, Bybit, Bitget, Gate.io, KuCoin, Kraken, Deribit) use api_key + api_secret (+ api_passphrase for OKX / KuCoin / Bitget) and are billed by x402 quota. DEX (Hyperliquid, Aster, Orderly) sign with a wallet and pay builder-code execution fees — no quota. Binance also supports TradFi perpetuals (gold, oil, US/HK/KR equities, pre-IPO) — see below.

Binance TradFi Perpetuals

binance-futures carries 157 TRADIFI_PERPETUAL contracts alongside the usual crypto perps:

Category Count Examples
Equities (US · HK · KR) 147 tslausdt, nvdausdt, spyusdt, tencentusdt, skhynixusdt
Commodities 8 xauusdt (gold), xagusdt (silver), clusdt (WTI), natgasusdt
Pre-IPO 2 openaiusdt, anthropicusdt

Binance gates all of them behind a one-time, account-level agreement. Until it is signed, every order on those symbols is rejected with:

-4411  Please sign TradFi-Perps agreement contract fapi.

The failure is silent from the ticket's point of view — the ticket simply sits at 0% filled while the executor retries a rejected order until the window expires.

So BlockFill signs it on your behalf. Whenever credentials for binance-futures verify successfully — bf.set_credentials(...), bf.check_credentials(), or blockfill check credentials — BlockFill sends POST /fapi/v1/stock/contract for that account and reports the outcome:

{
  "exchange": "binance-futures",
  "ok": true,
  "detail": "1 asset balances returned",
  "tradfi_perps": "signed"
}

You do not need to do anything else; bf.place(exchange="binance-futures", symbol="xauusdt", ...) works straight away.

Scope — binance-futures on mainnet only. No other venue has such an agreement. Portfolio Margin (/papi/v1/um/stock/contract) and Options (/eapi/v1/stock/contract) each carry their own separate agreement and BlockFill trades neither, so it never signs those. Testnet is skipped. When the exchange is out of scope the tradfi_perps field is absent entirely.

Signing never fails a credential check: if the call errors, tradfi_perps carries the reason and ok is unaffected — crypto perps keep working, only TradFi symbols would reject.

What you are agreeing to. TradFi Perps are offered by Nest Exchange Limited, a Binance entity regulated by the FSRA of Abu Dhabi Global Market (ADGM) — a different legal entity from the one behind Binance's crypto perps. The agreement covers the standard derivatives risk disclosures: these contracts do not represent ownership of the underlying asset (no shares, no dividends, no voting rights), they trade 24/7 including when the underlying cash market is closed, and positions can be margin-called or fully liquidated. Funding differs materially from crypto perps too — equity and pre-IPO contracts cap funding at ±2.00% versus ±0.30% for btcusdt. Binance exposes no API to query or revoke this agreement — POST is the only verb — so signing is one-way. If you would rather accept it yourself after reading Binance's full terms, sign it in the Binance interface before configuring credentials with BlockFill; re-signing is idempotent and BlockFill's call will simply succeed again.

Note that paxgusdt and xautusdt are not TradFi contracts despite tracking gold — they are ordinary gold-backed tokens with contract type PERPETUAL, need no agreement, and trade on spot as well.

Hyperliquid & Aster — the account wallet key

Both DEX venues take one credential — your account wallet's private key:

[exchanges.hyperliquid-perp]
private_key    = "0x..."   # required
wallet_address = "0x..."   # optional — derived from the key when omitted

wallet_address is optional because the address is a function of the key. If you supply both, check_credentials() cross-checks them and fails on a mismatch — that would mean the config names one account while signing as another.

Why the owner's key, not a delegated agent wallet

BlockFill's execution fee on these venues is collected through the exchange's own builder-code mechanism, and both exchanges reject any order carrying a builder the account has not approved — on Hyperliquid with Builder fee has not been approved, leaving the ticket at 0% filled until its window expires.

That approval is a user-signed action, and both exchanges deliberately refuse it from an agent (API) wallet: a delegated trading key must not be able to decide who gets paid. So with an agent key BlockFill could never grant it, and every order would fail until you authorized it by hand, out of band — with no way for a headless engine to even prompt you.

With the owner's key, check_credentials() signs that one approval for you:

{
  "exchange": "hyperliquid-perp",
  "ok": true,
  "detail": "agent signature accepted; 3 asset balances",
  "builder_fee": "approved just now at 0.015% (was 0 tenths bp)"
}

It reads the current approval first and only signs when ours is missing or below our rate, so re-running the check is cheap and idempotent. If signing fails the check fails, with the reason.

Venue Builder address Rate approved
Hyperliquid (perp and spot) 0xB972e5151b20863380A3E7354dd93F1b888E3352 0.015% (1.5 bp)
Aster (perp only) 0xB972e5151b20863380A3E7354dd93F1b888E3352 0.015% (1.5 bp)

Aster spot needs no approval: Aster Code exists only on perpetuals (its sole order endpoint is POST /fapi/v3/order; Aster spot is a separate sapi host that takes no builder parameters), so no execution fee is charged there.

What you are accepting. This key can withdraw — the exchange no longer stops that, only the absence of withdrawal code in the engine does. The complete set of actions the engine can sign is listed in trade-only permissions: five Hyperliquid trade actions plus approveBuilderFee, or on Aster the order/balance/position endpoints plus approveBuilder. Hyperliquid's withdraw3 / usdSend / spotSend / transfer actions are not implemented at all. If that trade is not acceptable for your funds, use the CEX venues, or fund the DEX account with only what you are willing to expose.

Testnet does not need the approval, and that is the trap. BlockFill attaches no builder code on testnet, so the precondition is invisible there. Since agent policy is to validate on testnet first, re-run check_credentials() after switching to mainnet.

Designed for AI Agents

BlockFill Agent Execution is designed to be called by AI agents, trading copilots, MCP servers, strategy systems, and execution workflows.

Typical agent instruction:

Buy 100,000 USDT worth of BTC perpetuals over 30 minutes using TWAP, with local API-key signing and no third-party custody.

BlockFill provides the execution layer behind that instruction:

  1. The agent decides the trading intent.
  2. BlockFill converts the intent into an executable ticket.
  3. The local daemon signs and places orders through the user's own exchange API keys.
  4. The agent can query ticket status, positions, and execution results.

Security Model

BlockFill Agent Execution is self-custodial by design.

Exchange API keys stay on the user's own machine (~/.blockfill/config.toml, chmod 0600). The local daemon signs orders locally and sends them directly to the exchange. BlockFill does not custody user funds, store exchange API keys, or route orders through a third-party trading server — no key custody, no order routing, no order-flow visibility by a third party. The package only ships the execution engine.

Trade-only operations. BlockFill never calls a withdrawal or transfer endpoint — verified: none exist in the engine source. Only a non-reversible SHA-256 hash of your account id leaves the machine; the raw api_key, api_secret, and wallet private_key never do.

Two different guarantees stand behind "trade-only", and it matters which one applies to you:

  • CEX venues — create the API key with trade permission only, and the exchange refuses a withdrawal regardless of what any code asks for. Holds even if the software were compromised.
  • DEX venues (Hyperliquid, Aster) — BlockFill holds your account wallet's private key, which the chain permits to withdraw. Nothing at the exchange blocks that; what does is that the engine contains no withdrawal or transfer code. That is auditable, and audited, but it is a weaker guarantee. The owner's key is required because authorizing the builder fee is a user-signed action both exchanges refuse from a delegated agent wallet — see Hyperliquid & Aster and trade-only permissions before funding a DEX account.

Full security & compliance referenceSECURITY.md and docs/security/: data flow, network flow, binary/daemon security, trade-only permissions, EIP-712 authorization, risk disclosure.

Risk disclosure (summary)

BlockFill is execution-only: not an exchange, not a custodian, not an adviser. It does not guarantee fills, prices, or returns; maker orders may not fully fill and TWAP may incur slippage. You (or your agent) are responsible for symbol, direction, size, leverage, and mainnet-vs-testnet selection — the SDK defaults to mainnet, so pass testnet=True unless mainnet is intended. Full text: Risk Disclosure.

Requirements

  • Python 3.10+
  • macOS (Apple Silicon / arm64) or Linux (x86_64) — the daemon binary is bundled inside the wheel, so pip fetches the platform-specific wheel automatically (macosx_11_0_arm64 or manylinux2014_x86_64). Other platforms (Windows, Intel macOS, linux-arm64) are not yet packaged.

Install

python3 -m venv .venv && source .venv/bin/activate
pip install -U blockfill

-U is intentional: it installs the latest release if you don't have it, and upgrades in place if you do (without it, pip install blockfill is a no-op when any version is already installed).

Verify the install

from blockfill import Blockfill
print(Blockfill().version())   # → "blockfill 1.0.X"

Quickstart

⚠️ Order matters. bf.start() will refuse to launch if no exchange credentials are configured. Always call set_credentials() first on a fresh machine — see DaemonStartTimeout: no config.toml at ... below.

from blockfill import Blockfill

bf = Blockfill()

# 1) Write exchange credentials to ~/.blockfill/config.toml (chmod 0600).
#    SDK auto-runs `check_credentials` (a signed REST round-trip) — proves
#    auth works AND the host can reach the exchange. If you're behind a
#    geo block (US → binance), set a proxy first via bf.set_proxy(...).
bf.set_credentials("binance-futures", api_key="...", api_secret="...", testnet=True)

# 2) Start daemon. ~50s warmup while it fetches market data.
bf.start()
bf.status()  # returns DaemonStatus(running=False, ...) if anything is wrong

# Place a ticket
ticket = bf.place(
    exchange="binance-futures",
    symbol="btcusdt",
    strategy="maker",
    target_position=0.1,
    time_constraint_ms=300_000,
)
print(ticket.ticket_id, ticket.status)  # tkt_xxx NEW

# Query active session (in-memory)
tickets = bf.query(status="NEW")

# Cancel
bf.cancel(ticket.ticket_id)

# Shut down daemon
bf.stop()

MCP (Claude Code / Claude Desktop)

BlockFill ships an MCP server that exposes the local daemon as tools an LLM can call directly — status, instruments, place, query, cancel, positions, nav, tca, quota, open_orders. Same security model: the server runs on your machine and your API keys never leave the host.

Install (one command)

Claude Code — no prior install needed (uvx fetches blockfill[mcp] from PyPI, bundled binary included):

claude mcp add blockfill -- uvx --from 'blockfill[mcp]' blockfill-mcp

Already pip installed? Register the installed server in one step:

pip install -U "blockfill[mcp]"
blockfill-mcp --install        # auto-configures detected clients + prints config for the rest

--install detects and configures Claude Code (claude mcp add), Codex (~/.codex/config.toml), and Cursor (~/.cursor/mcp.json) without clobbering existing entries, and prints the generic block below for any other client. The launch command is identical everywhere — only the config location/format differs.

Claude Desktop / any MCP client — add to the client config (e.g. claude_desktop_config.json), then restart the client:

{
  "mcpServers": {
    "blockfill": { "command": "uvx", "args": ["--from", "blockfill[mcp]", "blockfill-mcp"] }
  }
}

Then set exchange credentials once (credential entry is intentionally not an MCP tool):

blockfill set-credentials --exchange binance-futures --api-key ... --api-secret ...

Plugin marketplace (optional — also bundles the skill)

For discovery / to ship the BlockFill skill alongside the tools:

/plugin marketplace add <this repo url>
/plugin install blockfill@blockfill
/reload-plugins

(The plugin's MCP server still uses the same uvx --from blockfill[mcp] blockfill-mcp command under the hood.)


API Reference

Version

bf.version() -> str
# Returns "blockfill 1.0.X"

Credentials

bf.set_credentials(
    exchange: str,            # "binance-futures" | "okx-swap"
    api_key: str,
    api_secret: str,
    api_passphrase: str | None = None,  # OKX, KuCoin, Bitget — required for these three; None for all others
    testnet: bool = False,  # SDK default is MAINNET — agents MUST pass True explicitly for testnet; default to True unless the user has confirmed mainnet
) -> None
# Writes the [exchanges.<name>] block of {data_dir}/config.toml (chmod 0600),
# runs check_credentials() to print verification, then stops + starts the
# daemon (when running) so the new user_id/creds load immediately — avoids
# identity confusion where `place` / history queries would otherwise keep
# operating under the OLD creds cached in the running daemon.

The blockfill-server endpoint and API key are compiled into the binary at release time — you do not configure them.


Payment — x402 quota top-up

Quota applies to CEX venues only (Binance, OKX, Bybit, Bitget, Gate.io, KuCoin, Kraken, Deribit); DEX venues (Hyperliquid, Aster, Orderly) pay builder-code execution fees instead and have no quota.

Quota is tracked per (account, exchange) pair. Your account is a de-identified hash of the exchange api_key (or, for wallet venues, the wallet address) — computed locally, so the raw key/address never leaves your machine. Each exchange credential therefore has its own quota.

Every pair starts with a free tier. When it runs out you buy more quota by paying USDC on Base over x402 — a gasless EIP-3009 transferWithAuthorization. The daemon holds the wallet key and signs locally; only the signature + authorization leave the machine.

bf.set_payment(private_key: str | None) -> None
# Store (or clear, with None) the EVM wallet key that pays for top-ups, in
# [payment] of {data_dir}/config.toml (chmod 0600). Restarts the daemon so it
# picks the key up. The wallet must hold USDC on the quota network (Base /
# Base-Sepolia); gas is paid by the facilitator, so no ETH is needed.

bf.topup(exchange: str, usdc: float = 1.0) -> dict | None
# Manually buy quota for `exchange` by paying `usdc` USDC. The daemon fetches
# the 402 challenge, signs the EIP-3009 authorization with the set_payment
# wallet, and settles via the facilitator. Quota is per exchange account, so
# top up each exchange you trade. Returns {exchange, usdc, quota_balance,
# tx_hash} on success, or None on failure (a one-line error is printed).
bf.set_payment("0x<64-hex private key>")
bf.topup("okx-swap", 1)   # → {'exchange': 'okx-swap', 'quota_balance': ..., 'tx_hash': '0x…'}

The key is never sent anywhere — the server only ever sees the signature and a de-identified (hashed) account id, never your api_key or wallet key.


Daemon

bf.start(wait_timeout_s=10.0, env=None) -> None
# Spawns the daemon in the background, returns once the UDS socket is bound.
# Idempotent — no-op if already running.

bf.stop(wait_timeout_s=5.0) -> None
# Graceful shutdown.

bf.restart() -> None

bf.status() -> DaemonStatus
# Always returns a DaemonStatus (`running=False, ...` when the daemon is
# not reachable — never raises). Single entry point for everything you
# need to know about the daemon's state.

For debugging, tail the daemon log directly:

tail -F ~/.blockfill/runtime/daemon.startup.log

DaemonStatus fields:

Field Type Meaning
running bool Daemon process is up and the RPC socket answered.
pid int Daemon process id (0 if not running).
exchange (prop) dict[str, ExchangeStatus] Per-exchange status keyed by name — see below. Primary way to check what's configured and what's ready.
active_tickets int NEW/OPEN tickets currently tracked.
uptime_s int Daemon process uptime in seconds.
version str Daemon binary version (e.g. "1.0.X").
proxy str | None Active outbound proxy URL (or None for direct).
blockfill-server bool True if blockfill-server is reachable. blockfill-server hosts the ticket-history endpoints (/public/v1/tickets/*) used by bf.query(history=True); when down, live trading is unaffected but history queries fail. Updated by a 10s background ping; flips False after 30s of no response.

exchanges (list[str], configured names) and ready_exchanges (list[str], warmed-up names) are also present on the dataclass as flat-list shortcuts — they're just list(exchange.keys()) and [n for n,e in exchange.items() if e.ready] respectively. Prefer exchange[name].ready in code.

Per-exchange readiness (the only way to check ready — there is no top-level ready flag because "all-ready" is rarely meaningful in a multi-exchange daemon):

s = bf.status()
s.exchange
# → {
#     "binance-futures": ExchangeStatus(ready=True),
#     "okx-swap":        ExchangeStatus(ready=False),
#   }

if s.exchange["binance-futures"].ready:
    bf.place(exchange="binance-futures", ...)

ready=True means the executor finished warmup (REST symbol/book fetch, authenticated get_account_balances, market-stream connect — ready_cb fired). ready=False covers:

  • daemon not running
  • warmup in progress (typical 30–60s after bf.start())
  • init failed and supervisor is in its retry backoff (bad creds, IP whitelist, geo block, exchange API down)

bf.place(exchange=X, ...) while X is not ready is rejected at the RPC layer with -32000 X not ready — executor still warming up; poll system.status until ready_exchanges contains it — no ticket is created.


Tickets

Strategies

strategy Behavior
"maker" Passive maker. Posts PostOnly limit orders that sit on the book. In the last segment of the time window, falls back to IOC to clean up any unfilled remainder. Lower fees (maker rebate when available), no guarantee of full fill if the book never crosses.
"twap" Pure-taker TWAP. Places IOC orders on a TWAP schedule across the time window — no PostOnly phase. Guarantees completion at the cost of crossing the spread on every slice.
bf.place(
    exchange: str,
    symbol: str,
    strategy: str = "maker",        # "maker" | "twap" (see table above)
    target_position: float,          # positive = long, negative = short
    time_constraint_ms: int = 300_000,  # 60_000 .. 86_400_000 (1min .. 24h)
) -> Ticket

bf.query(
    status: str | None = None,       # "NEW" | "OPEN" | "COMPLETE" | "CANCEL"
    symbol: str | None = None,
    ticket_id: str | None = None,
    from_ms: int | None = None,
    to_ms: int | None = None,
    limit: int = 100,
    history: bool = False,           # False=in-memory; True=blockfill-server MongoDB
) -> list[Ticket]

bf.cancel(ticket_id=None, symbol=None, all=False) -> None | int
# - cancel(ticket_id="tkt_...") -> None     # prints an error and returns None if not found (never raises)
# - cancel(symbol="btcusdt")    -> int      # cancel NEW+OPEN for that symbol
# - cancel(all=True)            -> int      # cancel everything active

Spot vs perp target_position. For perp/futures it is the net directional position (positive = long, negative = short) — target=0.1 from flat opens 0.1 long. For spot it is the absolute base-asset holding you want to end up with, and init_position is your current base balance — so target=0.001 on a 1.0 BTC balance sells 0.999. To add to a spot holding, set target = current_holding + delta.

Ticket fields:

Field Type Notes
ticket_id str tkt_<hex>
status str NEW / OPEN / COMPLETE / CANCEL
exchange str binance-futures / okx-swap
symbol str exchange-format symbol
strategy str maker / taker
target_position float requested net position
init_position float | None exchange position at activation time
executed_position float | None actual delta filled so far
time_constraint_ms int execution time limit
start_time_ms int | None set when executor activates the ticket (NEW → OPEN)
last_update_time_ms int | None refreshed on every state change
is_expired bool flag-only; status stays OPEN until separately cancelled
cancel_reason str | None see table below

cancel_reason values: external, superseded, stale, rejected, min_notional, risk_breach, insufficient_margin, paused

Auto-supersede: placing a new ticket for the same exchange+symbol immediately cancels any existing NEW/OPEN ticket for that pair (cancel_reason="superseded"). The superseded ticket remains in query results.


Diagnostics

bf.check_credentials() -> None
# Calls a SIGNED REST endpoint on each configured exchange and prints one
# line per exchange:
#   ✓ binance-futures       3 asset balances returned
#   ✗ okx-swap              API error 50101: APIKey does not match current environment.
# Detects: wrong key/secret, IP whitelist mismatch, testnet/mainnet flag
# wrong, network / proxy / geo block. Does not raise, does not return a
# status code — visual output is the signal.
# Auto-invoked at the end of `set_credentials(...)`.

Positions

bf.positions() -> list[dict]
# Each entry: {exchange, symbol, size, entry_price, update_ts_ms}
# Aggregated across all running executors.

bf.nav() -> dict
# {exchanges: [{exchange, nav, wallet_balance, margin_value, unrealized_pnl}],
#  total_nav, exchanges_queried}. NAV = wallet + margin (USD) + unrealized PnL.

bf.tca(ticket_id=None, symbol=None, from_ms=None, to_ms=None,
       limit=100, history=False) -> list[dict]
# Ticket transaction-cost analysis, queried like bf.query():
# history=False -> active session (in-memory); history=True -> persistent
# (blockfill-server). Each entry: benchmarks, fills, maker/taker breakdown,
# execution_cost_usd / opportunity_cost_usd, duration_ms, ...

Proxy / Geo-bypass

For hosts that can't reach Binance directly (US IPs return HTTP 451), route exchange REST traffic through an HTTP CONNECT proxy.

Starchild users — the free sc-vpn skill provides a managed proxy across 18 countries (500 GB/month, no credentials). Pick a country code and pass the URL:

bf.set_proxy("http://jp:x@sc-vpn.internal:8080")   # Japan
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

Country codes (ISO-2):

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

For binance, jp / sg / hk give the lowest latency. See the sc-vpn skill repo for the authoritative list.

You can also pass any HTTP CONNECT proxy URL (residential / paid):

bf.set_proxy("http://user:pass@proxy.example.com:8080")

set_proxy auto-restarts the daemon (when running) so the new proxy takes effect immediately — the daemon reads the proxy only at startup and stashes it in a global, so a write-without-restart would leave the running daemon on the OLD proxy.

The proxy applies to all REST traffic from daemon → exchange. WebSocket proxy support is planned; until then, market-data streams connect directly and will fail on geo-blocked hosts.

Verify before committing: after bf.set_proxy(...) re-set credentials — set_credentials auto-runs check_credentials which does a signed REST round-trip through the proxy. A failure there means the proxy can't reach the exchange, so you find out before starting the daemon.


Context Manager

with Blockfill() as bf:
    bf.start()
    ticket = bf.place(...)
# daemon is stopped on exit

Patterns

Strategy system integration

from blockfill import Blockfill

bf = Blockfill()

if not bf.status().running:
    bf.start()

# On each signal
ticket = bf.place(
    exchange="binance-futures",
    symbol=symbol,
    strategy="maker",
    target_position=position,
    time_constraint_ms=300_000,
)

Supported Exchanges

Exchange Value Credentials
Binance Futures "binance-futures" api_key + api_secret (HMAC or Ed25519)
OKX Swap "okx-swap" api_key + api_secret + api_passphrase (HMAC)
Hyperliquid Perp "hyperliquid-perp" account wallet private_key (EIP-712)
Aster Perp "aster-perp" account wallet private_key (EIP-712)
Orderly (perps) "orderly" account_id + ed25519 secret + broker_id

("aster-perp-v1" also exists — Aster's legacy HMAC api_key/secret flow, configured like a CEX. Prefer "aster-perp".)

Orderly: broker = the exchange (= a separate vault)

Orderly is a settlement layer, not an exchange — the broker (WOOFi Pro, Raydium, …) is the venue. The same wallet under a different broker_id is a separate account/vault (account_id = keccak256(wallet, keccak256(broker_id))).

Pass the bare name "orderly"; the entry is stored under orderly-<broker_id>, which is the name to give place(). Configure each broker once:

bf.set_credentials("orderly",
    account_id="0x...", orderly_secret="...", broker_id="woofi_pro",
    testnet=True)                          # → stored as orderly-woofi_pro
bf.set_credentials("orderly",              # same wallet, different vault
    account_id="0x...", orderly_secret="...", broker_id="raydium",
    testnet=True)                          # → stored as orderly-raydium
# bf.place(exchange="orderly-raydium", ...); bf.nav() reports each separately

api_key / api_secret are accepted as aliases for account_id / orderly_secret. The secret is the base58 ed25519 seed; Orderly's key generator emits it as ed25519:<base58> and either form is accepted — the prefix belongs on the wire, so it is stripped before storage.

account_id is bound to broker_id at registration — you cannot switch brokers by changing broker_id alone; register under the new broker and use that account_id. Passing the (public) wallet_address is optional and turns that rule into a check: check_credentials recomputes the account id and fails when the trio disagrees, which is the only way to catch a broker_id that is real but belongs to a different account of the same wallet.

CLI equivalent:

blockfill set-credentials --exchange orderly \
  --api-key <account_id> --api-secret <base58 ed25519 secret> \
  --api-passphrase woofi_pro

Starting from just a wallet

register-orderly does the whole Orderly onboarding in one command — registers the account under the broker, generates an ed25519 pair, delegates it via EIP-712, and writes the credentials:

blockfill register-orderly --broker-id woofi_pro --testnet

The wallet key is prompted for when not passed, and is stored alongside the delegated access key so re-registering under another broker or rotating an expiring access key needs no re-entry — both operations require a wallet EIP-712 signature again. Note it can withdraw, unlike the ed25519 access key beside it; config.toml is 0600, but treat the entry as wallet-grade. Add --scope (must include trading) or --expiration-days (Orderly's maximum is 365) to override the defaults.

Binance Ed25519 keys

Binance Futures testnet issues self-generated Ed25519 keys (no HMAC secret). They work with the same call — pass the Ed25519 API Key id as api_key and the PEM private key as api_secret; the daemon auto-detects the PEM and signs with Ed25519 (REST + WS session.logon):

bf.set_credentials(
    "binance-futures",
    api_key="<Ed25519 API Key id from Binance>",
    api_secret="""-----BEGIN PRIVATE KEY-----
MC4CAQAw...your PKCS#8 ed25519 key...
-----END PRIVATE KEY-----""",
    testnet=True,
)

To create one: generate an Ed25519 keypair, register the public key (PEM) at https://testnet.binancefuture.com → API Management, and Binance returns the API Key id. Keep the private key for api_secret. HMAC keys keep working unchanged.

Hyperliquid and Aster are wallet-signed DEXes — there is no api_key/secret. Configure them via the SDK:

# Hyperliquid / Aster: the account owner's wallet key. It signs orders AND the
# one-time builder-fee approval, which an agent/API wallet is not allowed to
# sign — see docs/security/trade-only-permissions.md before using these venues.
bf.set_credentials(
    "hyperliquid-perp",
    private_key="0x...",      # required
    # wallet_address="0x...", # optional — derived from the key when omitted
)
bf.set_credentials("aster-perp", private_key="0x...")

or the bundled CLI (blockfill set-credentials --exchange hyperliquid-perp --hl-master-address 0x... --hl-agent-private-key 0x...; Aster: --aster-user/--aster-signer/--aster-agent-private-key).

⚠️ Symbol format differs per exchange

Each exchange has its own native symbol format. BlockFill does NOT cross-translate — bf.place(symbol=...) must match exactly what the exchange uses:

Exchange Format Example
binance-futures lowercase, concatenated dogeusdt
okx-swap dash-separated, -SWAP suffix DOGE-USDT-SWAP
hyperliquid-perp coin only (perps are vs USDC) BTC
aster-perp UPPERCASE, concatenated BTCUSDT

Don't guess — look it up with bf.instruments(<substring>). It scans all configured exchanges and returns native-format matches:

bf.instruments("doge")
# [
#   {"exchange": "binance-futures", "symbol": "dogeusdt",        ...},
#   {"exchange": "okx-swap",        "symbol": "DOGE-USDT-SWAP",  ...},
#   ...
# ]

bf.place(exchange="binance-futures", symbol="dogeusdt",       target_position=100)
bf.place(exchange="okx-swap",        symbol="DOGE-USDT-SWAP", target_position=-100)

Wrong format → ticket auto-cancelled with cancel_reason="rejected"; no order ever reaches the exchange.


FAQ

What is BlockFill Agent Execution?

BlockFill Agent Execution is an AI-agent-ready Python SDK and local execution engine for crypto perpetual futures order execution.

Can AI agents use BlockFill?

Yes. AI agents, MCP servers, trading copilots, and strategy systems can call the BlockFill Python SDK to place and manage execution tickets.

Does BlockFill custody user funds or API keys?

No. Exchange API keys stay on the user's machine. The local daemon signs orders locally and sends them directly to the exchange.

Which exchanges does BlockFill support?

BlockFill Agent Execution supports Binance, OKX, Bybit, Bitget, Gate.io, KuCoin, Kraken, Deribit, Hyperliquid, Aster, and Orderly — see the Supported Venues table for per-exchange product types and credential formats.

Which execution strategies are supported?

BlockFill currently supports TWAP execution and maker-style execution.

Is BlockFill an exchange?

No. BlockFill is not an exchange. It is an execution SDK and local order execution engine that connects to supported exchanges through user-owned API keys.


Related Concepts

BlockFill Agent Execution is related to AI agent trading, crypto order execution, TWAP execution, maker execution, algorithmic trading, perpetual futures trading, Binance Futures execution, OKX Swap execution, Bybit execution, Bitget execution, Gate.io execution, KuCoin execution, Kraken execution, Deribit execution, Hyperliquid execution, Aster execution, Orderly execution, MCP trading tools, and self-custodial trading infrastructure.


Support

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

blockfill-2.2.1-py3-none-manylinux2014_x86_64.whl (10.4 MB view details)

Uploaded Python 3

blockfill-2.2.1-py3-none-macosx_11_0_arm64.whl (9.0 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file blockfill-2.2.1-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for blockfill-2.2.1-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 116f400b67707d95caeb965fde0e15362d0a7d92a5916e1263c1028f48acd450
MD5 ed3d1a272e492f814ba27f3feecb9f4f
BLAKE2b-256 cacd90280acb54aafe068012e996cef84e06b04c8e720cd3900e30e7d0647e46

See more details on using hashes here.

File details

Details for the file blockfill-2.2.1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for blockfill-2.2.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e95a51f9880285d07e628e2babe2ba2f648c1297ae683ef0adab8cb45ba70605
MD5 0818eae65038369f0f1c05cfe7bb33bb
BLAKE2b-256 62a2e8b2411aff423b63b0e783114dbc87a169f36dc1aa8036c008c111277f65

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page