Migration Guide

Overview

The SatsTerminal SDK V2 API introduces a significantly simplified approach to token swapping while maintaining full backward compatibility with V1. This guide will help you understand the conceptual differences and provide practical examples for migrating your code.

Key Benefits of V2

  • Simplified Direction Control: Clear fromToken/toToken parameters instead of confusing sell boolean

  • Automatic Order Management: No need to manually handle orders - everything managed via swapId

  • Multiple PSBT Support: Can handle complex transactions requiring multiple PSBTs

  • Normalized Data: Consistent, clean response structures

  • Better UX: Automatic marketplace selection and optimization

  • Reduced Complexity: Fewer parameters and simpler responses

Conceptual Differences

1. Direction Control

V1 Problem: The sell boolean parameter was confusing and error-prone.

// V1: Confusing direction control
const quote = await client.fetchQuote({
  btcAmount: "0.00008",
  runeName: "GOLD•DUST",
  sell: false,  // What does this mean? Buy runes with BTC? Sell BTC for runes?
  address: "bc1..."
});

V2 Solution: Clear, intuitive fromToken and toToken parameters.

// V2: Crystal clear direction
const quote = await client.swapQuote({
  amount: "0.00008",
  fromToken: "BTC",        // I'm swapping FROM BTC
  toToken: "GOLD DUST",    // I'm swapping TO GOLD DUST
  address: "bc1...",
  protocol: "alkanes",
  params: {}
});

2. Order Management

V1 Problem: Manual order selection and management across multiple API calls.

// V1: Manual order management
const quote = await client.fetchQuote({ ... });
const selectedOrders = quote.selectedOrders;  

const psbt = await client.getPSBT({
  orders: selectedOrders,  // Must pass orders explicitly
  ...
});

const result = await client.confirmPSBT({
  orders: selectedOrders,  // Must pass orders again
  ...
});

V2 Solution: Automatic order management via swapId.

// V2: Automatic order management
const quote = await client.swapQuote({ ... });
// Orders are automatically managed internally

const psbt = await client.swapPSBT({
  swapId: quote.swapId,  // No orders needed - all handled automatically
  ...
});

const result = await client.swapSubmit({
  swapId: psbt.swapId,   // Still no orders needed
  ...
});

3. PSBT Handling

V1 Limitation: Always returned a single PSBT.

// V1: Single PSBT only
interface PSBTResponse {
  psbtBase64: string;    // Single PSBT
  psbtHex: string;       // Single PSBT
  inputs: number[];
}

V2 Enhancement: Can return multiple PSBTs for complex transactions.

// V2: Multiple PSBTs supported
interface SwapV2PSBTResponse {
  psbts: SwapV2PSBT[];   // Array of PSBTs
  swapId: string;
}

interface SwapV2PSBT {
  base64: string;
  hex: string;
  inputs: number[];
}

4. Data Structure Consistency

V1 Problem: Inconsistent, nested data structures.

// V1: Complex, inconsistent structure
{
  selectedOrders: [
    {
      market: "Unisat",           // Sometimes "market"
      marketplace: "MagicEden",   // Sometimes "marketplace"
      price: 0.00000004,          // Number format
      formattedUnitPrice: "0.490352", // String format
      // ... many other inconsistent fields
    }
  ],
  totalFormattedAmount: "0.00007542",
  totalPrice: "1843.84645",
  metrics: { /* complex nested structure */ }
}

V2 Solution: Normalized, consistent data format.

// V2: Clean, consistent structure
{
  bestMarketplace: "IdClub",
  swapId: "st-mdyr4s5y-14277fae41249423",
  fromTokenAmount: "0.00007946",    // Consistent string format
  toTokenAmount: "1843.84645",      // Consistent string format
  metrics: {
    idclub: {
      percentFulfilled: "99.33",
      totalPurchased: "1843.84645000",
      percentDifference: "0.00",
      averageUnitPrice: "0.00000004"
    }
  },
  marketplaces: {
    IdClub: {
      fromTokenAmount: "0.00007946",
      toTokenAmount: "1843.84645",
      swapId: "st-mdyr4s5y-14277fae41249423"
    }
  }
}

Migration Examples

Example 1: Basic Rune Purchase

V1 Implementation:

// V1: Complex rune purchase flow
async function buyRunesV1() {
  const client = new SatsTerminal({ apiKey: 'your-api-key' });
  
  try {
    // Step 1: Get quote
    const quote = await client.fetchQuote({
      btcAmount: 0.0001,
      runeName: "LOBO•THE•WOLF•PUP",
      address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
      sell: false,  // Buying runes with BTC
      marketplaces: ["MagicEden"]
    });
    
    // Step 2: Generate PSBT with manual order management
    const psbtResponse = await client.getPSBT({
      orders: quote.selectedOrders,  // Manual order selection
      address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
      publicKey: "03962e74f53d3620f82aab9ecfadbaa2d2b34ab1d794b346f53deb4c1a9d287bfc",
      paymentAddress: "bc1qjtyqu4uqrpnvsfg9tq0wzzsdge3e559wzk4epc",
      paymentPublicKey: "02325fb34ee9ff76df92b34d13059fa8d14008a9426fb1de52f038bfdca5075588",
      runeName: "LOBO•THE•WOLF•PUP",
      feeRate: 5,
      slippage: 9
    });
    
    // Step 3: Sign PSBT (user implementation)
    const signedPsbtBase64 = await signPSBT(psbtResponse.rbfProtected.base64);
    const signedRbfPsbtBase64 = await signPSBT(psbtResponse.rbfProtected.base64);
    
    // Step 4: Confirm with manual order management again
    const confirmation = await client.confirmPSBT({
      orders: quote.selectedOrders,  // Must pass orders again
      address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
      publicKey: "03962e74f53d3620f82aab9ecfadbaa2d2b34ab1d794b346f53deb4c1a9d287bfc",
      paymentAddress: "bc1qjtyqu4uqrpnvsfg9tq0wzzsdge3e559wzk4epc",
      paymentPublicKey: "02325fb34ee9ff76df92b34d13059fa8d14008a9426fb1de52f038bfdca5075588",
      signedPsbtBase64: signedPsbtBase64,
      signedRbfPsbtBase64: signedRbfPsbtBase64,
      swapId: psbtResponse.swapId,
      runeName: "LOBO•THE•WOLF•PUP"
    });
    
    console.log('V1 Transaction completed:', confirmation.txid);
    return confirmation;
    
  } catch (error) {
    console.error('V1 Error:', error);
    throw error;
  }
}

V2 Implementation (Migrated):

// V2: Simplified rune purchase flow
async function buyRunesV2() {
  const client = new SatsTerminal({ apiKey: 'your-api-key' });
  
  try {
    // Step 1: Get quote with clear direction
    const quote = await client.swapQuote({
      amount: "0.0001",
      fromToken: "BTC",                    // Clear: swapping FROM BTC
      toToken: "LOBO•THE•WOLF•PUP",        // Clear: swapping TO this rune
      address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
      protocol: "runes",
      params: {},
      marketplaces: ["MagicEden"]
    });
    
    // Step 2: Generate PSBT - no manual order management needed
    const psbtResponse = await client.swapPSBT({
      marketplace: quote.bestMarketplace,  // Use best marketplace from quote
      swapId: quote.swapId,                // Automatic order management
      address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
      publicKey: "03962e74f53d3620f82aab9ecfadbaa2d2b34ab1d794b346f53deb4c1a9d287bfc",
      paymentAddress: "bc1qjtyqu4uqrpnvsfg9tq0wzzsdge3e559wzk4epc",
      paymentPublicKey: "02325fb34ee9ff76df92b34d13059fa8d14008a9426fb1de52f038bfdca5075588",
      protocol: "runes",
      feeRate: 5,
      slippage: 9
    });
    
    // Step 3: Sign all PSBTs (V2 can have multiple)
    const signedPsbts = await Promise.all(
      psbtResponse.psbts.map(psbt => signPSBT(psbt.hex))
    );
    
    // Step 4: Submit - no order management needed
    const result = await client.swapSubmit({
      marketplace: quote.bestMarketplace,
      swapId: psbtResponse.swapId,         // Automatic order management
      address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
      publicKey: "03962e74f53d3620f82aab9ecfadbaa2d2b34ab1d794b346f53deb4c1a9d287bfc",
      paymentAddress: "bc1qjtyqu4uqrpnvsfg9tq0wzzsdge3e559wzk4epc",
      paymentPublicKey: "02325fb34ee9ff76df92b34d13059fa8d14008a9426fb1de52f038bfdca5075588",
      protocol: "runes",
      signedPsbts: signedPsbts
    });
    
    console.log('V2 Transaction completed:', result.txid);
    return result;
    
  } catch (error) {
    console.error('V2 Error:', error);
    throw error;
  }
}

Example 2: Selling Runes for BTC

V1 Implementation:

// V1: Selling runes (confusing direction)
async function sellRunesV1() {
  const client = new SatsTerminal({ apiKey: 'your-api-key' });
  
  const quote = await client.fetchQuote({
    btcAmount: "20000",  // Amount of runes to sell (confusing parameter name)
    runeName: "GOLD DUST", // Confusing parameter name
    address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
    sell: true,  // This means we're selling runes for BTC
    marketplaces: ["Unisat"],
    protocol: 'alkanes'
  });
  
  const psbt = await client.getPSBT({
    orders: quote.selectedOrders,
    address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
    publicKey: "03962e74f53d3620f82aab9ecfadbaa2d2b34ab1d794b346f53deb4c1a9d287bfc",
    paymentAddress: "bc1qjtyqu4uqrpnvsfg9tq0wzzsdge3e559wzk4epc",
    paymentPublicKey: "02325fb34ee9ff76df92b34d13059fa8d14008a9426fb1de52f038bfdca5075588",
    runeName: "GOLD DUST",
    sell: true,  // Must remember to set this again
    feeRate: 3
    protocol: 'alkanes'
  });
  
  const signedPsbt = await signPSBT(psbt.psbtHex);
  
  const result = await client.confirmPSBT({
    orders: quote.selectedOrders,
    address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
    publicKey: "03962e74f53d3620f82aab9ecfadbaa2d2b34ab1d794b346f53deb4c1a9d287bfc",
    paymentAddress: "bc1qjtyqu4uqrpnvsfg9tq0wzzsdge3e559wzk4epc",
    paymentPublicKey: "02325fb34ee9ff76df92b34d13059fa8d14008a9426fb1de52f038bfdca5075588",
    signedPsbtBase64: signedPsbt,
    swapId: psbt.swapId,
    runeName: "GOLD DUST",
    sell: true  // Must remember this in every call
    protocol: 'alkanes'
  });
  
  return result;
}

V2 Implementation (Migrated):

// V2: Selling runes (crystal clear direction)
async function sellRunesV2() {
  const client = new SatsTerminal({ apiKey: 'your-api-key' });
  
  const quote = await client.swapQuote({
    amount: "20000",
    fromToken: "GOLD DUST",  // Clear: swapping FROM GOLD DUST (protocol agnostic)
    toToken: "BTC",          // Clear: swapping TO BTC
    address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
    protocol: "alkanes",
    params: {}
  });
  
  const psbt = await client.swapPSBT({
    marketplace: quote.bestMarketplace,
    swapId: quote.swapId,    // No need to track orders
    address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
    publicKey: "03962e74f53d3620f82aab9ecfadbaa2d2b34ab1d794b346f53deb4c1a9d287bfc",
    paymentAddress: "bc1qjtyqu4uqrpnvsfg9tq0wzzsdge3e559wzk4epc",
    paymentPublicKey: "02325fb34ee9ff76df92b34d13059fa8d14008a9426fb1de52f038bfdca5075588",
    protocol: "alkanes",
    feeRate: 3,
    slippage: 5
  });
  
  const signedPsbts = await Promise.all(
    psbt.psbts.map(p => signPSBT(p.hex))
  );
  
  const result = await client.swapSubmit({
    marketplace: quote.bestMarketplace,
    swapId: psbt.swapId,     // No need to track orders
    address: "bc1p4mffk7l9a040dgqrl8spunwkguxn68ldwx848urafar9sj85nnrq9rcvyj",
    publicKey: "03962e74f53d3620f82aab9ecfadbaa2d2b34ab1d794b346f53deb4c1a9d287bfc",
    paymentAddress: "bc1qjtyqu4uqrpnvsfg9tq0wzzsdge3e559vzk4epc",
    paymentPublicKey: "02325fb34ee9ff76df92b34d13059fa8d14008a9426fb1de52f038bfdca5075588",
    protocol: "alkanes",
    signedPsbts: signedPsbts
  });
  
  return result;
}

Migration Checklist

1. Update Method Names

2. Update Parameters

3. Update Response Handling

4. Update PSBT Signing

5. Simplify Error Handling

Common Pitfalls and Solutions

Pitfall 1: Single PSBT Assumption

Problem: Assuming V2 returns only one PSBT like V1.

Solution:

// V1: Single PSBT
const signedPsbt = await signPSBT(psbt.psbtHex);

// V2: Multiple PSBTs
const signedPsbts = await Promise.all(
  psbt.psbts.map(p => signPSBT(p.hex)) // or signPSBTs for some wallets
);

Pitfall 2: Order Management Confusion

Problem: Trying to manage orders manually in V2.

Solution:

// V1: Manual order management
const psbt = await client.getPSBT({
  orders: quote.selectedOrders,  // Don't do this in V2
  // ...
});

// V2: Automatic order management
const psbt = await client.swapPSBT({
  swapId: quote.swapId,  // Use swapId instead
  // ...
});

Pitfall 3: Direction Confusion

Problem: Converting sell boolean incorrectly.

Solution:

// V1: Confusing sell parameter
const quote = await client.fetchQuote({
  btcAmount: "0.0001",
  runeName: "GOLD DUST",
  sell: false  // Buying runes with BTC
});

// V2: Clear direction - CORRECT conversion
const quote = await client.swapQuote({
  amount: "0.0001",
  fromToken: "BTC",        // What we're spending
  toToken: "GOLD DUST",    // What we're getting
  // ...
});

// WRONG conversion would be:
// fromToken: "GOLD DUST", toToken: "BTC" (this would be selling)

Last updated