Skip to main content

Cross-Chain Withdrawals

This guide covers withdrawing assets from your EVM smart account.

Overview

The SDK provides two withdrawal methods:
MethodDestinationGasUse Case
withdrawToEVM()EVM addressSponsored (Free)Transfer USDC to your personal wallet
withdrawToBitcoin()Bitcoin addressPaid via bridgeConvert to BTC

Withdraw USDC from your smart account to any EVM address with zero gas fees.

How It Works

  1. Your borrowed USDC is in your smart account (0th wallet)
  2. SDK executes transfer via ZeroDev paymaster
  3. Gas is sponsored - you pay nothing
  4. USDC arrives in your personal wallet

Basic Usage

const txHash = await sdk.withdrawToEVM({
  chain: ChainType.ARBITRUM,
  amount: '100',                    // 100 USDC
  destinationAddress: '0x742d...'   // Your wallet
});

console.log('Transaction:', txHash);
// Check on Arbiscan: https://arbiscan.io/tx/{txHash}

Complete Example

import { BorrowSDK, ChainType } from '@satsterminal-sdk/borrow';

async function withdrawToMyWallet() {
  const sdk = new BorrowSDK({
    apiKey: process.env.API_KEY!,
    chain: ChainType.ARBITRUM,
    wallet: walletProvider
  });

  await sdk.setup();

  // Check available balance
  const positions = await sdk.getWalletPositions();
  const usdcBalance = positions.data.find(
    p => p.attributes.fungible_info?.symbol === 'USDC'
  )?.attributes.quantity.float || 0;

  console.log('Available USDC:', usdcBalance);

  if (usdcBalance < 10) {
    console.log('Insufficient balance');
    return;
  }

  // Withdraw to your personal wallet (gasless!)
  const myWallet = '0x742d35Cc6634C0532925a3b844Bc9e7595f...';

  const txHash = await sdk.withdrawToEVM({
    chain: ChainType.ARBITRUM,
    amount: usdcBalance.toString(),
    destinationAddress: myWallet
  });

  console.log('Withdrawal complete!');
  console.log('TX Hash:', txHash);
  console.log('View:', `https://arbiscan.io/tx/${txHash}`);
}

Supported Chains

ChainStatus
Arbitrum✅ Supported
Base✅ Supported

Bitcoin Withdrawals

For withdrawing to a Bitcoin address, use withdrawToBitcoin().

How It Works

  1. Assets are transferred from your smart account
  2. Bridge protocol swaps to BTC
  3. BTC is sent to your Bitcoin address

Basic Withdrawal

const txId = await sdk.withdrawToBitcoin({
  chain: ChainType.ARBITRUM,  // Source chain
  amount: '100',               // Amount to withdraw
  assetSymbol: 'USDC',        // Asset to withdraw
  btcAddress: 'bc1q...'       // Destination BTC address
});

console.log('Withdrawal initiated:', txId);

Tracking Withdrawal Status

// Get status
const status = await sdk.getWithdrawStatus(txId);

console.log('Success:', status.success);
console.log('Stage:', status.transactionState?.stage);
console.log('Redeem TX:', status.transactionState?.redeemTxHash);

// Full transaction details
console.log('Details:', status.data);

Supported Assets

AssetChains
USDCArbitrum, Base, Ethereum
USDTArbitrum, Base, Ethereum

Withdrawal Parameters

interface WithdrawToBitcoinRequest {
  chain: ChainType;     // Source chain
  amount: string;       // Amount to withdraw (as string)
  assetSymbol: string;  // 'USDC' or 'USDT'
  btcAddress: string;   // Destination BTC address
}

Chain Selection

import { ChainType } from '@satsterminal-sdk/borrow';

// Withdraw from Arbitrum
await sdk.withdrawToBitcoin({
  chain: ChainType.ARBITRUM,
  // ...
});

// Withdraw from Base
await sdk.withdrawToBitcoin({
  chain: ChainType.BASE,
  // ...
});

Checking Balance Before Withdrawal

// Get available balance
const positions = await sdk.getWalletPositions();
const usdcPosition = positions.data.find(
  p => p.attributes.fungible_info?.symbol === 'USDC'
);

const availableBalance = usdcPosition?.attributes.quantity.float || 0;
console.log('Available USDC:', availableBalance);

// Validate withdrawal amount
const withdrawAmount = 100;
if (withdrawAmount > availableBalance) {
  throw new Error(`Insufficient balance. Available: ${availableBalance} USDC`);
}

Withdrawal Status Response

interface WithdrawStatusResponse {
  success: boolean;
  data: WithdrawTransaction;
  transactionState?: {
    stage: string;
    redeemTxHash?: string;  // BTC transaction hash
    error?: string;
  };
}

Status Stages

StageDescription
PENDINGWithdrawal initiated
PROCESSINGBeing processed
BRIDGINGBridge in progress
COMPLETEDBTC sent
FAILEDWithdrawal failed

Polling for Completion

async function waitForWithdrawal(sdk: BorrowSDK, txId: string): Promise<string> {
  const maxAttempts = 60; // 30 minutes with 30s intervals
  let attempts = 0;

  while (attempts < maxAttempts) {
    const status = await sdk.getWithdrawStatus(txId);

    if (status.transactionState?.stage === 'COMPLETED') {
      return status.transactionState.redeemTxHash!;
    }

    if (status.transactionState?.stage === 'FAILED') {
      throw new Error(status.transactionState.error || 'Withdrawal failed');
    }

    console.log(`Status: ${status.transactionState?.stage}...`);
    await new Promise(r => setTimeout(r, 30000)); // Wait 30s
    attempts++;
  }

  throw new Error('Withdrawal timed out');
}

// Usage
const btcTxHash = await waitForWithdrawal(sdk, txId);
console.log('BTC Transaction:', btcTxHash);

Fee Estimation

Get estimated fees before withdrawal:
const fees = await sdk.getFees({
  chain: ChainType.ARBITRUM,
  collateralAmount: '0.1' // Amount in BTC equivalent
});

console.log('Fee Breakdown:');
console.log('  Garden Fee:', fees.gardenFeePercent, '%');
console.log('  Bridge Fee:', fees.totalBridgeFeePercentage, '%');
console.log('  Estimated Gas:', fees.estimatedGasFee);
console.log('  Total Fee (USD):', fees.totalBridgeFeeUSD);

Error Handling

try {
  const txId = await sdk.withdrawToBitcoin({
    chain: ChainType.ARBITRUM,
    amount: '100',
    assetSymbol: 'USDC',
    btcAddress: 'bc1q...'
  });
} catch (error) {
  if (error instanceof Error) {
    if (error.message.includes('insufficient balance')) {
      console.error('Not enough balance to withdraw');
    } else if (error.message.includes('invalid address')) {
      console.error('Invalid BTC address');
    } else if (error.message.includes('minimum')) {
      console.error('Amount below minimum withdrawal');
    } else {
      console.error('Withdrawal error:', error.message);
    }
  }
}