import './App.css'; import { read, utils } from "xlsx"; import { ethers, formatEther, parseEther } from "ethers"; import { useCallback, useEffect, useRef, useState } from 'react'; import { tokenAbi } from "./tokenAbi"; const contractAddress = "0x1ebA64fDe3BF54545c86B9e3bB40c72f50f8D012"; function App() { const abi = [{"inputs": [{"internalType": "address","name": "token","type": "address"},{"internalType": "address[]","name": "addresses","type": "address[]"},{"internalType": "uint256[]","name": "amounts","type": "uint256[]"},{"internalType": "bool","name": "isToken","type": "bool"}],"name": "batchTransferFrom","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "payable","type": "function"}] const [from, setFrom] = useState(""); const [toArray, setToArray] = useState([]); const [amountArray, setAmountArray] = useState([]); const isToken = true; const [provider, setProvider] = useState(); const [text, setText] = useState("파일 업로드") const tokenRef = useRef(null); const handleFile = (file) => { if (!file) return; setText(file.name); const reader = new FileReader(); reader.onload = (evt) => { try { const data = new Uint8Array(evt.target.result); const workbook = read(data, { type: 'array' }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const jsonData = utils.sheet_to_json(worksheet, {raw:true}); const filteredData = jsonData.filter(row => { return ( typeof row.address === "string" && !row.address.toUpperCase().includes("EX") && !String(row.amount).toUpperCase().includes("EX") ); }); const addresses = filteredData.map((row) => row.address); const amount = filteredData.map((row) => { // 소수점 2자리까지 반올림 처리 const rounded = Math.round(Number(row.amount) * 100) / 100; return parseEther(rounded.toFixed(2)); }); setToArray(addresses); setAmountArray(amount); } catch (error) { alert(error) } }; reader.readAsArrayBuffer(file); }; const handleDrop = (e) => { e.preventDefault(); const file = e.dataTransfer.files[0]; handleFile(file); }; const handleFileChange = (e) => { const file = e.target.files[0]; handleFile(file); }; // const handleCheckboxChange = (e) => { // setIsToken(e.target.checked); // }; const handleDragOver = (e) => { e.preventDefault(); }; useEffect(()=>{ if (!window.ethereum) return alert("MetaMask not installed"); setProvider(new ethers.BrowserProvider(window.ethereum)) },[]) const walletProvider = useCallback(async ()=>{ try { await window.ethereum.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x38' }] //0x38 }); } catch (err) { await window.ethereum.request({ method: 'wallet_addEthereumChain', params: [{ chainId: '0x38', //0x38 chainName: 'BNB Smart Chain Mainnet', rpcUrls: ['https://binance.llamarpc.com'], nativeCurrency: { name: 'BNB', symbol: 'BNB', decimals: 18 }, blockExplorerUrls: ['https://bscscan.com/'] }] }); } await provider.send("eth_requestAccounts", []); window.ethereum.on("accountsChanged", (accounts) => { if (accounts.length > 0) { setFrom(accounts[0]); } else { setFrom(null); } }); const signer = provider.getSigner(); const senderAddress = await signer; setFrom(senderAddress.address) },[provider]) useEffect(()=>{ if(provider){ walletProvider() } return () => { if (window.ethereum?.removeListener) { window.ethereum.removeListener("accountsChanged", () => {}); window.ethereum.removeListener("chainChanged", () => {}); } }; },[provider,walletProvider]) const sendToken = async () => { const tokenAddress = tokenRef.current?.value; let totalAmount = 0; amountArray.map((row)=> totalAmount += parseFloat(formatEther(row))); const signer = await provider.getSigner(); const contract = new ethers.Contract(contractAddress, abi, signer); const batchSize = 200; const loopCount = Math.ceil(toArray.length / batchSize); let status; if(toArray.length !== 0 && amountArray.length !== 0 && toArray.length === amountArray.length) { try { const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, signer); let balance = isToken ? await tokenContract.balanceOf(from) : await provider.getBalance(from); console.log(balance, parseEther(totalAmount.toString())) if(balance >= parseEther(totalAmount.toString())){ if(isToken) { //token approve const tx = await tokenContract.approve( contractAddress, parseEther(totalAmount.toString()) ) await provider.waitForTransaction(tx.hash); } } else { throw new Error("잔액부족") } } catch (error) { alert(error) } } else { alert("양식이 맞지 않습니다.") } for (let i = 0; i < loopCount; i++) { const start = i * batchSize; const end = start + batchSize; const chunkAddresses = toArray.slice(start, end); const chunkAmounts = amountArray.slice(start, end); const chunkTotal = chunkAmounts.reduce((acc, amt) => acc + parseFloat(formatEther(amt)), 0); console.log(tokenAddress, chunkAddresses, chunkAmounts, isToken,chunkTotal) //multisender try { const tx = await contract.batchTransferFrom( tokenAddress, chunkAddresses, chunkAmounts, isToken, {value : isToken ? 0 : parseEther(chunkTotal.toString())} ); status = await provider.waitForTransaction(tx.hash); } catch (error) { alert(error) } } if(status){ setText("파일 업로드") setToArray([]); setAmountArray([]); } }; return (