@veil-cash/sdk 0.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.
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Wallet utilities for CLI transaction signing
3
+ */
4
+
5
+ import {
6
+ createWalletClient,
7
+ createPublicClient,
8
+ http,
9
+ decodeErrorResult,
10
+ BaseError,
11
+ ContractFunctionRevertedError,
12
+ } from 'viem';
13
+ import { privateKeyToAccount } from 'viem/accounts';
14
+ import { base } from 'viem/chains';
15
+ import type { TransactionData } from '../types.js';
16
+ import { ENTRY_ABI } from '../abi.js';
17
+ import { getAddresses } from '../addresses.js';
18
+
19
+ export interface WalletConfig {
20
+ privateKey: `0x${string}`;
21
+ rpcUrl?: string;
22
+ }
23
+
24
+ export interface SendTransactionResult {
25
+ hash: `0x${string}`;
26
+ receipt: {
27
+ status: 'success' | 'reverted';
28
+ blockNumber: bigint;
29
+ gasUsed: bigint;
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Create a wallet client for signing and sending transactions
35
+ * @returns Wallet and public clients with account and chain
36
+ *
37
+ * Note: Uses `any` return type to avoid viem's complex generic type mismatches
38
+ * that occur with Base chain's deposit transaction types. Runtime behavior is correct.
39
+ */
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ export function createWallet(config: WalletConfig): any {
42
+ const { privateKey, rpcUrl } = config;
43
+
44
+ const account = privateKeyToAccount(privateKey);
45
+ const chain = base;
46
+ const transport = http(rpcUrl);
47
+
48
+ const walletClient = createWalletClient({
49
+ account,
50
+ chain,
51
+ transport,
52
+ });
53
+
54
+ const publicClient = createPublicClient({
55
+ chain,
56
+ transport,
57
+ });
58
+
59
+ return { walletClient, publicClient, account, chain };
60
+ }
61
+
62
+ /**
63
+ * Known custom error selectors (first 4 bytes of keccak256 hash of error signature)
64
+ */
65
+ const ERROR_SELECTORS: Record<string, string> = {
66
+ '0x3ee5aeb5': 'DepositsDisabled',
67
+ '0x8e8f6d6f': 'MinimumDepositNotMet',
68
+ '0x5cd83192': 'NotAllowedToDeposit',
69
+ '0x6f5e8818': 'UserNotRegistered',
70
+ '0x8a2ef116': 'UserAlreadyRegistered',
71
+ '0x0dc149f0': 'InvalidDepositKey',
72
+ '0x584a7938': 'InvalidDepositKeyForUser',
73
+ '0xd92e233d': 'OnlyOwnerCanRegister',
74
+ };
75
+
76
+ /**
77
+ * Try to decode a custom error from the ABI or known selectors
78
+ */
79
+ function decodeCustomError(error: unknown): string | null {
80
+ // Convert error to string and look for hex data
81
+ const errorStr = String(error);
82
+
83
+ // Try to find revert data in the error (look for 0x followed by hex)
84
+ const dataMatch = errorStr.match(/data:\s*(0x[a-fA-F0-9]+)/);
85
+ if (dataMatch) {
86
+ const data = dataMatch[1];
87
+ const selector = data.slice(0, 10).toLowerCase();
88
+ if (ERROR_SELECTORS[selector]) {
89
+ return ERROR_SELECTORS[selector];
90
+ }
91
+ }
92
+
93
+ if (error instanceof BaseError) {
94
+ // Check if it's a ContractFunctionRevertedError
95
+ const revertError = error.walk(err => err instanceof ContractFunctionRevertedError);
96
+ if (revertError instanceof ContractFunctionRevertedError) {
97
+ const errorName = revertError.data?.errorName;
98
+ if (errorName) {
99
+ return errorName;
100
+ }
101
+ }
102
+
103
+ // Try to extract error data from various places
104
+ const anyError = error as any;
105
+ const possibleData = anyError.data || anyError.cause?.data || anyError.cause?.cause?.data;
106
+
107
+ if (possibleData && typeof possibleData === 'string' && possibleData.startsWith('0x')) {
108
+ try {
109
+ const decoded = decodeErrorResult({
110
+ abi: ENTRY_ABI,
111
+ data: possibleData as `0x${string}`,
112
+ });
113
+ return decoded.errorName;
114
+ } catch {
115
+ // Try selector lookup
116
+ const selector = possibleData.slice(0, 10).toLowerCase();
117
+ if (ERROR_SELECTORS[selector]) {
118
+ return ERROR_SELECTORS[selector];
119
+ }
120
+ }
121
+ }
122
+ }
123
+ return null;
124
+ }
125
+
126
+ /**
127
+ * Send a transaction and wait for confirmation
128
+ */
129
+ export async function sendTransaction(
130
+ config: WalletConfig,
131
+ tx: TransactionData
132
+ ): Promise<SendTransactionResult> {
133
+ const { walletClient, publicClient, account, chain } = createWallet(config);
134
+
135
+ try {
136
+ // Simulate first to get better error messages
137
+ await publicClient.call({
138
+ account,
139
+ to: tx.to,
140
+ data: tx.data,
141
+ value: tx.value,
142
+ });
143
+ } catch (error) {
144
+ // Try to decode custom error
145
+ const customError = decodeCustomError(error);
146
+ if (customError) {
147
+ throw new Error(customError);
148
+ }
149
+ // Re-throw with original message if we can't decode
150
+ throw error;
151
+ }
152
+
153
+ // Send the transaction
154
+ const hash = await walletClient.sendTransaction({
155
+ account,
156
+ chain,
157
+ to: tx.to,
158
+ data: tx.data,
159
+ value: tx.value,
160
+ });
161
+
162
+ // Wait for confirmation
163
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
164
+
165
+ return {
166
+ hash,
167
+ receipt: {
168
+ status: receipt.status,
169
+ blockNumber: receipt.blockNumber,
170
+ gasUsed: receipt.gasUsed,
171
+ },
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Get the address from a private key
177
+ */
178
+ export function getAddress(privateKey: `0x${string}`): `0x${string}` {
179
+ const account = privateKeyToAccount(privateKey);
180
+ return account.address;
181
+ }
182
+
183
+ /**
184
+ * Get ETH balance for an address
185
+ */
186
+ export async function getBalance(
187
+ address: `0x${string}`,
188
+ rpcUrl?: string
189
+ ): Promise<bigint> {
190
+ const transport = http(rpcUrl);
191
+ const publicClient = createPublicClient({
192
+ chain: base,
193
+ transport,
194
+ });
195
+
196
+ return publicClient.getBalance({ address });
197
+ }
198
+
199
+ /**
200
+ * Check if an address is already registered (has a deposit key on-chain)
201
+ */
202
+ export async function isRegistered(
203
+ address: `0x${string}`,
204
+ rpcUrl?: string
205
+ ): Promise<{ registered: boolean; depositKey: string | null }> {
206
+ const transport = http(rpcUrl);
207
+ const publicClient = createPublicClient({
208
+ chain: base,
209
+ transport,
210
+ });
211
+
212
+ const addresses = getAddresses();
213
+
214
+ const depositKey = await publicClient.readContract({
215
+ address: addresses.entry,
216
+ abi: ENTRY_ABI,
217
+ functionName: 'depositKeys',
218
+ args: [address],
219
+ }) as `0x${string}`;
220
+
221
+ // If depositKey has length > 2 (more than just "0x"), user is registered
222
+ const registered = depositKey && depositKey.length > 2;
223
+
224
+ return {
225
+ registered,
226
+ depositKey: registered ? depositKey : null,
227
+ };
228
+ }
package/src/deposit.ts ADDED
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Deposit functions for Veil SDK
3
+ * Build transactions for registration and deposits
4
+ */
5
+
6
+ import { encodeFunctionData, parseEther, parseUnits } from 'viem';
7
+ import { getAddresses, POOL_CONFIG } from './addresses.js';
8
+ import { ENTRY_ABI, ERC20_ABI } from './abi.js';
9
+ import type {
10
+ Token,
11
+ TransactionData,
12
+ } from './types.js';
13
+
14
+ /**
15
+ * Build a transaction to register a deposit key
16
+ * This is a one-time operation that links your address to your keypair
17
+ *
18
+ * @param depositKey - Deposit key from Keypair.depositKey()
19
+ * @param ownerAddress - Address that will own this deposit key
20
+ * @returns Transaction data to send
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const keypair = new Keypair();
25
+ * const tx = buildRegisterTx(keypair.depositKey(), '0x...');
26
+ * // Send tx using your wallet (viem, ethers, etc.)
27
+ * ```
28
+ */
29
+ export function buildRegisterTx(
30
+ depositKey: string,
31
+ ownerAddress: `0x${string}`
32
+ ): TransactionData {
33
+ const addresses = getAddresses();
34
+
35
+ const data = encodeFunctionData({
36
+ abi: ENTRY_ABI,
37
+ functionName: 'register',
38
+ args: [{
39
+ owner: ownerAddress,
40
+ depositKey: depositKey as `0x${string}`,
41
+ }],
42
+ });
43
+
44
+ return {
45
+ to: addresses.entry,
46
+ data,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Build a transaction to deposit ETH
52
+ *
53
+ * @param options - Deposit options
54
+ * @param options.depositKey - Deposit key from Keypair.depositKey()
55
+ * @param options.amount - Amount to deposit (human readable, e.g., '0.1')
56
+ * @returns Transaction data including value to send
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const tx = buildDepositETHTx({
61
+ * depositKey: keypair.depositKey(),
62
+ * amount: '0.1',
63
+ * });
64
+ * // Send tx with tx.value as the ETH amount
65
+ * ```
66
+ */
67
+ export function buildDepositETHTx(options: {
68
+ depositKey: string;
69
+ amount: string;
70
+ }): TransactionData {
71
+ const { depositKey, amount } = options;
72
+ const addresses = getAddresses();
73
+
74
+ const value = parseEther(amount);
75
+
76
+ const data = encodeFunctionData({
77
+ abi: ENTRY_ABI,
78
+ functionName: 'queueETH',
79
+ args: [depositKey as `0x${string}`],
80
+ });
81
+
82
+ return {
83
+ to: addresses.entry,
84
+ data,
85
+ value,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Build a transaction to approve USDC for deposit
91
+ * Must be called before depositUSDC if allowance is insufficient
92
+ *
93
+ * @param options - Approval options
94
+ * @param options.amount - Amount to approve (human readable, e.g., '100')
95
+ * @returns Transaction data
96
+ */
97
+ export function buildApproveUSDCTx(options: {
98
+ amount: string;
99
+ }): TransactionData {
100
+ const { amount } = options;
101
+ const addresses = getAddresses();
102
+
103
+ const amountWei = parseUnits(amount, POOL_CONFIG.usdc.decimals);
104
+
105
+ const data = encodeFunctionData({
106
+ abi: ERC20_ABI,
107
+ functionName: 'approve',
108
+ args: [addresses.entry, amountWei],
109
+ });
110
+
111
+ return {
112
+ to: addresses.usdcToken,
113
+ data,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Build a transaction to deposit USDC
119
+ * Note: You must approve USDC first using buildApproveUSDCTx
120
+ *
121
+ * @param options - Deposit options
122
+ * @param options.depositKey - Deposit key from Keypair.depositKey()
123
+ * @param options.amount - Amount to deposit (human readable, e.g., '100')
124
+ * @returns Transaction data
125
+ */
126
+ export function buildDepositUSDCTx(options: {
127
+ depositKey: string;
128
+ amount: string;
129
+ }): TransactionData {
130
+ const { depositKey, amount } = options;
131
+ const addresses = getAddresses();
132
+
133
+ const amountWei = parseUnits(amount, POOL_CONFIG.usdc.decimals);
134
+
135
+ const data = encodeFunctionData({
136
+ abi: ENTRY_ABI,
137
+ functionName: 'queueUSDC',
138
+ args: [amountWei, depositKey as `0x${string}`],
139
+ });
140
+
141
+ return {
142
+ to: addresses.entry,
143
+ data,
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Build a deposit transaction (ETH or USDC)
149
+ * Convenience function that routes to the correct builder
150
+ *
151
+ * @param options - Deposit options
152
+ * @returns Transaction data
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * // ETH deposit
157
+ * const ethTx = buildDepositTx({
158
+ * depositKey: keypair.depositKey(),
159
+ * amount: '0.1',
160
+ * token: 'ETH',
161
+ * });
162
+ *
163
+ * // USDC deposit (remember to approve first!)
164
+ * const usdcTx = buildDepositTx({
165
+ * depositKey: keypair.depositKey(),
166
+ * amount: '100',
167
+ * token: 'USDC',
168
+ * });
169
+ * ```
170
+ */
171
+ export function buildDepositTx(options: {
172
+ depositKey: string;
173
+ amount: string;
174
+ token?: Token;
175
+ }): TransactionData {
176
+ const { token = 'ETH', ...rest } = options;
177
+
178
+ if (token === 'USDC') {
179
+ return buildDepositUSDCTx(rest);
180
+ }
181
+
182
+ return buildDepositETHTx(rest);
183
+ }
package/src/index.ts ADDED
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Veil Cash SDK
3
+ *
4
+ * SDK for interacting with Veil Cash privacy pools.
5
+ * Generate keypairs, register, deposit, withdraw, and transfer privately.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { Keypair, withdraw, transfer, buildWithdrawProof } from '@veil-cash/sdk';
10
+ *
11
+ * // Generate keypair
12
+ * const keypair = new Keypair();
13
+ * console.log(keypair.depositKey()); // Register this
14
+ * console.log(keypair.privkey); // Save securely!
15
+ *
16
+ * // Withdraw from pool
17
+ * const result = await withdraw({
18
+ * amount: '0.1',
19
+ * recipient: '0x1234...',
20
+ * keypair,
21
+ * });
22
+ *
23
+ * // Transfer privately
24
+ * const transfer = await transfer({
25
+ * amount: '0.1',
26
+ * recipientAddress: '0x5678...',
27
+ * senderKeypair: keypair,
28
+ * });
29
+ * ```
30
+ *
31
+ * @packageDocumentation
32
+ */
33
+
34
+ // Keypair
35
+ export { Keypair, packEncryptedMessage, unpackEncryptedMessage } from './keypair.js';
36
+
37
+ // UTXO
38
+ export { Utxo } from './utxo.js';
39
+ export type { UtxoParams } from './utxo.js';
40
+
41
+ // Deposit functions
42
+ export {
43
+ buildRegisterTx,
44
+ buildDepositETHTx,
45
+ buildDepositUSDCTx,
46
+ buildApproveUSDCTx,
47
+ buildDepositTx,
48
+ } from './deposit.js';
49
+
50
+ // Balance functions
51
+ export {
52
+ getQueueBalance,
53
+ getPrivateBalance,
54
+ } from './balance.js';
55
+ export type { ProgressCallback } from './balance.js';
56
+
57
+ // Withdraw functions
58
+ export {
59
+ withdraw,
60
+ buildWithdrawProof,
61
+ selectUtxosForWithdraw,
62
+ } from './withdraw.js';
63
+
64
+ // Transfer functions
65
+ export {
66
+ transfer,
67
+ buildTransferProof,
68
+ checkRecipientRegistration,
69
+ mergeUtxos,
70
+ } from './transfer.js';
71
+
72
+ // Transaction preparation (core ZK proof building)
73
+ export {
74
+ prepareTransaction,
75
+ } from './transaction.js';
76
+ export type {
77
+ ExtData,
78
+ ProofArgs,
79
+ TransactionResult,
80
+ PrepareTransactionParams,
81
+ } from './transaction.js';
82
+
83
+ // Merkle tree utilities
84
+ export {
85
+ buildMerkleTree,
86
+ getMerklePath,
87
+ MERKLE_TREE_HEIGHT,
88
+ } from './merkle.js';
89
+
90
+ // ZK Prover
91
+ export {
92
+ prove,
93
+ selectCircuit,
94
+ CIRCUIT_CONFIG,
95
+ } from './prover.js';
96
+ export type { ProofInput } from './prover.js';
97
+
98
+ // Addresses and config
99
+ export {
100
+ ADDRESSES,
101
+ POOL_CONFIG,
102
+ getAddresses,
103
+ getRelayUrl,
104
+ } from './addresses.js';
105
+
106
+ // Relay functions
107
+ export {
108
+ submitRelay,
109
+ checkRelayHealth,
110
+ getRelayInfo,
111
+ RelayError,
112
+ } from './relay.js';
113
+
114
+ // ABIs
115
+ export { ENTRY_ABI, ERC20_ABI, QUEUE_ABI, POOL_ABI } from './abi.js';
116
+
117
+ // Utilities
118
+ export {
119
+ poseidonHash,
120
+ poseidonHash2,
121
+ toFixedHex,
122
+ toBuffer,
123
+ randomBN,
124
+ getExtDataHash,
125
+ shuffle,
126
+ FIELD_SIZE,
127
+ } from './utils.js';
128
+ export type { ExtDataInput } from './utils.js';
129
+
130
+ // Types
131
+ export type {
132
+ Token,
133
+ EncryptedMessage,
134
+ NetworkAddresses,
135
+ RegisterTxOptions,
136
+ DepositTxOptions,
137
+ TransactionData,
138
+ PoolConfig,
139
+ PendingDeposit,
140
+ QueueBalanceResult,
141
+ UtxoInfo,
142
+ PrivateBalanceResult,
143
+ // Relay types
144
+ RelayPool,
145
+ RelayType,
146
+ RelayProofArgs,
147
+ RelayExtData,
148
+ RelayMetadata,
149
+ RelayRequest,
150
+ RelayResponse,
151
+ RelayErrorResponse,
152
+ SubmitRelayOptions,
153
+ // Withdraw/Transfer types
154
+ BuildWithdrawProofOptions,
155
+ BuildTransferProofOptions,
156
+ ProofBuildResult,
157
+ WithdrawResult,
158
+ TransferResult,
159
+ UtxoSelectionResult,
160
+ } from './types.js';