This module provides centralized, reusable utilities for the Fireblocks Raw Client project. It eliminates code duplication and provides consistent interfaces across the application.
shared/
├── index.ts # Main export file
├── types.ts # TypeScript type definitions
├── constants.ts # Application constants
├── logger.ts # Colored logging utilities
├── errors.ts # Custom error classes
├── transaction-poller.ts # Transaction polling logic
├── validators.ts # Input validation functions
├── config.ts # Configuration management
└── README.md # This file
import {
// Types
TransferParams,
Web3InitParams,
BtcTransactionParams,
// Constants
GAS,
POLLING,
BALANCE_THRESHOLDS,
// Logging
Logger,
// Errors
TransactionError,
ValidationError,
// Utilities
pollTransaction,
validateAmount,
createFireblocksClient
} from '../shared';import { Logger } from '../shared/logger';
import { GAS, POLLING } from '../shared/constants';
import { pollTransaction } from '../shared/transaction-poller';Comprehensive TypeScript interfaces for type-safe development.
Key Types:
// Transfer parameters
interface TransferParams {
fireblocksApiClient: FireblocksSDK;
ethereumProviderUrl: string;
sourceVaultAccountId: string | number;
recipientAddress: string;
assetIdentifier: string;
assetSymbol: string;
transferAmount?: number;
erc20ContractAddress?: string;
// ... more fields
}
// Web3 initialization
interface Web3InitParams { ... }
// Bitcoin transactions
interface BtcTransactionParams { ... }
// Type guards
function isTerminalStatus(status: TransactionStatus): boolean;
function isFailedStatus(status: TransactionStatus): boolean;Usage:
import { TransferParams, isTerminalStatus } from '../shared/types';
const params: TransferParams = {
fireblocksApiClient,
ethereumProviderUrl: "https://...",
sourceVaultAccountId: "0",
recipientAddress: "0x...",
assetIdentifier: "ETH_TEST3",
assetSymbol: "ETH"
};
if (isTerminalStatus(txStatus)) {
console.log("Transaction complete");
}Centralized constants to eliminate magic numbers.
Available Constants:
// Gas constants
GAS.SIMPLE_TRANSFER_LIMIT // 21000
GAS.ESTIMATION_BUFFER // 1.2 (20% buffer)
// Polling
POLLING.INTERVAL_MS // 1000
POLLING.TIMEOUT_MS // 600000
POLLING.MAX_RETRIES // 3
// Balance thresholds
BALANCE_THRESHOLDS.MIN_ETH_FOR_GAS // 0.0005
BALANCE_THRESHOLDS.MIN_ACTIVE_BALANCE // 0.0001
BALANCE_THRESHOLDS.LARGE_INTERNAL_TRANSFER_WARNING // 10
// Paths
PATHS.API_SECRET // "../FB_KEY/fireblocks_secret.key"
// Assets
ASSETS.ETH // "ETH"
ASSETS.ETH_TEST3 // "ETH_TEST3"
ASSETS.BTC // "BTC"
// Error messages
ERROR_MESSAGES.INSUFFICIENT_BALANCE
ERROR_MESSAGES.INVALID_ADDRESSUsage:
import { GAS, POLLING, BALANCE_THRESHOLDS } from '../shared/constants';
// Instead of magic numbers
gasLimit: GAS.SIMPLE_TRANSFER_LIMIT,
gasWithBuffer: estimatedGas * GAS.ESTIMATION_BUFFER,
await sleep(POLLING.INTERVAL_MS);
if (amount > BALANCE_THRESHOLDS.LARGE_INTERNAL_TRANSFER_WARNING) {
Logger.warn("Large transfer detected");
}Colored, structured logging with consistent formatting.
Logger Methods:
Logger.debug(message, data?) // White - debug info
Logger.info(message, data?) // Cyan - general info
Logger.warn(message, data?) // Yellow - warnings
Logger.error(message, error?) // Red - errors with stack traces
Logger.success(message, data?) // Green - success messages
// Specialized logging
Logger.transaction(txId, status, note?)
Logger.polling(txId, status, note?)
Logger.balance(address, balance, unit?)
Logger.vault(vaultId, assetId, message)
// Utilities
Logger.separator(char?, length?)
Logger.section(title)
Logger.setWindowTitle(title)
Logger.custom(message, color, data?)Usage:
import { Logger } from '../shared/logger';
Logger.info("Starting transfer");
Logger.success("Transfer completed", { txHash });
Logger.error("Transfer failed", error);
Logger.transaction(txId, "COMPLETED");
Logger.polling(txId, "PENDING_SIGNATURE");
Logger.balance("0x123...", "1.5", "ETH");
Logger.vault("0", "ETH_TEST3", "Balance retrieved");
Logger.section("Processing Transactions");
// === Processing Transactions ===Output Example:
[2025-11-19T10:30:00.000Z] [INFO] Starting transfer
[2025-11-19T10:30:05.000Z] [SUCCESS] Transfer completed { txHash: '0x...' }
Custom error classes with context for better debugging.
Error Classes:
// Base error
class FireblocksError extends Error {
context?: ErrorContext;
timestamp: Date;
getDetailedMessage(): string;
}
// Specific errors
class TransactionError extends FireblocksError
class InsufficientBalanceError extends FireblocksError
class ValidationError extends FireblocksError
class ConfigurationError extends FireblocksError
class ApiError extends FireblocksError
class VaultError extends FireblocksError
class NoAddressesError extends VaultError
class TransactionTimeoutError extends TransactionError
class CsvProcessingError extends FireblocksError
class GasEstimationError extends FireblocksError
class NetworkError extends FireblocksError
// Error handler utilities
class ErrorHandler {
static async withErrorHandling<T>(fn, errorMessage, context?): Promise<T>
static normalize(error, defaultMessage?): FireblocksError
static logError(error, additionalContext?): void
}Usage:
import {
ValidationError,
TransactionError,
InsufficientBalanceError,
ErrorHandler
} from '../shared/errors';
// Throw with context
throw new ValidationError('amount', amount, 'Must be positive', {
vault: vaultId,
operation: 'transfer'
});
throw new InsufficientBalanceError(required, available, {
vault: vaultId,
assetId
});
throw new TransactionError('Transaction failed', txId, status, {
operation: 'signTransaction'
});
// Wrap operations with error handling
const result = await ErrorHandler.withErrorHandling(
async () => await riskyOperation(),
'Operation failed',
{ vault: vaultId }
);
// Normalize and log errors
ErrorHandler.logError(error, { vault: vaultId, asset: assetId });Reusable transaction polling with automatic cancellation of failed transactions.
Functions:
// Poll until terminal status
async function pollTransaction(
fireblocksClient: FireblocksSDK,
transactionId: string,
config?: PollingConfig
): Promise<PollingResult>
// Poll and throw on failure
async function pollTransactionUntilSuccess(
fireblocksClient: FireblocksSDK,
transactionId: string,
config?: PollingConfig
): Promise<any>
// Poll multiple transactions
async function pollTransactions(
fireblocksClient: FireblocksSDK,
transactionIds: string[],
config?: PollingConfig
): Promise<PollingResult[]>
// Resume existing transaction
async function resumeTransaction(
fireblocksClient: FireblocksSDK,
transactionId: string,
config?: PollingConfig
): Promise<PollingResult>
// Check if terminal
async function checkTransactionStatus(
fireblocksClient: FireblocksSDK,
transactionId: string
): Promise<TransactionStatus | undefined>Usage:
import { pollTransactionUntilSuccess, pollTransaction } from '../shared/transaction-poller';
// Simple polling (throws on failure)
const txInfo = await pollTransactionUntilSuccess(fireblocksClient, txId);
// Advanced polling with callbacks
const result = await pollTransaction(fireblocksClient, txId, {
intervalMs: 2000,
timeoutMs: 300000,
onStatusChange: (status) => {
console.log(`Status changed to: ${status}`);
}
});
// Poll multiple transactions concurrently
const results = await pollTransactions(fireblocksClient, [txId1, txId2, txId3]);Replaces:
// Before: 30 lines of polling logic
while (...) {
txInfo = await fireblocksApiClient.getTransactionById(txId);
currentStatus = txInfo.status;
// ... 20+ more lines
}
// After: 1 line!
const txInfo = await pollTransactionUntilSuccess(fireblocksClient, txId);Input validation to ensure data integrity.
Validation Functions:
validateEthereumAddress(address, fieldName?)
validateAmount(amount, fieldName?, allowZero?)
validateVaultId(vaultId, fieldName?)
validateAssetId(assetId, fieldName?)
validateTransactionId(txId, fieldName?)
validateRpcUrl(url, fieldName?)
validateCliArguments(args, expectedCount, usageMessage)
validateFileExists(filePath, fieldName?)
validateRequired<T>(value, fieldName)
validateNonEmptyArray<T>(array, fieldName?)
validateRange(value, min, max, fieldName?)
parseAndValidateNumber(value, fieldName?)Usage:
import { validateAmount, validateEthereumAddress, validateVaultId } from '../shared/validators';
// Throws ValidationError if invalid
validateAmount(amount, 'transferAmount');
validateEthereumAddress(address, 'recipientAddress');
validateVaultId(vaultId, 'sourceVaultAccountId');
// CLI validation
const args = process.argv.slice(2);
validateCliArguments(args, 2, 'ts-node script.ts <vaultId> <assetId>');
// Required field validation
validateRequired(config.apiKey, 'apiKey');
// Range validation
validateRange(gasPrice, 0, MAX_GAS_PRICE, 'gasPrice');Centralized configuration management with validation.
Functions:
// Load configuration
function loadConfig(apiKeyOverride?, secretPathOverride?): FireblocksConfig
// Create Fireblocks client
function createFireblocksClient(config?): FireblocksSDK
// Validate configuration
function validateConfig(config: FireblocksConfig): void
// Get validated config
function getValidatedConfig(): FireblocksConfig
// Legacy exports (deprecated)
export const apiSecret: string
export const apiKey: stringUsage:
import { createFireblocksClient, loadConfig } from '../shared/config';
// Simple usage
const fireblocksClient = createFireblocksClient();
// Custom configuration
const config = loadConfig('myApiKey', '/custom/path/secret.key');
const fireblocksClient = createFireblocksClient(config);
// Environment variable
// Set FIREBLOCKS_API_KEY=your_key_here
const fireblocksClient = createFireblocksClient();Replaces:
// Before (repeated in every file)
const { apiSecret, apiKey } = require('./config');
const fireblocksApi = new FireblocksSDK(apiSecret, apiKey);
// After
import { createFireblocksClient } from '../shared/config';
const fireblocksApi = createFireblocksClient();import { TransferParams } from '../shared/types';
// Good - fully typed
const params: TransferParams = { ... };
// Bad - untyped
const params = { ... };import { GAS, POLLING } from '../shared/constants';
// Good
gasLimit: GAS.SIMPLE_TRANSFER_LIMIT
// Bad
gasLimit: 21000import { Logger } from '../shared/logger';
// Good
Logger.success("Transfer completed");
// Bad
console.log("\x1b[32mTransfer completed\x1b[0m");import { ErrorHandler, ValidationError } from '../shared/errors';
try {
await operation();
} catch (error) {
ErrorHandler.logError(error, { vault: vaultId });
throw error; // Re-throw after logging
}import { validateAmount, validateVaultId } from '../shared/validators';
function transfer(vaultId: string, amount: number) {
validateVaultId(vaultId);
validateAmount(amount);
// ... proceed with transfer
}import { createFireblocksClient } from '../shared/config';
import { Logger } from '../shared/logger';
import { ErrorHandler } from '../shared/errors';
import { validateCliArguments } from '../shared/validators';
import { transfer } from '../EVM/transfer.refactored';
async function main() {
const args = process.argv.slice(2);
validateCliArguments(args, 3, 'ts-node script.ts <vault> <amount> <address>');
const [vaultId, amount, address] = args;
Logger.section("Transfer Operation");
const fireblocksClient = createFireblocksClient();
await transfer({
fireblocksApiClient: fireblocksClient,
ethereumProviderUrl: "https://eth-sepolia.g.alchemy.com/v2/...",
sourceVaultAccountId: vaultId,
recipientAddress: address,
assetIdentifier: "ETH_TEST3",
assetSymbol: "ETH",
transferAmount: parseFloat(amount)
});
Logger.success("Operation completed");
}
main().catch((error) => {
ErrorHandler.logError(error);
process.exit(1);
});See MIGRATION_GUIDE.md for detailed migration instructions.
Quick Summary:
- Replace
require('./config')withimport { createFireblocksClient } from '../shared/config' - Replace console.log with
Loggermethods - Replace magic numbers with constants
- Replace error strings with custom error classes
- Replace polling loops with
pollTransaction() - Add input validation
When adding new utilities:
- Add types to
types.ts- Define interfaces first - Add constants to
constants.ts- No magic numbers - Use Logger - Consistent logging
- Use custom errors - Context-rich error handling
- Add validation - Validate inputs early
- Document with JSDoc - Self-documenting code
- Export from
index.ts- Make it accessible
The shared module adds minimal overhead:
- Type definitions: Zero runtime cost (compile-time only)
- Constants: Direct property access, negligible cost
- Logger: ~0.1ms per log (colored output)
- Error classes: <1ms to construct
- Transaction poller: Same performance as inline polling
- Validators: <0.5ms per validation
Net benefit: Reduced code size and improved maintainability far outweigh any minimal overhead.
Same as parent project (MIT).