npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@veilo/sdk-core

v0.10.0

Published

TypeScript SDK for the Veilo Privacy Pool - UTXO-based privacy protocol on Solana with ZK-SNARKs

Readme

@veilo/sdk-core

Build private payments and swaps on Solana with Veilo.

The SDK gives TypeScript applications a safe, typed way to:

  • shield SOL or SPL tokens into Veilo;
  • withdraw, privately transfer, or privately swap shielded funds;
  • open and manage private positions, Jupiter perps, and predictions;
  • work with Veilo's relayer without handling its encryption protocol yourself;
  • add partner-powered private sends and swaps through the Cloak API.

It works with both ESM and CommonJS and includes TypeScript declarations.

Project status: Active development. Test integrations on Devnet before using them with real funds.

Install

npm install @veilo/sdk-core

Install snarkjs only if your application generates proofs locally:

npm install snarkjs

You will also need:

  • a Solana RPC connection;
  • a connected wallet when a user must sign;
  • the Veilo circuit .wasm and .zkey files for local proof generation;
  • an API key only for partner services such as Cloak or Jupiter Predictions.

Choose an integration

| What you want to build | Start with | Who signs? | | --- | --- | --- | | Let any wallet shield funds into Veilo | shield() | The user's wallet | | Add private transfers or swaps to a partner app | VeiloCloakClient | The user's wallet | | Build a full Veilo wallet experience | Managed relayer helpers | The relayer for private spends | | Manage proofs, notes, trees, and transactions yourself | Transaction helpers | Your application or relayer |

For most third-party deposit integrations, shield() is the best starting point. For a managed private send or swap flow, use Cloak.

Quick start: shield funds

shield() creates an unsigned transaction. Your application asks the user's wallet to sign it, submits it, and then finalizes the new private note.

import { Connection } from "@solana/web3.js";
import {
  createTransactionProver,
  finalizeShield,
  resolveShieldOwner,
  shield,
} from "@veilo/sdk-core";

const connection = new Connection(process.env.SOLANA_RPC_URL!, "confirmed");

// `wallet` is any connected Solana wallet with `publicKey` and
// `signTransaction` methods.
const owner = await resolveShieldOwner({ username: "alice" });
const prover = createTransactionProver({
  wasmPath: "/circuits/transaction.wasm",
  zkeyPath: "/circuits/transaction_final.zkey",
});

const result = await shield({
  connection,
  amount: 1_000_000_000n, // 1 SOL in lamports
  owner,
  signer: { publicKey: wallet.publicKey },
  prover,
});

const signed = await wallet.signTransaction(result.transaction);
const signature = await connection.sendRawTransaction(signed.serialize());

const confirmation = await connection.confirmTransaction(
  {
    signature,
    blockhash: result.blockhash,
    lastValidBlockHeight: result.lastValidBlockHeight,
  },
  "confirmed",
);

if (confirmation.value.err) {
  throw new Error("Shield transaction failed");
}

const finalized = await finalizeShield({
  connection,
  signature,
  note: result.note,
  owner,
});

console.log("Private note created at leaf", finalized.leafIndex);

To shield an SPL token, pass its mint:

const result = await shield({
  connection,
  mint: usdcMint,
  amount: 5_000_000n, // 5 USDC when the mint has 6 decimals
  owner,
  signer: { publicKey: wallet.publicKey },
  prover,
});

Keep result.note private. It contains the information needed to recover the shielded funds. finalizeShield() gives you the authoritative leaf index after the transaction lands.

If the blockhash expires before submission, reuse the proof with rebuild():

import { rebuild } from "@veilo/sdk-core";

const refreshed = await rebuild(result, { connection });

A stale Merkle root needs a new call to shield() because it requires a new proof.

Transaction v1 shields

buildShieldV1(result, opts) turns the same shield() / rebuild() result into unsigned transaction v1 bytes (every account inline, no lookup table, the compute budget in the header) without re-proving. Sign them with signV1Bytes from @veilo/sdk-core/tx-v1 or a wallet that signs version 1.

v1 has no implicit budget, so the resource limits matter:

  • limits you pass must be measured for that exact instruction set. SOL and SPL shields load different accounts, so their limits differ — do not reuse limits measured for one mint on another. A transaction that exceeds its header limits fails on-chain and still pays its fee.
  • Without limits, they are measured by simulation with estimateV1ResourceLimits, which POSTs to connection.rpcEndpoint with a plain fetch (no custom headers — the Connection's httpHeaders, fetch and fetchMiddleware are not used). If your RPC authenticates via custom headers, measure through your own transport and pass explicit limits.

Cloak Partner API

Cloak is the simplest route for partner applications that want a private send or cross-asset private swap. The SDK handles request types, API authentication, transaction decoding, polling, and structured errors. It never signs for the user.

Keep your Cloak API key on a trusted server. Do not include it in a browser or mobile bundle.

Create an order on your server

import { VeiloCloakClient } from "@veilo/sdk-core/cloak";

const cloak = new VeiloCloakClient({
  apiKey: process.env.VEILO_CLOAK_KEY!,
});

// The production API defaults to https://api.veilo.network/.
const { quote } = await cloak.getQuote({
  amount: "100000000", // raw token units
  sourceAssetId: "veilo-usdc",
  destinationAssetId: "veilo-usdc",
  senderAddress,
  recipientAddress,
  mode: "exact_in",
});

const { order } = await cloak.createOrder(
  {
    quote, // return the complete quote without changing it
    senderAddress,
    recipientAddress,
  },
  { idempotencyKey: crypto.randomUUID() },
);

For a cross-asset swap, use a different destinationAssetId and optionally add slippageBps to the quote request. Use listTokens() to discover supported asset IDs and their current limits.

You can override the API address for another environment. Both the origin and the full Cloak path are accepted:

new VeiloCloakClient({
  baseUrl: "https://api.veilo.network/", // `/cloak/v1` also works here
  apiKey: process.env.VEILO_CLOAK_KEY!,
});

Sign in the user's application

The order's transaction is an unsigned base64 deposit with the sender as the only signer. It is Solana transaction v1 by default (every account inline, no lookup table, compute limits and priority fee in the message header); order.transactionVersion says what was built, and txVersion: 0 in createOrder (or refreshTransaction(id, { txVersion: 0 })) requests v0.

Sign the raw bytes, which works for both versions. With a Wallet Standard wallet, check that it advertises transaction version 1 (typed from @solana/wallet-standard-features 1.5.0) and fall back to txVersion: 0 when it does not:

import { decodeCloakTransaction } from "@veilo/sdk-core/cloak";

const feature = wallet.features["solana:signTransaction"];
if (order.transactionVersion === 1 && !feature.supportedTransactionVersions.includes(1)) {
  // Ask your server for `cloak.refreshTransaction(order.trackingId, { txVersion: 0 })`
  // and sign that order instead; the API key never belongs in the browser.
}
const [{ signedTransaction }] = await feature.signTransaction({
  account,
  chain: "solana:mainnet",
  transaction: decodeCloakTransaction(order), // Uint8Array, v0 or v1
});
// sendRawTransaction only base64-encodes the bytes, so it is safe for v1.
const signature = await connection.sendRawTransaction(signedTransaction);
await connection.confirmTransaction(signature, "confirmed");

With a local keypair, signCloakTransaction(order, keypair) returns signed bytes for either version. A detached signer (KMS/HSM) signs getCloakTransactionMessage(order) and inserts the result with addCloakTransactionSignature(order, publicKey, signature), which verifies it first.

web3.js 1.x cannot sign a v1 transaction: 1.99 decodes it but VersionedTransaction.sign() / serialize() throw, and 1.98 and earlier cannot decode it at all. If your wallet layer signs web3.js objects, create the order with txVersion: 0 and keep using deserializeCloakTransaction, which returns a VersionedTransaction for v0 and throws for v1:

const { order } = await cloak.createOrder({ quote, senderAddress, recipientAddress, txVersion: 0 });
const transaction = deserializeCloakTransaction(order); // v0 only
const signed = await wallet.signTransaction(transaction);
const signature = await connection.sendRawTransaction(signed.serialize());

Always check order.transactionVersion (or getCloakTransactionVersion) before choosing a path: a v1 request can come back as v0 when Cloak falls back.

Send the signature back to your server. The deposit notification is optional, but it can reduce processing latency:

await cloak.notifyDeposit({
  trackingId: order.trackingId,
  txSignature: signature,
});

const finalStatus = await cloak.waitForFinalStatus(order.trackingId, {
  intervalMs: 3_000,
  timeoutMs: 20 * 60_000,
});

console.log(finalStatus.status);

If an unfunded order's transaction expires, call refreshTransaction(order.trackingId) and ask the user to sign the refreshed transaction. The refresh keeps the version the order was created with unless you pass { txVersion }.

Full private wallet flows

The SDK supports all Veilo privacy-pool operations. Pick the highest-level API that fits your application:

| Operation | Managed relayer call | Direct SDK helper | | --- | --- | --- | | Deposit | Not required | shield() or deposit() | | Withdraw | submitWithdraw() | withdraw() | | Private transfer | submitPrivateTransfer() | privateTransfer() | | Private swap | submitPrivateSwap() | buildPrivateSwapInstructions() or transactSwap() |

Use the managed relayer helpers

The SDK owns Veilo's production relayer URL, encryption key, request encryption, and safe retry behavior. Applications call typed functions directly; there is no relayer client or URL to configure.

import {
  submitWithdraw,
  type WithdrawRequest,
} from "@veilo/sdk-core/relayer";

async function submitWithdrawal(request: WithdrawRequest) {
  return submitWithdraw(request);
}

The same focused entry point provides account authentication, encrypted note storage, Merkle tree reads, private transfers, private swaps, and these product flows:

| Product | Relayer helpers | | --- | --- | | Private positions | submitOpenPosition(), submitClosePosition(), submitMergePositions() | | Jupiter perps | submitJperpOpen(), submitJperpClose(), TP/SL, cancellation, recovery, and reissue helpers | | Jupiter predictions | submitPredictionOpen() and submitPredictionReissue() |

Submission always targets Veilo's relayer

Every helper above submits to Veilo's relayer. There is no baseUrl option, no client to construct, and no supported way to redirect submission. That is deliberate, for two independent reasons:

  1. The program requires it. The privacy pool checks the submitting relayer against an on-chain allowlist on every spend. Deposits (public_amount > 0) are permissionless — which is why shield() and deposit() work with any wallet — but withdrawals, transfers, swaps, positions, perps, and predictions all fail with RelayerNotAllowed unless the transaction is signed by a whitelisted relayer. Pointing the SDK at a different host would produce a well-formed request that cannot land on chain.
  2. These requests carry spending keys. TransactNote.privateKey, and the claimantSecretKey on position close and merge, are the spend authority for the funds involved (see A private note is valuable secret data). A configurable submission target would be a configurable destination for user funds.

Running your own relayer is a whitelisting conversation rather than a configuration flag — see Support. Reaching past the package's exports map to import internal modules is unsupported and not covered by semver.

Finding your notes on the public feed

Every row in the compact feed is ciphertext plus a one-byte view tag; nothing on it says who a note belongs to. You find yours by trial decryption, and the view tag makes that cheap — it rejects roughly 255 of every 256 foreign rows with a single hash instead of a full decrypt.

import { scanCompactNotes, fetchNotesByCommitment } from "@veilo/sdk-core";

const { notes, scanned, nextCursor } = await scanCompactNotes(walletSecretKey, {
  onPage: ({ matched }) => console.log(`${matched} found so far`),
});

It paginates to exhaustion by default and derives the X25519 key once for the whole scan. Stop early with maxPages or by returning false from onPage; nextCursor is then non-null and can be passed back later to resume. The feed carries only unspent, unclaimed notes, and it needs no auth token — scanning reveals nothing without your key.

fetchNotesByCommitment(commitments) looks up known commitments instead, chunked at the server's 50-per-request cap and issued concurrently. Commitments the relayer does not know are simply absent from the result.

Consolidating notes before a large spend

When no one or two notes cover an amount, selectNotesForAmount reports requiresMerge. planNoteConsolidation says what to do about it:

const plan = planNoteConsolidation(unspentNotes, amountRaw, { mint });
if (plan.ok) {
  for (const { inputs, outputAmount } of plan.steps) {
    // one 2-in-1-out privateTransfer to yourself per step
  }
}

Each step is a self-transfer combining two inputs into one, so k notes need k-2 steps before a final two-input spend. Steps chain — a later step can consume an earlier step's output, and inputs are tagged { kind: "note" } or { kind: "step", step } so you always know which. The two smallest are merged each round, which retires dust first and leaves large notes intact. The plan is pure: it computes, it does not execute.

Spend status and private balance

Nothing in a note says whether it has been spent — a note is yours until its nullifier is published. So a balance is always two steps, and the SDK gives you both:

import { getPrivateBalance, checkNullifiersSpent } from "@veilo/sdk-core";

const { total, byMint, unspent, spent } = await getPrivateBalance(myNotes);

Each note needs an amount, a 64-character hex nullifier, and optionally a mint to group by. getPrivateBalance asks the relayer which nullifiers are spent, then sums what is left; unspent and spent hand back your own note objects, so you can render from them directly.

checkNullifiersSpent(nullifiers) is the same question on its own. It deduplicates, splits into the server's 200-per-request batches, issues them concurrently, and returns a Set for membership testing. It also lower-cases input: the endpoint validates case-insensitively but answers in lower case, so upper-case hex would otherwise never match and every note would read as unspent.

To compute a balance without the relayer — from a chain scan of nullifier events, or a local cache — supply the answer yourself:

await getPrivateBalance(myNotes, { resolveSpent: async (ns) => mySpentSet(ns) });

Amounts stay bigint end to end. A number amount above Number.MAX_SAFE_INTEGER is rejected rather than silently rounded, and a malformed nullifier throws instead of counting as unspent — either would overstate a balance.

Choosing notes to spend

Two rules govern this, and both are easy to get wrong:

  • the transaction circuit is 2-in-2-out, so one transaction spends at most two notes;
  • notes are only co-spendable inside the same Merkle tree, and the tree is derived from the mint.
import { selectNotesForAmount, canonicalTreeId } from "@veilo/sdk-core";

const selection = selectNotesForAmount(unspentNotes, amountRaw, { mint });
if (!selection.ok) throw new Error(selection.message); // NO_NOTES | INSUFFICIENT_FUNDS
if (selection.requiresMerge) {
  // more than two inputs — combine them before proving
}

It searches each tree independently and returns the best result: the smallest single note that covers the amount, else the pair with the least change (found by a two-pointer scan, not an all-pairs search), else the largest notes greedily with requiresMerge set. A failure reports available, the largest total reachable within one tree.

canonicalTreeId(mint) derives a note's tree the way the relayer does. Derive it; never trust a stored treeId. Change notes are written with the on-chain shard index while deposits and synced notes carry the mint-derived id — for native SOL both are 0 so the difference hides, but for SPL tokens they diverge and co-spending across the two fails at proof time as a commitment mismatch.

Private product keys and recovery

Private positions, perps, and predictions use deterministic client-held keys. The SDK provides the exact derivations used by Veilo's wallet, extension, relayer, and program tests. Keep the spending key and claimant secret on the user's device; send claimant secrets only inside the SDK's encrypted relayer requests.

import {
  derivePositionKeyBundle,
  deriveSpendingKeyWithSigner,
  encodeClaimantSecretKey,
  initPoseidon,
  submitOpenPosition,
} from "@veilo/sdk-core";

await initPoseidon();
const spendingKey = await deriveSpendingKeyWithSigner(
  (message) => wallet.signMessage(message),
);
const positionKeys = derivePositionKeyBundle(spendingKey, nextPositionIndex);

await submitOpenPosition({
  // Select private source notes in your wallet before calling the relayer.
  notes,
  sourceMintAddress,
  destMintAddress,
  swapAmountRaw,
  slippageBps: 50,
  userPublicKey: wallet.publicKey.toBase58(),
  veiloPublicKey,
  position: positionKeys.position,
});

// Persist the returned position data and `nextPositionIndex`. Re-derive the
// same claimant later when closing or merging the position.
const claimantSecretKey = encodeClaimantSecretKey(positionKeys.claimant);

Use deriveJperpKeyBundle() and getJperpMarketPayload() for private perps. Use derivePredictionKeyBundle() for private predictions. Lower-level key derivations are also exported when an application needs them individually. The PDA helpers in each focused entry point support on-chain recovery scans on a fresh device.

Predictions have one additional step: Jupiter returns an unsigned order, close, or claim transaction. The SDK exposes that API without taking custody of signing:

import {
  JupiterPredictionClient,
  deserializePredictionTransaction,
} from "@veilo/sdk-core/predictions";

const predictions = new JupiterPredictionClient({ apiKey: jupiterApiKey });
const order = await predictions.placeOrder({
  ownerPubkey: ephemeral.publicKey.toBase58(),
  marketId,
  isYes: true,
  isBuy: true,
  depositAmount: "5000000",
  depositMint: usdcMint,
});
const transaction = deserializePredictionTransaction(order);
// Ask the deterministic ephemeral wallet to sign, then submit and confirm it.

The SDK deliberately does not own application storage or choose which notes to spend. Persist indexes and returned product records only after the corresponding transaction is confirmed.

Use the direct helpers

Direct helpers are intended for applications that already maintain Veilo note and Merkle tree state and can generate proofs.

import {
  NATIVE_SOL_MINT,
  createTransactionProver,
  createVeiloProgram,
  deposit,
} from "@veilo/sdk-core";

const program = createVeiloProgram(connection, anchorWallet);
const proofBuilder = createTransactionProver({
  wasmPath: "/circuits/transaction.wasm",
  zkeyPath: "/circuits/transaction_final.zkey",
});

const built = await deposit({
  program,
  depositor: { publicKey: wallet.publicKey },
  mintAddress: NATIVE_SOL_MINT,
  amount: 1_000_000_000n,
  recipientPubkey: veiloOwnerPublicKey,
  noteRecipientWallet: wallet.publicKey,
  tree,
  proofBuilder,
  treeId: 0,
});

const signed = await wallet.signTransaction(built.transaction);
const signature = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(signature, "confirmed");

// Update the local tree only after on-chain confirmation.
const receipt = built.commit();

withdraw() and privateTransfer() accept two spendable input notes, build the proof, submit through the supplied relayer signer, and return the resulting change or output notes. Important rules are enforced by the SDK:

  • withdrawal amounts must be positive and fit within the selected notes;
  • private-transfer outputs must equal the total input amount;
  • direct private transfers use a zero public fee;
  • note-recipient wallet keys should be supplied so new notes remain recoverable.

Private swaps normally use Jupiter versioned transactions and address lookup tables. Use buildPrivateSwapInstructions() to add Veilo's atomic instructions to the exact Jupiter route. Preserve Jupiter's account order and duplicate accounts. transactSwap() is available when a legacy transaction is sufficient.

The low-level transact() and buildRawTransactInstruction() exports are for custom transaction composition. Most applications should use the helpers above.

Important concepts

Amounts use base units

Amounts are bigint values or decimal strings in the token's smallest unit:

import { sol } from "@veilo/sdk-core/config";

const oneSol = sol(1);       // 1_000_000_000n lamports
const fiveUsdc = 5_000_000n; // 5 USDC for a 6-decimal mint

Avoid JavaScript floating-point values for token arithmetic.

A private note is valuable secret data

A note contains the information needed to locate and spend private funds. Store it encrypted, never log it, and never send its unencrypted contents to an analytics or application server.

For shielding, prefer resolveShieldOwner(). It obtains the spending and note viewing keys as a matched pair. Supplying unrelated keys can create a note that the recipient cannot discover.

Confirmation comes before local state updates

Do not mark notes as spent, insert commitments into a local tree, or persist a predicted leaf index until the transaction is confirmed. Use finalizeShield() for shields and call a deposit's commit() callback only after confirmation.

Proof files are separate

Circuit .wasm and .zkey files are intentionally not bundled with the npm package because they are large — files ships dist/ only. Provide local paths, byte arrays, or hosted URLs to createTransactionProver() and createSwapProver().

Both arguments are optional. Omit them and the prover resolves artifacts from disk on first use, first hit wins:

  1. $VEILO_CIRCUITS_DIR
  2. <package>/circuits — populated by npm run copy:circuits
  3. <cwd>/circuits
  4. <cwd>/node_modules/@veilo/sdk-core/circuits
const prover = createTransactionProver();            // resolved from disk
const prover = createTransactionProver({ wasmPath, zkeyPath }); // explicit

resolveCircuitArtifacts("transaction" | "swap") performs that lookup on its own if you want the paths. Resolution is Node-only and lazy — it imports node:fs inside the call so browser bundles can still import this module, and there is nothing to resolve in a browser anyway. Browser builds must pass CircuitArtifacts explicitly. A miss throws with every path it tried.

Error handling

Shield and Cloak errors include stable fields that applications can use to show useful recovery actions.

import {
  CloakApiError,
  mapShieldError,
} from "@veilo/sdk-core";

try {
  // Build or submit a Veilo operation.
} catch (error) {
  if (error instanceof CloakApiError) {
    console.error(error.code, error.status, error.message);
  } else {
    const shieldError = mapShieldError(error);
    console.error(shieldError.code, shieldError.message);
    // retryable is `rebuild`, `reshield`, or `none`.
    console.log(shieldError.retryable);
  }
}

When handling Cloak separately, CloakApiError also exposes actionable values such as min, max, field, maxBps, and retryAfter when the API returns them.

Package entry points

Import from the package root for convenience or use a focused entry point:

import { shield } from "@veilo/sdk-core";
import { getPoolPdas } from "@veilo/sdk-core/accounts";
import { createUTXO } from "@veilo/sdk-core/notes";
import { deposit, withdraw } from "@veilo/sdk-core/transactions";
import { submitWithdraw } from "@veilo/sdk-core/relayer";
import { VeiloCloakClient } from "@veilo/sdk-core/cloak";
import { derivePositionKeyBundle } from "@veilo/sdk-core/positions";
import { getJperpMarketPayload } from "@veilo/sdk-core/perps";
import { JupiterPredictionClient } from "@veilo/sdk-core/predictions";

Available focused entry points:

  • @veilo/sdk-core/accounts
  • @veilo/sdk-core/cloak
  • @veilo/sdk-core/config
  • @veilo/sdk-core/idl
  • @veilo/sdk-core/identity
  • @veilo/sdk-core/notes
  • @veilo/sdk-core/perps
  • @veilo/sdk-core/positions
  • @veilo/sdk-core/predictions
  • @veilo/sdk-core/poseidon
  • @veilo/sdk-core/proof
  • @veilo/sdk-core/prover
  • @veilo/sdk-core/relayer
  • @veilo/sdk-core/shield
  • @veilo/sdk-core/transactions
  • @veilo/sdk-core/tx-v1

Advanced building blocks

The root package also exports tools for teams building their own Veilo wallet or relayer:

  • account and PDA queries;
  • UTXO creation, ownership, encryption, and recovery;
  • Merkle tree reconstruction from on-chain events;
  • transaction and swap witness preparation;
  • Poseidon hashing helpers;
  • pool initialization and administration;
  • raw IDL-correct transaction instruction builders.

These APIs are fully typed. Use their TypeScript definitions as the source of truth for required inputs.

Historical IDLs and commitment recovery

The SDK includes the current, legacy, and legacy-2 privacy-pool IDLs used by the relayer. Event scans automatically try the historical layouts, so applications can rebuild trees across program upgrades.

import {
  PRIVACY_POOL_IDLS,
  buildTreeFromEvents,
  resolveCommitmentEvents,
} from "@veilo/sdk-core";

buildTreeFromEvents() includes Veilo's protected historical repairs by default and rejects gaps that would produce an incorrect root. When a confirmed instruction appended commitments without emitting CommitmentEvent, use resolveCommitmentEvents() with the expected commitments and current local tree. It prefers emitted metadata and computes only the missing entries.

For the deployed eventless open_position path, use recoverEventlessOpenPositionEvents(). It decodes both commitments from the instruction and reads the authoritative global position-tree leaf index from the program-owned PositionPDA. buildGlobalPositionTreeFromEvents() mirrors the relayer's all-mint position-tree reconstruction.

Development

# Build CommonJS, ESM, and declaration outputs
npm run build

# Run deterministic unit and proof tests
npm test

# Run opt-in live Devnet integration tests
npm run test:integration

# Check module boundaries for dependency cycles
npm run check:cycles

Support

When reporting a problem, include the SDK version, runtime, Solana cluster, and the error's code and status or programCode where available. Never include private notes, wallet secret keys, API keys, or authentication tokens.

License

ISC