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 confusingsell
booleanAutomatic 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