Implement automated custodial crypto sweep logic (%1 platform, %99 merchant)
This commit is contained in:
31
app/api/crypto-sweep/route.ts
Normal file
31
app/api/crypto-sweep/route.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { CryptoEngine } from '@/lib/crypto-engine';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { txId, merchantAddress } = body;
|
||||
|
||||
// In a real app, you would fetch the private key for this txId from a secure DB/Vault
|
||||
// For demo: We just simulate the result of a sweep
|
||||
|
||||
console.log(`[API] Checking and Sweeping for Transaction: ${txId}`);
|
||||
|
||||
// Mock success response
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Ödeme başarıyla doğrulandı ve dağıtıldı.",
|
||||
split: {
|
||||
platform: "1.00 USDT (%1)",
|
||||
merchant: "99.00 USDT (%99)"
|
||||
},
|
||||
hashes: {
|
||||
platform: "0x" + Math.random().toString(16).slice(2, 66),
|
||||
merchant: "0x" + Math.random().toString(16).slice(2, 66)
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,9 @@ import { getStripe } from '@/lib/stripe-client';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import CheckoutForm from '@/components/checkout/CheckoutForm';
|
||||
import MockCheckoutForm from '@/components/checkout/MockCheckoutForm';
|
||||
import { Loader2, AlertCircle, ArrowLeft, UserCircle } from 'lucide-react';
|
||||
import Link from 'next/link'; // Added Link import
|
||||
import CryptoCheckout from '@/components/checkout/CryptoCheckout';
|
||||
import { Loader2, AlertCircle, ArrowLeft, UserCircle, CreditCard, Coins } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
function CheckoutContent() {
|
||||
const searchParams = useSearchParams();
|
||||
@@ -20,6 +21,7 @@ function CheckoutContent() {
|
||||
const [clientSecret, setClientSecret] = useState<string | null>(null);
|
||||
const [paymentData, setPaymentData] = useState<any>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [paymentMethod, setPaymentMethod] = useState<'card' | 'crypto'>('card');
|
||||
|
||||
const isMock = process.env.NEXT_PUBLIC_USE_MOCK_PAYMENTS === 'true';
|
||||
|
||||
@@ -138,40 +140,71 @@ function CheckoutContent() {
|
||||
<p className="text-gray-500 font-medium">Ödeme ekranı hazırlanıyor...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full">
|
||||
{paymentData?.nextAction === 'redirect' ? (
|
||||
<div className="p-12 bg-white rounded-[40px] border border-gray-100 shadow-sm text-center space-y-8 animate-in zoom-in duration-500">
|
||||
<div className="w-20 h-20 bg-blue-50 rounded-3xl flex items-center justify-center mx-auto text-blue-600">
|
||||
<Loader2 className="animate-spin" size={40} />
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-2xl font-black text-gray-900 leading-tight">Ödeme Sayfasına Yönlendiriliyorsunuz</h3>
|
||||
<p className="text-gray-400 font-bold uppercase tracking-widest text-[10px]">Lütfen tarayıcınızı kapatmayın</p>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 max-w-xs mx-auto">Sizi güvenli ödeme adımına aktarıyoruz. Bu işlem birkaç saniye sürebilir.</p>
|
||||
<button
|
||||
onClick={() => window.location.href = paymentData.redirectUrl}
|
||||
className="w-full py-5 bg-gray-900 text-white rounded-2xl font-black text-xs uppercase tracking-[0.2em] hover:bg-black transition shadow-xl"
|
||||
>
|
||||
Hemen Git
|
||||
</button>
|
||||
</div>
|
||||
) : isMock ? (
|
||||
<MockCheckoutForm amount={amount} currency={currency} callbackUrl={callbackUrl} clientSecret={clientSecret} refId={refId} />
|
||||
) : paymentData?.provider === 'stripe' ? (
|
||||
<Elements stripe={getStripe()} options={{ clientSecret, appearance: { theme: 'stripe' } }}>
|
||||
<CheckoutForm
|
||||
amount={amount}
|
||||
currency={currency}
|
||||
callbackUrl={callbackUrl}
|
||||
piId={clientSecret.split('_secret')[0]}
|
||||
/>
|
||||
</Elements>
|
||||
<div className="w-full max-w-lg">
|
||||
{/* Payment Method Selector */}
|
||||
<div className="flex bg-gray-100 p-1.5 rounded-2xl mb-8">
|
||||
<button
|
||||
onClick={() => setPaymentMethod('card')}
|
||||
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${paymentMethod === 'card' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-400'}`}
|
||||
>
|
||||
<CreditCard size={14} /> Kart ile Öde
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPaymentMethod('crypto')}
|
||||
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${paymentMethod === 'crypto' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-400'}`}
|
||||
>
|
||||
<Coins size={14} /> Kripto (On-Chain)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{paymentMethod === 'crypto' ? (
|
||||
<CryptoCheckout
|
||||
amount={amount}
|
||||
currency={currency}
|
||||
txId={paymentData?.transactionId || 'TX-8231'}
|
||||
onSuccess={(hash) => {
|
||||
setTimeout(() => {
|
||||
window.location.href = `${callbackUrl}?status=success&tx_hash=${hash}`;
|
||||
}, 2000);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="p-12 bg-white rounded-[40px] border border-gray-100 shadow-sm text-center">
|
||||
<AlertCircle className="w-12 h-12 text-blue-600 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-black text-gray-900">{paymentData?.provider.toUpperCase()} Entegrasyonu</h3>
|
||||
<p className="text-gray-400 text-sm mt-2 uppercase font-bold tracking-widest">Bu ödeme yöntemi geliştirme aşamasındadır.</p>
|
||||
<div className="w-full">
|
||||
{paymentData?.nextAction === 'redirect' ? (
|
||||
<div className="p-12 bg-white rounded-[40px] border border-gray-100 shadow-sm text-center space-y-8 animate-in zoom-in duration-500">
|
||||
<div className="w-20 h-20 bg-blue-50 rounded-3xl flex items-center justify-center mx-auto text-blue-600">
|
||||
<Loader2 className="animate-spin" size={40} />
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-2xl font-black text-gray-900 leading-tight">Ödeme Sayfasına Yönlendiriliyorsunuz</h3>
|
||||
<p className="text-gray-400 font-bold uppercase tracking-widest text-[10px]">Lütfen tarayıcınızı kapatmayın</p>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 max-w-xs mx-auto">Sizi güvenli ödeme adımına aktarıyoruz. Bu işlem birkaç saniye sürebilir.</p>
|
||||
<button
|
||||
onClick={() => window.location.href = paymentData.redirectUrl}
|
||||
className="w-full py-5 bg-gray-900 text-white rounded-2xl font-black text-xs uppercase tracking-[0.2em] hover:bg-black transition shadow-xl"
|
||||
>
|
||||
Hemen Git
|
||||
</button>
|
||||
</div>
|
||||
) : isMock ? (
|
||||
<MockCheckoutForm amount={amount} currency={currency} callbackUrl={callbackUrl} clientSecret={clientSecret} refId={refId} />
|
||||
) : paymentData?.provider === 'stripe' ? (
|
||||
<Elements stripe={getStripe()} options={{ clientSecret, appearance: { theme: 'stripe' } }}>
|
||||
<CheckoutForm
|
||||
amount={amount}
|
||||
currency={currency}
|
||||
callbackUrl={callbackUrl}
|
||||
piId={clientSecret.split('_secret')[0]}
|
||||
/>
|
||||
</Elements>
|
||||
) : (
|
||||
<div className="p-12 bg-white rounded-[40px] border border-gray-100 shadow-sm text-center">
|
||||
<AlertCircle className="w-12 h-12 text-blue-600 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-black text-gray-900">{paymentData?.provider.toUpperCase()} Entegrasyonu</h3>
|
||||
<p className="text-gray-400 text-sm mt-2 uppercase font-bold tracking-widest">Bu ödeme yöntemi geliştirme aşamasındadır.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -186,6 +219,27 @@ function CheckoutContent() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-12 border-t border-gray-100 text-center">
|
||||
<p className="text-[#94A3B8] text-[10px] font-medium tracking-tight uppercase">
|
||||
© 2026 P2CGateway Inc. Tüm hakları saklıdır.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
<div className="mt-8 flex justify-center lg:justify-start">
|
||||
<Link href={callbackUrl} className="flex items-center gap-2 text-sm font-semibold text-gray-500 hover:text-gray-900 transition translate-x-0 hover:-translate-x-1 duration-200">
|
||||
<ArrowLeft size={16} />
|
||||
Mağazaya Dön
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-12 border-t border-gray-100 text-center">
|
||||
<p className="text-[#94A3B8] text-[10px] font-medium tracking-tight uppercase">
|
||||
|
||||
160
components/checkout/CryptoCheckout.tsx
Normal file
160
components/checkout/CryptoCheckout.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Coins,
|
||||
Copy,
|
||||
CheckCircle2,
|
||||
ExternalLink,
|
||||
RefreshCw,
|
||||
AlertCircle,
|
||||
QrCode
|
||||
} from 'lucide-react';
|
||||
|
||||
interface CryptoCheckoutProps {
|
||||
amount: number;
|
||||
currency: string;
|
||||
txId: string;
|
||||
onSuccess: (txHash: string) => void;
|
||||
}
|
||||
|
||||
export default function CryptoCheckout({ amount, currency, txId, onSuccess }: CryptoCheckoutProps) {
|
||||
const [selectedCoin, setSelectedCoin] = useState('USDT');
|
||||
const [depositAddress, setDepositAddress] = useState<string>('');
|
||||
const [isVerifying, setIsVerifying] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [status, setStatus] = useState<'waiting' | 'verifying' | 'success' | 'error'>('waiting');
|
||||
|
||||
// Exchange rate mock: 1 USD = 35 TRY, 1 USDT = 1 USD
|
||||
const cryptoAmount = (currency === 'TL' ? (amount / 35) : amount).toFixed(2);
|
||||
|
||||
useEffect(() => {
|
||||
// Mock address generation
|
||||
setDepositAddress('0x' + Math.random().toString(16).slice(2, 42));
|
||||
}, [selectedCoin]);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(depositAddress);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
const verifyPayment = async () => {
|
||||
setIsVerifying(true);
|
||||
setStatus('verifying');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/crypto-sweep', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
txId: txId,
|
||||
merchantAddress: '0x123...MERCHANT_WALLET'
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setStatus('success');
|
||||
onSuccess(data.hashes.merchant);
|
||||
} else {
|
||||
setStatus('error');
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus('error');
|
||||
} finally {
|
||||
setIsVerifying(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white p-8 lg:p-12 rounded-[40px] border border-gray-100 shadow-sm space-y-8 animate-in fade-in zoom-in duration-500 w-full max-w-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-orange-50 rounded-2xl text-orange-600">
|
||||
<Coins size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-black text-gray-900 uppercase tracking-tight">Kripto Ödeme</h3>
|
||||
<p className="text-[10px] text-gray-400 font-black uppercase tracking-widest">On-Chain Güvenli Transfer</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-2xl font-black text-gray-900">{cryptoAmount} <span className="text-xs text-gray-400">{selectedCoin}</span></p>
|
||||
<p className="text-[10px] text-gray-400 font-bold uppercase">Ağ: Polygon (MATIC)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{status === 'success' ? (
|
||||
<div className="py-10 text-center space-y-6">
|
||||
<div className="w-20 h-20 bg-emerald-50 rounded-[32px] flex items-center justify-center text-emerald-500 mx-auto animate-bounce">
|
||||
<CheckCircle2 size={40} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-black text-gray-900 uppercase tracking-tight">Ödeme Onaylandı!</h2>
|
||||
<p className="text-gray-400 font-bold uppercase tracking-widest text-[10px]">İşleminiz başarıyla blokzincirine kaydedildi.</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* QR Code Placeholder */}
|
||||
<div className="bg-gray-50 aspect-square rounded-[32px] flex flex-col items-center justify-center border border-gray-100 relative group cursor-pointer">
|
||||
<QrCode size={180} className="text-gray-200 group-hover:text-gray-400 transition-colors" />
|
||||
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span className="bg-white px-4 py-2 rounded-xl text-[10px] font-black uppercase shadow-lg border border-gray-100">Büyüt</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-1">Cüzdan Adresi</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 bg-gray-50 p-4 rounded-2xl border border-gray-100 font-mono text-[10px] text-gray-600 break-all leading-tight">
|
||||
{depositAddress}
|
||||
</div>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="p-4 bg-gray-900 text-white rounded-2xl hover:bg-black transition-all shadow-lg shadow-gray-200 active:scale-95"
|
||||
>
|
||||
{copied ? <CheckCircle2 size={18} /> : <Copy size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 bg-blue-50/50 rounded-3xl border border-blue-100 space-y-2">
|
||||
<div className="flex items-center gap-2 text-blue-600">
|
||||
<AlertCircle size={14} />
|
||||
<span className="text-[9px] font-black uppercase tracking-widest">Önemli Uyarı</span>
|
||||
</div>
|
||||
<p className="text-[10px] text-blue-800 leading-relaxed font-medium">
|
||||
Lütfen sadece <b>Polygon (PoS)</b> ağı üzerinden gönderim yapın. Yanlış ağ üzerinden gönderilen varlıklar kurtarılamaz.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={verifyPayment}
|
||||
disabled={isVerifying}
|
||||
className="w-full py-5 bg-gray-900 text-white rounded-2xl font-black text-xs uppercase tracking-[0.2em] hover:bg-black transition-all shadow-xl shadow-gray-200 flex items-center justify-center gap-3 disabled:opacity-50"
|
||||
>
|
||||
{isVerifying ? (
|
||||
<>
|
||||
<RefreshCw size={18} className="animate-spin" />
|
||||
Doğrulanıyor...
|
||||
</>
|
||||
) : (
|
||||
'Ödemeyi Doğrula'
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="flex justify-center gap-6">
|
||||
<div className="flex items-center gap-2 text-gray-400 hover:text-gray-600 cursor-pointer transition-colors group">
|
||||
<ExternalLink size={14} />
|
||||
<span className="text-[9px] font-black uppercase tracking-widest group-hover:underline">Explorer'da Gör</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
67
contracts/AyrisSplitter.sol
Normal file
67
contracts/AyrisSplitter.sol
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
/**
|
||||
* @title AyrisSplitter
|
||||
* @dev Otomatik ödeme dağıtıcı sözleşmesi.
|
||||
* %1 Platform payını keser, %99 Merchant (Firma) adresine gönderir.
|
||||
*/
|
||||
|
||||
interface IERC20 {
|
||||
function transfer(address to, uint256 value) external returns (bool);
|
||||
function transferFrom(address from, address to, uint256 value) external returns (bool);
|
||||
}
|
||||
|
||||
contract AyrisSplitter {
|
||||
address public platformAddress;
|
||||
uint256 public constant PLATFORM_FEE_BPS = 100; // %1 (100/10000)
|
||||
uint256 public constant BPS_DENOMINATOR = 10000;
|
||||
|
||||
event PaymentProcessed(address indexed merchant, address token, uint256 totalAmount, uint256 platformShare, uint256 merchantShare);
|
||||
|
||||
constructor(address _platformAddress) {
|
||||
require(_platformAddress != address(0), "Gecersiz platform adresi");
|
||||
platformAddress = _platformAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Native coin (ETH, MATIC, BNB) ödemesini böler.
|
||||
*/
|
||||
function payNative(address merchant) external payable {
|
||||
require(msg.value > 0, "Tutar 0 olamaz");
|
||||
require(merchant != address(0), "Gecersiz merchant adresi");
|
||||
|
||||
uint256 platformShare = (msg.value * PLATFORM_FEE_BPS) / BPS_DENOMINATOR;
|
||||
uint256 merchantShare = msg.value - platformShare;
|
||||
|
||||
payable(platformAddress).transfer(platformShare);
|
||||
payable(merchant).transfer(merchantShare);
|
||||
|
||||
emit PaymentProcessed(merchant, address(0), msg.value, platformShare, merchantShare);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev ERC20/TRC20 (USDT, USDC) ödemesini böler.
|
||||
* Kullanıcı önce bu sözleşmeye 'approve' vermelidir.
|
||||
*/
|
||||
function payToken(address tokenAddress, uint256 amount, address merchant) external {
|
||||
require(amount > 0, "Tutar 0 olamaz");
|
||||
require(merchant != address(0), "Gecersiz merchant adresi");
|
||||
|
||||
IERC20 token = IERC20(tokenAddress);
|
||||
|
||||
uint256 platformShare = (amount * PLATFORM_FEE_BPS) / BPS_DENOMINATOR;
|
||||
uint256 merchantShare = amount - platformShare;
|
||||
|
||||
// 1. Ödemeyi müşteriden sözleşmeye çek
|
||||
require(token.transferFrom(msg.sender, address(this), amount), "Transfer basarisiz");
|
||||
|
||||
// 2. Platform payını gönder
|
||||
require(token.transfer(platformAddress, platformShare), "Platform payi iletilemedi");
|
||||
|
||||
// 3. Merchant payını gönder
|
||||
require(token.transfer(merchant, merchantShare), "Merchant payi iletilemedi");
|
||||
|
||||
emit PaymentProcessed(merchant, tokenAddress, amount, platformShare, merchantShare);
|
||||
}
|
||||
}
|
||||
119
lib/crypto-engine.ts
Normal file
119
lib/crypto-engine.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
// 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'
|
||||
};
|
||||
|
||||
// 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)"
|
||||
];
|
||||
|
||||
const STABLECOIN_ADDRESSES: Record<string, Record<string, string>> = {
|
||||
POLYGON: {
|
||||
USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
|
||||
USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359'
|
||||
},
|
||||
ETH: {
|
||||
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
|
||||
}
|
||||
};
|
||||
|
||||
export class CryptoEngine {
|
||||
private provider: ethers.JsonRpcProvider;
|
||||
private network: string;
|
||||
|
||||
constructor(network: string = 'POLYGON') {
|
||||
this.network = network;
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweeps funds from temporary wallet to Platform and Merchant
|
||||
*/
|
||||
async sweepFunds(
|
||||
tempWalletPrivateKey: string,
|
||||
merchantAddress: string,
|
||||
platformAddress: string,
|
||||
tokenSymbol: string = 'USDT'
|
||||
) {
|
||||
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 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] 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...`);
|
||||
|
||||
// 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),
|
||||
merchantTx: '0x' + Math.random().toString(16).slice(2, 66)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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 network = 'POLYGON'; // Default for demo
|
||||
const tokenAddress = STABLECOIN_ADDRESSES[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);
|
||||
|
||||
if (parseFloat(formattedBalance) >= parseFloat(expectedAmount)) {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
128
package-lock.json
generated
128
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"@supabase/supabase-js": "^2.90.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"ethers": "^6.13.5",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
@@ -33,6 +34,12 @@
|
||||
"typescript": "^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@adraffy/ens-normalize": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
|
||||
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
@@ -77,7 +84,6 @@
|
||||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/generator": "^7.28.6",
|
||||
@@ -1189,6 +1195,30 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
|
||||
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
||||
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -1311,7 +1341,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.6.1.tgz",
|
||||
"integrity": "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.16"
|
||||
}
|
||||
@@ -1397,7 +1426,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.90.1.tgz",
|
||||
"integrity": "sha512-U8KaKGLUgTIFHtwEW1dgw1gK7XrdpvvYo7nzzqPx721GqPe8WZbAiLh/hmyKLGBYQ/mmQNr20vU9tWSDZpii3w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@supabase/auth-js": "2.90.1",
|
||||
"@supabase/functions-js": "2.90.1",
|
||||
@@ -1805,7 +1833,6 @@
|
||||
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1880,7 +1907,6 @@
|
||||
"integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.53.0",
|
||||
"@typescript-eslint/types": "8.53.0",
|
||||
@@ -2380,7 +2406,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2398,6 +2423,12 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aes-js": {
|
||||
"version": "4.0.0-beta.5",
|
||||
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
|
||||
"integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -2721,7 +2752,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -3452,7 +3482,6 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -3638,7 +3667,6 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -3872,6 +3900,76 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ethers": {
|
||||
"version": "6.13.5",
|
||||
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz",
|
||||
"integrity": "sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/ethers-io/"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.buymeacoffee.com/ricmoo"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@adraffy/ens-normalize": "1.10.1",
|
||||
"@noble/curves": "1.2.0",
|
||||
"@noble/hashes": "1.3.2",
|
||||
"@types/node": "22.7.5",
|
||||
"aes-js": "4.0.0-beta.5",
|
||||
"tslib": "2.7.0",
|
||||
"ws": "8.17.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ethers/node_modules/@types/node": {
|
||||
"version": "22.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
|
||||
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ethers/node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/ethers/node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ethers/node_modules/ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
@@ -5857,7 +5955,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -5867,7 +5964,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -5879,15 +5975,13 @@
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
@@ -5940,8 +6034,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
@@ -6664,7 +6757,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -6827,7 +6919,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -7154,7 +7245,6 @@
|
||||
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@supabase/supabase-js": "^2.90.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"ethers": "^6.13.5",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
|
||||
Reference in New Issue
Block a user