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

Sending and receiving tokens

Spark supports tokens using the BTKN protocol. The Breez SDK enables you to send and receive these tokens using the standard payments API.

Fetching token balances API docs

Token balances for all tokens currently held in the wallet can be retrieved along with general wallet information. Each token balance includes both the balance amount and the token metadata (identifier, name, ticker, issuer public key, etc.).

Rust
let info = sdk.get_info(GetInfoRequest {
  // ensure_synced: true will ensure the SDK is synced with the Spark network
  // before returning the balance
  ensure_synced: Some(false),
}).await?;

// Token balances are a map of token identifier to balance
let token_balances = info.token_balances;
for (token_id, token_balance) in token_balances {
    info!("Token ID: {}", token_id);
    info!("Balance: {}", token_balance.balance);
    info!("Name: {}", token_balance.token_metadata.name);
    info!("Ticker: {}", token_balance.token_metadata.ticker);
    info!("Decimals: {}", token_balance.token_metadata.decimals);
}
Swift
// ensureSynced: true will ensure the SDK is synced with the Spark network
// before returning the balance
let info = try await sdk.getInfo(
    request: GetInfoRequest(
        ensureSynced: false
    ))

// Token balances are a map of token identifier to balance
let tokenBalances = info.tokenBalances
for (tokenId, tokenBalance) in tokenBalances {
    print("Token ID: \(tokenId)")
    print("Balance: \(tokenBalance.balance)")
    print("Name: \(tokenBalance.tokenMetadata.name)")
    print("Ticker: \(tokenBalance.tokenMetadata.ticker)")
    print("Decimals: \(tokenBalance.tokenMetadata.decimals)")
}
Kotlin
try {
    // ensureSynced: true will ensure the SDK is synced with the Spark network
    // before returning the balance
    val info = sdk.getInfo(GetInfoRequest(false))

    // Token balances are a map of token identifier to balance
    val tokenBalances = info.tokenBalances
    for ((tokenId, tokenBalance) in tokenBalances) {
        println("Token ID: $tokenId")
        println("Balance: ${tokenBalance.balance}")
        println("Name: ${tokenBalance.tokenMetadata.name}")
        println("Ticker: ${tokenBalance.tokenMetadata.ticker}")
        println("Decimals: ${tokenBalance.tokenMetadata.decimals}")
    }
} catch (e: Exception) {
    // handle error
}
Javascript
const info = await sdk.getInfo({
  // ensureSynced: true will ensure the SDK is synced with the Spark network
  // before returning the balance
  ensureSynced: false
})

// Token balances are a map of token identifier to balance
const tokenBalances = info.tokenBalances
for (const [tokenId, tokenBalance] of Object.entries(tokenBalances)) {
  console.log(`Token ID: ${tokenId}`)
  console.log(`Balance: ${tokenBalance.balance}`)
  console.log(`Name: ${tokenBalance.tokenMetadata.name}`)
  console.log(`Ticker: ${tokenBalance.tokenMetadata.ticker}`)
  console.log(`Decimals: ${tokenBalance.tokenMetadata.decimals}`)
}
React Native
const info = await sdk.getInfo({
  // ensureSynced: true will ensure the SDK is synced with the Spark network
  // before returning the balance
  ensureSynced: false
})

// Token balances are a map of token identifier to balance
const tokenBalances = info.tokenBalances
for (const [tokenId, tokenBalance] of Object.entries(tokenBalances)) {
  console.log(`Token ID: ${tokenId}`)
  console.log(`Balance: ${tokenBalance.balance}`)
  console.log(`Name: ${tokenBalance.tokenMetadata.name}`)
  console.log(`Ticker: ${tokenBalance.tokenMetadata.ticker}`)
  console.log(`Decimals: ${tokenBalance.tokenMetadata.decimals}`)
}
Flutter
// ensureSynced: true will ensure the SDK is synced with the Spark network
// before returning the balance
final info = await sdk.getInfo(request: GetInfoRequest(ensureSynced: false));

// Token balances are a map of token identifier to balance
final tokenBalances = info.tokenBalances;
tokenBalances.forEach((tokenId, tokenBalance) {
  print('Token ID: $tokenId');
  print('Balance: ${tokenBalance.balance}');
  print('Name: ${tokenBalance.tokenMetadata.name}');
  print('Ticker: ${tokenBalance.tokenMetadata.ticker}');
  print('Decimals: ${tokenBalance.tokenMetadata.decimals}');
});
Python
try:
    # ensure_synced: True will ensure the SDK is synced with the Spark network
    # before returning the balance
    info = await sdk.get_info(request=GetInfoRequest(ensure_synced=False))

    # Token balances are a map of token identifier to balance
    token_balances = info.token_balances
    for token_id, token_balance in token_balances.items():
        print(f"Token ID: {token_id}")
        print(f"Balance: {token_balance.balance}")
        print(f"Name: {token_balance.token_metadata.name}")
        print(f"Ticker: {token_balance.token_metadata.ticker}")
        print(f"Decimals: {token_balance.token_metadata.decimals}")
except Exception as error:
    logging.error(error)
    raise
Go
ensureSynced := false
info, err := sdk.GetInfo(breez_sdk_spark.GetInfoRequest{
    // EnsureSynced: true will ensure the SDK is synced with the Spark network
    // before returning the balance
    EnsureSynced: &ensureSynced,
})

if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
    return err
}

// Token balances are a map of token identifier to balance
tokenBalances := info.TokenBalances
for tokenId, tokenBalance := range tokenBalances {
    log.Printf("Token ID: %v", tokenId)
    log.Printf("Balance: %v", tokenBalance.Balance)
    log.Printf("Name: %v", tokenBalance.TokenMetadata.Name)
    log.Printf("Ticker: %v", tokenBalance.TokenMetadata.Ticker)
    log.Printf("Decimals: %v", tokenBalance.TokenMetadata.Decimals)
}

Developer note

Token balances are cached for fast responses. For details on ensuring up-to-date balances, see the Fetching the balance section.

Fetching token metadata API docs

Token metadata can be fetched for specific tokens by providing their identifiers. This is especially useful for retrieving metadata for tokens that are not currently held in the wallet. The metadata is cached locally after the first fetch for faster subsequent lookups.

Rust
let response = sdk.get_tokens_metadata(GetTokensMetadataRequest {
    token_identifiers: vec![String::from("<token identifier 1>"), String::from("<token identifier 2>")],
}).await?;

let tokens_metadata = response.tokens_metadata;
for token_metadata in tokens_metadata {
    info!("Token ID: {}", token_metadata.identifier);
    info!("Name: {}", token_metadata.name);
    info!("Ticker: {}", token_metadata.ticker);
    info!("Decimals: {}", token_metadata.decimals);
    info!("Max Supply: {}", token_metadata.max_supply);
    info!("Is Freezable: {}", token_metadata.is_freezable);
}
Swift
let response = try await sdk.getTokensMetadata(
    request: GetTokensMetadataRequest(tokenIdentifiers: [
        "<token identifier 1>", "<token identifier 2>",
    ]))

let tokensMetadata = response.tokensMetadata
for tokenMetadata in tokensMetadata {
    print("Token ID: \(tokenMetadata.identifier)")
    print("Name: \(tokenMetadata.name)")
    print("Ticker: \(tokenMetadata.ticker)")
    print("Decimals: \(tokenMetadata.decimals)")
    print("Max Supply: \(tokenMetadata.maxSupply)")
    print("Is Freezable: \(tokenMetadata.isFreezable)")
}
Kotlin
try {
    val response = 
        sdk.getTokensMetadata(
            GetTokensMetadataRequest(
                tokenIdentifiers = listOf("<token identifier 1>", "<token identifier 2>")
        )
    )   

    val tokensMetadata = response.tokensMetadata
    for (tokenMetadata in tokensMetadata) {
        println("Token ID: ${tokenMetadata.identifier}")
        println("Name: ${tokenMetadata.name}")
        println("Ticker: ${tokenMetadata.ticker}")
        println("Decimals: ${tokenMetadata.decimals}")
        println("Max Supply: ${tokenMetadata.maxSupply}")
        println("Is Freezable: ${tokenMetadata.isFreezable}")
    }
} catch (e: Exception) {
    // handle error
}
Javascript
const response = await sdk.getTokensMetadata({
  tokenIdentifiers: ['<token identifier 1>', '<token identifier 2>']
})

const tokensMetadata = response.tokensMetadata
for (const tokenMetadata of tokensMetadata) {
  console.log(`Token ID: ${tokenMetadata.identifier}`)
  console.log(`Name: ${tokenMetadata.name}`)
  console.log(`Ticker: ${tokenMetadata.ticker}`)
  console.log(`Decimals: ${tokenMetadata.decimals}`)
  console.log(`Max Supply: ${tokenMetadata.maxSupply}`)
  console.log(`Is Freezable: ${tokenMetadata.isFreezable}`)
}
React Native
const response = await sdk.getTokensMetadata({
  tokenIdentifiers: ['<token identifier 1>', '<token identifier 2>']
})

const tokensMetadata = response.tokensMetadata
for (const tokenMetadata of tokensMetadata) {
  console.log(`Token ID: ${tokenMetadata.identifier}`)
  console.log(`Name: ${tokenMetadata.name}`)
  console.log(`Ticker: ${tokenMetadata.ticker}`)
  console.log(`Decimals: ${tokenMetadata.decimals}`)
  console.log(`Max Supply: ${tokenMetadata.maxSupply}`)
  console.log(`Is Freezable: ${tokenMetadata.isFreezable}`)
}
Flutter
final response = await sdk.getTokensMetadata(
  request: GetTokensMetadataRequest(
    tokenIdentifiers: ['<token identifier 1>', '<token identifier 2>']
    )
  );

final tokensMetadata = response.tokensMetadata;
for (final tokenMetadata in tokensMetadata) {
  print('Token ID: $tokenMetadata.identifier');
  print('Name: ${tokenMetadata.name}');
  print('Ticker: ${tokenMetadata.ticker}');
  print('Decimals: ${tokenMetadata.decimals}');
  print('Max Supply: ${tokenMetadata.maxSupply}');
  print('Is Freezable: ${tokenMetadata.isFreezable}');
}
Python
try:
    response = await sdk.get_tokens_metadata(
        request=GetTokensMetadataRequest(
            token_identifiers=["<token identifier 1>", "<token identifier 2>"]
            )
        )

    tokens_metadata = response.tokens_metadata
    for token_metadata in tokens_metadata:
        print(f"Token ID: {token_metadata.identifier}")
        print(f"Name: {token_metadata.name}")
        print(f"Ticker: {token_metadata.ticker}")
        print(f"Decimals: {token_metadata.decimals}")
        print(f"Max Supply: {token_metadata.max_supply}")
        print(f"Is Freezable: {token_metadata.is_freezable}")
except Exception as error:
    logging.error(error)
    raise
Go
tokenIdentifiers := []string{"<token identifier 1>", "<token identifier 2>"}
response, err := sdk.GetTokensMetadata(breez_sdk_spark.GetTokensMetadataRequest{
    TokenIdentifiers: tokenIdentifiers,
})

if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
    return err
}

tokensMetadata := response.TokensMetadata
for _, tokenMetadata := range tokensMetadata {
    log.Printf("Token ID: %v", tokenMetadata.Identifier)
    log.Printf("Name: %v", tokenMetadata.Name)
    log.Printf("Ticker: %v", tokenMetadata.Ticker)
    log.Printf("Decimals: %v", tokenMetadata.Decimals)
    log.Printf("Max Supply: %v", tokenMetadata.MaxSupply)
    log.Printf("Is Freezable: %v", tokenMetadata.IsFreezable)
}

Receiving a token payment API docs

Token payments use the same Spark address as Bitcoin payments - no separate address is required. Your application can retrieve the Spark address as described in the Receiving a payment guide. The payer will use this address to send tokens to the wallet.

Sending a token payment API docs

To send tokens, provide a Spark address as the payment request. The token identifier must be specified in one of two ways:

  1. Using a Spark invoice: If the payee provides a Spark address with an embedded token identifier and amount (a Spark invoice), the SDK automatically extracts and uses those values.
  2. Manual specification: For a plain Spark address without embedded payment details, your application must provide both the token identifier and amount parameters when preparing the payment.

Your application can use the parse functionality to determine if a Spark address contains embedded token payment details before preparing the payment.

The code example below demonstrates manual specification. Follow the standard prepare/send payment flow as described in the Sending a payment guide.

Rust
let payment_request = "<spark address>".to_string();
let token_identifier = Some("<token identifier>".to_string());
// Set the amount of tokens you wish to send
let amount = Some(1_000);

let prepare_response = sdk
    .prepare_send_payment(PrepareSendPaymentRequest {
        payment_request,
        amount,
        token_identifier,
    })
    .await?;

// If the fees are acceptable, continue to send the token payment
if let SendPaymentMethod::SparkAddress { 
    fee,
    token_identifier: token_id,
    .. 
} = &prepare_response.payment_method {
    info!("Token ID: {:?}", token_id);
    info!("Fees: {} token units", fee);
}

// Send the token payment
let send_response = sdk
    .send_payment(SendPaymentRequest {
        prepare_response,
        options: None,
    })
    .await?;
let payment = send_response.payment;
info!("Payment: {payment:?}");
Swift
let paymentRequest = "<spark address>"
// The token identifier (e.g., asset ID or token contract)
let tokenIdentifier = "<token identifier>"
// Set the amount of tokens you wish to send (requires 'import BigNumber')
let amount = BInt(1_000)

let prepareResponse = try await sdk.prepareSendPayment(
    request: PrepareSendPaymentRequest(
        paymentRequest: paymentRequest,
        amount: amount,
        tokenIdentifier: tokenIdentifier
    ))

// If the fees are acceptable, continue to send the token payment
if case let .sparkAddress(address, fee, tokenId) = prepareResponse.paymentMethod {
    print("Token ID: \(String(describing: tokenId))")
    print("Fees: \(fee) sats")
}

// Send the token payment
let sendResponse = try await sdk.sendPayment(
    request: SendPaymentRequest(
        prepareResponse: prepareResponse,
        options: nil
    ))
let payment = sendResponse.payment
print("Payment: \(payment)")
Kotlin
try {
    val paymentRequest = "<spark address>"
    // The token identifier (e.g., asset ID or token contract)
    val tokenIdentifier = "<token identifier>"
    // Set the amount of tokens you wish to send
    // Kotlin MPP (BigInteger from com.ionspin.kotlin.bignum.integer, which is included in package)
    val amount = BigInteger.fromLong(1_000L)
    // Android (BigInteger from java.math)
    // val amount = BigInteger.valueOf(1_000L) // Android (BigInteger from java.math)

    val prepareResponse =
        sdk.prepareSendPayment(
            PrepareSendPaymentRequest(
                paymentRequest = paymentRequest,
                amount = amount,
                tokenIdentifier = tokenIdentifier
            )
        )

    // If the fees are acceptable, continue to send the token payment
    when (val method = prepareResponse.paymentMethod) {
        is SendPaymentMethod.SparkAddress -> {
            println("Token ID: ${method.tokenIdentifier}")
            println("Fees: ${method.fee} sats")
        }
        else -> {}
    }

    // Send the token payment
    val sendResponse =
        sdk.sendPayment(
            SendPaymentRequest(prepareResponse = prepareResponse, options = null)
        )
    val payment = sendResponse.payment
    println("Payment: $payment")
} catch (e: Exception) {
    // handle error
}
Javascript
const paymentRequest = '<spark address>'
// The token identifier (e.g., asset ID or token contract)
const tokenIdentifier = '<token identifier>'
// Set the amount of tokens you wish to send
const amount = BigInt(1_000)

const prepareResponse = await sdk.prepareSendPayment({
  paymentRequest,
  amount,
  tokenIdentifier
})

// If the fees are acceptable, continue to send the token payment
if (prepareResponse.paymentMethod.type === 'sparkAddress') {
  console.log(`Token ID: ${prepareResponse.paymentMethod.tokenIdentifier}`)
  console.log(`Fees: ${prepareResponse.paymentMethod.fee} sats`)
}

// Send the token payment
const sendResponse = await sdk.sendPayment({
  prepareResponse,
  options: undefined
})
const payment = sendResponse.payment
console.log(`Payment: ${JSON.stringify(payment)}`)
React Native
const paymentRequest = '<spark address>'
// The token identifier (e.g., asset ID or token contract)
const tokenIdentifier = '<token identifier>'
// Set the amount of tokens you wish to send
const amount = BigInt(1_000)

const prepareResponse = await sdk.prepareSendPayment({
  paymentRequest,
  amount,
  tokenIdentifier
})

// If the fees are acceptable, continue to send the token payment
if (prepareResponse.paymentMethod instanceof SendPaymentMethod.SparkAddress) {
  console.log(`Token ID: ${prepareResponse.paymentMethod.inner.tokenIdentifier}`)
  console.log(`Fees: ${prepareResponse.paymentMethod.inner.fee} sats`)
}

// Send the token payment
const sendResponse = await sdk.sendPayment({
  prepareResponse,
  options: undefined
})
const payment = sendResponse.payment
console.log(`Payment: ${JSON.stringify(payment)}`)
Flutter
final paymentRequest = '<spark address>';
final tokenIdentifier = '<token identifier>';
// Set the amount of tokens you wish to send
final amount = BigInt.from(1000);

final prepareResponse = await sdk.prepareSendPayment(
  request: PrepareSendPaymentRequest(
    paymentRequest: paymentRequest,
    amount: amount,
    tokenIdentifier: tokenIdentifier,
  ),
);

// If the fees are acceptable, continue to send the token payment
if (prepareResponse.paymentMethod is SendPaymentMethod_SparkAddress) {
  final method = prepareResponse.paymentMethod as SendPaymentMethod_SparkAddress;
  print('Token ID: ${method.tokenIdentifier}');
  print('Fees: ${method.fee} sats');
}

// Send the token payment
final sendResponse = await sdk.sendPayment(
  request: SendPaymentRequest(
    prepareResponse: prepareResponse,
    options: null,
  ),
);
final payment = sendResponse.payment;
print('Payment: $payment');
Python
try:
    payment_request = "<spark address>"
    # The token identifier (e.g., asset ID or token contract)
    token_identifier = "<token identifier>"
    # Set the amount of tokens you wish to send
    amount = 1_000

    prepare_response = await sdk.prepare_send_payment(
        request=PrepareSendPaymentRequest(
            payment_request=payment_request,
            amount=amount,
            token_identifier=token_identifier,
        )
    )

    # If the fees are acceptable, continue to send the token payment
    if isinstance(prepare_response.payment_method, SendPaymentMethod.SPARK_ADDRESS):
        print(f"Token ID: {prepare_response.payment_method.token_identifier}")
        print(f"Fees: {prepare_response.payment_method.fee} sats")

    # Send the token payment
    send_response = await sdk.send_payment(
        request=SendPaymentRequest(
            prepare_response=prepare_response,
            options=None,
        )
    )
    payment = send_response.payment
    print(f"Payment: {payment}")
except Exception as error:
    logging.error(error)
    raise
Go
paymentRequest := "<spark address>"
tokenIdentifier := "<token identifier>"
// Set the amount of tokens you wish to send
amount := new(big.Int).SetInt64(1_000)

prepareResponse, err := sdk.PrepareSendPayment(breez_sdk_spark.PrepareSendPaymentRequest{
    PaymentRequest:  paymentRequest,
    Amount:          &amount,
    TokenIdentifier: &tokenIdentifier,
})

if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
    return err
}

// If the fees are acceptable, continue to send the token payment
switch method := prepareResponse.PaymentMethod.(type) {
case breez_sdk_spark.SendPaymentMethodSparkAddress:
    log.Printf("Token ID: %v", method.TokenIdentifier)
    log.Printf("Fees: %v sats", method.Fee)
}

// Send the token payment
sendResponse, err := sdk.SendPayment(breez_sdk_spark.SendPaymentRequest{
    PrepareResponse: prepareResponse,
    Options:         nil,
})

if sdkErr := err.(*breez_sdk_spark.SdkError); sdkErr != nil {
    return err
}

payment := sendResponse.Payment
log.Printf("Payment: %#v", payment)

Listing token payments API docs

Token payments are included in the regular payment history alongside Bitcoin payments. Your application can retrieve and distinguish token payments from other payment types using the standard payment listing functionality. See the Listing payments guide for more details.