Api

Swap API

Direct swaps and automated swaps

The Swap API provides cross-chain atomic swap functionality with automatic channel setup capabilities.

Endpoints


Estimate Swap

Estimate a swap between two currencies using existing offchain balance.

Method: EstimateSwap

Requirements:

  • Must have sufficient offchain balance for both sending and receiving currencies
  • Use Simple Swap if you don't have channels set up

Parameters:

NameTypeRequiredDescription
sending_currencyOrderbookCurrencyYESCurrency to send
receiving_currencyOrderbookCurrencyYESCurrency to receive
amountSwapAmountYESAmount to swap (sending or receiving)

SwapAmount Object (one of):

FieldTypeDescription
sendingSendAmountSpecify amount to send
receivingSendAmountSpecify amount to receive

Response:

FieldTypeDescription
order_matchOrderMatchEstimated execution (optional - null if no liquidity)

Example Request:

TypeScript
import { SwapServiceClient, EstimateSwapRequest } from './proto/SwapServiceClientPb'

const client = new SwapServiceClient('http://localhost:5001')

const request = new EstimateSwapRequest()
request.setSendingCurrency({
  network: { protocol: 0, chainId: '0', name: 'Bitcoin' },
  assetId: 'BTC'
})
request.setReceivingCurrency({
  network: { protocol: 1, chainId: '1', name: 'Ethereum' },
  assetId: '0x0000000000000000000000000000000000000000'
})
request.setAmount({
  sending: { value: '1000000' } // 0.01 BTC
})

const response = await client.estimateSwap(request, {})
const match = response.getOrderMatch()

if (match) {
  console.log('Sending:', match.getSendingAmount())
  console.log('Receiving:', match.getReceivingAmount())
  console.log('Price:', match.getPrice())
} else {
  console.log('No liquidity available')
}
Go
import (
    "context"
    "log"
    pb "your-package/proto"
)

client := pb.NewSwapServiceClient(conn)

req := &pb.EstimateSwapRequest{
    SendingCurrency: &pb.OrderbookCurrency{
        Network: &pb.Network{
            Protocol: 0,
            ChainId:  "0",
            Name:     "Bitcoin",
        },
        AssetId: "BTC",
    },
    ReceivingCurrency: &pb.OrderbookCurrency{
        Network: &pb.Network{
            Protocol: 1,
            ChainId:  "1",
            Name:     "Ethereum",
        },
        AssetId: "0x0000000000000000000000000000000000000000",
    },
    Amount: &pb.SwapAmount{
        Amount: &pb.SwapAmount_Sending{
            Sending: &pb.SendAmount{Value: "1000000"}, // 0.01 BTC
        },
    },
}

resp, err := client.EstimateSwap(context.Background(), req)
if err != nil {
    log.Fatalf("Error: %v", err)
}

if match := resp.GetOrderMatch(); match != nil {
    log.Printf("Sending: %s", match.GetSendingAmount())
    log.Printf("Receiving: %s", match.GetReceivingAmount())
    log.Printf("Price: %s", match.GetPrice())
} else {
    log.Println("No liquidity available")
}
Rust
use tonic::Request;
use your_package::swap_service_client::SwapServiceClient;
use your_package::{EstimateSwapRequest, OrderbookCurrency, Network, SwapAmount, SendAmount};

let mut client = SwapServiceClient::connect("http://localhost:5001").await?;

let request = Request::new(EstimateSwapRequest {
    sending_currency: Some(OrderbookCurrency {
        network: Some(Network {
            protocol: 0,
            chain_id: "0".to_string(),
            name: "Bitcoin".to_string(),
        }),
        asset_id: "BTC".to_string(),
    }),
    receiving_currency: Some(OrderbookCurrency {
        network: Some(Network {
            protocol: 1,
            chain_id: "1".to_string(),
            name: "Ethereum".to_string(),
        }),
        asset_id: "0x0000000000000000000000000000000000000000".to_string(),
    }),
    amount: Some(SwapAmount {
        amount: Some(your_package::swap_amount::Amount::Sending(SendAmount {
            value: "1000000".to_string(), // 0.01 BTC
        })),
    }),
});

let response = client.estimate_swap(request).await?.into_inner();

if let Some(match_) = response.order_match {
    println!("Sending: {}", match_.sending_amount);
    println!("Receiving: {}", match_.receiving_amount);
    println!("Price: {}", match_.price);
} else {
    println!("No liquidity available");
}

Example Response:

{
  "order_match": {
    "sending_amount": "1000000",
    "receiving_amount": "28500000000000000000",
    "price": "28.5"
  }
}

Swap

Execute a swap between two currencies using existing offchain balance.

Method: Swap

Requirements:

  • Must have sufficient offchain balance for both sending and receiving currencies
  • Channels must be active for both currencies
  • Use Simple Swap for automatic channel setup

Parameters:

NameTypeRequiredDescription
sending_currencyOrderbookCurrencyYESCurrency to send
receiving_currencyOrderbookCurrencyYESCurrency to receive
amountSwapAmountYESAmount to swap

Response:

FieldTypeDescription
order_idstringSwap order identifier

Example Request:

TypeScript
const request = new SwapRequest()
request.setSendingCurrency({
  network: { protocol: 0, chainId: '0', name: 'Bitcoin' },
  assetId: 'BTC'
})
request.setReceivingCurrency({
  network: { protocol: 1, chainId: '1', name: 'Ethereum' },
  assetId: '0x0000000000000000000000000000000000000000'
})
request.setAmount({
  sending: { value: '1000000' }
})

const response = await client.swap(request, {})
const orderId = response.getOrderId()
console.log('Swap order created:', orderId)
Go
req := &pb.SwapRequest{
    SendingCurrency: &pb.OrderbookCurrency{
        Network: &pb.Network{
            Protocol: 0,
            ChainId:  "0",
            Name:     "Bitcoin",
        },
        AssetId: "BTC",
    },
    ReceivingCurrency: &pb.OrderbookCurrency{
        Network: &pb.Network{
            Protocol: 1,
            ChainId:  "1",
            Name:     "Ethereum",
        },
        AssetId: "0x0000000000000000000000000000000000000000",
    },
    Amount: &pb.SwapAmount{
        Amount: &pb.SwapAmount_Sending{
            Sending: &pb.SendAmount{Value: "1000000"},
        },
    },
}

resp, err := client.Swap(context.Background(), req)
if err != nil {
    log.Fatalf("Error: %v", err)
}

orderId := resp.GetOrderId()
log.Printf("Swap order created: %s", orderId)
Rust
let request = Request::new(SwapRequest {
    sending_currency: Some(OrderbookCurrency {
        network: Some(Network {
            protocol: 0,
            chain_id: "0".to_string(),
            name: "Bitcoin".to_string(),
        }),
        asset_id: "BTC".to_string(),
    }),
    receiving_currency: Some(OrderbookCurrency {
        network: Some(Network {
            protocol: 1,
            chain_id: "1".to_string(),
            name: "Ethereum".to_string(),
        }),
        asset_id: "0x0000000000000000000000000000000000000000".to_string(),
    }),
    amount: Some(SwapAmount {
        amount: Some(your_package::swap_amount::Amount::Sending(SendAmount {
            value: "1000000".to_string(),
        })),
    }),
});

let response = client.swap(request).await?.into_inner();
let order_id = response.order_id;
println!("Swap order created: {}", order_id);

Estimate Simple Swap

Estimate a swap with automatic channel setup. This calculates all fees including channel opening, deposits, and rentals.

Method: EstimateSimpleSwap

Benefits:

  • No need for existing channels
  • Automatic balance management
  • One-click cross-chain swaps
  • Optional auto-withdrawal

Parameters:

NameTypeRequiredDescription
sending_currencyOrderbookCurrencyYESCurrency to send
receiving_currencyOrderbookCurrencyYESCurrency to receive
amountSwapAmountYESAmount to swap
price_change_toleranceDecimalStringYESMax price slippage (e.g., "0.01" = 1%)
withdraw_sending_fundsboolYESAuto-withdraw sent funds after swap
withdraw_receiving_fundsboolYESAuto-withdraw received funds after swap

Response:

FieldTypeDescription
estimateSimpleSwapEstimateEstimate details (one of multiple variants)

SimpleSwapEstimate Variants:

Instant

Ready to swap immediately - you have sufficient offchain balance.

FieldTypeDescription
order_matchOrderMatchExpected execution
sending_withdrawal_feeWithdrawalFeeFee to withdraw sent funds (optional)
receiving_withdrawal_feeWithdrawalFeeFee to withdraw received funds (optional)

Deferred

Need to set up channels via deposits/rentals first.

FieldTypeDescription
order_matchOrderMatchExpected execution
sending_channel_depositSendingChannelDepositRequired deposit (optional)
receiving_channel_rentalReceivingChannelRentalRequired rental (optional)
sending_withdrawal_feeWithdrawalFeeWithdrawal fee (optional)
receiving_withdrawal_feeWithdrawalFeeWithdrawal fee (optional)

DualFundDeferred

Need to dual-fund a channel.

FieldTypeDescription
order_matchOrderMatchExpected execution
dual_fund_depositDualFundDepositRequired dual-fund deposit
sending_withdrawal_feeWithdrawalFeeWithdrawal fee (optional)
receiving_withdrawal_feeWithdrawalFeeWithdrawal fee (optional)

NotEnoughBalance

Insufficient onchain balance to set up channels.

FieldTypeDescription
needed_onchain_sendingDecimalStringAmount needed onchain

FeesHigherThanAmount

Setup fees exceed swap amount - not economical.

FieldTypeDescription
sending_feeDecimalStringTotal fee in sending currency
receiving_feeDecimalStringTotal fee in receiving currency

RentalTooBig

Rental amount exceeds provider's capacity.

FieldTypeDescription
needed_rentalDecimalStringRequired rental amount
available_rental_liquidityDecimalStringAvailable liquidity
max_rental_capacityDecimalStringMaximum capacity

NoLiquidity

No liquidity in orderbook for this pair.

Example Request:

TypeScript
const request = new EstimateSimpleSwapRequest()
request.setSendingCurrency({
  network: { protocol: 0, chainId: '0', name: 'Bitcoin' },
  assetId: 'BTC'
})
request.setReceivingCurrency({
  network: { protocol: 1, chainId: '1', name: 'Ethereum' },
  assetId: '0x0000000000000000000000000000000000000000'
})
request.setAmount({
  sending: { value: '10000000' } // 0.1 BTC
})
request.setPriceChangeTolerance('0.01') // 1% max slippage
request.setWithdrawSendingFunds(false)
request.setWithdrawReceivingFunds(true) // Auto-withdraw ETH to wallet

const response = await client.estimateSimpleSwap(request, {})
const estimate = response.getEstimate()

if (estimate.hasInstant()) {
  console.log('Ready to swap instantly!')
  const instant = estimate.getInstant()
  console.log('Will receive:', instant.getOrderMatch()?.getReceivingAmount())
}

if (estimate.hasDeferred()) {
  console.log('Need to set up channels first')
  const deferred = estimate.getDeferred()
  if (deferred.hasSendingChannelDeposit()) {
    const deposit = deferred.getSendingChannelDeposit()
    console.log('Deposit needed:', deposit.getAmount())
    console.log('Deposit fee:', deposit.getFee())
  }
  if (deferred.hasReceivingChannelRental()) {
    const rental = deferred.getReceivingChannelRental()
    console.log('Rental needed:', rental.getAmount())
    console.log('Rental fee:', rental.getRentalFee())
  }
}

if (estimate.hasNotEnoughBalance()) {
  console.error('Insufficient balance')
  const needed = estimate.getNotEnoughBalance().getNeededOnchainSending()
  console.error('Need:', needed, 'sats onchain')
}

if (estimate.hasFeesHigherThanAmount()) {
  console.error('Fees too high for this amount')
}

if (estimate.hasNoLiquidity()) {
  console.error('No market liquidity available')
}
Go
req := &pb.EstimateSimpleSwapRequest{
    SendingCurrency: &pb.OrderbookCurrency{
        Network: &pb.Network{
            Protocol: 0,
            ChainId:  "0",
            Name:     "Bitcoin",
        },
        AssetId: "BTC",
    },
    ReceivingCurrency: &pb.OrderbookCurrency{
        Network: &pb.Network{
            Protocol: 1,
            ChainId:  "1",
            Name:     "Ethereum",
        },
        AssetId: "0x0000000000000000000000000000000000000000",
    },
    Amount: &pb.SwapAmount{
        Amount: &pb.SwapAmount_Sending{
            Sending: &pb.SendAmount{Value: "10000000"}, // 0.1 BTC
        },
    },
    PriceChangeTolerance:   "0.01", // 1% max slippage
    WithdrawSendingFunds:   false,
    WithdrawReceivingFunds: true, // Auto-withdraw ETH to wallet
}

resp, err := client.EstimateSimpleSwap(context.Background(), req)
if err != nil {
    log.Fatalf("Error: %v", err)
}

estimate := resp.GetEstimate()

if instant := estimate.GetInstant(); instant != nil {
    log.Println("Ready to swap instantly!")
    log.Printf("Will receive: %s", instant.GetOrderMatch().GetReceivingAmount())
}

if deferred := estimate.GetDeferred(); deferred != nil {
    log.Println("Need to set up channels first")
    if deposit := deferred.GetSendingChannelDeposit(); deposit != nil {
        log.Printf("Deposit needed: %s", deposit.GetAmount())
        log.Printf("Deposit fee: %s", deposit.GetFee())
    }
    if rental := deferred.GetReceivingChannelRental(); rental != nil {
        log.Printf("Rental needed: %s", rental.GetAmount())
        log.Printf("Rental fee: %s", rental.GetRentalFee())
    }
}

if notEnough := estimate.GetNotEnoughBalance(); notEnough != nil {
    log.Printf("Insufficient balance. Need: %s sats onchain",
        notEnough.GetNeededOnchainSending())
}

if estimate.GetFeesHigherThanAmount() != nil {
    log.Println("Fees too high for this amount")
}

if estimate.GetNoLiquidity() != nil {
    log.Println("No market liquidity available")
}
Rust
let request = Request::new(EstimateSimpleSwapRequest {
    sending_currency: Some(OrderbookCurrency {
        network: Some(Network {
            protocol: 0,
            chain_id: "0".to_string(),
            name: "Bitcoin".to_string(),
        }),
        asset_id: "BTC".to_string(),
    }),
    receiving_currency: Some(OrderbookCurrency {
        network: Some(Network {
            protocol: 1,
            chain_id: "1".to_string(),
            name: "Ethereum".to_string(),
        }),
        asset_id: "0x0000000000000000000000000000000000000000".to_string(),
    }),
    amount: Some(SwapAmount {
        amount: Some(your_package::swap_amount::Amount::Sending(SendAmount {
            value: "10000000".to_string(), // 0.1 BTC
        })),
    }),
    price_change_tolerance: "0.01".to_string(), // 1% max slippage
    withdraw_sending_funds: false,
    withdraw_receiving_funds: true, // Auto-withdraw ETH to wallet
});

let response = client.estimate_simple_swap(request).await?.into_inner();
let estimate = response.estimate.unwrap();

match estimate.estimate {
    Some(Estimate::Instant(instant)) => {
        println!("Ready to swap instantly!");
        if let Some(order_match) = instant.order_match {
            println!("Will receive: {}", order_match.receiving_amount);
        }
    }
    Some(Estimate::Deferred(deferred)) => {
        println!("Need to set up channels first");
        if let Some(deposit) = deferred.sending_channel_deposit {
            println!("Deposit needed: {}", deposit.amount);
            println!("Deposit fee: {}", deposit.fee);
        }
        if let Some(rental) = deferred.receiving_channel_rental {
            println!("Rental needed: {}", rental.amount);
            println!("Rental fee: {}", rental.rental_fee);
        }
    }
    Some(Estimate::NotEnoughBalance(not_enough)) => {
        eprintln!("Insufficient balance. Need: {} sats onchain",
            not_enough.needed_onchain_sending);
    }
    Some(Estimate::FeesHigherThanAmount(_)) => {
        eprintln!("Fees too high for this amount");
    }
    Some(Estimate::NoLiquidity(_)) => {
        eprintln!("No market liquidity available");
    }
    _ => {}
}

Example Response (Instant):

{
  "estimate": {
    "instant": {
      "order_match": {
        "sending_amount": "10000000",
        "receiving_amount": "285000000000000000000",
        "price": "28.5"
      },
      "receiving_withdrawal_fee": {
        "fee": "50000000000000000",
        "fee_payment_currency": "RECEIVING"
      }
    }
  }
}

Example Response (Deferred):

{
  "estimate": {
    "deferred": {
      "order_match": {
        "sending_amount": "10000000",
        "receiving_amount": "285000000000000000000",
        "price": "28.5"
      },
      "sending_channel_deposit": {
        "amount": "10000000",
        "unspendable_reserve": "546",
        "fee": "2500",
        "fee_payment_currency": "SENDING",
        "counterparty": "02abc123..."
      },
      "receiving_channel_rental": {
        "amount": "300000000000000000000",
        "unspendable_reserve": "1000000000000000",
        "rental_fee": "300000000000000000",
        "rental_tx_fee_rate": { "relative": "5" },
        "pay_with_sending": true
      }
    }
  }
}

Simple Swap

Execute a swap with automatic channel setup.

Method: SimpleSwap

Features:

  • Automatically opens/deposits channels if needed
  • Rents inbound liquidity if required
  • Performs the swap when channels are ready
  • Optionally withdraws funds back onchain

Parameters:

NameTypeRequiredDescription
sending_currencyOrderbookCurrencyYESCurrency to send
receiving_currencyOrderbookCurrencyYESCurrency to receive
amountSwapAmountYESAmount to swap
price_change_toleranceDecimalStringYESMax price slippage
withdraw_sending_fundsboolYESAuto-withdraw sent funds
withdraw_receiving_fundsboolYESAuto-withdraw received funds

Response:

FieldTypeDescription
outputSimpleSwapOutputExecution details

SimpleSwapOutput:

FieldTypeDescription
simple_swap_idstringUnique swap identifier
instant or deferred or dual_fund_deferred-Execution type

Example Request:

TypeScript
const request = new SimpleSwapRequest()
request.setSendingCurrency({
  network: { protocol: 0, chainId: '0', name: 'Bitcoin' },
  assetId: 'BTC'
})
request.setReceivingCurrency({
  network: { protocol: 1, chainId: '1', name: 'Ethereum' },
  assetId: '0x0000000000000000000000000000000000000000'
})
request.setAmount({
  sending: { value: '10000000' }
})
request.setPriceChangeTolerance('0.01')
request.setWithdrawSendingFunds(false)
request.setWithdrawReceivingFunds(true)

const response = await client.simpleSwap(request, {})
const output = response.getOutput()
const swapId = output.getSimpleSwapId()

console.log('Simple swap started:', swapId)

if (output.hasInstant()) {
  const instant = output.getInstant()
  console.log('Swap order:', instant.getOrderId())
}

if (output.hasDeferred()) {
  console.log('Setting up channels...')
  console.log('Subscribe to updates with SubscribeSimpleSwaps')
}
Go
req := &pb.SimpleSwapRequest{
    SendingCurrency: &pb.OrderbookCurrency{
        Network: &pb.Network{
            Protocol: 0,
            ChainId:  "0",
            Name:     "Bitcoin",
        },
        AssetId: "BTC",
    },
    ReceivingCurrency: &pb.OrderbookCurrency{
        Network: &pb.Network{
            Protocol: 1,
            ChainId:  "1",
            Name:     "Ethereum",
        },
        AssetId: "0x0000000000000000000000000000000000000000",
    },
    Amount: &pb.SwapAmount{
        Amount: &pb.SwapAmount_Sending{
            Sending: &pb.SendAmount{Value: "10000000"},
        },
    },
    PriceChangeTolerance:   "0.01",
    WithdrawSendingFunds:   false,
    WithdrawReceivingFunds: true,
}

resp, err := client.SimpleSwap(context.Background(), req)
if err != nil {
    log.Fatalf("Error: %v", err)
}

output := resp.GetOutput()
swapId := output.GetSimpleSwapId()

log.Printf("Simple swap started: %s", swapId)

if instant := output.GetInstant(); instant != nil {
    log.Printf("Swap order: %s", instant.GetOrderId())
}

if output.GetDeferred() != nil {
    log.Println("Setting up channels...")
    log.Println("Subscribe to updates with SubscribeSimpleSwaps")
}
Rust
let request = Request::new(SimpleSwapRequest {
    sending_currency: Some(OrderbookCurrency {
        network: Some(Network {
            protocol: 0,
            chain_id: "0".to_string(),
            name: "Bitcoin".to_string(),
        }),
        asset_id: "BTC".to_string(),
    }),
    receiving_currency: Some(OrderbookCurrency {
        network: Some(Network {
            protocol: 1,
            chain_id: "1".to_string(),
            name: "Ethereum".to_string(),
        }),
        asset_id: "0x0000000000000000000000000000000000000000".to_string(),
    }),
    amount: Some(SwapAmount {
        amount: Some(your_package::swap_amount::Amount::Sending(SendAmount {
            value: "10000000".to_string(),
        })),
    }),
    price_change_tolerance: "0.01".to_string(),
    withdraw_sending_funds: false,
    withdraw_receiving_funds: true,
});

let response = client.simple_swap(request).await?.into_inner();
let output = response.output.unwrap();
let swap_id = output.simple_swap_id;

println!("Simple swap started: {}", swap_id);

match output.output {
    Some(Output::Instant(instant)) => {
        println!("Swap order: {}", instant.order_id);
    }
    Some(Output::Deferred(_)) => {
        println!("Setting up channels...");
        println!("Subscribe to updates with SubscribeSimpleSwaps");
    }
    _ => {}
}

Subscribe Simple Swaps

Subscribe to real-time updates for all simple swap operations.

Method: SubscribeSimpleSwaps

Parameters: None

Response: Stream of SimpleSwapUpdate

SimpleSwapUpdate:

FieldTypeDescription
timestampTimestampUpdate time
simple_swap_idstringSwap identifier
update-Update type (one of many variants)

Update Types:

UpdateDescription
funding_sending_channelOpening/depositing sending channel
renting_receiving_channelRenting receiving channel
dual_funding_channelDual-funding channel
sending_channel_readySending channel active
receiving_channel_readyReceiving channel active
dual_fund_channel_readyDual-funded channel active
waiting_for_balancesWaiting for sufficient balance
balances_readyBalances sufficient
order_createdSwap order placed
order_completedSwap executed
withdrawing_sending_fundsWithdrawing sent funds
withdrawing_receiving_fundsWithdrawing received funds
withdrawing_dual_funded_fundsWithdrawing dual-fund
sending_funds_withdrawnSend withdrawal complete
receiving_funds_withdrawnReceive withdrawal complete
dual_funded_funds_withdrawnDual-fund withdrawal complete
simple_swap_completedSwap fully complete
simple_swap_errorError occurred

Example:

TypeScript
const request = new SubscribeSimpleSwapsRequest()
const stream = client.subscribeSimpleSwaps(request, {})

stream.on('data', (update) => {
  const timestamp = update.getTimestamp()
  const swapId = update.getSimpleSwapId()

  if (update.hasFundingSendingChannel()) {
    const funding = update.getFundingSendingChannel()
    console.log(`[${swapId}] Funding channel ${funding.getChannelId()}`)
    console.log(`  TX: ${funding.getTxid()}`)
    console.log(`  Amount: ${funding.getAmount()}`)
  }

  if (update.hasRentingReceivingChannel()) {
    const renting = update.getRentingReceivingChannel()
    console.log(`[${swapId}] Renting channel ${renting.getChannelId()}`)
    console.log(`  TX: ${renting.getTxid()}`)
  }

  if (update.hasSendingChannelReady()) {
    console.log(`[${swapId}] Sending channel ready`)
  }

  if (update.hasReceivingChannelReady()) {
    console.log(`[${swapId}] Receiving channel ready`)
  }

  if (update.hasWaitingForBalances()) {
    const waiting = update.getWaitingForBalances()
    console.log(`[${swapId}] Waiting for balances...`)
    console.log(`  Need sending: ${waiting.getNeededSending()}`)
    console.log(`  Need receiving: ${waiting.getNeededReceiving()}`)
  }

  if (update.hasBalancesReady()) {
    console.log(`[${swapId}] Balances ready!`)
  }

  if (update.hasOrderCreated()) {
    const order = update.getOrderCreated()
    console.log(`[${swapId}] Order created: ${order.getOrderId()}`)
  }

  if (update.hasOrderCompleted()) {
    const completed = update.getOrderCompleted()
    console.log(`[${swapId}] Order completed!`)
    console.log(`  Sent: ${completed.getSentAmount()}`)
    console.log(`  Received: ${completed.getReceivedAmount()}`)
  }

  if (update.hasWithdrawingReceivingFunds()) {
    const withdrawing = update.getWithdrawingReceivingFunds()
    console.log(`[${swapId}] Withdrawing funds...`)
    console.log(`  TXs: ${withdrawing.getTxidsList()}`)
  }

  if (update.hasSimpleSwapCompleted()) {
    console.log(`[${swapId}] ✓ Swap completed successfully!`)
  }

  if (update.hasSimpleSwapError()) {
    const error = update.getSimpleSwapError()
    console.error(`[${swapId}] ✗ Error: ${error.getError()}`)
  }
})

stream.on('error', (err) => console.error('Stream error:', err))
Go
req := &pb.SubscribeSimpleSwapsRequest{}
stream, err := client.SubscribeSimpleSwaps(context.Background(), req)
if err != nil {
    log.Fatalf("Error: %v", err)
}

for {
    update, err := stream.Recv()
    if err != nil {
        log.Printf("Stream error: %v", err)
        break
    }

    swapId := update.GetSimpleSwapId()

    if funding := update.GetFundingSendingChannel(); funding != nil {
        log.Printf("[%s] Funding channel %s", swapId, funding.GetChannelId())
        log.Printf("  TX: %s", funding.GetTxid())
        log.Printf("  Amount: %s", funding.GetAmount())
    }

    if renting := update.GetRentingReceivingChannel(); renting != nil {
        log.Printf("[%s] Renting channel %s", swapId, renting.GetChannelId())
        log.Printf("  TX: %s", renting.GetTxid())
    }

    if update.GetSendingChannelReady() != nil {
        log.Printf("[%s] Sending channel ready", swapId)
    }

    if update.GetReceivingChannelReady() != nil {
        log.Printf("[%s] Receiving channel ready", swapId)
    }

    if waiting := update.GetWaitingForBalances(); waiting != nil {
        log.Printf("[%s] Waiting for balances...", swapId)
        log.Printf("  Need sending: %s", waiting.GetNeededSending())
        log.Printf("  Need receiving: %s", waiting.GetNeededReceiving())
    }

    if update.GetBalancesReady() != nil {
        log.Printf("[%s] Balances ready!", swapId)
    }

    if order := update.GetOrderCreated(); order != nil {
        log.Printf("[%s] Order created: %s", swapId, order.GetOrderId())
    }

    if completed := update.GetOrderCompleted(); completed != nil {
        log.Printf("[%s] Order completed!", swapId)
        log.Printf("  Sent: %s", completed.GetSentAmount())
        log.Printf("  Received: %s", completed.GetReceivedAmount())
    }

    if withdrawing := update.GetWithdrawingReceivingFunds(); withdrawing != nil {
        log.Printf("[%s] Withdrawing funds...", swapId)
        log.Printf("  TXs: %v", withdrawing.GetTxids())
    }

    if update.GetSimpleSwapCompleted() != nil {
        log.Printf("[%s] Swap completed successfully!", swapId)
    }

    if swapError := update.GetSimpleSwapError(); swapError != nil {
        log.Printf("[%s] Error: %s", swapId, swapError.GetError())
    }
}
Rust
let request = Request::new(SubscribeSimpleSwapsRequest {});
let mut stream = client.subscribe_simple_swaps(request).await?.into_inner();

while let Some(update) = stream.message().await? {
    let swap_id = &update.simple_swap_id;

    if let Some(funding) = &update.funding_sending_channel {
        println!("[{}] Funding channel {}", swap_id, funding.channel_id);
        println!("  TX: {}", funding.txid);
        println!("  Amount: {}", funding.amount);
    }

    if let Some(renting) = &update.renting_receiving_channel {
        println!("[{}] Renting channel {}", swap_id, renting.channel_id);
        println!("  TX: {}", renting.txid);
    }

    if update.sending_channel_ready.is_some() {
        println!("[{}] Sending channel ready", swap_id);
    }

    if update.receiving_channel_ready.is_some() {
        println!("[{}] Receiving channel ready", swap_id);
    }

    if let Some(waiting) = &update.waiting_for_balances {
        println!("[{}] Waiting for balances...", swap_id);
        println!("  Need sending: {}", waiting.needed_sending);
        println!("  Need receiving: {}", waiting.needed_receiving);
    }

    if update.balances_ready.is_some() {
        println!("[{}] Balances ready!", swap_id);
    }

    if let Some(order) = &update.order_created {
        println!("[{}] Order created: {}", swap_id, order.order_id);
    }

    if let Some(completed) = &update.order_completed {
        println!("[{}] Order completed!", swap_id);
        println!("  Sent: {}", completed.sent_amount);
        println!("  Received: {}", completed.received_amount);
    }

    if let Some(withdrawing) = &update.withdrawing_receiving_funds {
        println!("[{}] Withdrawing funds...", swap_id);
        println!("  TXs: {:?}", withdrawing.txids);
    }

    if update.simple_swap_completed.is_some() {
        println!("[{}] Swap completed successfully!", swap_id);
    }

    if let Some(error) = &update.simple_swap_error {
        eprintln!("[{}] Error: {}", swap_id, error.error);
    }
}

Common Workflows

One-click cross-chain swap

TypeScript
async function oneClickSwap(
  client: SwapServiceClient,
  fromCurrency: OrderbookCurrency,
  toCurrency: OrderbookCurrency,
  amount: string,
  withdrawToWallet: boolean = true
) {
  // 1. Estimate
  const estimateReq = new EstimateSimpleSwapRequest()
  estimateReq.setSendingCurrency(fromCurrency)
  estimateReq.setReceivingCurrency(toCurrency)
  estimateReq.setAmount({ sending: { value: amount } })
  estimateReq.setPriceChangeTolerance('0.02') // 2% max slippage
  estimateReq.setWithdrawSendingFunds(false)
  estimateReq.setWithdrawReceivingFunds(withdrawToWallet)

  const estimate = await client.estimateSimpleSwap(estimateReq, {})

  if (estimate.getEstimate().hasNoLiquidity()) {
    throw new Error('No liquidity available')
  }

  if (estimate.getEstimate().hasNotEnoughBalance()) {
    const needed = estimate.getEstimate().getNotEnoughBalance()
    throw new Error(`Need ${needed.getNeededOnchainSending()} more onchain`)
  }

  // 2. Execute
  const swapReq = new SimpleSwapRequest()
  swapReq.setSendingCurrency(fromCurrency)
  swapReq.setReceivingCurrency(toCurrency)
  swapReq.setAmount({ sending: { value: amount } })
  swapReq.setPriceChangeTolerance('0.02')
  swapReq.setWithdrawSendingFunds(false)
  swapReq.setWithdrawReceivingFunds(withdrawToWallet)

  const response = await client.simpleSwap(swapReq, {})
  return response.getOutput().getSimpleSwapId()
}

// Example usage
const swapId = await oneClickSwap(
  client,
  btcCurrency,
  ethCurrency,
  '10000000', // 0.1 BTC
  true // Auto-withdraw ETH to wallet
)

console.log('Swap started:', swapId)
console.log('Monitor progress with SubscribeSimpleSwaps')
Go
func oneClickSwap(
    client pb.SwapServiceClient,
    fromCurrency *pb.OrderbookCurrency,
    toCurrency *pb.OrderbookCurrency,
    amount string,
    withdrawToWallet bool,
) (string, error) {
    // 1. Estimate
    estimateReq := &pb.EstimateSimpleSwapRequest{
        SendingCurrency:   fromCurrency,
        ReceivingCurrency: toCurrency,
        Amount: &pb.SwapAmount{
            Amount: &pb.SwapAmount_Sending{
                Sending: &pb.SendAmount{Value: amount},
            },
        },
        PriceChangeTolerance:   "0.02", // 2% max slippage
        WithdrawSendingFunds:   false,
        WithdrawReceivingFunds: withdrawToWallet,
    }

    estimate, err := client.EstimateSimpleSwap(context.Background(), estimateReq)
    if err != nil {
        return "", err
    }

    if estimate.GetEstimate().GetNoLiquidity() != nil {
        return "", fmt.Errorf("no liquidity available")
    }

    if notEnough := estimate.GetEstimate().GetNotEnoughBalance(); notEnough != nil {
        return "", fmt.Errorf("need %s more onchain", notEnough.GetNeededOnchainSending())
    }

    // 2. Execute
    swapReq := &pb.SimpleSwapRequest{
        SendingCurrency:        fromCurrency,
        ReceivingCurrency:      toCurrency,
        Amount: &pb.SwapAmount{
            Amount: &pb.SwapAmount_Sending{
                Sending: &pb.SendAmount{Value: amount},
            },
        },
        PriceChangeTolerance:   "0.02",
        WithdrawSendingFunds:   false,
        WithdrawReceivingFunds: withdrawToWallet,
    }

    response, err := client.SimpleSwap(context.Background(), swapReq)
    if err != nil {
        return "", err
    }

    return response.GetOutput().GetSimpleSwapId(), nil
}

// Example usage
swapId, err := oneClickSwap(
    client,
    btcCurrency,
    ethCurrency,
    "10000000", // 0.1 BTC
    true,       // Auto-withdraw ETH to wallet
)
if err != nil {
    log.Fatalf("Error: %v", err)
}

log.Printf("Swap started: %s", swapId)
log.Println("Monitor progress with SubscribeSimpleSwaps")
Rust
async fn one_click_swap(
    client: &mut SwapServiceClient<Channel>,
    from_currency: OrderbookCurrency,
    to_currency: OrderbookCurrency,
    amount: String,
    withdraw_to_wallet: bool,
) -> Result<String, Box<dyn std::error::Error>> {
    // 1. Estimate
    let estimate_req = Request::new(EstimateSimpleSwapRequest {
        sending_currency: Some(from_currency.clone()),
        receiving_currency: Some(to_currency.clone()),
        amount: Some(SwapAmount {
            amount: Some(your_package::swap_amount::Amount::Sending(SendAmount {
                value: amount.clone(),
            })),
        }),
        price_change_tolerance: "0.02".to_string(), // 2% max slippage
        withdraw_sending_funds: false,
        withdraw_receiving_funds: withdraw_to_wallet,
    });

    let estimate = client.estimate_simple_swap(estimate_req).await?.into_inner();

    match &estimate.estimate.unwrap().estimate {
        Some(Estimate::NoLiquidity(_)) => {
            return Err("No liquidity available".into());
        }
        Some(Estimate::NotEnoughBalance(not_enough)) => {
            return Err(format!("Need {} more onchain", not_enough.needed_onchain_sending).into());
        }
        _ => {}
    }

    // 2. Execute
    let swap_req = Request::new(SimpleSwapRequest {
        sending_currency: Some(from_currency),
        receiving_currency: Some(to_currency),
        amount: Some(SwapAmount {
            amount: Some(your_package::swap_amount::Amount::Sending(SendAmount {
                value: amount,
            })),
        }),
        price_change_tolerance: "0.02".to_string(),
        withdraw_sending_funds: false,
        withdraw_receiving_funds: withdraw_to_wallet,
    });

    let response = client.simple_swap(swap_req).await?.into_inner();
    Ok(response.output.unwrap().simple_swap_id)
}

// Example usage
let swap_id = one_click_swap(
    &mut client,
    btc_currency,
    eth_currency,
    "10000000".to_string(), // 0.1 BTC
    true,                    // Auto-withdraw ETH to wallet
).await?;

println!("Swap started: {}", swap_id);
println!("Monitor progress with SubscribeSimpleSwaps");

Monitor swap progress

TypeScript
async function waitForSwapCompletion(
  client: SwapServiceClient,
  swapId: string
): Promise<void> {
  return new Promise((resolve, reject) => {
    const stream = client.subscribeSimpleSwaps(new SubscribeSimpleSwapsRequest(), {})

    stream.on('data', (update) => {
      if (update.getSimpleSwapId() !== swapId) return

      if (update.hasSimpleSwapCompleted()) {
        stream.cancel()
        resolve()
      }

      if (update.hasSimpleSwapError()) {
        stream.cancel()
        reject(new Error(update.getSimpleSwapError()?.getError()))
      }
    })

    stream.on('error', (err) => reject(err))
  })
}

// Usage
try {
  await waitForSwapCompletion(client, swapId)
  console.log('Swap completed successfully!')
} catch (error) {
  console.error('Swap failed:', error.message)
}
Go
func waitForSwapCompletion(
    client pb.SwapServiceClient,
    swapId string,
) error {
    req := &pb.SubscribeSimpleSwapsRequest{}
    stream, err := client.SubscribeSimpleSwaps(context.Background(), req)
    if err != nil {
        return err
    }

    for {
        update, err := stream.Recv()
        if err != nil {
            return err
        }

        if update.GetSimpleSwapId() != swapId {
            continue
        }

        if update.GetSimpleSwapCompleted() != nil {
            return nil
        }

        if swapError := update.GetSimpleSwapError(); swapError != nil {
            return fmt.Errorf("swap error: %s", swapError.GetError())
        }
    }
}

// Usage
err := waitForSwapCompletion(client, swapId)
if err != nil {
    log.Printf("Swap failed: %v", err)
} else {
    log.Println("Swap completed successfully!")
}
Rust
async fn wait_for_swap_completion(
    client: &mut SwapServiceClient<Channel>,
    swap_id: String,
) -> Result<(), Box<dyn std::error::Error>> {
    let request = Request::new(SubscribeSimpleSwapsRequest {});
    let mut stream = client.subscribe_simple_swaps(request).await?.into_inner();

    while let Some(update) = stream.message().await? {
        if update.simple_swap_id != swap_id {
            continue;
        }

        if update.simple_swap_completed.is_some() {
            return Ok(());
        }

        if let Some(error) = update.simple_swap_error {
            return Err(format!("Swap error: {}", error.error).into());
        }
    }

    Err("Stream ended unexpectedly".into())
}

// Usage
match wait_for_swap_completion(&mut client, swap_id).await {
    Ok(_) => println!("Swap completed successfully!"),
    Err(e) => eprintln!("Swap failed: {}", e),
}

Price Change Tolerance

The price_change_tolerance parameter protects you from excessive slippage:

TypeScript
// Formula: (max_tolerated_price - intended_price) / intended_price

// Example: BTC/ETH at 28.5 ETH per BTC
// With 1% tolerance (0.01):
// - Max buy price: 28.5 * 1.01 = 28.785 ETH/BTC
// - Min sell price: 28.5 * 0.99 = 28.215 ETH/BTC

request.setPriceChangeTolerance('0.01') // 1% tolerance
Go
// Formula: (max_tolerated_price - intended_price) / intended_price

// Example: BTC/ETH at 28.5 ETH per BTC
// With 1% tolerance (0.01):
// - Max buy price: 28.5 * 1.01 = 28.785 ETH/BTC
// - Min sell price: 28.5 * 0.99 = 28.215 ETH/BTC

request.PriceChangeTolerance = "0.01" // 1% tolerance
Rust
// Formula: (max_tolerated_price - intended_price) / intended_price

// Example: BTC/ETH at 28.5 ETH per BTC
// With 1% tolerance (0.01):
// - Max buy price: 28.5 * 1.01 = 28.785 ETH/BTC
// - Min sell price: 28.5 * 0.99 = 28.215 ETH/BTC

request.price_change_tolerance = "0.01".to_string(); // 1% tolerance

Recommended values:

  • 0.005 (0.5%) - Very tight, may fail in volatile markets
  • 0.01 (1%) - Standard for most swaps
  • 0.02 (2%) - More lenient for illiquid pairs
  • 0.05 (5%) - Very lenient, use with caution

Best Practices

  1. Always estimate first - Call EstimateSimpleSwap before SimpleSwap
  2. Set reasonable tolerance - 1-2% for most cases, higher for illiquid pairs
  3. Consider withdrawal costs - Withdrawing onchain has fees
  4. Monitor with streaming - Use SubscribeSimpleSwaps for real-time progress
  5. Handle errors gracefully - Simple swaps can fail at multiple stages
  6. Check balance requirements - Ensure sufficient onchain balance for setup
  7. Use Simple Swap for beginners - Regular Swap requires channel management

Error Handling

ErrorDescriptionSolution
NoLiquidityNo orderbook liquidityTry different amount or wait
NotEnoughBalanceInsufficient onchain fundsAdd more funds to wallet
FeesHigherThanAmountSetup costs too highSwap larger amount
RentalTooBigExceeds rental capacityReduce amount or use deposits
Price exceeded toleranceMarket moved too muchIncrease tolerance or retry

← Back to API Reference | Next: Node API →


Copyright © 2025