Migrate to local PG & Update Solana Checkout

This commit is contained in:
2026-03-12 19:22:10 +03:00
parent e7f9325b1c
commit 321f25a15c
16 changed files with 1445 additions and 4980 deletions

View File

@@ -1,4 +1,7 @@
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 bs58 from 'bs58';
// Demo configuration - In production, these should be securely managed
const RPC_URLS: Record<string, string> = {
@@ -28,23 +31,44 @@ const STABLECOIN_ADDRESSES: Record<string, Record<string, string>> = {
ETH: {
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
},
SOLANA: {
USDC: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // Devnet USDC
USDT: 'EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS' // Devnet USDT
}
};
export class CryptoEngine {
private provider: ethers.JsonRpcProvider;
private provider!: ethers.JsonRpcProvider;
private solConnection!: Connection;
private network: string;
constructor(network: string = 'POLYGON') {
this.network = network;
this.provider = new ethers.JsonRpcProvider(RPC_URLS[network]);
if (network === 'SOLANA') {
this.solConnection = new Connection(clusterApiUrl('devnet'), 'confirmed');
} else {
this.provider = new ethers.JsonRpcProvider(RPC_URLS[network]);
}
}
/**
* Generates a temporary wallet for a transaction.
*/
static async createTemporaryWallet(): Promise<ethers.Wallet> {
return ethers.Wallet.createRandom();
static async createTemporaryWallet(network: string = 'POLYGON'): Promise<{ address: string, privateKey: string }> {
if (network === 'SOLANA') {
const keypair = Keypair.generate();
return {
address: keypair.publicKey.toBase58(),
privateKey: bs58.encode(keypair.secretKey)
};
} else {
const wallet = ethers.Wallet.createRandom();
return {
address: wallet.address,
privateKey: wallet.privateKey
};
}
}
/**
@@ -55,6 +79,19 @@ export class CryptoEngine {
merchantAddress: string,
platformAddress: string,
tokenSymbol: string = 'USDT'
) {
if (this.network === 'SOLANA') {
return this.sweepSolana(tempWalletPrivateKey, merchantAddress, platformAddress, tokenSymbol);
} else {
return this.sweepEVM(tempWalletPrivateKey, merchantAddress, platformAddress, tokenSymbol);
}
}
private async sweepEVM(
tempWalletPrivateKey: string,
merchantAddress: string,
platformAddress: string,
tokenSymbol: string
) {
const tempWallet = new ethers.Wallet(tempWalletPrivateKey, this.provider);
const tokenAddress = STABLECOIN_ADDRESSES[this.network][tokenSymbol];
@@ -66,12 +103,10 @@ export class CryptoEngine {
const platformShare = (balance * 100n) / 10000n; // %1
const merchantShare = balance - platformShare; // %99
console.log(`[Sweep] Total: ${ethers.formatUnits(balance, 6)} ${tokenSymbol}`);
console.log(`[Sweep] Sending ${ethers.formatUnits(platformShare, 6)} to Platform...`);
console.log(`[Sweep] Sending ${ethers.formatUnits(merchantShare, 6)} to Merchant...`);
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...`);
// In reality, we'd send 2 transactions here.
// For the demo, we simulate success.
return {
success: true,
platformTx: '0x' + Math.random().toString(16).slice(2, 66),
@@ -79,6 +114,71 @@ export class CryptoEngine {
};
}
private async sweepSolana(
tempWalletPrivateKey: string,
merchantAddress: string,
platformAddress: string,
tokenSymbol: string
) {
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'
};
}
}
/**
* Verifies if a specific amount has arrived at the address.
*/
@@ -88,26 +188,50 @@ export class CryptoEngine {
error?: string;
}> {
try {
if (!tokenSymbol || tokenSymbol === 'ETH' || tokenSymbol === 'MATIC') {
const balance = await this.provider.getBalance(address);
const balanceInEth = ethers.formatEther(balance);
if (parseFloat(balanceInEth) >= parseFloat(expectedAmount)) {
return { success: true };
if (this.network === 'SOLANA') {
const pubKey = new PublicKey(address);
if (!tokenSymbol || tokenSymbol === 'SOL') {
const balance = await this.solConnection.getBalance(pubKey);
const balanceInSol = balance / LAMPORTS_PER_SOL;
if (balanceInSol >= parseFloat(expectedAmount)) {
return { success: true };
}
} else {
const tokenMint = new PublicKey(STABLECOIN_ADDRESSES['SOLANA'][tokenSymbol]);
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
}
}
} else {
// Check ERC20 balance (USDT/USDC)
const network = 'POLYGON'; // Default for demo
const tokenAddress = STABLECOIN_ADDRESSES[network][tokenSymbol];
if (!tokenAddress) throw new Error("Unsupported token");
if (!tokenSymbol || tokenSymbol === 'ETH' || tokenSymbol === 'MATIC') {
const balance = await this.provider.getBalance(address);
const balanceInEth = ethers.formatEther(balance);
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 balance = await contract.balanceOf(address);
const decimals = await contract.decimals();
const formattedBalance = ethers.formatUnits(balance, decimals);
const contract = new ethers.Contract(tokenAddress, 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 };
if (parseFloat(formattedBalance) >= parseFloat(expectedAmount)) {
return { success: true };
}
}
}