Metadata-Version: 2.5
Name: blockfill
Version: 2.1.1
Summary: BlockFill Agent Execution is an AI agent execution skill for crypto order execution, focused on TWAP, maker execution, transaction cost analysis and slippage reduction. Covers 11 CEX/DEX venues — Binance, OKX, Bybit, Bitget, Gate.io, KuCoin, Kraken, Deribit, Hyperliquid, Aster, Orderly — on perps and spot. API keys stay local.
Project-URL: Homepage, https://gitlab.com/quantech-services-group/blockfill-agent-execution
Project-URL: Source, https://gitlab.com/quantech-services-group/blockfill-agent-execution
Project-URL: Documentation, https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/blob/main/README.md
Project-URL: Issues, https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/issues
Project-URL: PyPI, https://pypi.org/project/blockfill/
Project-URL: Changelog, https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/tags
Project-URL: Security, https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/blob/main/SECURITY.md
Project-URL: Skill (SKILL.md), https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/blob/main/plugin/skills/blockfill/SKILL.md
Project-URL: Prompt Library, https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/blob/main/docs/prompt-library.md
Project-URL: MCP Server, https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/blob/main/docs/mcp-server.md
Author-email: Quantech <ai@quantech.services>
License: Proprietary
Keywords: agent-trading-skill,ai-agent,algorithmic-trading,aster,binance-futures,bitget,blockfill,bybit,crypto-perpetuals,crypto-trading,deribit,execution-algorithm,execution-cost,gate-io,hyperliquid,kraken,kucoin,maker-execution,mcp,mcp-server,okx,order-execution,orderly,perpetual-futures,slippage-reduction,smart-order-routing,spot-trading,tca,trading-sdk,transaction-cost-analysis,twap
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: toml
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == 'mcp'
Description-Content-Type: text/markdown

# BlockFill Agent Execution

<!-- mcp-name: services.quantech/blockfill -->

> 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](plugin/skills/blockfill/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 TWAP 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 TWAP 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 `twap` (pure taker TWAP) |
| `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 verified** — `bf.check_credentials()` returns `✓` for the target exchange (auth + IP whitelist + network reachability in one signed round-trip). → [docs/troubleshooting.md](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](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](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 margin** — `bf.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](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 TWAP order on binance-futures testnet: buy 0.1 BTCUSDT perpetual over 15 minutes using twap 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 TWAP) for a given exchange, symbol, target position, and time window | [api-reference.md](docs/api-reference.md) |
| `query_ticket` | Returns current status, fill progress, and metadata for tickets filtered by id, symbol, or time range | [api-reference.md](docs/api-reference.md) |
| `cancel_ticket` | Cancels an active `NEW` or `OPEN` ticket; outstanding exchange orders are pulled automatically | [api-reference.md](docs/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](docs/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](docs/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](docs/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](#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>` (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:

```json
{
  "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**:

```toml
[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:

```json
{
  "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](docs/security/trade-only-permissions.md): 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](#hyperliquid--aster--the-account-wallet-key) and
  [trade-only permissions](docs/security/trade-only-permissions.md) before
  funding a DEX account.

**Full security & compliance reference** — [`SECURITY.md`](SECURITY.md) and
[`docs/security/`](docs/security/index.md): [data flow](docs/security/data-flow-statement.md),
[network flow](docs/security/network-flow.md),
[binary/daemon security](docs/security/binary-daemon-security.md),
[trade-only permissions](docs/security/trade-only-permissions.md),
[EIP-712 authorization](docs/security/eip-712-authorization.md),
[risk disclosure](docs/security/risk-disclosure.md).

### 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](docs/security/risk-disclosure.md).

## 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

```bash
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

```python
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.

```python
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):

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

**Already `pip install`ed?** Register the installed server in one step:

```bash
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:

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

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

```bash
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

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

---

### Credentials

```python
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](https://github.com/coinbase/x402) — a
**gasless** EIP-3009 `transferWithAuthorization`. The daemon holds the wallet key
and signs locally; only the signature + authorization leave the machine.

```python
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).
```

```python
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

```python
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:

```bash
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):

```python
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.                                                                                    |

```python
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` / `twap`                                        |
| `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

```python
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

```python
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:

```python
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](https://github.com/Starchild-ai-agent/official-skills/tree/main/sc-vpn)
for the authoritative list.

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

```python
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

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

---

## Patterns

### Strategy system integration

```python
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-<broker>"` | 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))`).
So configure each broker as its own instance, `orderly-<broker>`:

```python
bf.set_credentials("orderly-woofi",
    account_id="0x...", orderly_secret="ed25519:...", broker_id="woofi_pro",
    testnet=True)
bf.set_credentials("orderly-raydium",     # same wallet, different vault
    account_id="0x...", orderly_secret="ed25519:...", broker_id="raydium",
    testnet=True)
# bf.place(exchange="orderly-raydium", ...); bf.nav() reports each separately
```

`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`. (CLI: `blockfill set-credentials --exchange orderly-raydium
--api-key <account_id> --api-secret <ed25519 secret> --api-passphrase <broker_id>`.)

### 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`):

```python
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:

```python
# 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:

```python
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

- **GitLab Issues**: <https://gitlab.com/quantech-services-group/blockfill-agent-execution/-/issues>
  — bug reports, feature requests
- **Telegram**: message [@blockfill_support](https://t.me/blockfill_support)
  — questions, integration help, incident reports
