@unisat/animated-qr
v1.0.3
Published
Animated QR code library with BBQR protocol support
Maintainers
Readme
@unisat/animated-qr
🔗 Multi-part animated QR code library for large data transmission with BBQR protocol support
📋 Quick Overview
What it does: Splits large data (Bitcoin transactions, account data) into multiple animated QR codes for reliable mobile transmission.
Core Protocol: BBQR (Bitcoin QR) - Custom format with compression and multi-part support.
Use Cases: Bitcoin wallet data sync, PSBT transmission, large transaction signing.
🚀 Core Features
- ✅ Multi-part QR splitting - Handles data up to ~500KB
- ✅ BBQR protocol - Efficient compression + error detection
- ✅ React components - Ready-to-use UI components
- ✅ Progressive scanning - Scan parts in any order
- ✅ TypeScript native - Full type safety
- ✅ Zero external deps - Only uses lz-string + qrcode
📦 Installation
npm install @unisat/animated-qr
# For React components
npm install react qrcode.react⚡ Quick Start
Basic Encode/Decode
import { encode, decode } from '@unisat/animated-qr';
// Encode large data → QR code array
const qrParts = encode('very long bitcoin data...', 'psbt');
// → ['B$HP010150hex...', 'B$HP020250hex...', ...]
// Decode QR array → original data
const originalData = decode(qrParts);Progressive Scanning
import { createDecoder } from '@unisat/animated-qr';
const decoder = createDecoder();
// Scan QR codes one by one
const result = decoder.addPart('B$HP010150hex...');
console.log(`Progress: ${result.progress.percentage}%`);
if (result.isComplete) {
console.log('Data:', result.data);
}React Component
import { AnimatedQR } from '@unisat/animated-qr/react';
<AnimatedQR
type="psbt"
data={psbtHexString}
options={{
size: 300,
autoPlay: true,
showProgress: true,
maxChunkSize: 800
}}
/>📚 API Reference
Core Functions
| Function | Parameters | Returns | Description |
|----------|------------|---------|-------------|
| encode(data, type?, options?) | data: stringtype: DataTypeoptions: QROptions | string[] | Split data into QR parts |
| decode(qrParts) | qrParts: string[] | string | Reconstruct original data |
| createDecoder() | - | BBQRDecoder | Create progressive decoder |
| createEncoder() | - | BBQREncoder | Create encoder instance |
| detectProtocol(qrText) | qrText: string | 'bbqr' \| 'bc-ur' \| null | Detect QR protocol |
| isSupportedFormat(qrText) | qrText: string | boolean | Check if QR is supported |
Data Types
// Data categories
type DataType = 'psbt' | 'transaction' | 'account' | 'signature';
// Protocol types
type Protocol = 'bbqr' | 'bc-ur';
// Encoding options
interface QROptions {
maxChunkSize?: number; // Default: 800
compression?: boolean; // Default: true
protocolOptions?: Record<string, any>;
}
// Scan progress tracking
interface ScanProgress {
collected: number; // Parts received
total: number; // Total parts needed
percentage: number; // Completion %
missing?: number[]; // Missing part indices
}
// Scan result
interface ScanResult {
isComplete: boolean;
data?: string; // Available when complete
dataType?: DataType;
protocol?: Protocol;
progress: ScanProgress;
metadata?: Record<string, any>;
}React Components
<AnimatedQR>
interface AnimatedQRProps {
// Data input (choose one)
data?: string; // Raw data to encode
qrParts?: string[]; // Pre-encoded QR parts
// Configuration
type?: DataType; // Default: 'account'
protocol?: Protocol; // Default: 'bbqr'
// Display options
options?: {
size?: number; // QR size in pixels (default: 300)
interval?: number; // Animation interval ms (default: 300)
autoPlay?: boolean; // Auto-advance parts (default: true)
showProgress?: boolean;// Show dots indicator (default: true)
showControls?: boolean;// Show prev/next buttons (default: true)
showProtocolInfo?: boolean; // Show protocol info (default: false)
// Encoding options
maxChunkSize?: number; // Max chars per QR (default: 800)
compression?: boolean; // Enable compression (default: true)
errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'; // Default: 'M'
};
// Styling
style?: React.CSSProperties;
className?: string;
}BBQRDecoder Methods
class BBQRDecoder {
addPart(qrText: string): ScanResult;
reset(): void;
getProgress(): ScanProgress;
getProtocolType(): Protocol;
}BBQREncoder Methods
class BBQREncoder {
encode(data: string, dataType: DataType, options?: QROptions): string[];
getProtocolType(): Protocol;
}🎯 Usage Examples
1. Bitcoin PSBT Transmission
import { encode, createDecoder } from '@unisat/animated-qr';
// Sender side - encode PSBT
const psbtHex = "70736274ff01007d..."; // Your PSBT hex
const qrParts = encode(psbtHex, 'psbt', { maxChunkSize: 1000 });
console.log(`Split into ${qrParts.length} QR codes`);
// Receiver side - decode step by step
const decoder = createDecoder();
for (const part of qrParts) {
const result = decoder.addPart(part);
console.log(`Progress: ${result.progress.percentage}%`);
if (result.isComplete) {
console.log('Received PSBT:', result.data);
break;
}
}2. Account Sync Data
// Prepare account data
const accountData = JSON.stringify({
publicKeys: ['xpub6D3i46Y43SFfjEBYheBK3btYMRm9Cfb8Tt4M5Bv16tArNBw5ATNyJWjdcMyLxoCdHWTvm3ak7j2BWacq5Lw478aYUeARoYm4dvaQgJBAGsb'],
addresses: ['bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh'],
addressType: 0
});
const qrParts = encode(accountData, 'account', {
compression: true,
maxChunkSize: 600
});3. React Component with Custom Styling
import { AnimatedQR } from '@unisat/animated-qr/react';
function WalletQRDisplay({ transactionData }) {
return (
<div className="qr-container">
<h3>Scan to Import Transaction</h3>
<AnimatedQR
type="transaction"
data={transactionData}
options={{
size: 400,
autoPlay: true,
interval: 500,
showProgress: true,
showControls: true,
showProtocolInfo: true,
maxChunkSize: 1200,
compression: true
}}
style={{
border: '2px solid #007bff',
borderRadius: '10px',
padding: '20px'
}}
/>
</div>
);
}4. Error Handling & Protocol Detection
import { detectProtocol, isSupportedFormat, createDecoder } from '@unisat/animated-qr';
function handleQRScan(qrText: string) {
// Check if QR is supported
if (!isSupportedFormat(qrText)) {
console.error('Unsupported QR format');
return;
}
// Detect protocol
const protocol = detectProtocol(qrText);
console.log('Detected protocol:', protocol);
// Process based on protocol
if (protocol === 'bbqr') {
const decoder = createDecoder();
try {
const result = decoder.addPart(qrText);
if (result.isComplete && result.data) {
handleCompleteData(result.data, result.dataType);
} else {
updateScanProgress(result.progress);
}
} catch (error) {
console.error('Decode error:', error.message);
}
}
}
function handleCompleteData(data: string, dataType?: DataType) {
switch (dataType) {
case 'psbt':
console.log('Received PSBT:', data);
break;
case 'transaction':
console.log('Received transaction:', data);
break;
case 'account':
console.log('Received account data:', data);
break;
default:
console.log('Received data:', data);
}
}5. Large Data Optimization
// For very large data, optimize chunk size
const largeAccountData = JSON.stringify(hugeAccountObject);
// Test different chunk sizes
const sizes = [600, 800, 1000, 1200];
sizes.forEach(size => {
const parts = encode(largeAccountData, 'account', { maxChunkSize: size });
console.log(`Chunk size ${size}: ${parts.length} QR codes`);
});
// Use optimal size (fewer QR codes)
const optimizedParts = encode(largeAccountData, 'account', {
maxChunkSize: 1200,
compression: true
});🔧 BBQR Protocol Specification
Format Structure
B$[encoding][fileType][totalParts][currentPart][data]Components:
B$: Protocol identifier (required)encoding:H(Hex) |J(JSON) |2(Base32)fileType:P(PSBT) |T(Transaction) |J(JSON) |A(Account)totalParts: Base36 number (1-1295 parts supported)currentPart: Base36 number (0-indexed)data: Encoded payload
Examples:
B$HP0100cHNidP8BAH... # Single PSBT part
B$JP020150{"psbt":"cH..."} # JSON part 1 of 2
B$HP020250736274ff01007d... # Hex part 2 of 2Data Type Mapping
| DataType | BBQR Code | Description |
|----------|-----------|-------------|
| psbt | P | Bitcoin PSBT (hex encoded) |
| transaction | T | Signed transaction (hex) |
| account | J | Account data (JSON) |
| signature | J | Signature result (JSON) |
Compression
Uses lz-string compression when enabled:
// Compression reduces QR count for repetitive data
const uncompressed = encode(data, 'account', { compression: false });
const compressed = encode(data, 'account', { compression: true });
console.log(`Uncompressed: ${uncompressed.length} parts`);
console.log(`Compressed: ${compressed.length} parts`);🛠️ Development Setup
# Clone and install
git clone https://github.com/unisat-wallet/animated-qr.git
cd animated-qr
npm install
# Development commands
npm run dev # Watch mode
npm run build # Production build
npm run test # Run tests
npm run lint # Code lintingProject Structure
src/
├── core/
│ └── bbqr.ts # BBQR protocol implementation
├── react/
│ ├── AnimatedQR.tsx # Main React component
│ └── index.ts # React exports
├── types/
│ ├── protocols.ts # Core type definitions
│ └── react.ts # React component types
└── index.ts # Main library exports
examples/
├── basic/ # Vanilla JS example
└── react/ # React example📝 Integration Checklist
When integrating into your project:
- [ ] Install package:
npm install @unisat/animated-qr - [ ] For React: Install
reactandqrcode.reactpeer dependencies - [ ] Import core functions or React components
- [ ] Choose appropriate
DataTypefor your use case - [ ] Set
maxChunkSizebased on your QR scanner capabilities - [ ] Enable compression for large data
- [ ] Handle scan progress and errors appropriately
- [ ] Test with real QR scanner apps
🔗 Links & Resources
- Repository: https://github.com/unisat-wallet/animated-qr
- NPM Package: https://www.npmjs.com/package/@unisat/animated-qr
- UniSat Wallet: https://unisat.io/
- Issues: https://github.com/unisat-wallet/animated-qr/issues
📄 License
MIT License - Free for commercial and personal use.
For AI Integration: This library provides a complete solution for QR-based data transmission in Bitcoin/blockchain applications. Key integration points are the
encode(),decode(), and<AnimatedQR>React component. All functions are type-safe and well-documented.
