@inco/lightning-js
v1.0.2
Published
TypeScript SDK for building privacy-preserving dapps on Inco Lightning.
Readme
@inco/lightning-js
TypeScript SDK for building privacy-preserving dapps on Inco Lightning. Encrypt values client-side, post ciphertexts to smart contracts for confidential computation, and retrieve attested decryptions — all with post-quantum hybrid encryption (X-Wing KEM).
Installation
npm install @inco/lightning-js
# or
yarn add @inco/lightning-js
# or
bun add @inco/lightning-jsPeer dependency: viem ^2.39.3 is used for Ethereum interactions.
Note: The library has currently only been tested with Webpack and Next.js. If you encounter issues with Rollup or Vite, please open an issue.
Quick Start
A typical workflow has three steps:
- Encrypt a value client-side
- Submit the ciphertext to a smart contract for confidential computation
- Decrypt the result via attested decryption
import { getViemChain, supportedChains, handleTypes } from '@inco/lightning-js';
import { Lightning } from '@inco/lightning-js/lite';
import { createWalletClient } from 'viem';
// 1. Setup
const chainId = supportedChains.baseSepolia;
const zap = await Lightning.baseSepoliaTestnet();
const walletClient = createWalletClient({
chain: getViemChain(chainId),
account: /* your account */,
transport: /* your transport */,
});
const dappAddress = '0x...'; // Your contract address
// 2. Encrypt
const ciphertext = await zap.encrypt(42n, {
accountAddress: walletClient.account.address,
dappAddress,
handleType: handleTypes.euint256,
});
// 3. Submit ciphertext to contract via viem writeContract (not SDK-specific)
// 4. Decrypt the result
const resultHandle = '0x...'; // Retrieved from the contract
const [attestation] = await zap.attestedDecrypt(walletClient, [resultHandle]);
console.log(attestation.plaintext.value);Package Exports
| Import Path | Description |
| --------------------------------- | --------------------------------------------------------------------------------- |
| @inco/lightning-js | Core utilities: binary helpers, chain definitions, handle types, viem integration |
| @inco/lightning-js/lite | Main SDK: Lightning class, encryption, attested decrypt/compute, session keys |
| @inco/lightning-js/encryption | Encryption types, plaintext/ciphertext conversions, scheme constants |
| @inco/lightning-js/reencryption | Reencryption types, EIP-712 payload creation |
| @inco/lightning-js/abis | Generated smart contract ABIs (Lightning, Verifier) |
API Reference
Lightning Class
Import: import { Lightning } from '@inco/lightning-js/lite'
The primary entry point for all SDK operations. Binds to a specific Inco Lightning deployment and provides encryption, attested decryption, attested compute, and session key management.
Factory Methods
The constructor is private. Use one of the static factory methods. Prefer the network-specific factories — they bind to the latest deployment for that network:
// Connect to the latest deployment on a known network
const zap = await Lightning.baseSepoliaTestnet(); // Base Sepolia testnet
const zap = await Lightning.baseMainnet(); // Base mainnet
// Provide your own RPC endpoint(s) (recommended for production).
// When more than one URL is given, they are wrapped in viem's `fallback()`
// transport for automatic failover.
const zap = await Lightning.baseSepoliaTestnet({
hostChainRpcUrls: ["https://your-rpc-url"],
});Both network factories (baseSepoliaTestnet, baseMainnet) accept an optional { hostChainRpcUrls?: readonly string[] } argument.
| Method | Returns | Description |
| ---------------------------------------- | -------------------- | ----------------------------------------- |
| Lightning.baseSepoliaTestnet(options?) | Promise<Lightning> | Latest deployment on Base Sepolia (84532) |
| Lightning.baseMainnet(options?) | Promise<Lightning> | Latest deployment on Base mainnet (8453) |
Local development: to run against a local Anvil + covalidator node, see the quick start guide and tutorials in the Inco docs.
Properties
| Property | Type | Description |
| ----------------- | --------- | -------------------------------------------- |
| executorAddress | Address | The executor contract address |
| chainId | bigint | The host chain ID |
| deployment | T | Shallow copy of the deployment configuration |
encrypt(value, context)
Encrypts a plaintext value using the network's public key.
async encrypt<T extends boolean | bigint | number>(
value: T,
context: {
accountAddress: string; // EOA interacting with the dapp
dappAddress: string; // Contract address
handleType: TeeType; // Required — the TEE handle type to encrypt as
}
): Promise<HexString>Recommended handle type per value type:
| Value Type | Handle Type | Example |
| ------------------- | ---------------------- | ------------------------------------------------------------------------- |
| boolean | handleTypes.ebool | zap.encrypt(true, { ...ctx, handleType: handleTypes.ebool }) |
| bigint / number | handleTypes.euint256 | zap.encrypt(42n, { ...ctx, handleType: handleTypes.euint256 }) |
| bigint (address) | handleTypes.euint160 | zap.encrypt(BigInt(addr), { ...ctx, handleType: handleTypes.euint160 }) |
Returns a hex string ciphertext to pass to your contract as bytes.
attestedDecrypt(walletClient, handles, ...)
Requests attested decryption of one or more handles. Only handles approved via e.allow() can be decrypted.
Three calling modes:
import { generateXwingKeypair } from "@inco/lightning-js/lite";
// Mode 1: Plaintext result (auto-generates ephemeral keypair internally)
const attestations = await zap.attestedDecrypt(walletClient, [handle]);
attestations[0].plaintext.value; // The decrypted value
// Mode 2: Reencrypt for a delegate (returns encrypted result)
const encrypted = await zap.attestedDecrypt(walletClient, [handle], {
reencryptPubKey: delegatePubKey,
});
encrypted[0].encryptedPlaintext.ciphertext.value;
// Mode 3: Reencrypt and decrypt locally
const keypair = await generateXwingKeypair();
const decrypted = await zap.attestedDecrypt(walletClient, [handle], {
reencryptPubKey: keypair.encodePublicKey(),
reencryptKeypair: keypair,
});
decrypted[0].plaintext.value;All modes accept an optional backoffConfig on the trailing opts object for retry tuning:
const attestations = await zap.attestedDecrypt(walletClient, [handle], {
backoffConfig: {
maxRetries: 10,
baseDelayInMs: 1000,
backoffFactor: 1.5,
},
});Return types:
| Mode | Return Type |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Plaintext | DecryptionAttestation[] — { handle, plaintext, covalidatorSignatures } |
| Reencrypt | EncryptedDecryptionAttestation[] — { handle, encryptedPlaintext, encryptedSignatures, covalidatorSignatures } |
| Reencrypt + local decrypt | DecryptionAttestation[] |
attestedReveal(handles, opts?)
Decrypts publicly revealed handles (those marked with e.reveal()). Does not require a wallet — anyone can call this.
const attestations = await zap.attestedReveal([handle]);
attestations[0].plaintext.value;Warning: Once
e.reveal()is called on-chain, the ciphertext is considered public and can be accessed by anyone. This cannot be undone.
attestedCompute(walletClient, lhsHandle, op, rhsPlaintext, ...)
Performs an off-chain comparison operation on an encrypted handle against a plaintext scalar, returning an attested result.
import { AttestedComputeSupportedOps } from "@inco/lightning-js/lite";
// Plaintext result
const result = await zap.attestedCompute(
walletClient,
handle,
AttestedComputeSupportedOps.Ge,
700n,
);
console.log(result.plaintext.value); // true or falseSupported operations:
| Operation | Description |
| -------------------------------- | --------------------- |
| AttestedComputeSupportedOps.Eq | Equal |
| AttestedComputeSupportedOps.Ne | Not equal |
| AttestedComputeSupportedOps.Ge | Greater than or equal |
| AttestedComputeSupportedOps.Gt | Greater than |
| AttestedComputeSupportedOps.Le | Less than or equal |
| AttestedComputeSupportedOps.Lt | Less than |
Supports the same three calling modes as attestedDecrypt (plaintext, reencrypt, reencrypt + local decrypt).
Session Key Methods
Session keys (allowance vouchers) enable delegated decryption without requiring the data owner's wallet signature for each request.
// Owner grants a session key to a delegate
const voucher = await zap.grantSessionKeyAllowanceVoucher(
walletClient, // Owner's wallet
granteeAddress, // Delegate's address
expiresAt, // Expiration Date
sessionVerifierAddr, // Session verifier contract
);
// Delegate decrypts using the voucher
const attestations = await zap.attestedDecryptWithVoucher(
ephemeralAccount,
voucher,
[handle],
);
// Delegate computes using the voucher
const result = await zap.attestedComputeWithVoucher(
ephemeralAccount,
voucher,
handle,
AttestedComputeSupportedOps.Eq,
42n,
);
// Owner revokes all active vouchers
const txHash = await zap.updateActiveVouchersSessionNonce(walletClient);| Method | Description |
| ------------------------------------------------------------------------------ | ------------------------------------ |
| grantSessionKeyAllowanceVoucher(walletClient, grantee, expiresAt, verifier) | Grant a time-limited session key |
| grantCustomSessionKeyAllowanceVoucher(walletClient, verifier, sharerArgData) | Grant with custom verifier arguments |
| attestedDecryptWithVoucher(account, voucher, handles, opts?) | Decrypt using a session key |
| attestedComputeWithVoucher(account, voucher, handle, op, rhs, opts?) | Compute using a session key |
| updateActiveVouchersSessionNonce(walletClient) | Revoke all active vouchers |
Static Helpers
// Read network public key from on-chain verifier
const pubkey = await Lightning.getNetworkPubkey(publicClient, executorAddress);
// Get the IncoVerifier contract instance
const verifier = await Lightning.getIncoVerifierContract(
publicClient,
executorAddress,
);
// Get covalidator URLs for a deployment
const urls = Lightning.getCovalidatorUrls(deployment, signers);Core Utilities (@inco/lightning-js)
Chain Support
import {
supportedChains,
getSupportedChain,
getViemChain,
} from "@inco/lightning-js";
// Supported chains
supportedChains.base; // 8453
supportedChains.baseSepolia; // 84532
supportedChains.anvil; // 31337
// Resolve chain from ID, name, or object
const chain = getSupportedChain(84532);
const chain = getSupportedChain("baseSepolia");
const chain = getSupportedChain({ id: 84532 });
// Get viem Chain object
const viemChain = getViemChain(84532);Handle Types
import {
handleTypes,
isTeeType,
validateHandle,
getHandleType,
} from "@inco/lightning-js";
// FHE type identifiers
handleTypes.ebool; // 0
handleTypes.euint4; // 1
handleTypes.euint8; // 2
handleTypes.euint16; // 3
handleTypes.euint32; // 4
handleTypes.euint64; // 5
handleTypes.euint128; // 6
handleTypes.euint160; // 7
handleTypes.euint256; // 8
handleTypes.ebytes64; // 9
handleTypes.ebytes128; // 10
handleTypes.ebytes256; // 11
// Validate and inspect handles
validateHandle("0x..."); // throws if malformed
const handleType = getHandleType("0x..."); // extracts the handle type
isTeeType(5); // true (euint64)Binary Utilities
import {
bytesToBigInt,
bigintToBytes,
bigintToBytes32,
bytes32ToBigint,
bytesFromHexString,
bytesToHex,
mustBeHex,
normaliseToHex,
padLeft,
asBytes32,
parseAddress,
parseHex,
} from "@inco/lightning-js";
// Conversions
bytesToBigInt(new Uint8Array([1, 0])); // 256n
bigintToBytes(42n); // 32-byte Buffer
bigintToBytes32(42n); // '0x000...002a' (Bytes32)
// Hex helpers
mustBeHex("0xdead"); // returns typed Hex, throws if invalid
normaliseToHex("dead"); // '0xdead'
bytesToHex(new Uint8Array()); // '0x'
// Parsing with validation
parseAddress("0x1234...5678"); // validated Address (20 bytes)
parseHex("0xabcd"); // validated HexString
asBytes32("0x00...00"); // validated Bytes32 (32 bytes)Encryption Module (@inco/lightning-js/encryption)
Types and conversion utilities for plaintexts and ciphertexts.
import {
// Constants
encryptionSchemes, // { xwing: 2 }
supportedTeeTypes, // { euint64: 5, euint160: 7, euint256: 8, ebool: 0 }
// Conversions
bigintToPlaintext, // (scheme, type, bigint) => Plaintext
plaintextToBigint, // (plaintext) => bigint
plaintextToBytes32, // (plaintext) => Bytes32
plaintextToBytes, // (plaintext) => Buffer
bytes32ToPlaintext, // (bytes32, scheme, type) => Plaintext
bytesToPlaintext, // (bytes, scheme, type) => Plaintext
// Helpers
getEncryptionSchemeName, // (scheme) => 'X-Wing'
} from "@inco/lightning-js/encryption";Key types:
| Type | Description |
| ------------------ | -------------------------------------------------------------------------------------- |
| Plaintext | { scheme, type, value } — value is bigint for integer types, boolean for ebool |
| Ciphertext | { scheme, type, value } — value is a hex string |
| EncryptionScheme | Currently only 2 (X-Wing) |
| SupportedTeeType | 0, 5, 7, 8 (ebool, euint64, euint160, euint256) |
Deployment Utilities (@inco/lightning-js/lite)
import {
getActiveLightningDeployment,
getLightningDeployments,
} from "@inco/lightning-js/lite";
// Get the latest active deployment for a chain
const deployment = getActiveLightningDeployment("baseSepolia");
// Get all deployments for a chain
const deployments = getLightningDeployments(84532);X-Wing Keypair (@inco/lightning-js/lite)
Generate an ephemeral X-Wing keypair for local reencryption (Mode 3 of attestedDecrypt and attestedCompute):
import {
generateXwingKeypair,
type XwingKeypair,
} from "@inco/lightning-js/lite";
const keypair: XwingKeypair = await generateXwingKeypair();
// Pass the public key to the attested call and the keypair for local decryption
const decrypted = await zap.attestedDecrypt(walletClient, [handle], {
reencryptPubKey: keypair.encodePublicKey(),
reencryptKeypair: keypair,
});
decrypted[0].plaintext.value;X-Wing is a post-quantum hybrid KEM combining ML-KEM-768 and X25519. The generated keypair is ephemeral — generate a fresh one per request.
Reencryption Module (@inco/lightning-js/reencryption)
EIP-712 typed data utilities for signing reencryption requests.
import { createEIP712Payload } from "@inco/lightning-js/reencryption";
const payload = createEIP712Payload({
chainId: 84532n,
primaryType: "Reencrypt",
primaryTypeFields: [{ name: "publicKey", type: "bytes" }],
message: { publicKey: "0x..." },
domainName: "IncoLite",
domainVersion: "1",
verifyingContract: "0x...",
});Key types:
| Type | Description |
| ----------------- | ---------------------------------------------- |
| Reencryptor<S> | (args, backoffConfig?) => Promise<Plaintext> |
| PubKeyEncodable | { encodePublicKey(): Uint8Array } |
| EIP712<Message> | Full EIP-712 typed data payload |
ABIs (@inco/lightning-js/abis)
Generated contract ABIs for direct contract interaction with viem.
import { incoLightningAbi } from "@inco/lightning-js/abis/lightning";
import { incoVerifierAbi } from "@inco/lightning-js/abis/verifier";Supported Chains
| Chain | Chain ID | Name |
| ------------- | -------- | ------------- |
| Base | 8453 | base |
| Base Sepolia | 84532 | baseSepolia |
| Anvil (local) | 31337 | anvil |
BackoffConfig
All attested decrypt/compute methods support optional retry configuration:
type BackoffConfig = {
maxRetries: number; // Default: 5
baseDelayInMs: number; // Default: 1000
backoffFactor: number; // Default: 1.5
errHandler?: (error: Error, attempt: number) => "stop" | "continue";
};Only transient errors (timeouts, connection issues, 503s) are retried. Security-critical errors (auth failures, invalid signatures) fail immediately.
Migrating from @inco/js (v0.7.x → v1.0.0)
The package was renamed from @inco/js to @inco/lightning-js (the old package is deprecated on npm), and v1.0.0 ships breaking API changes on top of the rename. The headline change: encryption moved from ECIES to a post-quantum X-Wing scheme, so ciphertexts and reencryption keys produced by v0.7.x are not interoperable with v1.
Step 1 — Update the package
npm uninstall @inco/js && npm install @inco/lightning-jsUpdate every import specifier, including subpaths:
-import { supportedChains } from '@inco/js';
-import { Lightning } from '@inco/js/lite';
+import { supportedChains } from '@inco/lightning-js';
+import { Lightning } from '@inco/lightning-js/lite';@inco/js/encryption, @inco/js/reencryption, and @inco/js/abis rename the same way. Note also that the @inco/js/fhevm subpath was removed.
Step 2 — Breaking API changes
| Area | Before (@inco/js v0.7.x) | After (@inco/lightning-js v1.0.0) |
| ---------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Encryption scheme | ECIES (encryptionSchemes.ecies, 1) | post-quantum X-Wing (encryptionSchemes.xwing, 2) — not interoperable |
| Reencryption keypair | generateSecp256k1Keypair() (sync), Secp256k1Keypair | await generateXwingKeypair() (async), XwingKeypair |
| Attested method extras | positional reencryptPubKey / reencryptKeypair / backoffConfig | a single trailing opts object |
| Voucher methods | ephemeralKeypair: Secp256k1Keypair + positional ethClient | ephemeralAccount: PrivateKeyAccount, no ethClient, trailing opts |
| RPC config | hostChainRpcUrl?: string | hostChainRpcUrls?: readonly string[] (multiple URLs use viem fallback()) |
| Type names | FheType, SupportedFheType, supportedFheTypes | TeeType, SupportedTeeType, supportedTeeTypes |
| AllowanceVoucher | — | new required warning: string field (shown by wallets; verifier enforces text) |
Attested methods now take an options object instead of positional arguments:
-await zap.attestedDecrypt(walletClient, handles, reencryptPubKey);
+await zap.attestedDecrypt(walletClient, handles, { reencryptPubKey });
-await zap.attestedDecrypt(walletClient, handles, reencryptPubKey, keypair, backoff);
+await zap.attestedDecrypt(walletClient, handles, {
+ reencryptPubKey,
+ reencryptKeypair: keypair,
+ backoffConfig: backoff,
+});The voucher variants drop the ethClient argument and take a viem account for the ephemeral key:
-await zap.attestedDecryptWithVoucher(ephemeralKeypair, voucher, ethClient, handles, reencryptPubKey);
+await zap.attestedDecryptWithVoucher(ephemeralAccount, voucher, handles, { reencryptPubKey });attestedCompute / attestedComputeWithVoucher change the same way, and attestedReveal's trailing backoffConfig moves into the opts object.
New in v1.0.0: Lightning.baseMainnet() and grantCustomSessionKeyAllowanceVoucher().
