From 889eff49c87debec6bc05dac4fedaab564179482 Mon Sep 17 00:00:00 2001 From: mstfyldz Date: Thu, 12 Mar 2026 23:22:52 +0300 Subject: [PATCH] Feature: Implemented Secure Merchant Vault Architecture with On-chain segregation and Encryption --- app/api/crypto-sweep/route.ts | 13 +++- app/merchant/[id]/(dashboard)/layout.tsx | 10 +++ app/merchant/[id]/(dashboard)/page.tsx | 35 ++++++++++- lib/encryption.ts | 49 +++++++++++++++ lib/vault-manager.ts | 80 ++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 lib/encryption.ts create mode 100644 lib/vault-manager.ts diff --git a/app/api/crypto-sweep/route.ts b/app/api/crypto-sweep/route.ts index 1e3a7d7..730b0a6 100644 --- a/app/api/crypto-sweep/route.ts +++ b/app/api/crypto-sweep/route.ts @@ -52,7 +52,18 @@ export async function POST(request: Request) { // 4. Define Merchant Address (Fetch from transaction's merchant) const merchantResult = await db.query('SELECT * FROM merchants WHERE id = $1', [transaction.merchant_id]); - const merchantAddress = merchantResult.rows[0]?.wallet_address || platformAddress; + const merchant = merchantResult.rows[0]; + + let merchantAddress = merchant?.wallet_address || platformAddress; + + // Use merchant's specific vault if available + if (selectedNetwork === 'SOLANA') { + if (merchant?.sol_vault_address) merchantAddress = merchant.sol_vault_address; + } else { + if (merchant?.evm_vault_address) merchantAddress = merchant.evm_vault_address; + } + + console.log(`[Sweep] Destination for merchant: ${merchantAddress}`); // 5. Initialize Engine and Verify Payment first const cryptoEngine = new CryptoEngine(selectedNetwork); diff --git a/app/merchant/[id]/(dashboard)/layout.tsx b/app/merchant/[id]/(dashboard)/layout.tsx index 0009ef5..fc1e73e 100644 --- a/app/merchant/[id]/(dashboard)/layout.tsx +++ b/app/merchant/[id]/(dashboard)/layout.tsx @@ -9,6 +9,7 @@ import { } from 'lucide-react'; import MerchantSidebar from '@/components/merchant/MerchantSidebar'; import { db } from '@/lib/db'; +import { ensureMerchantVaults } from '@/lib/vault-manager'; export default async function MerchantLayout({ children, @@ -33,6 +34,15 @@ export default async function MerchantLayout({ } } + // Ensure vaults exist for this merchant + if (resolvedId) { + try { + await ensureMerchantVaults(resolvedId); + } catch (err) { + console.error('[MerchantLayout] Vault sync failed:', err); + } + } + // 2. Auth Check const isAuth = cookieStore.get(`merchant_auth_${resolvedId}`); const isShortAuth = cookieStore.get(`merchant_auth_${identifier}`); diff --git a/app/merchant/[id]/(dashboard)/page.tsx b/app/merchant/[id]/(dashboard)/page.tsx index 99c5b1a..5981bfd 100644 --- a/app/merchant/[id]/(dashboard)/page.tsx +++ b/app/merchant/[id]/(dashboard)/page.tsx @@ -132,13 +132,46 @@ export default async function MerchantDashboardPage(props: { + {/* On-chain Vault Section */} +
+
+
+ +
+
+

EVM Kasanız (Polygon/BSC/ETH)

+
+ + {merchant.evm_vault_address || 'Henüz Oluşturulmadı'} + +
Copy
+
+
+
+ +
+
+ +
+
+

Solana Kasanız

+
+ + {merchant.sol_vault_address || 'Henüz Oluşturulmadı'} + +
Copy
+
+
+
+
+ {/* Stats Cards */}

Toplam Ciro

- +
diff --git a/lib/encryption.ts b/lib/encryption.ts new file mode 100644 index 0000000..c1f15e6 --- /dev/null +++ b/lib/encryption.ts @@ -0,0 +1,49 @@ +import crypto from 'crypto'; + +const ENCRYPTION_KEY = process.env.ENCRYPTION_SECRET || 'fallback_secret_do_not_use_in_production_12345'; // Must be 32 chars for aes-256-gcm +const IV_LENGTH = 16; +const ALGORITHM = 'aes-256-gcm'; + +/** + * Encrypts a piece of sensitive text (like a private key) + */ +export function encrypt(text: string): string { + // Ensure key is 32 bytes + const key = crypto.createHash('sha256').update(ENCRYPTION_KEY).digest(); + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv(ALGORITHM, key, iv); + + let encrypted = cipher.update(text, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + + const authTag = cipher.getAuthTag().toString('hex'); + + // Format: iv:authTag:encrypted + return `${iv.toString('hex')}:${authTag}:${encrypted}`; +} + +/** + * Decrypts an encrypted string back to its original form + */ +export function decrypt(encryptedText: string): string { + try { + const parts = encryptedText.split(':'); + if (parts.length !== 3) throw new Error('Invalid encrypted text format'); + + const iv = Buffer.from(parts[0], 'hex'); + const authTag = Buffer.from(parts[1], 'hex'); + const encrypted = parts[2]; + + const key = crypto.createHash('sha256').update(ENCRYPTION_KEY).digest(); + const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); + decipher.setAuthTag(authTag); + + let decrypted = decipher.update(encrypted, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + + return decrypted; + } catch (error) { + console.error('Decryption failed:', error); + throw new Error('Critical: Could not decrypt sensitive data. Check ENCRYPTION_SECRET.'); + } +} diff --git a/lib/vault-manager.ts b/lib/vault-manager.ts new file mode 100644 index 0000000..a49f0ae --- /dev/null +++ b/lib/vault-manager.ts @@ -0,0 +1,80 @@ +import { ethers } from 'ethers'; +import { Keypair } from '@solana/web3.js'; +import bs58 from 'bs58'; +import { db } from './db'; +import { encrypt, decrypt } from './encryption'; + +/** + * Ensures a merchant has EVM and Solana vault wallets. + * Generates them if they don't exist. + */ +export async function ensureMerchantVaults(merchantId: string) { + const result = await db.query( + 'SELECT id, evm_vault_address, sol_vault_address FROM merchants WHERE id = $1', + [merchantId] + ); + + if (result.rows.length === 0) throw new Error('Merchant not found'); + + const merchant = result.rows[0]; + + // If already has vaults, skip + if (merchant.evm_vault_address && merchant.sol_vault_address) { + return { + evm: merchant.evm_vault_address, + sol: merchant.sol_vault_address + }; + } + + console.log(`[VaultManager] Generating vaults for merchant ${merchantId}...`); + + // 1. Generate EVM Wallet + const evmWallet = ethers.Wallet.createRandom(); + const encryptedEvmKey = encrypt(evmWallet.privateKey); + + // 2. Generate Solana Wallet + const solKeypair = Keypair.generate(); + const encryptedSolKey = encrypt(bs58.encode(solKeypair.secretKey)); + + // 3. Save to DB + await db.query( + `UPDATE merchants SET + evm_vault_address = $1, + evm_vault_key = $2, + sol_vault_address = $3, + sol_vault_key = $4 + WHERE id = $5`, + [ + evmWallet.address, + encryptedEvmKey, + solKeypair.publicKey.toBase58(), + encryptedSolKey, + merchantId + ] + ); + + return { + evm: evmWallet.address, + sol: solKeypair.publicKey.toBase58() + }; +} + +/** + * Gets the decrypted private keys for a merchant's vaults. + * CRITICAL: Use only when needed (e.g. during manual payout) + */ +export async function getMerchantVaultSecrets(merchantId: string) { + const result = await db.query( + 'SELECT evm_vault_key, sol_vault_key FROM merchants WHERE id = $1', + [merchantId] + ); + + if (result.rows.length === 0) throw new Error('Merchant not found'); + + const merchant = result.rows[0]; + + return { + evmKey: merchant.evm_vault_key ? decrypt(merchant.evm_vault_key) : null, + solKey: merchant.sol_vault_key ? decrypt(merchant.sol_vault_key) : null + }; +}