Skip to main content

Blocklock Encryption

Conditional encryption is a powerful cryptographic technique that keeps data encrypted until specific conditions are met. The dcipher network's blocklock encryption implements this concept on-chain, allowing data to remain encrypted until predefined conditions are satisfied, which is a block number in this case. This encryption is crucial for many blockchain applications, from sealed-bid auctions to delayed-reveal NFTs.

While this quickstart focuses on blocklock, where the decryption condition is reaching a specific blockchain height (block number), that is only one type of conditional encryption. The dcipher network also supports various types of conditions, including:

  • Block height/number based conditions
  • Timestamp based conditions
  • Oracle-driven conditions
  • Multi-condition combinations

This quickstart shows you how to integrate blocklock encryption into your Solidity smart contracts using the blocklock-solidity and blocklock-js libraries.

🚀 This Quickstart Walks You Through:

  1. Setting up a new Hardhat project
  2. Installing the blocklock-solidity & blocklock-js libraries
  3. Writing a customized receiver contract to handle blocklock decryption requests
  4. Deploying to Base Sepolia with pre-deployed blocklockSender contract
  5. Simulating an encrypted payload with block height condition

🧰 Prerequisites​

Before you begin, ensure you have the following:

  1. Development Environment

    • Node.js (v16 or later)
    • npm (v7 or later)
  2. Blockchain Tools

    • A wallet with test ETH (for Base Sepolia)
    • Basic knowledge of Solidity and smart contracts
    • Familiarity with Hardhat or similar development frameworks
  3. Network Requirements

    • Access to the Base Sepolia RPC endpoint
    • Test ETH for deployment and testing

1. Set Up a New Hardhat Project​

mkdir my-blocklock-app && cd my-blocklock-app
npm init -y
npm install --save-dev hardhat
npx hardhat init

Choose "Create a TypeScript project" when prompted and follow the instructions to finish the init process.

💡 Note: Besides Hardhat, you can also choose other popular frameworks to create the smart contract project, such as Foundry.

2. Install the blocklock-solidity Library​

To extend the blocklock functions, we need to first install blocklock-solidity library into your project.

npm install blocklock-solidity

Then we can build a smart contract to extend the AbstractBlocklockReceiver contract and use supporting utilities.

3. Create a BlocklockReceiver Contract​

In this step, you'll create a Solidity contract that implements the functions to request the blocklock encryption and receive the callback from the dcipher network. We'll start by creating a new file: contracts/MyBlocklockReceiver.sol.

This contract must import the necessary types and inherit from the AbstractBlocklockReceiver from blocklock-solidity library. The constructor takes the address of a deployed BlocklockSender(Proxy) contract, which serves as the entry point for registering encrypted payloads and triggering blocklock decryption. Check Networks page for supported chains and corresponding contract addresses.

Funding models

The dcipher network supports two ways to pay for blocklock encryption and decryption services:

  • Direct Funding: Users send native tokens (e.g. ETH) directly when making a blocklock encryption request.
  • Subscription Account: A pre-funded account balance is used to cover multiple requests over time.

In this example, we'll use direct funding for simplicity. This means the user must send native tokens along with the blocklock encryption request to cover callback gas fee when the decryption key is delivered. You can also customize the payment in createTimelockRequestWithDirectFunding for calling _requestBlocklockPayInNative.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {TypesLib} from "blocklock-solidity/src/libraries/TypesLib.sol";
import {AbstractBlocklockReceiver} from "blocklock-solidity/src/AbstractBlocklockReceiver.sol";

contract MyBlocklockReceiver is AbstractBlocklockReceiver {
uint256 public requestId;
TypesLib.Ciphertext public encryptedValue;
uint256 public decryptedValue;

constructor(address blocklockSender) AbstractBlocklockReceiver(blocklockSender) {}

function createTimelockRequestWithDirectFunding(
uint32 callbackGasLimit,
bytes calldata condition,
TypesLib.Ciphertext calldata encryptedData
) external payable returns (uint256, uint256) {
// create timelock request
(uint256 _requestId, uint256 requestPrice) =
_requestBlocklockPayInNative(callbackGasLimit, condition, encryptedData);
// store request id
requestId = _requestId;
// store Ciphertext
encryptedValue = encryptedData;
return (requestId, requestPrice);
}

function _onBlocklockReceived(uint256 _requestId, bytes calldata decryptionKey) internal override {
require(requestId == _requestId, "Invalid request id.");
// decrypt stored Ciphertext with decryption key
decryptedValue = abi.decode(_decrypt(encryptedValue, decryptionKey), (uint256));
// Placeholder for builders to add any logic to consume the decrypted data in smart contract.
}
}

🧠 Explanation

CodePurpose
AbstractBlocklockReceiverBase contract from the blocklock-solidity library that handles the blocklock request from the dcipher network.
constructor(address blocklockSender)Initializes the receiver contract and links it to the deployed BlocklockSender(Proxy) contract on the target network.
createTimelockRequestWithDirectFunding(...)Override this function to register a new blocklock encryption request using direct funding. This must be payble function.
_onBlocklockReceived(...)Override this function with your project logic consuming blocklock. It will be invoked automatically by the dcipher network when the decryption key becomes available after the condition is met.

4. Deploy the Contract​

We will use Hardhat Ignition to manage smart contract deployments. Let's create a file ignition/modules/MyBlocklockReceiver.ts with the following code to deploy MyBlocklockReceiver.

💡 Note: Use a network where BlocklockSender is deployed (e.g, Base Sepolia, Filecoin Calibration, Polygon, etc.). Check the Networks page for the deployed BlockLockSender contract address. In this quickstart, we have used the Base Sepolia deployed contract.

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

const DEFAULT_SENDER = "0xBlockLockSenderAddressOnBase";

const BlockLockModule = buildModule("BlockLockModule", (m) => {
// Get the sender parameter with a default value
const blocklockSenderAddress = m.getParameter("sender", DEFAULT_SENDER);
const blockReceiver = m.contract("MyBlocklockReceiver", [blocklockSenderAddress]);

return { blockReceiver };
});

export default BlockLockModule;

Now, let's add the desired network to hardhat.config.ts, we are using Base Sepolia as an example here.

module.exports = {
solidity: "0.8.28",
networks: {
baseSepolia: {
url: "https://sepolia.base.org/",
chainId: 84532,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
}
}
}

Add your private key for the deployer wallet with test tokens funded. You can get Base Sepolia test tokens from these faucets.

Create a new .env file, as shown below, to manage your secrets and prevent them from being leaked.

# Network RPC URLs
BASE_SEPOLIA_RPC_URL=https://sepolia.base.org

# Private Key (without 0x prefix)
PRIVATE_KEY=13c2fb33d069aece5f674ec27ef9077104397f04c6393ef5159c6ac3ca7a6a41

Deploy using:

npx hardhat ignition deploy ./ignition/modules/MyBlocklockReceiver.ts --network baseSepolia --parameters '{"sender":"0x82Fed730CbdeC5A2D8724F2e3b316a70A565e27e"}'

5. Simulate a Blocklock Request​

In practice, the data encryption is done off-chain using blocklock-js. So, let's install the JS library to your project.

npm install blocklock-js

For the purpose of quickstart, we will create a script to simulate the blocklock encryption request.

  1. Connect to your deployed MyBlocklockReceiver contract
  2. Prepare the blocklock encryption payload, including blockHeight, encrypted message, callBack gaslimt and price, etc.
  3. Call the myBlocklockReceiver contract to request blocklock encryption with direct funding.

Let's create a scripts\requestBlocklock.ts with the following code, and update 0xMyBlocklockReceiverContractAddress with the Blocklock Receiver Contract Address you just deployed.

import { ethers } from "hardhat";
import { getBytes, Signer } from "ethers";
import { Blocklock, encodeCiphertextToSolidity, encodeCondition } from "blocklock-js";
import { MyBlocklockReceiver } from "../typechain-types";

async function main() {
// Get the signer from hardhat config
const [signer] = await ethers.getSigners();

// 1. Connect to the deployed myBlocklockReceiver contract
const contractAddress = '0xMyBlocklockReceiverContractAddress';
const ContractFactory = await ethers.getContractFactory("MyBlocklockReceiver");
const contract = ContractFactory.connect(signer).attach(contractAddress) as MyBlocklockReceiver;

// 2. Create blocklock request payload
// Set block height for blocklock decryption (current block + 10)
const blockHeight = BigInt(await ethers.provider.getBlockNumber() + 10);
const conditionBytes = encodeCondition(blockHeight);

// Set the message to encrypt
const msg = ethers.parseEther("8"); // Example: BigInt for blocklock ETH transfer
const msgBytes = ethers.AbiCoder.defaultAbiCoder().encode(["uint256"], [msg]);
const encodedMessage = getBytes(msgBytes);

// Encrypt the encoded message usng Blocklock.js library
const blocklockjs = Blocklock.createBaseSepolia(signer as unknown as Signer);
const cipherMessage = blocklockjs.encrypt(encodedMessage, blockHeight);

// Set the callback gas limit and price
// Best practice is to estimate the callback gas limit e.g., by extracting gas reports from Solidity tests
const callbackGasLimit = 700_000n;
// Based on the callbackGasLimit, we can estimate the request price by calling BlocklockSender
// Note: Add a buffer to the estimated request price to cover for fluctuating gas prices between blocks
const requestCallBackPrice = await blocklockjs.calculateRequestPriceNative(callbackGasLimit)

console.log("Target block for unlock:", blockHeight);
console.log("Callback gas limit:", callbackGasLimit);
console.log("Request CallBack price:", ethers.formatEther(requestCallBackPrice), "ETH");

//Ensure wallet has enought token to cover the callback fee
const balance = await ethers.provider.getBalance(signer.address);
console.log("Wallet balance:", ethers.formatEther(balance), "ETH");
if (balance < requestCallBackPrice) {
throw new Error(`Insufficient balance. Need ${ethers.formatEther(requestCallBackPrice)} ETH but have ${ethers.formatEther(balance)} ETH`);
}

// 3. Invoke myBlocklockReceiver contract to request blocklock encryption with direct funding.
console.log("Sending transaction...");
const tx = await contract.createTimelockRequestWithDirectFunding(
callbackGasLimit,
conditionBytes,
encodeCiphertextToSolidity(cipherMessage),
{ value: requestCallBackPrice }
);

console.log("Transaction sent, waiting for confirmation...");
const receipt = await tx.wait(1);
if (!receipt) {
throw new Error("Transaction failed");
}
console.log("BlockLock requested in tx:", receipt.hash);
}

main().catch((err) => {
console.error("Invocation failed:", err);
process.exitCode = 1;
});

Run the script with:

npx hardhat run scripts/requestBlocklock.ts

💡 Note: Best Practice is to estimate callback gas limit and request price dynamically based on calldata size and network. You can implement gas estimation based on your contract and network, rather than being hardcoded. For instance, using the Hardhat gas reporter.

📌 What Happens Next?​

Once the request is submitted:

  • The MyBlocklockReceiver contract stores your encrypted payload and tracks the request ID.
  • The dcipher network monitors the chain. Once the target block height is reached, it will deliver the decryption key by calling the _onBlocklockReceived() callback in your contract.
  • Your contract will then decrypt the payload and store or act on the decrypted result.

To simulate the blocklock request and send the transaction to your contract, use the following Hardhat command. Ensure you have sufficient test token balance in your wallet:

npx hardhat run scripts/requestBlocklock.ts --network baseSepolia

# Output example
Current block: 26387874
Target block for unlock: 26387884n
Callback gas limit: 700000n
Request CallBack price: 0.003 ETH
Wallet balance: 0.033797044091982776 ETH
Sending transaction...
Transaction sent, waiting for confirmation...
BlockLock requested in tx: 0xe64958da7f81498891f5f5665caecee758dfa301c24a999823195ec932ef90ef

6. Check the Blocklock Request​

Once you've successfully submitted a blocklock request, the hard part is done — now it's time to wait for the unlock condition to be met.

In our example, the condition is a block height set 10 blocks into the future. Once the blockchain reaches or exceeds that height, the dcipher network will automatically:

  1. Generate the required decryption key
  2. Call back into your smart contract via _onBlocklockReceived(...)
  3. Deliver the decryption key to your contract
  4. Trigger decryption and the blocklock encrypted value will be available on-chain

Let's also create scripts\getDecryptedValue.ts to check the decrypted value in your smart contract. Make sure you use the contract address you just deployed.

import { ethers } from "hardhat";
import { MyBlocklockReceiver } from "../typechain-types";

async function main() {
// Get the signer from hardhat config
const [signer] = await ethers.getSigners();

const contractAddress = '0xMyBlocklockReceiverContractAddress';
const ContractFactory = await ethers.getContractFactory("MyBlocklockReceiver");
const contract = ContractFactory.connect(signer).attach(contractAddress) as MyBlocklockReceiver;

const plainTextValue = await contract.plainTextValue();
console.log("Unlocked value:", plainTextValue);
}

main().catch((err) => {
console.error("Invocation failed:", err);
process.exitCode = 1;
});

After the blocklock condition is met, let's run the script to check the decrypted value in your smart contract.

npx hardhat run scripts/getDecryptedValue.ts --network baseSepolia

# Output example
Unlocked value: 15000000000000000000n

🧠 Summary​

By following this quickstart, we've built a complete hardhat project using the blocklock encryption from the dcipher network using both blocklock-solidity and blocklock-js libraries.

This setup provides a solid foundation to build more complex features, including encrypted auctions, sealed bids, private on-chain voting, delayed asset unlocks, and more. You can extend your contracts with richer logic, build a frontend for monitoring requests, or implement off-chain services to coordinate encrypted interactions at scale.

👉 Next steps:

  • Try different types of encrypted payloads (e.g., strings, addresses, structs)
  • Add support for subscription-based funding
  • Emit events for external monitoring or analytics
  • Explore advanced blocklock-js usage such as custom condition encoders.