Skip to main content

Complete Loan Flow

This example demonstrates the entire loan lifecycle from setup to repayment.

Overview

Complete Example

import {
  BorrowSDK,
  ChainType,
  Quote,
  UserTransaction,
  WorkflowStatus,
  DepositInfo,
  LoanCollateralInfo
} from '@satsterminal-sdk/borrow';

// Configuration
const CONFIG = {
  apiKey: process.env.SATSTERMINAL_API_KEY!,
  chain: ChainType.ARBITRUM,
  btcAddress: process.env.BTC_ADDRESS!
};

// Loan parameters
const LOAN_PARAMS = {
  collateralBTC: 0.1,
  loanAmountUSD: 5000,
  ltv: 70,
  term: 30
};

class LoanManager {
  private sdk: BorrowSDK;
  private activeLoanId: string | null = null;
  private workflowId: string | null = null;

  constructor(signMessage: (msg: string) => Promise<string>) {
    this.sdk = new BorrowSDK({
      ...CONFIG,
      wallet: {
        address: CONFIG.btcAddress,
        signMessage
      }
    });
  }

  // ========================================
  // PHASE 1: SETUP
  // ========================================

  async setup(): Promise<void> {
    console.log('='.repeat(50));
    console.log('PHASE 1: SETUP');
    console.log('='.repeat(50));

    const { baseWallet, userStatus, activeSession, transactions } = await this.sdk.setup();

    console.log('\nSetup Complete:');
    console.log(`  BTC Address: ${userStatus.btcAddress}`);
    console.log(`  Smart Account: ${baseWallet.address}`);
    console.log(`  Is Deployed: ${userStatus.isDeployed}`);
    console.log(`  Session Expires: ${new Date(activeSession.validUntil * 1000).toLocaleString()}`);
    console.log(`  Existing Transactions: ${transactions.length}`);
  }

  // ========================================
  // PHASE 2: GET QUOTES
  // ========================================

  async getQuotes(): Promise<Quote[]> {
    console.log('\n' + '='.repeat(50));
    console.log('PHASE 2: GET QUOTES');
    console.log('='.repeat(50));

    const quotes = await this.sdk.getQuotes({
      collateralAmount: LOAN_PARAMS.collateralBTC.toString(),
      loanAmount: LOAN_PARAMS.loanAmountUSD.toString(),
      ltv: LOAN_PARAMS.ltv,
      term: LOAN_PARAMS.term
    });

    console.log(`\nFound ${quotes.length} quotes:`);
    quotes.forEach((q, i) => {
      console.log(`\n  ${i + 1}. ${q.protocol.toUpperCase()}`);
      console.log(`     Chain: ${q.chain}`);
      console.log(`     Collateral: ${q.collateralAmount} BTC`);
      console.log(`     Loan Amount: $${q.loanAmount}`);
      console.log(`     Variable APY: ${q.borrowApy.variable}%`);
      console.log(`     Stable APY: ${q.borrowApy.stable}%`);
    });

    return quotes;
  }

  selectBestQuote(quotes: Quote[]): Quote {
    // Select quote with lowest variable APY
    const bestQuote = quotes.reduce((best, current) => {
      const bestApy = parseFloat(best.borrowApy.variable);
      const currentApy = parseFloat(current.borrowApy.variable);
      return currentApy < bestApy ? current : best;
    });

    console.log(`\nSelected: ${bestQuote.protocol} with ${bestQuote.borrowApy.variable}% APY`);
    return bestQuote;
  }

  // ========================================
  // PHASE 3: EXECUTE BORROW
  // ========================================

  async executeBorrow(): Promise<void> {
    console.log('\n' + '='.repeat(50));
    console.log('PHASE 3: EXECUTE BORROW');
    console.log('='.repeat(50));

    return new Promise(async (resolve, reject) => {
      try {
        const result = await this.sdk.getLoan({
          collateralBTC: LOAN_PARAMS.collateralBTC,
          loanAmountUSD: LOAN_PARAMS.loanAmountUSD,
          ltv: LOAN_PARAMS.ltv,
          term: LOAN_PARAMS.term,

          onStatusUpdate: (status: WorkflowStatus) => {
            this.handleStatusUpdate(status);
          },

          onDepositReady: (info: DepositInfo) => {
            this.handleDepositReady(info);
          },

          onComplete: (result: any) => {
            this.handleBorrowComplete(result);
            resolve();
          },

          onError: (error: string) => {
            console.error('\n❌ Borrow Error:', error);
            reject(new Error(error));
          }
        });

        this.workflowId = result.workflowId;
        console.log(`\nWorkflow Started: ${this.workflowId}`);

      } catch (error) {
        reject(error);
      }
    });
  }

  private handleStatusUpdate(status: WorkflowStatus): void {
    const icon = status.isComplete ? '✅' :
                 status.isFailed ? '❌' : '⏳';

    console.log(`\n${icon} [Step ${status.step}] ${status.label}`);
    console.log(`   ${status.description}`);

    if (status.stage === 'COLLATERAL_DEPOSITED') {
      console.log('   💰 Collateral received and processing...');
    }
  }

  private handleDepositReady(info: DepositInfo): void {
    console.log('\n' + '─'.repeat(50));
    console.log('📥 DEPOSIT REQUIRED');
    console.log('─'.repeat(50));
    console.log(`   Amount: ${info.amountBTC} BTC (${info.amount} sats)`);
    console.log(`   Address: ${info.address}`);
    console.log('─'.repeat(50));
    console.log('\n⏳ Waiting for deposit confirmation...');
  }

  private handleBorrowComplete(result: any): void {
    console.log('\n' + '─'.repeat(50));
    console.log('✅ BORROW COMPLETE');
    console.log('─'.repeat(50));
    console.log(`   Workflow ID: ${this.workflowId}`);
    console.log('   Funds are now in your smart account.');
  }

  // ========================================
  // PHASE 4: MONITOR LOAN
  // ========================================

  async monitorLoan(): Promise<void> {
    console.log('\n' + '='.repeat(50));
    console.log('PHASE 4: MONITOR LOAN');
    console.log('='.repeat(50));

    // Get active loans
    const history = await this.sdk.getLoanHistory({ status: 'active' });

    if (history.transactions.length === 0) {
      console.log('\nNo active loans found.');
      return;
    }

    const loan = history.transactions[0];
    this.activeLoanId = loan.id;

    console.log(`\nActive Loan: ${loan.id}`);
    console.log(`  Type: ${loan.type}`);
    console.log(`  Amount: ${loan.amount} ${loan.currency}`);
    console.log(`  Status: ${loan.status}`);
    console.log(`  Created: ${new Date(loan.timestamp).toLocaleString()}`);

    // Get collateral info
    const collateralInfo = await this.sdk.getLoanCollateralInfo(loan.id);

    if (collateralInfo) {
      console.log('\nCollateral Information:');
      console.log(`  Total Collateral: ${collateralInfo.totalCollateral} BTC`);
      console.log(`  Available Collateral: ${collateralInfo.availableCollateral} BTC`);
      console.log(`  Max Withdrawable: ${collateralInfo.maxWithdrawable} BTC`);
      console.log(`  Total Debt: $${collateralInfo.totalDebt}`);
      console.log(`  Remaining Debt: $${collateralInfo.remainingDebt}`);

      // Calculate health
      const health = parseFloat(collateralInfo.availableCollateral) /
                     parseFloat(collateralInfo.totalDebt);
      console.log(`  Health Ratio: ${health.toFixed(4)}`);
    }

    // Check wallet positions
    const positions = await this.sdk.getWalletPositions();

    console.log('\nWallet Positions:');
    positions.data.forEach(p => {
      const symbol = p.attributes.fungible_info?.symbol || 'Unknown';
      const amount = p.attributes.quantity.float;
      const value = p.attributes.value || 0;
      console.log(`  ${symbol}: ${amount.toFixed(4)} ($${value.toFixed(2)})`);
    });
  }

  // ========================================
  // PHASE 5: REPAY LOAN
  // ========================================

  async repayLoan(btcWithdrawAddress: string): Promise<void> {
    console.log('\n' + '='.repeat(50));
    console.log('PHASE 5: REPAY LOAN');
    console.log('='.repeat(50));

    if (!this.activeLoanId) {
      // Get active loan
      const history = await this.sdk.getLoanHistory({ status: 'active' });
      if (history.transactions.length === 0) {
        console.log('\nNo active loans to repay.');
        return;
      }
      this.activeLoanId = history.transactions[0].id;
    }

    // Get collateral info for repayment
    const collateralInfo = await this.sdk.getLoanCollateralInfo(this.activeLoanId);

    if (!collateralInfo) {
      console.error('\nCould not get collateral info.');
      return;
    }

    console.log(`\nRepaying loan ${this.activeLoanId}`);
    console.log(`  Debt: $${collateralInfo.remainingDebt}`);
    console.log(`  Collateral to withdraw: ${collateralInfo.maxWithdrawable} BTC`);
    console.log(`  Withdraw to: ${btcWithdrawAddress}`);

    return new Promise(async (resolve, reject) => {
      try {
        const txId = await this.sdk.repay(
          this.activeLoanId!,
          collateralInfo.remainingDebt, // Full repayment
          {
            collateralToWithdraw: collateralInfo.maxWithdrawable,
            userBtcWithdrawAddress: btcWithdrawAddress,
            trackWorkflow: true,
            callbacks: {
              onStatusUpdate: (status: WorkflowStatus) => {
                console.log(`\n⏳ [${status.step}] ${status.label}`);

                if (status.stage.includes('BRIDGE')) {
                  console.log('   🌉 Bridge in progress...');
                }
              },

              onComplete: () => {
                console.log('\n' + '─'.repeat(50));
                console.log('✅ REPAYMENT COMPLETE');
                console.log('─'.repeat(50));
                console.log('   Loan fully repaid.');
                console.log(`   Collateral withdrawn to ${btcWithdrawAddress}`);
                resolve();
              },

              onError: (error: string) => {
                console.error('\n❌ Repayment Error:', error);
                reject(new Error(error));
              }
            }
          }
        );

        console.log(`\nRepayment Transaction: ${txId}`);

      } catch (error) {
        reject(error);
      }
    });
  }

  // ========================================
  // CLEANUP
  // ========================================

  cleanup(): void {
    console.log('\n' + '='.repeat(50));
    console.log('CLEANUP');
    console.log('='.repeat(50));

    this.sdk.clearSession();
    console.log('\nSession cleared.');
  }
}

// ========================================
// MAIN EXECUTION
// ========================================

async function main() {
  console.log('\n');
  console.log('╔══════════════════════════════════════════════════╗');
  console.log('║     SATSTERMINAL BORROW - COMPLETE LOAN FLOW     ║');
  console.log('╚══════════════════════════════════════════════════╝');
  console.log('\n');

  // Initialize with your signing function
  const signMessage = async (message: string): Promise<string> => {
    // Replace with your actual wallet signing implementation
    return await yourWallet.signMessage(message);
  };

  const manager = new LoanManager(signMessage);

  try {
    // Phase 1: Setup
    await manager.setup();

    // Phase 2: Get Quotes
    const quotes = await manager.getQuotes();
    const selectedQuote = manager.selectBestQuote(quotes);

    // Phase 3: Execute Borrow
    await manager.executeBorrow();

    // Phase 4: Monitor (wait a bit for loan to be active)
    console.log('\n⏳ Waiting for loan to become active...');
    await new Promise(r => setTimeout(r, 5000));
    await manager.monitorLoan();

    // Phase 5: Repay (optional - uncomment to repay)
    // const btcAddress = 'bc1q...';
    // await manager.repayLoan(btcAddress);

    console.log('\n');
    console.log('╔══════════════════════════════════════════════════╗');
    console.log('║              LOAN FLOW COMPLETE                   ║');
    console.log('╚══════════════════════════════════════════════════╝');

  } catch (error) {
    console.error('\n❌ Error:', error);
  } finally {
    manager.cleanup();
  }
}

main().catch(console.error);

Flow Diagram

Key Takeaways

  1. Always call setup() first - This initializes the smart account and session
  2. Handle all callbacks - Status updates, deposit ready, complete, and error
  3. Monitor loan health - Check collateral info periodically
  4. Provide BTC address for repayment - Required for collateral withdrawal
  5. Clear session when done - Cleanup resources properly