Pick the privacy piece you need: encrypt a message for one recipient, split a key so M of N
parties must cooperate, or seal a swap order so only the solver can read it — each importable on
its own from @fabrknt/veil-core, with no Solana and no network. Solana extensions sit behind
their own import paths, and the apps, QuickNode add-on and MCP server further down show how the
pieces combine.
| I want to… | Import from | Pulls in at runtime |
|---|---|---|
| encrypt data for one or several recipients (NaCl box: X25519 + XSalsa20-Poly1305) | @fabrknt/veil-core/box |
tweetnacl, bs58 |
| split a 32-byte secret so any M of N shares rebuild it, or encrypt data under a key only M of N can rebuild (Shamir) | @fabrknt/veil-core/shamir |
tweetnacl |
| pack structured fields into fixed-size bytes and back | @fabrknt/veil-core/payload |
bn.js |
| seal a swap or perp order's limits so only the solver can read them (MEV protection) | @fabrknt/veil-core/orders |
box, shamir, payload, bn.js, Node crypto |
Solana extensions — built on Solana types; most need an RPC or a service endpoint to do anything.
They need the optional peer dependencies, which npm install @fabrknt/veil-core does not install:
npm install @solana/web3.js @lightprotocol/stateless.js @lightprotocol/compressed-tokenWithout them, importing a Solana path fails with Node's Cannot find module '@solana/web3.js'
(or @lightprotocol/...). @fabrknt/veil-core/solana loads all of them at once.
| I want to… | Import from | Loads | Status |
|---|---|---|---|
| compress accounts and tokens with Light Protocol | @fabrknt/veil-core/solana/zk-compression |
@solana/web3.js, @lightprotocol/* |
token calls go through @lightprotocol/*; compressData / decompressData are placeholders — the data is not compressed and the proof and root are zero-filled; compressData still sends a signed Light transaction from payer (pays a fee) |
| connect to Helius (with ZK compression) or QuickNode (plain RPC) | @fabrknt/veil-core/solana/rpc-providers |
@solana/web3.js, @lightprotocol/* |
works |
| shielded transfers (Privacy Cash) | @fabrknt/veil-core/solana/shielded |
@solana/web3.js |
stub — returns placeholder signatures, not wired to the Privacy Cash SDK |
| encrypted shared state for a dark pool (Arcium MPC) | @fabrknt/veil-core/solana/arcium |
@solana/web3.js |
partial — ArciumClient POSTs to the Arcium endpoint you configure; the dark-pool deposit and swap steps return placeholder values |
| swap / range proofs (Noir) | @fabrknt/veil-core/solana/noir |
— (Solana types only) | stub — simulated prover and verifier, runs locally, no Noir circuit |
import … from '@fabrknt/veil-core' exports box, shamir and payload — no Solana, so it works
without the peers. orders and the Solana extensions are only on their own paths.
Upgrading from 1.x:
- Solana parts left the root and the dependencies.
createZkRpc,shieldTokens,ArciumClient,NoirProverand the other Solana exports are no longer exported from@fabrknt/veil-core; import them from@fabrknt/veil-core/solana(all of them) or@fabrknt/veil-core/solana/<part>.@solana/web3.jsand@lightprotocol/*are now optional peer dependencies: install them yourself if you use a Solana part.privacycashandtweetnacl-utilwere never imported and are gone. A plain install drops from 194 packages (234 MB) to 5 (under 1 MB). createThresholdEncryptionoutput changed format. 1.x XORed the data with a repeating 32-byte key: equal 32-byte blocks of plaintext showed up as equal ciphertext, and a modified ciphertext decrypted without error. 2.0 encrypts with NaCl secretbox (XSalsa20-Poly1305), soencryptedSecretis a 24-byte nonce + 16-byte tag + ciphertext (40 bytes longer than the data), anddecryptWithThresholdthrows if the data was modified or the shares do not rebuild the key (e.g. fewer than the threshold). Data sealed with 1.x cannot be opened with 2.0; decrypt it with 1.x and re-encrypt.splitSecretrejects a secret at or above the field prime (0xFFFF…FFFEFFFFFC2F) instead of silently returning shares of a different value.exportsmap.dist,dist/index,dist/*.js,dist/*andpackage.jsonimports still resolve;@fabrknt/veil-core/dist/(with a trailing slash) no longer does, because Node does not allow that form inexports. Import@fabrknt/veil-coreinstead.
Each needs no keys, makes no network calls, and uses only the part it names (npx downloads tsx on first run):
git clone https://github.com/fabrknt/veil && cd veil
pnpm install --filter ./packages/core && cd packages/core
npx tsx examples/box-quickstart.ts # alice seals a message, bob opens it, eve can't
npx tsx examples/shamir-quickstart.ts # 5 shares, any 3 rebuild the secret, 2 don't
npx tsx examples/orders-quickstart.ts # a swap order only the solver can readsealed 26 bytes into 66 (24 nonce + 42 ciphertext)
bob opens it: "KYC record #4821: verified"
eve opens it: rejected
split into 5 shares, any 3 rebuild it
shares 1,3,5 rebuild it: true
shares 2,3,4 rebuild it: true
shares 1,2 only: false
order sealed: 64 bytes, min output not visible on the wire
solver reads: min output 995000000, slippage 50 bp, deadline 1767225600
npm install @fabrknt/veil-coreimport { generateEncryptionKeypair, encrypt, decrypt } from '@fabrknt/veil-core/box';
const sender = generateEncryptionKeypair();
const recipient = generateEncryptionKeypair();
const sealed = encrypt(new TextEncoder().encode('secret'), recipient.publicKey, sender);
// send sealed.bytes (24-byte nonce + ciphertext) any way you like
const opened = decrypt(sealed.bytes, sender.publicKey, recipient);
console.log(new TextDecoder().decode(opened)); // "secret"This installs 5 packages (tweetnacl, bs58, base-x, bn.js and veil-core itself); the
Solana peers come only if you install them.
Chain-agnostic encryption and privacy primitives for any blockchain, plus optional Solana-specific extensions.
Each module below has its own import path (see Pick a part). The
chain-agnostic ones (box, shamir, payload, orders) load no Solana code; the
solana/* ones need @solana/web3.js.
Curve25519-XSalsa20-Poly1305 authenticated encryption for encrypting order payloads, routing hints, and any data that must be kept private from MEV searchers. Works with any blockchain.
import {
generateEncryptionKeypair,
deriveEncryptionKeypair,
encrypt,
decrypt,
encryptForMultiple,
encryptionKeyToBase58,
base58ToEncryptionKey,
encryptionKeyToHex,
hexToEncryptionKey,
validateEncryptedData,
} from '@fabrknt/veil-core/box';
// Generate a random keypair
const alice = generateEncryptionKeypair();
const bob = generateEncryptionKeypair();
// Or derive deterministically from a wallet seed
const derived = deriveEncryptionKeypair(walletSecretKey.slice(0, 32));
// Encrypt data for a single recipient
const plaintext = new TextEncoder().encode('secret order data');
const encrypted = encrypt(plaintext, bob.publicKey, alice);
// encrypted.bytes contains nonce + ciphertext, ready for on-chain storage
// Decrypt
const decrypted = decrypt(encrypted.bytes, alice.publicKey, bob);
// Encrypt for multiple recipients (each gets their own ciphertext)
const solverKeys = [solver1.publicKey, solver2.publicKey];
const multiEncrypted = encryptForMultiple(plaintext, solverKeys, alice);
// Returns Map<hexPubkey, EncryptedData>
// Convert keys to/from base58 (Solana, Bitcoin)
const b58 = encryptionKeyToBase58(alice.publicKey); // "7xKX..."
const bytes = base58ToEncryptionKey(b58);
// Convert keys to/from hex (Ethereum, EVM chains)
const hex = encryptionKeyToHex(alice.publicKey); // "0xabcd..."
const bytesFromHex = hexToEncryptionKey(hex);
// Validate encrypted data structure
const isValid = validateEncryptedData(encrypted.bytes);interface EncryptionKeypair {
publicKey: Uint8Array; // 32 bytes, X25519
secretKey: Uint8Array; // 32 bytes
}
interface EncryptedData {
nonce: Uint8Array; // 24 bytes
ciphertext: Uint8Array;
bytes: Uint8Array; // nonce + ciphertext combined
}M-of-N threshold secret splitting for scenarios where multiple parties must cooperate to decrypt (e.g., multi-solver decryption, escrow). Pure math -- works on any blockchain.
import {
splitSecret,
combineShares,
verifyShares,
createThresholdEncryption,
decryptWithThreshold,
} from '@fabrknt/veil-core/shamir';
// Split a 32-byte secret into 5 shares, requiring 3 to reconstruct
const secret = new Uint8Array(32); // your secret key
crypto.getRandomValues(secret);
const shares = splitSecret(secret, 3, 5);
// shares[0..4], each with { index, value }
// Any 3 shares can reconstruct the secret
const reconstructed = combineShares([shares[0], shares[2], shares[4]]);
// reconstructed === secret
// Verify shares are consistent
const valid = verifyShares(shares, 3); // true
// Higher-level: encrypt data of any length with NaCl secretbox, split the key
const { encryptedSecret, keyShares } = createThresholdEncryption(
myData, // Uint8Array to protect
3, // threshold
5 // total shares
);
// Decrypt with enough shares (throws if the data was modified or the shares are too few).
// Pass the threshold so fewer than 3 shares are refused up front. This does not prove who made
// the bundle: sign it if someone could swap in their own key, shares and ciphertext.
const original = decryptWithThreshold(encryptedSecret, [
keyShares[0], keyShares[1], keyShares[3],
], 3);interface SecretShare {
index: number; // 1-based share index
value: Uint8Array; // 32 bytes
}
interface ThresholdConfig {
threshold: number; // minimum shares required (M)
totalShares: number; // total shares generated (N)
}splitSecret: the secret must be exactly 32 bytes and below the field prime (createThresholdEncryptiontakes data of any length and picks a valid key itself)- Threshold must be >= 2
- Total shares must be >= threshold and <= 255
Type-safe binary serialization for structured data that needs to go on-chain or be transported off-chain. Includes pre-defined schemas for common Veil payloads.
import {
serializePayload,
deserializePayload,
calculateSchemaSize,
SWAP_ORDER_SCHEMA,
RWA_ASSET_SCHEMA,
RWA_ACCESS_GRANT_SCHEMA,
} from '@fabrknt/veil-core/payload';
// Use a built-in schema
const size = calculateSchemaSize(SWAP_ORDER_SCHEMA);
const bytes = serializePayload({
minOutputAmount: new Uint8Array(8),
slippageBps: 50,
deadline: Math.floor(Date.now() / 1000) + 300,
padding: new Uint8Array(6),
}, SWAP_ORDER_SCHEMA);
const parsed = deserializePayload(bytes, SWAP_ORDER_SCHEMA);
// Define a custom schema
const MY_SCHEMA = {
fields: [
{ name: 'amount', type: 'u64' as const },
{ name: 'recipient', type: 'pubkey' as const },
{ name: 'memo', type: 'bytes' as const, size: 32 },
],
};| Type | Size | Description |
|---|---|---|
u8 |
1 byte | Unsigned 8-bit integer |
u16 |
2 bytes | Unsigned 16-bit integer (LE) |
u32 |
4 bytes | Unsigned 32-bit integer (LE) |
u64 |
8 bytes | Unsigned 64-bit integer (LE) |
i64 |
8 bytes | Signed 64-bit integer (LE) |
pubkey |
32 bytes | 32-byte public key (any chain) |
bytes |
variable | Raw bytes (requires size field) |
Compressed token operations through Light Protocol. Requires @solana/web3.js. compressData and decompressData are placeholders for now: they return the input unchanged with a zero-filled proof and root, and decompressData only checks the hash. compressData still sends a signed Light compress transaction (0 lamports) from payer, so it costs a transaction fee.
import {
createZkRpc,
compressData,
decompressData,
createCompressedMint,
mintCompressedTokens,
transferCompressedTokens,
getCompressedTokenBalance,
compressTokenAccount,
decompressTokenAccount,
estimateCompressionSavings,
} from '@fabrknt/veil-core/solana/zk-compression';
// Set up the ZK-enabled RPC
const rpc = createZkRpc({
rpcUrl: 'https://devnet.helius-rpc.com/?api-key=YOUR_KEY',
compressionRpcUrl: 'https://devnet.helius-rpc.com/?api-key=YOUR_KEY', // optional, defaults to rpcUrl
proverRpcUrl: 'https://devnet.helius-rpc.com/?api-key=YOUR_KEY', // optional, defaults to rpcUrl
});
// Compress arbitrary data
const payload = await compressData(rpc, myData, payerKeypair);
const original = await decompressData(rpc, payload);
// Compressed token operations
const mint = await createCompressedMint(rpc, payer, mintAuthority, 6);
await mintCompressedTokens(rpc, payer, mint, destination, mintAuthority, 1_000_000n);
await transferCompressedTokens(rpc, payer, mint, 500_000n, owner, toAddress);
const balance = await getCompressedTokenBalance(rpc, owner, mint);
// Move between compressed and standard token accounts
await compressTokenAccount(rpc, payer, owner, mint, 1_000_000n);
await decompressTokenAccount(rpc, payer, owner, mint, 500_000n);
// Estimate savings before compressing
const savings = estimateCompressionSavings(1024, 6960);
// { uncompressedCost, compressedCost, savings, savingsPercent }Private token transfers where amounts and participants are hidden on-chain. Requires @solana/web3.js.
import {
PrivacyCashClient,
createShieldedTransfer,
verifyShieldedProof,
estimateShieldedFee,
isPrivacyCashAvailable,
shieldTokens,
unshieldTokens,
} from '@fabrknt/veil-core/solana/shielded';
// Check if Privacy Cash is available on the network
const available = await isPrivacyCashAvailable(connection);
// High-level client
const client = new PrivacyCashClient({ rpcUrl: 'https://api.devnet.solana.com', network: 'devnet' });
await client.initialize(walletKeypair);
// Check private balance
const balance = await client.getPrivateBalance();
const usdcBalance = await client.getPrivateBalanceSpl('USDC');
// Deposit into shielded pool
const deposit = await client.deposit(1_000_000_000n); // 1 SOL
const depositUsdc = await client.depositSpl(1_000_000n, 'USDC');
// Withdraw from shielded pool
const withdrawal = await client.withdraw(500_000_000n, recipientPubkey);
const withdrawUsdc = await client.withdrawSpl(500_000n, recipientPubkey, 'USDC');
// Lower-level functions
const txId = await createShieldedTransfer(connection, sender, {
amount: 1_000_000n,
recipient: recipientPubkey,
tokenType: 'USDC',
});
const fee = estimateShieldedFee('USDC');
// Convenience wrappers
await shieldTokens(connection, wallet, 1_000_000n, 'USDC');
await unshieldTokens(connection, wallet, 500_000n, recipient, 'USDC');Encrypted shared state and multi-party computation for dark pools and confidential DeFi. Requires @solana/web3.js.
import {
ArciumClient,
DarkPoolStateManager,
createArciumClient,
createDarkPoolManager,
} from '@fabrknt/veil-core/solana/arcium';
// Create an Arcium client
const arcium = createArciumClient(connection, 'devnet', 'YOUR_API_KEY');
// Encrypt data for shared state
const encrypted = arcium.encryptForState(data, recipientPubkey);
const decrypted = arcium.decryptFromState(encrypted, senderPubkey);
// Submit an encrypted position to a dark pool
const position = await arcium.submitEncryptedPosition(
poolAddress,
1_000_000n, // amount
poolEncryptionKey // pool's public key
);
// Submit a dark order
const order = await arcium.submitDarkOrder(
inputMint,
outputMint,
inputAmount,
minOutputAmount,
deadline,
solverKey
);
// Query aggregated pool state (without revealing individual positions)
const aggregates = await arcium.queryPoolAggregates(poolAddress);
// Run an MPC computation
const result = await arcium.mpcCompute('sum', new Map([['input1', input1], ['input2', input2]]));
// Dark pool management
const pool = createDarkPoolManager(connection, poolAddress, 'devnet');
await pool.processEncryptedDeposit(position);
await pool.processDarkSwap(order, proof);
const poolAggregates = await pool.getAggregates();Simulated: the prover and verifier run locally and do not execute a Noir circuit yet, so these proofs carry no cryptographic guarantee. The API shape is what a real Noir backend would plug into.
import {
createNoirProver,
createNoirVerifier,
generateSwapProof,
verifySwapProof,
generateRangeProof,
} from '@fabrknt/veil-core/solana/noir';
// Generate a swap validity proof
const swapProof = await generateSwapProof(
1_000_000n, // inputAmount
990_000n, // minOutputAmount
commitment, // balanceCommitment (Uint8Array)
root, // poolStateRoot (Uint8Array)
proof, // balanceMerkleProof (Uint8Array[], optional)
);
// Verify the proof
const valid = await verifySwapProof(swapProof); // boolean
// Generate a range proof (prove value is within bounds without revealing it)
const rangeProof = await generateRangeProof(500_000n, 0n, 1_000_000n, commitment);
// Or use the prover/verifier instances directly
const prover = createNoirProver();
const verifier = createNoirVerifier();Unified configuration for connecting to Solana RPC providers with ZK compression support. Requires @solana/web3.js.
import {
createRpcConnections,
createHeliusRpc,
createQuicknodeRpc,
createRpcFromEnv,
createPublicRpc,
getRpcAttribution,
} from '@fabrknt/veil-core/solana/rpc-providers';
// From explicit config
const { connection, zkRpc } = createRpcConnections({
provider: 'helius',
apiKey: 'YOUR_KEY',
network: 'devnet',
enableZkCompression: true,
});
// Convenience constructors
const helius = createHeliusRpc('YOUR_KEY', 'devnet', true);
const quicknode = createQuicknodeRpc('https://your-qn.url', 'devnet');
// Auto-detect from environment variables
// Checks HELIUS_API_KEY -> QUICKNODE_ENDPOINT -> RPC_URL
const fromEnv = createRpcFromEnv();
// Public RPC (testing only — rate limited)
const publicConn = createPublicRpc('devnet');
// Attribution string for UI
const attribution = getRpcAttribution(helius); // "Powered by Helius"Chain-agnostic order encryption utilities for any DEX. Ships in @fabrknt/veil-core as the
/orders path (the workspace package @fabrknt/veil-orders re-exports the same code). Builds on box, shamir and payload to provide a high-level API for encrypting swap orders that solvers can decrypt but MEV searchers cannot. Works with Solana, Ethereum, and any other blockchain.
import {
createEncryptedOrder,
decryptOrderPayload,
generateEncryptionKeypair,
} from '@fabrknt/veil-core/orders';
import BN from 'bn.js';
// User side: encrypt an order
const userKeypair = generateEncryptionKeypair();
const solverPublicKey = /* solver's known public key */;
const encryptedBytes = createEncryptedOrder(
new BN('1000000'), // minOutputAmount (1 USDC)
50, // slippageBps (0.5%)
Math.floor(Date.now() / 1000) + 300, // deadline (5 minutes)
solverPublicKey,
userKeypair,
);
// encryptedBytes is ready for submission (on-chain or off-chain)
// Solver side: decrypt the order
const order = decryptOrderPayload(
encryptedBytes,
userKeypair.publicKey, // user's public key (from order account or off-chain exchange)
solverKeypair,
);
console.log(order.minOutputAmount.toString()); // "1000000"
console.log(order.slippageBps); // 50
console.log(order.deadline); // unix timestampimport {
serializeOrderPayload,
deserializeOrderPayload,
encryptOrderPayload,
decryptOrderPayload,
createEncryptedOrder,
validateEncryptedPayload,
getEncryptionPublicKey,
} from '@fabrknt/veil-core/orders';
// Serialize/deserialize without encryption (useful for testing)
const payload = {
minOutputAmount: new BN('1000000'),
slippageBps: 50,
deadline: 1700000000,
};
const bytes = serializeOrderPayload(payload);
const parsed = deserializeOrderPayload(bytes);
// Encrypt with full control over the EncryptedPayload object
const encrypted = encryptOrderPayload(payload, solverPublicKey, userKeypair);
// encrypted.nonce — 24-byte nonce
// encrypted.ciphertext — encrypted order data
// encrypted.bytes — nonce + ciphertext combined
// Validate that bytes look like a valid encrypted payload
const isValid = validateEncryptedPayload(encrypted.bytes); // true
// Extract public key from keypair to share with solver
const pubkey = getEncryptionPublicKey(userKeypair);interface OrderPayload {
minOutputAmount: BN; // Minimum tokens the user expects
slippageBps: number; // Slippage tolerance (50 = 0.5%)
deadline: number; // Unix timestamp in seconds
routingHint?: Uint8Array; // Optional routing data
}
interface EncryptedPayload {
nonce: Uint8Array; // 24-byte NaCl nonce
ciphertext: Uint8Array; // Encrypted order data
bytes: Uint8Array; // Combined nonce + ciphertext
}These use the parts above together. They are examples of combining them, not something you need to install to use a part.
| Where | What it combines |
|---|---|
packages/qn-addon |
QuickNode Marketplace REST add-on serving box, shamir, payload, orders and compression over HTTP |
packages/mcp-server |
MCP server exposing the same tools to AI agents (chain-agnostic + Solana identity tools) |
packages/browser |
Browser client that keeps encryption on the user's device |
apps/dark-pool |
Veil Dark Pool — shielded perp execution on Solana (devnet) |
apps/* |
5 more Solana apps (confidential swaps, RWA secrets, umbra, darkflow, shadowlaunch) |
qn-addon, mcp-server and browser are not on npm; run them from this repo.
Fabrknt Privacy (slug: fabrknt-privacy, author: Fabrknt) — QuickNode Marketplace REST Add-On that wraps Veil privacy primitives as a JSON API. The core endpoints (encryption, Shamir, orders, payload serialization) are chain-agnostic and work regardless of which blockchain your QuickNode endpoint is connected to. Solana-specific endpoints (ZK compression via Light Protocol) require a Solana QuickNode endpoint.
# From the monorepo root
pnpm install && pnpm build
# Start the add-on server (default port 3030)
cd packages/qn-addon
pnpm dev# Health check
curl http://localhost:3030/healthcheck
# {"status":"ok"}The add-on implements the four mandatory QuickNode Marketplace provisioning endpoints, all protected by HTTP Basic Auth:
| Method | Path | Action |
|---|---|---|
| POST | /provision |
Store or update endpoint (idempotent — quicknode-id, endpoint-id, plan, http-url, chain, network) |
| PUT | /update |
Update an existing endpoint |
| DELETE | /deactivate_endpoint |
Soft-deactivate (set active=0) |
| DELETE | /deprovision |
Hard-delete all instances for an account |
# Provision an endpoint
curl -X POST http://localhost:3030/provision \
-u quicknode:changeme \
-H "Content-Type: application/json" \
-d '{"quicknode-id":"qn-1","endpoint-id":"ep-1","plan":"starter","http-url":"https://example.solana-mainnet.quiknode.pro/abc","chain":"solana","network":"mainnet-beta"}'
# {"status":"success"}All binary data in JSON uses base64 encoding. Key material responses include both base64 and hex fields.
# Generate a keypair
curl -X POST http://localhost:3030/v1/keypair/generate
# {"success":true,"publicKey":{"base64":"...","hex":"..."},"secretKey":{"base64":"...","hex":"..."}}
# Derive a keypair from a seed
curl -X POST http://localhost:3030/v1/keypair/derive \
-H "Content-Type: application/json" \
-d '{"seed":"<base64-encoded-32-bytes>"}'
# Encrypt
curl -X POST http://localhost:3030/v1/encrypt \
-H "Content-Type: application/json" \
-d '{"plaintext":"<base64>","recipientPublicKey":"<base64>","senderSecretKey":"<base64>","senderPublicKey":"<base64>"}'
# Decrypt
curl -X POST http://localhost:3030/v1/decrypt \
-H "Content-Type: application/json" \
-d '{"bytes":"<base64>","senderPublicKey":"<base64>","recipientSecretKey":"<base64>","recipientPublicKey":"<base64>"}'
# Encrypt for multiple recipients at once
curl -X POST http://localhost:3030/v1/crypto/encrypt-multiple \
-H "Content-Type: application/json" \
-d '{"plaintext":"<base64>","recipientPublicKeys":["<base64>","<base64>"],"senderSecretKey":"<base64>","senderPublicKey":"<base64>"}'
# Validate encrypted data structure
curl -X POST http://localhost:3030/v1/crypto/validate \
-H "Content-Type: application/json" \
-d '{"bytes":"<base64>"}'
# Convert key between base64 and base58
curl -X POST http://localhost:3030/v1/crypto/key-convert \
-H "Content-Type: application/json" \
-d '{"publicKey":"<base64>"}'
# or: -d '{"base58":"7xKX..."}'# Split a 32-byte secret into shares
curl -X POST http://localhost:3030/v1/threshold/split \
-H "Content-Type: application/json" \
-d '{"secret":"<base64-32-bytes>","threshold":3,"totalShares":5}'
# Combine shares to recover the secret
curl -X POST http://localhost:3030/v1/threshold/combine \
-H "Content-Type: application/json" \
-d '{"shares":[{"index":1,"value":"<base64>"},{"index":3,"value":"<base64>"},{"index":5,"value":"<base64>"}]}'
# Verify shares are consistent with a given threshold
curl -X POST http://localhost:3030/v1/threshold/verify \
-H "Content-Type: application/json" \
-d '{"shares":[{"index":1,"value":"<base64>"},{"index":2,"value":"<base64>"}],"threshold":2}'# Encrypt an order payload
curl -X POST http://localhost:3030/v1/orders/encrypt \
-H "Content-Type: application/json" \
-d '{"minOutputAmount":"1000000","slippageBps":50,"deadline":1700000000,"solverPublicKey":"<base64>","userSecretKey":"<base64>","userPublicKey":"<base64>"}'
# Decrypt an order payload
curl -X POST http://localhost:3030/v1/orders/decrypt \
-H "Content-Type: application/json" \
-d '{"bytes":"<base64>","userPublicKey":"<base64>","solverSecretKey":"<base64>","solverPublicKey":"<base64>"}'
# Validate encrypted order structure
curl -X POST http://localhost:3030/v1/orders/validate \
-H "Content-Type: application/json" \
-d '{"bytes":"<base64>"}'# Serialize structured data (schemas: SWAP_ORDER, RWA_ASSET, RWA_ACCESS_GRANT)
curl -X POST http://localhost:3030/v1/payload/serialize \
-H "Content-Type: application/json" \
-d '{"data":{"minOutputAmount":"5000000","slippageBps":100,"deadline":1700000000,"padding":"AAAAAAAA"},"schema":"SWAP_ORDER"}'
# Deserialize bytes back to structured data
curl -X POST http://localhost:3030/v1/payload/deserialize \
-H "Content-Type: application/json" \
-d '{"bytes":"<base64>","schema":"SWAP_ORDER"}'# Estimate compression savings (stateless, no RPC needed)
curl "http://localhost:3030/v1/compression/estimate?size=4096"
# {"success":true,"dataSize":4096,"uncompressedCost":"...","compressedCost":"...","savings":"...","savingsPercent":...}
# Compress data (requires provisioned endpoint with http-url)
# Header X-INSTANCE-ID must match a provisioned endpoint
curl -X POST http://localhost:3030/v1/compression/compress \
-H "Content-Type: application/json" \
-H "X-INSTANCE-ID: ep-1" \
-d '{"data":"<base64>","payerSecretKey":"<base64>"}'
# Decompress data
curl -X POST http://localhost:3030/v1/compression/decompress \
-H "Content-Type: application/json" \
-H "X-INSTANCE-ID: ep-1" \
-d '{"compressedData":"<base64>","proof":"<base64>","publicInputs":"<base64>","stateTreeRoot":"<base64>","dataHash":"<base64>"}'Copy .env.example and edit:
PORT=3030 # Server port
QN_BASIC_AUTH_USERNAME=quicknode # Basic Auth for PUDD endpoints
QN_BASIC_AUTH_PASSWORD=changeme
DB_PATH=./data/qn-addon.db # SQLite database pathAPI endpoints are rate-limited by default:
| Scope | Limit |
|---|---|
API endpoints (/v1/*) |
100 requests/minute per IP |
Provisioning (/provision, /update, etc.) |
20 requests/minute per IP |
Health check (/healthcheck) |
No limit |
Rate limit headers (RateLimit-*) are included in responses per the IETF draft standard.
Build and run from the monorepo root:
docker build -f packages/qn-addon/Dockerfile -t veil-qn-addon .
docker run -p 3030:3030 \
-e QN_BASIC_AUTH_USERNAME=quicknode \
-e QN_BASIC_AUTH_PASSWORD=your-secret \
veil-qn-addoncd packages/qn-addon
# Run all tests (125 tests across 21 suites)
pnpm test
# End-to-end curl test (server must be running)
./scripts/test-qn-cli.sh
# Validate with QuickNode's official CLI (requires Go)
go install github.com/quiknode-labs/qn-marketplace-cli@latest
qn-marketplace-cli pudd \
--base-url http://localhost:3030 \
--basic-auth "$(echo -n 'quicknode:changeme' | base64)" \
--chain solana --network mainnet-beta --plan starter
# Tier 2 RPC test (requires real QuickNode Solana devnet endpoint)
export QN_HTTP_URL="https://your-endpoint.quiknode.pro/..."
export PAYER_SECRET_KEY="<base64-encoded-solana-keypair>"
./scripts/test-tier2-rpc.shGitHub Actions runs pnpm build and pnpm test on every push and PR to main, tested against Node 20 and 22.
Pull requests that touch one of the Anchor programs under apps/*/programs/ also run
solinv's solinv diff (.github/workflows/solinv.yml), all five
programs in one job. The job fails, with an annotation on the line, when the change removes an account check
such as a has_one, seeds, a constraint or a signer. It reads the source only: no Solana toolchain,
no build.
The add-on is also available on RapidAPI as a hosted API. The OpenAPI specification is at openapi-rapidapi.yaml.
https://veil-privacy-suite.p.rapidapi.com
All the same endpoints listed below are available via RapidAPI with your RapidAPI API key.
Chain-agnostic endpoints (work with any blockchain):
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /healthcheck |
Health check | None |
| GET | / |
Add-on info and endpoint listing | None |
| POST | /provision |
Provision endpoint | Basic |
| PUT | /update |
Update endpoint | Basic |
| DELETE | /deactivate_endpoint |
Soft-deactivate | Basic |
| DELETE | /deprovision |
Hard-delete account | Basic |
| POST | /v1/keypair/generate |
Generate NaCl keypair | None |
| POST | /v1/keypair/derive |
Derive keypair from seed | None |
| POST | /v1/encrypt |
NaCl box encrypt | None |
| POST | /v1/decrypt |
NaCl box decrypt | None |
| POST | /v1/crypto/encrypt-multiple |
Encrypt for multiple recipients | None |
| POST | /v1/crypto/validate |
Validate encrypted data structure | None |
| POST | /v1/crypto/key-convert |
Convert key between base64, base58, and hex | None |
| POST | /v1/threshold/split |
Shamir split secret | None |
| POST | /v1/threshold/combine |
Shamir combine shares | None |
| POST | /v1/threshold/verify |
Verify shares consistency | None |
| POST | /v1/orders/encrypt |
Encrypt swap order | None |
| POST | /v1/orders/decrypt |
Decrypt swap order | None |
| POST | /v1/orders/validate |
Validate encrypted order structure | None |
| POST | /v1/payload/serialize |
Serialize structured data | None |
| POST | /v1/payload/deserialize |
Deserialize structured data | None |
| GET | /v1/compression/estimate |
Estimate ZK compression savings | None |
Solana-specific endpoints (require Solana QuickNode endpoint):
| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /v1/compression/compress |
Compress data via Light Protocol | X-INSTANCE-ID |
| POST | /v1/compression/decompress |
Decompress data via Light Protocol | X-INSTANCE-ID |
Model Context Protocol server that exposes Veil privacy primitives as MCP tools for AI agents (Claude, GPT, etc.). Runs over stdio transport. Each tool is labeled [Chain-agnostic] or [Solana-specific] so AI agents know which tools work on any chain vs Solana only.
# Build
cd packages/mcp-server
pnpm build
# Run directly
node dist/index.js
# Or use the bin alias
npx veil-mcpAdd to your Claude Desktop claude_desktop_config.json:
{
"mcpServers": {
"veil-privacy-suite": {
"command": "node",
"args": ["/path/to/veil/packages/mcp-server/dist/index.js"]
}
}
}| Tool | Description |
|---|---|
generate_keypair |
Generate a random NaCl Box encryption keypair |
derive_keypair |
Derive a deterministic keypair from a 32-byte seed |
encrypt |
Encrypt a message using NaCl Box |
decrypt |
Decrypt NaCl Box ciphertext |
encrypt_multiple |
Encrypt for multiple recipients at once |
validate_encrypted |
Validate encrypted data structure without decrypting |
key_convert |
Convert keys between base64, base58 (Solana), and hex (EVM) |
shamir_split |
Split a 32-byte secret into M-of-N Shamir shares |
shamir_combine |
Reconstruct a secret from Shamir shares |
shamir_verify |
Verify that shares are consistent |
encrypt_order |
Encrypt a DEX swap order payload (MEV protection) |
decrypt_order |
Decrypt an encrypted swap order payload |
vault_read |
Read encrypted data from a DataSov2 vault on Arweave |
vault_disclose |
Selective disclosure of encrypted vault fields via ECDH |
| Tool | Description |
|---|---|
sovereign_read |
Read SOVEREIGN identity scores and tier from Solana |
trust_query |
Query LATTICE trust graph for a SOVEREIGN dimension |
trust_score |
Check trust level of a wallet for a SOVEREIGN dimension |
ephemeral_wallet |
Generate a fresh ephemeral Solana wallet |
zk_prove_tier |
ZK proof that SOVEREIGN tier meets a minimum threshold |
All binary data (keys, ciphertext, nonces, secrets) is base64-encoded in both inputs and outputs.
User: Generate an encryption keypair and encrypt "hello" for another keypair.
Agent calls: generate_keypair → gets sender keys
Agent calls: generate_keypair → gets recipient keys
Agent calls: encrypt { message: <base64("hello")>, recipientPublicKey: ..., senderSecretKey: ..., senderPublicKey: ... }
→ returns { nonce, ciphertext, bytes } (all base64)
Privacy-focused DeFi applications built on Veil primitives, targeting Solana hackathons (PrivacyHack 2026, FairScale 2026, Pump.fun Build In Public 2026).
| App | Description | Program ID |
|---|---|---|
confidential-swap-router |
MEV-protected swaps via encrypted order parameters | v7th...CCRM |
rwa-secrets-service |
End-to-end encrypted RWA metadata with role-based access | DWgi...ySam |
umbra |
Reputation-gated privacy via SOVEREIGN tier routing | 41Ps...QXLr |
darkflow |
Encrypted LP positions, solver-co-signed dark swaps and orders (ZK proofs are mocks) | 8Uvv...s38U |
shadowlaunch |
Private pump.fun purchases via ephemeral wallets + shielded transfers | -- |
Each app includes an Anchor program (programs/), TypeScript SDK (sdk/), and Next.js frontend (app/). Some include a solver service (solver/).
These programs were built for hackathons and are not audited; do not deploy them with real funds.
In darkflow, pool creation is limited to a config admin set by the upgrade authority, and order
fills and dark swaps must be co-signed by the pool's solver; its proof checks are hash-based
placeholders. See apps/darkflow for roles, deploy
order and the account layout changes.
Hackathon documentation is in docs/solana/.
# Install dependencies
pnpm install
# Build all packages (uses turbo)
pnpm build
# Run tests
pnpm test# Solana-specific RPC provider (only needed for Solana features: ZK compression, shielded transfers)
HELIUS_API_KEY=your-helius-key # Recommended — supports ZK compression
QUICKNODE_ENDPOINT=https://your-qn.url # Alternative
RPC_URL=https://custom-rpc.url # Fallback
# Network (Solana-specific)
SOLANA_NETWORK=devnet # mainnet-beta | devnet | testnet| Package | Chain-agnostic deps | Solana-specific deps (optional) |
|---|---|---|
@fabrknt/veil-core |
tweetnacl, bs58, bn.js |
optional peers: @solana/web3.js, @lightprotocol/stateless.js, @lightprotocol/compressed-token (install them yourself for /solana/*) |
@fabrknt/veil-orders (workspace only) |
re-exports @fabrknt/veil-core/orders |
-- |
@fabrknt/veil-qn-addon |
@fabrknt/veil-core, @fabrknt/veil-orders, express, better-sqlite3, morgan, express-rate-limit, bn.js |
@solana/web3.js, @lightprotocol/* (compression endpoints) |
@fabrknt/veil-mcp-server |
@modelcontextprotocol/sdk, @fabrknt/veil-core, @fabrknt/veil-orders, zod |
@solana/web3.js (for SOVEREIGN/trust tools) |
Apache-2.0