Skip to content

Commit faef27b

Browse files
committed
feat: ship RFQ-only with Connect and migrate from public ladder
- New bots are RFQ-only (`book_enabled = false`) and refuse Start without a maker credential; Connect registers the wallet with Textile and writes `rfq-api.key`. - Add `stitch connect --config <path>` for CLI-only operators to enroll without the panel; moves enroll logic into a shared module so panel and CLI use the same protocol. - Extract enroll protocol from panel HTTP handler into `src/enroll.rs` so both panel and CLI share the same wire types, signature, config patch, and corridor selection. - Add Legacy card to Settings to toggle `book_enabled` on migrated bots; card is collapsed by default, reads config, and refuses the switch when a side lacks both spread and size. - Add `migrate-book-to-rfq.md` guide covering both panel and standalone CLI paths; explains what changes in config (spreads/liquidity carry over, ttl/refresh/max-orders stop applying, TWAP and lean reach limit-taker leg only), how to verify from logs, and rollback via Legacy card. - Update `stitch init` to point at `stitch connect` rather than the panel; install guides now include Connect before Approve. - Update all install guides (panel, Docker, Linux, macOS, Windows, server) with Connect steps; update example configs and docker-compose.example.yml to show per-bot directories. - Drop RFQ beta gates (`enable-rfq-beta`, `enable-rfq`); corridor mode stays `RFQ_BETA` until Limit is deleted, but stitch config sees no change. - Add container-entrypoint.sh support for `STITCH_RFQ_API_KEY` environment variable; systemd service gets ImportCredential for optional `rfq-api.key`. - Update `stitch.service` with detailed comments on loading optional `rfq-api.key` via ImportCredential; clarifies why it must be optional and where it lives. - Update Cargo.toml description to reflect RFQ firm quoting and optional limit-order filling. - Update README and SKILL.md to clarify new-bot RFQ-only flow and that Connect is required before Start. - Clarify in ADVANCED.md that TWAP and lean apply only to public ladder and limit-taker leg, not RFQ; add ops section covering production cutover steps and inventory when both channels are on.
1 parent 1fb185e commit faef27b

55 files changed

Lines changed: 4179 additions & 849 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/stitch/SKILL.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ description: Operate Stitch, the Textile operator bot — start, stop, restart,
55

66
# Operate Stitch
77

8-
Stitch is the Textile operator bot: per-pool market making. New installs use the
9-
**Stitch panel** (Docker web UI). This skill operates an existing layout and, if
10-
nothing is set up yet, installs the panel only — bot config happens in the browser.
8+
Stitch is the Textile operator bot: per-pool Swap quoting via RFQ. New installs
9+
use the **Stitch panel** (Docker web UI). This skill operates an existing layout
10+
and, if nothing is set up yet, installs the panel only — bot config happens in
11+
the browser. New bots need **Connect** on Settings before Start.
1112

1213
## Always start here
1314

@@ -95,7 +96,8 @@ see [Not installed yet](#not-installed-yet).
9596
- After any change to pricing or sizing (spreads, liquidity, feed), run a
9697
`--dry-run` before going live again.
9798
- Don't start live operation or install a service without the operator's
98-
explicit go-ahead.
99+
explicit go-ahead. A new RFQ-only bot also needs Connect on Settings first
100+
— Start without a maker key quotes nothing.
99101

100102
## Start
101103

.codex/skills/stitch/SKILL.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ metadata:
77

88
# Operate Stitch
99

10-
Stitch is the Textile operator bot: per-pool market making. New installs use the
11-
**Stitch panel** (Docker web UI). This skill operates an existing layout and, if
12-
nothing is set up yet, installs the panel only — bot config happens in the browser.
10+
Stitch is the Textile operator bot: per-pool Swap quoting via RFQ. New installs
11+
use the **Stitch panel** (Docker web UI). This skill operates an existing layout
12+
and, if nothing is set up yet, installs the panel only — bot config happens in
13+
the browser. New bots need **Connect** on Settings before Start.
1314

1415
## Always start here
1516

@@ -96,7 +97,8 @@ see [Not installed yet](#not-installed-yet).
9697
- After any change to pricing or sizing (spreads, liquidity, feed), run a
9798
`--dry-run` before going live again.
9899
- Don't start live operation or install a service without the operator's
99-
explicit go-ahead.
100+
explicit go-ahead. A new RFQ-only bot also needs Connect on Settings first
101+
— Start without a maker key quotes nothing.
100102

101103
## Start
102104

.textile-monorepo-source

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
bf58a6cebce223a65e6633df6b70136c7d6bfd63
1+
f72f0ba3b80e50d26117c3e5a7779614de9552a1

.textile-stitch-release-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.212
1+
0.1.213

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
22
name = "stitch-bot"
3-
version = "0.1.212"
3+
version = "0.1.213"
44
edition = "2021"
5-
description = "Stitch — Textile filler-network operator bot; market-makes the filler order book with signed UniswapX limit orders."
5+
description = "Stitch — Textile filler-network operator bot; quotes Swap via RFQ firm quotes and optionally fills resting limit orders."
66
license = "AGPL-3.0-or-later"
77
repository = "https://github.com/textile-protocol/textile-stitch"
88
homepage = "https://github.com/textile-protocol/textile-stitch"

README.md

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@
99
Stitch is the Textile operator bot for filler-network market making. It runs
1010
as a single binary named `stitch`.
1111

12-
Stitch does one job for each configured pool by default, plus an optional
13-
second:
12+
Stitch quotes Swap by default, plus an optional second job:
1413

15-
- **Market making**: keep live buy and sell quotes for a configured
16-
soft-asset/stablecoin pair.
14+
- **Swap quoting (RFQ)**: answer venue quote requests with firm, taker-bound
15+
orders for a configured soft-asset/stablecoin pair. Connect the bot to
16+
Textile before going live — a new bot will not Start without that. Connect
17+
from the panel's Settings, or with `stitch connect` on the CLI.
1718
- **Limit-order taking** (opt-in): fill traders' resting limit orders on-chain
1819
when their price is at or beyond your own quote, priced by the same spreads
19-
as your market making.
20+
as your Swap quotes.
2021

2122
## Contents
2223

@@ -53,11 +54,12 @@ No Docker and no terminal. [Download the release for your OS](https://github.com
5354
entry). For a headless server, prefer Option 2.
5455

5556
In the browser: sign in with the password you created, **Add a bot**, pick a
56-
corridor, paste your operator wallet key, approve allowances, dry-run, then
57-
Start. Use **Start at login** in Settings if you want the panel (and any bots
58-
left running) to come back after a reboot — login starts stay in the tray and
59-
skip the control window. Quit Stitch from the menu / tray / window to stop the
60-
panel. For unattended 24/7 quoting on a server, use Option 2.
57+
corridor, paste your operator wallet key, approve allowances, then **Connect**
58+
the bot to Textile on Settings before Start. Use **Start at login** in Settings
59+
if you want the panel (and any bots left running) to come back after a reboot
60+
— login starts stay in the tray and skip the control window. Quit Stitch from
61+
the menu / tray / window to stop the panel. For unattended 24/7 quoting on a
62+
server, use Option 2.
6163

6264
### Option 2 — Server / Docker
6365

@@ -109,6 +111,11 @@ Everything else — custom reverse proxy, building from source — is in
109111
- [Manual install — Windows](docs/install-windows.md)
110112
- [Manual install — Linux](docs/install-linux.md) — includes the systemd service setup.
111113

114+
Already running a bot on the public ladder?
115+
[Migrating from the public ladder to Swap (RFQ)](docs/migrate-book-to-rfq.md)
116+
covers both the panel and the
117+
[standalone CLI](docs/migrate-book-to-rfq.md#standalone-cli).
118+
112119
### Install with an AI agent
113120

114121
Your coding agent installs the Stitch panel and opens the web UI. You finish
@@ -154,15 +161,18 @@ The full copyable prompt is in [AI_INSTALL_PROMPT.md](docs/AI_INSTALL_PROMPT.md)
154161
</details>
155162

156163
Running the binary directly without the panel? `stitch init` writes `stitch.toml`,
157-
`stitch.env` and an owner-only `stitch.key` for a chosen corridor. The per-OS
158-
guides cover it.
164+
`stitch.env` and an owner-only `stitch.key` for a chosen corridor, then
165+
`stitch connect` registers the wallet with Textile so the bot can quote Swap.
166+
The per-OS guides cover both.
159167

160168
## How It Works
161169

162-
Stitch reads `stitch.toml`, polls your configured price feed, signs UniswapX
163-
limit orders, and posts those signed orders to the Textile indexer. The wallet
164-
private key is read from `STITCH_PRIVATE_KEY_FILE`, or from `STITCH_PRIVATE_KEY`
165-
for compatibility. If both are set, `STITCH_PRIVATE_KEY_FILE` takes precedence.
170+
Stitch reads `stitch.toml`, polls your configured price feed, and answers Swap
171+
quote requests with firm, taker-bound UniswapX limit orders signed against the
172+
operator wallet. Connect in the panel registers the maker and writes
173+
`rfq-api.key`. The wallet private key is read from `STITCH_PRIVATE_KEY_FILE`,
174+
or from `STITCH_PRIVATE_KEY` for compatibility. If both are set,
175+
`STITCH_PRIVATE_KEY_FILE` takes precedence.
166176

167177
### Signer / wallet backend
168178

@@ -200,13 +210,19 @@ manual `[signer]` fields below are for CLI and server operators editing
200210
The operator wallet still needs a little native gas for Permit2 approvals
201211
(`stitch approve`) no matter which signer you use.
202212

203-
For market making, each configured pool can have:
213+
For Swap quoting, each configured pool can have:
204214

205215
- a **buy side**, where Stitch spends the stable/debt asset to buy the
206216
soft/collateral asset below the feed price;
207217
- a **sell side**, where Stitch spends the soft/collateral asset to sell above
208218
the feed price.
209219

220+
RFQ prices those quotes off the latest feed print plus your configured
221+
spreads, capped by your liquidity — no TWAP smoothing and no inventory lean.
222+
TWAP and lean apply to the public ladder and, when it's on, the limit-order
223+
taker leg. TTL, refresh, and max-orders are ladder-only (`book_enabled`). New
224+
bots ship with the book off.
225+
210226
With `limit_taker_enabled = true` on a pool, Stitch also checks the corridor's
211227
resting trader limit orders every tick and fills the profitable ones on-chain:
212228
a trader selling the soft asset fills at or below your bid, a trader buying it
@@ -246,6 +262,7 @@ indexer_url = "https://api.textilecredit.com"
246262
permit2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3"
247263
reactor = "0x0000000000000000000000000000000000000000"
248264
tick_interval_secs = 5
265+
book_enabled = false
249266

250267
[feed]
251268
url = "https://your-feed.example/cngn-usdt"

deploy/container-entrypoint.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ if [ -n "${STITCH_PRIVATE_KEY:-}" ]; then
2424
unset STITCH_PRIVATE_KEY
2525
fi
2626

27+
# The maker credential from `stitch connect`. A new bot quotes Swap over RFQ and
28+
# rests no public ladder, so without this it starts and serves nothing. Filed the
29+
# same way as the wallet key rather than left in the environment, which
30+
# `docker inspect` and /proc expose. Only the default `[rfq].api_key_env` name is
31+
# handled here; a custom one is the operator's to pass.
32+
if [ -n "${STITCH_RFQ_API_KEY:-}" ]; then
33+
rfq_key_path="${runtime_dir}/rfq-api.key"
34+
printf '%s\n' "${STITCH_RFQ_API_KEY}" > "${rfq_key_path}"
35+
export STITCH_RFQ_API_KEY_FILE="${rfq_key_path}"
36+
unset STITCH_RFQ_API_KEY
37+
fi
38+
2739
# MPC-wallet credentials (only one signer is used at a time; whichever the
2840
# config selects). Each secret env var, if present, is written to a 0600 file and
2941
# the matching *_FILE var is exported, same as the local key above. The Turnkey

deploy/stitch.service

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
# sudo mkdir -p /etc/stitch-bot
99
# sudo cp stitch.toml /etc/stitch-bot/stitch.toml
1010
# sudo cp stitch.key /etc/stitch-bot/stitch.key # holds the private key, chmod 600
11+
# sudo mkdir -p /etc/credstore && sudo chmod 700 /etc/credstore
12+
# sudo cp rfq-api.key /etc/credstore/rfq-api.key # maker credential from `stitch connect`, chmod 600
13+
# # (optional: without it the bot runs, minus RFQ)
1114
# sudo cp stitch.env /etc/stitch-bot/stitch.env # non-secret env, e.g. RUST_LOG
12-
# sudo chmod 600 /etc/stitch-bot/stitch.key
15+
# sudo chmod 600 /etc/stitch-bot/stitch.key /etc/credstore/rfq-api.key
1316
# sudo cp stitch.service /etc/systemd/system/
1417
# sudo systemctl daemon-reload
1518
# sudo systemctl enable --now stitch
@@ -48,6 +51,26 @@ Type=simple
4851
EnvironmentFile=/etc/stitch-bot/stitch.env
4952
LoadCredential=stitch.key:/etc/stitch-bot/stitch.key
5053
Environment=STITCH_PRIVATE_KEY_FILE=%d/stitch.key
54+
# The maker credential `stitch connect` writes. A new bot quotes Swap over RFQ
55+
# and rests no public ladder, so without this it starts and serves nothing.
56+
# It has to come through LoadCredential for the same reason the wallet key does:
57+
# DynamicUser=yes runs as a transient UID that cannot read a root-owned 0600 file
58+
# under /etc, so the bot's "rfq-api.key next to stitch.toml" fallback never
59+
# resolves here.
60+
#
61+
# ImportCredential=, not LoadCredential=, because this one is optional. A
62+
# LoadCredential= source that does not exist fails the unit before ExecStart,
63+
# which would strand every bot that legitimately has no maker key: one still
64+
# waiting on `stitch connect`, or a limit-taker-only bot that never needs one.
65+
# ImportCredential= simply stages nothing when there is no match, and the bot
66+
# then logs that the maker key is missing, skips the responder, and runs its
67+
# other legs. (An empty `SetCredential=rfq-api.key:` is NOT the way to do this —
68+
# systemd rejects an empty value outright: "Invalid syntax, ignoring".)
69+
#
70+
# ImportCredential= reads the credential store, so this key lives in
71+
# /etc/credstore rather than beside the config.
72+
ImportCredential=rfq-api.key
73+
Environment=STITCH_RFQ_API_KEY_FILE=%d/rfq-api.key
5174
ExecStart=/usr/local/bin/stitch --config /etc/stitch-bot/stitch.toml
5275
# The bot traps SIGTERM and exits cleanly after the current tick.
5376
Restart=always

docker-compose.example.yml

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
# Run two Stitch bots side by side, each with its own config and wallet key.
22
#
33
# One container = one bot = one signer = one corridor. To add more, copy a
4-
# service block, point it at another stitch.<name>.toml + key, and give it a
4+
# service block, point it at another bots/<name> directory, and give it a
55
# unique container_name.
66
#
7+
# One directory per bot, not one shared folder of stitch.<name>.toml files:
8+
# `stitch connect` writes `rfq-api.key` next to the config it was given, so two
9+
# bots sharing a directory would overwrite each other's maker credential.
10+
#
711
# Copy this file to docker-compose.yml (or run it directly with
812
# `docker compose -f docker-compose.example.yml ...`) and adjust the paths.
913
#
10-
# Prereqs in this directory (see docs/install-docker.md):
11-
# - stitch.bot1.toml, stitch.bot2.toml copy from stitch.example.toml
12-
# - stitch.bot1.key, stitch.bot2.key one wallet per bot, chmod 600
14+
# Prereqs (see docs/install-docker.md):
15+
# - bots/bot1/stitch.toml, bots/bot2/stitch.toml copy from stitch.example.toml
16+
# - bots/bot1/stitch.key, bots/bot2/stitch.key one wallet per bot, chmod 600
17+
# - the container runs as uid 1000 and a bind mount keeps host ownership:
18+
# sudo chown -R 1000 ./bots # Linux only; Docker Desktop maps this for you
19+
#
20+
# Connect each bot to Textile before its first start. New bots quote Swap over
21+
# RFQ and rest no public ladder, so without a maker credential a container
22+
# starts and serves nothing. This writes bots/<name>/rfq-api.key, which the bot
23+
# picks up from beside its config on every later run:
24+
# docker compose -f docker-compose.example.yml run --rm bot1 \
25+
# stitch connect --config /home/stitch/run/stitch.toml
1326
#
1427
# Approve Permit2 once per bot before the first live start:
1528
# docker compose -f docker-compose.example.yml run --rm bot1 \
@@ -37,12 +50,12 @@ services:
3750
<<: *stitch
3851
container_name: stitch-bot1
3952
volumes:
40-
- ./stitch.bot1.toml:/home/stitch/run/stitch.toml:ro
41-
- ./stitch.bot1.key:/home/stitch/run/stitch.key:ro
53+
# The whole bot directory, writable: `stitch connect` rewrites stitch.toml
54+
# and drops rfq-api.key beside it, and both have to survive the container.
55+
- ./bots/bot1:/home/stitch/run
4256

4357
bot2:
4458
<<: *stitch
4559
container_name: stitch-bot2
4660
volumes:
47-
- ./stitch.bot2.toml:/home/stitch/run/stitch.toml:ro
48-
- ./stitch.bot2.key:/home/stitch/run/stitch.key:ro
61+
- ./bots/bot2:/home/stitch/run

0 commit comments

Comments
 (0)