Files
Pay2Gateway/lib/crypto-engine.ts

183 lines
7.1 KiB
TypeScript

import { ethers } from 'ethers';
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';
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 transfer(address to, uint256 value) public returns (bool)"
];
export class CryptoEngine {
private provider!: ethers.JsonRpcProvider;
private solConnection!: Connection;
private network: string;
private config: any;
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(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.
*/
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
};
}
}
/**
* Sweeps funds from temporary wallet to Platform and Merchant
*/
async sweepFunds(
tempWalletPrivateKey: string,
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 tokenConfig = this.getTokenConfig(tokenSymbol);
if (!tokenConfig) throw new Error(`Unsupported token ${tokenSymbol} on ${this.network}`);
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),
merchantTx: '0x' + Math.random().toString(16).slice(2, 66)
};
}
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,
platformAddress: string,
tokenSymbol: string
) {
const tempKeypair = Keypair.fromSecretKey(bs58.decode(tempWalletPrivateKey));
const pubKey = tempKeypair.publicKey;
// 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)
};
}
/**
* Verifies if a specific amount has arrived at the address.
*/
async verifyPayment(address: string, expectedAmount: string, tokenSymbol?: string): Promise<{
success: boolean;
txHash?: string;
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 (tokenConfig.address === 'NATIVE') {
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(tokenConfig.address);
const ata = await getAssociatedTokenAddress(tokenMint, pubKey);
try {
const accountInfo = await getAccount(this.solConnection, ata);
const balance = Number(accountInfo.amount) / Math.pow(10, tokenConfig.decimals);
if (balance >= parseFloat(expectedAmount)) return { success: true };
} catch (e) {}
}
} else {
if (tokenConfig.address === 'NATIVE') {
const balance = await this.provider.getBalance(address);
const balanceInEth = ethers.formatEther(balance);
if (parseFloat(balanceInEth) >= parseFloat(expectedAmount)) return { success: true };
} else {
const contract = new ethers.Contract(tokenConfig.address, ERC20_ABI, this.provider);
const balance = await contract.balanceOf(address);
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 };
}
}
}