@satsterminal-sdk/borrow
v1.6.21
Published
Borrowing module for SatsTerminal SDK
Maintainers
Readme
@satsterminal-sdk/borrow
Borrow stablecoins against your Bitcoin collateral. Gasless, non-custodial, powered by smart accounts.
Installation
npm install @satsterminal-sdk/borrowOr use the aggregator if you also need swaps:
import { createClient } from 'satsterminal-sdk';
import { ChainType } from '@satsterminal-sdk/borrow';
const { borrow } = createClient({
apiKey: process.env.API_KEY!,
borrow: { chain: ChainType.BASE, wallet }
});Quick Start
import { BorrowSDK, ChainType } from '@satsterminal-sdk/borrow';
const sdk = new BorrowSDK({
apiKey: 'your-api-key',
// baseUrl optional (defaults to https://api.satsterminal.com)
chain: ChainType.ARBITRUM, // or ChainType.BASE
wallet: {
address: 'bc1q...',
signMessage: async (msg) => wallet.signMessage(msg),
sendBitcoin: async (to, sats) => wallet.sendBitcoin(to, sats)
}
});
// Get a loan
const result = await sdk.getLoan({
collateralBTC: 0.1,
loanAmountUSD: 5000,
onDepositReady: (info) => console.log(`Deposit ${info.amount} BTC to ${info.address}`),
onComplete: () => console.log('Loan complete!')
});Features
- Non-custodial: Your Bitcoin, your keys
- Gasless: All EVM transactions are sponsored
- Multi-chain: Arbitrum, Base supported
- Smart accounts: ERC-4337 account abstraction via ZeroDev
Usage
Get a Loan
interface BorrowSDKConfig {
apiKey: string; // Required: Your API key
baseUrl?: string; // Optional: API base URL (defaults to https://api.satsterminal.com)
chain: ChainType; // Required: Chain to use
wallet: WalletProvider; // Required: Wallet provider
rpcUrl?: string; // Optional: Custom RPC URL
bundlerUrl?: string; // Optional: Custom bundler URL
storage?: StorageProvider; // Optional: Custom storage (defaults to localStorage)
workflowPollInterval?: number; // Optional: Polling interval in ms (default: 2000)
sessionValiditySeconds?: number; // Optional: Session validity (default: 259200)
autoTrackWorkflows?: boolean; // Optional: Auto-track workflows (default: true)
quoteSelector?: (quotes: Quote[]) => Quote; // Optional: Custom quote selector
retryConfig?: RetryConfig; // Optional: Retry configuration
logger?: Logger; // Optional: Custom logger
}Wallet Provider
The SDK requires a wallet provider that implements:
interface WalletProvider {
address: string; // Bitcoin address
publicKey?: string; // Optional: Public key
signMessage: (message: string) => Promise<string>; // Sign messages
sendBitcoin?: (toAddress: string, satoshis: number) => Promise<string>; // Send BTC
}Example Configuration
const sdk = new BorrowSDK({
apiKey: process.env.SATSTERMINAL_API_KEY!,
chain: ChainType.ARBITRUM,
wallet: {
address: bitcoinAddress,
signMessage: async (message) => {
// Use sats-connect, Xverse, or UniSat wallet
const response = await signMessage({ message });
return response.signature;
},
sendBitcoin: async (to, amount) => {
const response = await sendBtcTransaction({
recipients: [{ address: to, amount }]
});
return response.txid;
}
},
autoTrackWorkflows: true,
workflowPollInterval: 2000
});Core Concepts
1. Setup
Before borrowing, you need to set up the SDK by calling setup(). This initializes the smart account and creates a session.
2. Quotes
Get loan quotes based on your collateral amount and desired loan amount. The SDK fetches quotes from multiple protocols.
3. Borrowing
Once you have a quote, you can execute the borrow operation. The SDK handles the entire workflow.
4. Repaying
Repay your loan partially or fully. You can also withdraw collateral if your loan health allows it.
5. Workflow Tracking
The SDK tracks asynchronous workflows and provides status updates via callbacks.
Repay Flow
The repay workflow is an asynchronous process that handles loan repayment and collateral withdrawal. Here's how it works:
Overview
When you call repay() or withdrawCollateral(), the SDK:
- Validates the amounts against the current loan state
- Creates a repay transaction on the backend
- Starts a Temporal workflow that handles the entire process
- Tracks the workflow and provides status updates
Workflow Stages
The repay workflow goes through the following stages:
1. INITIALIZING
- The workflow is being set up
- Validates amounts and prepares the transaction
- User Action: None - wait for next stage
2. TRANSFERRING_TO_KERNEL (if repaying)
- USDC is transferred from your wallet to the smart account (kernel wallet)
- User Action: If insufficient balance, deposit USDC to the smart account address shown
- Status: "Moving repay tokens to kernel wallet"
3. REPAYING_LOAN (if repaying)
- The loan is repaid on the protocol (AAVE, Morpho, etc.)
- USDC is used to pay down the debt
- User Action: None - automatic
- Status: "Executing loan repayment on protocol"
4. WITHDRAWING_COLLATERAL (if withdrawing)
- Collateral (WBTC) is withdrawn from the protocol
- User Action: None - automatic
- Status: "Withdrawing collateral from protocol"
5. TRANSFERRING_TO_PLATFORM_WALLET (if withdrawing)
- Withdrawn collateral is transferred to the platform wallet
- User Action: None - automatic
- Status: "Moving collateral to platform wallet"
6. BRIDGE Stages (if withdrawing)
If you're withdrawing collateral, it needs to be bridged from EVM (WBTC) to Bitcoin (BTC):
- BRIDGE_INITIALIZING: Preparing the bridge
- BRIDGE_QUOTE_READY: Bridge quote received
- BRIDGE_SWAP_CREATED: Bridge swap order created
- BRIDGE_EXECUTING_APPROVAL: Approving bridge contract
- BRIDGE_APPROVAL_CONFIRMED: Approval confirmed
- BRIDGE_EXECUTING_INITIATE: Initiating bridge
- BRIDGE_INITIATE_CONFIRMED: Bridge initiated
- BRIDGE_AWAITING_BRIDGE_COMPLETION: Waiting for bridge to complete
- BRIDGE_COMPLETED: Bridge completed, BTC ready
7. COMPLETED
- All operations completed successfully
- If repaying: Loan debt reduced
- If withdrawing: BTC sent to your withdrawal address
8. FAILED or CANCELLED
- Workflow encountered an error or was cancelled
- Check error details for resolution
Repay Flow Scenarios
Scenario 1: Repay Only (No Withdrawal)
INITIALIZING
↓
TRANSFERRING_TO_KERNEL (User deposits USDC)
↓
REPAYING_LOAN
↓
COMPLETEDScenario 2: Withdraw Collateral Only (No Repay)
INITIALIZING
↓
WITHDRAWING_COLLATERAL
↓
TRANSFERRING_TO_PLATFORM_WALLET
↓
BRIDGE_INITIALIZING → ... → BRIDGE_COMPLETED
↓
COMPLETEDScenario 3: Repay and Withdraw (Both)
INITIALIZING
↓
TRANSFERRING_TO_KERNEL (User deposits USDC)
↓
REPAYING_LOAN
↓
WITHDRAWING_COLLATERAL
↓
TRANSFERRING_TO_PLATFORM_WALLET
↓
BRIDGE_INITIALIZING → ... → BRIDGE_COMPLETED
↓
COMPLETEDImportant Notes
USDC Deposit Required: If repaying, you must deposit USDC to the smart account address before the workflow can proceed past
TRANSFERRING_TO_KERNEL.Max Withdrawable: Before withdrawing, check
getLoanCollateralInfo()to get the accuratemaxWithdrawableamount (accounts for fees and health factor).Workflow Tracking: Use callbacks to track progress:
await sdk.repay(loanId, amount, { trackWorkflow: true, callbacks: { onStatusUpdate: (status) => { console.log(`[${status.step}] ${status.label}`); console.log(status.description); }, onComplete: () => console.log('Done!'), onError: (error) => console.error('Error:', error) } });Resuming Stuck Workflows: If a workflow gets stuck (e.g., waiting for USDC deposit), you can retry by calling
repay()again with the same parameters, or use the "I've Deposited Funds" button in the UI.Partial Repays: You can repay any amount less than the total debt. The workflow will handle partial repayment correctly.
Health Factor: The system ensures your loan remains healthy after operations. If a withdrawal would make the loan unhealthy, it will be rejected with an error message showing the maximum withdrawable amount.
API Reference
Initialization
new BorrowSDK(config: BorrowSDKConfig)
Creates a new SDK instance.
const sdk = new BorrowSDK({
apiKey: 'your-api-key',
chain: 'arbitrum',
wallet: walletProvider
});Wallet Connection
setup(): Promise<UserStatus>
Initializes the smart account and creates a session. Must be called before borrowing.
const userStatus = await sdk.setup();
console.log('Smart Account:', userStatus.smartAccountAddress);
console.log('Is Deployed:', userStatus.isDeployed);Returns: UserStatus object with connection details.
Loan Operations
getLoan(options: GetLoanOptions): Promise<LoanResult>
High-level method that handles the entire loan process: setup, quoting, and borrowing.
const result = await sdk.getLoan({
collateralBTC: 0.01,
loanAmountUSD: 1000,
ltv: 70,
destinationAddress: '0x...', // Optional: Where to send the loan
onStatusUpdate: (status) => {
console.log('Status:', status.label, status.description);
},
onDepositReady: (info) => {
console.log('Deposit to:', info.depositAddress);
console.log('Amount:', info.amount, 'BTC');
},
onComplete: (result) => {
console.log('Loan completed!', result);
},
onError: (error) => {
console.error('Error:', error);
}
});Options:
collateralBTC: number- Amount of BTC to use as collateralloanAmountUSD: number- Desired loan amount in USDltv?: number- Loan-to-value ratio (default: 70)term?: number- Loan term in days (default: 30)destinationAddress?: string- Address to receive the loanquoteSelector?: (quotes: Quote[]) => Quote- Custom quote selection logiconStatusUpdate?: (status: WorkflowStatus) => void- Status update callbackonDepositReady?: (info: DepositInfo) => void- Deposit ready callbackonComplete?: (result: any) => void- Completion callbackonError?: (error: string) => void- Error callback
Returns: LoanResult with workflow ID and tracker.
setup(): Promise<UserStatus>
Sets up the SDK for loan operations. Must be called before getQuotes() or executeBorrow().
const userStatus = await sdk.setup();getQuotes(request: QuoteRequest): Promise<Quote[]>
Fetches loan quotes from available protocols.
const quotes = await sdk.getQuotes({
collateralAmount: '0.01',
loanAmount: '1000',
ltv: 70,
term: 30 // Optional: Loan term in days (default: 30)
});
// Select a quote (or use default selector)
const selectedQuote = quotes[0]; // Or use custom selectorReturns: Array of Quote objects from different protocols.
executeBorrow(quote: Quote, options?: BorrowOptions): Promise<string>
Executes a borrow transaction with the selected quote.
const workflowId = await sdk.executeBorrow(selectedQuote, {
destinationAddress: '0x...',
trackWorkflow: true,
callbacks: {
onStatusUpdate: (status) => console.log(status),
onComplete: () => console.log('Done!')
}
});Returns: Workflow ID string.
Repay Operations
repay(originalBorrowId: string, repayAmount: string, options?: RepayOptions): Promise<string>
Repay a loan (full or partial).
const transactionId = await sdk.repay('loan-id', '500', {
useCollateral: false, // Use collateral to repay?
collateralToWithdraw: '0.001', // Optional: Withdraw collateral
userBtcWithdrawAddress: 'bc1q...', // Required if withdrawing
trackWorkflow: true,
callbacks: {
onStatusUpdate: (status) => console.log(status),
onComplete: () => console.log('Repay complete!')
}
});Parameters:
originalBorrowId: string- The loan IDrepayAmount: string- Amount to repay in USDC (use '0' for collateral-only withdrawal)options?: RepayOptionsuseCollateral?: boolean- Use collateral for repaymentcollateralToWithdraw?: string- Amount of collateral to withdrawuserBtcWithdrawAddress?: string- BTC address for withdrawaltrackWorkflow?: boolean- Track the workflowcallbacks?: WorkflowCallbacks- Workflow callbacks
Returns: Transaction ID string.
withdrawCollateral(originalBorrowId: string, collateralAmount: string, btcWithdrawAddress: string, options?: WithdrawOptions): Promise<string>
Withdraw collateral from a loan.
const transactionId = await sdk.withdrawCollateral(
'loan-id',
'0.001',
'bc1q...',
{
trackWorkflow: true,
callbacks: {
onStatusUpdate: (status) => console.log(status),
onComplete: () => console.log('Withdrawal complete!')
}
}
);Returns: Transaction ID string.
getLoanCollateralInfo(loanId: string): Promise<LoanCollateralInfo | null>
Get loan collateral information including max withdrawable amount.
const collateralInfo = await sdk.getLoanCollateralInfo('loan-id');
if (collateralInfo) {
console.log('Max Withdrawable:', collateralInfo.maxWithdrawable, 'BTC');
console.log('Available Collateral:', collateralInfo.availableCollateral, 'BTC');
console.log('Total Debt:', collateralInfo.totalDebt, 'USDC');
console.log('Remaining Debt:', collateralInfo.remainingDebt, 'USDC');
}Returns: LoanCollateralInfo object or null if not available.
Transaction History
getLoanHistory(options?: HistoryOptions): Promise<PaginatedResponse<UserTransaction>>
Get loan transaction history.
const history = await sdk.getLoanHistory({
page: 1,
limit: 20,
status: 'active' // 'active' | 'pending' | 'all'
});
console.log(`Found ${history.transactions.length} transactions`);
console.log(`Total: ${history.pagination.totalTransactions}`);Options:
page?: number- Page number (default: 1)limit?: number- Items per page (default: 10)status?: 'active' | 'pending' | 'all'- Filter by status (default: 'all')
Returns: Paginated response with transactions and pagination info.
getPendingLoans(): Promise<UserTransaction[]>
Get all pending loans (awaiting deposit).
const pendingLoans = await sdk.getPendingLoans();
console.log(`You have ${pendingLoans.length} pending loans`);getRepayTransactions(loanId?: string): Promise<RepayTransaction[]>
Get repay transactions for a loan or all repay transactions.
// Get repay transactions for a specific loan
const repayTxs = await sdk.getRepayTransactions('loan-id');
// Get all repay transactions
const allRepayTxs = await sdk.getRepayTransactions();getRepayStatus(transactionId: string): Promise<RepayTransactionStatusResponse>
Get status of a repay transaction.
const status = await sdk.getRepayStatus('transaction-id');
console.log('Status:', status.transactionDetails);
console.log('Workflow State:', status.transactionState);Workflow Tracking
trackWorkflow(workflowId: string, callbacks: WorkflowCallbacks, workflowType?: 'borrow' | 'repay'): Promise<void>
Manually track a workflow.
await sdk.trackWorkflow('workflow-id', {
onStatusUpdate: (status) => {
console.log('Status:', status.label);
},
onComplete: (result) => {
console.log('Workflow complete!', result);
},
onError: (error) => {
console.error('Error:', error);
}
}, 'borrow'); // or 'repay'resumeLoan(workflowId: string, callbacks?: WorkflowCallbacks): Promise<void>
Resume tracking a loan workflow.
await sdk.resumeLoan('workflow-id', {
onStatusUpdate: (status) => console.log(status),
onComplete: () => console.log('Resumed and completed!')
});Utility Methods
getStatus(workflowId: string): Promise<any>
Get workflow status.
const status = await sdk.getStatus('workflow-id');clearSession(): void
Clear the current session.
sdk.clearSession();Examples
Complete Loan Flow
import { BorrowSDK } from '@satsterminal-sdk/borrow';
const sdk = new BorrowSDK({
apiKey: 'your-api-key',
chain: 'arbitrum',
wallet: walletProvider
});
// Get a loan with automatic workflow tracking
const result = await sdk.getLoan({
collateralBTC: 0.01,
loanAmountUSD: 1000,
ltv: 70,
onStatusUpdate: (status) => {
console.log(`[${status.step}/${status.totalSteps}] ${status.label}`);
console.log(status.description);
},
onDepositReady: (info) => {
console.log('📥 Deposit Required:');
console.log(`Address: ${info.depositAddress}`);
console.log(`Amount: ${info.amount} BTC`);
// User deposits BTC here
},
onComplete: (result) => {
console.log('✅ Loan completed!');
console.log('Workflow ID:', result.workflowId);
},
onError: (error) => {
console.error('❌ Error:', error);
}
});
// Stop tracking when done
result.stop();Manual Loan Flow (Advanced)
// Step 1: Setup
await sdk.setup();
// Step 2: Get quotes
const quotes = await sdk.getQuotes({
collateralAmount: '0.01',
loanAmount: '1000',
ltv: 70
});
// Step 3: Select best quote (or use default selector)
const selectedQuote = quotes[0];
// Step 4: Execute borrow with workflow tracking
const workflowId = await sdk.executeBorrow(selectedQuote, {
trackWorkflow: true,
callbacks: {
onStatusUpdate: (status) => {
console.log(`[${status.step}/${status.totalSteps}] ${status.label}`);
},
onDepositReady: (info) => {
console.log('Deposit to:', info.depositAddress);
},
onComplete: () => console.log('Done!')
}
});Withdraw USDC (Gasless)
// Withdraw borrowed USDC to your wallet - gas is sponsored
const txHash = await sdk.withdrawToEVM({
chain: ChainType.ARBITRUM,
amount: '100',
destinationAddress: '0x...'
});Repay a Loan
await sdk.repay(loanId, '1000', {
trackWorkflow: true,
callbacks: {
onComplete: () => console.log('Repaid!')
}
});Withdraw Collateral to Bitcoin
await sdk.withdrawCollateral(loanId, '0.01', 'bc1q...', {
trackWorkflow: true
});Check Positions
// Get wallet balances
const positions = await sdk.getWalletPositions();
// Get loan history
const history = await sdk.getLoanHistory({ status: 'active' });
// Get collateral info
const info = await sdk.getLoanCollateralInfo(loanId);
console.log('Max withdrawable:', info.maxWithdrawable);Configuration
interface BorrowSDKConfig {
apiKey: string; // Required
baseUrl: string; // Required
chain: ChainType; // Required: ARBITRUM | BASE
wallet: WalletProvider; // Required
workflowPollInterval?: number; // Default: 2000ms
sessionValiditySeconds?: number; // Default: 259200 (3 days)
autoTrackWorkflows?: boolean; // Default: true
}
interface WalletProvider {
address: string;
signMessage: (message: string) => Promise<string>;
sendBitcoin?: (toAddress: string, satoshis: number) => Promise<string>;
}Wallet Integration
Works with any Bitcoin wallet that can sign messages:
// With sats-connect
import { signMessage, sendBtcTransaction } from 'sats-connect';
const wallet = {
address: bitcoinAddress,
signMessage: async (msg) => {
const res = await signMessage({ message: msg });
return res.signature;
},
sendBitcoin: async (to, amount) => {
const res = await sendBtcTransaction({
recipients: [{ address: to, amount }]
});
return res.txid;
}
};API Reference
| Method | Description |
|--------|-------------|
| setup() | Initialize SDK and create session |
| getLoan(options) | Get a loan (handles full flow) |
| getQuotes(params) | Get loan quotes |
| executeBorrow(quote) | Execute borrow with quote |
| repay(loanId, amount, options) | Repay a loan |
| withdrawCollateral(loanId, amount, btcAddress) | Withdraw collateral to BTC |
| withdrawToEVM(params) | Withdraw USDC to EVM (gasless) |
| withdrawToBitcoin(params) | Withdraw USDC to BTC |
| getLoanHistory(options) | Get transaction history |
| getLoanCollateralInfo(loanId) | Get loan collateral details |
| getWalletPositions() | Get wallet token balances |
Error Handling
import { BorrowSDKError, ApiError } from '@satsterminal-sdk/borrow';
try {
await sdk.getLoan({ ... });
} catch (error) {
if (error instanceof ApiError) {
console.error('API Error:', error.statusCode, error.message);
}
}Documentation
Full documentation: docs.satsterminal.com
License
MIT
