# Fireblocks

> The Fireblocks API Co-Signer calls Immiscible before it signs each transfer, and signs only when an unused receipt covers it.

Source: https://immiscible.fly.dev/docs/guides/fireblocks

Fireblocks documents an outside approval step that runs before signing: the **API Co-Signer callback handler**. The Co-Signer, which you run and which holds your key share, calls a handler you name before it signs each transaction, and signs only if the handler answers `APPROVE`. Immiscible can be that handler. It holds no key share, no seed phrase and no funds, and never signs a transaction; the Co-Signer does.

Fireblocks' documentation: [creating a callback handler](https://developers.fireblocks.com/docs/create-api-co-signer-callback-handler), [secure communication](https://developers.fireblocks.com/reference/cosigner-callbackhandler-secure-communication-authentication), [the response object](https://developers.fireblocks.com/reference/response-object), [approving transactions](https://developers.fireblocks.com/reference/approve-transactions).

## The exchange, as documented

- The Co-Signer sends `POST {callback URL}/v2/tx_sign_request`. The body is a JWT signed RS256 with the Co-Signer's private key. Its payload carries `requestId`, `txId`, `operation`, `asset`, `amountStr`, `destAddress`, `externalTxId`, `note` and more.
- The handler answers with a JWT signed RS256 with its own RSA-2048 key: `{ action, requestId, rejectionReason }`, where `action` is `APPROVE`, `REJECT` or `RETRY`.

## How Immiscible answers

The same rule as the card rail: **no unused receipt, no signature.**

1. The agent asks `POST /v1/actions/authorize` with the crypto payment, as on [Crypto payments](https://immiscible.fly.dev/docs/guides/crypto-payments.md).
2. On allow, it creates the Fireblocks transaction with the Immiscible action id as `externalTxId` (or `immiscible:<action id>` in the `note`).
3. The Co-Signer calls Immiscible. Immiscible verifies the request's signature with the Co-Signer's public key, then approves only if that action was allowed, its agent is not frozen, its receipt is unused, unrevoked and unexpired, and the transfer matches it: the same destination address, an asset id that names the same asset (`USDC`, `USDC_POLYGON` and so on), and `amountStr` no larger than allowed. Approving uses the receipt up, so it approves one signature.
4. Anything else, including a request it cannot verify or any error, is a signed `REJECT` with a reason. Every answer is a `wallet_authorization` record in the ledger.

What is not checked: the network inside a Fireblocks asset id (asset ids are Fireblocks' own names, and the mapping to networks is not published in a form Immiscible reads), and the source vault account. Immiscible answers `TRANSFER` operations only and rejects any other.

## Set it up

1. In Immiscible, **Settings, Connections**, choose **Connect** on the Fireblocks API Co-Signer card and paste the Co-Signer's public key (PEM). Owners and admins can do this. Immiscible makes the handler's RSA-2048 key pair and shows the public key and the callback URL, `https://<your Immiscible>/wallets/<workspace>/fireblocks`.
2. In your Co-Signer's setup, give it that callback URL and the handler public key.
3. In Fireblocks, route the agent's transactions through the API user whose Co-Signer has the callback.

The API is `PUT /api/w/:wid/wallets/fireblocks` with `{ "cosignerPublicKey": "-----BEGIN PUBLIC KEY-----..." }`, and `DELETE` to disconnect.

## Not verified

Tested against a fake Co-Signer that replays the documented request shape and checks the signed answer (`test/crypto-wallets.test.js`), not against a real Co-Signer. The configuration-change callback (`/v2/config_change_sign_request`) is not handled.
