A stateless SDK and REST API server for interacting with Fireblocks and the Stacks Network, enabling secure operations on Stacks using Fireblocks services.
The SDK Typedocs can be found here: https://fireblocks.github.io/stacks-fireblocks-sdk/
Stacks Fireblocks SDK lets you securely execute Stacks transactions using Fireblocks vaults and raw signing. It's designed to simplify integration with Fireblocks for secure Stacks transactions.
| Mode | Use Case | How |
|---|---|---|
| TypeScript SDK | Import into your Node.js application | import { StacksSDK } from "stacks-fireblocks-sdk" |
| REST API Server | Dockerized service for non-TS environments | docker-compose up or node dist/server.js |
- Fireblocks workspace with raw signing enabled.
- Fireblocks API key and secret key file.
- Node.js v18+
- Docker and Docker Compose (for REST API server mode).
- Secure Stacks Transactions: All transactions are Fireblocks-signed and submitted to Stacks.
- Fireblocks raw signing support
- Native STX transfers: Send STX with optional gross transactions (fee deduction from recipient)
- Fungible token transfers: Support for SIP-010 token transfers (sBTC, USDC, etc.)
- Nonce management: Optional nonce override on every transaction method; query confirmed on-chain nonce via
getAccountNonce() - Replace-by-fee: Replace a stuck pending STX transaction with a higher-fee one using the same nonce
- PoX-5 / BTC Bonding:
- STX staking and unstaking via signer-manager
- BTC bond lifecycle: create, renew, unlock matured bonds
- Early-exit announcement and spend (cosigner-assisted)
- Reward calculation, claiming (BTC + STX-only paths), and earned rewards query
- Signer key grant and verification
- Transaction monitoring: Real-time transaction status polling with error code mapping
- REST API mode: Easily integrate through HTTP requests.
- Vault pooling: Efficient per-vault instance management.
Install the package in your project:
npm install stacks-fireblocks-sdkImport and use in your code:
import { StacksSDK, FireblocksConfig } from "stacks-fireblocks-sdk";
const config: FireblocksConfig = {
apiKey: process.env.FIREBLOCKS_API_KEY!,
apiSecret: fs.readFileSync(process.env.FIREBLOCKS_SECRET_KEY_PATH!, "utf8"),
testnet: true,
};
const sdk = await StacksSDK.create("YOUR_VAULT_ID", config);Note: Importing the SDK does NOT start a server. The SDK is a pure library.
For non-TypeScript environments, run the SDK as a dockerized REST API service:
git clone https://github.com/fireblocks/stacks-fireblocks-sdk
cd stacks-fireblocks-sdk
cp .env.example .env
# Make sure your Fireblocks secret key is in ./secrets/fireblocks_secret.key
docker-compose up --build # Dev Mode
docker-compose -f docker-compose.yml up --build # Prod ModeAPI will run on port
3000by default. Change viaPORTin.env.
git clone https://github.com/fireblocks/stacks-fireblocks-sdk
cd stacks-fireblocks-sdk
npm install
cp .env.example .envEdit .env to include your API key, private key path, and Stacks network config.
npm run dev # Start REST API server with hot reload
npm run build # Build for productionEnvironment variables (via .env) control SDK behavior:
| Variable | Required | Default | Description |
|---|---|---|---|
| FIREBLOCKS_API_KEY | Yes | β | Your Fireblocks API key |
| FIREBLOCKS_SECRET_KEY_PATH | Yes | β | Path to your Fireblocks secret key file |
| FIREBLOCKS_BASE_PATH | No | BasePath.US from "@fireblocks/ts-sdk" | Base URL of the Fireblocks API |
| NETWORK | No | MAINNET | Stacks mainnet or testnet |
| PORT | No | 3000 | Port to run the REST API server |
| EARLY_EXIT_SIGNER_URL | No | Built-in testnet URL (none on mainnet) | Base URL of the external KMS cosigner service for bond early-exit spends |
FIREBLOCKS_BASE_PATH=https://api.fireblocks.io/v1
FIREBLOCKS_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
FIREBLOCKS_SECRET_KEY_PATH=./secrets/fireblocks_secret.key
NETWORK=TESTNET
PORT=3000Note: Setting NETWORK to anything other than TESTNET (or testnet) will set the network as mainnet.
π Never commit your
.envfile or secret key to source control.
- Place your Fireblocks private key at:
./secrets/fireblocks_secret.key
- Your
.envshould reference this file relative to the project root:
FIREBLOCKS_SECRET_KEY_PATH=./secrets/fireblocks_secret.key- Docker Compose mounts this file automatically:
volumes:
- ./secrets/fireblocks_secret.key:/app/secrets/fireblocks_secret.key:roimport { StacksSDK, FireblocksConfig } from "stacks-fireblocks-sdk";
import fs from "fs";
const fireblocksConfig: FireblocksConfig = {
apiKey: process.env.FIREBLOCKS_API_KEY!,
apiSecret: fs.readFileSync(process.env.FIREBLOCKS_SECRET_KEY_PATH!, "utf8"),
testnet: true, // or false for mainnet
};
const sdk = await StacksSDK.create("YOUR_VAULT_ID", fireblocksConfig);// Get Stacks address
const address = sdk.getAddress();
console.log("Stacks Address:", address);
// Get public key
const publicKey = sdk.getPublicKey();
console.log("Public Key:", publicKey);
// Get BTC rewards address (for stacking)
const btcAddress = sdk.getBtcRewardsAddress();
console.log("BTC Rewards Address:", btcAddress);// Get native STX balance
const balanceResponse = await sdk.getBalance();
if (balanceResponse.success) {
console.log("STX Balance:", balanceResponse.balance);
}
// Get fungible token balances
const ftBalances = await sdk.getFtBalances();
if (ftBalances.success) {
ftBalances.data?.forEach((token) => {
console.log(`${token.token}: ${token.balance}`);
});
}// Basic STX transfer
const transferResponse = await sdk.createNativeTransaction(
"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG", // recipient
10.5, // amount in STX
false, // grossTransaction (if true, fee is deducted from amount)
"Payment for services", // optional note
);
if (transferResponse.success) {
console.log("Transaction Hash:", transferResponse.txHash);
}
// Gross transaction (fee deducted from recipient)
const grossTransfer = await sdk.createNativeTransaction(
"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
10.5,
true, // fee will be deducted from the 10.5 STX
);
// With explicit nonce and fee override
const transfer = await sdk.createNativeTransaction(
"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
10.5,
false,
undefined, // note
7, // nonce override (integer)
0.01, // fee in STX (overrides auto-estimation)
);import { TokenType } from "stacks-fireblocks-sdk";
// Transfer sBTC (built-in token)
const ftTransfer = await sdk.createFTTransaction(
"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
0.1, // amount in token units
TokenType.sBTC,
);
if (ftTransfer.success) {
console.log("Transaction Hash:", ftTransfer.txHash);
}
// Transfer custom SIP-010 token
// Note: tokenAssetName is the name from define-fungible-token (may differ from contract name)
const customTransfer = await sdk.createFTTransaction(
"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
100, // amount in token units
TokenType.CUSTOM,
"SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9", // contract address
"my-token", // contract name
"my-token-asset", // asset name (from define-fungible-token)
);Finding the asset name: For custom tokens, the
tokenAssetNameis found in the contract's source code in thedefine-fungible-tokendeclaration. View the contract on a block explorer (e.g., explorer.hiro.so) and look for(define-fungible-token <asset-name>). This may differ from the contract name - for example, USDCx contract (usdcx) defines its token asusdcx-token.
const status = await sdk.checkStatus();
if (status.success) {
console.log("Balance Information:");
console.log(" Total STX:", status.data?.balance.stx_total);
console.log(" Locked STX:", status.data?.balance.stx_locked);
console.log(" Unlock Height:", status.data?.balance.burnchain_unlock_height);
console.log("\nDelegation Status:");
console.log(" Is Delegated:", status.data?.delegation.is_delegated);
console.log(" Delegated To:", status.data?.delegation.delegated_to);
console.log(" Amount:", status.data?.delegation.amount_delegated);
}// Returns confirmed nonce, pending tx count, and the next safe nonce to use.
// nextAvailable is gap-aware: if pending nonces are [5, 6, 9], it returns 7
// (the first gap) rather than 10, so your tx confirms as soon as possible.
const nonceResponse = await sdk.getAccountNonce();
if (nonceResponse.success) {
console.log("Confirmed nonce:", nonceResponse.confirmedNonce);
console.log("Pending txs: ", nonceResponse.pendingTxCount);
console.log("Use this nonce: ", nonceResponse.nextAvailable);
}All transaction methods (createNativeTransaction, createFTTransaction, delegateToPool, allowContractCaller, revokeDelegation, stackSolo, increaseStackedAmount, extendStackingPeriod) accept an optional nonce?: number parameter as their last argument. When omitted, the SDK automatically uses nextAvailable from getAccountNonce() β the same gap-aware value the nonce endpoint returns β so auto-nonce and manual nonce are always consistent.
If a transaction is stuck in the mempool due to a low fee, you can replace it by submitting a new transaction with the same nonce and a higher fee. Both native STX transfers and contract calls (PoX operations, etc.) are supported.
// Replace any pending transaction visible to the Hiro indexer.
// The original tx is looked up automatically β same nonce, same args, higher fee.
const replacement = await sdk.replaceTransaction(
0.01, // new fee in STX (must be β₯ RBF_MIN_FEE_MULTIPLIER Γ original fee)
"0xabc123...", // original tx ID
);
// For token_transfer only: optionally change recipient or amount
const replacement = await sdk.replaceTransaction(
0.01,
"0xabc123...",
"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG", // newRecipient
10.5, // newAmount in STX
);
// Replace a future-nonce STX transfer not visible to the Hiro indexer.
// nonceOverride bypasses the indexer lookup. Only STX transfers are supported
// on this path since contract call args cannot be inferred.
const replacement = await sdk.replaceTransaction(
0.01,
undefined, // originalTxId is unused on the nonceOverride path
"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG", // newRecipient (required)
10.5, // newAmount in STX (required)
7, // nonceOverride
);
if (replacement.success) {
console.log("Replacement tx hash:", replacement.txHash);
}The minimum fee bump is controlled by
RBF_MIN_FEE_MULTIPLIERinconstants.ts(default1.25). The fee check only applies on the lookup path where the original fee is known.
// Get transaction status with error code mapping
const txStatus = await sdk.getTxStatusById("0xabcd1234...");
if (txStatus.success) {
console.log("Status:", txStatus.data?.tx_status);
if (txStatus.data?.tx_status !== "success") {
console.log("Error:", txStatus.data?.tx_error);
console.log("Error Code:", txStatus.data?.tx_result?.repr);
}
}// Get transaction history (cached)
const history = await sdk.getTransactionHistory(true);
// Get fresh transaction history with pagination
const freshHistory = await sdk.getTransactionHistory(
false, // don't use cache
50, // limit
0, // offset
);
history.forEach((tx) => {
console.log(`${tx.transaction_hash}: ${tx.tx_type} - ${tx.tx_status}`);
});| Method | Route | Description |
|---|---|---|
| GET | /api/:vaultId/address |
Fetch the Stacks address associated with the given vault |
| GET | /api/:vaultId/publicKey |
Retrieve the public key for the vault account |
| GET | /api/:vaultId/btc-rewards-address |
Get the BTC rewards address associated with the given vault (for stacking) |
| GET | /api/:vaultId/nonce |
Get confirmed nonce, pending tx count, and next available nonce (gap-aware) |
| Method | Route | Description |
|---|---|---|
| GET | /api/:vaultId/balance |
Get the native STX balance |
| GET | /api/:vaultId/ft-balances |
Get all fungible token balances for the vault |
| Method | Route | Description |
|---|---|---|
| GET | /api/:vaultId/transactions |
List recent transactions for this vault |
| GET | /api/transactions/:txId |
Get detailed transaction status with error code mapping |
| POST | /api/:vaultId/transfer |
Transfer STX or Fungible Tokens to another address |
| POST | /api/:vaultId/replace-transaction |
Replace a stuck pending STX transaction with a higher-fee one (same nonce) |
/transfer accepts optional nonce (integer) and fee (STX, for STX transfers only) body fields to override the auto-estimated values.
/replace-transaction body fields:
| Field | Type | Required | Description |
|---|---|---|---|
originalTxId |
string | No | Transaction ID of the pending transaction to replace. Required unless nonceOverride is provided. |
newFee |
number | Yes | New fee in STX β must be at least RBF_MIN_FEE_MULTIPLIER Γ the original fee |
newRecipient |
string | No | New recipient address. Defaults to the original recipient |
newAmount |
number | No | New transfer amount in STX. Defaults to the original amount |
nonceOverride |
integer | No | Nonce to use directly, bypassing the Hiro indexer lookup. Required when the original tx is a future-nonce tx not visible in the explorer. When set, newRecipient and newAmount are also required. |
| Method | Route | Description |
|---|---|---|
| GET | /api/:vaultId/check-status |
Check account stacking status and delegation info |
| GET | /api/poxInfo |
Fetch current PoX-4 info from blockchain |
PoX-4 is the protocol currently live on Stacks mainnet. The PoX-5 endpoints below target the private-1 test network.
| Method | Route | Description |
|---|---|---|
| POST | /api/:vaultId/stacking/solo |
Solo stack STX |
| POST | /api/:vaultId/stacking/solo/increase |
Increase the STX amount of an existing solo position |
| POST | /api/:vaultId/stacking/solo/extend |
Extend the lock period of an existing solo position |
| POST | /api/:vaultId/stacking/pool/delegate |
Delegate STX to a stacking pool (mainnet only) |
| POST | /api/:vaultId/stacking/pool/allow-contract-caller |
Allow a pool contract to lock your STX (mainnet only) |
| POST | /api/:vaultId/revoke-delegation |
Revoke any active STX delegation (mainnet only) |
| Method | Route | Description |
|---|---|---|
| GET | /api/stacking/pox5/info |
Fetch current PoX-5 protocol info |
| GET | /api/:vaultId/stacking/pox5/requirements |
Get minimum STX amount and cycle requirements |
| GET | /api/:vaultId/stacking/pox5/staker-info |
Get current staker position and status |
| POST | /api/:vaultId/stacking/pox5/stake |
Stake STX via signer-manager (replaces PoX-4 solo stack) |
| POST | /api/:vaultId/stacking/pox5/update |
Update (increase) an existing PoX-5 stake |
| POST | /api/:vaultId/stacking/pox5/unstake |
Unstake STX from PoX-5 |
| POST | /api/:vaultId/stacking/pox5/grant-signer-key |
Grant signer key via signer-manager |
| GET | /api/:vaultId/stacking/pox5/verify-signer-grant |
Verify that the signer grant is active |
| POST | /api/:vaultId/stacking/pox5/revoke-signer-grant |
Revoke an existing signer key grant |
| Method | Route | Description |
|---|---|---|
| POST | /api/:vaultId/stacking/pox5/bond/create |
Create a BTC bond (locks BTC, registers on Stacks L2) |
| GET | /api/:vaultId/stacking/pox5/bond/position |
Get current bond position for the vault |
| GET | /api/:vaultId/stacking/pox5/bond/lock-address |
Get the BTC lock address for a bond |
| POST | /api/:vaultId/stacking/pox5/bond/fund-lock |
Fund the BTC lock address (alternative to in-band funding) |
| POST | /api/:vaultId/stacking/pox5/bond/unlock |
Unlock a matured bond and reclaim BTC |
| POST | /api/:vaultId/stacking/pox5/bond/renew |
Renew an existing bond for additional cycles |
| POST | /api/:vaultId/stacking/pox5/bond/announce-early-exit |
Announce intent to early-exit a bond (starts cosigner flow) |
| POST | /api/:vaultId/stacking/pox5/bond/early-exit |
Spend the early-exit UTXO after cosigner approval |
| GET | /api/:vaultId/stacking/pox5/bond/early-exit/public-key |
Get the cosigner public key for the early-exit path |
| Method | Route | Description |
|---|---|---|
| POST | /api/:vaultId/stacking/pox5/rewards/calculate |
Calculate expected rewards for a staking position |
| POST | /api/:vaultId/stacking/pox5/rewards/claim |
Claim BTC + STX rewards |
| POST | /api/:vaultId/stacking/pox5/rewards/claim-stx |
Claim STX-only rewards |
| GET | /api/:vaultId/stacking/pox5/rewards/earned |
Query earned rewards for the vault |
| Method | Route | Description |
|---|---|---|
| GET | /api/metrics |
Pool metrics (instance counts) |
| POST | /api/:vaultId/faucet |
Fund vault address via STX faucet (testnet only) |
- * IMPORTANT NOTE **: Transactions could sometimes pass at blockchain level but fail at smart contract level,
in this case a {success: true, txid: } 200 response will be returned to user, please double check
the success of the transaction by polling the txid status with the
/api/:vaultId/transactions/:txIdendpoint.
curl -X 'GET' \
'http://localhost:3000/api/123/address' \
-H 'accept: application/json'curl -X 'GET' \
'http://localhost:3000/api/123/balance' \
-H 'accept: application/json'curl -X 'POST' \
'http://localhost:3000/api/123/transfer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"recipientAddress": "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
"amount": 100.5,
"assetType": "STX",
"grossTransaction": false,
"note": "Payment for services"
}'curl -X 'POST' \
'http://localhost:3000/api/123/transfer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"recipientAddress": "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
"amount": 100,
"assetType": "Custom",
"tokenContractAddress": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9",
"tokenContractName": "my-token",
"tokenAssetName": "my-token-asset"
}'Note:
tokenAssetNameis the name from the contract'sdefine-fungible-tokendeclaration, which may differ fromtokenContractName.
curl http://localhost:3000/api/123/nonce
# β {
# "success": true,
# "confirmedNonce": 5,
# "pendingTxCount": 2,
# "nextAvailable": 7
# }curl -X POST http://localhost:3000/api/123/transfer \
-H 'Content-Type: application/json' \
-d '{
"recipientAddress": "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
"amount": 10.5,
"assetType": "STX",
"nonce": 7,
"fee": 0.01
}'curl -X POST http://localhost:3000/api/123/replace-transaction \
-H 'Content-Type: application/json' \
-d '{
"originalTxId": "0xabc123...",
"newFee": 0.01
}'For a future-nonce transaction not visible in the explorer, provide nonceOverride with the exact nonce, plus newRecipient and newAmount:
curl -X POST http://localhost:3000/api/123/replace-transaction \
-H 'Content-Type: application/json' \
-d '{
"originalTxId": "0xabc123...",
"newFee": 0.01,
"newRecipient": "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
"newAmount": 10.5,
"nonceOverride": 7
}'curl -X 'GET' \
'http://localhost:3000/api/123/check-status' \
-H 'accept: application/json'curl -X 'GET' \
'http://localhost:3000/api/transactions/0xabcd1234...' \
-H 'accept: application/json'npm run devnpm testnpm run buildSwagger UI API Documentation will be available at http://localhost:3000/api-docs after running the project.
- Never commit your
.envor secrets. - Use secrets management in production.
- Fireblocks raw signing provides secure transaction signing without exposing private keys.
- All transactions are signed within Fireblocks secure infrastructure.
- Network: Stacks Mainnet
- API:
https://api.hiro.so - PoX-4 Contract:
SP000000000000000000002Q6VF78.pox-4
- Network: Stacks Testnet
- API:
https://api.testnet.hiro.so - PoX-4 Contract:
ST000000000000000000002AMW42H.pox-4
PoX-5 operates on a private testnet. Set NETWORK=testnet β the SDK automatically routes PoX-5 calls to the private-1 node.
- API:
https://api.private-1.hiro.so - Chain ID:
256