@cedros/login-sidecar 0.0.1

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,84 @@
1
+ /**
2
+ * Privacy Cash SDK wrapper service
3
+ *
4
+ * This service wraps the Privacy Cash SDK for SSS embedded wallets only.
5
+ *
6
+ * Architecture:
7
+ * - User deposits to THEIR OWN Privacy Cash account (user's pubkey)
8
+ * - Server holds Share A + temporarily stores Share B during privacy period
9
+ * - Server can reconstruct keypair to withdraw to company wallet
10
+ * - On-chain: User deposit and Company withdrawal are unlinkable
11
+ *
12
+ * Requirements:
13
+ * - SSS wallets only (no external wallets like Phantom)
14
+ * - No-recovery option required (user has only Share B, no Share C)
15
+ * - Server stores Share B during "privacy period" until withdrawal completes
16
+ */
17
+ import { Keypair } from '@solana/web3.js';
18
+ import { Config } from '../config.js';
19
+ import { SolanaService } from './solana.js';
20
+ export interface ExecuteDepositResult {
21
+ success: boolean;
22
+ txSignature: string;
23
+ }
24
+ export interface WithdrawResult {
25
+ success: boolean;
26
+ txSignature: string;
27
+ feeLamports: number;
28
+ /** Actual amount withdrawn (after fees) - may be less than requested if partial */
29
+ amountLamports: number;
30
+ /** True if the full requested amount couldn't be withdrawn (insufficient balance) */
31
+ isPartial: boolean;
32
+ }
33
+ export declare class PrivacyCashService {
34
+ private solana;
35
+ private connection;
36
+ private companyWalletAddress;
37
+ private storage;
38
+ private lightWasm;
39
+ private keyBasePath;
40
+ private sdkInitialized;
41
+ constructor(config: Config, solanaService: SolanaService);
42
+ /**
43
+ * Initialize SDK components (lazy loading)
44
+ */
45
+ private ensureInitialized;
46
+ /**
47
+ * Create an encryption service for a specific user keypair
48
+ */
49
+ private createEncryptionService;
50
+ /**
51
+ * Execute a deposit for an SSS embedded wallet
52
+ *
53
+ * Deposits to the USER's Privacy Cash account (user's pubkey is the owner).
54
+ * The user signs the transaction.
55
+ *
56
+ * @param userKeypair - The user's reconstructed keypair (from Share A + Share B)
57
+ * @param amountLamports - Amount to deposit in lamports
58
+ */
59
+ executeDeposit(userKeypair: Keypair, amountLamports: number): Promise<ExecuteDepositResult>;
60
+ /**
61
+ * Withdraw funds from a user's Privacy Cash account to the company wallet
62
+ *
63
+ * This is called server-side after the "privacy period" has elapsed.
64
+ * Requires the user's keypair (reconstructed from stored shares).
65
+ *
66
+ * @param userKeypair - The user's reconstructed keypair (from Share A + Share B)
67
+ * @param amountLamports - Amount to withdraw in lamports
68
+ */
69
+ withdrawFromUser(userKeypair: Keypair, amountLamports: number): Promise<WithdrawResult>;
70
+ /**
71
+ * Get a user's private balance in Privacy Cash
72
+ *
73
+ * @param userKeypair - The user's reconstructed keypair
74
+ */
75
+ getUserPrivateBalance(userKeypair: Keypair): Promise<number>;
76
+ /**
77
+ * Check if the SDK is properly loaded
78
+ */
79
+ isLoaded(): Promise<boolean>;
80
+ /**
81
+ * Parse a base58-encoded private key into a Keypair
82
+ */
83
+ static parseKeypair(privateKeyBase58: string): Keypair;
84
+ }
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ /**
3
+ * Privacy Cash SDK wrapper service
4
+ *
5
+ * This service wraps the Privacy Cash SDK for SSS embedded wallets only.
6
+ *
7
+ * Architecture:
8
+ * - User deposits to THEIR OWN Privacy Cash account (user's pubkey)
9
+ * - Server holds Share A + temporarily stores Share B during privacy period
10
+ * - Server can reconstruct keypair to withdraw to company wallet
11
+ * - On-chain: User deposit and Company withdrawal are unlinkable
12
+ *
13
+ * Requirements:
14
+ * - SSS wallets only (no external wallets like Phantom)
15
+ * - No-recovery option required (user has only Share B, no Share C)
16
+ * - Server stores Share B during "privacy period" until withdrawal completes
17
+ */
18
+ var __importDefault = (this && this.__importDefault) || function (mod) {
19
+ return (mod && mod.__esModule) ? mod : { "default": mod };
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.PrivacyCashService = void 0;
23
+ const web3_js_1 = require("@solana/web3.js");
24
+ const hasher_rs_1 = require("@lightprotocol/hasher.rs");
25
+ const node_localstorage_1 = require("node-localstorage");
26
+ const bs58_1 = __importDefault(require("bs58"));
27
+ const node_path_1 = __importDefault(require("node:path"));
28
+ // SDK module cache
29
+ let sdkModule = null;
30
+ async function loadSdk() {
31
+ if (!sdkModule) {
32
+ // Dynamic import of SDK (using any to bypass type checking for untyped package)
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ const exportUtils = await import('privacycash');
35
+ sdkModule = {
36
+ deposit: exportUtils.deposit,
37
+ withdraw: exportUtils.withdraw,
38
+ EncryptionService: exportUtils.EncryptionService,
39
+ getUtxos: exportUtils.getUtxos,
40
+ getBalanceFromUtxos: exportUtils.getBalanceFromUtxos,
41
+ };
42
+ }
43
+ return sdkModule;
44
+ }
45
+ class PrivacyCashService {
46
+ solana;
47
+ connection;
48
+ companyWalletAddress;
49
+ storage;
50
+ lightWasm = null;
51
+ keyBasePath;
52
+ sdkInitialized = false;
53
+ constructor(config, solanaService) {
54
+ this.solana = solanaService;
55
+ this.connection = solanaService.getConnection();
56
+ // Company wallet is the recipient for withdrawals (no private key needed here)
57
+ this.companyWalletAddress = new web3_js_1.PublicKey(config.companyWalletAddress);
58
+ // Initialize storage for UTXO caching
59
+ this.storage = new node_localstorage_1.LocalStorage(node_path_1.default.join(process.cwd(), 'privacy-cache'));
60
+ // Path to circuit files (bundled with SDK)
61
+ this.keyBasePath = '';
62
+ console.log(`[PrivacyCash] Initialized with company wallet: ${this.companyWalletAddress.toBase58()}`);
63
+ }
64
+ /**
65
+ * Initialize SDK components (lazy loading)
66
+ */
67
+ async ensureInitialized() {
68
+ const sdk = await loadSdk();
69
+ if (!this.lightWasm) {
70
+ this.lightWasm = await hasher_rs_1.WasmFactory.getInstance();
71
+ }
72
+ // Set keyBasePath if not already set
73
+ if (!this.keyBasePath) {
74
+ this.keyBasePath = node_path_1.default.join(process.cwd(), 'node_modules', 'privacycash', 'circuit2', 'transaction2');
75
+ }
76
+ this.sdkInitialized = true;
77
+ return sdk;
78
+ }
79
+ /**
80
+ * Create an encryption service for a specific user keypair
81
+ */
82
+ async createEncryptionService(sdk, keypair) {
83
+ const encryptionService = new sdk.EncryptionService();
84
+ encryptionService.deriveEncryptionKeyFromWallet(keypair);
85
+ return encryptionService;
86
+ }
87
+ /**
88
+ * Execute a deposit for an SSS embedded wallet
89
+ *
90
+ * Deposits to the USER's Privacy Cash account (user's pubkey is the owner).
91
+ * The user signs the transaction.
92
+ *
93
+ * @param userKeypair - The user's reconstructed keypair (from Share A + Share B)
94
+ * @param amountLamports - Amount to deposit in lamports
95
+ */
96
+ async executeDeposit(userKeypair, amountLamports) {
97
+ const sdk = await this.ensureInitialized();
98
+ const encryptionService = await this.createEncryptionService(sdk, userKeypair);
99
+ // Deposit to USER's Privacy Cash account (user owns the private balance)
100
+ const result = await sdk.deposit({
101
+ lightWasm: this.lightWasm,
102
+ amount_in_lamports: amountLamports,
103
+ connection: this.connection,
104
+ encryptionService,
105
+ publicKey: userKeypair.publicKey, // User owns the Privacy Cash account
106
+ transactionSigner: async (tx) => {
107
+ tx.sign([userKeypair]);
108
+ return tx;
109
+ },
110
+ keyBasePath: this.keyBasePath,
111
+ storage: this.storage,
112
+ });
113
+ return {
114
+ success: true,
115
+ txSignature: result.tx,
116
+ };
117
+ }
118
+ /**
119
+ * Withdraw funds from a user's Privacy Cash account to the company wallet
120
+ *
121
+ * This is called server-side after the "privacy period" has elapsed.
122
+ * Requires the user's keypair (reconstructed from stored shares).
123
+ *
124
+ * @param userKeypair - The user's reconstructed keypair (from Share A + Share B)
125
+ * @param amountLamports - Amount to withdraw in lamports
126
+ */
127
+ async withdrawFromUser(userKeypair, amountLamports) {
128
+ const sdk = await this.ensureInitialized();
129
+ const encryptionService = await this.createEncryptionService(sdk, userKeypair);
130
+ const result = await sdk.withdraw({
131
+ lightWasm: this.lightWasm,
132
+ amount_in_lamports: amountLamports,
133
+ connection: this.connection,
134
+ encryptionService,
135
+ publicKey: userKeypair.publicKey, // User's Privacy Cash account
136
+ recipient: this.companyWalletAddress, // Withdraw to company wallet
137
+ keyBasePath: this.keyBasePath,
138
+ storage: this.storage,
139
+ });
140
+ return {
141
+ success: true,
142
+ txSignature: result.tx,
143
+ feeLamports: result.fee_in_lamports,
144
+ amountLamports: result.amount_in_lamports,
145
+ isPartial: result.isPartial,
146
+ };
147
+ }
148
+ /**
149
+ * Get a user's private balance in Privacy Cash
150
+ *
151
+ * @param userKeypair - The user's reconstructed keypair
152
+ */
153
+ async getUserPrivateBalance(userKeypair) {
154
+ const sdk = await this.ensureInitialized();
155
+ const encryptionService = await this.createEncryptionService(sdk, userKeypair);
156
+ const utxos = await sdk.getUtxos({
157
+ connection: this.connection,
158
+ encryptionService,
159
+ publicKey: userKeypair.publicKey,
160
+ storage: this.storage,
161
+ });
162
+ const { lamports } = sdk.getBalanceFromUtxos(utxos);
163
+ return lamports;
164
+ }
165
+ /**
166
+ * Check if the SDK is properly loaded
167
+ */
168
+ async isLoaded() {
169
+ try {
170
+ await this.ensureInitialized();
171
+ return true;
172
+ }
173
+ catch {
174
+ return false;
175
+ }
176
+ }
177
+ /**
178
+ * Parse a base58-encoded private key into a Keypair
179
+ */
180
+ static parseKeypair(privateKeyBase58) {
181
+ const privateKeyBytes = bs58_1.default.decode(privateKeyBase58);
182
+ return web3_js_1.Keypair.fromSecretKey(privateKeyBytes);
183
+ }
184
+ }
185
+ exports.PrivacyCashService = PrivacyCashService;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Solana RPC connection service
3
+ */
4
+ import { Connection, PublicKey } from '@solana/web3.js';
5
+ import { Config } from '../config.js';
6
+ export declare class SolanaService {
7
+ private connection;
8
+ private networkName;
9
+ constructor(config: Config);
10
+ /**
11
+ * Get the Solana connection
12
+ */
13
+ getConnection(): Connection;
14
+ /**
15
+ * Get the network name
16
+ */
17
+ getNetwork(): string;
18
+ /**
19
+ * Check if RPC is connected by fetching the latest blockhash
20
+ */
21
+ isConnected(): Promise<boolean>;
22
+ /**
23
+ * Get the latest blockhash with expiry info
24
+ */
25
+ getLatestBlockhash(): Promise<{
26
+ blockhash: string;
27
+ lastValidBlockHeight: number;
28
+ }>;
29
+ /**
30
+ * Get balance for a public key
31
+ */
32
+ getBalance(publicKey: PublicKey): Promise<number>;
33
+ /**
34
+ * Send a signed transaction and confirm it
35
+ */
36
+ sendAndConfirmTransaction(signedTx: Buffer, options?: {
37
+ skipPreflight?: boolean;
38
+ }): Promise<string>;
39
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ /**
3
+ * Solana RPC connection service
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SolanaService = void 0;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ class SolanaService {
9
+ connection;
10
+ networkName;
11
+ constructor(config) {
12
+ this.connection = new web3_js_1.Connection(config.solanaRpcUrl, {
13
+ commitment: 'confirmed',
14
+ });
15
+ this.networkName = config.solanaNetwork;
16
+ }
17
+ /**
18
+ * Get the Solana connection
19
+ */
20
+ getConnection() {
21
+ return this.connection;
22
+ }
23
+ /**
24
+ * Get the network name
25
+ */
26
+ getNetwork() {
27
+ return this.networkName;
28
+ }
29
+ /**
30
+ * Check if RPC is connected by fetching the latest blockhash
31
+ */
32
+ async isConnected() {
33
+ try {
34
+ await this.connection.getLatestBlockhash();
35
+ return true;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ /**
42
+ * Get the latest blockhash with expiry info
43
+ */
44
+ async getLatestBlockhash() {
45
+ return this.connection.getLatestBlockhash('confirmed');
46
+ }
47
+ /**
48
+ * Get balance for a public key
49
+ */
50
+ async getBalance(publicKey) {
51
+ return this.connection.getBalance(publicKey);
52
+ }
53
+ /**
54
+ * Send a signed transaction and confirm it
55
+ */
56
+ async sendAndConfirmTransaction(signedTx, options) {
57
+ const signature = await this.connection.sendRawTransaction(signedTx, {
58
+ skipPreflight: options?.skipPreflight ?? false,
59
+ preflightCommitment: 'confirmed',
60
+ });
61
+ // Wait for confirmation
62
+ const latestBlockhash = await this.connection.getLatestBlockhash('confirmed');
63
+ await this.connection.confirmTransaction({
64
+ signature,
65
+ blockhash: latestBlockhash.blockhash,
66
+ lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
67
+ }, 'confirmed');
68
+ return signature;
69
+ }
70
+ }
71
+ exports.SolanaService = SolanaService;
@@ -0,0 +1,2 @@
1
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
2
+ export declare function fetchWithTimeout(fetchFn: FetchLike, input: string, init: RequestInit | undefined, timeoutMs: number): Promise<Response>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchWithTimeout = fetchWithTimeout;
4
+ async function fetchWithTimeout(fetchFn, input, init, timeoutMs) {
5
+ const controller = new AbortController();
6
+ const signal = controller.signal;
7
+ const timeout = setTimeout(() => {
8
+ controller.abort();
9
+ }, timeoutMs);
10
+ try {
11
+ const mergedInit = { ...init, signal };
12
+ return await fetchFn(input, mergedInit);
13
+ }
14
+ finally {
15
+ clearTimeout(timeout);
16
+ }
17
+ }
@@ -0,0 +1 @@
1
+ export declare function redactRpcUrl(url: string): string;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redactRpcUrl = redactRpcUrl;
4
+ function redactRpcUrl(url) {
5
+ try {
6
+ const parsed = new URL(url);
7
+ return parsed.host;
8
+ }
9
+ catch {
10
+ return '<invalid-url>';
11
+ }
12
+ }
@@ -0,0 +1,22 @@
1
+ import type { ParsedTransactionWithMeta } from '@solana/web3.js';
2
+ export type VerifySolTransferParams = {
3
+ signature: string;
4
+ expectedSource?: string;
5
+ expectedDestination: string;
6
+ minLamports?: number;
7
+ };
8
+ export type VerifySolTransferResult = {
9
+ signature: string;
10
+ observedLamports: number;
11
+ source: string;
12
+ destination: string;
13
+ };
14
+ /**
15
+ * Verify a finalized SOL transfer using a parsed Solana transaction.
16
+ *
17
+ * This is used to prove a claimed tx signature:
18
+ * - exists and succeeded
19
+ * - includes a SystemProgram transfer
20
+ * - moves lamports from expected source (optional) to expected destination
21
+ */
22
+ export declare function verifySolTransferFromParsedTransaction(tx: ParsedTransactionWithMeta | null, params: VerifySolTransferParams): VerifySolTransferResult;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifySolTransferFromParsedTransaction = verifySolTransferFromParsedTransaction;
4
+ function isRecord(value) {
5
+ return !!value && typeof value === 'object';
6
+ }
7
+ function isSystemTransferInstruction(ix) {
8
+ if (!isRecord(ix))
9
+ return false;
10
+ if (ix.program !== 'system')
11
+ return false;
12
+ const parsed = ix.parsed;
13
+ if (!isRecord(parsed))
14
+ return false;
15
+ if (parsed.type !== 'transfer')
16
+ return false;
17
+ const info = parsed.info;
18
+ if (!isRecord(info))
19
+ return false;
20
+ if (typeof info.source !== 'string')
21
+ return false;
22
+ if (typeof info.destination !== 'string')
23
+ return false;
24
+ const lamports = info.lamports;
25
+ if (!(typeof lamports === 'number' || typeof lamports === 'string'))
26
+ return false;
27
+ return true;
28
+ }
29
+ function toLamports(value) {
30
+ const n = typeof value === 'string' ? Number(value) : value;
31
+ if (!Number.isFinite(n) || n <= 0) {
32
+ throw new Error('Invalid lamports value');
33
+ }
34
+ return Math.floor(n);
35
+ }
36
+ /**
37
+ * Verify a finalized SOL transfer using a parsed Solana transaction.
38
+ *
39
+ * This is used to prove a claimed tx signature:
40
+ * - exists and succeeded
41
+ * - includes a SystemProgram transfer
42
+ * - moves lamports from expected source (optional) to expected destination
43
+ */
44
+ function verifySolTransferFromParsedTransaction(tx, params) {
45
+ if (!tx) {
46
+ throw new Error('Transaction not found');
47
+ }
48
+ if (tx.meta?.err) {
49
+ throw new Error('Transaction failed');
50
+ }
51
+ const { signature, expectedDestination, expectedSource, minLamports } = params;
52
+ const instructions = tx.transaction.message.instructions;
53
+ let observedLamports = 0;
54
+ let matchedSource = '';
55
+ for (const ix of instructions) {
56
+ if (!isSystemTransferInstruction(ix))
57
+ continue;
58
+ const source = ix.parsed.info.source;
59
+ const destination = ix.parsed.info.destination;
60
+ if (destination !== expectedDestination)
61
+ continue;
62
+ if (expectedSource && source !== expectedSource)
63
+ continue;
64
+ observedLamports += toLamports(ix.parsed.info.lamports);
65
+ if (!matchedSource)
66
+ matchedSource = source;
67
+ }
68
+ if (observedLamports <= 0) {
69
+ throw new Error('No matching SOL transfer found');
70
+ }
71
+ if (minLamports !== undefined && observedLamports < minLamports) {
72
+ throw new Error('Transfer amount below minimum');
73
+ }
74
+ return {
75
+ signature,
76
+ observedLamports,
77
+ source: expectedSource ?? matchedSource,
78
+ destination: expectedDestination,
79
+ };
80
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@cedros/login-sidecar",
3
+ "version": "0.0.1",
4
+ "description": "Cedros Login sidecar service (Privacy Cash + Solana utilities)",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "start": "node dist/index.js"
8
+ },
9
+ "dependencies": {
10
+ "@solana/web3.js": "^1.98.0",
11
+ "@types/node-localstorage": "^1.3.3",
12
+ "bs58": "^6.0.0",
13
+ "express": "^4.21.2",
14
+ "node-localstorage": "^3.0.5",
15
+ "privacycash": "^1.1.11"
16
+ },
17
+ "engines": {
18
+ "node": ">=24.0.0"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ ".env.example"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ }
27
+ }