const MultiSenderWidget = ({ context, NearAPI }) => { // Initialize the state if (!state) { State.init({ tokenContract: "", csvInput: "", isConnected: context.accountId ? true : false, // Check if user is connected errorMessage: "", showModal: false, transactions: [], tokens: [], accountId: context.accountId || "", // Initialize accountId from context }); if (context.accountId) { fetchAndLogTokens(context.accountId); } } const fetchAndLogTokens = async (accountId) => { try { console.log("Fetching tokens for account:", accountId); // Fetch tokens for the given account const response = await fetch( `https://api.near-indexer.com/account/${accountId}/tokens` ); const tokenList = await response.json(); console.log("Available Tokens:", tokenList); State.update({ tokens: tokenList }); } catch (error) { console.error("Error fetching tokens:", error); } }; const parseCSVtoJSON = (csvInput) => { const rows = csvInput .split(";") .map((row) => row.trim()) .filter((row) => row.length > 0); const formattedList = []; let errorMessage = ""; rows.forEach((row, index) => { const [recipient, amount] = row.split(",").map((item) => item.trim()); if (!recipient || !amount || isNaN(amount)) { errorMessage = `Invalid input in row ${index + 1}: ${row}`; return; } formattedList.push({ recipient, amount }); }); if (errorMessage) { State.update({ errorMessage }); return false; } else { return formattedList; } }; const handleMultiSend = () => { const csvInput = state.csvInput; const tokenContract = state.tokenContract; console.log("Button Clicked, State:", state); if (!csvInput) { State.update({ errorMessage: "CSV Input is empty or undefined" }); return; } const transactions = parseCSVtoJSON(csvInput); if (!transactions) { console.log("Validation failed:", state.errorMessage); return; } console.log("Parsed Transactions:", transactions); State.update({ transactions, showModal: true }); }; const confirmSendTokens = () => { const tokenContract = state.tokenContract; const transactions = state.transactions; // Prepare batch transactions const batchTransactions = transactions.map((tx) => ({ contractName: tokenContract, methodName: "ft_transfer", args: { receiver_id: tx.recipient, amount: tx.amount, }, gas: "300000000000000", // gas limit deposit: "1", // deposit, in yoctoNEAR })); Near.call(batchTransactions) .then(() => { console.log("Tokens sent successfully"); State.update({ errorMessage: "Tokens sent successfully!", showModal: false, }); }) .catch((error) => { console.error("Error during multisend:", error); State.update({ errorMessage: `Error during multisend: ${error.message}`, showModal: false, }); }); }; const handleCSVInput = (e) => { const csvInput = e.target.value; console.log("Updating CSV Input:", csvInput); State.update({ csvInput }); }; const handleTokenContractInput = (e) => { const tokenContract = e.target.value; console.log("Updating Token Contract:", tokenContract); State.update({ tokenContract }); }; const closeModal = () => { console.log("Closing Modal"); State.update({ showModal: false }); }; return ( <div> <h1>Bulk ft_transfer</h1> <div> <h6>Sender Account ID</h6> <p>{state.accountId}</p> </div> <div> <label>Token Contract:</label> <input type="text" value={state.tokenContract} onChange={handleTokenContractInput} placeholder="Enter token contract address" /> </div> <br /> <div> <label>CSV Input:</label> <br /> <textarea placeholder="wallet1.near, amount1; wallet2.near, amount2; ..." value={state.csvInput} onChange={handleCSVInput} /> </div> <br /> <div> <button onClick={handleMultiSend}>Send Tokens</button> </div> <br /> {state.errorMessage && ( <div style={{ color: "red" }}> <strong>Error:</strong> {state.errorMessage} </div> )} <div> <h3>CSV Template:</h3> <br /> <p>wallet1.near, amount1; wallet2.near, amount2; ...</p> <br /> <h5> *** Please make sure to add the tokens decimals *** <br />* ex: add 18 `0` at the end of the amount to send. * </h5> <h6> ** check the token contract for the correct amount to add for that token. ** </h6> </div> <div> <h3>How to Use the NEAR Social MultiSender App</h3> <p>Overview</p> <p> The NEAR Social MultiSender app is designed to facilitate the mass distribution of tokens to multiple NEAR accounts in one batch. It offers a streamlined process for specifying recipients and amounts through a CSV input, providing a user-friendly interface to manage and execute multiple token transfers efficiently. </p> <p>Steps to Use the App</p> <ol> <li> <strong>Connect Your Wallet:</strong> <p> Ensure you are logged into your NEAR wallet. The app will automatically detect and display your NEAR account ID. </p> </li> <li> <strong>Enter Token Contract:</strong> <p> Manually enter the token contract address in the provided input field. This is the address of the token you intend to send. </p> </li> <li> <strong>Prepare CSV Input:</strong> <p> In the CSV input field, list the recipient wallet addresses and the corresponding token amounts. The format should be: wallet1.near, amount1; wallet2.near, amount2; ... Ensure that each wallet and amount pair is separated by a comma and each pair is separated by a semicolon. </p> </li> <li> <strong>Send Tokens:</strong> <p> Click the "Send Tokens" button to initiate the process. This will open a confirmation modal with the list of transactions. </p> </li> <li> <strong>Confirm Transactions:</strong> <p> Review the transactions in the modal to ensure all details are correct. Click "Confirm" to execute the transactions or "Cancel" to abort. </p> </li> <li> <strong>Transaction Status:</strong> <p> After confirmation, the app will process the transactions. A success message will be displayed upon successful execution, or an error message if something goes wrong. </p> </li> </ol> <p>Example</p> <ol> <li> <strong>Token Contract Input:</strong> <p>enter token contract address here</p> </li> <li> <strong>CSV Input Example:</strong> <p>alice.near, 10; bob.near, 15; carol.near, 20;</p> </li> <li> <strong>Confirmation Modal:</strong> <p> The modal will display: alice.near: 10 bob.near: 15 carol.near: 20 <br /> You can then confirm or cancel the transactions. </p> <br /> </li> </ol> </div> {state.showModal && ( <div style={fixedModalStyle}> <div style={fixedModalContentStyle}> <h2>Confirm Transactions</h2> <ul> {state.transactions.map((tx, index) => ( <li key={index}> {tx.recipient}: {tx.amount} </li> ))} </ul> <div style={buttonContainerStyle}> <button style={confirmButtonStyle} onClick={confirmSendTokens}> Confirm </button> <button style={cancelButtonStyle} onClick={closeModal}> Cancel </button> </div> </div> </div> )} </div> ); }; const fixedModalStyle = { position: "fixed", top: "20%", left: "50%", transform: "translate(-50%, -20%)", backgroundColor: "white", padding: "20px", boxShadow: "0 0 10px rgba(0, 0, 0, 0.3)", zIndex: 1000, }; const fixedModalContentStyle = { display: "flex", flexDirection: "column", alignItems: "center", }; const buttonContainerStyle = { display: "flex", justifyContent: "space-between", width: "100%", }; const confirmButtonStyle = { backgroundColor: "green", color: "white", padding: "10px 20px", border: "none", borderRadius: "5px", cursor: "pointer", marginRight: "10px", }; const cancelButtonStyle = { backgroundColor: "red", color: "white", padding: "10px 20px", border: "none", borderRadius: "5px", cursor: "pointer", }; return <MultiSenderWidget context={context} NearAPI={NearAPI} />;