@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,37 @@
1
+ "use strict";
2
+ /**
3
+ * Health check endpoint
4
+ *
5
+ * GET /health - Returns service health status
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.createHealthRoutes = createHealthRoutes;
9
+ const express_1 = require("express");
10
+ function createHealthRoutes(solana, privacyCash) {
11
+ const router = (0, express_1.Router)();
12
+ router.get('/health', async (_req, res) => {
13
+ try {
14
+ const rpcConnected = await solana.isConnected();
15
+ const sdkLoaded = await privacyCash.isLoaded();
16
+ const status = rpcConnected && sdkLoaded ? 'healthy' : 'degraded';
17
+ res.json({
18
+ status,
19
+ timestamp: new Date().toISOString(),
20
+ network: solana.getNetwork(),
21
+ checks: {
22
+ rpc_connected: rpcConnected,
23
+ sdk_loaded: sdkLoaded,
24
+ },
25
+ });
26
+ }
27
+ catch (error) {
28
+ console.error('[Health] Check failed:', error);
29
+ res.status(503).json({
30
+ status: 'unhealthy',
31
+ timestamp: new Date().toISOString(),
32
+ error: error instanceof Error ? error.message : 'Unknown error',
33
+ });
34
+ }
35
+ });
36
+ return router;
37
+ }
@@ -0,0 +1,2 @@
1
+ import type { SolanaService } from '../services/solana.js';
2
+ export declare function createVerifyRoutes(solanaService: SolanaService): import("express-serve-static-core").Router;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createVerifyRoutes = createVerifyRoutes;
4
+ const express_1 = require("express");
5
+ const verifySolTransfer_js_1 = require("../utils/verifySolTransfer.js");
6
+ function createVerifyRoutes(solanaService) {
7
+ const router = (0, express_1.Router)();
8
+ router.post('/verify/sol-transfer', async (req, res) => {
9
+ try {
10
+ const body = req.body;
11
+ if (!body || typeof body.signature !== 'string') {
12
+ return res.status(400).json({ error: 'Missing signature' });
13
+ }
14
+ if (typeof body.expectedDestination !== 'string' || body.expectedDestination.length === 0) {
15
+ return res.status(400).json({ error: 'Missing expectedDestination' });
16
+ }
17
+ if (body.expectedSource !== undefined && typeof body.expectedSource !== 'string') {
18
+ return res.status(400).json({ error: 'Invalid expectedSource' });
19
+ }
20
+ if (body.minLamports !== undefined && typeof body.minLamports !== 'number') {
21
+ return res.status(400).json({ error: 'Invalid minLamports' });
22
+ }
23
+ const connection = solanaService.getConnection();
24
+ const tx = await connection.getParsedTransaction(body.signature, {
25
+ commitment: 'finalized',
26
+ maxSupportedTransactionVersion: 0,
27
+ });
28
+ const result = (0, verifySolTransfer_js_1.verifySolTransferFromParsedTransaction)(tx, {
29
+ signature: body.signature,
30
+ expectedSource: body.expectedSource,
31
+ expectedDestination: body.expectedDestination,
32
+ minLamports: body.minLamports,
33
+ });
34
+ return res.json({
35
+ ok: true,
36
+ signature: result.signature,
37
+ observedLamports: result.observedLamports,
38
+ source: result.source,
39
+ destination: result.destination,
40
+ });
41
+ }
42
+ catch (err) {
43
+ const message = err instanceof Error ? err.message : 'Verification failed';
44
+ return res.status(400).json({ error: message });
45
+ }
46
+ });
47
+ return router;
48
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Withdrawal endpoints for withdrawing from user's Privacy Cash to company wallet
3
+ *
4
+ * POST /withdraw - Withdraw from a user's Privacy Cash account to company wallet
5
+ * POST /withdraw/balance - Get a user's private balance in Privacy Cash
6
+ *
7
+ * Architecture:
8
+ * - Withdrawal requires user's keypair (reconstructed from stored shares)
9
+ * - Funds go from user's Privacy Cash account to company wallet
10
+ * - This is the second half of the privacy flow (deposit → wait → withdraw)
11
+ */
12
+ import { Router } from 'express';
13
+ import { PrivacyCashService } from '../services/privacy-cash.js';
14
+ import { JupiterService } from '../services/jupiter.js';
15
+ export declare function createWithdrawRoutes(privacyCash: PrivacyCashService, _jupiter?: JupiterService): Router;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /**
3
+ * Withdrawal endpoints for withdrawing from user's Privacy Cash to company wallet
4
+ *
5
+ * POST /withdraw - Withdraw from a user's Privacy Cash account to company wallet
6
+ * POST /withdraw/balance - Get a user's private balance in Privacy Cash
7
+ *
8
+ * Architecture:
9
+ * - Withdrawal requires user's keypair (reconstructed from stored shares)
10
+ * - Funds go from user's Privacy Cash account to company wallet
11
+ * - This is the second half of the privacy flow (deposit → wait → withdraw)
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createWithdrawRoutes = createWithdrawRoutes;
18
+ const express_1 = require("express");
19
+ const web3_js_1 = require("@solana/web3.js");
20
+ const bs58_1 = __importDefault(require("bs58"));
21
+ function createWithdrawRoutes(privacyCash, _jupiter) {
22
+ const router = (0, express_1.Router)();
23
+ /**
24
+ * POST /withdraw
25
+ *
26
+ * Withdraw funds from a user's Privacy Cash account to the company wallet.
27
+ * This is called after the "privacy period" has elapsed.
28
+ *
29
+ * The user's keypair is reconstructed from stored SSS shares (Share A + Share B).
30
+ * Funds are sent to the configured company wallet address.
31
+ */
32
+ router.post('/withdraw', async (req, res) => {
33
+ try {
34
+ const body = req.body;
35
+ // Validate request
36
+ if (!body.user_private_key || typeof body.user_private_key !== 'string') {
37
+ res.status(400).json({ error: 'user_private_key is required and must be a base58 string' });
38
+ return;
39
+ }
40
+ if (!body.amount_lamports || typeof body.amount_lamports !== 'number') {
41
+ res.status(400).json({ error: 'amount_lamports is required and must be a number' });
42
+ return;
43
+ }
44
+ if (body.amount_lamports <= 0) {
45
+ res.status(400).json({ error: 'amount_lamports must be positive' });
46
+ return;
47
+ }
48
+ // Decode the private key
49
+ let userKeypair;
50
+ try {
51
+ const privateKeyBytes = bs58_1.default.decode(body.user_private_key);
52
+ userKeypair = web3_js_1.Keypair.fromSecretKey(privateKeyBytes);
53
+ }
54
+ catch {
55
+ res.status(400).json({ error: 'Invalid private key format' });
56
+ return;
57
+ }
58
+ // Execute the withdrawal from user's account to company wallet
59
+ const result = await privacyCash.withdrawFromUser(userKeypair, body.amount_lamports);
60
+ // NOTE: Swap-on-withdraw is not supported.
61
+ // The Privacy Cash withdrawal sends funds to the company wallet.
62
+ // Swapping would require signing with the company/treasury key.
63
+ const targetCurrency = body.target_currency?.toUpperCase() || 'SOL';
64
+ if (targetCurrency !== 'SOL') {
65
+ res.json({
66
+ success: result.success,
67
+ tx_signature: result.txSignature,
68
+ fee_lamports: result.feeLamports,
69
+ amount_lamports: result.amountLamports,
70
+ is_partial: result.isPartial,
71
+ swap_failed: true,
72
+ swap_error: 'Swap-on-withdraw is not supported; funds remain in SOL',
73
+ currency: 'SOL',
74
+ });
75
+ return;
76
+ }
77
+ // No swap needed - return SOL
78
+ res.json({
79
+ success: result.success,
80
+ tx_signature: result.txSignature,
81
+ fee_lamports: result.feeLamports,
82
+ amount_lamports: result.amountLamports,
83
+ is_partial: result.isPartial,
84
+ currency: 'SOL',
85
+ });
86
+ }
87
+ catch (error) {
88
+ console.error('[Withdraw] Error:', error);
89
+ res.status(500).json({
90
+ error: 'Failed to withdraw funds',
91
+ details: error instanceof Error ? error.message : 'Unknown error',
92
+ });
93
+ }
94
+ });
95
+ /**
96
+ * POST /withdraw/balance
97
+ *
98
+ * Get a user's private balance in Privacy Cash.
99
+ * Requires the user's keypair to decrypt UTXO data.
100
+ *
101
+ * Note: This is POST (not GET) because it requires a request body with the private key.
102
+ */
103
+ router.post('/withdraw/balance', async (req, res) => {
104
+ try {
105
+ const body = req.body;
106
+ // Validate request
107
+ if (!body.user_private_key || typeof body.user_private_key !== 'string') {
108
+ res.status(400).json({ error: 'user_private_key is required and must be a base58 string' });
109
+ return;
110
+ }
111
+ // Decode the private key
112
+ let userKeypair;
113
+ try {
114
+ const privateKeyBytes = bs58_1.default.decode(body.user_private_key);
115
+ userKeypair = web3_js_1.Keypair.fromSecretKey(privateKeyBytes);
116
+ }
117
+ catch {
118
+ res.status(400).json({ error: 'Invalid private key format' });
119
+ return;
120
+ }
121
+ const balanceLamports = await privacyCash.getUserPrivateBalance(userKeypair);
122
+ res.json({
123
+ balance_lamports: balanceLamports,
124
+ balance_sol: balanceLamports / 1_000_000_000,
125
+ user_pubkey: userKeypair.publicKey.toBase58(),
126
+ });
127
+ }
128
+ catch (error) {
129
+ console.error('[Withdraw/Balance] Error:', error);
130
+ res.status(500).json({
131
+ error: 'Failed to get private balance',
132
+ details: error instanceof Error ? error.message : 'Unknown error',
133
+ });
134
+ }
135
+ });
136
+ return router;
137
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Jupiter Ultra API service for gasless SPL token → SOL swaps
3
+ *
4
+ * Jupiter Ultra provides gasless swaps when:
5
+ * - User wallet has < 0.01 SOL (embedded wallets qualify)
6
+ * - Trade size > ~$10 USD (configured via JUPITER_MIN_SWAP_USD)
7
+ * - Jupiter pays transaction fees, deducted from swap output
8
+ *
9
+ * Flow:
10
+ * 1. GET /order - Get unsigned swap transaction
11
+ * 2. Sign transaction with user keypair
12
+ * 3. POST /execute - Submit signed transaction (Jupiter pays gas)
13
+ */
14
+ import { Keypair } from '@solana/web3.js';
15
+ import { Config } from '../config.js';
16
+ /** USDC token mint address on Solana mainnet */
17
+ export declare const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
18
+ /** USDT token mint address on Solana mainnet */
19
+ export declare const USDT_MINT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";
20
+ /** Parameters for getting a swap order */
21
+ export interface SwapOrderParams {
22
+ /** SPL token mint address to swap from (e.g., USDC) */
23
+ inputMint: string;
24
+ /** Amount in token's smallest unit (e.g., 10 USDC = 10_000_000 for 6 decimals) */
25
+ amount: string;
26
+ /** User's wallet address (taker) */
27
+ takerAddress: string;
28
+ }
29
+ /** Result of getting a swap order */
30
+ export interface SwapOrder {
31
+ /** Base64 encoded unsigned transaction */
32
+ transaction: string;
33
+ /** Request ID for tracking */
34
+ requestId: string;
35
+ /** Expected output amount in smallest unit */
36
+ expectedOutAmount: string;
37
+ /** Price impact percentage */
38
+ priceImpactPct: string;
39
+ /** Whether this order qualifies for gasless execution */
40
+ gasless: boolean;
41
+ /** Slippage in basis points */
42
+ slippageBps: number;
43
+ }
44
+ /** Result of executing a swap */
45
+ export interface SwapResult {
46
+ success: boolean;
47
+ /** Transaction signature */
48
+ txSignature: string;
49
+ /** Expected output amount (from order) */
50
+ expectedOutAmount: string;
51
+ /** Actual output amount received (if available) */
52
+ actualOutAmount?: string;
53
+ /** Whether gasless was used */
54
+ gasless: boolean;
55
+ /** Error message if failed */
56
+ error?: string;
57
+ /** Error code if failed */
58
+ errorCode?: number;
59
+ }
60
+ export declare class JupiterService {
61
+ private apiUrl;
62
+ private apiKey;
63
+ private minSwapUsd;
64
+ private rateLimit;
65
+ constructor(config: Config);
66
+ /**
67
+ * Get minimum swap amount in USD required for gasless swaps
68
+ */
69
+ getMinSwapUsd(): number;
70
+ /**
71
+ * Get configured rate limit (requests per 10 seconds) for queue throttling
72
+ */
73
+ getRateLimit(): number;
74
+ /**
75
+ * Get a swap order (unsigned transaction) from Jupiter Ultra API
76
+ *
77
+ * @param params - Swap parameters (inputMint, amount, takerAddress)
78
+ * @returns SwapOrder with unsigned transaction and expected output
79
+ * @throws Error if Jupiter API returns an error or network fails
80
+ */
81
+ getSwapOrder(params: SwapOrderParams): Promise<SwapOrder>;
82
+ /**
83
+ * Sign and execute a swap transaction
84
+ *
85
+ * Takes an unsigned transaction from getSwapOrder(), signs it with the user's
86
+ * keypair, and submits it to Jupiter for execution (gasless).
87
+ *
88
+ * @param order - SwapOrder from getSwapOrder()
89
+ * @param userKeypair - User's keypair to sign the transaction
90
+ * @returns SwapResult with transaction signature
91
+ * @throws Error if signing fails or Jupiter execution fails
92
+ */
93
+ executeSwap(order: SwapOrder, userKeypair: Keypair): Promise<SwapResult>;
94
+ /**
95
+ * Perform a complete gasless swap from SPL token to SOL
96
+ *
97
+ * Combines getSwapOrder() and executeSwap() into a single call.
98
+ * This is the main method to use for swapping tokens.
99
+ *
100
+ * @param inputMint - SPL token mint address to swap from
101
+ * @param amount - Amount in token's smallest unit
102
+ * @param userKeypair - User's keypair (for signing and as taker address)
103
+ * @returns SwapResult with transaction signature and output amount
104
+ */
105
+ swapToSol(inputMint: string, amount: string, userKeypair: Keypair): Promise<SwapResult>;
106
+ /**
107
+ * Perform a complete swap from SOL to SPL token (for withdrawals)
108
+ *
109
+ * This is used when the company's preferred currency is not SOL.
110
+ * The SOL from Privacy Cash is swapped to USDC/USDT via Jupiter.
111
+ *
112
+ * @param outputMint - SPL token mint address to swap to (e.g., USDC, USDT)
113
+ * @param amountLamports - Amount of SOL in lamports to swap
114
+ * @param userKeypair - User's keypair (for signing and as taker address)
115
+ * @returns SwapResult with transaction signature and output amount
116
+ */
117
+ swapFromSol(outputMint: string, amountLamports: string, userKeypair: Keypair): Promise<SwapResult>;
118
+ /**
119
+ * Get the mint address for a currency code
120
+ *
121
+ * @param currency - Currency code (SOL, USDC, USDT)
122
+ * @returns Mint address or null if unsupported/is SOL
123
+ */
124
+ static getMintForCurrency(currency: string): string | null;
125
+ /**
126
+ * Parse a base58-encoded private key into a Keypair
127
+ */
128
+ static parseKeypair(privateKeyBase58: string): Keypair;
129
+ }
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ /**
3
+ * Jupiter Ultra API service for gasless SPL token → SOL swaps
4
+ *
5
+ * Jupiter Ultra provides gasless swaps when:
6
+ * - User wallet has < 0.01 SOL (embedded wallets qualify)
7
+ * - Trade size > ~$10 USD (configured via JUPITER_MIN_SWAP_USD)
8
+ * - Jupiter pays transaction fees, deducted from swap output
9
+ *
10
+ * Flow:
11
+ * 1. GET /order - Get unsigned swap transaction
12
+ * 2. Sign transaction with user keypair
13
+ * 3. POST /execute - Submit signed transaction (Jupiter pays gas)
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.JupiterService = exports.USDT_MINT = exports.USDC_MINT = void 0;
20
+ const web3_js_1 = require("@solana/web3.js");
21
+ const bs58_1 = __importDefault(require("bs58"));
22
+ const fetchWithTimeout_js_1 = require("../utils/fetchWithTimeout.js");
23
+ /** SOL token mint address (native SOL wrapped) */
24
+ const SOL_MINT = 'So11111111111111111111111111111111111111112';
25
+ /** Default Jupiter HTTP timeout (ms) */
26
+ const JUPITER_TIMEOUT_MS = 10_000;
27
+ /** USDC token mint address on Solana mainnet */
28
+ exports.USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
29
+ /** USDT token mint address on Solana mainnet */
30
+ exports.USDT_MINT = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB';
31
+ class JupiterService {
32
+ apiUrl;
33
+ apiKey;
34
+ minSwapUsd;
35
+ rateLimit;
36
+ constructor(config) {
37
+ this.apiUrl = config.jupiter.apiUrl;
38
+ this.apiKey = config.jupiter.apiKey;
39
+ this.minSwapUsd = config.jupiter.minSwapUsd;
40
+ this.rateLimit = config.jupiter.rateLimit;
41
+ if (!this.apiKey) {
42
+ console.warn('[Jupiter] WARNING: No API key configured. Jupiter Ultra API requires an API key. Get one free at https://portal.jup.ag');
43
+ }
44
+ console.log(`[Jupiter] Initialized with API: ${this.apiUrl}, minSwapUsd: ${this.minSwapUsd}, rateLimit: ${this.rateLimit}/10s`);
45
+ }
46
+ /**
47
+ * Get minimum swap amount in USD required for gasless swaps
48
+ */
49
+ getMinSwapUsd() {
50
+ return this.minSwapUsd;
51
+ }
52
+ /**
53
+ * Get configured rate limit (requests per 10 seconds) for queue throttling
54
+ */
55
+ getRateLimit() {
56
+ return this.rateLimit;
57
+ }
58
+ /**
59
+ * Get a swap order (unsigned transaction) from Jupiter Ultra API
60
+ *
61
+ * @param params - Swap parameters (inputMint, amount, takerAddress)
62
+ * @returns SwapOrder with unsigned transaction and expected output
63
+ * @throws Error if Jupiter API returns an error or network fails
64
+ */
65
+ async getSwapOrder(params) {
66
+ const { inputMint, amount, takerAddress } = params;
67
+ // Build query parameters
68
+ const queryParams = new URLSearchParams({
69
+ inputMint,
70
+ outputMint: SOL_MINT,
71
+ amount,
72
+ taker: takerAddress,
73
+ });
74
+ const url = `${this.apiUrl}/order?${queryParams.toString()}`;
75
+ const headers = {
76
+ 'Accept': 'application/json',
77
+ };
78
+ // Add API key if configured (required for production)
79
+ if (this.apiKey) {
80
+ headers['x-api-key'] = this.apiKey;
81
+ }
82
+ console.log(`[Jupiter] Getting swap order: ${inputMint} -> SOL, amount: ${amount}`);
83
+ const response = await (0, fetchWithTimeout_js_1.fetchWithTimeout)(fetch, url, { headers }, JUPITER_TIMEOUT_MS);
84
+ if (!response.ok) {
85
+ const errorBody = await response.text();
86
+ let errorMessage = `Jupiter API error: ${response.status}`;
87
+ try {
88
+ const errorJson = JSON.parse(errorBody);
89
+ errorMessage = errorJson.error || errorMessage;
90
+ }
91
+ catch {
92
+ errorMessage = errorBody || errorMessage;
93
+ }
94
+ throw new Error(errorMessage);
95
+ }
96
+ const order = await response.json();
97
+ // Check for order-level errors (codes 1, 2, 3)
98
+ if (order.error || order.code) {
99
+ const errorMessages = {
100
+ 1: 'Insufficient token balance for swap',
101
+ 2: 'Insufficient SOL for gas fees',
102
+ 3: 'Trade size below minimum for gasless swap',
103
+ };
104
+ const errorMsg = order.error || errorMessages[order.code] || `Order failed with code ${order.code}`;
105
+ throw new Error(errorMsg);
106
+ }
107
+ // Validate required fields are present
108
+ if (!order.transaction || !order.requestId || !order.outAmount) {
109
+ throw new Error('Invalid order response: missing required fields');
110
+ }
111
+ console.log(`[Jupiter] Order received: requestId=${order.requestId}, outAmount=${order.outAmount}, gasless=${order.gasless}, router=${order.router}`);
112
+ return {
113
+ transaction: order.transaction,
114
+ requestId: order.requestId,
115
+ expectedOutAmount: order.outAmount,
116
+ priceImpactPct: order.priceImpactPct || '0',
117
+ gasless: order.gasless ?? false,
118
+ slippageBps: order.slippageBps ?? 0,
119
+ };
120
+ }
121
+ /**
122
+ * Sign and execute a swap transaction
123
+ *
124
+ * Takes an unsigned transaction from getSwapOrder(), signs it with the user's
125
+ * keypair, and submits it to Jupiter for execution (gasless).
126
+ *
127
+ * @param order - SwapOrder from getSwapOrder()
128
+ * @param userKeypair - User's keypair to sign the transaction
129
+ * @returns SwapResult with transaction signature
130
+ * @throws Error if signing fails or Jupiter execution fails
131
+ */
132
+ async executeSwap(order, userKeypair) {
133
+ console.log(`[Jupiter] Executing swap: requestId=${order.requestId}, gasless=${order.gasless}`);
134
+ // Deserialize the transaction from base64
135
+ const txBuffer = Buffer.from(order.transaction, 'base64');
136
+ const transaction = web3_js_1.VersionedTransaction.deserialize(txBuffer);
137
+ // Sign the transaction with user's keypair
138
+ transaction.sign([userKeypair]);
139
+ // Serialize the signed transaction back to base64
140
+ const signedTxBase64 = Buffer.from(transaction.serialize()).toString('base64');
141
+ // Submit to Jupiter execute endpoint
142
+ const headers = {
143
+ 'Content-Type': 'application/json',
144
+ 'Accept': 'application/json',
145
+ };
146
+ if (this.apiKey) {
147
+ headers['x-api-key'] = this.apiKey;
148
+ }
149
+ const response = await (0, fetchWithTimeout_js_1.fetchWithTimeout)(fetch, `${this.apiUrl}/execute`, {
150
+ method: 'POST',
151
+ headers,
152
+ body: JSON.stringify({
153
+ signedTransaction: signedTxBase64,
154
+ requestId: order.requestId,
155
+ }),
156
+ }, JUPITER_TIMEOUT_MS);
157
+ if (!response.ok) {
158
+ const errorBody = await response.text();
159
+ let errorMessage = `Jupiter execute error: ${response.status}`;
160
+ try {
161
+ const errorJson = JSON.parse(errorBody);
162
+ errorMessage = errorJson.error || errorMessage;
163
+ }
164
+ catch {
165
+ errorMessage = errorBody || errorMessage;
166
+ }
167
+ throw new Error(errorMessage);
168
+ }
169
+ const result = await response.json();
170
+ // Check for failure via status or error code
171
+ if (result.status !== 'Success' || (result.code !== undefined && result.code !== 0)) {
172
+ return {
173
+ success: false,
174
+ txSignature: result.signature || '',
175
+ expectedOutAmount: order.expectedOutAmount,
176
+ gasless: order.gasless,
177
+ error: result.error || `Swap failed with code ${result.code}`,
178
+ errorCode: result.code,
179
+ };
180
+ }
181
+ console.log(`[Jupiter] Swap executed: signature=${result.signature}, actualOut=${result.outputAmountResult || 'N/A'}`);
182
+ return {
183
+ success: true,
184
+ txSignature: result.signature,
185
+ expectedOutAmount: order.expectedOutAmount,
186
+ actualOutAmount: result.outputAmountResult,
187
+ gasless: order.gasless,
188
+ };
189
+ }
190
+ /**
191
+ * Perform a complete gasless swap from SPL token to SOL
192
+ *
193
+ * Combines getSwapOrder() and executeSwap() into a single call.
194
+ * This is the main method to use for swapping tokens.
195
+ *
196
+ * @param inputMint - SPL token mint address to swap from
197
+ * @param amount - Amount in token's smallest unit
198
+ * @param userKeypair - User's keypair (for signing and as taker address)
199
+ * @returns SwapResult with transaction signature and output amount
200
+ */
201
+ async swapToSol(inputMint, amount, userKeypair) {
202
+ const takerAddress = userKeypair.publicKey.toBase58();
203
+ // Get the swap order (unsigned transaction)
204
+ const order = await this.getSwapOrder({
205
+ inputMint,
206
+ amount,
207
+ takerAddress,
208
+ });
209
+ // Sign and execute the swap
210
+ return this.executeSwap(order, userKeypair);
211
+ }
212
+ /**
213
+ * Perform a complete swap from SOL to SPL token (for withdrawals)
214
+ *
215
+ * This is used when the company's preferred currency is not SOL.
216
+ * The SOL from Privacy Cash is swapped to USDC/USDT via Jupiter.
217
+ *
218
+ * @param outputMint - SPL token mint address to swap to (e.g., USDC, USDT)
219
+ * @param amountLamports - Amount of SOL in lamports to swap
220
+ * @param userKeypair - User's keypair (for signing and as taker address)
221
+ * @returns SwapResult with transaction signature and output amount
222
+ */
223
+ async swapFromSol(outputMint, amountLamports, userKeypair) {
224
+ const takerAddress = userKeypair.publicKey.toBase58();
225
+ // Build query parameters for SOL -> outputMint
226
+ const queryParams = new URLSearchParams({
227
+ inputMint: SOL_MINT,
228
+ outputMint,
229
+ amount: amountLamports,
230
+ taker: takerAddress,
231
+ });
232
+ const url = `${this.apiUrl}/order?${queryParams.toString()}`;
233
+ const headers = {
234
+ 'Accept': 'application/json',
235
+ };
236
+ if (this.apiKey) {
237
+ headers['x-api-key'] = this.apiKey;
238
+ }
239
+ console.log(`[Jupiter] Getting swap order: SOL -> ${outputMint}, amount: ${amountLamports} lamports`);
240
+ const response = await fetch(url, { headers });
241
+ if (!response.ok) {
242
+ const errorBody = await response.text();
243
+ let errorMessage = `Jupiter API error: ${response.status}`;
244
+ try {
245
+ const errorJson = JSON.parse(errorBody);
246
+ errorMessage = errorJson.error || errorMessage;
247
+ }
248
+ catch {
249
+ errorMessage = errorBody || errorMessage;
250
+ }
251
+ throw new Error(errorMessage);
252
+ }
253
+ const order = await response.json();
254
+ // Check for order-level errors (codes 1, 2, 3)
255
+ if (order.error || order.code) {
256
+ const errorMessages = {
257
+ 1: 'Insufficient token balance for swap',
258
+ 2: 'Insufficient SOL for gas fees',
259
+ 3: 'Trade size below minimum for gasless swap',
260
+ };
261
+ const errorMsg = order.error || errorMessages[order.code] || `Order failed with code ${order.code}`;
262
+ throw new Error(errorMsg);
263
+ }
264
+ // Validate required fields are present
265
+ if (!order.transaction || !order.requestId || !order.outAmount) {
266
+ throw new Error('Invalid order response: missing required fields');
267
+ }
268
+ console.log(`[Jupiter] Order received: requestId=${order.requestId}, outAmount=${order.outAmount}, gasless=${order.gasless}, router=${order.router}`);
269
+ // Execute the swap
270
+ return this.executeSwap({
271
+ transaction: order.transaction,
272
+ requestId: order.requestId,
273
+ expectedOutAmount: order.outAmount,
274
+ priceImpactPct: order.priceImpactPct || '0',
275
+ gasless: order.gasless ?? false,
276
+ slippageBps: order.slippageBps ?? 0,
277
+ }, userKeypair);
278
+ }
279
+ /**
280
+ * Get the mint address for a currency code
281
+ *
282
+ * @param currency - Currency code (SOL, USDC, USDT)
283
+ * @returns Mint address or null if unsupported/is SOL
284
+ */
285
+ static getMintForCurrency(currency) {
286
+ switch (currency.toUpperCase()) {
287
+ case 'USDC':
288
+ return exports.USDC_MINT;
289
+ case 'USDT':
290
+ return exports.USDT_MINT;
291
+ case 'SOL':
292
+ default:
293
+ return null; // No swap needed for SOL
294
+ }
295
+ }
296
+ /**
297
+ * Parse a base58-encoded private key into a Keypair
298
+ */
299
+ static parseKeypair(privateKeyBase58) {
300
+ const privateKeyBytes = bs58_1.default.decode(privateKeyBase58);
301
+ return web3_js_1.Keypair.fromSecretKey(privateKeyBytes);
302
+ }
303
+ }
304
+ exports.JupiterService = JupiterService;