# @inco/lightning-js

TypeScript SDK for building privacy-preserving dapps on [Inco Lightning](https://docs.inco.org). 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

```bash
npm install @inco/lightning-js
# or
yarn add @inco/lightning-js
# or
bun add @inco/lightning-js
```

**Peer dependency:** [viem](https://viem.sh) `^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:

1. **Encrypt** a value client-side
2. **Submit** the ciphertext to a smart contract for confidential computation
3. **Decrypt** the result via attested decryption

```ts
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:

```ts
// 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](https://docs.inco.org) 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.

```ts
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:**

```ts
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:

```ts
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.

```ts
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.

```ts
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 false
```

**Supported 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.

```ts
// 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

```ts
// 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

```ts
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

```ts
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

```ts
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.

```ts
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`)

```ts
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`):

```ts
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.

```ts
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.

```ts
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:

```ts
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

```bash
npm uninstall @inco/js && npm install @inco/lightning-js
```

Update every import specifier, including subpaths:

```diff
-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:

```diff
-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:

```diff
-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()`.

---

## Further Reading

- [Inco Documentation](https://docs.inco.org)
- [Encryption Guide](https://docs.inco.org/js-sdk/encryption)
- [Attested Decrypt](https://docs.inco.org/js-sdk/attestations/attested-decrypt)
- [Attested Compute](https://docs.inco.org/js-sdk/attestations/attested-compute)
- [Attested Reveal](https://docs.inco.org/js-sdk/attestations/attested-reveal)
- [Allowance Vouchers](https://docs.inco.org/js-sdk/voucher/allowance-voucher)

## License

[Apache-2.0](./LICENSE)
