Sending Encrypted Transactions with BITE
This comprehensive tutorial walks you through building a complete encrypted transaction system using BITE (Blockchain Integrated Threshold Encryption). You’ll learn how to create private transfers that remain encrypted until consensus finality, preventing MEV attacks while maintaining blockchain transparency.
This tutorial requires access to a BITE-enabled SKALE chain. Check BITE Protocol Phases for current availability.
What You’ll Build
A full-featured encrypted transfer application that:
- Encrypts transaction data (recipient, amount) before blockchain submission
- Shows real-time pending encrypted transactions
- Displays decrypted results after consensus finality
- Handles committee rotations seamlessly
- Provides complete privacy for sensitive transfers
Prerequisites
- Node.js 16+ and npm/yarn
- MetaMask browser extension
- Access to a BITE-enabled SKALE chain
- Basic knowledge of JavaScript and smart contracts
Step 0: Setup BITE SDK
If you haven’t already, install the BITE TypeScript SDK:npm i @skalenetwork/bite ethers dotenv
Step 1: Smart Contract Setup
First, let’s create a simple smart contract for our encrypted transfers.
Create the Transfer Contract
Create a new file contracts/SimpleTransferDemo.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleTransferDemo {
mapping(address => uint256) public balances;
event Transferred(address indexed from, address indexed to, uint256 amount);
constructor() {
balances[msg.sender] = 1_000_000;
}
function allocate(address user, uint256 amount) external {
balances[user] += amount;
}
function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount, "Not enough balance");
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transferred(msg.sender, to, amount);
}
function balanceOf(address user) external view returns (uint256) {
return balances[user];
}
}
Contract Features:
- Simple ERC20-like interface for easy testing
- Initial funding of 1,000,000 tokens to deployer
- Allocate function for funding test accounts
- Transfer event for tracking encrypted operations
Deploy the Contract
Use Foundry or Hardhat to deploy the contract to your BITE-enabled SKALE chain. See ERC20 Deployment Guide for detailed deployment instructions.
Step 2: Project Setup
Let’s set up our frontend application structure.
Initialize the Project
# Create project directory
mkdir bite-encrypted-transfers
cd bite-encrypted-transfers
# Initialize npm project
npm init -y
# Install dependencies
npm install @skalenetwork/bite ethers dotenv vite
npm install --save-dev vite @vitejs/plugin-react
# Create basic structure
mkdir src
touch src/main.js src/style.css index.html .env package.json
Create .env file with your configuration:
VITE_EXPLORER_URL=https://explorer.skale.network/
VITE_SCHAIN_ENDPOINT=https://your-bite-chain.skale.network/
VITE_TRANSFER_CONTRACT=0xYourDeployedContractAddress
Environment Variables:
VITE_EXPLORER_URL: Block explorer for transaction links
VITE_SCHAIN_ENDPOINT: BITE-enabled SKALE chain RPC
VITE_TRANSFER_CONTRACT: Address of deployed transfer contract
Update package.json
{
"name": "bite-encrypted-transfers",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@skalenetwork/bite": "^0.7.0",
"ethers": "^6.13.5"
},
"devDependencies": {
"vite": "^6.3.1"
}
}
Step 3: Core Implementation
Let’s build the main application logic.
Initialize BITE
Create src/main.js:
import { ethers } from 'ethers';
import { BITE } from '@skalenetwork/bite';
// Configuration
const SCHAIN_ENDPOINT = import.meta.env.VITE_SCHAIN_ENDPOINT;
const TRANSFER_CONTRACT_ADDRESS = import.meta.env.VITE_TRANSFER_CONTRACT;
const EXPLORER_URL = import.meta.env.VITE_EXPLORER_URL;
// Initialize BITE
const bite = new BITE(SCHAIN_ENDPOINT);
// Contract ABI for our transfer function
const TRANSFER_ABI = [
'function transfer(address to, uint256 amount)',
'function balanceOf(address user) view returns (uint256)',
'event Transferred(address indexed from, address indexed to, uint256 amount)'
];
let currentAccount = null;
let provider = null;
Transaction Data Encoding
function encodeTransferData(recipient, amount) {
const iface = new ethers.Interface(TRANSFER_ABI);
return iface.encodeFunctionData('transfer', [recipient, amount]);
}
function parseTransferData(data) {
const iface = new ethers.Interface(TRANSFER_ABI);
try {
const parsed = iface.parseTransaction({ data });
return {
to: parsed.args[0],
amount: parsed.args[1].toString()
};
} catch (error) {
return null;
}
}
Wallet Connection
async function connectWallet() {
if (typeof window.ethereum === 'undefined') {
throw new Error('MetaMask not detected');
}
try {
// Request account access
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
currentAccount = accounts[0];
provider = new ethers.BrowserProvider(window.ethereum);
// Update UI
document.getElementById('walletAddress').textContent = currentAccount;
document.getElementById('connectBtn').style.display = 'none';
document.getElementById('transferForm').style.display = 'block';
// Load initial balance
await loadBalance();
} catch (error) {
console.error('Wallet connection failed:', error);
alert('Failed to connect wallet');
}
}
Encrypted Transaction Submission
async function sendEncryptedTransfer(recipient, amount) {
if (!currentAccount) {
throw new Error('Wallet not connected');
}
try {
// Encode the transfer function call
const encodedData = encodeTransferData(recipient, ethers.parseEther(amount));
// Create transaction parameters
const txParams = {
from: currentAccount,
to: TRANSFER_CONTRACT_ADDRESS,
value: '0x0',
data: encodedData,
gasLimit: 300000, // Important: set manually for BITE transactions
};
// Encrypt transaction data using BITE
const encryptedParams = await bite.encryptTransaction(txParams);
console.log('Encrypted transaction:', encryptedParams);
// Submit encrypted transaction
const txHash = await window.ethereum.request({
method: 'eth_sendTransaction',
params: [encryptedParams],
});
console.log('Transaction submitted:', txHash);
// Add to pending transactions
addPendingTransaction(txHash, recipient, amount);
// Monitor for finality
waitForTransactionFinality(txHash);
return txHash;
} catch (error) {
console.error('Transfer failed:', error);
alert(`Transfer failed: ${error.message}`);
}
}
Transaction Finality Monitoring
async function waitForTransactionFinality(txHash) {
const maxAttempts = 60; // 60 seconds timeout
let attempts = 0;
while (attempts < maxAttempts) {
try {
// Check transaction receipt
const receipt = await provider.getTransactionReceipt(txHash);
if (receipt && receipt.status === 1) {
// Transaction confirmed, wait a bit more for decryption
await new Promise(resolve => setTimeout(resolve, 3000));
// Try to get decrypted data
try {
const decryptedData = await bite.getDecryptedTransactionData(txHash);
if (decryptedData) {
// Successfully decrypted!
const transferInfo = parseTransferData(decryptedData.data);
addDecryptedResult(txHash, transferInfo);
removePendingTransaction(txHash);
await loadBalance(); // Update balance
return;
}
} catch (decryptError) {
console.log('Decryption not ready yet:', decryptError.message);
}
}
} catch (error) {
console.log('Transaction not ready yet...');
}
attempts++;
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Timeout occurred
removePendingTransaction(txHash);
alert('Transaction confirmation timeout. Please check the block explorer.');
}
Step 4: User Interface
Let’s create a user-friendly interface for our encrypted transfers.
HTML Structure
Create index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BITE Encrypted Transfers</title>
<link rel="stylesheet" href="src/style.css">
</head>
<body>
<div class="container">
<header>
<h1>BITE Encrypted Transfers</h1>
<p>Private blockchain transactions with threshold encryption</p>
</header>
<main>
<!-- Wallet Connection -->
<section class="wallet-section">
<button id="connectBtn" onclick="connectWallet()">Connect Wallet</button>
<div id="walletInfo" style="display: none;">
<p>Connected: <strong id="walletAddress"></strong></p>
<p>Balance: <strong id="walletBalance">0</strong> tokens</p>
</div>
</section>
<!-- Transfer Form -->
<section id="transferForm" class="transfer-section" style="display: none;">
<h2>Send Encrypted Transfer</h2>
<form id="transferFormElement" onsubmit="handleTransferSubmit(event)">
<div class="form-group">
<label for="recipient">Recipient Address:</label>
<input type="text" id="recipient" required
placeholder="0x..." pattern="^0x[a-fA-F0-9]{40}$">
</div>
<div class="form-group">
<label for="amount">Amount:</label>
<input type="number" id="amount" required min="0" step="0.001">
</div>
<button type="submit">Encrypt & Send Transfer</button>
</form>
</section>
<!-- Pending Transactions -->
<section class="pending-section">
<h2>Pending Encrypted Transactions</h2>
<div id="pendingTransactions" class="transaction-list">
<p class="empty-state">No pending transactions</p>
</div>
</section>
<!-- Decrypted Results -->
<section class="results-section">
<h2>Completed Transfers</h2>
<div id="completedTransactions" class="transaction-list">
<p class="empty-state">No completed transfers</p>
</div>
</section>
</main>
</div>
<script type="module" src="src/main.js"></script>
</body>
</html>
CSS Styling
Create src/style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
background: #f8fafc;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 40px;
padding: 40px 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
}
header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
header p {
font-size: 1.1rem;
opacity: 0.9;
}
section {
background: white;
padding: 30px;
margin-bottom: 30px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #4a5568;
}
.form-group input {
width: 100%;
padding: 12px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.2s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
}
button {
background: #667eea;
color: white;
border: none;
padding: 14px 28px;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
button:hover {
background: #5a67d8;
transform: translateY(-2px);
}
.transaction-list {
max-height: 300px;
overflow-y: auto;
}
.transaction-item {
background: #f7fafc;
padding: 16px;
margin-bottom: 12px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.transaction-item.pending {
border-left-color: #f6ad55;
background: #fffbf0;
}
.transaction-item.completed {
border-left-color: #48bb78;
background: #f0fff4;
}
.transaction-link {
display: inline-block;
margin-top: 8px;
color: #667eea;
text-decoration: none;
font-size: 0.9rem;
}
.transaction-link:hover {
text-decoration: underline;
}
.empty-state {
text-align: center;
color: #718096;
padding: 40px;
font-style: italic;
}
.tx-hash {
font-family: monospace;
font-size: 0.9rem;
color: #4a5568;
margin-top: 4px;
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
header h1 {
font-size: 2rem;
}
section {
padding: 20px;
}
}
UI Management Functions
Add these functions to src/main.js:
// Transaction tracking
let pendingTransactions = new Map();
let completedTransactions = [];
function addPendingTransaction(txHash, recipient, amount) {
const txData = {
hash: txHash,
recipient,
amount,
timestamp: new Date(),
status: 'pending'
};
pendingTransactions.set(txHash, txData);
updatePendingTransactionsUI();
}
function removePendingTransaction(txHash) {
pendingTransactions.delete(txHash);
updatePendingTransactionsUI();
}
function addDecryptedResult(txHash, transferInfo) {
const result = {
hash: txHash,
recipient: transferInfo.to,
amount: ethers.formatEther(transferInfo.amount),
timestamp: new Date(),
status: 'completed'
};
completedTransactions.unshift(result);
updateCompletedTransactionsUI();
}
function updatePendingTransactionsUI() {
const container = document.getElementById('pendingTransactions');
if (pendingTransactions.size === 0) {
container.innerHTML = '<p class="empty-state">No pending transactions</p>';
return;
}
container.innerHTML = Array.from(pendingTransactions.values())
.map(tx => `
<div class="transaction-item pending">
<div><strong>To:</strong> ${tx.recipient}</div>
<div><strong>Amount:</strong> ${tx.amount} tokens</div>
<div class="tx-hash"><strong>Hash:</strong> ${tx.hash}</div>
<div><strong>Time:</strong> ${tx.timestamp.toLocaleString()}</div>
<a href="${EXPLORER_URL}/tx/${tx.hash}" target="_blank" class="transaction-link">
View on Explorer
</a>
</div>
`).join('');
}
function updateCompletedTransactionsUI() {
const container = document.getElementById('completedTransactions');
if (completedTransactions.length === 0) {
container.innerHTML = '<p class="empty-state">No completed transfers</p>';
return;
}
container.innerHTML = completedTransactions
.map(tx => `
<div class="transaction-item completed">
<div><strong>To:</strong> ${tx.recipient}</div>
<div><strong>Amount:</strong> ${tx.amount} tokens</div>
<div class="tx-hash"><strong>Hash:</strong> ${tx.hash}</div>
<div><strong>Time:</strong> ${tx.timestamp.toLocaleString()}</div>
<a href="${EXPLORER_URL}/tx/${tx.hash}" target="_blank" class="transaction-link">
View on Explorer
</a>
</div>
`).join('');
}
async function loadBalance() {
try {
if (!currentAccount) return;
const contract = new ethers.Contract(
TRANSFER_CONTRACT_ADDRESS,
TRANSFER_ABI,
provider
);
const balance = await contract.balanceOf(currentAccount);
const formattedBalance = ethers.formatEther(balance);
document.getElementById('walletBalance').textContent =
parseFloat(formattedBalance).toFixed(4);
} catch (error) {
console.error('Failed to load balance:', error);
}
}
function handleTransferSubmit(event) {
event.preventDefault();
const recipient = document.getElementById('recipient').value;
const amount = document.getElementById('amount').value;
// Validate inputs
if (!recipient || !amount) {
alert('Please fill in all fields');
return;
}
if (!recipient.match(/^0x[a-fA-F0-9]{40}$/)) {
alert('Invalid recipient address');
return;
}
sendEncryptedTransfer(recipient, amount);
// Reset form
document.getElementById('transferFormElement').reset();
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
// Check if wallet is already connected
if (typeof window.ethereum !== 'undefined') {
window.ethereum.request({ method: 'eth_accounts' })
.then(accounts => {
if (accounts.length > 0) {
connectWallet();
}
});
}
});
Step 5: Testing and Running
Run the Application
# Start development server
npm run dev
# Open browser to http://localhost:5173
Test the Flow
- Connect Wallet: Click “Connect Wallet” and approve in MetaMask
- Enter Transfer Details: Input recipient address and amount
- Send Transfer: Click “Encrypt & Send Transfer”
- Monitor: Watch the transaction go through stages:
- Pending: Transaction submitted and encrypted
- Finality: Transaction confirmed by consensus
- Decryption: Original data revealed and executed
- View Results: Check completed transfers section
Make sure your MetaMask is connected to the correct BITE-enabled SKALE network with sufficient sFUEL for transaction fees.
Step 6: Advanced Features
Committee Rotation Monitoring
Add this to monitor committee changes:
async function monitorCommitteeRotation() {
try {
const committees = await bite.getCommitteesInfo();
if (committees.length === 2) {
console.warn('⚠️ Committee rotation in progress');
showRotationWarning();
}
// Schedule next check
setTimeout(monitorCommitteeRotation, 30000);
} catch (error) {
console.error('Committee monitoring failed:', error);
}
}
function showRotationWarning() {
const warning = document.createElement('div');
warning.className = 'rotation-warning';
warning.innerHTML = '⚠️ Committee rotation in progress - dual encryption active';
warning.style.cssText = `
background: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
`;
document.querySelector('.container').prepend(warning);
// Remove warning after 5 minutes
setTimeout(() => warning.remove(), 300000);
}
Real-Time Pending Transaction Updates
async function fetchPendingTransactions() {
try {
const response = await fetch(SCHAIN_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method: 'eth_pendingTransactions',
params: [],
id: 1,
}),
});
const data = await response.json();
const pendingTxs = data.result || [];
// Update UI with new pending transactions
pendingTxs.forEach(tx => {
if (tx.to === '0x0000000000000000000000000000000000000001') { // BITE magic address
addPendingTransaction(tx.hash, 'Encrypted', 'Unknown until finality');
}
});
} catch (error) {
console.error('Failed to fetch pending transactions:', error);
}
}
// Start polling for pending transactions
setInterval(fetchPendingTransactions, 5000);
monitorCommitteeRotation();
Security Best Practices
function validateTransferData(recipient, amount) {
// Address validation
if (!ethers.isAddress(recipient)) {
throw new Error('Invalid recipient address');
}
// Amount validation
const parsedAmount = ethers.parseEther(amount);
if (parsedAmount <= 0) {
throw new Error('Amount must be greater than 0');
}
// Balance check
const currentBalance = ethers.parseEther(
document.getElementById('walletBalance').textContent
);
if (parsedAmount > currentBalance) {
throw new Error('Insufficient balance');
}
return true;
}
Error Handling
class BiteTransferError extends Error {
constructor(message, code) {
super(message);
this.name = 'BiteTransferError';
this.code = code;
}
}
async function sendEncryptedTransferWithRetry(recipient, amount, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await sendEncryptedTransfer(recipient, amount);
} catch (error) {
if (attempt === maxRetries) {
throw new BiteTransferError(
`Failed after ${maxRetries} attempts: ${error.message}`,
'MAX_RETRIES_EXCEEDED'
);
}
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}
Troubleshooting
Common Issues
”Transaction not decrypting”
Solutions:
- Wait longer for consensus finality
- Check if committee rotation is in progress
- Verify transaction was sent via BITE protocol
”Gas limit too low”
Solutions:
- Increase gas limit to 300,000 or higher
- Always set gasLimit manually for BITE transactions
”Connection failed”
Solutions:
- Verify SKALE chain endpoint URL is correct
- Check network connectivity
- Ensure chain supports BITE protocol
”Balance not updating”
Solutions:
- Wait for transaction decryption (3-5 seconds after finality)
- Refresh wallet connection
- Check if transfer actually succeeded
Debug Mode
Add this to main.js for debugging:
const DEBUG = true; // Set to false in production
function debug(message, data = null) {
if (DEBUG) {
console.log(`[BITE DEBUG] ${message}`, data);
}
}
// Usage examples
debug('Initializing BITE with endpoint:', SCHAIN_ENDPOINT);
debug('Transaction parameters:', txParams);
debug('Encrypted transaction:', encryptedParams);
debug('Decrypted data:', decryptedData);
Next Steps
Congratulations! You’ve built a complete encrypted transaction system with BITE. Here are ways to enhance it:
Additional Features
- Batch transfers: Encrypt multiple transfers in one transaction
- Recurring payments: Scheduled encrypted transfers
- Multi-signature support: Require multiple approvals
- Transaction history: Persistent storage with encryption keys
- Advanced privacy: Zero-knowledge proof integration
Production Considerations
- Error logging: Comprehensive error tracking
- Performance optimization: Debouncing and caching
- Security audits: Professional security review
- User testing: Comprehensive UX testing
- Documentation: API documentation for integration
Integration Opportunities
- DeFi protocols: Private swaps and lending
- NFT marketplaces: Confidential bidding
- Gaming platforms: Private in-game transactions
- Enterprise applications: Secure supply chain tracking
Resources