diff --git a/rair-front/src/components/adminViews/BatchERC20Transfer.tsx b/rair-front/src/components/adminViews/BatchERC20Transfer.tsx new file mode 100644 index 000000000..b99fdab1c --- /dev/null +++ b/rair-front/src/components/adminViews/BatchERC20Transfer.tsx @@ -0,0 +1,230 @@ +import { useCallback, useState } from 'react'; +import Dropzone from 'react-dropzone'; +import { useSelector } from 'react-redux'; +import { parseUnits } from 'ethers/lib/utils'; + +import { RootState } from '../../ducks'; +import { ColorStoreType } from '../../ducks/colors/colorStore.types'; +import { ContractsInitialType } from '../../ducks/contracts/contracts.types'; +import useSwal from '../../hooks/useSwal'; +import useWeb3Tx from '../../hooks/useWeb3Tx'; +import csvParser from '../../utils/csvParser'; +import InputField from '../common/InputField'; + +const structure = [ + { + label: 'Name', + defaultValue: '', + disabled: false, + type: 'text' + }, + { + label: 'Address', + defaultValue: '', + disabled: false, + type: 'text' + }, + { + label: 'Amount', + defaultValue: 0, + disabled: false, + type: 'number' + }, + { + label: 'Status', + defaultValue: 'Ready', + disabled: true, + type: 'text' + }, + { + label: 'TxHash', + defaultValue: '', + disabled: true, + type: 'text' + } +]; + +type RowData = { + [key: string]: string; +}; + +const BatchERC20Transfer = () => { + const [data, setData] = useState([]); + const { textColor, primaryColor, primaryButtonColor, secondaryButtonColor } = + useSelector((store) => store.colorStore); + const { erc777Instance } = useSelector( + (store) => store.contractStore + ); + const { web3TxHandler } = useWeb3Tx(); + const reactSwal = useSwal(); + const updateRowData = useCallback( + (key: string, index: number, value: string) => { + const aux = [...data]; + if (aux[index]) { + aux[index][key] = value; + } + setData(aux); + }, + [data] + ); + + const addRow = useCallback(() => { + const aux = [...data]; + const newData = {}; + structure.forEach((data) => { + newData[data.label] = data.defaultValue; + }); + aux.push(newData); + setData(aux); + }, [data]); + + const onCSVDrop = useCallback((file: File[]) => { + csvParser(file[0], (data) => { + setData( + data.map((item) => { + const { Name, Address, Amount } = item; + return { Name, Address, Amount, Status: 'Ready', TxHash: '' }; + }) + ); + }); + }, []); + + const transferProcess = useCallback(async () => { + if (!erc777Instance) { + reactSwal.fire('No ERC20 connected'); + return; + } + const aux = [...data]; + for await (const dataItem of aux) { + if (dataItem.Status !== 'Ready' || !dataItem.Amount) { + continue; + } + reactSwal.fire({ + icon: 'info', + title: 'Preparing transaction', + html: `Sending ${dataItem.Amount} tokens to ${dataItem.Name} (${dataItem.Address})`, + showConfirmButton: false + }); + const txHash = await web3TxHandler(erc777Instance, 'transfer', [ + dataItem.Address, + parseUnits(dataItem.Amount, 18) + ]); + if (txHash) { + dataItem.TxHash = txHash; + dataItem.Status = 'Complete'; + await reactSwal.fire({ + icon: 'success', + title: 'Transaction complete', + html: `Transaction id: ${txHash}` + }); + } else { + await reactSwal.fire({ + icon: 'error', + title: 'An error has ocurred', + html: 'The process will stop' + }); + break; + } + } + setData(aux); + }, [erc777Instance, data, web3TxHandler, reactSwal]); + + const deleteRow = useCallback( + (index: number) => { + const aux = [...data]; + aux.splice(index, 1); + setData(aux); + }, + [data] + ); + + return ( +
+
ERC20 tool
+ + {({ getRootProps, getInputProps, isDragActive }) => ( +
+
+ +
+ {isDragActive ? ( + <>Drop the CSV file here ... + ) : ( + <>Drag and drop or click to upload the CSV file + )} +
+
+ )} +
+ + + + + {structure.map((data, index) => { + return ; + })} + + + + {data.map((data, index) => { + return ( + + + {Object.keys(data).map((key, keyIndex) => { + console.info(key, keyIndex, structure[keyIndex]); + return ( + + ); + })} + + ); + })} + +
+ + {data.label}
+ + + updateRowData(key, index, value)} + /> +
+
+
+ +
+
+ ); +}; + +export default BatchERC20Transfer; diff --git a/rair-front/src/components/adminViews/ImportExternalContracts.tsx b/rair-front/src/components/adminViews/ImportExternalContracts.tsx index c7b77546e..b2303f7e9 100644 --- a/rair-front/src/components/adminViews/ImportExternalContracts.tsx +++ b/rair-front/src/components/adminViews/ImportExternalContracts.tsx @@ -4,6 +4,8 @@ import axios from 'axios'; import { utils } from 'ethers'; import { isAddress } from 'ethers/lib/utils'; +import BatchERC20Transfer from './BatchERC20Transfer'; + import { diamondFactoryAbi } from '../../contracts'; import { RootState } from '../../ducks'; import { ColorStoreType } from '../../ducks/colors/colorStore.types'; @@ -301,6 +303,7 @@ const ImportExternalContract = () => { })} +
); }; diff --git a/rair-front/src/hooks/useWeb3Tx.ts b/rair-front/src/hooks/useWeb3Tx.ts index 825c214aa..fb5860fe0 100644 --- a/rair-front/src/hooks/useWeb3Tx.ts +++ b/rair-front/src/hooks/useWeb3Tx.ts @@ -152,7 +152,7 @@ const useWeb3Tx = () => { if (transactionReceipt && transactionReceipt.blockNumber) { handleReceipt(transactionReceipt.transactionHash, options?.callback); } - return true; + return transactionReceipt.transactionHash; } return paramsValidation; }, diff --git a/rair-node/bin/api/auth/auth.Service.js b/rair-node/bin/api/auth/auth.Service.js index 073de96c3..5e96acfda 100644 --- a/rair-node/bin/api/auth/auth.Service.js +++ b/rair-node/bin/api/auth/auth.Service.js @@ -66,16 +66,20 @@ module.exports = { return next(new AppError('User not found.', 404)); } - // Check OFAC blocklist - // Read the file content - const content = fs.readFileSync( - './bin/integrations/ofac/sanctioned_addresses_ETH.json', - 'utf8', - ); - const ofacBlocklist = JSON.parse(content).map((address) => address.toLowerCase()); - if (ofacBlocklist.includes(ethAddress)) { - await User.findByIdAndUpdate(userData._id, { $set: { blocked: true } }); - userData.blocked = true; + try { + // Check OFAC blocklist + // Read the file content + const content = fs.readFileSync( + './bin/integrations/ofac/sanctioned_addresses_ETH.json', + 'utf8', + ); + const ofacBlocklist = JSON.parse(content).map((address) => address.toLowerCase()); + if (ofacBlocklist.includes(ethAddress)) { + await User.findByIdAndUpdate(userData._id, { $set: { blocked: true } }); + userData.blocked = true; + } + } catch (error) { + log.error("Cannot read OFAC list"); } if (userData.blocked) { log.error(`Blocked user tried to login: ${ethAddress}`); diff --git a/rair-node/ofac-sanctioned-digital-currency-addresses/generate-address-list.py b/rair-node/ofac-sanctioned-digital-currency-addresses/generate-address-list.py index d1f4c2001..518b1c33b 100755 --- a/rair-node/ofac-sanctioned-digital-currency-addresses/generate-address-list.py +++ b/rair-node/ofac-sanctioned-digital-currency-addresses/generate-address-list.py @@ -6,7 +6,7 @@ import json FEATURE_TYPE_TEXT = "Digital Currency Address - " -NAMESPACE = {'sdn': 'http://www.un.org/sanctions/1.0'} +NAMESPACE = {'sdn': 'https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/ADVANCED_XML'} # List of assets that have been sanctioned by the OFAC. # Possible assets be seen by grepping the sdn_advanced.xml file for "Digital Currency Address".