
Mishmar
io.github.mishmar-io由 GitHub 帳號 mishmar-io 發布v0.3.0更新於 Nov 8, 2026
Risk management for trading bots and AI agents: limits, approvals and a stop on every order.
概覽
Mishmar 在助手與交易所之間加一層交易風控:助手提出的委託與取消單會先依限額檢查並共同簽章,之後才送往交易所。
- 可以做什麼
- 在交易帳戶上提出委託與取消單,每筆在到達交易所前先經引擎規則檢查
- 追蹤被擱置等待人工核准的決策並讀回結果
- 讀取持倉、損益、核准紀錄、交易帳戶及其交易所規則
- 以代理簽章器身分透過 stdio 執行,僅在請求與提案一致時共同簽章
- 適用情境
- 適合讓 AI 代理或機器人在 Hyperliquid 測試網或 Binance 模擬交易上操作,並具備硬性限額、大額委託人工核准,以及無法單獨交易的金鑰。目前不適合真實資金。
- 執行需求
- 透過 npx(Node 20 以上)或 uvx 以 stdio 在本機執行,或使用位於 PATH、MISHMAR_BIN 或 ~/.mishmar/bin 下的 mishmar 執行檔,缺少時會從發行版本下載。需要 ~/.mishmar/agent.json(或以 --config 指定的路徑)中的簽章器設定,包含引擎位址、金鑰 ID、代理密鑰與交易所金鑰份額。支援 Linux 與 macOS。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 Mishmar,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
mishmar
Mishmar is a risk management layer for trading bots
and AI agents: every order is checked against your limits before it reaches
the exchange, large orders wait for your approval, and no key can trade
alone. This is the client side: the API contract, the test vectors every SDK
must pass, the agent's signer, the mishmar CLI and MCP server, and the
SDKs themselves. The engine that enforces the rules is a separate, private
repo; this one is what runs next to your agent.
Status: testnet. Mishmar runs on Hyperliquid testnet and Binance demo
trading today; real money is not live yet. The contract, the Go packages, the
agent's signer and the mishmar CLI are here, and so are the Python and
TypeScript SDKs (mishmar on PyPI and npm). Binaries for Linux and macOS are
on the Releases page.
Layout
Run the checks
Two-party ECDSA (ecdsa2p, Hyperliquid API wallets) is Coinbase's cb-mpc, C++
behind cgo, through the mishmar-io fork of cb-mpc-go
checked out next to this repo at the ref in cbmpc.ref (go.mod replaces
the module with ../cb-mpc-go). Its scripts/build_cpp.sh builds the
library once (CMake, a static OpenSSL 3.6.1 from its scripts/openssl), and
make runs every Go command through its scripts/go_with_cpp.sh, which sets
the cgo flags. Linux and macOS; Windows through WSL or Docker.
A tag vX.Y.Z builds the binary on Linux and macOS, both architectures,
and attaches it to the release (.github/workflows/release.yml); the
Dockerfile builds the same binary for Linux, running the signer service
by default.
How an agent talks to the engine
- The engine issues the agent a key and shows its secret once (
msk_…). The engine keeps the public half. - The agent builds a proposal (symbol, side, size, price or market, a
deadline, a nonce), signs the canonical JSON of it with the key, and
POSTs it to
/v1/proposals. - The engine checks the signature, evaluates every rule in scope, records
the decision, and either forwards the order, parks it for a person, or
blocks it. The response is the decision; a parked one resolves later and
is read back from
/v1/agent/decisions/{id}, a request the agent signs with the same key (theagentAuthheaders). - When the trading account's key is split, the engine asks the agent's signer to
co-sign the venue request over the signer channel (
/v1/agent/signer/…); the signer checks that it is the order it proposed before it signs.
The exact bytes signed are specified in proposal and agentkey and pinned
in spec/vectors/.
The signer
When a trading account's venue key is split, the engine holds one share and the
agent's signer the other, and a venue request is signed only when both sign
it. The signer is the signer package as a library and mishmar signer run
as a process. It long-polls POST /v1/agent/signer/poll with the agent's
key and, for each request, refuses unless, in this order:
- the scheme is
frost-ed25519(Binance) orecdsa-2p(Hyperliquid); RSA and the proxy are not built yet; - the venue key is in its config, on the request's trading account and venue;
- the request has not expired and was not answered already in this session (a request handed out again after it was answered is a replay, not a second signature);
- the purpose is
order,cancelorverify; - an
ordercarries the agent's own proposal: signed by this signer's key, verifying with it, for this trading account, not yet expired; - the message, decoded, is that order and nothing more. On Binance the
message is the parameter string the venue verifies: it may carry only the
parameters the engine sends;
newClientOrderIdis the request's;symbolis the request's and starts with the proposal's;sideis the proposal's;typeisMARKETfor a market proposal andLIMITfor a limit one;quantityis a decimal with0 < quantity <= size(the engine floors to the lot step, never more); a limitpriceis within 1 % of the proposal's price and on its passive side (at most the price for a buy, at least for a sell), and a market order carries none;reduceOnlyis the proposal's. Averifymessage must decode to a read and not an order: no symbol, side, type, quantity, price or client order id. On Hyperliquid the message is the EIP-712 digest, which cannot be read, so the request's body carries the action and nonce it came from: the signer decodes them withvenues/hyperliquid, recomputes the digest for the key's network and compares, checks the nonce is fresh, and checks the action is the proposal's order (asset, side, size, a limit price on the tick rules within 1 % on the passive side, reduce-only, the cloid of the request's client order id) or the cancel of its target; - the hook, when the config names one, exits 0 (it gets the request's JSON on stdin and two seconds; its first line of output is the refusal reason).
Then it makes its half. FROST: it parses the engine's commitment, commits,
makes its share of the signature over the exact bytes it checked, and
answers {"kind":"share","commitment":…,"share":…}. Two-party ECDSA: it
spends one of its presigned slots (a batch of nonces it ran the protocol's
first three messages for with the engine's signer ahead of time, through
POST /v1/agent/signer/presign), computes its last message over the digest,
and answers {"kind":"share","share":…,"engineCommitmentId":<the slot>};
the engine's signer verifies that message and finishes the signature alone.
On both venues an order the signer can build itself goes one-shot instead:
the venue request and the signer's half travel inside the proposal
(venueMessage), so an allowed order needs no round trip back. On
Hyperliquid that is limit orders and cancels; a market order is priced by
the engine at its mark and signed over the channel. A refusal is
{"kind":"refusal","refusal":{"reason":…}} with a stable code first
(message_mismatch: side "SELL" is not the proposed BUY), which the engine
records on the decision's signing. Decimals are compared exactly; no float
touches them.
The config file
~/.mishmar/agent.json, or the file MISHMAR_AGENT_CONFIG names, written
with mode 0600:
share is the agent's share as key generation left it (or as mishmar keygen wrote it). The file holds the identity secret and the agent's shares
only; an engine share in it is refused on load.
The commands
Restarts and rate limits
The engine answers 503 unavailable while it restarts (it drains for up to
28 s, so an order already in flight finishes) and 429 rate_limited with a
Retry-After when a caller sends too much. Both mean the request did
nothing, and every client here sends it again on its own: the Go client
(signer.Client, which the signer's poll loop, mishmar propose and
mishmar mcp use), the Python Client and the TypeScript Client. They
wait the seconds in Retry-After when there is one, otherwise 0.25 s
doubling to 8 s with jitter, for up to 45 s after the first attempt, then
raise the last answer. A signed /agent request is signed anew for each
attempt, so a retry is never a replay; a proposal goes again byte for byte.
Nothing else is retried: 413 too_large would come back the same, and any
other answer means the request was taken. Turn retries off or tune them
with Client.Retries (Go), Client(..., retries=None) (Python) or
new Client(..., { retries: false }) (TypeScript).
From an SDK, or from a model
The Python and TypeScript SDKs can run the signer as a child process, so the
bot holds no secret at all: Signer starts mishmar signer run --local …
with the config and talks to it over a private Unix socket (localhost with
a token on Windows). An order is an intent; the child turns it into a
proposal signed with the agent's key, sends it one-shot where it can and
through the channel otherwise, and hands back the decision. Reads pass
through the child the same way. The binary is found on the PATH, in
MISHMAR_BIN or under ~/.mishmar/bin, or fetched from a release with
download_binary / downloadBinary, checked against the release's
checksums.
The child lives and dies with the bot. The SDK starts it with
--exit-on-stdin-close and a pipe on its stdin that it never writes to; the
pipe closes when the bot's process ends, however it ends (a crash or a
SIGKILL included), and the signer stops as it would on SIGTERM. So a signer
never stays online for an agent whose bot is gone.
A signer can also run on its own, kept up by systemd, launchd or Docker,
with mishmar signer run --local unix:/run/mishmar/signer.sock (or a
loopback port with a token in MISHMAR_LOCAL_TOKEN), and the bot attaches to
it instead of starting one: Signer.connect("unix:/run/mishmar/signer.sock")
in Python, await Signer.connect("unix:/run/mishmar/signer.sock", { token })
in TypeScript. connect starts nothing and stop leaves the signer running.
That is the way when several processes trade as one agent (one signer per
config: a second Signer() on the same config fails, naming the process
that holds it), or to keep the signer connected and its presigned slots warm
while the bot restarts.
mishmar mcp is the same signer behind an MCP server on stdio: a language
model gets tools to propose orders and cancels, follow a decision parked
for a person, and read positions, PnL, approvals, the trading account and
its venue rules. It never sees a key, and every order it proposes passes
the engine's rules before anything reaches a venue. Point an MCP client at
the command, with --config when the config is not at the default path:
In Claude Code, /plugin install mishmar --marketplace mishmar-io/mishmar
adds the server together with a skill for writing bots against the SDK
(skills/mishmar/SKILL.md; kit/ has the same guidance as an AGENTS.md
snippet and a Cursor rule).
The npm and PyPI packages put the same command on the path, so a client
needs no install step: npx -y mishmar mcp (Node 20 or later) or uvx mishmar mcp runs the binary found through MISHMAR_BIN, the PATH or
~/.mishmar/bin, and when there is none fetches the pinned release into
~/.mishmar/bin first, checked against the release's checksums. The MCP
registry lists the server as io.github.mishmar-io/mishmar (server.json).
The engine's signer
mishmar signer serve is the same program in the engine's role
(spec/signer-service.yaml, the signer/service package): the one process
on Mishmar's side that holds the engine's share of every two-party ECDSA
key, the Paillier keys and the presigned nonces. The engine holds no signing
secret; it relays key generation and presigning between the agent's signer
and this service, and asks it to finish one signature per order. The service
signs only an order or a cancel it decoded itself and that matches a
decision the engine signed with its own key (--engine-key, the public key
GET /v1/ledger/head shows), rebuilding the venue digest from the action.
Reach it from the engine alone: a Unix socket when the two share a machine,
else mTLS; plain HTTP is for localhost. The shares are sealed at rest
(AES-256-GCM, the key id bound as associated data) under the master key in
MISHMAR_SIGNER_KEY, which must not live next to the store. On Google Cloud it
can be wrapped by a KMS key instead: MISHMAR_KMS_KEY names the key and
MISHMAR_SIGNER_KEY_WRAPPED holds the wrapped form (mishmar signer wrap-key
makes it, from the raw key or anew). It is unwrapped at startup with the
workload's identity, each time in the KMS audit log, and if KMS cannot unwrap,
the service does not start: there is no fallback, and a raw key left beside it
is refused. --store is
where the sealed shares are kept, a file by default (mode 0600, replaced
atomically) or object storage: s3://bucket/prefix on AWS S3 or any
S3-compatible store (MISHMAR_S3_ENDPOINT, MISHMAR_S3_REGION,
MISHMAR_S3_ACCESS_KEY, MISHMAR_S3_SECRET_KEY, or the AWS SDK's own
variables and roles), gs://bucket/prefix on Google Cloud Storage as the
workload itself, with no keys: its identity is granted the prefix, and the
token comes from the metadata server as it does for KMS. Each key is one
object, created conditionally so two replicas
can never both create it; turn bucket versioning on for the history. Every share is opened
into memory at startup, so no order waits on the store, and a wrong master
key fails there rather than on the first order; replicas sharing a backend
reconcile on --sync, and a key not yet in memory is fetched from the
backend on first use, so a cell that takes on an account finds its shares
at once. Slots and sessions live in memory and go with a restart; never
snapshot, clone or live-migrate a running signer, since two copies of one
presigned nonce would sign two messages and give the key away. Back up
the store and the master key separately: losing either
means every agent generates its key again and every owner approves again,
nothing more, since the agent's half is useless alone and these keys hold
no funds.
Audit status
The two-party ECDSA comes from Coinbase's cb-mpc, which has had independent security reviews. Our change to it, which runs the first three of its four signing messages ahead of time so an order needs one round trip, has not been audited yet; docs/ecdsa-presign-review.md is the note for its reviewer, and SECURITY.md says how to report a flaw.
來源:README.md,提交 3755b4a
工具
0版本歷史
1- v0.3.0最新Nov 8, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分