Skip to content

Running Stitch on Fireblocks ​

This guide sets up a Stitch filler bot to sign with a Fireblocks vault account. The signing key stays in Fireblocks' MPC custody. The bot holds API credentials, requests each signature from Fireblocks, and never has access to the key.

If you want an HSM key without Fireblocks, Stitch also signs with Google Cloud KMS and AWS KMS.

It covers the API user and its IP allowlist, a dedicated vault account, the Signing Guard, the policy rules the bot needs, the Permit2 approval, and a revocation drill to run at onboarding. The numbered steps follow the console order.

If you have not set up a Stitch bot before, start with How to become a filler and return here for the signer.

Configuration rules ​

Stitch signs EIP-712 typed messages only: Permit2 orders, the session handshake and enrolment with the Textile venue, and a throwaway check when you press Verify. It never asks Fireblocks to sign arbitrary bytes, so it does not need the Raw Signing entitlement.

The bot host holds two credentials, the API key and the RSA private key. Whoever holds both can request any signature the workspace allows this API user, through Stitch or by calling the Fireblocks API directly. The setup below makes a stolen pair worth as little as possible:

  1. The key only works from the bot host. The API user's IP allowlist names the host's fixed address and nothing else. Step 2.
  2. It signs only Stitch's messages, for one vault. The Typed Message rule auto-approves for one dedicated vault account and this API user and nothing wider, and the Signing Guard rejects any typed message Stitch does not produce. Steps 3 to 5.
  3. It cannot move funds on its own. No Transfer rule lets this API user start a transfer, the Contract Call rule waits for a person, and no Raw Signing rule names this user. Steps 6 and 8. Stitch does not use Raw Signing, so if your workspace has the entitlement, leave this user out of its rules.
  4. An order cannot pull more than you approved. A capped Permit2 allowance limits what any order can take from the vault, whoever signed it. Step 8.

The first three are required. The fourth is our recommendation for custodied inventory, and step 8 also covers the unlimited alternative and what it costs. Step 9 is a short drill that proves you can stop the bot from the Fireblocks side.

Prerequisites ​

A Fireblocks workspace with an API Co-Signer. The Signer role holds an MPC key share, and Fireblocks only offers that role once a co-signer exists to hold it. Without a co-signer, the role dropdown does not list Signer.

If your workspace already signs programmatically, it has a co-signer. If not, arrange one with Fireblocks before continuing.

A server for the Signing Guard. It runs apart from the bot host, and your co-signer has to reach it over HTTPS. Step 4.

One-time addresses enabled on the workspace. The panel sends each Permit2 approval to the token contract as a one-time address. Fireblocks ships this feature disabled. Enabling it requires the workspace Owner and the Admin Quorum, so request it early.

A bot host with a fixed public IP address. Step 2 pins the API key to it, so a laptop that changes networks will not do.

1. Create the API user ​

Developer Center → API users → Add API user, role Signer, and pick the co-signer under Co-signer setup.

Two other roles look plausible and are not. Editor and Viewer can read vaults but cannot sign. Embedded Wallet Signer belongs to a different product. It signs for end-user wallets, not for the vault account Stitch trades from. On a Developer Sandbox there is no Signer role. Use Non-Signing Admin there, or the API user the sandbox already ships with under the same menu.

Fireblocks asks for a CSR. Generate the key pair on your own machine:

bash
openssl req -new -newkey rsa:4096 -nodes -subj "/O=stitch" \
  -keyout fireblocks_secret.key -out fireblocks.csr

This writes two files. Upload fireblocks.csr as the CSR file, the public half. Keep fireblocks_secret.key. Fireblocks never sees it, and it is the file you give Stitch. The -nodes flag leaves the key without a passphrase, which is required because the bot reads it unattended. Fireblocks' own example sets only the organisation, so -subj "/O=stitch" skips the prompts.

When the Owner and admin quorum approve the user on the mobile app, the API key (a UUID) appears in the API User (ID) column of the API users list. That plus the key file is everything Stitch needs.

2. Lock the API user to the bot host ​

With no IP allowlist, a Fireblocks API key works from any machine, so a copied key is usable anywhere. Fireblocks lets the workspace Owner restrict each API user to a list of single IP addresses: /32 only, no ranges (Fireblocks: Whitelist IPs for API keys, Manage API Access).

Have the Owner open Developer Center → API users, choose Allowlist IP address from this user's ⋮ menu, and add the bot host's fixed public egress address, or each of them if it has several (Fireblocks Help Center). The panel calls Fireblocks too, for Verify and for Permit2 approvals, so run it on the bot host or list its address as well.

When the bot moves to a new host or its address changes, have the Owner add the new address before the move and remove the old one after. Fireblocks refuses requests from an address that is not listed, so moving first stops signing, and with it quoting, until the list catches up. Never empty the list to get unstuck: an empty list accepts any address.

3. Prepare a dedicated vault account ​

Use a vault account that holds only the bot's trading inventory, and create a new one rather than reuse one that holds anything else. The Typed Message rule, the Permit2 allowance and anything a bad signature could reach all stop at this one account, so keeping treasury and other balances elsewhere keeps them out of the bot's reach.

Your liquidity stays in this account until a swap fills. The bot signs orders against it and does not move funds on its own.

The account needs at least one EVM asset wallet so there is an address to read. If it has none, add Ethereum. You do not need to hold ETH or trade on Ethereum. Adding the asset is how Fireblocks creates the wallet and its address.

On EVM chains, a Fireblocks vault account has one address across every network. The same address serves Celo, Base, Arbitrum, BSC and the rest. Stitch reads the address from whichever EVM asset the vault has.

Hold a small amount of native gas on each chain you quote. The Permit2 approvals in step 8 need it.

4. Run the Signing Guard ​

This step is required whenever the Typed Message rule auto-approves, which it has to for the bot to quote. Set it up before you write that rule.

Fireblocks policy decides who may sign for which vault, but it does not read what an EIP-712 message says. With an auto-approving rule and nothing checking contents, a stolen bot credential can sign any typed message for the vault: a Permit2 order that pays your tokens to someone else, or an EIP-2612 permit under a token's own domain, which grants an allowance with no Permit2 involved. A co-signer callback handler closes that gap, because the co-signer asks it about every request before signing.

This guide calls that handler the Signing Guard. Textile is building a reference Signing Guard for Stitch and will open-source it. Once it is ready, this page will link its public repository here. Until then, run your own handler with the checks under What the Signing Guard checks. Fireblocks publishes a plugin-based callback handler to start from.

Run it on its own server, not the bot host, so a compromised bot host cannot change what it approves. Then, in Developer Center → Co-signers, expand your co-signer and configure a callback handler for this API user (Fireblocks: Configure the Callback Handler).

From then on, the co-signer sends every signing request from this API user to the guard and signs only what it approves. A rejection, or no answer, fails the request (Fireblocks: Setup API Co-signer Callback Handler). If the guard is down, the bot stops quoting, which is the safe way to fail. The guard sits on every signature's path, so it has to answer quickly.

5. Write the Typed Message policy rule ​

Fireblocks rejects typed-message signing until a policy allows it. In Policies, open the Typed message policy and use Edit policy → Add rule so your API user (Initiator) can sign for the vault account from step 3 (Source). Then Publish policy; your approval group confirms on the mobile app. Keep the rule that narrow. A workspace-wide rule lets this key sign for vaults it has no business with.

Set it to auto-approve. Textile gives a filler 5.5 seconds to answer a quote request. A rule that routes to a human approver cannot meet that.

Once this rule is active, nothing but the Signing Guard stands between a typed message for this vault and a signature. That is what lets the bot quote, and why step 4 comes first.

6. Block transfers for this API user ​

A Signer API user can start transfers like any console user. If a Transfer rule allows it, a stolen bot credential can send funds straight out of the vault, with no typed message or Permit2 involved. Stitch never sends a transfer, so no Transfer rule may let this API user start one.

In Policies, open the Transfer policy and add a rule at the top with this API user as Initiator, any source, destination and asset, set to block. Publish it and have your approval group approve it.

To check it, read the Transfer policy the way Fireblocks applies it: from the top, first match wins (Fireblocks: Set Policies). Find the first rule whose Initiator takes in this API user, whether by name, through a group it belongs to, or as all users. That rule has to block. Check again whenever someone edits the Transfer policy, since a broad rule moved above yours would win.

The Signing Guard would also refuse a transfer. Do not leave it to the guard alone: policy is the layer Fireblocks enforces before anything reaches the co-signer.

7. Point Stitch at it ​

In the Stitch panel, open the bot's settings and choose Signer → Fireblocks. Enter the API key, then drop fireblocks_secret.key onto the key field. The panel reads the file in the browser and hides it once loaded, so the key is not left on screen during setup. Choose your workspace region if it is not the global one. An EU or US-East workspace answers only on its own host.

Load the vault accounts, select yours, and press Verify.

Verify signs one throwaway message. That single action confirms the credentials parse, the co-signer is online, the Signing Guard approves it, the policy rule exists and auto-approves, the vault resolves to an address, and how long a signature takes. It signs under its own domain, so the result authorises nothing, costs no gas, and touches no chain.

The address Verify reports is written into the bot's configuration. It is not read from an API response. It is the address a real signature recovered to. Verify also records which Fireblocks asset resolved the address, and the bot signs under that asset from then on.

8. Approve Permit2 ​

Permit2 pulls your tokens when a quote fills. Until you grant it an allowance, the bot quotes and every fill fails.

That allowance is also the most Permit2 orders can ever pull from the vault, whoever signed them. For custodied inventory, cap it: approve each token for about the liquidity you quote with it, not an unlimited amount. The unlimited setup is still there as a deliberate choice, described below.

An approval is a transaction, which a typed-message signer cannot sign. It goes out as a Fireblocks contract call instead. Fireblocks builds, prices, nonces, signs and broadcasts the transaction itself. The bot never handles a transaction hash, and no extra entitlement is needed.

Before the first approval:

  1. Add a Contract Call policy rule, scoped to the same vault account and API user as the Typed Message rule, with the destination set to one-time address.

    Do not set this rule to auto-approve. Route it to an approver. With Request approval, leave Transaction initiator can approve off. With it on and a threshold of 1, the bot approves its own calls. Approvals are rare and an operator is present when they happen. The panel waits up to three minutes for the call to mine, which is enough time to approve it in the console or the mobile app.

    When the request arrives, check it against Contract addresses. The destination is one of the token contracts for that chain, the spender in the calldata is Permit2, and the amount is the one you meant to grant.

  2. Hold native gas in the vault on each chain you quote. An ERC-20 approve costs about 46,000 gas.

Set fixed liquidity first. The corridor presets ship buy_total_liquidity_debt and sell_total_liquidity_collateral set to "max", which means "quote everything funded". A side in that mode only accepts an effectively unlimited allowance, and Stitch refuses to start against a capped one. Set a number on every enabled side in stitch.toml. A token's allowance then has to cover the total of those numbers across every side that spends it.

Send the approval yourself. The panel cannot send a capped one: its Approve button always grants an unlimited allowance, and stitch approve --exact does not run on a Fireblocks bot without raw signing, because it signs its own transaction. Send approve(Permit2, amount) to the token as a contract call from the vault, through the Fireblocks API, SDK or CLI (Fireblocks: Contract calls). The call's assetId is the chain's native asset (ETH on Ethereum, for example), and it decides which chain the call lands on, so check it. Send it as this API user from the bot host and it goes through the rule above and the Signing Guard, like the panel's approvals. Any other user needs a Contract Call rule of its own. On Ethereum, USDT refuses to change one non-zero allowance to another, so set it to 0 first.

In the add-bot wizard, press Skip on the approval step so it does not send unlimited approvals, and start the bot from its page once yours have landed.

Top it up. Fills spend the allowance down. The bot keeps quoting what is left, but it will not start again while a token's allowance is below its configured liquidity, and the Tools tab then shows that token as "Not approved". Do not press Approve there to fix it, since that sends an unlimited allowance. Send another capped one.

Unlimited allowance ​

The add-bot wizard sends one unlimited approval per input token on the way to a live bot. To approve later, open the bot's Tools tab and press Approve beside each token marked "Not approved". The bot can then quote the full vault balance, works with "max" liquidity and never needs a top-up.

The cost is that the allowance stops limiting anything. Any Permit2 order the Signing Guard lets through can pull the vault's whole balance of that token, so a gap in the guard's checks is bounded only by what the vault holds. Choose it on purpose, with the Signing Guard running and a vault that holds nothing but trading inventory.

The panel resolves the Fireblocks asset id for the chain by reading your workspace. The call is filed under the correct network without manual configuration. A chain your workspace does not have is reported as such.

The request stays open until the call is mined, normally a few seconds after approval. If it hangs after approval, confirm that the rule covers this vault account and API user.

9. Run a revocation drill ​

Do this once at onboarding, after the bot is quoting and before you move in full inventory. It proves you can stop the bot from the Fireblocks side without touching the bot host, and shows how long that takes and who has to act.

  1. Cut off the API user. Fireblocks documents no switch that pauses an API user. In a real incident the workspace Owner deletes it, which takes effect at once, needs no mobile approval and fails anything in flight. Deletion is permanent, so coming back means a new API user, a new CSR and updated policy rules (Fireblocks Help Center: Rename and delete API users). For the drill, have the Owner replace the bot host's address in the IP allowlist from step 2 with one you do not use. Do not empty the list.
  2. Revoke the Permit2 allowance. Send approve(Permit2, 0) to each token from the vault as a contract call. The console's Web3 Access → Token allowances page can send the same revoke for allowances it lists. Whoever sends it needs a Contract Call rule that covers them, and finding out whether yours does is part of the drill. Once the revoke lands, no outstanding order can fill.
  3. Confirm quoting stops. Verify in the bot's settings fails, the bot's log shows signing failures instead of quotes, and the Tools tab shows each token as "Not approved".

Then restore the address, approve again, and press Verify. Write down how long each step took and who had to act. Only the Owner can do step 1.

Latency ​

Fireblocks signing is asynchronous. Stitch creates a signing request and polls for the result. With an auto-approving policy, a signature normally completes in a few hundred milliseconds.

Textile gives a filler 5.5 seconds to answer a quote request. A signature that completes within a second leaves ample room for the bot to price and respond. Verify reports the measured round trip, including the Signing Guard's answer. Check it once during setup.

If a signature takes several seconds, the usual cause is a policy rule that routes to an approver instead of auto-approving, or a Signing Guard that is slow to answer. Fix it and run Verify again. Every signature is verified before use in either case. Latency only affects how many quotes you win.

What runs and what does not ​

A typed-message signer cannot sign transactions. That defines what a Fireblocks filler can do.

Runs. Answering RFQ quote requests, and resting orders on the public ladder. Both sign Permit2 orders only.

Does not run. Filling other users' resting limit orders, and the closer. Both build and sign their own transactions on every fill. Stitch rejects these legs when you save the configuration rather than failing at the first fill.

The panel's Withdraw does not run either, for the same reason. Move funds out of the vault from the Fireblocks console.

If the on-chain legs matter to your strategy, run them from a second bot with its own key. One Fireblocks bot for RFQ and the ladder, and a separate bot for the on-chain legs, is a common setup. Stitch also accepts raw_signing = true for workspaces that hold the Raw Signing entitlement. That widens the key from the EIP-712 structures Stitch produces to any transaction, and we do not recommend it.

Contract addresses ​

Every contract the bot touches on chain, per network. The token rows are the destinations you will see on Contract Call approval requests. Permit2 is the spender those approvals grant. The reactor is the spender named inside every Permit2 order, which the Signing Guard checks.

Permit2 is the canonical deployment at 0x000000000022D473030F116dDEE9F6B43aC78BA3 on every chain below.

ChainContractAddress
BNB Smart Chain (56)LimitOrderReactor0xe03261c0436DB575F92F09EdDF3591E2566B7D97
cNGN0xa8AEA66B361a8d53e8865c62D142167Af28Af058
USDT0x55d398326f99059fF775485246999027B3197955
Ethereum (1)LimitOrderReactor0x26A1aedaa26aFFc4fd9F910acFcA754A31bC43c6
WETH0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
USDT0xdAC17F958D2ee523a2206206994597C13D831ec7
Base (8453)LimitOrderReactor0xEb5A29F869FF084B3Fce18d3487a38A56feDC59E
cNGN0x46C85152bFe9f96829aA94755D9f915F9B10EF5F
TESOURO0x7ceE47e7B7CD04CF984e8Dd86C42595B5771A9B2
ZARU0xE205b4e7Ac03E3f7b2060C3EDf2171AA7126C0d5
USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Celo (42220)LimitOrderReactor0xa9AA0a64769cBed4d3B1Ceb4Df01CdE915C235b3
cNGN0xF6829D7393dAe24509eb1E52eE8e572e2E271a4f
USDC0xcebA9300f2b948710d2653dD7B07f33A8B32118C
USDT0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e
wARS0x0DC4F92879B7670e5f4e4e6e3c801D229129D90D
wBRL0xD76f5Faf6888e24D9F04Bf92a0c8B921FE4390e0
IDRX0x18Bc5bcC660cf2B9cE3cd51a404aFe1a0cBD3C22
Polygon (137)LimitOrderReactor0xF3ffCF21E621552CFcCC724B965e901cDF0D83fe
TESOURO0xd574b191B5a00262Ff19953d3CE25543AB7C8098
USDT0xc2132D05D31c914a87C6611C10748AEb04B58e8F

The address book is the source of truth and also lists every other live corridor token plus the BSC testnet and Base Sepolia deployments. If you quote a corridor that is not in this table, take its addresses from there.

What the Signing Guard checks ​

These are the checks a handler needs, whether you write your own or later run Textile's reference Signing Guard. Policy cannot express them.

It is an allowlist. Approve only the domains and primary types Stitch produces. Reject everything else. An allowlist is the right shape because the set of typed messages a contract might honour is open-ended. EIP-2612 permit under a token's own domain is the clearest example. It is a typed message, it grants a token allowance on its own, and the Permit2 allowance plays no part in it. Check the primary type as well as the domain: Permit2 also accepts PermitSingle and plain PermitTransferFrom, which let whatever spender the message names take the tokens.

These pass:

  • PermitWitnessTransferFrom with the LimitOrder witness, under the Permit2 domain. This is an order and needs the field checks below.
  • MakerSession and MakerEnroll under the Textile venue domains. These authenticate the bot to the venue and move no funds.
  • SignerCheck under the Stitch Signer Check domain. This is what the panel's Verify button signs. It authorises nothing. A handler that omits it makes Verify fail after every credential rotation.

That list covers a bot quoting from its own Fireblocks vault, which is what this guide sets up. A bot that trades from an OperatorVault also co-signs NavAttestation messages, and its orders name the OperatorVault as swapper and recipient, so its guard needs both.

For an order, check all of the following:

  • spender is the LimitOrderReactor for that chain, listed under Contract addresses.
  • witness.info.swapper is the vault address.
  • Every witness.outputs[].recipient is the vault address.
  • Both tokens are on an allowlist.
  • permitted.amount is within a per-order cap.
  • The output amount against the input amount is within an acceptable rate band.
  • The deadline is near-term.

The two recipient checks are the easiest to miss. BaseReactor._fill pays the output to whichever address the signed order names. An order that passes the reactor, token, cap and deadline checks but names another recipient would still fill, with the output paid there. Binding swapper and every output recipient to the vault guarantees that every fill pays your vault. The rate band does the same for price.

Contract calls go through the same handler. Approve an ERC-20 approve to the Permit2 address on a token you trade, plus the ERC-8004 registry call if you register the bot's identity. Reject everything else. If you cap the allowance, also reject an approve above your cap, which includes the unlimited one the panel's Approve sends.

Protect the bot host ​

fireblocks_secret.key is the credential that matters. The API key UUID is not usable without it. Stitch writes the key to disk owner-only, and both the bot and the panel read it. Anyone with access to the host has the key, and with it every signature the policy and the Signing Guard allow. The IP allowlist makes a copy of the key useless anywhere else, but not on the host itself. Restrict host access the way you would restrict access to the key itself.

To rotate, create a new API user with a new CSR, and give it the same IP allowlist, Signing Guard and policy rules as the old one before you delete the old one. The private key never leaves your machine in either direction.

Reference ​

The technical walkthrough, including manual stitch.toml configuration and troubleshooting, is in the Stitch repository: docs/signer-fireblocks.md.