@solforge/qr-tx-decoder
v0.0.1
Published
Decode QR payloads back into Solana transactions for wallets
Maintainers
Readme
@solforge/qr-tx-decoder
Decode QR payloads back into Solana transactions for wallets to sign.
Part of the Solana QR Transaction Standard - an open standard for transferring Solana transactions between dApps and wallets without extensions or WalletConnect.
Installation
bun add @solforge/qr-tx-decoderQuick Start
import { decodeTransaction } from '@solforge/qr-tx-decoder';
// QR frames scanned from camera (can be in any order)
const frames = [
'frame-1-data-from-qr-scan',
'frame-2-data-from-qr-scan',
// ... more frames if transaction was split
];
try {
const result = await decodeTransaction(frames);
// Verify transaction details before signing
console.log('Network:', result.payload.network);
console.log('Origin:', result.payload.meta.origin);
console.log('Description:', result.payload.meta.description);
console.log('Required signers:', result.payload.meta.required_signers);
console.log('Expires at:', new Date(result.payload.meta.expires_at * 1000));
// The decoded Solana transaction ready for signing
const transaction = result.transaction;
// Sign with your wallet
const signature = await wallet.signTransaction(transaction);
// Return signature to dApp
const returnUrl = result.payload.sig_return.url.replace(':id', result.payload.session);
await fetch(returnUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ signature: signature.toString() })
});
} catch (error) {
console.error('Failed to decode transaction:', error);
}API Reference
decodeTransaction(frames, options?)
Decodes QR frame data back into a Solana transaction.
Parameters:
frames:string[]- Array of QR frame data (order doesn't matter)options?:DecodeOptions- Optional configuration
Returns: Promise<DecodeResult>
DecodeOptions
interface DecodeOptions {
validateExpiration?: boolean; // Check if transaction expired (default: true)
validateChecksum?: boolean; // Verify payload checksum (default: true)
requiredSigner?: string; // Verify this pubkey is in required_signers
}DecodeResult
interface DecodeResult {
transaction: Transaction | VersionedTransaction; // Decoded Solana transaction
payload: TransactionPayload; // Original payload metadata
isExpired: boolean; // Whether transaction has expired
}Features
- Frame Reassembly: Handles animated QR sequences in any order
- Automatic Decompression: Detects and decompresses gzipped payloads
- Validation: Built-in checksum and expiration validation
- Type Safety: Full TypeScript support with proper transaction types
- Error Recovery: Graceful handling of missing or corrupted frames
Validation
The decoder performs several security validations:
- Checksum Verification: Ensures payload integrity
- Expiration Check: Validates transaction hasn't expired
- Required Signers: Optionally verifies wallet can sign
- Frame Completeness: Ensures all frames are present
- Format Validation: Validates payload structure
Error Handling
try {
const result = await decodeTransaction(frames);
} catch (error) {
switch (error.code) {
case 'MISSING_FRAMES':
// Not all frames have been scanned yet
console.log(`Missing frames: ${error.missingFrames.join(', ')}`);
break;
case 'CHECKSUM_MISMATCH':
// Payload corruption detected
console.error('Transaction data corrupted');
break;
case 'TRANSACTION_EXPIRED':
// Transaction past expiration time
console.error('Transaction expired');
break;
case 'INVALID_SIGNER':
// Wallet cannot sign this transaction
console.error('Wallet not authorized to sign');
break;
case 'DECOMPRESSION_FAILED':
// Failed to decompress payload
console.error('Failed to decompress transaction data');
break;
default:
console.error('Unknown decode error:', error);
}
}Frame Collection
For animated QR codes, collect frames until you have all pieces:
import { getFrameInfo, decodeTransaction } from '@solforge/qr-tx-decoder';
const collectedFrames = new Set<string>();
// When scanning each QR frame
function onQRScanned(frameData: string) {
try {
const frameInfo = getFrameInfo(frameData);
collectedFrames.add(frameData);
console.log(`Collected ${collectedFrames.size}/${frameInfo.totalFrames} frames`);
if (collectedFrames.size === frameInfo.totalFrames) {
// All frames collected, decode transaction
decodeTransaction(Array.from(collectedFrames))
.then(result => {
// Handle decoded transaction
});
}
} catch (error) {
console.error('Invalid QR frame:', error);
}
}Security Best Practices
- Always Validate: Don't skip checksum or expiration validation
- Verify Origin: Check
payload.meta.originmatches expected dApp - Review Details: Show transaction details to user before signing
- Secure Transport: Only return signatures over HTTPS
- Rate Limiting: Implement limits on signature return endpoints
Transaction Types
The decoder handles both transaction formats:
// Legacy Transaction
if (result.transaction instanceof Transaction) {
const signature = await wallet.signTransaction(result.transaction);
}
// Versioned Transaction
if (result.transaction instanceof VersionedTransaction) {
const signature = await wallet.signTransaction(result.transaction);
}Related Packages
- @solforge/qr-tx-encoder - Encode transactions for QR display
License
MIT
