Skip to main content

Node.js Usage

This guide shows how to use the SatsTerminal Borrow SDK in a Node.js environment.

Setup

Installation

npm install @satsterminal-sdk/borrow

Environment Variables

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

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

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

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

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

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

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

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

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

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