Refactor: Fully migrated to direct PostgreSQL, implemented Public API v1, fixed Vercel deployment conflicts, and updated documentation

This commit is contained in:
mstfyldz
2026-03-12 21:54:57 +03:00
parent 321f25a15c
commit 515d513c1f
29 changed files with 1002 additions and 675 deletions

21
lib/api-auth.ts Normal file
View File

@@ -0,0 +1,21 @@
import { db } from './db';
export async function validateApiKey(apiKey: string | null) {
if (!apiKey) return null;
try {
const result = await db.query(
'SELECT * FROM merchants WHERE api_key = $1 LIMIT 1',
[apiKey]
);
if (result.rows.length === 0) {
return null;
}
return result.rows[0];
} catch (error) {
console.error('API Key Validation Error:', error);
return null;
}
}

67
lib/crypto-config.json Normal file
View File

@@ -0,0 +1,67 @@
{
"networks": [
{
"id": "POLYGON",
"name": "Polygon",
"icon": "🟣",
"rpc": "https://rpc.ankr.com/polygon",
"tokens": [
{ "symbol": "USDT", "address": "0xc2132D05D31C914a87C6611C10748AEb04B58e8F", "decimals": 6 },
{ "symbol": "USDC", "address": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", "decimals": 6 },
{ "symbol": "DAI", "address": "0x8f3Cf7ad23Cd3BaDDb9735AFf95930030000000", "decimals": 18 },
{ "symbol": "MATIC", "address": "NATIVE", "decimals": 18 },
{ "symbol": "WBTC", "address": "0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6", "decimals": 8 },
{ "symbol": "WETH", "address": "0x7ceb23fd6bc0ad59e62ac25578270cff1b9f619", "decimals": 18 },
{ "symbol": "SHIB", "address": "0x6f8a36397efed74758fdef2850935bb27d49e1ed", "decimals": 18 },
{ "symbol": "LINK", "address": "0xb0897686c545045aFc77CF20eC7A532E3120E0F1", "decimals": 18 },
{ "symbol": "PEPE", "address": "0x98f6d546343544fae8e60aaead11a68e64c29df6", "decimals": 18 }
]
},
{
"id": "BSC",
"name": "BNB Chain",
"icon": "🟡",
"rpc": "https://rpc.ankr.com/bsc",
"tokens": [
{ "symbol": "USDT", "address": "0x55d398326f99059fF775485246999027B3197955", "decimals": 18 },
{ "symbol": "USDC", "address": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", "decimals": 18 },
{ "symbol": "BNB", "address": "NATIVE", "decimals": 18 },
{ "symbol": "BTCCB", "address": "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", "decimals": 18 },
{ "symbol": "ETH", "address": "0x2170ed0880ac9a755fd29b2688956bd959f933f8", "decimals": 18 },
{ "symbol": "XRP", "address": "0x1d2f0da169059048e02d847144ee6dd583849764", "decimals": 18 },
{ "symbol": "ADA", "address": "0x3ee2200efb3400fabb9aacf31297cbdd1d435d47", "decimals": 18 },
{ "symbol": "DOGE", "address": "0xba2ae424d960c26247dd5c32ed17016355e8eb10", "decimals": 8 },
{ "symbol": "DOT", "address": "0x7083609fce4d1d8dc0c979aab8c869ea2c873402", "decimals": 18 },
{ "symbol": "LTC", "address": "0x4338665c00995c36411f1233069cc04868f18731", "decimals": 18 }
]
},
{
"id": "ETH",
"name": "Ethereum",
"icon": "🔵",
"rpc": "https://rpc.ankr.com/eth",
"tokens": [
{ "symbol": "USDT", "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "decimals": 6 },
{ "symbol": "USDC", "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "decimals": 6 },
{ "symbol": "DAI", "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", "decimals": 18 },
{ "symbol": "ETH", "address": "NATIVE", "decimals": 18 },
{ "symbol": "WBTC", "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", "decimals": 8 },
{ "symbol": "SHIB", "address": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", "decimals": 18 },
{ "symbol": "LINK", "address": "0x514910771af9ca656af840dff83e8264ecf986ca", "decimals": 18 },
{ "symbol": "UNI", "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "decimals": 18 },
{ "symbol": "PEPE", "address": "0x6982508145454ce325ddbe47a25d4ec3d2311933", "decimals": 18 }
]
},
{
"id": "SOLANA",
"name": "Solana (Dev)",
"icon": "🟢",
"rpc": "https://api.devnet.solana.com",
"tokens": [
{ "symbol": "SOL", "address": "NATIVE", "decimals": 9 },
{ "symbol": "USDC", "address": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", "decimals": 6 },
{ "symbol": "USDT", "address": "EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS", "decimals": 6 }
]
}
]
}

View File

@@ -1,57 +1,43 @@
import { ethers } from 'ethers';
import { Connection, PublicKey, Keypair, Transaction, SystemProgram, sendAndConfirmTransaction, clusterApiUrl, LAMPORTS_PER_SOL } from '@solana/web3.js';
import { getAssociatedTokenAddress, getAccount, createTransferInstruction } from '@solana/spl-token';
import { Connection, PublicKey, Keypair, Transaction, SystemProgram, clusterApiUrl, LAMPORTS_PER_SOL } from '@solana/web3.js';
import { getAssociatedTokenAddress, getAccount } from '@solana/spl-token';
import bs58 from 'bs58';
// Demo configuration - In production, these should be securely managed
const RPC_URLS: Record<string, string> = {
ETH: 'https://rpc.ankr.com/eth',
POLYGON: 'https://rpc.ankr.com/polygon',
BSC: 'https://rpc.ankr.com/bsc'
};
// AyrisSplitter Contract Address (Example addresses, in production use real deployed addresses)
const SPLITTER_ADDRESSES: Record<string, string> = {
POLYGON: '0x999...AYRIS_SPLITTER_POLYGON',
ETH: '0x888...AYRIS_SPLITTER_ETH'
};
import cryptoConfig from './crypto-config.json';
// ERC20 ABI for checking USDT/USDC balances
const ERC20_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)"
"function symbol() view returns (string)",
"function transfer(address to, uint256 value) public returns (bool)"
];
const STABLECOIN_ADDRESSES: Record<string, Record<string, string>> = {
POLYGON: {
USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359'
},
ETH: {
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
},
SOLANA: {
USDC: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // Devnet USDC
USDT: 'EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS' // Devnet USDT
}
};
export class CryptoEngine {
private provider!: ethers.JsonRpcProvider;
private solConnection!: Connection;
private network: string;
private config: any;
constructor(network: string = 'POLYGON') {
this.network = network;
if (network === 'SOLANA') {
this.solConnection = new Connection(clusterApiUrl('devnet'), 'confirmed');
constructor(networkId: string = 'POLYGON') {
this.network = networkId;
this.config = cryptoConfig.networks.find(n => n.id === networkId);
if (!this.config) throw new Error(`Network ${networkId} not found in config.`);
if (this.network === 'SOLANA') {
this.solConnection = new Connection(this.config.rpc, 'confirmed');
} else {
this.provider = new ethers.JsonRpcProvider(RPC_URLS[network]);
this.provider = new ethers.JsonRpcProvider(this.config.rpc);
}
}
/**
* Helper to get token config from JSON
*/
private getTokenConfig(symbol: string) {
return this.config.tokens.find((t: any) => t.symbol === symbol);
}
/**
* Generates a temporary wallet for a transaction.
*/
@@ -94,19 +80,12 @@ export class CryptoEngine {
tokenSymbol: string
) {
const tempWallet = new ethers.Wallet(tempWalletPrivateKey, this.provider);
const tokenAddress = STABLECOIN_ADDRESSES[this.network][tokenSymbol];
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, tempWallet);
const tokenConfig = this.getTokenConfig(tokenSymbol);
if (!tokenConfig) throw new Error(`Unsupported token ${tokenSymbol} on ${this.network}`);
const balance = await contract.balanceOf(tempWallet.address);
if (balance === 0n) throw new Error("Balance is zero");
const platformShare = (balance * 100n) / 10000n; // %1
const merchantShare = balance - platformShare; // %99
console.log(`[Sweep EVM] Total: ${ethers.formatUnits(balance, 6)} ${tokenSymbol}`);
console.log(`[Sweep EVM] Sending ${ethers.formatUnits(platformShare, 6)} to Platform...`);
console.log(`[Sweep EVM] Sending ${ethers.formatUnits(merchantShare, 6)} to Merchant...`);
console.log(`[Sweep EVM] Network: ${this.network} Total for ${tokenSymbol}`);
// Mocking the real transfer for demo
return {
success: true,
platformTx: '0x' + Math.random().toString(16).slice(2, 66),
@@ -114,6 +93,26 @@ export class CryptoEngine {
};
}
async fuelWallet(targetAddress: string, amount: string) {
const gasTankKey = process.env.CRYPTO_GAS_TANK_KEY;
if (!gasTankKey) {
console.warn("[CryptoEngine] No CRYPTO_GAS_TANK_KEY provided. Fueling skipped (Demo mode).");
return;
}
try {
const gasTank = new ethers.Wallet(gasTankKey, this.provider);
const tx = await gasTank.sendTransaction({
to: targetAddress,
value: ethers.parseEther(amount)
});
await tx.wait();
console.log(`[CryptoEngine] Fueled ${targetAddress} with ${amount} native currency. Hash: ${tx.hash}`);
} catch (error) {
console.error("[CryptoEngine] Fueling failed:", error);
}
}
private async sweepSolana(
tempWalletPrivateKey: string,
merchantAddress: string,
@@ -123,60 +122,17 @@ export class CryptoEngine {
const tempKeypair = Keypair.fromSecretKey(bs58.decode(tempWalletPrivateKey));
const pubKey = tempKeypair.publicKey;
if (tokenSymbol === 'SOL') {
const balance = await this.solConnection.getBalance(pubKey);
if (balance === 0) throw new Error("Balance is zero");
// Leave some lamports for tx fees. Mock logic for now
const actionableBalance = balance - 5000;
const platformShare = Math.floor(actionableBalance * 0.01);
const merchantShare = actionableBalance - platformShare;
console.log(`[Sweep SOL] Total: ${balance / LAMPORTS_PER_SOL} SOL`);
console.log(`[Sweep SOL] Platform Share: ${platformShare / LAMPORTS_PER_SOL} SOL`);
console.log(`[Sweep SOL] Merchant Share: ${merchantShare / LAMPORTS_PER_SOL} SOL`);
// Off-chain transaction split using SystemProgram
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: pubKey,
toPubkey: new PublicKey(platformAddress),
lamports: platformShare,
}),
SystemProgram.transfer({
fromPubkey: pubKey,
toPubkey: new PublicKey(merchantAddress),
lamports: merchantShare,
})
);
// Uncomment the following line to actually send the transaction on devnet
// const txSig = await sendAndConfirmTransaction(this.solConnection, transaction, [tempKeypair]);
const mockTxSig = bs58.encode(ethers.randomBytes(32));
return {
success: true,
platformTx: mockTxSig,
merchantTx: mockTxSig
};
} else {
// Processing SPL tokens (USDC/USDT)
const tokenMint = new PublicKey(STABLECOIN_ADDRESSES['SOLANA'][tokenSymbol]);
const tempAta = await getAssociatedTokenAddress(tokenMint, pubKey);
// For real transactions we would:
// 1. Check/create ATAs for platform & merchant
// 2. Add transfer instructions
// 3. Send transaction
console.log(`[Sweep SOL SPL] Mint: ${tokenMint.toBase58()}`);
return {
success: true,
platformTx: 'sol_mock_tx_spl',
merchantTx: 'sol_mock_tx_spl'
};
// Check if wallet needs SOL for gas
const solBalance = await this.solConnection.getBalance(pubKey);
if (solBalance < 5000000 && tokenSymbol !== 'SOL') {
console.log(`[Sweep SOL] Low SOL for gas, fueling...`);
}
return {
success: true,
platformTx: 'sol_mock_tx_' + Math.random().toString(36).substring(7),
merchantTx: 'sol_mock_tx_' + Math.random().toString(36).substring(7)
};
}
/**
@@ -188,53 +144,36 @@ export class CryptoEngine {
error?: string;
}> {
try {
const tokenConfig = this.getTokenConfig(tokenSymbol || 'USDT');
if (!tokenConfig) return { success: false, error: "Token not supported in config" };
if (this.network === 'SOLANA') {
const pubKey = new PublicKey(address);
if (!tokenSymbol || tokenSymbol === 'SOL') {
if (tokenConfig.address === 'NATIVE') {
const balance = await this.solConnection.getBalance(pubKey);
const balanceInSol = balance / LAMPORTS_PER_SOL;
if (balanceInSol >= parseFloat(expectedAmount)) {
return { success: true };
}
if (balanceInSol >= parseFloat(expectedAmount)) return { success: true };
} else {
const tokenMint = new PublicKey(STABLECOIN_ADDRESSES['SOLANA'][tokenSymbol]);
const tokenMint = new PublicKey(tokenConfig.address);
const ata = await getAssociatedTokenAddress(tokenMint, pubKey);
try {
const accountInfo = await getAccount(this.solConnection, ata);
// Using mock decimals 6 for USDT/USDC
const balance = Number(accountInfo.amount) / 1_000_000;
if (balance >= parseFloat(expectedAmount)) {
return { success: true };
}
} catch (e) {
// Account might not exist yet
}
const balance = Number(accountInfo.amount) / Math.pow(10, tokenConfig.decimals);
if (balance >= parseFloat(expectedAmount)) return { success: true };
} catch (e) {}
}
} else {
if (!tokenSymbol || tokenSymbol === 'ETH' || tokenSymbol === 'MATIC') {
if (tokenConfig.address === 'NATIVE') {
const balance = await this.provider.getBalance(address);
const balanceInEth = ethers.formatEther(balance);
if (parseFloat(balanceInEth) >= parseFloat(expectedAmount)) {
return { success: true };
}
if (parseFloat(balanceInEth) >= parseFloat(expectedAmount)) return { success: true };
} else {
// Check ERC20 balance (USDT/USDC)
const tokenAddress = STABLECOIN_ADDRESSES[this.network][tokenSymbol];
if (!tokenAddress) throw new Error("Unsupported token");
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.provider);
const contract = new ethers.Contract(tokenConfig.address, ERC20_ABI, this.provider);
const balance = await contract.balanceOf(address);
const decimals = await contract.decimals();
const formattedBalance = ethers.formatUnits(balance, decimals);
if (parseFloat(formattedBalance) >= parseFloat(expectedAmount)) {
return { success: true };
}
const formattedBalance = ethers.formatUnits(balance, tokenConfig.decimals);
if (parseFloat(formattedBalance) >= parseFloat(expectedAmount)) return { success: true };
}
}
return { success: false };
} catch (error: any) {
return { success: false, error: error.message };

View File

@@ -1,9 +0,0 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
// This should ONLY be used in Server Components or API Routes
export const supabaseAdmin = createClient(
supabaseUrl,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);

View File

@@ -1,6 +0,0 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);