A Next.js playground for creating and monitoring BSV wallet transactions with live logs and real-time status updates.
This application creates ECDH-locked payments between a frontend wallet (browser extension) and a backend wallet, displaying live transaction logs and wallet action status as they happen.
- Transaction Testing: Create ECDH-locked payments with various amounts
- Wallet State Monitoring: Real-time display of wallet action status transitions
- Transaction Logging: Detailed logs with transaction hex and parameters
- File Storage: All transactions saved to
transaction-history.jsonwith full ECDH parameters - Key Derivation Testing: Verify frontend/backend ECDH key synchronization
- Single-Input Mode: Option to enforce single-input transactions by stripping wallet-added inputs
- Frontend-Only Test: Create and spend a transaction entirely in the frontend wallet
- Node.js 18+ installed
- A BSV wallet browser extension (e.g., Panda Wallet, MetaStrata)
- A backend wallet private key for mainnet
Create a .env.local file in the project root:
# Backend wallet private key (WIF format)
BACKEND_WALLET_PRIVATE_KEY=your_private_key_hereYou can also copy from the example:
cp .env.local.example .env.localThe backend wallet uses Babbage's storage URL by default on mainnet:
makeWallet('main', 'https://storage.babbage.systems', privateKey)- Network: Mainnet (
main) - Storage URL:
https://storage.babbage.systems - Private Key: Your wallet's private key in WIF format
Security Note: Never commit your .env.local file or expose your private key publicly.
# Install dependencies
npm install
# Run the development server
npm run devOpen http://localhost:3000 (redirects to http://localhost:3000/playground)
Click "Connect Wallet" to connect your browser wallet extension.
Click "π Test Key Derivation (Debug)" to verify that frontend and backend derive matching ECDH keys.
Click "Create Transaction" to:
- Frontend wallet creates a 100 sat ECDH-locked payment
- Payment is locked to the backend using
WalletP2PKH.lock() - Backend unlocks it using
WalletP2PKH.unlock() - Transaction details are saved to
transaction-history.json
Click "Try Again Immediately" to create another transaction right away without waiting for the previous one to reach completed status.
Click "Try Again After Proof" to:
- Wait for the latest transaction to reach
completedstatus - Poll
wallet.listActions()until status changes - Create a new transaction once proven
Click "Create 200 Sats (Single Input Enforced)" to create a 200 sat transaction where the backend strips any extra inputs the wallet might add, keeping only the input matching the derived payment key.
Click "π¬ Frontend-Only Test (No Backend)" to create a self-locked payment and immediately unlock it entirely within the frontend wallet β no backend involved.
Frontend Backend
βββββββββββββββββββββββββββββββββββββββββββββββββββββ
WalletP2PKH.lock() WalletP2PKH.unlock()
protocolID: same protocolID: same
keyID: same keyID: same
counterparty: backendIdentity counterparty: frontendIdentity
(swapped!)
Both sides derive the same key via ECDH with swapped counterparties.
The createAction call uses acceptDelayedBroadcast: false in its options to ensure transactions are broadcast synchronously before the call returns. This was the fix for a previous issue where rapid sequential transactions could fail due to the wallet still processing the prior broadcast.
await wallet.createAction({
description: '...',
outputs: [...],
options: {
randomizeOutputs: false,
acceptDelayedBroadcast: false,
},
});unsigned β nosend β unproven β completed
- Create unlocking script template and estimate length
- Create action with
unlockingScriptLength(not actual script) - Extract
signableTransaction, add template + source, sign to get actual unlocking script - Call
signAction()with actual unlocking scripts
All transactions are automatically saved to transaction-history.json in the project root with:
{
"timestamp": "2025-01-19T12:34:56.789Z",
"frontendTxid": "abc123...",
"frontendTxHex": "01000000...",
"backendTxid": "def456...",
"backendTxHex": "01000000...",
"protocolID": [0, "wallet playground 1737291296789"],
"keyID": "keyabc123",
"counterparty": "03...",
"paymentKey": "02...",
"success": true,
"error": null
}src/app/playground/page.tsx- Main playground page (server component)src/components/PlaygroundClient.tsx- Client component with statesrc/components/TransactionTester.tsx- Transaction creation UIsrc/components/WalletStateViewer.tsx- Real-time wallet action displaysrc/components/LogViewer.tsx- Transaction log displaysrc/utils/walletClient.ts- Frontend WalletClient (@bsv/sdk)
src/app/api/wallet/unlock-payment/route.ts- Standard unlock endpointsrc/app/api/wallet/unlock-payment-single-input/route.ts- Single-input enforcing unlocksrc/app/api/wallet/derive-key/route.ts- Key derivation endpointsrc/app/api/wallet/identity/route.ts- Backend identity key endpointsrc/app/api/storage/save-transaction/route.ts- Transaction file storagesrc/utils/backendWallet.ts- Backend wallet initialization
# Install dependencies
npm install
# Run development server
npm run dev
# Build for production
npm run build
# Start production server
npm start- Ensure you have a BSV wallet extension installed (Panda, MetaStrata, etc.)
- Refresh the page and try connecting again
- Check that
BACKEND_WALLET_PRIVATE_KEYis set in.env.local - Verify the private key is in valid WIF format
- Restart the dev server after changing
.env.local
- Ensure all
wallet.getPublicKey()calls includeforSelf: true - Use the "Test Key Derivation" button to verify keys match
MIT