Node.js Usage
This guide shows how to use the SatsTerminal Borrow SDK in a Node.js environment.Setup
Installation
Copy
npm install @satsterminal-sdk/borrow
Environment Variables
Copy
# .env
SATSTERMINAL_API_KEY=your-api-key
SATSTERMINAL_BASE_URL=https://api.satsterminal.com
BTC_ADDRESS=bc1q...
BTC_PRIVATE_KEY=... # For signing (use secure key management in production)
Basic Configuration
Copy
import { BorrowSDK, ChainType } from '@satsterminal-sdk/borrow';
import * as bitcoin from 'bitcoinjs-lib';
import * as ecc from 'tiny-secp256k1';
import { ECPairFactory } from 'ecpair';
const ECPair = ECPairFactory(ecc);
// Load environment variables
const config = {
apiKey: process.env.SATSTERMINAL_API_KEY!,
baseUrl: process.env.SATSTERMINAL_BASE_URL!,
btcAddress: process.env.BTC_ADDRESS!,
btcPrivateKey: process.env.BTC_PRIVATE_KEY!
};
// Create key pair for signing
const keyPair = ECPair.fromWIF(config.btcPrivateKey);
// Create SDK instance
const sdk = new BorrowSDK({
apiKey: config.apiKey,
baseUrl: config.baseUrl,
chain: ChainType.ARBITRUM,
wallet: {
address: config.btcAddress,
signMessage: async (message: string) => {
// Sign message with Bitcoin private key
const signature = keyPair.sign(
bitcoin.crypto.sha256(Buffer.from(message))
);
return signature.toString('base64');
}
},
// Use memory storage in Node.js
storage: {
_data: new Map<string, string>(),
getItem(key: string) { return this._data.get(key) || null; },
setItem(key: string, value: string) { this._data.set(key, value); },
removeItem(key: string) { this._data.delete(key); },
clear() { this._data.clear(); }
}
});
CLI Application
Copy
// cli.ts
import { BorrowSDK, ChainType } from '@satsterminal-sdk/borrow';
import { Command } from 'commander';
const program = new Command();
let sdk: BorrowSDK;
async function initSDK() {
sdk = new BorrowSDK({
apiKey: process.env.API_KEY!,
baseUrl: process.env.BASE_URL!,
chain: ChainType.ARBITRUM,
wallet: {
address: process.env.BTC_ADDRESS!,
signMessage: async (msg) => signWithWallet(msg)
}
});
await sdk.setup();
console.log('SDK initialized');
}
program
.name('borrow-cli')
.description('SatsTerminal Borrow CLI')
.version('1.0.0');
program
.command('status')
.description('Show current status')
.action(async () => {
await initSDK();
console.log('User Status:', sdk.userStatus);
});
program
.command('quote')
.description('Get loan quotes')
.option('-c, --collateral <btc>', 'Collateral amount in BTC', '0.1')
.option('-l, --loan <usd>', 'Loan amount in USD', '5000')
.option('--ltv <percent>', 'Loan-to-value ratio', '70')
.action(async (options) => {
await initSDK();
const quotes = await sdk.getQuotes({
collateralAmount: options.collateral,
loanAmount: options.loan,
ltv: parseInt(options.ltv)
});
console.log('\nAvailable Quotes:');
quotes.forEach((q, i) => {
console.log(`${i + 1}. ${q.protocol} - APY: ${q.borrowApy.variable}%`);
});
});
program
.command('borrow')
.description('Get a loan')
.option('-c, --collateral <btc>', 'Collateral amount in BTC', '0.1')
.option('-l, --loan <usd>', 'Loan amount in USD', '5000')
.action(async (options) => {
await initSDK();
console.log('Starting loan...');
await sdk.getLoan({
collateralBTC: parseFloat(options.collateral),
loanAmountUSD: parseFloat(options.loan),
onStatusUpdate: (status) => {
console.log(`[${status.step}] ${status.label}`);
},
onDepositReady: (info) => {
console.log('\n=== DEPOSIT REQUIRED ===');
console.log(`Amount: ${info.amountBTC} BTC`);
console.log(`Address: ${info.address}`);
console.log('========================\n');
},
onComplete: () => {
console.log('\nLoan complete!');
process.exit(0);
},
onError: (error) => {
console.error('\nError:', error);
process.exit(1);
}
});
});
program
.command('history')
.description('Show loan history')
.option('-s, --status <status>', 'Filter by status (active|pending|all)', 'all')
.action(async (options) => {
await initSDK();
const history = await sdk.getLoanHistory({
status: options.status as 'active' | 'pending' | 'all'
});
console.log(`\nLoan History (${history.pagination.totalTransactions} total):\n`);
history.transactions.forEach((tx) => {
console.log(`ID: ${tx.id}`);
console.log(` Type: ${tx.type}`);
console.log(` Amount: ${tx.amount} ${tx.currency}`);
console.log(` Status: ${tx.status}`);
console.log(` Date: ${new Date(tx.timestamp).toLocaleString()}`);
console.log('');
});
});
program
.command('positions')
.description('Show wallet positions')
.action(async () => {
await initSDK();
const positions = await sdk.getWalletPositions();
console.log('\nWallet Positions:\n');
let totalValue = 0;
positions.data.forEach((p) => {
const symbol = p.attributes.fungible_info?.symbol || 'Unknown';
const amount = p.attributes.quantity.float;
const value = p.attributes.value || 0;
totalValue += value;
console.log(`${symbol}: ${amount.toFixed(4)} ($${value.toFixed(2)})`);
});
console.log(`\nTotal Value: $${totalValue.toFixed(2)}`);
});
program
.command('repay <loanId> <amount>')
.description('Repay a loan')
.option('-w, --withdraw <btc>', 'Withdraw collateral')
.option('-a, --address <btc>', 'BTC address for withdrawal')
.action(async (loanId, amount, options) => {
await initSDK();
console.log(`Repaying ${amount} USD...`);
await sdk.repay(loanId, amount, {
collateralToWithdraw: options.withdraw,
userBtcWithdrawAddress: options.address,
trackWorkflow: true,
callbacks: {
onStatusUpdate: (status) => {
console.log(`[${status.step}] ${status.label}`);
},
onComplete: () => {
console.log('\nRepayment complete!');
process.exit(0);
},
onError: (error) => {
console.error('\nError:', error);
process.exit(1);
}
}
});
});
program.parse();
Automation Scripts
Automated Loan Monitoring
Copy
// monitor.ts
import { BorrowSDK, ChainType, UserTransaction } from '@satsterminal-sdk/borrow';
async function monitorLoans(sdk: BorrowSDK) {
console.log('Starting loan monitor...');
setInterval(async () => {
try {
const history = await sdk.getLoanHistory({ status: 'active' });
for (const loan of history.transactions) {
const collateral = await sdk.getLoanCollateralInfo(loan.id);
if (collateral) {
const healthRatio =
parseFloat(collateral.availableCollateral) /
parseFloat(collateral.totalDebt);
console.log(`Loan ${loan.id.slice(0, 8)}... Health: ${healthRatio.toFixed(2)}`);
// Alert if health is low
if (healthRatio < 1.2) {
console.warn(`⚠️ LOW HEALTH: Loan ${loan.id} health is ${healthRatio.toFixed(2)}`);
// Send notification (email, Slack, etc.)
await sendAlert({
type: 'low_health',
loanId: loan.id,
health: healthRatio
});
}
}
}
} catch (error) {
console.error('Monitor error:', error);
}
}, 60000); // Check every minute
}
Batch Operations
Copy
// batch.ts
import { BorrowSDK, ChainType } from '@satsterminal-sdk/borrow';
async function batchRepay(
sdk: BorrowSDK,
loans: Array<{ id: string; amount: string }>
) {
console.log(`Processing ${loans.length} repayments...`);
const results: Array<{ id: string; success: boolean; error?: string }> = [];
for (const loan of loans) {
try {
console.log(`Repaying loan ${loan.id}...`);
await sdk.repay(loan.id, loan.amount, {
trackWorkflow: true,
callbacks: {
onComplete: () => {
console.log(`✓ Loan ${loan.id} repaid`);
}
}
});
results.push({ id: loan.id, success: true });
} catch (error) {
console.error(`✗ Loan ${loan.id} failed:`, error);
results.push({
id: loan.id,
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
});
}
// Rate limiting
await new Promise(r => setTimeout(r, 2000));
}
// Summary
const successful = results.filter(r => r.success).length;
console.log(`\nCompleted: ${successful}/${loans.length} successful`);
return results;
}
Scheduled Tasks
Copy
// scheduler.ts
import cron from 'node-cron';
import { BorrowSDK, ChainType } from '@satsterminal-sdk/borrow';
async function setupScheduler(sdk: BorrowSDK) {
// Check positions every hour
cron.schedule('0 * * * *', async () => {
console.log('Running hourly position check...');
const positions = await sdk.getWalletPositions();
// Process positions
});
// Check loan health every 15 minutes
cron.schedule('*/15 * * * *', async () => {
console.log('Running health check...');
const history = await sdk.getLoanHistory({ status: 'active' });
for (const loan of history.transactions) {
const info = await sdk.getLoanCollateralInfo(loan.id);
// Check health and alert if needed
}
});
// Daily summary at 9 AM
cron.schedule('0 9 * * *', async () => {
console.log('Generating daily summary...');
const portfolio = await sdk.getWalletPortfolio();
const history = await sdk.getLoanHistory({ status: 'all' });
// Generate and send report
});
console.log('Scheduler started');
}
API Server
Copy
// server.ts
import express from 'express';
import { BorrowSDK, ChainType } from '@satsterminal-sdk/borrow';
const app = express();
app.use(express.json());
// SDK instances per user (in production, use proper session management)
const sdkInstances = new Map<string, BorrowSDK>();
function getSDK(userId: string): BorrowSDK {
if (!sdkInstances.has(userId)) {
throw new Error('SDK not initialized for user');
}
return sdkInstances.get(userId)!;
}
// Initialize SDK for user
app.post('/api/init', async (req, res) => {
try {
const { userId, btcAddress, signature } = req.body;
const sdk = new BorrowSDK({
apiKey: process.env.API_KEY!,
baseUrl: process.env.BASE_URL!,
chain: ChainType.ARBITRUM,
wallet: {
address: btcAddress,
signMessage: async () => signature // Pre-signed
}
});
await sdk.setup();
sdkInstances.set(userId, sdk);
res.json({ success: true, userStatus: sdk.userStatus });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get quotes
app.post('/api/quotes', async (req, res) => {
try {
const sdk = getSDK(req.body.userId);
const quotes = await sdk.getQuotes(req.body.params);
res.json({ quotes });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get loan history
app.get('/api/loans/:userId', async (req, res) => {
try {
const sdk = getSDK(req.params.userId);
const history = await sdk.getLoanHistory({
status: (req.query.status as any) || 'all'
});
res.json(history);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get positions
app.get('/api/positions/:userId', async (req, res) => {
try {
const sdk = getSDK(req.params.userId);
const positions = await sdk.getWalletPositions();
res.json(positions);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok', instances: sdkInstances.size });
});
app.listen(3000, () => {
console.log('API server running on port 3000');
});
Best Practices
Error Handling
Copy
import { BorrowSDKError, ApiError } from '@satsterminal-sdk/borrow';
async function safeOperation<T>(
operation: () => Promise<T>,
retries = 3
): Promise<T> {
let lastError: Error | undefined;
for (let i = 0; i < retries; i++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (error instanceof ApiError) {
if (error.statusCode === 429) {
// Rate limited - wait and retry
await new Promise(r => setTimeout(r, 5000 * (i + 1)));
continue;
}
if (error.statusCode && error.statusCode >= 500) {
// Server error - retry
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
continue;
}
}
// Non-retryable error
throw error;
}
}
throw lastError;
}
Logging
Copy
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
const sdk = new BorrowSDK({
// ...config
logger: {
debug: (msg) => logger.debug(msg),
info: (msg) => logger.info(msg),
warn: (msg) => logger.warn(msg),
error: (msg) => logger.error(msg)
}
});
Graceful Shutdown
Copy
let sdk: BorrowSDK;
process.on('SIGTERM', async () => {
console.log('Shutting down...');
if (sdk) {
sdk.clearSession();
}
process.exit(0);
});
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
if (sdk) {
sdk.clearSession();
}
process.exit(1);
});