The Scanner module is the core component for decoding and parsing various types of Bitcoin and Lightning Network invoices and addresses.
- Bitcoin Address Support
- Decodes multiple address formats (P2PKH, P2SH, P2WPKH, P2WSH, P2TR)
- Processes BIP21 Bitcoin payment URIs
- Network support for Mainnet, Testnet, Regtest, and Signet
- Lightning Network Features
- Decodes BOLT-11 Lightning invoices
- Supports Lightning Addresses
- Handles multiple LNURL types:
- LNURL-pay
- LNURL-withdraw
- LNURL-auth
- LNURL-channel
- Node connection string parsing
- Pubky authentication string handling
- Gift code decoding (format: bitkit://gift-
-)
import BitkitCore
func decodeInvoice() async {
do {
let result = try await Scanner.decode("lnbc500n1ps...")
switch result {
case .lightning(let invoice):
print("Lightning Invoice:")
print("Amount: \(invoice.amountSatoshis) sats")
print("Payment Hash: \(invoice.paymentHash.hex)")
print("Timestamp: \(invoice.timestampSeconds)")
print("Expiry: \(invoice.expirySeconds) seconds")
print("Is Expired: \(invoice.isExpired)")
if let desc = invoice.description {
print("Description: \(desc)")
}
print("Network: \(invoice.networkType)")
if let nodeId = invoice.payeeNodeId {
print("Node ID: \(nodeId.hex)")
}
case .onChain(let invoice):
print("On-chain Invoice:")
print("Address: \(invoice.address)")
print("Amount: \(invoice.amountSatoshis) sats")
if let label = invoice.label {
print("Label: \(label)")
}
if let message = invoice.message {
print("Message: \(message)")
}
if let params = invoice.params {
print("Parameters:")
params.forEach { key, value in
print("\t\(key): \(value)")
}
}
case .lnurlPay(let data):
print("LNURL-pay:")
print("URI: \(data.uri)")
print("Callback: \(data.callback)")
print("Min Sendable: \(data.minSendable) sats")
print("Max Sendable: \(data.maxSendable) sats")
print("Metadata: \(data.metadataStr)")
if let commentLength = data.commentAllowed {
print("Comment allowed (max length): \(commentLength)")
}
print("Allows Nostr: \(data.allowsNostr)")
if let pubkey = data.nostrPubkey {
print("Nostr Pubkey: \(pubkey.hex)")
}
case .lnurlWithdraw(let data):
print("LNURL-withdraw:")
print("URI: \(data.uri)")
print("Callback: \(data.callback)")
print("K1: \(data.k1)")
print("Description: \(data.defaultDescription)")
print("Max Withdrawable: \(data.maxWithdrawable) sats")
if let min = data.minWithdrawable {
print("Min Withdrawable: \(min) sats")
}
print("Tag: \(data.tag)")
case .lnurlAuth(let data):
print("LNURL-auth:")
print("URI: \(data.uri)")
print("Tag: \(data.tag)")
print("K1: \(data.k1)")
case .lnurlChannel(let data):
print("LNURL-channel:")
print("URI: \(data.uri)")
print("Callback: \(data.callback)")
print("K1: \(data.k1)")
print("Tag: \(data.tag)")
case .lnurlAddress(let data):
print("Lightning Address:")
print("URI: \(data.uri)")
print("Username: \(data.username)")
print("Domain: \(data.domain)")
case .nodeId(let url, let network):
print("Node Connection:")
print("URL: \(url)")
print("Network: \(network)")
case .gift(let code, let amount):
print("Gift Code:")
print("Code: \(code)")
print("Amount: \(amount) sats")
case .pubkyAuth(let auth):
print("pubky Auth:")
print("Data: \(auth.data)")
}
} catch let error as DecodingError {
switch error {
case .invalidFormat:
print("Invalid invoice format")
case .invalidNetwork:
print("Invalid network type")
case .invalidAmount:
print("Invalid amount")
case .invalidLNURLPayAmount(let amount, let min, let max):
print("Invalid LNURL pay amount: \(amount) sats (must be between \(min) and \(max) sats)")
case .invalidTimestamp:
print("Invalid timestamp")
case .invalidChecksum:
print("Invalid checksum")
case .invalidResponse:
print("Invalid response")
case .unsupportedType:
print("Unsupported invoice type")
case .invalidAddress:
print("Invalid address")
case .requestFailed:
print("LNURL request failed")
case .clientCreationFailed:
print("Client creation failed")
case .invoiceCreationFailed(let message):
print("Invoice creation failed: \(message)")
}
}
}
import com.synonym.bitkitcore.*
suspend fun decodeInvoice() {
try {
when (val result = Scanner.decode("lnbc500n1ps...")) {
is Scanner.Lightning -> with(result.invoice) {
println("Lightning Invoice:")
println("Amount: $amountSatoshis sats")
println("Payment Hash: ${paymentHash.joinToString("") { "%02x".format(it) }}")
println("Timestamp: $timestampSeconds")
println("Expiry: $expirySeconds seconds")
println("Is Expired: $isExpired")
description?.let { println("Description: $it") }
println("Network: $networkType")
payeeNodeId?.let {
println("Node ID: ${it.joinToString("") { b -> "%02x".format(b) }}")
}
}
is Scanner.OnChain -> with(result.invoice) {
println("On-chain Invoice:")
println("Address: $address")
println("Amount: $amountSatoshis sats")
label?.let { println("Label: $it") }
message?.let { println("Message: $it") }
params?.forEach { (key, value) ->
println("\t$key: $value")
}
}
is Scanner.LnurlPay -> with(result.data) {
println("LNURL-pay:")
println("URI: $uri")
println("Callback: $callback")
println("Min Sendable: $minSendable sats")
println("Max Sendable: $maxSendable sats")
println("Metadata: $metadataStr")
commentAllowed?.let { println("Comment allowed (max length): $it") }
println("Allows Nostr: $allowsNostr")
nostrPubkey?.let {
println("Nostr Pubkey: ${it.joinToString("") { b -> "%02x".format(b) }}")
}
}
is Scanner.LnurlWithdraw -> with(result.data) {
println("LNURL-withdraw:")
println("URI: $uri")
println("Callback: $callback")
println("K1: $k1")
println("Description: $defaultDescription")
println("Max Withdrawable: $maxWithdrawable sats")
minWithdrawable?.let { println("Min Withdrawable: $it sats") }
println("Tag: $tag")
}
is Scanner.LnurlAuth -> with(result.data) {
println("LNURL-auth:")
println("URI: $uri")
println("Tag: $tag")
println("K1: $k1")
}
is Scanner.LnurlChannel -> with(result.data) {
println("LNURL-channel:")
println("URI: $uri")
println("Callback: $callback")
println("K1: $k1")
println("Tag: $tag")
}
is Scanner.LnurlAddress -> with(result.data) {
println("Lightning Address:")
println("URI: $uri")
println("Username: $username")
println("Domain: $domain")
}
is Scanner.NodeId -> {
println("Node Connection:")
println("URL: ${result.url}")
println("Network: ${result.network}")
}
is Scanner.Gift -> {
println("Gift Code:")
println("Code: ${result.code}")
println("Amount: ${result.amount} sats")
}
is Scanner.PubkyAuth -> {
println("pubky Auth:")
println("Data: ${result.auth.data}")
}
}
} catch (e: DecodingError) {
when (e) {
is DecodingError.InvalidFormat -> println("Invalid invoice format")
is DecodingError.InvalidNetwork -> println("Invalid network type")
is DecodingError.InvalidAmount -> println("Invalid amount")
is DecodingError.InvalidLNURLPayAmount -> println(
"Invalid LNURL pay amount: ${e.amount_satoshis} sats " +
"(must be between ${e.min} and ${e.max} sats)"
)
is DecodingError.InvalidTimestamp -> println("Invalid timestamp")
is DecodingError.InvalidChecksum -> println("Invalid checksum")
is DecodingError.InvalidResponse -> println("Invalid response")
is DecodingError.UnsupportedType -> println("Unsupported invoice type")
is DecodingError.InvalidAddress -> println("Invalid address")
is DecodingError.RequestFailed -> println("LNURL request failed")
is DecodingError.ClientCreationFailed -> println("Client creation failed")
is DecodingError.InvoiceCreationFailed -> println("Invoice creation failed: ${e.error_message}")
}
}
}
from bitkitcore import Scanner, DecodingError
try:
result = await Scanner.decode("lnbc500n1ps...")
if isinstance(result, Scanner.Lightning):
print("Lightning Invoice:")
print(f"Amount: {result.invoice.amount_satoshis} sats")
print(f"Payment Hash: {result.invoice.payment_hash.hex()}")
print(f"Timestamp: {result.invoice.timestamp_seconds}")
print(f"Expiry: {result.invoice.expiry_seconds} seconds")
print(f"Is Expired: {result.invoice.is_expired}")
if result.invoice.description:
print(f"Description: {result.invoice.description}")
print(f"Network: {result.invoice.network_type}")
if result.invoice.payee_node_id:
print(f"Node ID: {result.invoice.payee_node_id.hex()}")
elif isinstance(result, Scanner.OnChain):
print("On-chain Invoice:")
print(f"Address: {result.invoice.address}")
print(f"Amount: {result.invoice.amount_satoshis} sats")
if result.invoice.label:
print(f"Label: {result.invoice.label}")
if result.invoice.message:
print(f"Message: {result.invoice.message}")
if result.invoice.params:
print("Parameters:")
for key, value in result.invoice.params.items():
print(f"\t{key}: {value}")
elif isinstance(result, Scanner.LnurlPay):
print("LNURL-pay:")
print(f"URI: {result.data.uri}")
print(f"Callback: {result.data.callback}")
print(f"Min Sendable: {result.data.min_sendable} sats")
print(f"Max Sendable: {result.data.max_sendable} sats")
print(f"Metadata: {result.data.metadata_str}")
if result.data.comment_allowed:
print(f"Comment allowed (max length): {result.data.comment_allowed}")
print(f"Allows Nostr: {result.data.allows_nostr}")
if result.data.nostr_pubkey:
print(f"Nostr Pubkey: {result.data.nostr_pubkey.hex()}")
elif isinstance(result, Scanner.LnurlWithdraw):
print("LNURL-withdraw:")
print(f"URI: {result.data.uri}")
print(f"Callback: {result.data.callback}")
print(f"K1: {result.data.k1}")
print(f"Description: {result.data.default_description}")
print(f"Max Withdrawable: {result.data.max_withdrawable} sats")
if result.data.min_withdrawable:
print(f"Min Withdrawable: {result.data.min_withdrawable} sats")
print(f"Tag: {result.data.tag}")
elif isinstance(result, Scanner.LnurlAuth):
print("LNURL-auth:")
print(f"URI: {result.data.uri}")
print(f"Tag: {result.data.tag}")
print(f"K1: {result.data.k1}")
elif isinstance(result, Scanner.LnurlChannel):
print("LNURL-channel:")
print(f"URI: {result.data.uri}")
print(f"Callback: {result.data.callback}")
print(f"K1: {result.data.k1}")
print(f"Tag: {result.data.tag}")
elif isinstance(result, Scanner.LnurlAddress):
print("Lightning Address:")
print(f"URI: {result.data.uri}")
print(f"Username: {result.data.username}")
print(f"Domain: {result.data.domain}")
elif isinstance(result, Scanner.NodeId):
print("Node Connection:")
print(f"URL: {result.url}")
print(f"Network: {result.network}")
elif isinstance(result, Scanner.Gift):
print("Gift Code:")
print(f"Code: {result.code}")
print(f"Amount: {result.amount} sats")
elif isinstance(result, Scanner.PubkyAuth):
print("pubky Auth:")
print(f"Data: {result.auth.data}")
except DecodingError as e:
if isinstance(e, DecodingError.InvalidFormat):
print("Invalid invoice format")
elif isinstance(e, DecodingError.InvalidNetwork):
print("Invalid network type")
elif isinstance(e, DecodingError.InvalidAmount):
print("Invalid amount")
elif isinstance(e, DecodingError.InvalidLNURLPayAmount):
print(f"Invalid LNURL pay amount: {e.amount_satoshis} sats " +
f"(must be between {e.min} and {e.max} sats)")
elif isinstance(e, DecodingError.InvalidTimestamp):
print("Invalid timestamp")
elif isinstance(e, DecodingError.InvalidChecksum):
print("Invalid checksum")
elif isinstance(e, DecodingError.InvalidResponse):
print("Invalid response")
elif isinstance(e, DecodingError.UnsupportedType):
print("Unsupported invoice type")
elif isinstance(e, DecodingError.InvalidAddress):
print("Invalid address")
elif isinstance(e, DecodingError.RequestFailed):
print("LNURL request failed")
elif isinstance(e, DecodingError.ClientCreationFailed):
print("Client creation failed")
elif isinstance(e, DecodingError.InvoiceCreationFailed):
print(f"Invoice creation failed: {e.error_message}")
The Scanner can decode:
- Lightning Network BOLT-11 invoices
- Bitcoin addresses (P2PKH, P2SH, P2WPKH, P2WSH, P2TR)
- LNURL-pay requests
- LNURL-withdraw requests
- LNURL-auth requests
- LNURL-channel requests
- Lightning Addresses
- BIP21 Bitcoin URIs
- Pubky authentication strings
- Gift codes (format: bitkit://gift-
-)
The module uses the following error types:
InvalidFormat: The input string format is invalid
InvalidNetwork: The network type is invalid or mismatched
InvalidAmount: The amount specified is invalid
InvalidTimestamp: The timestamp is invalid
InvalidChecksum: The checksum verification failed
InvalidResponse: Received an invalid response
UnsupportedType: The invoice type is not supported
InvalidAddress: The address format is invalid
RequestFailed: The LNURL request failed
ClientCreationFailed: Failed to create the client
InvoiceCreationFailed: Failed to create the invoice