Quantum Shielddocs

SDK

Generate keys, sign operations and submit them, in TypeScript.

@quantum-shield/sdk is the TypeScript side of the protocol: the same signature scheme as the contract, the leaf bookkeeping a wallet needs, and the ERC-4337 plumbing. It is built on viem and @noble/hashes, and lives in packages/sdk.

ModuleContains
xmssXmssKey (generate, sign), recover, signedDigits
walletLeafSigner, LeafStore, MemoryLeafStore, KeyExhausted
opOp, emptyOp, opDigest, opId, vaultAbi, routerAbi
useropbuildUserOp, placeholderSignature, bundlerSubmitter, directSubmitter, saleExit

Make a key

import { XmssKey, LeafSigner, MemoryLeafStore } from "@quantum-shield/sdk";
import { bytesToHex } from "viem";

const master = crypto.getRandomValues(new Uint8Array(32)); // the account's secret
const key = XmssKey.generate(master, 0, 8);                // tree 0, height 8: 256 signatures
const account = bytesToHex(key.key);                       // the account's name

const signer = new LeafSigner(key, new MemoryLeafStore());

XmssKey.generate(master, treeIndex, height, onLeaf?) derives every leaf from the master secret and the tree's number. Tree 1 is the account's next key, tree 2 the one after. onLeaf reports progress.

In a real wallet, implement LeafStore over durable storage: save must complete before a signature is used.

Buy into the account

import { routerAbi } from "@quantum-shield/sdk";

await wallet.writeContract({
  address: router,
  abi: routerAbi,
  functionName: "buy",
  args: [account, minTokensOut],
  value: parseEther("0.01"),
});

Sign an operation

import { emptyOp, opDigest, vaultAbi, type Op } from "@quantum-shield/sdk";

const [, nextLeaf, epoch] = await client.readContract({ address: vault, abi: vaultAbi, functionName: "accounts", args: [account] });

const op: Op = { ...emptyOp(account), to: otherAccount, transferShares: 1_000n };
const digest = opDigest(op, { chainId: 4663, vault, epoch });
const { leaf, signature } = await signer.sign(digest, Number(nextLeaf));

signer.sign picks the leaf, records it, and returns the signature. Asking again for the same digest returns the same leaf.

Submit with a wallet

await wallet.writeContract({ address: vault, abi: vaultAbi, functionName: "transact", args: [op, signature] });

Submit with no wallet

A sale of the whole balance through a bundler, paying the network fee from the account:

import {
  DEFAULT_VERIFICATION_GAS, SALE_CALL_GAS, buildUserOp, bundlerSubmitter,
  networkFeeError, placeholderSignature, saleExit,
} from "@quantum-shield/sdk";

const shares = await client.readContract({ address: vault, abi: vaultAbi, functionName: "sharesOf", args: [account] });
const fee = await client.readContract({ address: vault, abi: vaultAbi, functionName: "networkFee" });
const gasFee = (await client.readContract({ address: vault, abi: vaultAbi, functionName: "convertToShares", args: [fee] })) + 1n;

const op: Op = {
  ...emptyOp(account),
  exitShares: shares - gasFee,
  gasFee,
  recipient,                          // receives the ETH
  ...saleExit(router, minEthOut),
  caller: vault,                      // ERC-4337 operations name the vault
  minCallGasLimit: SALE_CALL_GAS,
};

const submitter = bundlerSubmitter(bundlerUrl, { entryPoint });
let gas = { verificationGasLimit: DEFAULT_VERIFICATION_GAS, callGasLimit: SALE_CALL_GAS, preVerificationGas: 60_000n, ...(await submitter.gasPrice()) };

// Estimate with a placeholder: no leaf is spent until the price is known.
gas = await submitter.estimate(buildUserOp(vault, op, epoch, 0, placeholderSignature(key.height, 0), gas), gas);

const limit = await client.readContract({ address: vault, abi: vaultAbi, functionName: "maxOpCostWei" });
const refused = networkFeeError(gas, limit);
if (refused) throw new Error(refused);

const digest = opDigest(op, { chainId: 4663, vault, epoch });
const { leaf, signature } = await signer.sign(digest, Number(nextLeaf));
const { transactionHash, success } = await submitter.send(buildUserOp(vault, op, epoch, leaf, signature, gas), gas);

bundlerSubmitter works with any ERC-4337 bundler that serves EntryPoint v0.8 on the chain. directSubmitter calls EntryPoint.handleOps from a wallet instead, with no external service.

Change the key

const next = XmssKey.generate(master, 1, 8);
const op: Op = { ...emptyOp(account), newKey: bytesToHex(next.key) };
// sign with the current key, submit either way; then sign with `next`, starting from leaf 0

Verify off-chain

import { recover } from "@quantum-shield/sdk";

const result = recover(digestBytes, signatureBytes); // { key, leaf } or null

recover returns exactly what Xmss.recover returns on-chain.

Gas constants

ConstantValueFor
DEFAULT_VERIFICATION_GAS400,000Signature check and applying the operation
SALE_CALL_GAS650,000Execution of a sale
PLAIN_CALL_GAS120,000Execution of an exit paid out as tokens

On this page