@velumdotcash/sdk 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -61
- package/dist/deposit.js +13 -6
- package/dist/depositSPL.js +9 -5
- package/dist/utils/constants.d.ts +2 -0
- package/dist/utils/constants.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# @velumdotcash/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@velumdotcash/sdk)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
TypeScript SDK for private payments on Solana using Zero-Knowledge proofs. Deposit, withdraw, and transfer shielded funds with ZK-SNARKs.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
6
9
|
|
|
@@ -10,116 +13,125 @@ npm install @velumdotcash/sdk
|
|
|
10
13
|
|
|
11
14
|
## Quick Start
|
|
12
15
|
|
|
16
|
+
### Browser (with wallet adapter)
|
|
17
|
+
|
|
13
18
|
```typescript
|
|
14
19
|
import { PrivacyCash } from "@velumdotcash/sdk";
|
|
15
20
|
|
|
16
|
-
//
|
|
21
|
+
// Sign a deterministic message to derive shielded keys
|
|
22
|
+
const message = new TextEncoder().encode(
|
|
23
|
+
`Welcome to Velum\n\nSign this message to derive your private encryption keys.\n\nThis request will not trigger a blockchain transaction or cost any fees.\n\nWallet: ${publicKey.toBase58()}`
|
|
24
|
+
);
|
|
25
|
+
const signature = await wallet.signMessage(message);
|
|
26
|
+
|
|
17
27
|
const sdk = new PrivacyCash({
|
|
18
28
|
RPC_url: "https://api.mainnet-beta.solana.com",
|
|
19
29
|
publicKey: walletPublicKey,
|
|
20
30
|
signature: walletSignature,
|
|
21
31
|
transactionSigner: async (tx) => wallet.signTransaction(tx),
|
|
22
32
|
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Node.js (with keypair)
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Keypair } from "@solana/web3.js";
|
|
39
|
+
import { PrivacyCash } from "@velumdotcash/sdk";
|
|
23
40
|
|
|
24
|
-
// Or with keypair (Node.js / scripts)
|
|
25
41
|
const sdk = new PrivacyCash({
|
|
26
|
-
RPC_url:
|
|
27
|
-
owner:
|
|
42
|
+
RPC_url: process.env.SOLANA_RPC_URL!,
|
|
43
|
+
owner: Keypair.fromSecretKey(secretKey),
|
|
44
|
+
circuitPath: "./circuits",
|
|
28
45
|
});
|
|
29
46
|
```
|
|
30
47
|
|
|
31
|
-
##
|
|
32
|
-
|
|
33
|
-
### Deposits
|
|
48
|
+
## Deposits
|
|
34
49
|
|
|
35
50
|
```typescript
|
|
36
|
-
// Deposit SOL
|
|
37
|
-
await sdk.deposit({ lamports: 10_000_000 });
|
|
51
|
+
// Deposit SOL to your shielded account
|
|
52
|
+
await sdk.deposit({ lamports: 10_000_000 }); // 0.01 SOL
|
|
38
53
|
|
|
39
54
|
// Deposit USDC
|
|
40
|
-
await sdk.depositUSDC({ base_units: 1_000_000 });
|
|
55
|
+
await sdk.depositUSDC({ base_units: 1_000_000 }); // 1 USDC
|
|
41
56
|
|
|
42
57
|
// Deposit any SPL token
|
|
43
|
-
await sdk.depositSPL({
|
|
58
|
+
await sdk.depositSPL({
|
|
59
|
+
base_units: 1_000_000,
|
|
60
|
+
mintAddress: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
|
61
|
+
});
|
|
44
62
|
|
|
45
|
-
// Deposit to a third-party recipient
|
|
63
|
+
// Deposit to a third-party recipient (payment link flow)
|
|
46
64
|
await sdk.deposit({
|
|
47
65
|
lamports: 10_000_000,
|
|
48
|
-
recipientUtxoPublicKey:
|
|
49
|
-
recipientEncryptionKey:
|
|
66
|
+
recipientUtxoPublicKey: recipientPubkey,
|
|
67
|
+
recipientEncryptionKey: recipientEncKey,
|
|
50
68
|
});
|
|
51
69
|
```
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
## Withdrawals
|
|
54
72
|
|
|
55
73
|
```typescript
|
|
56
|
-
// Withdraw SOL
|
|
57
|
-
await sdk.withdraw({
|
|
74
|
+
// Withdraw SOL to any address
|
|
75
|
+
await sdk.withdraw({
|
|
76
|
+
lamports: 10_000_000,
|
|
77
|
+
recipientAddress: "Destination...",
|
|
78
|
+
});
|
|
58
79
|
|
|
59
80
|
// Withdraw USDC
|
|
60
|
-
await sdk.withdrawUSDC({
|
|
81
|
+
await sdk.withdrawUSDC({
|
|
82
|
+
base_units: 1_000_000,
|
|
83
|
+
recipientAddress: "Destination...",
|
|
84
|
+
});
|
|
61
85
|
|
|
62
86
|
// Withdraw any SPL token
|
|
63
87
|
await sdk.withdrawSPL({
|
|
64
88
|
base_units: 1_000_000,
|
|
65
|
-
mintAddress: "
|
|
66
|
-
recipientAddress: "...",
|
|
89
|
+
mintAddress: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
|
90
|
+
recipientAddress: "Destination...",
|
|
67
91
|
});
|
|
68
92
|
```
|
|
69
93
|
|
|
70
|
-
|
|
94
|
+
## Private Balance
|
|
71
95
|
|
|
72
96
|
```typescript
|
|
73
97
|
const sol = await sdk.getPrivateBalance();
|
|
74
|
-
console.log(sol.lamports);
|
|
75
|
-
|
|
76
98
|
const usdc = await sdk.getPrivateBalanceUSDC();
|
|
77
|
-
console.log(usdc.base_units);
|
|
78
|
-
|
|
79
99
|
const spl = await sdk.getPrivateBalanceSpl(mintAddress);
|
|
80
|
-
console.log(spl.base_units);
|
|
81
100
|
```
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
## Key Derivation
|
|
84
103
|
|
|
85
104
|
```typescript
|
|
86
|
-
// Get public keys
|
|
87
|
-
const encryptionKey = sdk.getAsymmetricPublicKey(); // Uint8Array
|
|
88
|
-
const utxoPubkey = await sdk.getShieldedPublicKey(); // string
|
|
105
|
+
// Get your shielded public keys (share these to receive payments)
|
|
106
|
+
const encryptionKey = sdk.getAsymmetricPublicKey(); // Uint8Array (X25519)
|
|
107
|
+
const utxoPubkey = await sdk.getShieldedPublicKey(); // string (BN254)
|
|
89
108
|
```
|
|
90
109
|
|
|
91
|
-
##
|
|
110
|
+
## How It Works
|
|
92
111
|
|
|
93
|
-
|
|
112
|
+
1. **Key derivation**: A wallet signature deterministically derives two keypairs — BN254 (UTXO ownership) and X25519 (note encryption)
|
|
113
|
+
2. **Deposit**: Funds enter a shielded pool via a ZK-SNARK that proves validity without revealing the amount or recipient
|
|
114
|
+
3. **UTXO creation**: An encrypted note is stored onchain — only the recipient's X25519 key can decrypt it
|
|
115
|
+
4. **Withdraw**: A second ZK proof verifies UTXO ownership without revealing which deposit created it
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
const sdk = new PrivacyCash({
|
|
97
|
-
RPC_url: rpcEndpoint,
|
|
98
|
-
publicKey: walletPublicKey,
|
|
99
|
-
signature: walletSignature,
|
|
100
|
-
transactionSigner: async (tx) => wallet.signTransaction(tx),
|
|
101
|
-
circuitPath: "/circuit", // path to circuit files in public/
|
|
102
|
-
});
|
|
103
|
-
```
|
|
117
|
+
The result: no onchain link between sender and receiver.
|
|
104
118
|
|
|
105
|
-
##
|
|
119
|
+
## Circuit Files
|
|
106
120
|
|
|
107
|
-
|
|
121
|
+
The SDK requires ZK circuit files (`circuit.wasm` ~3MB, `circuit.zkey` ~16MB) for proof generation.
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
import { Keypair } from "@solana/web3.js";
|
|
123
|
+
**Browser**: Serve from your public directory. Files are lazy-loaded on first deposit/withdraw and cached in IndexedDB.
|
|
111
124
|
|
|
112
|
-
|
|
125
|
+
```typescript
|
|
113
126
|
const sdk = new PrivacyCash({
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
storage: new LocalStorage("./cache"),
|
|
117
|
-
circuitPath: "./circuits",
|
|
118
|
-
enableDebug: true,
|
|
127
|
+
// ...
|
|
128
|
+
circuitPath: "/circuit", // relative to your public dir
|
|
119
129
|
});
|
|
120
130
|
```
|
|
121
131
|
|
|
122
|
-
|
|
132
|
+
**Node.js**: Point to a local directory containing the circuit files.
|
|
133
|
+
|
|
134
|
+
## Error Types
|
|
123
135
|
|
|
124
136
|
```typescript
|
|
125
137
|
import {
|
|
@@ -128,15 +140,14 @@ import {
|
|
|
128
140
|
NetworkError,
|
|
129
141
|
TransactionTimeoutError,
|
|
130
142
|
} from "@velumdotcash/sdk";
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
await sdk.deposit({ lamports: amount });
|
|
134
|
-
} catch (err) {
|
|
135
|
-
if (err instanceof InsufficientBalanceError) { /* ... */ }
|
|
136
|
-
if (err instanceof ZKProofError) { /* ... */ }
|
|
137
|
-
}
|
|
138
143
|
```
|
|
139
144
|
|
|
145
|
+
## Related
|
|
146
|
+
|
|
147
|
+
- [`@velumdotcash/api`](https://www.npmjs.com/package/@velumdotcash/api) — Server-side REST client for paylinks and transactions
|
|
148
|
+
- [Developer Guide](https://velum.cash/docs/developer-guide) — Full integration documentation
|
|
149
|
+
- [How It Works](https://velum.cash/docs/how-it-works) — Cryptographic architecture
|
|
150
|
+
|
|
140
151
|
## License
|
|
141
152
|
|
|
142
|
-
|
|
153
|
+
[MIT](./LICENSE)
|
package/dist/deposit.js
CHANGED
|
@@ -8,7 +8,7 @@ import { InsufficientBalanceError, DepositLimitError, TransactionTimeoutError, }
|
|
|
8
8
|
import { serializeProofAndExtData, } from "./utils/encryption.js";
|
|
9
9
|
import { Keypair as UtxoKeypair } from "./models/keypair.js";
|
|
10
10
|
import { getUtxos } from "./getUtxos.js";
|
|
11
|
-
import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS, } from "./utils/constants.js";
|
|
11
|
+
import { FIELD_SIZE, FEE_RECIPIENT, VELUM_FEE_WALLET, VELUM_FEE_BPS, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS, } from "./utils/constants.js";
|
|
12
12
|
import { useExistingALT } from "./utils/address_lookup_table.js";
|
|
13
13
|
import { logger } from "./utils/logger.js";
|
|
14
14
|
// Function to relay pre-signed deposit transaction to indexer backend
|
|
@@ -56,15 +56,16 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
|
|
|
56
56
|
}
|
|
57
57
|
// const amount_in_lamports = amount_in_sol * LAMPORTS_PER_SOL
|
|
58
58
|
const fee_amount_in_lamports = 0;
|
|
59
|
+
const velum_fee_lamports = Math.ceil(amount_in_lamports * VELUM_FEE_BPS / 10_000);
|
|
59
60
|
logger.debug("Encryption key generated from user keypair");
|
|
60
61
|
logger.debug(`User wallet: ${signer.toString()}`);
|
|
61
62
|
logger.debug(`Deposit amount: ${amount_in_lamports} lamports (${amount_in_lamports / LAMPORTS_PER_SOL} SOL)`);
|
|
62
|
-
logger.debug(`
|
|
63
|
-
// Check wallet balance
|
|
63
|
+
logger.debug(`Protocol fee: ${fee_amount_in_lamports} lamports, Velum fee: ${velum_fee_lamports} lamports`);
|
|
64
|
+
// Check wallet balance (deposit + protocol fee + velum fee)
|
|
64
65
|
const balance = await connection.getBalance(signer);
|
|
65
66
|
logger.debug(`Wallet balance: ${balance / 1e9} SOL`);
|
|
66
|
-
if (balance < amount_in_lamports + fee_amount_in_lamports) {
|
|
67
|
-
throw new InsufficientBalanceError(amount_in_lamports + fee_amount_in_lamports, balance);
|
|
67
|
+
if (balance < amount_in_lamports + fee_amount_in_lamports + velum_fee_lamports) {
|
|
68
|
+
throw new InsufficientBalanceError(amount_in_lamports + fee_amount_in_lamports + velum_fee_lamports, balance);
|
|
68
69
|
}
|
|
69
70
|
const { treeAccount, treeTokenAccount, globalConfigAccount } = getProgramAccounts();
|
|
70
71
|
// Create the merkle tree with the pre-initialized poseidon hash
|
|
@@ -364,12 +365,18 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
|
|
|
364
365
|
const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
|
|
365
366
|
units: 1_000_000,
|
|
366
367
|
});
|
|
368
|
+
// Velum fee transfer instruction
|
|
369
|
+
const velumFeeInstruction = SystemProgram.transfer({
|
|
370
|
+
fromPubkey: signer,
|
|
371
|
+
toPubkey: VELUM_FEE_WALLET,
|
|
372
|
+
lamports: velum_fee_lamports,
|
|
373
|
+
});
|
|
367
374
|
// Create versioned transaction with Address Lookup Table
|
|
368
375
|
const recentBlockhash = await connection.getLatestBlockhash();
|
|
369
376
|
const messageV0 = new TransactionMessage({
|
|
370
377
|
payerKey: signer, // User pays for their own deposit
|
|
371
378
|
recentBlockhash: recentBlockhash.blockhash,
|
|
372
|
-
instructions: [modifyComputeUnits, depositInstruction],
|
|
379
|
+
instructions: [modifyComputeUnits, velumFeeInstruction, depositInstruction],
|
|
373
380
|
}).compileToV0Message([lookupTableAccount.value]);
|
|
374
381
|
let versionedTransaction = new VersionedTransaction(messageV0);
|
|
375
382
|
// sign tx
|
package/dist/depositSPL.js
CHANGED
|
@@ -7,10 +7,10 @@ import { MerkleTree } from "./utils/merkle_tree.js";
|
|
|
7
7
|
import { serializeProofAndExtData, } from "./utils/encryption.js";
|
|
8
8
|
import { Keypair as UtxoKeypair } from "./models/keypair.js";
|
|
9
9
|
import { getUtxosSPL } from "./getUtxosSPL.js";
|
|
10
|
-
import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS, tokens, } from "./utils/constants.js";
|
|
10
|
+
import { FIELD_SIZE, FEE_RECIPIENT, MERKLE_TREE_DEPTH, RELAYER_API_URL, PROGRAM_ID, ALT_ADDRESS, tokens, VELUM_FEE_WALLET, VELUM_FEE_BPS, } from "./utils/constants.js";
|
|
11
11
|
import { useExistingALT, } from "./utils/address_lookup_table.js";
|
|
12
12
|
import { logger } from "./utils/logger.js";
|
|
13
|
-
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, getAccount, } from "@solana/spl-token";
|
|
13
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, getAccount, createTransferInstruction, } from "@solana/spl-token";
|
|
14
14
|
// Function to relay pre-signed deposit transaction to indexer backend
|
|
15
15
|
async function relayDepositToIndexer({ signedTransaction, publicKey, referrer, mintAddress, }) {
|
|
16
16
|
try {
|
|
@@ -84,6 +84,7 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
|
|
|
84
84
|
}
|
|
85
85
|
// const base_units = amount_in_sol * units_per_token
|
|
86
86
|
const fee_base_units = 0;
|
|
87
|
+
const velum_fee_base_units = Math.ceil(base_units * VELUM_FEE_BPS / 10_000);
|
|
87
88
|
logger.debug("Encryption key generated from user keypair");
|
|
88
89
|
logger.debug(`User wallet: ${signer.toString()}`);
|
|
89
90
|
logger.debug(`Deposit amount: ${base_units} base_units (${base_units / token.units_per_token} ${token.name.toUpperCase()})`);
|
|
@@ -94,8 +95,8 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
|
|
|
94
95
|
logger.debug(`wallet balance: ${balance / token.units_per_token} ${token.name.toUpperCase()}`);
|
|
95
96
|
logger.debug("balance", balance);
|
|
96
97
|
logger.debug("base_units + fee_base_units", base_units + fee_base_units);
|
|
97
|
-
if (balance < base_units + fee_base_units) {
|
|
98
|
-
throw new Error(`Insufficient balance. Need at least ${(base_units + fee_base_units) / token.units_per_token} ${token.name.toUpperCase()}.`);
|
|
98
|
+
if (balance < base_units + fee_base_units + velum_fee_base_units) {
|
|
99
|
+
throw new Error(`Insufficient balance. Need at least ${(base_units + fee_base_units + velum_fee_base_units) / token.units_per_token} ${token.name.toUpperCase()}.`);
|
|
99
100
|
}
|
|
100
101
|
// Check SOL balance for account rent + transaction fees
|
|
101
102
|
// The program creates a rent-exempt account (~953,520 lamports) during deposit
|
|
@@ -412,12 +413,15 @@ export async function depositSPL({ lightWasm, storage, keyBasePath, publicKey, c
|
|
|
412
413
|
const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
|
|
413
414
|
units: 1_000_000,
|
|
414
415
|
});
|
|
416
|
+
// Velum fee transfer instruction (SPL token)
|
|
417
|
+
const velumFeeATA = getAssociatedTokenAddressSync(token.pubkey, VELUM_FEE_WALLET);
|
|
418
|
+
const velumFeeInstruction = createTransferInstruction(signerTokenAccount, velumFeeATA, signer, velum_fee_base_units);
|
|
415
419
|
// Create versioned transaction with Address Lookup Table
|
|
416
420
|
const recentBlockhash = await connection.getLatestBlockhash();
|
|
417
421
|
const messageV0 = new TransactionMessage({
|
|
418
422
|
payerKey: signer, // User pays for their own deposit
|
|
419
423
|
recentBlockhash: recentBlockhash.blockhash,
|
|
420
|
-
instructions: [modifyComputeUnits, depositInstruction],
|
|
424
|
+
instructions: [modifyComputeUnits, velumFeeInstruction, depositInstruction],
|
|
421
425
|
}).compileToV0Message([lookupTableAccount.value]);
|
|
422
426
|
let versionedTransaction = new VersionedTransaction(messageV0);
|
|
423
427
|
// sign tx
|
|
@@ -3,6 +3,8 @@ import BN from 'bn.js';
|
|
|
3
3
|
export declare const FIELD_SIZE: BN;
|
|
4
4
|
export declare const PROGRAM_ID: PublicKey;
|
|
5
5
|
export declare const FEE_RECIPIENT: PublicKey;
|
|
6
|
+
export declare const VELUM_FEE_WALLET: PublicKey;
|
|
7
|
+
export declare const VELUM_FEE_BPS = 15;
|
|
6
8
|
export declare const FETCH_UTXOS_GROUP_SIZE = 20000;
|
|
7
9
|
export declare const TRANSACT_IX_DISCRIMINATOR: Buffer<ArrayBuffer>;
|
|
8
10
|
export declare const TRANSACT_SPL_IX_DISCRIMINATOR: Buffer<ArrayBuffer>;
|
package/dist/utils/constants.js
CHANGED
|
@@ -3,6 +3,8 @@ import BN from 'bn.js';
|
|
|
3
3
|
export const FIELD_SIZE = new BN('21888242871839275222246405745257275088548364400416034343698204186575808495617');
|
|
4
4
|
export const PROGRAM_ID = process.env.NEXT_PUBLIC_PROGRAM_ID ? new PublicKey(process.env.NEXT_PUBLIC_PROGRAM_ID) : new PublicKey('9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD');
|
|
5
5
|
export const FEE_RECIPIENT = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM');
|
|
6
|
+
export const VELUM_FEE_WALLET = new PublicKey('8CzjWm8yJBMZhBNwQtRMAgvf7xf8TFQMwQGuv4pd4A4s');
|
|
7
|
+
export const VELUM_FEE_BPS = 15; // 0.15% = 15 basis points
|
|
6
8
|
export const FETCH_UTXOS_GROUP_SIZE = 20_000;
|
|
7
9
|
export const TRANSACT_IX_DISCRIMINATOR = Buffer.from([217, 149, 130, 143, 221, 52, 252, 119]);
|
|
8
10
|
export const TRANSACT_SPL_IX_DISCRIMINATOR = Buffer.from([154, 66, 244, 204, 78, 225, 163, 151]);
|