Migrate to local PG & Update Solana Checkout
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user