A fully-featured template for building privacy-preserving decentralized applications using LuxFHE CoFHE (Confidential on Fully Homomorphic Encryption) and Base MiniApps. This template demonstrates how to integrate Fully Homomorphic Encryption (FHE) into a Base or Farcaster MiniApp, allowing users to interact with encrypted on-chain data without ever revealing the actual values.
This template showcases how to build applications that leverage Fully Homomorphic Encryption to:
- Store and manipulate encrypted data on-chain
- Perform computations on encrypted values without decryption
- Maintain user privacy while ensuring transparency and verifiability
- Create seamless user experiences with Web3 wallets and Farcaster integration
The template includes a simple encrypted counter example that demonstrates the complete FHE workflow: encryption, on-chain computation, and local decryption.
- Node.js >= 18.0.0
- npm >= 8.0.0
- A wallet with a private key and testnet funds (Base Sepolia)
- Get Base Sepolia ETH from: Base Sepolia Faucet
- OnchainKit API Key from Coinbase Developer Portal
- (Optional) A Farcaster account for testing the MiniApp
This is a monorepo containing two main packages:
fhe-miniapp-template/
βββ packages/
β βββ hardhat/ # Smart contract development
β βββ miniapp/ # Next.js MiniApp frontend
βββ package.json # Root package with workspace scripts
βββ README.md
# Install all dependencies
npm run install:allNote: After setting up your environment, you'll need to deploy the contract. See the π’ Deploying Your Contract section below.
- For Hardhat (
packages/hardhat/.env):
PRIVATE_KEY=your_private_key_here- For MiniApp (
packages/miniapp/.env.local):
NEXT_PUBLIC_ONCHAINKIT_API_KEY=your_coinbase_api_key
NEXT_PUBLIC_URL=your_app_deployed_urlBefore you can use the MiniApp, you need to deploy the Counter contract and configure the frontend to use it.
Navigate to the Hardhat package and deploy to Base Sepolia:
# From the root directory
npm run deploycounter:hardhat
# OR from the hardhat directory
cd packages/hardhat
npx hardhat deploy-counter --network base-sepoliaExpected Output:
Deploying Counter to base-sepolia...
Deploying with account: 0x...
Counter deployed to: 0x316afF9FB759ab89124464048A6B6b305A618Bd0
The deployment script will automatically save the contract address to packages/hardhat/deployments/base-sepolia.json.
After compilation, the ABI is available in the artifacts:
# Compile contracts (if not already compiled)
cd packages/hardhat
npx hardhat compileThe ABI will be generated at:
packages/hardhat/artifacts/contracts/Counter.sol/Counter.json
Update the contract configuration in the MiniApp:
File: packages/miniapp/src/contracts/deployedContracts.ts
export const CONTRACT_ABI = [
// ... (copy the ABI from Counter.json)
];
export const CONTRACT_ADDRESS = "0xYourDeployedContractAddress";How to get the ABI:
- Open
packages/hardhat/artifacts/contracts/Counter.sol/Counter.json - Copy the
abiarray from the JSON file - Paste it as
CONTRACT_ABIindeployedContracts.ts
How to get the Address:
- Copy the address from the deployment output
- OR check
packages/hardhat/deployments/base-sepolia.json - Set it as
CONTRACT_ADDRESSindeployedContracts.ts
Make sure the contract is properly configured:
# Start the MiniApp
npm run dev:miniappIf the contract address is correctly set, you should be able to:
- Connect your wallet
- Initialize CoFHE
- Generate a permit
- Interact with the encrypted counter
The deployment task is located at packages/hardhat/tasks/deploy-counter.ts:
task('deploy-counter', 'Deploy the Counter contract')
.setAction(async (_, hre) => {
const [deployer] = await ethers.getSigners()
const Counter = await ethers.getContractFactory('Counter')
const counter = await Counter.deploy()
await counter.waitForDeployment()
const counterAddress = await counter.getAddress()
saveDeployment(network.name, 'Counter', counterAddress)
return counterAddress
})The script:
- Gets the deployer's signer from your
PRIVATE_KEY - Deploys the Counter contract
- Waits for deployment confirmation
- Saves the address to the deployments folder
To deploy to different networks, update packages/hardhat/hardhat.config.ts and use:
# Ethereum Sepolia
npx hardhat deploy-counter --network eth-sepolia
# Arbitrum Sepolia
npx hardhat deploy-counter --network arb-sepoliaβ
Install dependencies: npm run install:all
β
Create .env file in packages/hardhat/ with PRIVATE_KEY
β
Ensure wallet has Base Sepolia testnet ETH
β
Compile contracts: npm run build:hardhat
β
Deploy contract: npm run deploycounter:hardhat
β
Copy deployed address from terminal output
β
Update CONTRACT_ADDRESS in packages/miniapp/src/contracts/deployedContracts.ts
β
Start MiniApp: npm run dev:miniapp
Issue: "insufficient funds for intrinsic transaction cost"
- Solution: Get Base Sepolia ETH from the faucet
Issue: "missing PRIVATE_KEY"
- Solution: Create a
.envfile inpackages/hardhat/and add your private key
Issue: "CONTRACT_ADDRESS is empty" error in MiniApp
- Solution: Make sure you've updated
packages/miniapp/src/contracts/deployedContracts.tswith your deployed contract address
Issue: Network connection timeout
- Solution: Check your internet connection or try again. The Base Sepolia network may be experiencing high traffic.
| Script | Description |
|---|---|
npm run dev:miniapp |
Start the MiniApp development server |
npm run build:miniapp |
Build the MiniApp for production |
npm run start:miniapp |
Start the production MiniApp server |
npm run build:hardhat |
Compile smart contracts |
npm run test:hardhat |
Run smart contract tests |
npm run deploycounter:hardhat |
Deploy Counter contract to Base Sepolia |
npm run clean |
Remove all node_modules |
npm run install:all |
Install dependencies for all packages |
The Hardhat package handles smart contract development and deployment for CoFHE-enabled contracts.
- CoFHE Integration: Uses
@luxfheprotocol/fhe-contractsfor FHE operations - Network Support: Configured for Ethereum Sepolia, Arbitrum Sepolia, and Base Sepolia
- Testing: Includes test suite for encrypted operations
- Deployment Tasks: Custom Hardhat tasks for contract interaction
The example contract demonstrates FHE operations:
contract Counter {
euint32 public count; // Encrypted counter value
function increment() public { /* ... */ }
function decrement() public { /* ... */ }
function reset(InEuint32 memory value) public { /* ... */ }
}Key FHE Operations:
FHE.asEuint32()- Convert plaintext to encrypted valueFHE.add()/FHE.sub()- Arithmetic on encrypted valuesFHE.allowSender()- Grant decryption permissionsFHE.decrypt()- Request decryption (async operation)
cd packages/hardhat
# Compile contracts
npx hardhat compile
# Run tests
npx hardhat test
# Deploy to Base Sepolia
npx hardhat deploy-counter --network base-sepolia
# Interact with deployed contract
npx hardhat increment-counter --network base-sepolia
npx hardhat reset-counter --network base-sepoliacontracts/Counter.sol- Example FHE contracttasks/- Custom Hardhat tasks for deployment and interactiontest/Counter.test.ts- Contract test suitedeployments/base-sepolia.json- Deployed contract addresses
A Next.js application that integrates with Farcaster MiniApps and demonstrates the complete CoFHE workflow.
The MiniApp demonstrates a complete privacy-preserving workflow with 5 key steps:
File: src/app/page.tsx
Users authenticate via Farcaster and connect their wallet:
const { isFrameReady, context } = useMiniKit();
const { isConnected } = useAccount();Features:
- Farcaster Frame authentication
- Wallet connection via OnchainKit
- Access to user's Farcaster profile (FID, display name)
File: src/hooks/useFHE.ts
Initialize the CoFHE client to enable encryption/decryption operations:
const { isInitialized, isInitializing } = useFHE();What happens:
- Connects to CoFHE network (verifier, coprocessor, threshold network)
- Downloads FHE public keys
- Initializes encryption/decryption capabilities
- Sets up the viem client for blockchain interaction
Key Configuration:
await fhe.initializeWithViem({
viemClient: publicClient,
viemWalletClient: walletClient,
environment: "TESTNET",
generatePermit: false, // Permits generated manually
});File: src/hooks/usePermit.ts
Create a permit that grants permission to encrypt/decrypt specific values:
const { hasValidPermit, generatePermit } = usePermit();
// Generate a new permit
await generatePermit();What is a Permit?
- A cryptographic proof that grants encryption rights
- Signed by the user's wallet
- Has an expiration date (30 days in this template)
- Stored locally in browser storage
- Required before you can encrypt/decrypt any encrypted values
Permit Configuration:
await fhe.createPermit({
type: "self",
name: "MiniApp Name",
issuer: userAddress,
expiration: Math.round(Date.now() / 1000) + (30 * 24 * 60 * 60),
});File: src/components/FHECounter.tsx
Encrypt user input and send it to the smart contract:
// 1. Encrypt the user's input
const encryptedInput = await fhe.encrypt([
Encryptable.uint32(userValue)
]);
// 2. Send encrypted value to contract
const calls: ContractFunctionParameters[] = [{
address: CONTRACT_ADDRESS,
abi: CONTRACT_ABI,
functionName: "reset",
args: [encryptedInput.data?.[0]],
}];
// 3. Execute transaction via OnchainKit
<Transaction calls={calls} onStatus={handleOnStatus}>
<TransactionButton />
</Transaction>Key Points:
- User input is encrypted client-side before being sent
- The blockchain never sees the plaintext value
- The contract can perform operations on the encrypted data
- Uses OnchainKit's Transaction components for seamless UX
File: src/components/FHECounter.tsx (EncryptedValue component)
Decrypt encrypted values locally for display:
// Read encrypted value from contract
const { data: count } = useReadContract({
address: CONTRACT_ADDRESS,
abi: CONTRACT_ABI,
functionName: "count",
});
// Decrypt locally using CoFHE
const decryptedValue = await fhe.unseal(ctHash, FheTypes.Uint32);
if (decryptedValue.success) {
// Display the decrypted value
setValue(decryptedValue.data!.toString());
}Key Points:
- Decryption happens client-side using the permit
- Only users with valid permits can decrypt
- The contract must grant permission via
FHE.allowSender() - Decryption requires interaction with CoFHE decryption service
Privacy Model:
- The encrypted value (
count) is public on-chain - Only authorized users can decrypt it locally
- The decrypted value is never transmitted to the blockchain
The main component demonstrating all FHE operations:
- SetCounterRow - Encrypts and sets new values
- IncrementButton - Increments encrypted counter
- DecrementButton - Decrements encrypted counter
- EncryptedValue - Displays and decrypts encrypted values
- EncryptedCounterDisplay - Wrapper for counter display
Manages CoFHE initialization:
- Auto-initializes when wallet connects
- Resets on chain/account changes
- Provides encryption/decryption functions
- Manages permit generation
Manages permit lifecycle:
- Check for existing permits
- Generate new permits
- Remove/revoke permits
- Validate permit expiration
Global state management:
interface FHEStore {
isInitialized: boolean;
setIsInitialized: (value: boolean) => void;
}Sets up the application context:
- Wagmi configuration for Base Sepolia
- OnchainKit provider for transactions
- React Query for data fetching
- Farcaster MiniApp connector
cd packages/miniapp
# Start development server
npm run dev
# Build for production
npm run build
# Start production server
npm start
# Lint code
npm run lintThe MiniApp can be deployed to Vercel, Netlify, or any platform supporting Next.js:
- Build the application:
npm run build - Deploy the
.nextfolder - Ensure environment variables are set
- Update the MiniKit config with your production URL
- Main Docs: https://docs.luxfhe.zone
- Base Docs: https://docs.base.org
- OnchainKit: https://onchainkit.xyz
- MiniApp SDK: https://docs.farcaster.xyz/developers/frames/v2/miniapps
- Farcaster Docs: https://docs.farcaster.xyz
- CoFHE Contracts: https://github.com/LuxFHEProtocol/fhe-contracts
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USER JOURNEY β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1. π AUTHENTICATION
User connects Farcaster account + Web3 wallet
β
2. βοΈ INITIALIZE COFHE
Download FHE keys and setup encryption/decryption
β
3. π GENERATE PERMIT
Create cryptographic proof for decryption rights
β
4. π ENCRYPT & TRANSACT
Encrypt data locally β Send to smart contract β Contract operates on encrypted data
β
5. π DECRYPT & DISPLAY
Read encrypted value from chain β Decrypt locally using permit β Display to user
- Client-Side Encryption: User data is encrypted in the browser before transmission
- On-Chain Privacy: The blockchain only stores encrypted values
- Permissioned Decryption: Only users with valid permits can decrypt
- Local Decryption: Decryption happens client-side, not on-chain
- Compute on Encrypted Data: Smart contracts can perform operations without seeing plaintext
CoFHE won't initialize
- Ensure you're connected to a supported network (Base Sepolia)
- Check that your wallet is properly connected
- Clear browser storage and reconnect
Permit generation fails
- Verify wallet connection
- Check that CoFHE is initialized
- Ensure you're on the correct network
Decryption fails
- Confirm you have a valid permit
- Verify you've interacted with the contract (needed for permission)
- Check that
FHE.allowSender()was called in the contract
Transaction fails
- Ensure you have sufficient testnet funds
- Verify contract address is correct
- Check network connectivity
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
For questions and support:
- LuxFHE Discord: https://discord.gg/luxfhe
- LuxFHE Telegram: https://t.me/LuxFHEOfficial
- Documentation: https://docs.luxfhe.zone