Skip to main content

Troubleshooting

Common issues and their solutions when using the SatsTerminal Borrow SDK.

Setup Issues

”apiKey is required”

Problem: SDK initialization fails with configuration error. Solution:
// Ensure apiKey is provided and not empty
const sdk = new BorrowSDK({
  apiKey: process.env.API_KEY!, // Check this isn't undefined
  // ...
});
Check:
  • Environment variable is set
  • Variable name is correct
  • Value is not empty string

”wallet.signMessage must be a function”

Problem: Wallet provider configuration error. Solution:
const sdk = new BorrowSDK({
  wallet: {
    address: btcAddress,
    signMessage: async (message: string) => {
      // This must be an async function that returns a string
      return await yourWallet.signMessage(message);
    }
  }
});

“Setup failed” / Smart Account Error

Problem: setup() fails with SmartAccountError. Causes:
  1. Invalid signature from wallet
  2. Network connectivity issues
  3. API key issues
Solution:
try {
  await sdk.setup();
} catch (error) {
  if (error instanceof SmartAccountError) {
    console.log('Original cause:', error.cause);

    // Try clearing session and retry
    sdk.clearSession();
    await sdk.setup();
  }
}

Wallet Issues

”User rejected signature”

Problem: User cancelled the signature request. Solution:
try {
  await sdk.setup();
} catch (error) {
  if (error.message.includes('rejected') ||
      error.message.includes('cancelled')) {
    showMessage('Please sign the message to continue.');
  }
}

“Invalid signature format”

Problem: Wallet returns signature in unexpected format. Solution:
wallet: {
  signMessage: async (message: string) => {
    const signature = await yourWallet.signMessage(message);

    // Ensure signature is base64 string
    if (typeof signature !== 'string') {
      throw new Error('Invalid signature format');
    }

    // Some wallets return hex, convert if needed
    if (signature.startsWith('0x')) {
      return Buffer.from(signature.slice(2), 'hex').toString('base64');
    }

    return signature;
  }
}

Quote Issues

”No quotes available”

Problem: getQuotes() returns empty array or throws QuoteError. Causes:
  1. Collateral amount too small
  2. Loan amount too large for collateral
  3. LTV too high
  4. No liquidity available
Solution:
// Check your parameters
const quotes = await sdk.getQuotes({
  collateralAmount: '0.1', // Minimum ~0.01 BTC
  loanAmount: '5000',      // Must be <= collateral value * LTV
  ltv: 70                  // Max typically 80%
});

if (quotes.length === 0) {
  // Try with different parameters
  const lowerLtvQuotes = await sdk.getQuotes({
    ...params,
    ltv: 50 // Lower LTV
  });
}

Workflow Issues

”Deposit not received” / Stuck on AWAITING_DEPOSIT

Problem: Workflow stuck waiting for deposit. Causes:
  1. BTC not sent to deposit address
  2. Transaction not confirmed
  3. Wrong amount sent
Solution:
  1. Verify transaction was sent to correct address
  2. Check transaction confirmations (typically need 1-3)
  3. Verify amount matches exactly
onDepositReady: (info) => {
  console.log('Deposit to:', info.address);
  console.log('Amount:', info.amountBTC, 'BTC');
  console.log('Satoshis:', info.amount);

  // Save this info for verification
  saveDepositInfo(info);
}

”Workflow timeout”

Problem: Workflow times out without completing. Solution:
// Resume tracking if interrupted
const pendingWorkflowId = localStorage.getItem('workflowId');

if (pendingWorkflowId) {
  // Check status
  const status = await sdk.getStatus(pendingWorkflowId);

  if (!status.isComplete && !status.isFailed) {
    // Resume tracking
    await sdk.resumeLoan(pendingWorkflowId, callbacks);
  }
}

“Bridge failed”

Problem: Collateral withdrawal bridge operation fails. Causes:
  1. Network congestion
  2. Bridge liquidity issues
  3. Invalid BTC address
Solution:
// Verify BTC address before withdrawal
function validateBtcAddress(address: string): boolean {
  // Bech32
  if (address.startsWith('bc1')) {
    return /^bc1[a-zA-HJ-NP-Z0-9]{39,59}$/.test(address);
  }
  // Legacy
  return /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address);
}

// Check status after bridge failure
const status = await sdk.getRepayStatus(transactionId);
console.log('Bridge state:', status.transactionState);

Session Issues

”Session expired”

Problem: Operations fail after session expires. Solution:
// Check session before operations
if (!sdk.userStatus.hasActiveSession ||
    Date.now() / 1000 > (sdk.userStatus.sessionExpiry || 0)) {
  await sdk.setup(); // Refresh session
}

// Or handle in error handler
try {
  await sdk.getLoanHistory();
} catch (error) {
  if (error.message.includes('session')) {
    await sdk.setup();
    await sdk.getLoanHistory(); // Retry
  }
}

“Not initialized”

Problem: Operations fail with “call setup() first”. Solution:
// Always call setup before other operations
async function initializeSDK() {
  const sdk = new BorrowSDK(config);
  await sdk.setup(); // Required!
  return sdk;
}

API Issues

”Rate limited” (429)

Problem: Too many API requests. Solution:
// Add delay between requests
async function rateLimitedFetch<T>(operation: () => Promise<T>): Promise<T> {
  try {
    return await operation();
  } catch (error) {
    if (error instanceof ApiError && error.statusCode === 429) {
      console.log('Rate limited, waiting...');
      await new Promise(r => setTimeout(r, 5000));
      return await operation();
    }
    throw error;
  }
}

// Or configure retry
const sdk = new BorrowSDK({
  retryConfig: {
    maxRetries: 3,
    retryDelay: 2000,
    retryableStatusCodes: [429]
  }
});

Storage Issues

”Storage quota exceeded”

Problem: Browser localStorage full. Solution:
// Use custom storage with cleanup
const sdk = new BorrowSDK({
  storage: {
    _data: new Map(),
    getItem(key) {
      return this._data.get(key) || localStorage.getItem(key);
    },
    setItem(key, value) {
      try {
        localStorage.setItem(key, value);
      } catch {
        // Fallback to memory
        this._data.set(key, value);
      }
    },
    removeItem(key) {
      localStorage.removeItem(key);
      this._data.delete(key);
    },
    clear() {
      // Only clear SDK keys
      const prefix = '@satsterminal/borrow';
      for (let i = localStorage.length - 1; i >= 0; i--) {
        const key = localStorage.key(i);
        if (key?.startsWith(prefix)) {
          localStorage.removeItem(key);
        }
      }
      this._data.clear();
    }
  }
});

React-Specific Issues

”Cannot update unmounted component”

Problem: State updates after component unmounts. Solution:
useEffect(() => {
  let mounted = true;

  sdk.getLoan({
    onStatusUpdate: (status) => {
      if (mounted) {
        setStatus(status);
      }
    },
    onComplete: () => {
      if (mounted) {
        setComplete(true);
      }
    }
  });

  return () => {
    mounted = false;
    // Stop tracking if needed
  };
}, []);

Debug Mode

Enable detailed logging:
const sdk = new BorrowSDK({
  logger: {
    debug: (msg) => console.debug('[SDK Debug]', msg),
    info: (msg) => console.info('[SDK Info]', msg),
    warn: (msg) => console.warn('[SDK Warn]', msg),
    error: (msg) => console.error('[SDK Error]', msg)
  }
});

Getting Help

If issues persist:
  1. Check documentation - docs.satsterminal.com
  2. Search issues - github.com/satsterminal/sdk/issues
  3. Contact support - [email protected]
When reporting issues, include:
  • SDK version
  • Error message and stack trace
  • Steps to reproduce
  • Browser/Node.js version
  • Relevant configuration (without API keys)