Skip to main content
The Inco JS SDK has been renamed from @inco/js to @inco/lightning-js for v1. This guide covers the package rename plus the breaking API changes when upgrading from @inco/js 0.7.x.
Update every import — @inco/js@inco/lightning-js and @inco/js/lite@inco/lightning-js/lite — then reinstall. The old @inco/js package is no longer updated.

Install

npm install @inco/lightning-js@latest

Instantiating the SDK

v1 adds explicit per-network factories — Lightning.baseSepoliaTestnet() and Lightning.baseMainnet() — which take no pepper or chainId. Prefer them over the v0.7 Lightning.latest(...) call:
- // v0.7
- const zap = await Lightning.latest('testnet', supportedChains.baseSepolia);
+ // v1 — Base Sepolia
+ const zap = await Lightning.baseSepoliaTestnet();

+ // v1 — Base mainnet
+ const zap = await Lightning.baseMainnet();
Pass your own hostChainRpcUrls (recommended — otherwise the SDK falls back to viem’s public RPC endpoints):
const zap = await Lightning.baseSepoliaTestnet({
  hostChainRpcUrls: ['https://your-rpc-url'],
});
Lightning.latest(pepper, chainId) still works for advanced cases, but the network factories are the recommended entry point.

Breaking Changes

1. Keypair generation: generateSecp256k1Keypair removed

generateSecp256k1Keypair was used for reencryption in attested decrypt calls. It has been replaced by generateXwingKeypair, a post-quantum X-Wing keypair. The new function is async.
- import { generateSecp256k1Keypair } from '@inco/lightning-js/lite';
+ import { generateXwingKeypair } from '@inco/lightning-js/lite';

- const keypair = generateSecp256k1Keypair();
+ const keypair = await generateXwingKeypair();
The returned keypair still exposes encodePublicKey() for use in attested decrypt calls.
Session keys are unaffected — they use a plain viem PrivateKeyAccount (via privateKeyToAccount(generatePrivateKey())), not an X-Wing keypair.

2. Attested methods: positional args replaced by an opts object

All attested methods (attestedDecrypt, attestedCompute, attestedDecryptWithVoucher, attestedComputeWithVoucher) previously accepted reencryption keys as extra positional arguments. They now use a typed opts object as the last parameter.

attestedDecrypt — reencrypt for a delegate

- const results = await zap.attestedDecrypt(walletClient, handles, delegatePubKey);
+ const results = await zap.attestedDecrypt(walletClient, handles, { reencryptPubKey: delegatePubKey });

attestedDecrypt — reencrypt and decrypt locally

- const results = await zap.attestedDecrypt(walletClient, handles, pubKey, keypair);
+ const results = await zap.attestedDecrypt(walletClient, handles, { reencryptPubKey: pubKey, reencryptKeypair: keypair });

attestedCompute — reencrypt for a delegate

- const result = await zap.attestedCompute(walletClient, handle, op, value, delegatePubKey);
+ const result = await zap.attestedCompute(walletClient, handle, op, value, { reencryptPubKey: delegatePubKey });

No-opts calls are unchanged

If you were calling attestedDecrypt or attestedCompute without any reencryption options (plain decryption), no change is needed — the default behaviour is still to return plaintext directly.

3. Session key methods: reencryption options moved into an opts object

The session key is still a plain viem PrivateKeyAccount, and attestedDecryptWithVoucher / attestedComputeWithVoucher keep their positional arguments. What changed is that reencryption keys moved out of positional arguments and into a trailing opts object (the same change as section 2).
+ import { generateXwingKeypair } from '@inco/lightning-js/lite';
+ const decryptKeypair = await generateXwingKeypair();

- await zap.attestedDecryptWithVoucher(ephemeralAccount, voucher, handles, pubKey, keypair);
+ await zap.attestedDecryptWithVoucher(ephemeralAccount, voucher, handles, {
+   reencryptPubKey: decryptKeypair.encodePublicKey(),
+   reencryptKeypair: decryptKeypair,
+ });

4. BackoffConfig field renames

The backoffConfig object passed to attested methods has two renamed fields, and is now passed inside the opts object.
- await zap.attestedDecrypt(walletClient, handles, {
-   maxRetries: 5,
-   initialDelay: 1000,
-   maxDelay: 10000,
- });
+ await zap.attestedDecrypt(walletClient, handles, {
+   backoffConfig: {
+     maxRetries: 5,
+     baseDelayInMs: 1000,
+     backoffFactor: 2,
+   }
+ });
Old fieldNew fieldNotes
initialDelaybaseDelayInMsRenamed
maxDelay(removed)Replaced by backoffFactor
(n/a)backoffFactorMultiplier applied each retry

5. AllowanceVoucher: warning field is first and enforced on-chain (EIP-712 digest change)

The AllowanceVoucher EIP-712 struct includes a string warning field that is now first in the struct. This ensures wallets display it before the opaque byte fields. The field order is part of the EIP-712 type hash, so any voucher signed with the old field order produces a different digest and will fail verification. Additionally, isAllowedWithProof now enforces the exact warning text on-chain — it reverts with InvalidVoucherWarning if the voucher’s warning does not match REQUIRED_ALLOWANCE_VOUCHER_WARNING exactly. Passing an empty string, a custom message, or the old warning text will be rejected. If you use grantSessionKeyAllowanceVoucher or grantCustomSessionKeyAllowanceVoucher (the Lightning methods), no change is needed — the correct warning is populated automatically. Always create vouchers through the SDK, which sets the correct warning for you:
import { Lightning } from '@inco/lightning-js/lite';

const zap = await Lightning.baseSepoliaTestnet();

// warning is set automatically to the required on-chain text
const voucher = await zap.grantSessionKeyAllowanceVoucher(
  walletClient,
  granteeAddress,                          
  new Date(Date.now() + 10 * 60 * 1000),   
  sessionVerifierAddress,                  
);
For custom session-verifier logic, use grantCustomSessionKeyAllowanceVoucher(walletClient, sessionVerifierAddress, sharerArgData) instead — it also sets the warning automatically.

New Features

Multiple RPC endpoints with automatic fallback

All Lightning factory methods now accept an optional hostChainRpcUrls array. When multiple URLs are provided the SDK uses viem’s fallback() transport internally — it tries providers in order and moves to the next on failure.
// Recommended: use the explicit network factory for your chain.
// for Base mainnet: Lightning.baseMainnet({ ... })
const zap = await Lightning.baseSepoliaTestnet({
  hostChainRpcUrls: ['https://primary.rpc', 'https://fallback.rpc'],
});
baseSepoliaTestnet() and baseMainnet() are the recommended way to initialize the SDK. Passing your own hostChainRpcUrls is recommended — without it the SDK falls back to viem’s public RPC endpoints. A single URL also works.