Skip to content

Latest commit

Β 

History

51 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Stacks Fireblocks SDK

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/


⚑ Project Overview

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.

Two Usage Modes

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

Prerequisites

  • 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).

πŸš€ Features

  • 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.

πŸ“¦ Installation

Option 1: TypeScript SDK (for Node.js applications)

Install the package in your project:

npm install stacks-fireblocks-sdk

Import 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.

Option 2: REST API Server (Docker)

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 Mode

API will run on port 3000 by default. Change via PORT in .env.

Option 3: Local Development

git clone https://github.com/fireblocks/stacks-fireblocks-sdk
cd stacks-fireblocks-sdk
npm install
cp .env.example .env

Edit .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 production

βš™οΈ Configuration

Environment 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

Sample .env:

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=3000

Note: Setting NETWORK to anything other than TESTNET (or testnet) will set the network as mainnet.

πŸ”’ Never commit your .env file or secret key to source control.


πŸ”‘ Secret Key Setup (Docker)

  1. Place your Fireblocks private key at:
./secrets/fireblocks_secret.key
  1. Your .env should reference this file relative to the project root:
FIREBLOCKS_SECRET_KEY_PATH=./secrets/fireblocks_secret.key
  1. Docker Compose mounts this file automatically:
volumes:
  - ./secrets/fireblocks_secret.key:/app/secrets/fireblocks_secret.key:ro

πŸ“– SDK Usage Examples

Initialize the SDK

import { 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 Account Information

// 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);

Check Balance

// 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}`);
  });
}

Transfer STX

// 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)
);

Transfer Fungible Tokens

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 tokenAssetName is found in the contract's source code in the define-fungible-token declaration. 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 as usdcx-token.

Check Account Status

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);
}

Nonce Management

// 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.

Replace a Stuck Transaction

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_MULTIPLIER in constants.ts (default 1.25). The fee check only applies on the lookup path where the original fee is known.

Transaction Status Monitoring

// 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);
  }
}

Transaction History

// 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}`);
});

πŸ› οΈ API Reference

Account Information Endpoints

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)

Balance Endpoints

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

Transaction Endpoints

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.

Account Status & Protocol Info

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 Stacking Endpoints

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)

PoX-5 Staking Endpoints

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

PoX-5 BTC Bond Endpoints

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

PoX-5 Rewards Endpoints

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

Utility Endpoints

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/:txId endpoint.

πŸ“Š REST API Examples (cURL)

Get Account Address

curl -X 'GET' \
  'http://localhost:3000/api/123/address' \
  -H 'accept: application/json'

Check Balance

curl -X 'GET' \
  'http://localhost:3000/api/123/balance' \
  -H 'accept: application/json'

Transfer STX

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"
}'

Transfer Custom SIP-010 Token

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: tokenAssetName is the name from the contract's define-fungible-token declaration, which may differ from tokenContractName.

Get Account Nonce

curl http://localhost:3000/api/123/nonce
# β†’ {
#     "success": true,
#     "confirmedNonce": 5,
#     "pendingTxCount": 2,
#     "nextAvailable": 7
#   }

Transfer STX with Nonce Override

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
  }'

Replace a Stuck Transaction

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
  }'

Check Stacking Status

curl -X 'GET' \
  'http://localhost:3000/api/123/check-status' \
  -H 'accept: application/json'

Get Transaction Status

curl -X 'GET' \
  'http://localhost:3000/api/transactions/0xabcd1234...' \
  -H 'accept: application/json'

πŸ“„ Development

Run locally with hot reload

npm run dev

Run tests

npm test

Build for production

npm run build

πŸ”— API Documentation

Swagger UI API Documentation will be available at http://localhost:3000/api-docs after running the project.


πŸšͺ Security

  • Never commit your .env or 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 Information

Mainnet

  • Network: Stacks Mainnet
  • API: https://api.hiro.so
  • PoX-4 Contract: SP000000000000000000002Q6VF78.pox-4

Testnet (PoX-4)

  • Network: Stacks Testnet
  • API: https://api.testnet.hiro.so
  • PoX-4 Contract: ST000000000000000000002AMW42H.pox-4

PoX-5 / BTC Bonding Testnet

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

πŸ“š Additional Resources

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages