Documentation Index
Fetch the complete documentation index at: https://mintlify.com/satsigner/satsigner/llms.txt
Use this file to discover all available pages before exploring further.
SatSigner implements BIP329, the standard for exporting and importing wallet labels, enabling comprehensive organization of your Bitcoin transactions, addresses, and UTXOs.
BIP329 Overview
What is BIP329?
Bitcoin Improvement Proposal 329 defines a standardized format for wallet labels, enabling:
- Portability - Move labels between wallets
- Backup - Export labels separately from wallet
- Privacy - Keep labels separate from blockchain
- Interoperability - Share labels across wallet software
- Organization - Maintain transaction context
Label Types
SatSigner supports all BIP329 label types:
type LabelType =
| 'tx' // Transaction labels
| 'addr' // Address labels
| 'pubkey' // Public key labels
| 'input' // Input labels
| 'output' // Output labels (UTXOs)
| 'xpub' // Extended public key labels
type Label = {
type: LabelType
ref: string // Reference (txid, address, etc.)
label: string // User-defined label text
// Optional metadata
spendable?: boolean // Mark as spendable/frozen
fee?: number // Transaction fee
fmv?: Prices // Fair market value
height?: number // Block height
keypath?: string // Derivation path
origin?: string // Source description
time?: Date // Timestamp
value?: number // Amount in sats
}
Label Management
Creating Labels
Address Labels
// Label an address
setAddrLabel(
accountId,
"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
"Customer Payment - Invoice #1234"
)
// Label is stored
const label = {
type: 'addr',
ref: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq',
label: 'Customer Payment - Invoice #1234',
spendable: true
}
Address Label Inheritance:
When you label an address, the label automatically cascades:
// Label address
setAddrLabel(accountId, address, "Donation Address")
// Automatically inherits to:
// 1. All transactions to this address
// 2. All UTXOs at this address
// Unless they already have their own labels
Transaction Labels
// Label a transaction
setTxLabel(
accountId,
"1a2b3c4d5e6f7g8h9i0j...",
"Equipment Purchase - Office Supplies"
)
// Transaction label inheritance
const label = {
type: 'tx',
ref: '1a2b3c4d5e6f7g8h9i0j...',
label: 'Equipment Purchase - Office Supplies'
}
// Inherits to:
// - All outputs (if unlabeled)
// - All input previous outputs (if unlabeled)
// - Recipient addresses (if unlabeled)
Transaction Label Cascading:
// When you label a transaction:
setTxLabel(accountId, txid, "Payroll - March 2024")
// Unlabeled outputs inherit the label
for (const output of tx.vout) {
if (!hasLabel(output.address)) {
setAddrLabel(accountId, output.address, "Payroll - March 2024")
}
const utxoRef = `${txid}:${output.index}`
if (!hasLabel(utxoRef)) {
setUtxoLabel(accountId, txid, output.index, "Payroll - March 2024")
}
}
// Unlabeled inputs inherit the label
for (const input of tx.vin) {
const inputRef = `${input.previousOutput.txid}:${input.previousOutput.vout}`
if (!hasLabel(inputRef)) {
setUtxoLabel(
accountId,
input.previousOutput.txid,
input.previousOutput.vout,
"Payroll - March 2024"
)
}
}
UTXO Labels
// Label a specific UTXO (output)
setUtxoLabel(
accountId,
"1a2b3c4d5e6f7g8h9i0j...", // txid
0, // vout
"Large Deposit - Client ABC"
)
// UTXO label format
const label = {
type: 'output',
ref: '1a2b3c4d5e6f7g8h9i0j...:0', // txid:vout
label: 'Large Deposit - Client ABC',
spendable: true
}
// UTXO label inheritance
// Inherits to:
// - Parent transaction (if unlabeled)
// - Output address (if unlabeled)
Label Hierarchy
Label inheritance follows specificity:
Most Specific (highest priority)
↓
UTXO Label (output:txid:vout)
↓
Address Label (addr:address)
↓
Transaction Label (tx:txid)
↓
No Label
Example:
// Address labeled "Savings"
setAddrLabel(accountId, address, "Savings")
// Transaction to that address labeled "Bonus"
setTxLabel(accountId, txid, "Bonus")
// Specific UTXO labeled "Q1 2024 Bonus"
setUtxoLabel(accountId, txid, vout, "Q1 2024 Bonus")
// Display priority:
// UTXO shows: "Q1 2024 Bonus" (most specific)
// Transaction shows: "Bonus"
// Address shows: "Savings"
Label Export/Import
SatSigner supports three BIP329 export formats:
JSONL (Recommended)
JSON Lines format - one JSON object per line:
{"type":"tx","ref":"1a2b3c4d5e6f...","label":"Payment to Vendor A"}
{"type":"addr","ref":"bc1qar0srrr7xfkvy...","label":"Cold Storage"}
{"type":"output","ref":"1a2b3c4d:0","label":"Large Deposit","spendable":true}
Benefits:
- Streamable
- Easy to process line-by-line
- Human readable
- Append-friendly
JSON
Standard JSON array:
[
{
"type": "tx",
"ref": "1a2b3c4d5e6f7g8h9i0j...",
"label": "Payment to Vendor A"
},
{
"type": "addr",
"ref": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
"label": "Cold Storage"
},
{
"type": "output",
"ref": "1a2b3c4d5e6f7g8h9i0j...:0",
"label": "Large Deposit",
"spendable": true
}
]
Benefits:
- Standard format
- Easy to parse
- Tool-friendly
CSV
Comma-separated values:
type,ref,spendable,label
tx,1a2b3c4d5e6f7g8h9i0j...,true,"Payment to Vendor A"
addr,bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq,true,"Cold Storage"
output,1a2b3c4d5e6f7g8h9i0j...:0,true,"Large Deposit"
Benefits:
- Spreadsheet compatible
- Excel/Sheets friendly
- Simple format
Exporting Labels
// Export all account labels
function exportAccountLabels(
account: Account,
format: 'JSONL' | 'JSON' | 'CSV'
): string {
// Collect all labels
const labels = formatAccountLabels(account)
// Export in selected format
switch (format) {
case 'JSONL':
return labelsToJSONL(labels)
case 'JSON':
return labelsToJSON(labels)
case 'CSV':
return labelsToCSV(labels)
}
}
// Usage
const jsonlExport = exportAccountLabels(account, 'JSONL')
saveToFile('labels.jsonl', jsonlExport)
const csvExport = exportAccountLabels(account, 'CSV')
saveToFile('labels.csv', csvExport)
Importing Labels
// Import labels from file
function importLabelsFromFile(
accountId: string,
fileContent: string,
format: 'JSONL' | 'JSON' | 'CSV'
): number {
// Parse based on format
let labels: Label[]
switch (format) {
case 'JSONL':
labels = JSONLtoLabels(fileContent)
break
case 'JSON':
labels = JSONtoLabels(fileContent)
break
case 'CSV':
labels = CSVtoLabels(fileContent)
break
}
// Import to account
const importedCount = importLabels(accountId, labels)
return importedCount
}
// Usage
const fileContent = readFile('labels.jsonl')
const count = importLabelsFromFile(accountId, fileContent, 'JSONL')
console.log(`Imported ${count} labels`)
Cross-Wallet Compatibility
SatSigner’s BIP329 implementation is compatible with:
Supported Wallets:
- Sparrow Wallet
- Bitcoin Core (with patch)
- BlueWallet
- Electrum (custom format)
- Specter Desktop
- Nunchuk
Import Notes:
// Handle wallet-specific formats
const bip329Aliases = {
ref: ['ref', 'txid', 'address', 'Payment Address'],
label: ['label', 'memo'],
fee: ['fee', 'Fee sat/vbyte'],
height: ['height', 'Block height', 'Blockheight'],
time: ['date', 'Date (UTC)', 'time', 'timestamp'],
value: ['value', 'sats', 'satoshis', 'amount']
}
// Auto-detect and map fields
function normalizeLabel(rawLabel: any): Label {
const normalized = {}
for (const [key, value] of Object.entries(rawLabel)) {
const standardKey = bip329Alias[key.toLowerCase()]
if (standardKey) {
normalized[standardKey] = value
}
}
return normalized as Label
}
Label Organization
Organize labels with tags:
// Tag system
const tags = [
'income',
'expense',
'savings',
'investment',
'donation',
'tax-deductible',
'business',
'personal'
]
// Tag in label text
setTxLabel(
accountId,
txid,
"#income #business Client Payment - Invoice #1234"
)
// Extract tags
function extractTags(label: string): string[] {
const tagRegex = /#(\w+)/g
const tags = []
let match
while ((match = tagRegex.exec(label)) !== null) {
tags.push(match[1])
}
return tags
}
// Filter by tag
function getTransactionsByTag(account: Account, tag: string) {
return account.transactions.filter(tx => {
const tags = extractTags(tx.label || '')
return tags.includes(tag)
})
}
Label Templates
Create consistent labels:
// Label templates
const templates = {
invoice: (invoiceNumber: string, client: string) =>
`Invoice #${invoiceNumber} - ${client}`,
payroll: (month: string, year: number, recipient: string) =>
`Payroll - ${month} ${year} - ${recipient}`,
purchase: (category: string, vendor: string) =>
`Purchase - ${category} - ${vendor}`,
donation: (organization: string, taxDeductible: boolean) =>
`Donation - ${organization}${taxDeductible ? ' (Tax Deductible)' : ''}`,
savings: (goal: string, date: Date) =>
`Savings - ${goal} - ${date.toISOString().split('T')[0]}`
}
// Usage
setTxLabel(
accountId,
txid,
templates.invoice('1234', 'Acme Corp')
)
// Result: "Invoice #1234 - Acme Corp"
setAddrLabel(
accountId,
address,
templates.donation('Red Cross', true)
)
// Result: "Donation - Red Cross (Tax Deductible)"
Label Search
// Search labels
function searchLabels(
account: Account,
query: string
): {
transactions: Transaction[]
addresses: Address[]
utxos: Utxo[]
} {
const lowerQuery = query.toLowerCase()
return {
transactions: account.transactions.filter(tx =>
tx.label?.toLowerCase().includes(lowerQuery)
),
addresses: account.addresses.filter(addr =>
addr.label?.toLowerCase().includes(lowerQuery)
),
utxos: account.utxos.filter(utxo =>
utxo.label?.toLowerCase().includes(lowerQuery)
)
}
}
// Usage
const results = searchLabels(account, 'invoice')
console.log(`Found ${results.transactions.length} transactions`)
Advanced Features
Spendable Flag
Mark UTXOs as frozen (unspendable):
// Freeze UTXO
const frozenLabel: Label = {
type: 'output',
ref: `${txid}:${vout}`,
label: 'Emergency Fund - Do Not Spend',
spendable: false // Marks as frozen
}
// Filter spendable UTXOs
function getSpendableUtxos(account: Account): Utxo[] {
return account.utxos.filter(utxo => {
const outpoint = getUtxoOutpoint(utxo)
const label = account.labels[outpoint]
return label?.spendable !== false
})
}
// Use in transaction building
const spendableUtxos = getSpendableUtxos(account)
const { inputs } = selectEfficientUtxos(
spendableUtxos,
targetAmount,
feeRate
)
// Rich label with metadata
const detailedLabel: Label = {
type: 'tx',
ref: txid,
label: 'Equipment Purchase',
// Financial metadata
value: 500000, // 0.005 BTC
fee: 2000, // 2000 sats
fmv: { USD: 250.00 }, // Fair market value
// Blockchain metadata
height: 750000, // Block height
time: new Date('2024-01-15'),
// Key metadata
keypath: "m/84'/0'/0'/0/5",
origin: 'Hardware Wallet',
// Status
spendable: true
}
Label Analytics
// Analyze labels
function analyzeLabelUsage(account: Account) {
const stats = {
totalLabels: 0,
byType: {
tx: 0,
addr: 0,
output: 0
},
avgLabelLength: 0,
mostCommonTags: [],
unlabeled: {
transactions: 0,
addresses: 0,
utxos: 0
}
}
// Count by type
for (const label of Object.values(account.labels)) {
stats.totalLabels++
stats.byType[label.type]++
}
// Count unlabeled
stats.unlabeled.transactions = account.transactions
.filter(tx => !tx.label).length
stats.unlabeled.addresses = account.addresses
.filter(addr => !addr.label).length
stats.unlabeled.utxos = account.utxos
.filter(utxo => !utxo.label).length
// Average label length
const labelTexts = Object.values(account.labels)
.map(l => l.label)
stats.avgLabelLength = labelTexts
.reduce((sum, text) => sum + text.length, 0) / labelTexts.length
return stats
}
Best Practices
Labeling Strategy
- Label Immediately - Label transactions as they occur
- Be Consistent - Use consistent naming conventions
- Be Descriptive - Include context and purpose
- Use Tags - Organize with hashtags
- Review Regularly - Update labels as needed
- Backup Labels - Export regularly
- Document System - Maintain labeling guidelines
Good Labels:
✓ "Invoice #1234 - Acme Corp - Web Design"
✓ "Payroll - March 2024 - John Doe"
✓ "Equipment Purchase - Laptop - $2500"
✓ "#income #business Client Payment"
✓ "Cold Storage - Long Term Hold"
Poor Labels:
✗ "tx" // Too vague
✗ "payment" // No context
✗ "stuff" // Meaningless
✗ "asdf" // Random characters
✗ "123" // Just numbers
Privacy Considerations
Remember:
- Labels are stored locally only
- Never included on blockchain
- Can contain sensitive information
- Export files should be encrypted
- Backup securely
Sensitive Label Handling:
// Sanitize labels for export
function sanitizeLabelForExport(label: string): string {
// Remove personal identifiers
return label
.replace(/\b[A-Z][a-z]+ [A-Z][a-z]+\b/g, '[NAME]') // Names
.replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[SSN]') // SSN
.replace(/\b[\w.-]+@[\w.-]+\.\w+\b/g, '[EMAIL]') // Email
.replace(/\b\d{4}-\d{4}-\d{4}-\d{4}\b/g, '[CARD]') // Card
}
// Export with sanitization
function exportSanitizedLabels(account: Account) {
const labels = Object.values(account.labels).map(label => ({
...label,
label: sanitizeLabelForExport(label.label)
}))
return labelsToJSONL(labels)
}
Tax and Accounting
Tax Lot Tracking
// Label with tax information
const taxLabel: Label = {
type: 'output',
ref: `${txid}:${vout}`,
label: 'Purchase - 2024-01-15 - $45,000',
value: 100000000, // 1 BTC
fmv: { USD: 45000 },
time: new Date('2024-01-15'),
origin: 'Exchange Purchase'
}
// Calculate capital gains
function calculateGains(
acquisition: Label,
disposal: Transaction
): {
costBasis: number
proceeds: number
gain: number
holdingPeriod: number
} {
const costBasis = acquisition.fmv?.USD || 0
const proceeds = disposal.fmv?.USD || 0
const gain = proceeds - costBasis
const holdingPeriod = disposal.timestamp.getTime() -
acquisition.time.getTime()
return {
costBasis,
proceeds,
gain,
holdingPeriod
}
}
Report Generation
// Generate tax report
function generateTaxReport(
account: Account,
year: number
): {
shortTerm: Transaction[]
longTerm: Transaction[]
income: Transaction[]
deductions: Transaction[]
} {
const yearStart = new Date(year, 0, 1)
const yearEnd = new Date(year, 11, 31)
const yearTxs = account.transactions.filter(
tx => tx.timestamp >= yearStart && tx.timestamp <= yearEnd
)
return {
shortTerm: yearTxs.filter(tx =>
tx.label?.includes('#short-term')
),
longTerm: yearTxs.filter(tx =>
tx.label?.includes('#long-term')
),
income: yearTxs.filter(tx =>
tx.label?.includes('#income')
),
deductions: yearTxs.filter(tx =>
tx.label?.includes('#tax-deductible')
)
}
}
Troubleshooting
Labels Not Importing
Problem: Imported labels not applying
Solutions:
- Verify format matches (JSONL vs JSON vs CSV)
- Check for syntax errors in file
- Ensure references match account data
- Try alternative format
- Check for encoding issues (UTF-8)
Labels Not Cascading
Problem: Child items not inheriting labels
Cause: Child item already has label
Remember: Label inheritance only applies to unlabeled items
Missing Labels After Sync
Problem: Labels disappeared after sync
Cause: Labels stored separately from blockchain data
Solution:
- Labels persist through syncs
- Check if using correct account
- Restore from backup if needed
Integration with Other Wallets
Export to Sparrow
// Export for Sparrow Wallet
const sparrowLabels = exportAccountLabels(account, 'JSONL')
saveToFile('sparrow-labels.jsonl', sparrowLabels)
// Import in Sparrow:
// File > Import > Labels (BIP329)
Export to Bitcoin Core
// Bitcoin Core uses 'setlabel' RPC
function exportForBitcoinCore(account: Account): string[] {
const commands = []
for (const label of Object.values(account.labels)) {
if (label.type === 'addr') {
commands.push(
`bitcoin-cli setlabel "${label.ref}" "${label.label}"`
)
}
}
return commands
}
Import from Electrum
// Electrum uses custom format
function importFromElectrum(electrumLabels: any): Label[] {
const labels: Label[] = []
for (const [ref, label] of Object.entries(electrumLabels)) {
// Detect type from reference format
const type = ref.includes(':') ? 'output' :
ref.length === 64 ? 'tx' : 'addr'
labels.push({
type,
ref,
label: label as string
})
}
return labels
}
SatSigner’s BIP329 implementation provides professional-grade label management with full portability across compatible Bitcoin wallets, enabling organized and documented transaction history.