Swap API
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:
| Name | Type | Required | Description |
|---|---|---|---|
sending_currency | OrderbookCurrency | YES | Currency to send |
receiving_currency | OrderbookCurrency | YES | Currency to receive |
amount | SwapAmount | YES | Amount to swap (sending or receiving) |
SwapAmount Object (one of):
| Field | Type | Description |
|---|---|---|
sending | SendAmount | Specify amount to send |
receiving | SendAmount | Specify amount to receive |
Response:
| Field | Type | Description |
|---|---|---|
order_match | OrderMatch | Estimated 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:
| Name | Type | Required | Description |
|---|---|---|---|
sending_currency | OrderbookCurrency | YES | Currency to send |
receiving_currency | OrderbookCurrency | YES | Currency to receive |
amount | SwapAmount | YES | Amount to swap |
Response:
| Field | Type | Description |
|---|---|---|
order_id | string | Swap 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:
| Name | Type | Required | Description |
|---|---|---|---|
sending_currency | OrderbookCurrency | YES | Currency to send |
receiving_currency | OrderbookCurrency | YES | Currency to receive |
amount | SwapAmount | YES | Amount to swap |
price_change_tolerance | DecimalString | YES | Max price slippage (e.g., "0.01" = 1%) |
withdraw_sending_funds | bool | YES | Auto-withdraw sent funds after swap |
withdraw_receiving_funds | bool | YES | Auto-withdraw received funds after swap |
Response:
| Field | Type | Description |
|---|---|---|
estimate | SimpleSwapEstimate | Estimate details (one of multiple variants) |
SimpleSwapEstimate Variants:
Instant
Ready to swap immediately - you have sufficient offchain balance.
| Field | Type | Description |
|---|---|---|
order_match | OrderMatch | Expected execution |
sending_withdrawal_fee | WithdrawalFee | Fee to withdraw sent funds (optional) |
receiving_withdrawal_fee | WithdrawalFee | Fee to withdraw received funds (optional) |
Deferred
Need to set up channels via deposits/rentals first.
| Field | Type | Description |
|---|---|---|
order_match | OrderMatch | Expected execution |
sending_channel_deposit | SendingChannelDeposit | Required deposit (optional) |
receiving_channel_rental | ReceivingChannelRental | Required rental (optional) |
sending_withdrawal_fee | WithdrawalFee | Withdrawal fee (optional) |
receiving_withdrawal_fee | WithdrawalFee | Withdrawal fee (optional) |
DualFundDeferred
Need to dual-fund a channel.
| Field | Type | Description |
|---|---|---|
order_match | OrderMatch | Expected execution |
dual_fund_deposit | DualFundDeposit | Required dual-fund deposit |
sending_withdrawal_fee | WithdrawalFee | Withdrawal fee (optional) |
receiving_withdrawal_fee | WithdrawalFee | Withdrawal fee (optional) |
NotEnoughBalance
Insufficient onchain balance to set up channels.
| Field | Type | Description |
|---|---|---|
needed_onchain_sending | DecimalString | Amount needed onchain |
FeesHigherThanAmount
Setup fees exceed swap amount - not economical.
| Field | Type | Description |
|---|---|---|
sending_fee | DecimalString | Total fee in sending currency |
receiving_fee | DecimalString | Total fee in receiving currency |
RentalTooBig
Rental amount exceeds provider's capacity.
| Field | Type | Description |
|---|---|---|
needed_rental | DecimalString | Required rental amount |
available_rental_liquidity | DecimalString | Available liquidity |
max_rental_capacity | DecimalString | Maximum 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:
| Name | Type | Required | Description |
|---|---|---|---|
sending_currency | OrderbookCurrency | YES | Currency to send |
receiving_currency | OrderbookCurrency | YES | Currency to receive |
amount | SwapAmount | YES | Amount to swap |
price_change_tolerance | DecimalString | YES | Max price slippage |
withdraw_sending_funds | bool | YES | Auto-withdraw sent funds |
withdraw_receiving_funds | bool | YES | Auto-withdraw received funds |
Response:
| Field | Type | Description |
|---|---|---|
output | SimpleSwapOutput | Execution details |
SimpleSwapOutput:
| Field | Type | Description |
|---|---|---|
simple_swap_id | string | Unique 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:
| Field | Type | Description |
|---|---|---|
timestamp | Timestamp | Update time |
simple_swap_id | string | Swap identifier |
update | - | Update type (one of many variants) |
Update Types:
| Update | Description |
|---|---|
funding_sending_channel | Opening/depositing sending channel |
renting_receiving_channel | Renting receiving channel |
dual_funding_channel | Dual-funding channel |
sending_channel_ready | Sending channel active |
receiving_channel_ready | Receiving channel active |
dual_fund_channel_ready | Dual-funded channel active |
waiting_for_balances | Waiting for sufficient balance |
balances_ready | Balances sufficient |
order_created | Swap order placed |
order_completed | Swap executed |
withdrawing_sending_funds | Withdrawing sent funds |
withdrawing_receiving_funds | Withdrawing received funds |
withdrawing_dual_funded_funds | Withdrawing dual-fund |
sending_funds_withdrawn | Send withdrawal complete |
receiving_funds_withdrawn | Receive withdrawal complete |
dual_funded_funds_withdrawn | Dual-fund withdrawal complete |
simple_swap_completed | Swap fully complete |
simple_swap_error | Error 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
- Always estimate first - Call
EstimateSimpleSwapbeforeSimpleSwap - Set reasonable tolerance - 1-2% for most cases, higher for illiquid pairs
- Consider withdrawal costs - Withdrawing onchain has fees
- Monitor with streaming - Use
SubscribeSimpleSwapsfor real-time progress - Handle errors gracefully - Simple swaps can fail at multiple stages
- Check balance requirements - Ensure sufficient onchain balance for setup
- Use Simple Swap for beginners - Regular Swap requires channel management
Error Handling
| Error | Description | Solution |
|---|---|---|
NoLiquidity | No orderbook liquidity | Try different amount or wait |
NotEnoughBalance | Insufficient onchain funds | Add more funds to wallet |
FeesHigherThanAmount | Setup costs too high | Swap larger amount |
RentalTooBig | Exceeds rental capacity | Reduce amount or use deposits |
| Price exceeded tolerance | Market moved too much | Increase tolerance or retry |