Developer Guide
Integrate Velum private payments into your application using the official npm packages.
Packages
| Package | Purpose | Install |
|---|---|---|
@velumdotcash/api | Server-side REST client (paylinks, transactions) | npm install @velumdotcash/api |
@velumdotcash/sdk | Client-side ZK operations (deposit, withdraw, proofs) | npm install @velumdotcash/sdk |
Server-Side: @velumdotcash/api
Use @velumdotcash/api to create and manage payment links from your backend.
Installation
bash
npm install @velumdotcash/apiCreate a Payment Link
server.ts
typescript
import { VelumClient } from "@velumdotcash/api";
const client = new VelumClient({
apiKey: process.env.VELUM_API_KEY!,
// baseUrl defaults to "https://velum.cash/api"
});
// Create a payment link
const paylink = await client.paylinks.create({
recipientUtxoPubkey: "21888242871839...",
recipientEncryptionKey: "base64encodedkey...",
token: "USDC",
amountLamports: "1000000", // 1 USDC
memo: "Order #12345",
});
// Redirect your user to paylink.url
console.log(paylink.url); // https://velum.cash/pay/clx1234...Retrieve a Payment Link
typescript
const paylink = await client.paylinks.get("clx1234567890");
console.log(paylink.token, paylink.amountLamports);Log Transactions
typescript
await client.transactions.log({
type: "deposit",
token: "USDC",
amountLamports: "1000000",
signature: "5xK9m...",
status: "confirmed",
paylinkId: "clx1234567890",
});
const history = await client.transactions.list({
type: "deposit",
token: "USDC",
limit: 20,
});Error Handling
typescript
import { VelumClient, VelumApiError } from "@velumdotcash/api";
try {
await client.paylinks.get("nonexistent");
} catch (err) {
if (err instanceof VelumApiError) {
console.error(err.code); // "NOT_FOUND"
console.error(err.message); // "Paylink not found"
console.error(err.status); // 404
}
}Client-Side: @velumdotcash/sdk
Use @velumdotcash/sdk for client-side ZK proof generation, deposits, and withdrawals.
Installation
bash
npm install @velumdotcash/sdkInitialize the SDK
The SDK derives shielded keys from a wallet signature. The same wallet + message always produces the same keys.
client.ts
typescript
import { PrivacyCash } from "@velumdotcash/sdk";
// 1. Sign a deterministic message with the user's wallet
const message = new TextEncoder().encode(
`Welcome to Velum\n\nWallet: ${publicKey.toBase58()}`
);
const signature = await wallet.signMessage(message);
// 2. Initialize the SDK
const sdk = new PrivacyCash({
RPC_url: "https://api.mainnet-beta.solana.com",
publicKey: publicKey,
signature: signature,
transactionSigner: async (tx) => {
return await wallet.signTransaction(tx);
},
});
// 3. Get the shielded public keys (use these in payment links)
const encryptionPubkey = sdk.getAsymmetricPublicKey();
const utxoPubkey = await sdk.getShieldedPublicKey();Deposit (Shield Funds)
typescript
// Deposit SOL
const result = await sdk.deposit({ lamports: 10_000_000 }); // 0.01 SOL
console.log(result.tx); // transaction signature
// Deposit to a third-party recipient (paylink flow)
await sdk.deposit({
lamports: 10_000_000,
recipientUtxoPublicKey: paylink.recipientUtxoPubkey,
recipientEncryptionKey: Buffer.from(paylink.recipientEncryptionKey, "base64"),
});
// Deposit USDC
await sdk.depositUSDC({ base_units: 1_000_000 }); // 1 USDC
// Deposit any SPL token
await sdk.depositSPL({
base_units: 1_000_000,
mintAddress: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", // USDT
});Withdraw (Unshield Funds)
typescript
// Withdraw SOL to any address
const result = await sdk.withdraw({
lamports: 10_000_000,
recipientAddress: "Destination...",
});
// Withdraw USDC
await sdk.withdrawUSDC({
base_units: 1_000_000,
recipientAddress: "Destination...",
});
// Withdraw any SPL token
await sdk.withdrawSPL({
base_units: 1_000_000,
mintAddress: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
recipientAddress: "Destination...",
});Check Private Balance
typescript
const solBalance = await sdk.getPrivateBalance();
console.log(solBalance.lamports);
const usdcBalance = await sdk.getPrivateBalanceUSDC();
console.log(usdcBalance.base_units);
// Any SPL token
const usdtBalance = await sdk.getPrivateBalanceSpl("Es9vMF...");
console.log(usdtBalance.base_units);Full Integration Example
A typical integration combines both packages:
Backend: Create payment link
typescript
// Your backend creates the payment link
import { VelumClient } from "@velumdotcash/api";
const client = new VelumClient({ apiKey: process.env.VELUM_API_KEY! });
app.post("/checkout", async (req, res) => {
const paylink = await client.paylinks.create({
recipientUtxoPubkey: req.body.utxoPubkey,
recipientEncryptionKey: req.body.encryptionKey,
token: "USDC",
amountLamports: req.body.amount,
});
res.json({ paymentUrl: paylink.url });
});Frontend: User pays
typescript
// Your frontend uses the SDK to execute the payment
import { PrivacyCash } from "@velumdotcash/sdk";
async function payLink(paylinkId: string) {
const res = await fetch(`https://velum.cash/api/v1/paylinks/${paylinkId}`, {
headers: { "x-api-key": apiKey },
});
const paylink = await res.json();
await sdk.deposit({
lamports: Number(paylink.amountLamports),
recipientUtxoPublicKey: paylink.recipientUtxoPubkey,
recipientEncryptionKey: Buffer.from(paylink.recipientEncryptionKey, "base64"),
});
}Security Considerations
⚠️
Never expose your API key on the client side. Use @velumdotcash/api only in server environments.
- Store API keys in environment variables, never in client bundles
- The
@velumdotcash/sdkhandles all cryptographic operations client-side - Wallet signatures used for key derivation never leave the browser
- Use HTTPS for all API calls
- Implement rate limiting on your backend endpoints