Skip to main content
This example demonstrates a full interaction between a dApp and an FHE-enabled smart contract using cofhejs. You’ll learn how to set up the client, encrypt data, send it to the contract, create a permit for accessing sealed data, and finally unseal the returned data for the user.

Prerequisites

Before you begin, ensure you have:
  • Node.js installed (version 18 or higher)
  • Ethers.js or Viem installed in your project
  • A deployed FHE-enabled smart contract

Complete Flow

The example demonstrates:
  1. Initializing Cofhejs with a provider and signer
  2. Encrypting input data before sending to the contract
  3. Interacting with an FHE-enabled smart contract
  4. Creating permits for data access
  5. Unsealing encrypted output data

Example Implementation

Follow these steps to implement a complete FHE interaction workflow:
1

Initialize dependencies and provider

Import required modules and set up your web3 provider. Connect to your local Fhenix network or testnet.
const { cofhejs, FheTypes, Encryptable } = require("cofhejs/node");
const { ethers } = require("ethers");

// Connect to your local Fhenix network or testnet
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:42069");
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
You should have a connected provider and wallet/signer ready for use.
2

Initialize Cofhejs

Initialize the Cofhejs client with your provider and signer. This sets up the FHE encryption capabilities and loads the chain key automatically.
const initResult = await cofhejs.initializeWithEthers({
	ethersProvider: provider,
	ethersSigner: wallet,
	environment: "TESTNET"
});

if (!initResult.success) {
	console.error("Failed to initialize cofhejs:", initResult.error);
	process.exit(1);
}
Cofhejs is initialized and ready to encrypt and decrypt data.
3

Set up contract instance

Connect to your deployed FHE-enabled smart contract using the contract address and ABI.
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, wallet);
You have a contract instance ready for interaction.
4

Configure encryption state logging

Set up a callback function to track encryption progress. This helps provide better user experience during encryption operations.
const logState = (state) => {
	console.log(`Encryption State: ${state}`);
	// In browser: updateEncryptionProgress(state);
};
The encryption state callback receives states like: Extract, Pack, Prove, Verify, Replace, and Done.
Now that you’ve completed the setup, here’s the complete implementation example:
const { cofhejs, FheTypes, Encryptable } = require("cofhejs/node");
const { ethers } = require("ethers");

// Initialize your web3 provider
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:42069");
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// Initialize cofhejs Client with ethers
const initResult = await cofhejs.initializeWithEthers({
	ethersProvider: provider,
	ethersSigner: wallet,
	environment: "TESTNET"
});

if (!initResult.success) {
	console.error("Failed to initialize cofhejs:", initResult.error);
	process.exit(1);
}

// Set up the contract instance
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, wallet);

// Set up encryption state logging callback
const logState = (state) => {
	console.log(`Encryption State: ${state}`);
};

// Step 5: Function to read decrypted counter value
// This reads the counter after it has been decrypted on-chain
const readCounterDecryptedValue = async () => {
	try {
		const result = await contract.get_counter_value();
		console.log("Decrypted counter value:", result.toString());
		return result;
	} catch (error) {
		console.error("Error reading decrypted counter:", error);
		throw error;
	}
};

// Step 6: Function to read encrypted counter value and unseal it
// This demonstrates the unsealing process for encrypted data
const readCounterEncryptedValue = async () => {
	try {
		// Get the encrypted counter value from the contract
		const encryptedResult = await contract.get_encrypted_counter_value();
		console.log("Encrypted counter value:", encryptedResult);

		// Step 7: Create a permit to authorize unsealing
		// The permit proves you own the private key to decrypt this data
		const permitResult = await cofhejs.createPermit({
			type: "self",
			issuer: wallet.address
		});

		if (!permitResult.success) {
			console.error("Failed to create permit:", permitResult.error);
			return;
		}

		const permit = permitResult.data;

		// Step 8: Unseal the encrypted value
		// When creating a permit, cofhejs will use it automatically,
		// but you can pass it manually as well for explicit control
		const unsealResult = await cofhejs.unseal(
			encryptedResult,
			FheTypes.Uint64,
			permit.data.issuer,
			permit.data.getHash()
		);

		if (!unsealResult.success) {
			console.error("Failed to unseal counter:", unsealResult.error);
			return;
		}

		console.log("Unsealed counter value:", unsealResult.data.toString());
		return unsealResult.data;
	} catch (error) {
		console.error("Error reading encrypted counter:", error);
		throw error;
	}
};

// Step 9: Function to increment the counter
// This adds 1 to the encrypted counter value on-chain
const incrementCounter = async () => {
	try {
		const tx = await contract.increment_counter();
		console.log("Increment transaction hash:", tx.hash);
		
		// Wait for transaction confirmation
		const receipt = await tx.wait();
		console.log("Transaction confirmed in block:", receipt.blockNumber);
		
		return receipt;
	} catch (error) {
		console.error("Error incrementing counter:", error);
		throw error;
	}
};

// Step 10: Function to reset the counter with an encrypted value
// This demonstrates encrypting input data before sending to the contract
const resetCounter = async (encryptedValue) => {
	try {
		// Send the encrypted value to the contract
		const tx = await contract.reset_counter(encryptedValue);
		console.log("Reset counter transaction hash:", tx.hash);
		
		// Wait for transaction confirmation
		const receipt = await tx.wait();
		console.log("Transaction confirmed in block:", receipt.blockNumber);
		
		return receipt;
	} catch (error) {
		console.error("Error resetting counter:", error);
		throw error;
	}
};

// Step 11: Function to decrypt the counter on-chain
// This requests the contract to decrypt the counter value
const decryptCounter = async () => {
	try {
		const tx = await contract.decrypt_counter();
		console.log("Decrypt counter transaction hash:", tx.hash);
		
		// Wait for transaction confirmation
		const receipt = await tx.wait();
		console.log("Transaction confirmed in block:", receipt.blockNumber);
		
		return receipt;
	} catch (error) {
		console.error("Error decrypting counter:", error);
		throw error;
	}
};

// ============================================
// Example Usage Flow
// ============================================

async function main() {
	console.log("=== Starting End-to-End Example ===\n");

	try {
		// Step 1: Read initial counter value
		// Expected: 0 (or uninitialized)
		console.log("1. Reading initial counter value...");
		const initialValue = await readCounterDecryptedValue();
		console.log(`   ✓ Initial value: ${initialValue.toString()}`);

		// Step 2: Increment the counter
		// Adds 1 to the encrypted counter value on-chain
		console.log("\n2. Incrementing counter...");
		await incrementCounter();
		console.log("   ✓ Counter incremented successfully");

		// Step 3: Read and unseal encrypted counter
		// Expected: 1 (after unsealing)
		console.log("\n3. Reading encrypted counter value...");
		const unsealedValue1 = await readCounterEncryptedValue();
		console.log(`   ✓ Unsealed value: ${unsealedValue1.toString()}`);

		// Step 4: Increment again
		console.log("\n4. Incrementing counter again...");
		await incrementCounter();
		console.log("   ✓ Counter incremented successfully");

		// Step 5: Decrypt the counter on-chain
		console.log("\n5. Decrypting counter on-chain...");
		await decryptCounter();
		console.log("   ✓ Counter decrypted on-chain");

		// Step 6: Read the decrypted value
		// Expected: 2
		console.log("\n6. Reading decrypted counter value...");
		const decryptedValue = await readCounterDecryptedValue();
		console.log(`   ✓ Decrypted value: ${decryptedValue.toString()}`);

		// Step 7: Encrypt new value and reset counter
		console.log("\n7. Encrypting new value (10) and resetting counter...");
		const encryptResult = await cofhejs.encrypt([Encryptable.uint64(10n)], logState);
		
		if (!encryptResult.success) {
			console.error("Failed to encrypt value:", encryptResult.error);
			return;
		}

		await resetCounter(encryptResult.data[0]);
		console.log("   ✓ Counter reset with encrypted value 10");

		// Step 8: Read encrypted counter after reset
		// Expected: 10 (after unsealing)
		console.log("\n8. Reading encrypted counter value after reset...");
		const unsealedValue2 = await readCounterEncryptedValue();
		console.log(`   ✓ Unsealed value: ${unsealedValue2.toString()}`);

		console.log("\n=== Example completed successfully ===");
	} catch (error) {
		console.error("\n=== Example failed ===");
		console.error("Error:", error);
		process.exit(1);
	}
}

// Run the example
main();

Expected Output

When you run this example, you should see output similar to:
=== Starting End-to-End Example ===

1. Reading initial counter value...
   ✓ Initial value: 0

2. Incrementing counter...
   ✓ Counter incremented successfully

3. Reading encrypted counter value...
   ✓ Unsealed value: 1

4. Incrementing counter again...
   ✓ Counter incremented successfully

5. Decrypting counter on-chain...
   ✓ Counter decrypted on-chain

6. Reading decrypted counter value...
   ✓ Decrypted value: 2

7. Encrypting new value (10) and resetting counter...
   ✓ Counter reset with encrypted value 10

8. Reading encrypted counter value after reset...
   ✓ Unsealed value: 10

=== Example completed successfully ===

Key Concepts Demonstrated

This example showcases several important concepts:
  1. Initialization: Setting up Cofhejs with a provider and signer
  2. Encryption: Encrypting input data before sending to smart contracts
  3. Contract Interaction: Calling functions on FHE-enabled smart contracts
  4. Permit Creation: Creating permits to authorize data access
  5. Unsealing: Decrypting encrypted output data using permits
  6. Error Handling: Properly handling errors throughout the flow
You can modify the example to work with your own FHE-enabled contract by updating the CONTRACT_ADDRESS and CONTRACT_ABI variables.

Next Steps