Keyboard shortcuts

Press โ† or โ†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Buying Bitcoin

The Breez SDK allows users to purchase Bitcoin through external providers. Two providers are currently supported: MoonPay and CashApp. The user is directed to a provider URL in their browser (or the CashApp app) to complete the purchase, and the funds are deposited directly into their wallet. Buying Bitcoin is only available on mainnet.

MoonPay

MoonPay supports fiat purchases via credit card, Apple Pay, Google Pay, and other payment methods. The request's deliverydeliverydeliverydeliverydeliverydeliverydeliveryDeliveryDelivery chooses how the purchase is delivered: as Bitcoin to an on-chain deposit address, or as USDC on Solana. Left unset, it is delivered as Bitcoin. Either way, the SDK returns a URL to open in a browser, where the user completes the purchase.

Bitcoin delivery

MoonPay sends Bitcoin to an on-chain deposit address. The amount is optional. When set, it prefills the purchase.

Rust
// Optionally, prefill the purchase amount
let optional_amount_sat = Some(100_000);
// Optionally, set a redirect URL for after the purchase is completed
let optional_redirect_url = Some("https://example.com/purchase-complete".to_string());

let request = BuyBitcoinRequest::Moonpay {
    delivery: Some(MoonpayDelivery::Bitcoin {
        amount_sat: optional_amount_sat,
    }),
    redirect_url: optional_redirect_url,
};

let response = sdk.buy_bitcoin(request).await?;
info!("Open this URL in a browser to complete the purchase:");
info!("{}", response.url);
Swift
// Optionally, prefill the purchase amount
let optionalAmountSat: UInt64? = 100_000
// Optionally, set a redirect URL for after the purchase is completed
let optionalRedirectUrl: String? = "https://example.com/purchase-complete"

let request = BuyBitcoinRequest.moonpay(
    delivery: .bitcoin(amountSat: optionalAmountSat),
    redirectUrl: optionalRedirectUrl
)

let response = try await sdk.buyBitcoin(request: request)
print("Open this URL in a browser to complete the purchase:")
print("\(response.url)")
Kotlin
// Optionally, prefill the purchase amount
val optionalAmountSat: ULong? = 100_000u
// Optionally, set a redirect URL for after the purchase is completed
val optionalRedirectUrl: String? = "https://example.com/purchase-complete"

val request = BuyBitcoinRequest.Moonpay(
    delivery = MoonpayDelivery.Bitcoin(amountSat = optionalAmountSat),
    redirectUrl = optionalRedirectUrl
)

val response = sdk.buyBitcoin(request)
// Log.v("Breez", "Open this URL in a browser to complete the purchase:")
// Log.v("Breez", "${response.url}")
C#
// Optionally, prefill the purchase amount
ulong? optionalAmountSat = 100_000;
// Optionally, set a redirect URL for after the purchase is completed
var optionalRedirectUrl = "https://example.com/purchase-complete";

var request = new BuyBitcoinRequest.Moonpay(
    delivery: new MoonpayDelivery.Bitcoin(amountSat: optionalAmountSat),
    redirectUrl: optionalRedirectUrl
);

var response = await sdk.BuyBitcoin(request: request);
Console.WriteLine("Open this URL in a browser to complete the purchase:");
Console.WriteLine($"{response.url}");
Javascript
// Optionally, prefill the purchase amount
const optionalAmountSat = 100_000
// Optionally, set a redirect URL for after the purchase is completed
const optionalRedirectUrl = 'https://example.com/purchase-complete'

const response = await sdk.buyBitcoin({
  type: 'moonpay',
  delivery: {
    type: 'bitcoin',
    amountSat: optionalAmountSat
  },
  redirectUrl: optionalRedirectUrl
})
console.log('Open this URL in a browser to complete the purchase:')
console.log(response.url)
React Native
// Optionally, prefill the purchase amount
const optionalAmountSat = BigInt(100_000)
// Optionally, set a redirect URL for after the purchase is completed
const optionalRedirectUrl = 'https://example.com/purchase-complete'

const request = new BuyBitcoinRequest.Moonpay({
  delivery: new MoonpayDelivery.Bitcoin({ amountSat: optionalAmountSat }),
  redirectUrl: optionalRedirectUrl
})

const response = await sdk.buyBitcoin(request)
console.log('Open this URL in a browser to complete the purchase:')
console.log(response.url)
Flutter
// Optionally, prefill the purchase amount
BigInt? optionalAmountSat = BigInt.from(100000);
// Optionally, set a redirect URL for after the purchase is completed
String? optionalRedirectUrl = "https://example.com/purchase-complete";

final request = BuyBitcoinRequest_Moonpay(
  delivery: MoonpayDelivery_Bitcoin(amountSat: optionalAmountSat),
  redirectUrl: optionalRedirectUrl,
);

final response = await sdk.buyBitcoin(request: request);
print("Open this URL in a browser to complete the purchase:");
print(response.url);
Python
# Optionally, prefill the purchase amount
optional_amount_sat = 100_000
# Optionally, set a redirect URL for after the purchase is completed
optional_redirect_url = "https://example.com/purchase-complete"

try:
    request = BuyBitcoinRequest.MOONPAY(
        delivery=MoonpayDelivery.BITCOIN(
            amount_sat=optional_amount_sat,
        ),
        redirect_url=optional_redirect_url,
    )

    response = await sdk.buy_bitcoin(request=request)
    logging.debug("Open this URL in a browser to complete the purchase:")
    logging.debug(response.url)
except Exception as error:
    logging.error(error)
    raise
Go
// Optionally, prefill the purchase amount
optionalAmountSat := uint64(100_000)
// Optionally, set a redirect URL for after the purchase is completed
optionalRedirectUrl := "https://example.com/purchase-complete"

var delivery breez_sdk_spark.MoonpayDelivery = breez_sdk_spark.MoonpayDeliveryBitcoin{
	AmountSat: &optionalAmountSat,
}
request := breez_sdk_spark.BuyBitcoinRequestMoonpay{
	Delivery:    &delivery,
	RedirectUrl: &optionalRedirectUrl,
}

response, err := sdk.BuyBitcoin(request)
if err != nil {
	return err
}

log.Printf("Open this URL in a browser to complete the purchase:")
log.Printf("%v", response.Url)

USDC on Solana delivery

MoonPay sends USDC on Solana, which settles much faster than on-chain Bitcoin. The USDC is converted on arrival and received as a Spark payment: as the token when a stable balance is active, otherwise as Bitcoin. This requires USDC/USDT receives to be enabled with cross_chain_configcross_chain_configcrossChainConfigcrossChainConfigcrossChainConfigcrossChainConfigcrossChainConfigCrossChainConfigCrossChainConfig.

The amountamountamountamountamountamountamountAmountAmount is in USD at 6 decimals, so 1_000_000 is $1. By default this is the amount received, and the conversion fee is added on top of what MoonPay buys. With fee_modefee_modefeeModefeeModefeeModefeeModefeeModeFeeModeFeeMode set to CrossChainFeeMode::FeesIncludedCrossChainFeeMode.FEES_INCLUDEDCrossChainFeeMode.feesIncludedCrossChainFeeMode.FeesIncludedCrossChainFeeMode.FeesIncludedCrossChainFeeMode.FeesIncludedCrossChainFeeMode.FeesIncludedCrossChainFeeModeFeesIncludedCrossChainFeeMode.FeesIncluded the amount is what MoonPay buys and the fee is deducted from it.

Rust
// USD amount to receive, in 6-decimal base units ($50)
let amount = 50_000_000;

let request = BuyBitcoinRequest::Moonpay {
    delivery: Some(MoonpayDelivery::CrossChain {
        amount,
        fee_mode: None,
    }),
    redirect_url: None,
};

let response = sdk.buy_bitcoin(request).await?;
info!("Open this URL in a browser to complete the purchase:");
info!("{}", response.url);

if let Some(info) = response.cross_chain_info {
    info!("USDC to buy: {}", info.deposit_amount);
    info!(
        "Expected to receive: {} {}",
        info.expected_received_amount, info.destination_asset
    );
    info!("Conversion fee: {}", info.service_fee_amount);
}
Swift
// USD amount to receive, in 6-decimal base units ($50)
let amount = BInt(50_000_000)

let request = BuyBitcoinRequest.moonpay(
    delivery: .crossChain(amount: amount, feeMode: nil),
    redirectUrl: nil
)

let response = try await sdk.buyBitcoin(request: request)
print("Open this URL in a browser to complete the purchase:")
print("\(response.url)")

if let info = response.crossChainInfo {
    print("USDC to buy: \(info.depositAmount)")
    print(
        "Expected to receive: \(info.expectedReceivedAmount) "
            + "\(info.destinationAsset)"
    )
    print("Conversion fee: \(info.serviceFeeAmount)")
}
Kotlin
// USD amount to receive, in 6-decimal base units ($50)
val amount = BigInteger.fromLong(50_000_000L)

val request = BuyBitcoinRequest.Moonpay(
    delivery = MoonpayDelivery.CrossChain(
        amount = amount,
        feeMode = null
    ),
    redirectUrl = null
)

val response = sdk.buyBitcoin(request)
// Log.v("Breez", "Open this URL in a browser to complete the purchase:")
// Log.v("Breez", "${response.url}")

response.crossChainInfo?.let { info ->
    // Log.v("Breez", "USDC to buy: ${info.depositAmount}")
    // Log.v("Breez", "Expected to receive: ${info.expectedReceivedAmount} " +
    //     "${info.destinationAsset}")
    // Log.v("Breez", "Conversion fee: ${info.serviceFeeAmount}")
}
C#
// USD amount to receive, in 6-decimal base units ($50)
var amount = new BigInteger(50_000_000);

var request = new BuyBitcoinRequest.Moonpay(
    delivery: new MoonpayDelivery.CrossChain(
        amount: amount,
        feeMode: null
    ),
    redirectUrl: null
);

var response = await sdk.BuyBitcoin(request: request);
Console.WriteLine("Open this URL in a browser to complete the purchase:");
Console.WriteLine($"{response.url}");

if (response.crossChainInfo is { } info)
{
    Console.WriteLine($"USDC to buy: {info.depositAmount}");
    Console.WriteLine(
        "Expected to receive: "
            + $"{info.expectedReceivedAmount} {info.destinationAsset}"
    );
    Console.WriteLine($"Conversion fee: {info.serviceFeeAmount}");
}
Javascript
// USD amount to receive, in 6-decimal base units ($50)
const amount = BigInt(50_000_000)

const response = await sdk.buyBitcoin({
  type: 'moonpay',
  delivery: {
    type: 'crossChain',
    amount,
    feeMode: undefined
  },
  redirectUrl: undefined
})
console.log('Open this URL in a browser to complete the purchase:')
console.log(response.url)

if (response.crossChainInfo !== undefined) {
  const info = response.crossChainInfo
  console.log(`USDC to buy: ${info.depositAmount}`)
  console.log(
    `Expected to receive: ${info.expectedReceivedAmount} ${info.destinationAsset}`
  )
  console.log(`Conversion fee: ${info.serviceFeeAmount}`)
}
React Native
// USD amount to receive, in 6-decimal base units ($50)
const amount = BigInt(50_000_000)

const request = new BuyBitcoinRequest.Moonpay({
  delivery: new MoonpayDelivery.CrossChain({ amount, feeMode: undefined }),
  redirectUrl: undefined
})

const response = await sdk.buyBitcoin(request)
console.log('Open this URL in a browser to complete the purchase:')
console.log(response.url)

if (response.crossChainInfo !== undefined) {
  const info = response.crossChainInfo
  console.log(`USDC to buy: ${info.depositAmount}`)
  console.log(
    `Expected to receive: ${info.expectedReceivedAmount} ${info.destinationAsset}`
  )
  console.log(`Conversion fee: ${info.serviceFeeAmount}`)
}
Flutter
// USD amount to receive, in 6-decimal base units ($50)
final amount = BigInt.from(50000000);

final request = BuyBitcoinRequest_Moonpay(
  delivery: MoonpayDelivery_CrossChain(amount: amount, feeMode: null),
  redirectUrl: null,
);

final response = await sdk.buyBitcoin(request: request);
print("Open this URL in a browser to complete the purchase:");
print(response.url);

final info = response.crossChainInfo;
if (info != null) {
  print("USDC to buy: ${info.depositAmount}");
  print(
    "Expected to receive: "
    "${info.expectedReceivedAmount} ${info.destinationAsset}",
  );
  print("Conversion fee: ${info.serviceFeeAmount}");
}
Python
# USD amount to receive, in 6-decimal base units ($50)
amount = 50_000_000

try:
    request = BuyBitcoinRequest.MOONPAY(
        delivery=MoonpayDelivery.CROSS_CHAIN(
            amount=amount,
            fee_mode=None,
        ),
        redirect_url=None,
    )

    response = await sdk.buy_bitcoin(request=request)
    logging.debug("Open this URL in a browser to complete the purchase:")
    logging.debug(response.url)

    info = response.cross_chain_info
    if info is not None:
        logging.debug(f"USDC to buy: {info.deposit_amount}")
        logging.debug(
            f"Expected to receive: {info.expected_received_amount} "
            f"{info.destination_asset}"
        )
        logging.debug(f"Conversion fee: {info.service_fee_amount}")
except Exception as error:
    logging.error(error)
    raise
Go
// USD amount to receive, in 6-decimal base units ($50)
amount := new(big.Int).SetInt64(50_000_000)

var delivery breez_sdk_spark.MoonpayDelivery = breez_sdk_spark.MoonpayDeliveryCrossChain{
	Amount:  amount,
	FeeMode: nil,
}
request := breez_sdk_spark.BuyBitcoinRequestMoonpay{
	Delivery:    &delivery,
	RedirectUrl: nil,
}

response, err := sdk.BuyBitcoin(request)
if err != nil {
	return err
}

log.Printf("Open this URL in a browser to complete the purchase:")
log.Printf("%v", response.Url)

if info := response.CrossChainInfo; info != nil {
	log.Printf("USDC to buy: %v", info.DepositAmount)
	log.Printf(
		"Expected to receive: %v %s",
		info.ExpectedReceivedAmount, info.DestinationAsset,
	)
	log.Printf("Conversion fee: %v", info.ServiceFeeAmount)
}

The response's cross_chain_infocross_chain_infocrossChainInfocrossChainInfocrossChainInfocrossChainInfocrossChainInfoCrossChainInfoCrossChainInfo describes the quote. Use it to show the user how much USDC MoonPay buys (deposit_amountdeposit_amountdepositAmountdepositAmountdepositAmountdepositAmountdepositAmountDepositAmountDepositAmount), what they can expect to receive (expected_received_amountexpected_received_amountexpectedReceivedAmountexpectedReceivedAmountexpectedReceivedAmountexpectedReceivedAmountexpectedReceivedAmountExpectedReceivedAmountExpectedReceivedAmount in destination_assetdestination_assetdestinationAssetdestinationAssetdestinationAssetdestinationAssetdestinationAssetDestinationAssetDestinationAsset), and the conversion fee (service_fee_amountservice_fee_amountserviceFeeAmountserviceFeeAmountserviceFeeAmountserviceFeeAmountserviceFeeAmountServiceFeeAmountServiceFeeAmount). MoonPay's own fees are shown in its checkout.

These figures are estimates. The amount is a prefill the user can change in MoonPay, and a purchase completed after the quote expires is converted at the live rate. See Quote expiry. MoonPay also enforces its own minimum order, which the user sees in the checkout.

Developer note

MoonPay supports Apple Pay and Google Pay, but these payment methods will not work inside an iframe or standard web view. To ensure compatibility:
  • iOS: Open the URL using SFSafariViewController.
  • Android: Open the URL using Chrome Custom Tabs.
  • Desktop: Apple Pay requires Safari; Google Pay requires Chrome.

CashApp

CashApp uses Lightning (bolt11 invoices) to receive purchased funds. The caller specifies the amount in satoshis; the SDK generates a bolt11 invoice for that amount and returns a CashApp deep link (cash.app/launch/lightning/...) that opens CashApp so the user can complete payment.

Developer note

The amount is required. With an amountless invoice, Cash App only lets the payer fund from their existing Cash App BTC balance. When the invoice carries an amount, Cash App opens up funding via fiat balance and debit card.

To initiate a Bitcoin purchase via CashApp:

Rust
// Cash App requires the amount to be specified up front.
let amount_sats = 50_000;

let request = BuyBitcoinRequest::CashApp { amount_sats };

let response = sdk.buy_bitcoin(request).await?;
info!("Open this URL in Cash App to complete the purchase:");
info!("{}", response.url);
Swift
// Cash App requires the amount to be specified up front.
let amountSats: UInt64 = 50_000

let request = BuyBitcoinRequest.cashApp(amountSats: amountSats)

let response = try await sdk.buyBitcoin(request: request)
print("Open this URL in Cash App to complete the purchase:")
print("\(response.url)")
Kotlin
// Cash App requires the amount to be specified up front.
val amountSats: ULong = 50_000u

val request = BuyBitcoinRequest.CashApp(amountSats = amountSats)

val response = sdk.buyBitcoin(request)
// Log.v("Breez", "Open this URL in Cash App to complete the purchase:")
// Log.v("Breez", "${response.url}")
C#
// Cash App requires the amount to be specified up front.
var amountSats = (ulong)50_000;

var request = new BuyBitcoinRequest.CashApp(
    amountSats: amountSats
);

var response = await sdk.BuyBitcoin(request: request);
Console.WriteLine("Open this URL in Cash App to complete the purchase:");
Console.WriteLine($"{response.url}");
Javascript
// Cash App requires the amount to be specified up front.
const amountSats = 50_000

const response = await sdk.buyBitcoin({
  type: 'cashApp',
  amountSats
})
console.log('Open this URL in Cash App to complete the purchase:')
console.log(response.url)
React Native
// Cash App requires the amount to be specified up front.
const amountSats = BigInt(50_000)

const request = new BuyBitcoinRequest.CashApp({
  amountSats
})

const response = await sdk.buyBitcoin(request)
console.log('Open this URL in Cash App to complete the purchase:')
console.log(response.url)
Flutter
// Cash App requires the amount to be specified up front.
final amountSats = BigInt.from(50000);

final request = BuyBitcoinRequest_CashApp(amountSats: amountSats);

final response = await sdk.buyBitcoin(request: request);
print("Open this URL in Cash App to complete the purchase:");
print(response.url);
Python
# Cash App requires the amount to be specified up front.
amount_sats = 50_000

try:
    request = BuyBitcoinRequest.CASH_APP(
        amount_sats=amount_sats,
    )

    response = await sdk.buy_bitcoin(request=request)
    logging.debug("Open this URL in Cash App to complete the purchase:")
    logging.debug(response.url)
except Exception as error:
    logging.error(error)
    raise
Go
// Cash App requires the amount to be specified up front.
amountSats := uint64(50_000)

request := breez_sdk_spark.BuyBitcoinRequestCashApp{
	AmountSats: amountSats,
}

response, err := sdk.BuyBitcoin(request)
if err != nil {
	return err
}

log.Printf("Open this URL in Cash App to complete the purchase:")
log.Printf("%v", response.Url)

The returned URL is a CashApp universal link (https://cash.app/launch/lightning/<bolt11>). On devices with CashApp installed it opens the app directly; otherwise it falls back to the CashApp website.

  1. Collect a non-zero amount before calling buy_bitcoinbuy_bitcoinbuyBitcoinbuyBitcoinbuyBitcoinbuyBitcoinbuyBitcoinBuyBitcoinBuyBitcoin.
  2. On mobile, redirect to the returned URL. On desktop, render it as a QR code and dismiss when SdkEvent::PaymentSucceededSdkEvent.PAYMENT_SUCCEEDEDSdkEvent.paymentSucceededSdkEvent.PaymentSucceededSdkEvent.PaymentSucceededSdkEvent.PaymentSucceededSdkEvent.PaymentSucceededSdkEventPaymentSucceededSdkEvent.PaymentSucceeded fires for the invoice.

On web, window.open() called after await sdk.buyBitcoin(...) is typically blocked by mobile browsers and PWAs because it falls outside the original user gesture. Pre-open a blank tab synchronously inside the click handler, then navigate it once the URL is ready:

// Open a placeholder tab during the user gesture so the browser
// allows it; we navigate it once the SDK returns.
const newTab = window.open('', '_blank');

// Generate the Cash App invoice for the chosen amount.
const response = await sdk.buyBitcoin({ type: 'cashApp', amountSats: 50_000 });

// Send the user to Cash App. If the placeholder was blocked, redirect
// the current tab. The OS opens Cash App via the universal link.
if (newTab) {
  newTab.location.href = response.url;
} else {
  window.location.href = response.url;
}

Platform-specific guidance

PlatformBehaviorRecommendation
Desktop browsersPre-opened tab works reliably; most desktops won't have CashApp installedRender the CashApp URL as a QR for the user to scan on their phone
Mobile browserswindow.open may be blocked after asyncPre-open a tab (see above); falls back to location.href automatically
PWA (standalone)window.open is almost always blockedSame-tab redirect via location.href; opens system browser, which hands off to CashApp
iOS (native)Universal link triggers CashApp if installedOpen via UIApplication.open() or SFSafariViewController
Android (native)Universal link triggers CashApp if installedOpen via Intent or Chrome Custom Tabs

CashApp availability: US and UK only (excluding New York State for Bitcoin/Lightning features). CashApp handles region restrictions on their end, so no client-side gating is needed.