zama/gateway-decrypt

Relayer SDK & Gateway Decrypt

zamatechnical-doc👥 Communityconfidence mediumhealth 100%
v1.0.0·Updated 4/5/2026

Use this skill when building frontend TypeScript apps that interact with fhEVM contracts, or when writing Hardhat tests for confidential contracts.


Relayer SDK — Installation & Setup

npm install @zama-fhe/relayer-sdk
import { createInstance, SepoliaConfig } from "@zama-fhe/relayer-sdk";

const instance = await createInstance(SepoliaConfig);

Available configs: SepoliaConfig, MainnetConfig.


Encrypt User Input

const encrypted = await instance
  .createEncryptedInput(contractAddress, userAddress)
  .add64(transferAmount)   // match Solidity type: add8 / add16 / add32 / add64 / add128 / add256
  .encrypt();

// Pass to contract:
await contract.myFunction(encrypted.handles[0], encrypted.inputProof);

Multiple values in one input:

const enc = await instance
  .createEncryptedInput(contractAddress, userAddress)
  .add64(amount)
  .addBool(isActive)
  .encrypt();

// enc.handles[0] → amount handle
// enc.handles[1] → isActive handle
await contract.myFunction(enc.handles[0], enc.handles[1], enc.inputProof);

Type mapping (Solidity → SDK method):

Solidity typeSDK method
externalEuint8.add8(value)
externalEuint16.add16(value)
externalEuint32.add32(value)
externalEuint64.add64(value)
externalEuint128.add128(value)
externalEuint256.add256(value)
externalEbool.addBool(value)
externalEaddress.addAddress(value)

User Decrypt

The user signs a decryption request — only they can see the result.

// Get handle from contract
const handle = await contract.getBalance(userAddress);

// Decrypt — requires user's ethers.js signer
const clearValue = await instance.userDecrypt(
  handle,
  contractAddress,
  signer         // ethers.js Signer
);
console.log("Balance:", clearValue); // bigint

Public Decrypt (after makePubliclyDecryptable)

Used for auction resolution, voting reveals, etc.

const results = await instance.publicDecrypt([encryptedHandle]);
// results.abiEncodedClearValues — pass to resolveAuction() etc.
// results.decryptionProof       — verification proof

await contract.resolveAuction(results.abiEncodedClearValues, results.decryptionProof);

Hardhat Testing

import { ethers, fhevm } from "hardhat";
import { FhevmType } from "@fhevm/hardhat-plugin";

it("test encrypted counter", async function () {
  const [alice] = await ethers.getSigners();
  const contract = await ethers.deployContract("FHECounter");
  const address = await contract.getAddress();

  // Encrypt input
  const enc = await fhevm
    .createEncryptedInput(address, alice.address)
    .add32(5)
    .encrypt();

  await contract.connect(alice).increment(enc.handles[0], enc.inputProof);

  // Decrypt and verify
  const handle = await contract.getCount();
  const value = await fhevm.userDecryptEuint(FhevmType.euint32, handle, address, alice);
  expect(value).to.equal(5n);
});

userDecryptEuint type mapping:

Solidity typeFhevmType
euint8FhevmType.euint8
euint16FhevmType.euint16
euint32FhevmType.euint32
euint64FhevmType.euint64
euint128FhevmType.euint128
euint256FhevmType.euint256

Public decrypt in tests:

const results = await fhevm.publicDecrypt([encryptedHandle]);
await contract.resolveAuction(results.abiEncodedClearValues, results.decryptionProof);

Hardhat config (hardhat.config.ts):

import "@fhevm/hardhat-plugin";

const config: HardhatUserConfig = {
  solidity: "0.8.27",
  networks: {
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL,
      accounts: [process.env.PRIVATE_KEY!],
    },
  },
};