Skip to main content

Sending Encrypted Transactions with Programmable Privacy

This comprehensive tutorial walks you through building a complete encrypted transaction system using SKALE’s Programmable Privacy. 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 privacy-enabled SKALE chain. Check Programmable Privacy for current feature 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 pre-execution privacy for sensitive transfers — data is hidden until consensus finality

Prerequisites

  • Node.js 16+ and npm/yarn
  • MetaMask browser extension
  • Access to a privacy-enabled SKALE chain
  • Basic knowledge of JavaScript and smart contracts

Step 0: Setup Privacy SDK

If you haven’t already, install the Privacy SDK:

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:
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 privacy-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

Configure Environment

Create .env file with your configuration:
Environment Variables:
  • VITE_EXPLORER_URL: Block explorer for transaction links
  • VITE_SCHAIN_ENDPOINT: Privacy-enabled SKALE chain RPC
  • VITE_TRANSFER_CONTRACT: Address of deployed transfer contract

Update package.json

Step 3: Core Implementation

Let’s build the main application logic.

Initialize the Privacy SDK

Create src/main.js:

Transaction Data Encoding

Wallet Connection

Encrypted Transaction Submission

Transaction Finality Monitoring

Step 4: User Interface

Let’s create a user-friendly interface for our encrypted transfers.

HTML Structure

Create index.html:

CSS Styling

Create src/style.css:

UI Management Functions

Add these functions to src/main.js:

Step 5: Testing and Running

Run the Application

Test the Flow

  1. Connect Wallet: Click “Connect Wallet” and approve in MetaMask
  2. Enter Transfer Details: Input recipient address and amount
  3. Send Transfer: Click “Encrypt & Send Transfer”
  4. Monitor: Watch the transaction go through stages:
    • Pending: Transaction submitted and encrypted
    • Finality: Transaction confirmed by consensus
    • Decryption: Original data revealed and executed
  5. View Results: Check completed transfers section
Make sure your MetaMask is connected to the correct privacy-enabled SKALE network with sufficient sFUEL for transaction fees.

Step 6: Advanced Features

Committee Rotation Monitoring

Add this to monitor committee changes:

Real-Time Pending Transaction Updates

Security Best Practices

Input Validation

Error Handling

Troubleshooting

Common Issues

”Transaction not decrypting”

Solutions:
  1. Wait longer for consensus finality
  2. Check if committee rotation is in progress
  3. Verify transaction was sent via threshold encryption

”Gas limit too low”

Solutions:
  1. Increase gas limit to 300,000 or higher
  2. Always set gasLimit manually for encrypted transactions

”Connection failed”

Solutions:
  1. Verify SKALE chain endpoint URL is correct
  2. Check network connectivity
  3. Ensure chain supports Programmable Privacy

”Balance not updating”

Solutions:
  1. Wait for transaction decryption (3-5 seconds after finality)
  2. Refresh wallet connection
  3. Check if transfer actually succeeded

Debug Mode

Add this to main.js for debugging: