diff --git a/.env b/.env new file mode 100644 index 0000000..a80fed0 --- /dev/null +++ b/.env @@ -0,0 +1,10 @@ +# Supabase Configuration +NEXT_PUBLIC_SUPABASE_URL=https://zhzupqzarlnymbkkbapk.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpoenVwcXphcmxueW1ia2tiYXBrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MjkyMjI0NDksImV4cCI6MjA0NDc5ODQ0OX0.vuw49RtIbCXBqNsQaLaEHIDIAhP_pe3zYBamgzQ7Ixk + +# Server Configuration +PORT=5000 +NEXT_PUBLIC_API_URL=http://localhost:5000 + +# File Size Limit (in bytes) +MAX_FILE_SIZE=10485760 \ No newline at end of file diff --git a/Test_1.sol b/Test_1.sol new file mode 100644 index 0000000..3228ff2 --- /dev/null +++ b/Test_1.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract VulnerableContract { + // State variables + uint256 private counter; + address public owner; + mapping(address => uint256) public balances; + bool private locked; + uint256[] public values; + + // Events + event Transfer(address indexed from, address indexed to, uint256 amount); + event Deposit(address indexed user, uint256 amount); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor() { + owner = msg.sender; + } + + function setOwner(address _newOwner) public { + owner = _newOwner; + } + + function withdraw(uint256 _amount) public { + require(balances[msg.sender] >= _amount, "Insufficient balance"); + (bool success, ) = msg.sender.call{value: _amount}(""); + require(success, "Transfer failed"); + balances[msg.sender] -= _amount; + } + + function increment() public { + counter++; + } + + function transfer(address _to, uint256 _amount) public { + payable(_to).transfer(_amount); + } + + function isOwner() public view returns (bool) { + return tx.origin == owner; + } + + function deposit() public payable { + balances[msg.sender] += msg.value; + } + + function getBalance() public view returns (uint256) { + return balances[msg.sender] + balances[msg.sender]; + } + + // Missing zero-address check + function approve(address _spender, uint256 _value) public { + allowed[msg.sender][_spender] = _value; + } + + uint256 public totalSupply; + mapping(address => mapping(address => uint256)) public allowed; + + function processValues() public { + for(uint i = 0; i < values.length; i++) { + values[i] = values[i] * 2; + } + } + + function updateConfig(uint256 _value) public { + require(msg.sender == owner, "Not owner"); + counter = _value; + } + + function getName() public pure returns (string memory) { + return "VulnerableContract"; + } + + uint256 private unusedVar; + + function setValues(uint256[] memory _values) public { + values = _values; + } + + function riskyOperation() public { + require(msg.sender != address(0), "Zero address"); + locked = true; + } + + function getBlockHash() public view returns (bytes32) { + return blockhash(block.number - 1); + } + + function inefficientLoop() public { + uint256[] memory tempArray = new uint256[](100); + for(uint i = 0; i < tempArray.length; i++) { + tempArray[i] = i; + } + } + + function destroyContract() public { + selfdestruct(payable(msg.sender)); + } + + function transferOwnership(address _newOwner) public { + require(msg.sender == owner, "Not owner"); + owner = _newOwner; + } + + function multiply(uint256 a, uint256 b) public pure returns (uint256) { + return a * b; + } + + function helperFunction() internal { + counter += 1; + } + + function unusedParams(uint256 _unused, string memory _alsounused) public { + counter += 1; + } + + function riskyCall(address _target) public { + _target.call(""); + } + + modifier onlyOwner { + require(msg.sender == owner, "Not owner"); + require(msg.sender == owner, "Not owner"); + _; + } + + receive() external payable {} + + function processArray(uint256[] memory _array) public { + for(uint i = 0; i < 10; i++) { + values.push(_array[i]); + } + } + + function useAssembly() public view returns (uint size) { + assembly { + size := extcodesize(caller()) + } + } + + string constant private CONSTANT_STRING = "This is a very long string that could be shortened"; +} \ No newline at end of file diff --git a/app/api/backend.mjs b/app/api/backend.mjs index 027e9e3..9483fae 100644 --- a/app/api/backend.mjs +++ b/app/api/backend.mjs @@ -1,87 +1,348 @@ -// Imports using ES6 modules +// backend.mjs import express from 'express'; -import mongoose from 'mongoose'; import cors from 'cors'; import path from 'path'; -import multer from 'multer'; // Use multer for file uploads -import ProjectNameModel from './schema.mjs'; // Import your Mongoose model +import multer from 'multer'; +import dotenv from 'dotenv'; +import { createClient } from '@supabase/supabase-js'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import fs from 'fs/promises'; +import { exec } from 'child_process'; +import { promisify } from 'util'; -const app = express(); // Initialize Express app -app.use(express.json()); // Parse JSON bodies +// Initialize environment variables +dotenv.config(); +// Setup async exec +const execAsync = promisify(exec); +// Setup directory paths +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const UPLOADS_DIR = path.join(__dirname, 'uploads'); -// Handle OPTIONS request (Preflight request) +// Create uploads directory if it doesn't exist +try { + await fs.mkdir(UPLOADS_DIR, { recursive: true }); +} catch (error) { + console.error('Error creating uploads directory:', error); +} + +// Initialize Express app +const app = express(); + +// Middleware +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// Initialize Supabase +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; +const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + +if (!supabaseUrl || !supabaseKey) { + console.error('Missing Supabase credentials'); + process.exit(1); +} +//connect to supabase +const supabase = createClient(supabaseUrl, supabaseKey); + +// CORS configuration const corsOptions = { - origin: 'https://blockscan-swin.vercel.app', // Your Vercel frontend URL + origin: ['http://localhost:3000', 'http://localhost:3001', 'https://blockscan-swin.vercel.app'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], - credentials: true, // Optional: allow cookies or authentication + credentials: true }; -app.use(cors(corsOptions)); -app.options('*', cors(corsOptions)); // For all routes -// MongoDB connection -const mongoUrl = "mongodb+srv://leviron:123456Bom@blockscan.gooou.mongodb.net/BlockScanDB?retryWrites=true&w=majority&appName=BlockScan"; -mongoose - .connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }) - .then(() => console.log('Connected to MongoDB')) - .catch((e) => console.error('Database connection error:', e)); +app.use(cors(corsOptions)); -// Configure Multer storage for file uploads +// Multer configuration const storage = multer.diskStorage({ - destination: function (_req, _file, cb) { - cb(null, 'uploads/'); // Save files to 'uploads' folder + destination: (_req, _file, cb) => { + cb(null, UPLOADS_DIR); }, - filename: function (_req, file, cb) { - cb(null, file.originalname); // Save files with original names + filename: (_req, file, cb) => { + const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1E9)}`; + cb(null, `${path.parse(file.originalname).name}-${uniqueSuffix}.sol`); + } +}); + +const upload = multer({ + storage, + fileFilter: (_req, file, cb) => { + if (!file.originalname.toLowerCase().endsWith('.sol')) { + return cb(new Error('Only .sol files are allowed')); + } + cb(null, true); }, + limits: { + fileSize: 10 * 1024 * 1024 // 10MB limit + } }); -const upload = multer({ storage }); -// Route: Contract File Upload -app.post('/contract-upload', upload.single('contractFile'), (req, res) => { - try { - const contractFile = req.file; // Access uploaded file - const fileExtension = path.extname(contractFile.originalname).toLowerCase(); // Check extension +// Health check +app.get('/health', (_req, res) => { + res.json({ status: 'ok', message: 'Server is running' }); +}); + - if (fileExtension !== '.sol') { - return res - .status(400) - .json({ status: 'error', message: 'Invalid file type. Only .sol files allowed.' }); +// File upload endpoint +app.post('/contract-upload', upload.single('contractFile'), async (req, res) => { + try { + console.log('Received upload request'); + + if (!req.file) { + return res.status(400).json({ + status: 'error', + message: 'No file uploaded' + }); } - res.status(200).json({ status: 'ok', message: 'File uploaded successfully.' }); + res.status(200).json({ + status: 'success', + message: 'File uploaded successfully', + data: { + filename: req.file.filename + } + }); } catch (error) { - console.error('Error:', error); - res.status(500).json({ status: 'error', message: 'File upload failed.' }); + console.error('Upload error:', error); + res.status(500).json({ + status: 'error', + message: error.message || 'File upload failed' + }); } }); -// Route: Contract Analyze and Save Project Name +// Replace the contract analysis endpoint with this fixed version app.post('/contract-analyze', async (req, res) => { try { - const { projectName } = req.body; // Access project name from request body + const { projectName, filename } = req.body; + console.log('Analyzing:', { projectName, filename }); + + if (!projectName || !filename) { + return res.status(400).json({ + status: 'error', + message: 'Project name and filename are required' + }); + } + + const filePath = path.join(UPLOADS_DIR, filename); + + try { + await fs.access(filePath); + } catch { + return res.status(404).json({ + status: 'error', + message: 'Contract file not found' + }); + } + + // Execute Slither with better output handling + let slitherOutput = ''; + try { + const { stdout, stderr } = await execAsync(`slither "${filePath}" --print human-summary`); + + // Combine stdout and stderr as Slither might output to either + slitherOutput = stdout || ''; + if (stderr) { + console.log('Slither stderr:', stderr); + // Only add stderr if it contains useful information + if (stderr.includes('contracts in source files') || + stderr.includes('Source lines of code') || + stderr.includes('Number of')) { + slitherOutput += '\n' + stderr; + } + } + } catch (execError) { + console.error('Slither execution error:', execError); + // Even if the command fails, we might have useful output + slitherOutput = execError.stdout || ''; + if (execError.stderr) { + console.log('Error stderr:', execError.stderr); + if (execError.stderr.includes('contracts in source files') || + execError.stderr.includes('Source lines of code') || + execError.stderr.includes('Number of')) { + slitherOutput += '\n' + execError.stderr; + } + } + } - if (!projectName) { - return res.status(400).json({ status: 'error', message: 'Project name is required.' }); + if (!slitherOutput.trim()) { + throw new Error('No output received from Slither analysis'); } - console.log(`Project Name: ${projectName}`); - // Save project name to the database - await ProjectNameModel.create({ projectName }); + // Clean the output before parsing + const cleanOutput = slitherOutput + .replace(/\u001b\[\d+m/g, '') // Remove ANSI color codes + .replace(/\r\n/g, '\n') // Normalize line endings + .trim(); + + // Parse the cleaned output + const metrics = parseSlitherOutput(cleanOutput); + + // Convert all numeric values explicitly + const metricsData = { + project_name: projectName, + total_contracts: parseInt(metrics.total_contracts) || 0, + source_lines: parseInt(metrics.source_lines) || 0, + assembly_lines: parseInt(metrics.assembly_lines) || 0, + optimization_issues: parseInt(metrics.optimization_issues) || 0, + informational_issues: parseInt(metrics.informational_issues) || 0, + low_issues: parseInt(metrics.low_issues) || 0, + medium_issues: parseInt(metrics.medium_issues) || 0, + high_issues: parseInt(metrics.high_issues) || 0, + ercs: metrics.ercs || 'None' + }; + + + // Save to Supabase + const { data, error } = await supabase + .from('slither_metrics') + .insert([metricsData]) + .select(); + + if (error) { + console.error('Supabase error:', error); + throw new Error(`Database error: ${error.message}`); + } + + // Clean up file + try { + await fs.unlink(filePath); + console.log('Cleaned up file:', filePath); + } catch (cleanupError) { + console.error('Error cleaning up file:', cleanupError); + } + + res.status(200).json({ + status: 'success', + message: 'Analysis completed successfully', + data: { + projectName, + metrics: { + total_contracts: metrics.total_contracts, + source_lines: metrics.source_lines, + assembly_lines: metrics.assembly_lines, + issues: { + optimization: metrics.optimization_issues, + informational: metrics.informational_issues, + low: metrics.low_issues, + medium: metrics.medium_issues, + high: metrics.high_issues, + }, + ercs: metrics.ercs + }, + timestamp: new Date().toISOString(), + id: data?.[0]?.id + } + }); - res.status(200).json({ status: 'ok', message: 'Project name saved successfully.' }); } catch (error) { - console.error('Error:', error); - res.status(500).json({ status: 'error', message: 'Failed to save project name.' }); + console.error('Analysis error:', error); + res.status(500).json({ + status: 'error', + message: error.message || 'Analysis failed', + details: error.stack + }); } }); -// Start the server +// And update the parseSlitherOutput function +const parseSlitherOutput = (output) => { + try { + + // Initialize metrics with default values + const metrics = { + total_contracts: 0, + source_lines: 0, + assembly_lines: 0, + optimization_issues: 0, + informational_issues: 0, + low_issues: 0, + medium_issues: 0, + high_issues: 0, + ercs: 'None' + }; + + // Split output into lines and process each line + const lines = output.split('\n') + .map(line => line.trim()) + .filter(line => line.length > 0); + + + // Process each line + lines.forEach(line => { + let value; + if (line.includes('Total number of contracts')) { + value = line.match(/:\s*(\d+)/)?.[1]; + if (value) metrics.total_contracts = parseInt(value); + } + else if (line.includes('Source lines of code')) { + value = line.match(/:\s*(\d+)/)?.[1]; + if (value) metrics.source_lines = parseInt(value); + } + else if (line.includes('Number of assembly lines')) { + value = line.match(/:\s*(\d+)/)?.[1]; + if (value) metrics.assembly_lines = parseInt(value); + } + else if (line.includes('Number of optimization issues')) { + value = line.match(/:\s*(\d+)/)?.[1]; + if (value) metrics.optimization_issues = parseInt(value); + } + else if (line.includes('Number of informational issues')) { + value = line.match(/:\s*(\d+)/)?.[1]; + if (value) metrics.informational_issues = parseInt(value); + } + else if (line.includes('Number of low issues')) { + value = line.match(/:\s*(\d+)/)?.[1]; + if (value) metrics.low_issues = parseInt(value); + } + else if (line.includes('Number of medium issues')) { + value = line.match(/:\s*(\d+)/)?.[1]; + if (value) metrics.medium_issues = parseInt(value); + } + else if (line.includes('Number of high issues')) { + value = line.match(/:\s*(\d+)/)?.[1]; + if (value) metrics.high_issues = parseInt(value); + } + else if (line.includes('ERCs:')) { + metrics.ercs = line.split('ERCs:')[1]?.trim() || 'None'; + } + }); + + return metrics; + } catch (error) { + console.error('Error parsing output:', error); + throw new Error(`Failed to parse Slither output: ${error.message}`); + } +}; + + + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Start server const PORT = process.env.PORT || 5000; app.listen(PORT, () => { - console.log(`Backend server running on port ${PORT}`); + console.log(`Server running on port ${PORT}`); + console.log(`Uploads directory: ${UPLOADS_DIR}`); + console.log('CORS enabled for:', corsOptions.origin); }); + +// Handle shutdown +process.on('SIGTERM', async () => { + console.log('SIGTERM received. Cleaning up...'); + try { + const files = await fs.readdir(UPLOADS_DIR); + await Promise.all(files.map(file => fs.unlink(path.join(UPLOADS_DIR, file)))); + } catch (error) { + console.error('Cleanup error:', error); + } + process.exit(0); +}); \ No newline at end of file diff --git a/app/api/schema.mjs b/app/api/schema.mjs deleted file mode 100644 index 47765f2..0000000 --- a/app/api/schema.mjs +++ /dev/null @@ -1,47 +0,0 @@ -import mongoose from 'mongoose'; - -// Define the schema -const projectSchema = new mongoose.Schema( - { - projectName: { type: String, required: true }, // Use camelCase field names - }, - { - collection: 'ProjectName', // Name of the MongoDB collection - } -); - -// Create and export the model -const ProjectNameModel = mongoose.model('ProjectName', projectSchema); -export default ProjectNameModel; - -// Schema for individual vulnerabilities -{/*const VulnerabilitySchema = new mongoose.Schema({ - severity: { type: String, enum: ['informational', 'low', 'medium', 'high'], required: true }, - description: { type: String, required: true }, - location: { type: String, required: true }, // Function or line where the issue occurs -}); - -// Schema for contract metadata and vulnerabilities -const ContractSchema = new mongoose.Schema({ - name: { type: String, required: true }, // Contract name - functionsCount: { type: Number, required: true }, // Number of functions - isERC: { type: Boolean, default: false }, // ERC compliance (e.g., ERC20) - pausable: { type: Boolean, default: false }, // Pausable functionality - mintingRestricted: { type: Boolean, default: false }, // Minting restrictions - raceConditionMitigated: { type: Boolean, default: false }, // ERC20 race condition mitigation - complexCode: { type: Boolean, default: false }, // Advanced logic (assembly, delegatecall) - vulnerabilities: [VulnerabilitySchema], // List of vulnerabilities -}); - -// Schema for the entire scan report -const ScanResultSchema = new mongoose.Schema({ - contracts: [ContractSchema], // List of contracts/libraries scanned - totalIssues: { - informational: { type: Number, default: 0 }, - low: { type: Number, default: 0 }, - medium: { type: Number, default: 0 }, - high: { type: Number, default: 0 }, - }, - createdAt: { type: Date, default: Date.now }, // Timestamp of the scan -});*/} - diff --git a/app/contract/[id]/page.tsx b/app/contract/[id]/page.tsx index de51bf9..4e530fc 100644 --- a/app/contract/[id]/page.tsx +++ b/app/contract/[id]/page.tsx @@ -6,7 +6,7 @@ import { SmartScanning, ContractScanResult } from '@/components/index'; const Page = () => { return ( -
+
diff --git a/app/globals.css b/app/globals.css index bedd785..f806176 100644 --- a/app/globals.css +++ b/app/globals.css @@ -297,5 +297,19 @@ background: linear-gradient(45deg,#9333ea, #e70606); filter: blur(250px); overflow: hidden; } +/* Add these styles to your CSS */ +@keyframes progressAnimation { + 0% { + stroke-dasharray: 0 126; + } +} + +.circle-progress { + animation: progressAnimation 1s ease-out forwards; +} +.progress-wrapper { + transform: rotate(-90deg); + transform-origin: 50% 50%; +} /* END: search manufacturer styles */ \ No newline at end of file diff --git a/components/button/upload-form.tsx b/components/button/upload-form.tsx index 7f02f20..c0a16bc 100644 --- a/components/button/upload-form.tsx +++ b/components/button/upload-form.tsx @@ -20,7 +20,7 @@ const UploadForm = ({style, title}: UploadFormProps) => { const [contractFile, setContractFile] = useState(null); const [message, setMessage] = useState(''); const [messageName, setMessageName] = useState(''); - const {isScanning, startScanning} = useScanning(); // Destructuring scanning state and key press handler from custom hook + const {isScanning, startScanning, setIsScanning} = useScanning(); // Destructuring scanning state and key press handler from custom hook const [openUpload, setOpenUpload] = useState(false); const API_URL = process.env.NEXT_PUBLIC_API_URL; const handleProjectNameChange = (event: React.ChangeEvent) => { @@ -34,87 +34,107 @@ const UploadForm = ({style, title}: UploadFormProps) => { removeFile(); }; - // Handle File Selection - const handleFileChange = async (event: React.ChangeEvent) => { - const selectedFile = event.target.files?.[0] || null; - setContractFile(selectedFile); + // Remove Selected File + const removeFile = () => { + setContractFile(null); + setMessage('') + }; + const handleFileChange = async (event: React.ChangeEvent) => { + const selectedFile = event.target.files?.[0] || null; + if (!selectedFile) { setMessage('Please select a file to upload.'); + setContractFile(null); return; } - const formData = new FormData(); - formData.append('contractFile', selectedFile); - - try { - console.log(`${API_URL}/contract-upload`); - const upload = await axios.post(`${API_URL}/contract-upload`, formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); + // Validate file extension + if (!selectedFile.name.toLowerCase().endsWith('.sol')) { + setMessage('Please select a .sol file'); + setContractFile(null); + return; + } - // Handle success response - if (upload.status === 200) { - setMessage("Your file is ready!"); - } - } catch (error: unknown) { - if (axios.isAxiosError(error)) { - if (error.response) { - setMessage('Please select a .sol file'); - } else { - setMessage('An error occurred. Failed to upload.'); - } - } else { - setMessage('An unexpected error occurred.'); - } + // Validate file size (10MB) + if (selectedFile.size > 10 * 1024 * 1024) { + setMessage('File size must be less than 10MB'); + setContractFile(null); + return; } - - }; - // Remove Selected File - const removeFile = () => { - setContractFile(null); - setMessage('') + setContractFile(selectedFile); + setMessage('File selected successfully'); }; - // Handle Form Submission and File Upload const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); // Prevent page refresh - - // Validate project name + event.preventDefault(); + if (!projectName.trim()) { setMessageName('Please enter a project name before uploading.'); return; - } - setMessageName(''); // Clear any previous error messages - - // Validate file selection + } + setMessageName(''); + if (!contractFile) { setMessage('Please select a file to upload.'); return; } - - + try { - console.log(`API URL: ${API_URL}`); - - const response = await axios.post(`${API_URL}/contract-analyze`, {projectName,}, { - headers: { 'Content-Type': 'application/json' }, + setIsScanning(true); + // First upload the file + const formData = new FormData(); + formData.append('contractFile', contractFile); + + const uploadResponse = await axios.post(`${API_URL}/contract-upload`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, }); - - if (response.status === 200) { + + if (uploadResponse.data.status !== 'success') { + throw new Error(uploadResponse.data.message || 'File upload failed'); + } + + // Then start analysis + const analyzeResponse = await axios.post(`${API_URL}/contract-analyze`, { + projectName, + filename: uploadResponse.data.data.filename + }, { + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (analyzeResponse.data.status === 'success') { setMessage('Upload successful! The scan has started.'); - startScanning(''); // Call the scanning function - } else { - setMessage( - `Error: Something went wrong.` )} + startScanning(analyzeResponse.data.data); + + // Clear form + setProjectName(''); + setContractFile(null); + if (event.target instanceof HTMLFormElement) { + event.target.reset(); + } + closeUploadButton(); + } } catch (error) { console.error('Error:', error); - setMessage('Failed to connect to server.'); + if (axios.isAxiosError(error)) { + if (error.code === 'ERR_NETWORK') { + setMessage('Cannot connect to server. Please make sure the server is running.'); + } else if (error.response) { + setMessage(error.response.data.message || 'An error occurred during processing'); + } else { + setMessage('Network error occurred. Please try again.'); + } + } else { + setMessage('An unexpected error occurred.'); + } } }; - return ( @@ -197,7 +217,7 @@ const UploadForm = ({style, title}: UploadFormProps) => { ) : (
-

{contractFile.name}{message && {message}}

+

{contractFile.name}{message && {message}}

)} - {isScanning && } + {isScanning && } diff --git a/components/chart/DonutChart.js b/components/chart/DonutChart.js index 0a530d3..5e6900a 100644 --- a/components/chart/DonutChart.js +++ b/components/chart/DonutChart.js @@ -5,27 +5,30 @@ import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; ChartJS.register(ArcElement, Tooltip, Legend); -const DonutChart = () => { + +const DonutChart = ({Optimization, Informational, Low, Medium, High}) => { const data = { - labels: ['Unlocked', 'Brunt', 'Locked', 'Owner'], //set properties for the chart and nodes + labels: ['Optimization', 'Informational', 'Low', 'Medium', 'High'], //set properties for the chart and nodes datasets: [ { - label: 'Token Liquidity', - data: [35, 35, 15, 15], + label: 'Issues', + data: [Optimization, Informational, Low, Medium, High], backgroundColor: [ - '#9333ea', - '#ec4899', - '#f9a8d4', - '#fbcfe8', + '#3b82f6', // blue-500 + '#6b7280', // gray-500 + '#22c55e', // green-500 + '#eab308', // yellow-500 + '#dc2626' // red-600 ], borderColor: '#f7f8f7', borderWidth: 8, borderRadius: 14, hoverBackgroundColor: [ - '#6b13bb', - '#b4226a', - '#a96a8c', - '#bf87a7', + '#2563eb', // blue-600 + '#4b5563', // gray-600 + '#16a34a', // green-600 + '#ca8a04', // yellow-600 + '#b91c1c' // red-700 ], }, ], @@ -43,7 +46,7 @@ const DonutChart = () => { return chart.data.labels.map((label, i) => { const value = dataset.data[i]; return { - text: `${label}: ${value}%`, // Use backticks for template literals + text: `${label}: ${value}`, // Use backticks for template literals fillStyle: dataset.backgroundColor[i], strokeStyle: undefined, lineWidth: 0, @@ -75,20 +78,12 @@ const DonutChart = () => { return (
-

- Token Liquidity Analysis -

+
{ justifyContent: 'center', }} > -
- Total Liquidity:{' '} - $1.5M -
-
+
{/* Set height to ensure chart fits */} diff --git a/components/index.js b/components/index.js index 6f8e88f..57c6a1e 100644 --- a/components/index.js +++ b/components/index.js @@ -7,7 +7,7 @@ import CustomButton from './utils/CustomButton'; import RiliabilitySection from './section/reliability-section'; import YTRefSection from './section/youtube-reference-section'; import ContractScanResult from './section/contract-scan-result-section'; -import SafetyCheck from './section/safety-check-section'; +import ResultBody from './section/result-body'; import Video from './section/full-screen-video-section'; import PdfViewer from './section/pdf-display-section'; import SmartScanning from './section/smart-scanning-section'; @@ -16,13 +16,13 @@ import RadarChart from './chart/RadarChart'; import FAQ from './section/frequently-asked-question-section'; import {useScanning} from './utils/useScanning'; import CopyButton from './button/copy-button'; -import HandleSafetyCheck from './section/handle-safety-check'; +import Vulnerability from './section/vulnerability'; import ContractList from './section/contract-list-section'; import ScanningNotification from './section/scanning-notification'; import splitString from './utils/split-string' import TokenBasicInfo from './section/token-basic-info'; import UploadForm from './button/upload-form'; - +import { Overview } from './section/overview'; export { Hero, @@ -33,7 +33,7 @@ export { ContractScanResult, RiliabilitySection, YTRefSection, - SafetyCheck, + ResultBody, Video, PdfViewer, SmartScanning, @@ -41,10 +41,11 @@ export { FAQ, useScanning, CopyButton, - HandleSafetyCheck, + Vulnerability, ContractList, ScanningNotification, splitString, TokenBasicInfo, - UploadForm + UploadForm, + Overview } diff --git a/components/section/contract-scan-result-section.tsx b/components/section/contract-scan-result-section.tsx index fe110e6..16601ea 100644 --- a/components/section/contract-scan-result-section.tsx +++ b/components/section/contract-scan-result-section.tsx @@ -1,105 +1,263 @@ -import React from 'react'; -import {CustomButton, SafetyCheck, RadarChart, DonutChart, CopyButton, TokenBasicInfo} from '@/components/index'; +import React, { useState, useEffect } from 'react'; +import { CustomButton, ResultBody} from '@/components/index'; +import { createClient } from '@supabase/supabase-js'; + + +interface AnalysisMetrics { + id: string; + project_name: string; + created_at: string; + total_contracts: number; + source_lines: number; + assembly_lines: number; + optimization_issues: number; + informational_issues: number; + low_issues: number; + medium_issues: number; + high_issues: number; + ercs: string; +} +// CircularProgress.tsx +interface CircularProgressProps { + value: number; // value between 0-100 + size?: number; // size in pixels + strokeWidth?: number; // border width + primaryColor?: string; // main color + secondaryColor?: string; // background color +} + +const CircularProgress: React.FC = ({ + value, + size = 48, + strokeWidth = 4, + primaryColor = '#4F46E5', // indigo-600 + secondaryColor = '#E5E7EB' // gray-200 +}) => { + const radius = (size - strokeWidth) / 2; + const circumference = radius * 2 * Math.PI; + const offset = circumference - (value / 100) * circumference; + + return ( +
+ {/* Background circle */} + + + {/* Foreground circle */} + + +
+ ); +}; const ContractScanResult = () => { - - // Handler for redirecting to PDF result + const [metrics, setMetrics] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchResults = async () => { + try { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ); + + const { data, error } = await supabase + .from('slither_metrics') + .select('*') + .order('created_at', { ascending: false }) + .limit(1) + .single(); + + if (error) throw error; + setMetrics(data); + } catch (error) { + console.error('Error fetching metrics:', error); + setError(error instanceof Error ? error.message : 'Failed to fetch analysis results'); + } finally { + setLoading(false); + } + }; + + fetchResults(); + }, []); + + // Calculate safety score based on issues + const calculateSafetyScore = (metrics: AnalysisMetrics): number => { + if (!metrics) return 0; + + const maxScore = 100; + const deductions = { + high: 5, // Deduct 15 points per high issue + medium: 2, // Deduct 10 points per medium issue + low: 1 // Deduct 5 points per low issue + }; + + const totalDeduction = + (metrics.high_issues * deductions.high) + + (metrics.medium_issues * deductions.medium) + + (metrics.low_issues * deductions.low); + + return Math.max(0, Math.min(100, maxScore - totalDeduction)); + }; + const handleRedirectToPdf = () => { - // Replace the URL below with the actual URL of your PDF const pdfUrl = '/contract/scanresult'; - // Open the PDF in a new tab window.open(pdfUrl, '_blank'); }; - // Full contract address - const fullAddress = '0x576e2bed8f7b46d34016198911cdf9886f78bea7'; + if (loading) { + return ( +
+
+
+ ); + } - // Function to shorten the address for display - const shortenAddress = (address: string) => { - return `${address.slice(0, 6)}...${address.slice(-4)}`; - }; + if (error || !metrics) { + return ( +
+
Failed to load analysis results
+
+ ); + } - // Data for radar chart - const radarLabels = ['Contract', 'Social', 'Holder', 'Liquidity', 'Governance']; - const radarScores = [90, 80, 70, 85, 75]; + const safetyScore = calculateSafetyScore(metrics); return (
-
- {/* Contract information header */} -
- {/* Token logo and basic info */} -
- coin logo -
-
-

DiDi Coin

- DC -
-
-

- {shortenAddress(fullAddress)} -

- -
- - {/* Deployment info */} -
-

Deployed – Wed, 2 Oct 2024 • Project age 6h

-
-
-
- - {/* Risk and safety score information */} -
- {/* Left Column */} -
-

- Medium Risk: 3 -

- -

- High Risk: 2 -

-
- - {/* Right Column */} -
-

- Safety score: 93/100 -

- -

- Attention Required: 8 -

-
-
- - {/* Export button */} - } - title="Export" - containerStyles=" bg-primary-red text-white rounded-full px-4 py-2 lg:py-3 shadow-glow-red transition-all duration-300 ease-in-out transform hover:scale-105" - handleClick={handleRedirectToPdf} - /> -
+
+ {/* Header section */} +
+ {/* Top section with file info and generate button */} +
+
+
+ solidity file +
+
+

{metrics.project_name}

+

File Scan

+
+
+ + {/* Export button */} + + + } + title="Generate report" + containerStyles="bg-primary-red text-white font-semibold rounded-full px-4 py-2 lg:py-3 shadow-glow-red transition-all duration-300 ease-in-out transform hover:scale-105" + handleClick={handleRedirectToPdf} + /> +
+ + {/* Metrics section */} +
+ {/* Security Score */} +
+ + {/* Security Score */} + + +
+

Security Score

+

+ {safetyScore.toFixed(2)}/100 +

+
+
+ + {/* Scan Duration */} +
+
+ scan duration +
+
+

Scan duration

+

8 secs

+
+
+ + {/* Lines of Code */} +
+
+ line of code +
+
+

Lines of code

+

{metrics.source_lines}

+
+
+ + {/* Issues Count */} +
+
+ issues count +
+
+

Issues Count

+

+ {metrics.high_issues + metrics.medium_issues + metrics.low_issues} +

+
+
+
+
+ + + + + + + + + + + + + + + {/* Main content area */} +
+ {/* Safety check component */} +
+ +
- {/* Main content area */} -
- {/* Safety check component */} -
- -
- {/* Charts and token info */} -
- - - -
+ {/* Metrics Overview */} +
+
-
- ) -} +
+ + ); +}; -export default ContractScanResult \ No newline at end of file +export default ContractScanResult; \ No newline at end of file diff --git a/components/section/handle-safety-check.tsx b/components/section/handle-safety-check.tsx deleted file mode 100644 index 097776f..0000000 --- a/components/section/handle-safety-check.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import React from 'react' -import { useState } from 'react'; -import { ChevronDown } from 'lucide-react'; -interface HandleSafetyCheckProps { - isTokenDetector: boolean; // Boolean prop to indicate if the Token Detector is active -} - -//Component that is passed boolean argument to display safety check results. -const HandleSafetyCheck : React.FC = ({ isTokenDetector }) => { - // State to store the indexes of expanded sections for Token Detector - const [openIndexesToken, setOpenIndexesToken] = useState([]); - - // State to store the indexes of expanded sections for General Detector - const [openIndexesGeneral, setOpenIndexesGeneral] = useState([]); - - // Mock data for Token Detector tab, simulating various checks and their statuses - const fakeResultsOne = [ - { check: 'Inaccurate Supply', status: '/images/shield-x-solid-24.png', bg: 'bg-black', risk: 'high' }, - { check: 'Dump Risk', status: '/images/error-solid-24.png', bg: 'bg-black', risk: 'medium' }, - { check: 'Whitelisting', status: '/images/error-alt-solid-24.png', bg: 'bg-black', risk: 'attention' }, - { check: 'No vulnerable withdrawal functions found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No reentrancy risk found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No locks detected', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'Verified source code found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No mintable risks found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No blacklisted functions found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No proxy contract detected', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'Liquidity locked', status: '/images/check-2.png', bg: 'bg-primary-red' }, - ]; - -// Dữ liệu giả cho tab General Detector -const fakeResultsTwo = [ - { check: 'Inaccurate Supply', status: '/images/shield-x-solid-24.png', bg: 'bg-black', risk: 'high' }, - { check: 'Dump Risk', status: '/images/error-solid-24.png', bg: 'bg-black', risk: 'medium' }, - { check: 'Incorrect Solidity Version', status: '/images/error-alt-solid-24.png', bg: 'bg-black', risk: 'attention' }, - { check: 'No error found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No reentrancy risk found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No locks detected', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'Verified source code found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No mintable risks found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No blacklisted functions found', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'No proxy contract detected', status: '/images/check-2.png', bg: 'bg-primary-red' }, - { check: 'Liquidity locked', status: '/images/check-2.png', bg: 'bg-primary-red' }, -]; - -// Hàm mở rộng cho Token Detector -const toggleExpandToken = (index: number) => { - if (openIndexesToken.includes(index)) { - setOpenIndexesToken(openIndexesToken.filter(i => i !== index)); - } else { - setOpenIndexesToken([...openIndexesToken, index]); - } -}; - -// Hàm mở rộng cho General Detector -const toggleExpandGeneral = (index: number) => { - if (openIndexesGeneral.includes(index)) { - setOpenIndexesGeneral(openIndexesGeneral.filter(i => i !== index)); - } else { - setOpenIndexesGeneral([...openIndexesGeneral, index]); - } -}; - -// Hàm render chi tiết riêng cho Token Detector -const renderDetailsToken = (risk: string) => { - switch (risk) { - case 'high': - return ( -
-

Token Detector - High Risk:

-

Declared total supply is lesser than the sum of actual circulating tokens.

-
- ); - case 'medium': - return ( -
-

Token Detector - Medium Risk:

-

A private wallet owns a significant percentage of this token’s total supply.

-
- ); - case 'attention': - return ( -
-

Token Detector - Attention Required:

-

The issue is not critical.

-
- ); - default: - return null; - } -}; - -// Hàm render chi tiết riêng cho General Detector -const renderDetailsGeneral = (risk: string) => { - switch (risk) { - case 'high': - return ( -
-

General Detector - High Risk:

-

Declared total supply does not match the actual token supply.

-
- ); - case 'medium': - return ( -
-

General Detector - Medium Risk:

-

A large wallet holds a significant share of the token.

-
- ); - case 'attention': - return ( -
-

General Detector - Attention Required:

-

This contract uses an unconventional or very old version of Solidity.

-
- ); - default: - return null; - } -}; - - return ( -
    - {isTokenDetector - ? fakeResultsOne.map((result, index) => ( -
  • -
    -
    - checking symbol -

    {result.check}

    -
    - {result.risk && ( - - )} -
    - - {/* Advanced View 1 nằm ngoài khung xám */} - {openIndexesToken.includes(index) && ( - <> -
    -

    Advanced View 1:

    -

    - {result.risk === 'high' - ? 'Review the security protocols immediately to mitigate risks.' - : result.risk === 'medium' - ? 'Mitigation measures should be taken to address this risk soon.' - : 'Attention is needed, but this issue is not critical.'} -

    -
    - - {/* Chi tiết mở rộng bên trong khung xám */} -
    - {renderDetailsToken(result.risk!)} -
    - - {/* Advanced View 2 nằm ngoài khung xám */} -
    -

    Advanced View 2:

    -

    - This is additional information regarding the issue. -

    -
    - - )} -
  • - )) - : fakeResultsTwo.map((result, index) => ( -
  • -
    -
    - checking symbol -

    {result.check}

    -
    - {result.risk && ( - - )} -
    - - {/* Advanced View 1 nằm ngoài khung xám */} - {openIndexesGeneral.includes(index) && ( - <> -
    -

    Advanced View 1:

    -

    - {result.risk === 'high' - ? 'Review the security protocols immediately to mitigate risks.' - : result.risk === 'medium' - ? 'Mitigation measures should be taken to address this risk soon.' - : 'Attention is needed, but this issue is not critical.'} -

    -
    - - {/* Chi tiết mở rộng bên trong khung xám */} -
    - {renderDetailsGeneral(result.risk!)} -
    - - {/* Advanced View 2 nằm ngoài khung xám */} -
    -

    Advanced View 2:

    -

    - This is additional information regarding the issue. -

    -
    - - )} -
  • - ))} -
- ) -} - -export default HandleSafetyCheck \ No newline at end of file diff --git a/components/section/overview.tsx b/components/section/overview.tsx new file mode 100644 index 0000000..6de53b5 --- /dev/null +++ b/components/section/overview.tsx @@ -0,0 +1,117 @@ +import React from 'react' +import { useState, useEffect } from 'react'; +import { createClient } from '@supabase/supabase-js'; +import { RadarChart, DonutChart } from '@/components/index' + +interface AnalysisMetrics { + id: string; + created_at: string; + total_contracts: number; + source_lines: number; + assembly_lines: number; + optimization_issues: number; + informational_issues: number; + low_issues: number; + medium_issues: number; + high_issues: number; + ercs: string; +} + +export const Overview = () => { + const [metrics, setMetrics] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchResults = async () => { + try { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ); + + const { data, error } = await supabase + .from('slither_metrics') + .select('*') + .order('created_at', { ascending: false }) + .limit(1) + .single(); + + if (error) throw error; + setMetrics(data); + } catch (error) { + console.error('Error fetching metrics:', error); + setError(error instanceof Error ? error.message : 'Failed to fetch analysis results'); + } finally { + setLoading(false); + } + }; + + fetchResults(); + }, []); + + if (!metrics) { + return ( +
+
No metrics data available
+
+ ); + } + + return ( +
+
+
+ Project ID: + {metrics.id} +
+
+ Created at: + {metrics.created_at} +
+
+ Total contract: + {metrics.total_contracts} +
+
+ Assembly lines: + {metrics.assembly_lines} +
+
+
+
+

Contract Metrics

+
+
+ Optimization Issues + {metrics.optimization_issues} +
+
+ Informational Issues + {metrics.informational_issues} +
+
+ Low Issues + {metrics.low_issues} +
+
+ Medium Issues + {metrics.medium_issues} +
+
+ High Issues + {metrics.high_issues} +
+
+
+
+ +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/section/safety-check-section.tsx b/components/section/result-body.tsx similarity index 88% rename from components/section/safety-check-section.tsx rename to components/section/result-body.tsx index 6bfb39a..4b250b7 100644 --- a/components/section/safety-check-section.tsx +++ b/components/section/result-body.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import {HandleSafetyCheck} from '@/components/index'; +import {Vulnerability, Overview} from '@/components/index'; //Component for displaying contract safety check results @@ -19,7 +19,7 @@ const SafetyCheck: React.FC = () => { ${isChosen ? 'border-b-4 border-primary-red text-opacity-100 ' : 'text-opacity-70 border-b-2 border-transparent hover:border-primary-red hover:text-opacity-100 hover:scale-105'}`} > - Token Detector + Overview
- + {isChosen ? : }
); diff --git a/components/section/smart-scanning-section.tsx b/components/section/smart-scanning-section.tsx index 70687f0..f65a8a9 100644 --- a/components/section/smart-scanning-section.tsx +++ b/components/section/smart-scanning-section.tsx @@ -11,7 +11,7 @@ const SmartScanning = () => { return ( -
+
diff --git a/components/section/vulnerability.tsx b/components/section/vulnerability.tsx new file mode 100644 index 0000000..55f44f8 --- /dev/null +++ b/components/section/vulnerability.tsx @@ -0,0 +1,150 @@ +import React from 'react' +import { useState } from 'react'; +import { ChevronDown } from 'lucide-react'; + + +//Component that is passed boolean argument to display safety check results. +const HandleSafetyCheck= () => { + // State to store the indexes of expanded sections for Token Detector + const [openIndexesToken, setOpenIndexesToken] = useState([]); + + // State to store the indexes of expanded sections for General Detector + const [openIndexesGeneral, setOpenIndexesGeneral] = useState([]); + + // Mock data for Token Detector tab, simulating various checks and their statuses + const fakeResultsOne = [ + { check: 'Inaccurate Supply', status: '/images/shield-x-solid-24.png', bg: 'bg-black', risk: 'high' }, + { check: 'Dump Risk', status: '/images/error-solid-24.png', bg: 'bg-black', risk: 'medium' }, + { check: 'Whitelisting', status: '/images/error-alt-solid-24.png', bg: 'bg-black', risk: 'attention' }, + { check: 'No vulnerable withdrawal functions found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No reentrancy risk found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No locks detected', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'Verified source code found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No mintable risks found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No blacklisted functions found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No proxy contract detected', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'Liquidity locked', status: '/images/check-2.png', bg: 'bg-primary-red' }, + ]; + +// Dữ liệu giả cho tab General Detector +const fakeResultsTwo = [ + { check: 'Inaccurate Supply', status: '/images/shield-x-solid-24.png', bg: 'bg-black', risk: 'high' }, + { check: 'Dump Risk', status: '/images/error-solid-24.png', bg: 'bg-black', risk: 'medium' }, + { check: 'Incorrect Solidity Version', status: '/images/error-alt-solid-24.png', bg: 'bg-black', risk: 'attention' }, + { check: 'No error found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No reentrancy risk found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No locks detected', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'Verified source code found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No mintable risks found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No blacklisted functions found', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'No proxy contract detected', status: '/images/check-2.png', bg: 'bg-primary-red' }, + { check: 'Liquidity locked', status: '/images/check-2.png', bg: 'bg-primary-red' }, +]; + +// Hàm mở rộng cho Token Detector +const toggleExpandToken = (index: number) => { + if (openIndexesToken.includes(index)) { + setOpenIndexesToken(openIndexesToken.filter(i => i !== index)); + } else { + setOpenIndexesToken([...openIndexesToken, index]); + } +}; + +// Hàm mở rộng cho General Detector +const toggleExpandGeneral = (index: number) => { + if (openIndexesGeneral.includes(index)) { + setOpenIndexesGeneral(openIndexesGeneral.filter(i => i !== index)); + } else { + setOpenIndexesGeneral([...openIndexesGeneral, index]); + } +}; + +// Hàm render chi tiết riêng cho Token Detector +const renderDetailsToken = (risk: string) => { + switch (risk) { + case 'high': + return ( +
+

Token Detector - High Risk:

+

Declared total supply is lesser than the sum of actual circulating tokens.

+
+ ); + case 'medium': + return ( +
+

Token Detector - Medium Risk:

+

A private wallet owns a significant percentage of this token’s total supply.

+
+ ); + case 'attention': + return ( +
+

Token Detector - Attention Required:

+

The issue is not critical.

+
+ ); + default: + return null; + } +}; + + + +return ( +
    + {fakeResultsOne.map((result, index) => ( +
  • +
    +
    + checking symbol +

    {result.check}

    +
    + {result.risk && ( + + )} +
    + + {/* Advanced View 1 nằm ngoài khung xám */} + {openIndexesToken.includes(index) && ( + <> +
    +

    Advanced View 1:

    +

    + {result.risk === 'high' + ? 'Review the security protocols immediately to mitigate risks.' + : result.risk === 'medium' + ? 'Mitigation measures should be taken to address this risk soon.' + : 'Attention is needed, but this issue is not critical.'} +

    +
    + + {/* Chi tiết mở rộng bên trong khung xám */} +
    + {renderDetailsToken(result.risk!)} +
    + + {/* Advanced View 2 nằm ngoài khung xám */} +
    +

    Advanced View 2:

    +

    + This is additional information regarding the issue. +

    +
    + + )} +
  • + ))} +
+); +} +export default HandleSafetyCheck \ No newline at end of file diff --git a/components/utils/useScanning.tsx b/components/utils/useScanning.tsx index f17f78a..adff39a 100644 --- a/components/utils/useScanning.tsx +++ b/components/utils/useScanning.tsx @@ -12,7 +12,7 @@ export const useScanning = () => { setIsScanning(false); // After the scanning is done, navigate to the contract page router.push(`/contract/${inputValue || defaultContract}`); - }, 800); // Simulate a 3-second scan + }, 800); }; const handleKeyPress = (e: React.KeyboardEvent, inputValue: string) => { @@ -21,5 +21,5 @@ export const useScanning = () => { } }; - return { isScanning, startScanning, handleKeyPress }; + return { isScanning, startScanning, handleKeyPress, setIsScanning }; }; diff --git a/package-lock.json b/package-lock.json index 12d1ead..dd7bab9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", + "@supabase/supabase-js": "^2.45.5", "@tsparticles/all": "^3.5.0", "@tsparticles/react": "^3.0.0", "axios": "^1.7.7", @@ -33,6 +34,7 @@ "react-dom": "^18", "react-tsparticles": "^2.12.2", "solc": "^0.8.27", + "supabase": "^1.204.3", "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", "web3": "^4.13.0" @@ -221,6 +223,17 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -867,6 +880,81 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@supabase/auth-js": { + "version": "2.65.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.65.1.tgz", + "integrity": "sha512-IA7i2Xq2SWNCNMKxwmPlHafBQda0qtnFr8QnyyBr+KaSxoXXqEzFCnQ1dGTy6bsZjVBgXu++o3qrDypTspaAPw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.3.tgz", + "integrity": "sha512-sOLXy+mWRyu4LLv1onYydq+10mNRQ4rzqQxNhbrKLTLTcdcmS9hbWif0bGz/NavmiQfPs4ZcmQJp4WqOXlR4AQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.16.2.tgz", + "integrity": "sha512-dA/CIrSO2YDQ6ABNpbvEg9DwBMMbuKfWaFuZAU9c66PenoLSoIoyXk1Yq/wC2XISgEIqaMHmTrDAAsO80kjHqg==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.7.tgz", + "integrity": "sha512-OLI0hiSAqQSqRpGMTUwoIWo51eUivSYlaNBgxsXZE7PSoWh12wPRdVt0psUMaUzEonSB85K21wGc7W5jHnT6uA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/realtime-js/node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.45.5", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.5.tgz", + "integrity": "sha512-xTPsv33Hcj6C38SXa4nKobwEwkNQuwcCKtcuBsDT6bvphl1VUAO3x2QoLOuuglJzk2Oaf3WcVsvRcxXNE8PG/g==", + "dependencies": { + "@supabase/auth-js": "2.65.1", + "@supabase/functions-js": "2.4.3", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.16.2", + "@supabase/realtime-js": "2.10.7", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -2385,6 +2473,11 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -3094,6 +3187,21 @@ "node": ">= 0.6.0" } }, + "node_modules/bin-links": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-5.0.0.tgz", + "integrity": "sha512-sdleLVfCjBtgO5cNjA2HVRvWBJAHs4zwenaCPMNJAJU0yNxpzj80IpjOIimkpkr+mhlA+how5poQtt53PygbHA==", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3484,6 +3592,14 @@ "node": ">=6" } }, + "node_modules/cmd-shim": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-7.0.0.tgz", + "integrity": "sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw==", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3754,6 +3870,14 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -4787,6 +4911,28 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -4936,6 +5082,17 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5447,7 +5604,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -6630,6 +6786,24 @@ "resolved": "https://registry.npmjs.org/node-bin-setup/-/node-bin-setup-1.1.3.tgz", "integrity": "sha512-opgw9iSCAzT2+6wJOETCpeRYAQxSopqQ2z+N6BXwIMsQQ7Zj5M8MaafQY8JMlolRR6R1UXg2WmhKp0p9lSOivg==" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -6687,6 +6861,14 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -7202,6 +7384,14 @@ "node": ">= 0.8.0" } }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -7447,6 +7637,14 @@ "pify": "^2.3.0" } }, + "node_modules/read-cmd-shim": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-5.0.0.tgz", + "integrity": "sha512-SEbJV7tohp3DAAILbEMPXavBjAnMN0tVnh4+9G8ihV4Pq3HYF9h8QNez9zkJ1ILkv9G2BjdzwctznGZXgu/HGw==", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -8261,6 +8459,136 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supabase": { + "version": "1.204.3", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.204.3.tgz", + "integrity": "sha512-uO09eyAw7TZAX/7wPeieQBWrl4QAJ0WLF+HTkFy35GWBmQULP5nkJR93LcuhSyooYiqwEUKlChEF/PGAEmTCKw==", + "hasInstallScript": true, + "dependencies": { + "bin-links": "^5.0.0", + "https-proxy-agent": "^7.0.2", + "node-fetch": "^3.3.2", + "tar": "7.4.3" + }, + "bin": { + "supabase": "bin/supabase" + }, + "engines": { + "npm": ">=8" + } + }, + "node_modules/supabase/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/supabase/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "engines": { + "node": ">=18" + } + }, + "node_modules/supabase/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/supabase/node_modules/minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/supabase/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supabase/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/supabase/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supabase/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/supabase/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "engines": { + "node": ">=18" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8822,6 +9150,14 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, "node_modules/web3": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/web3/-/web3-4.13.0.tgz", @@ -9393,6 +9729,18 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "devOptional": true }, + "node_modules/write-file-atomic": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-6.0.0.tgz", + "integrity": "sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/package.json b/package.json index 38bfebe..d43e76d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", + "@supabase/supabase-js": "^2.45.5", "@tsparticles/all": "^3.5.0", "@tsparticles/react": "^3.0.0", "axios": "^1.7.7", @@ -36,6 +37,7 @@ "react-dom": "^18", "react-tsparticles": "^2.12.2", "solc": "^0.8.27", + "supabase": "^1.204.3", "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", "web3": "^4.13.0" diff --git a/public/images/bug_icon.svg b/public/images/bug_icon.svg new file mode 100644 index 0000000..021cd1e --- /dev/null +++ b/public/images/bug_icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/images/filescan.svg b/public/images/filescan.svg new file mode 100644 index 0000000..fb2e26c --- /dev/null +++ b/public/images/filescan.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/images/lineofcode.svg b/public/images/lineofcode.svg new file mode 100644 index 0000000..3e7105d --- /dev/null +++ b/public/images/lineofcode.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/scan_duration.svg b/public/images/scan_duration.svg new file mode 100644 index 0000000..176eee4 --- /dev/null +++ b/public/images/scan_duration.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tsconfig.json b/tsconfig.json index 6fb4123..af14bfd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,6 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/contract/scanResult/display-scanned-result.js", "components/feature/useScanning.js", "components/utils/auditService.js"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/contract/scanResult/display-scanned-result.js", "components/feature/useScanning.js", "components/utils/auditService.js", "components/button/upload-form.js", "components/button/upload-form.js"], "exclude": ["node_modules"] } diff --git a/uploads/ActivityPool.sol b/uploads/ActivityPool.sol deleted file mode 100644 index daf3372..0000000 --- a/uploads/ActivityPool.sol +++ /dev/null @@ -1,1648 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) - -pragma solidity ^0.8.1; - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - (bool success, ) = recipient.call{value: amount}(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - string memory errorMessage - ) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling - * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. - * - * _Available since v4.8._ - */ - function verifyCallResultFromTarget( - address target, - bool success, - bytes memory returndata, - string memory errorMessage - ) internal view returns (bytes memory) { - if (success) { - if (returndata.length == 0) { - // only check isContract if the call was successful and the return data is empty - // otherwise we already know that it was a contract - require(isContract(target), "Address: call to non-contract"); - } - return returndata; - } else { - _revert(returndata, errorMessage); - } - } - - /** - * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason or using the provided one. - * - * _Available since v4.3._ - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - _revert(returndata, errorMessage); - } - } - - function _revert(bytes memory returndata, string memory errorMessage) private pure { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } -} - -// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) - - - -/** - * @dev Interface of the ERC20 standard as defined in the EIP. - */ -interface IERC20 { - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); - - /** - * @dev Returns the amount of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the amount of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves `amount` tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address to, uint256 amount) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 amount) external returns (bool); - - /** - * @dev Moves `amount` tokens from `from` to `to` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); - - - function mint(address account_, uint256 amount_) external; - - function burn(uint256 amount) external; - -} - - - - - - - - - - - -/** - * @dev Wrappers over Solidity's arithmetic operations with added overflow - * checks. - * - * Arithmetic operations in Solidity wrap on overflow. This can easily result - * in bugs, because programmers usually assume that an overflow raises an - * error, which is the standard behavior in high level programming languages. - * `SafeMath` restores this intuition by reverting the transaction when an - * operation overflows. - * - * Using this library instead of the unchecked operations eliminates an entire - * class of bugs, so it's recommended to use it always. - */ -library SafeMath { - /** - * @dev Returns the addition of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `+` operator. - * - * Requirements: - * - * - Addition cannot overflow. - */ - function add(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a, "SafeMath: addition overflow"); - - return c; - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting on - * overflow (when the result is negative). - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - return sub(a, b, "SafeMath: subtraction overflow"); - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting with custom message on - * overflow (when the result is negative). - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub( - uint256 a, - uint256 b, - string memory errorMessage - ) internal pure returns (uint256) { - require(b <= a, errorMessage); - uint256 c = a - b; - - return c; - } - - /** - * @dev Returns the multiplication of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `*` operator. - * - * Requirements: - * - * - Multiplication cannot overflow. - */ - function mul(uint256 a, uint256 b) internal pure returns (uint256) { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 - if (a == 0) { - return 0; - } - - uint256 c = a * b; - require(c / a == b, "SafeMath: multiplication overflow"); - - return c; - } - - /** - * @dev Returns the integer division of two unsigned integers. Reverts on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. Note: this function uses a - * `revert` opcode (which leaves remaining gas untouched) while Solidity - * uses an invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div(uint256 a, uint256 b) internal pure returns (uint256) { - return div(a, b, "SafeMath: division by zero"); - } - - /** - * @dev Returns the integer division of two unsigned integers. Reverts with custom message on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. Note: this function uses a - * `revert` opcode (which leaves remaining gas untouched) while Solidity - * uses an invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div( - uint256 a, - uint256 b, - string memory errorMessage - ) internal pure returns (uint256) { - require(b > 0, errorMessage); - uint256 c = a / b; - // assert(a == b * c + a % b); // There is no case in which this doesn't hold - - return c; - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * Reverts when dividing by zero. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function mod(uint256 a, uint256 b) internal pure returns (uint256) { - return mod(a, b, "SafeMath: modulo by zero"); - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * Reverts with custom message when dividing by zero. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function mod( - uint256 a, - uint256 b, - string memory errorMessage - ) internal pure returns (uint256) { - require(b != 0, errorMessage); - return a % b; - } - - // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) - function sqrrt(uint256 a) internal pure returns (uint256 c) { - if (a > 3) { - c = a; - uint256 b = add(div(a, 2), 1); - while (b < c) { - c = b; - b = div(add(div(a, b), b), 2); - } - } else if (a != 0) { - c = 1; - } - } - - /* - * Expects percentage to be trailed by 00, - */ - function percentageAmount(uint256 total_, uint8 percentage_) internal pure returns (uint256 percentAmount_) { - return div(mul(total_, percentage_), 1000); - } - - /* - * Expects percentage to be trailed by 00, - */ - function substractPercentage(uint256 total_, uint8 percentageToSub_) internal pure returns (uint256 result_) { - return sub(total_, div(mul(total_, percentageToSub_), 1000)); - } - - function percentageOfTotal(uint256 part_, uint256 total_) internal pure returns (uint256 percent_) { - return div(mul(part_, 100), total_); - } - - /** - * Taken from Hypersonic https://github.com/M2629/HyperSonic/blob/main/Math.sol - * @dev Returns the average of two numbers. The result is rounded towards - * zero. - */ - function average(uint256 a, uint256 b) internal pure returns (uint256) { - // (a + b) / 2 can overflow, so we distribute - return (a / 2) + (b / 2) + (((a % 2) + (b % 2)) / 2); - } - - function quadraticPricing(uint256 payment_, uint256 multiplier_) internal pure returns (uint256) { - return sqrrt(mul(multiplier_, payment_)); - } - - function bondingCurve(uint256 supply_, uint256 multiplier_) internal pure returns (uint256) { - return mul(multiplier_, supply_); - } -} - -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol) - - - - - -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) - - - -/** - * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in - * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - * - * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by - * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't - * need to send a transaction, and thus is not required to hold Ether at all. - */ -interface IERC20Permit { - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * - * IMPORTANT: The same issues {IERC20-approve} has related to transaction - * ordering also apply here. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - * - * For more information on the signature format, see the - * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP - * section]. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256); - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32); -} - - - -/** - * @title SafeERC20 - * @dev Wrappers around ERC20 operations that throw on failure (when the token - * contract returns false). Tokens that return no value (and instead revert or - * throw on failure) are also supported, non-reverting calls are assumed to be - * successful. - * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, - * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. - */ -library SafeERC20 { - using Address for address; - - function safeTransfer( - IERC20 token, - address to, - uint256 value - ) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); - } - - function safeTransferFrom( - IERC20 token, - address from, - address to, - uint256 value - ) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); - } - - /** - * @dev Deprecated. This function has issues similar to the ones found in - * {IERC20-approve}, and its usage is discouraged. - * - * Whenever possible, use {safeIncreaseAllowance} and - * {safeDecreaseAllowance} instead. - */ - function safeApprove( - IERC20 token, - address spender, - uint256 value - ) internal { - // safeApprove should only be called when setting an initial allowance, - // or when resetting it to zero. To increase and decrease it, use - // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' - require( - (value == 0) || (token.allowance(address(this), spender) == 0), - "SafeERC20: approve from non-zero to non-zero allowance" - ); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); - } - - function safeIncreaseAllowance( - IERC20 token, - address spender, - uint256 value - ) internal { - uint256 newAllowance = token.allowance(address(this), spender) + value; - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); - } - - function safeDecreaseAllowance( - IERC20 token, - address spender, - uint256 value - ) internal { - unchecked { - uint256 oldAllowance = token.allowance(address(this), spender); - require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); - uint256 newAllowance = oldAllowance - value; - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); - } - } - - function safePermit( - IERC20Permit token, - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) internal { - uint256 nonceBefore = token.nonces(owner); - token.permit(owner, spender, value, deadline, v, r, s); - uint256 nonceAfter = token.nonces(owner); - require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); - } - - /** - * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - * on the return value: the return value is optional (but if data is returned, it must not be false). - * @param token The token targeted by the call. - * @param data The call data (encoded using abi.encode or one of its variants). - */ - function _callOptionalReturn(IERC20 token, bytes memory data) private { - // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since - // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that - // the target address contains contract code and also asserts for success in the low-level call. - - bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); - if (returndata.length > 0) { - // Return data is optional - require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); - } - } -} - - - - - - - - - - - - - - -/** - * @dev Provides information about the current execution context, including the - * sender of the transaction and its data. While these are generally available - * via msg.sender and msg.data, they should not be accessed in such a direct - * manner, since when dealing with meta-transactions the account sending and - * paying for execution may not be the actual sender (as far as an application - * is concerned). - * - * This contract is only required for intermediate, library-like contracts. - */ -abstract contract Context { - function _msgSender() internal view virtual returns (address) { - return msg.sender; - } - - function _msgData() internal view virtual returns (bytes calldata) { - return msg.data; - } -} - - -/** - * @dev Contract module which provides a basic access control mechanism, where - * there is an account (an owner) that can be granted exclusive access to - * specific functions. - * - * By default, the owner account will be the one that deploys the contract. This - * can later be changed with {transferOwnership}. - * - * This module is used through inheritance. It will make available the modifier - * `onlyOwner`, which can be applied to your functions to restrict their use to - * the owner. - */ -abstract contract Ownable is Context { - address private _owner; - - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - /** - * @dev Initializes the contract setting the deployer as the initial owner. - */ - constructor() { - _transferOwnership(_msgSender()); - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - _checkOwner(); - _; - } - - /** - * @dev Returns the address of the current owner. - */ - function owner() public view virtual returns (address) { - return _owner; - } - - /** - * @dev Throws if the sender is not the owner. - */ - function _checkOwner() internal view virtual { - require(owner() == _msgSender(), "Ownable: caller is not the owner"); - } - - /** - * @dev Leaves the contract without owner. It will not be possible to call - * `onlyOwner` functions anymore. Can only be called by the current owner. - * - * NOTE: Renouncing ownership will leave the contract without an owner, - * thereby removing any functionality that is only available to the owner. - */ - function renounceOwnership() public virtual onlyOwner { - _transferOwnership(address(0)); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Can only be called by the current owner. - */ - function transferOwnership(address newOwner) public virtual onlyOwner { - require(newOwner != address(0), "Ownable: new owner is the zero address"); - _transferOwnership(newOwner); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Internal function without access restriction. - */ - function _transferOwnership(address newOwner) internal virtual { - address oldOwner = _owner; - _owner = newOwner; - emit OwnershipTransferred(oldOwner, newOwner); - } -} - - -contract Operator is Ownable { - address public operator; - uint256 constant baseRate = 10000; - - modifier onlyOperator { - require(msg.sender == owner() || msg.sender == operator, "no permission"); - _; - } - - function setOperator(address operator_) external onlyOwner { - operator = operator_; - } - - - function getCurrTime() external view returns(uint256) { - return block.timestamp; - } - - function getBlockNum() external view returns(uint256) { - return block.number; - } - -} - - - - - -/** - * @dev Library for managing - * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive - * types. - * - * Sets have the following properties: - * - * - Elements are added, removed, and checked for existence in constant time - * (O(1)). - * - Elements are enumerated in O(n). No guarantees are made on the ordering. - * - * ``` - * contract Example { - * // Add the library methods - * using EnumerableSet for EnumerableSet.AddressSet; - * - * // Declare a set state variable - * EnumerableSet.AddressSet private mySet; - * } - * ``` - * - * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) - * and `uint256` (`UintSet`) are supported. - * - * [WARNING] - * ==== - * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure unusable. - * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. - * - * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an array of EnumerableSet. - * ==== - */ - -library EnumerableSet { - // To implement this library for multiple types with as little code - // repetition as possible, we write it in terms of a generic Set type with - // bytes32 values. - // The Set implementation uses private functions, and user-facing - // implementations (such as AddressSet) are just wrappers around the - // underlying Set. - // This means that we can only create new EnumerableSets for types that fit - // in bytes32. - - struct Set { - // Storage of set values - bytes32[] _values; - // Position of the value in the `values` array, plus 1 because index 0 - // means a value is not in the set. - mapping(bytes32 => uint256) _indexes; - } - - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function _add(Set storage set, bytes32 value) private returns (bool) { - if (!_contains(set, value)) { - set._values.push(value); - // The value is stored at length-1, but we add 1 to all indexes - // and use 0 as a sentinel value - set._indexes[value] = set._values.length; - return true; - } else { - return false; - } - } - - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function _remove(Set storage set, bytes32 value) private returns (bool) { - // We read and store the value's index to prevent multiple reads from the same storage slot - uint256 valueIndex = set._indexes[value]; - - if (valueIndex != 0) { - // Equivalent to contains(set, value) - // To delete an element from the _values array in O(1), we swap the element to delete with the last one in - // the array, and then remove the last element (sometimes called as 'swap and pop'). - // This modifies the order of the array, as noted in {at}. - - uint256 toDeleteIndex = valueIndex - 1; - uint256 lastIndex = set._values.length - 1; - - if (lastIndex != toDeleteIndex) { - bytes32 lastValue = set._values[lastIndex]; - - // Move the last value to the index where the value to delete is - set._values[toDeleteIndex] = lastValue; - // Update the index for the moved value - set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex - } - - // Delete the slot where the moved value was stored - set._values.pop(); - - // Delete the index for the deleted slot - delete set._indexes[value]; - - return true; - } else { - return false; - } - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function _contains(Set storage set, bytes32 value) private view returns (bool) { - return set._indexes[value] != 0; - } - - /** - * @dev Returns the number of values on the set. O(1). - */ - function _length(Set storage set) private view returns (uint256) { - return set._values.length; - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function _at(Set storage set, uint256 index) private view returns (bytes32) { - return set._values[index]; - } - - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function _values(Set storage set) private view returns (bytes32[] memory) { - return set._values; - } - - // Bytes32Set - - struct Bytes32Set { - Set _inner; - } - - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { - return _add(set._inner, value); - } - - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { - return _remove(set._inner, value); - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { - return _contains(set._inner, value); - } - - /** - * @dev Returns the number of values in the set. O(1). - */ - function length(Bytes32Set storage set) internal view returns (uint256) { - return _length(set._inner); - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { - return _at(set._inner, index); - } - - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { - return _values(set._inner); - } - - // AddressSet - - struct AddressSet { - Set _inner; - } - - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(AddressSet storage set, address value) internal returns (bool) { - return _add(set._inner, bytes32(uint256(uint160(value)))); - } - - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function remove(AddressSet storage set, address value) internal returns (bool) { - return _remove(set._inner, bytes32(uint256(uint160(value)))); - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function contains(AddressSet storage set, address value) internal view returns (bool) { - return _contains(set._inner, bytes32(uint256(uint160(value)))); - } - - /** - * @dev Returns the number of values in the set. O(1). - */ - function length(AddressSet storage set) internal view returns (uint256) { - return _length(set._inner); - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(AddressSet storage set, uint256 index) internal view returns (address) { - return address(uint160(uint256(_at(set._inner, index)))); - } - - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function values(AddressSet storage set) internal view returns (address[] memory) { - bytes32[] memory store = _values(set._inner); - address[] memory result; - - /// @solidity memory-safe-assembly - assembly { - result := store - } - - return result; - } - - // UintSet - - struct UintSet { - Set _inner; - } - - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(UintSet storage set, uint256 value) internal returns (bool) { - return _add(set._inner, bytes32(value)); - } - - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function remove(UintSet storage set, uint256 value) internal returns (bool) { - return _remove(set._inner, bytes32(value)); - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function contains(UintSet storage set, uint256 value) internal view returns (bool) { - return _contains(set._inner, bytes32(value)); - } - - /** - * @dev Returns the number of values on the set. O(1). - */ - function length(UintSet storage set) internal view returns (uint256) { - return _length(set._inner); - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(UintSet storage set, uint256 index) internal view returns (uint256) { - return uint256(_at(set._inner, index)); - } - - /** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function values(UintSet storage set) internal view returns (uint256[] memory) { - bytes32[] memory store = _values(set._inner); - uint256[] memory result; - - /// @solidity memory-safe-assembly - assembly { - result := store - } - - return result; - } -} - - -contract ActivityPool is Operator { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using EnumerableSet for EnumerableSet.AddressSet; - using EnumerableSet for EnumerableSet.UintSet; - - event AddNewAct(uint256 id); - event Bet(address user, uint256 pID, uint256 bType, uint256 amount, uint256 time); - event SetPrize(uint256 pID, uint256 bType); - event Claim(address user, uint256 pID, uint256 bType, uint256 amount); - event RemovePeriodAct(uint256 pID); - - uint256 public constant muti = 1e18; - uint256 public periodID; - uint256 public betID; - - struct UserInfo { - address user; - uint256 pID; - uint256 amount; - uint256 betTime; - uint256 bType; - uint256 claimAmount; - } - - struct ActInfo { - string name; - string aName; - string bName; - address rewardToken; - uint256 startTime; - uint256 endTime; - uint256 minAmount; - uint256 maxAmount; - uint256 aAmount; - uint256 bAmount; - uint256 winType; - bool isBet; - } - - struct TimeInfo { - uint256 time; - uint256 aAmount; - uint256 bAmount; - } - - struct BestInfo { - address aUser; - address bUser; - uint256 aBet; - uint256 bBet; - } - - mapping(uint256 => BestInfo) bestInfo; - mapping(uint256 => uint256) public timeID; - mapping(uint256 => mapping(uint256 => TimeInfo)) public timeInfo; - mapping(address => mapping(uint256 => uint256)) public userBet; - mapping(uint256 => UserInfo) public betInfo; - mapping(uint256 => uint256) public perAmount; - mapping(uint256 => ActInfo) public actInfo; - mapping(uint256 => mapping(uint256 => string)) public descripName; - - mapping(address => EnumerableSet.UintSet) userPid; - mapping(uint256 => EnumerableSet.AddressSet) aUsers; - mapping(uint256 => EnumerableSet.AddressSet) bUsers; - EnumerableSet.UintSet activePeriod; - EnumerableSet.UintSet removePeriod; - - function addNewAct( - string memory name, - string memory aName, - string memory bName, - address token, - uint256 startTime, - uint256 endTime, - uint256 minAmount, - uint256 maxAmount - ) external onlyOperator { - - checkAdd(msg.sender, startTime, endTime, minAmount, maxAmount); - uint256 id = ++periodID; - actInfo[id].name = name; - actInfo[id].aName = aName; - actInfo[id].bName = bName; - actInfo[id].startTime = startTime; - actInfo[id].endTime = endTime; - actInfo[id].minAmount = minAmount; - actInfo[id].maxAmount = maxAmount; - actInfo[id].rewardToken = token; - descripName[id][1] = aName; - descripName[id][2] = bName; - activePeriod.add(id); - - emit AddNewAct(id); - } - - - function removePeriodAct(uint256 pID) external onlyOperator { - checkRemove(pID); - - activePeriod.remove(pID); - removePeriod.add(pID); - - emit RemovePeriodAct(pID); - } - - - function bet(uint256 pID, uint256 bType, uint256 amount) external payable { - checkBet(msg.sender, pID, bType, amount); - if(actInfo[pID].rewardToken == address(0)) { - require(msg.value == amount, "value err"); - } else { - require(msg.value == 0, "value amount err"); - } - - if(bType == 1) { - if(amount > bestInfo[pID].aBet) { - bestInfo[pID].aUser = msg.sender; - bestInfo[pID].aBet = amount; - } - } else { - if(amount > bestInfo[pID].bBet) { - bestInfo[pID].bUser = msg.sender; - bestInfo[pID].bBet = amount; - } - } - - userPid[msg.sender].add(pID); - uint256 id = ++betID; - userBet[msg.sender][pID] = id; - - if(bType == 1) { - aUsers[pID].add(msg.sender); - actInfo[pID].aAmount = actInfo[pID].aAmount.add(amount); - } else { - bUsers[pID].add(msg.sender); - actInfo[pID].bAmount = actInfo[pID].bAmount.add(amount); - } - - actInfo[pID].isBet = true; - betInfo[id].user = msg.sender; - betInfo[id].pID = pID; - betInfo[id].amount = amount; - betInfo[id].betTime = block.timestamp; - betInfo[id].bType = bType; - - uint256 tID = ++timeID[pID]; - timeInfo[pID][tID].time = block.timestamp; - timeInfo[pID][tID].aAmount = actInfo[pID].aAmount; - timeInfo[pID][tID].bAmount = actInfo[pID].bAmount; - - if(actInfo[pID].rewardToken != address(0)) { - IERC20(actInfo[pID].rewardToken).safeTransferFrom(msg.sender, address(this), amount); - } - emit Bet(msg.sender, pID, bType, amount, betInfo[id].betTime); - } - - function setPrize(uint256 pID, uint256 bType) external onlyOperator { - checkSetPrize(msg.sender, pID, bType); - - if(block.timestamp <= actInfo[pID].startTime) { - actInfo[pID].startTime = block.timestamp; - - } - actInfo[pID].endTime = block.timestamp; - - actInfo[pID].winType = bType; - perAmount[pID] = getPerAmount(pID, bType); - - emit SetPrize(pID, bType); - } - - function recive() public payable {} - - function claim(uint256 pID) external { - uint256 amount = checkClaim(msg.sender, pID); - - uint256 id = userBet[msg.sender][pID]; - betInfo[id].claimAmount = amount; - if(actInfo[pID].rewardToken != address(0)) { - IERC20(actInfo[pID].rewardToken).safeTransfer(msg.sender, amount); - } else { - payable(msg.sender).transfer(amount); - } - - emit Claim(msg.sender, pID, betInfo[id].bType, amount); - } - - function checkClaim(address user, uint256 pID) public view returns(uint256 amount) { - require(userPid[user].contains(pID), "not bet"); - uint256 id = userBet[user][pID]; - require(betInfo[id].user == user, "not user"); - require(actInfo[pID].winType != 0, "not prize"); - require(betInfo[id].claimAmount == 0, "has claim"); - - if(actInfo[pID].winType != 3) { - require(actInfo[pID].winType == betInfo[id].bType, "not right"); - amount = perAmount[pID].mul(betInfo[id].amount).div(muti); - require(amount > 0, "no amount"); - } else { - amount = betInfo[id].amount; - } - - return amount; - } - - function getActualRate( - address user, - uint256 pID, - uint256 bType, - uint256 amount - ) external view returns(uint256, uint256, bool) { - if(bType < 1 || bType > 2 || !activePeriod.contains(pID)) { - return (0, 0, false); - } else if(userBet[user][pID] != 0 || actInfo[pID].winType != 0 || amount == 0) { - (uint256 ra, uint256 rb) = getRate(pID, timeID[pID]); - return (ra, rb, false); - } else { - (uint256 aAmount, uint256 bAmount) = (actInfo[pID].aAmount, actInfo[pID].bAmount); - if(bType == 1) { - aAmount = aAmount.add(amount); - } else { - bAmount = bAmount.add(amount); - } - - uint256 total = aAmount.add(bAmount); - if(bAmount == 0 && aAmount != 0) { - return (muti, 0, true); - } - if(bAmount != 0 && aAmount == 0) { - return (0, muti, true); - } - return (total.mul(muti).div(aAmount), total.mul(muti).div(bAmount), true); - } - } - - function getRate(uint256 pID, uint256 tID) public view returns(uint256, uint256) { - (uint256 aAmount, uint256 bAmount) = (timeInfo[pID][tID].aAmount, timeInfo[pID][tID].bAmount); - uint256 total = aAmount.add(bAmount); - if(total == 0) { - return (0, 0); - } - if(bAmount == 0 && aAmount != 0) { - return (muti, 0); - } - if(bAmount != 0 && aAmount == 0) { - return (0, muti); - } - return (total.mul(muti).div(aAmount), total.mul(muti).div(bAmount)); - } - - function getBestInfo(uint256 pID) - external - view - returns(address aUser, address bUser, uint256 aBet, uint256 bBet, uint256 aReward, uint256 bReward) - { - aUser = bestInfo[pID].aUser; - bUser = bestInfo[pID].bUser; - aBet = bestInfo[pID].aBet; - bBet = bestInfo[pID].bBet; - aReward = getPerAmount(pID, 1).mul(aBet).div(muti); - bReward = getPerAmount(pID, 2).mul(bBet).div(muti); - } - - function getPerAmount(uint256 pID, uint256 bType) public view returns(uint256) { - uint256 total = actInfo[pID].aAmount.add(actInfo[pID].bAmount); - if(total == 0) { - return 0; - } - - if(actInfo[pID].winType == 3) { - return muti; - } - - if(bType == 1) { - if(actInfo[pID].aAmount == 0 || actInfo[pID].winType == 2) { - return 0; - } - return total.mul(muti).div(actInfo[pID].aAmount); - } - - if(bType == 2) { - if(actInfo[pID].bAmount == 0 || actInfo[pID].winType == 1) { - return 0; - } - return total.mul(muti).div(actInfo[pID].bAmount); - } - - return 0; - - } - - function checkSetPrize(address user, uint256 pID, uint256 bType) public view returns(bool) { - require(user == owner() || user == operator, "no permission"); - require(bType > 0 && bType <= 3, "type err"); - require(activePeriod.contains(pID), "not active"); - require(actInfo[pID].winType == 0, "has set"); - - return true; - } - - function checkBet( - address user, - uint256 pID, - uint256 bType, - uint256 amount - ) public view returns(bool) { - require(activePeriod.contains(pID), "not active"); - require(actInfo[pID].startTime <= block.timestamp, "not start"); - require(actInfo[pID].endTime > block.timestamp, "has end"); - require(!userPid[user].contains(pID), "has bet"); - require(bType > 0 && bType < 3, "type err"); - require(amount >= actInfo[pID].minAmount && amount <= actInfo[pID].maxAmount, "amount err"); - if(actInfo[pID].rewardToken != address(0)) { - require(IERC20(actInfo[pID].rewardToken).allowance(user, address(this)) >= amount, "not approve enough"); - require(IERC20(actInfo[pID].rewardToken).balanceOf(user) >= amount, "not enough"); - } else { - require(user.balance >= amount, "main token err"); - } - - return true; - } - - - function checkRemove(uint256 pID) public view returns(bool) { - require(activePeriod.contains(pID), "not in"); - require(!actInfo[pID].isBet, "has bet"); - - return true; - } - - function checkAdd( - address user, - uint256 startTime, - uint256 endTime, - uint256 minAmount, - uint256 maxAmount - ) public view returns(bool) { - require(user == owner() || user == operator, "no permission"); - require(startTime > block.timestamp, "startTime err"); - require(endTime > startTime, "endTime err"); - require(minAmount > 0 && maxAmount > minAmount, "amount err"); - - return true; - } - - function getStatus(uint256 pID) external view returns(uint256) { - if(!removePeriod.contains(pID) && !activePeriod.contains(pID)) { - return 5; - } else if(removePeriod.contains(pID)) { - return 6; - } else if(block.timestamp < actInfo[pID].startTime) { - return 1; - } else if ( - actInfo[pID].startTime <= block.timestamp && - actInfo[pID].endTime > block.timestamp - ) { - return 2; - } else if (actInfo[pID].endTime <= block.timestamp && actInfo[pID].winType == 0){ - return 3; - } else if(actInfo[pID].winType != 0) { - return 4; - } - return 0; - } - - function getUserPidNum(address user) external view returns(uint256) { - return userPid[user].length(); - } - - function getUserPid(address user, uint256 index) external view returns(uint256) { - return userPid[user].at(index); - } - - function getAUsersNum(uint256 pID) external view returns(uint256) { - return aUsers[pID].length(); - } - - function getAUsers(uint256 pID, uint256 index) external view returns(address) { - return aUsers[pID].at(index); - } - - function getBUsersNum(uint256 pID) external view returns(uint256) { - return bUsers[pID].length(); - } - - function getBUsers(uint256 pID, uint256 index) external view returns(address) { - return bUsers[pID].at(index); - } - - function getActivePeriodNum() external view returns(uint256) { - return activePeriod.length(); - } - - function getActivePeriod(uint256 index) external view returns(uint256) { - return activePeriod.at(index); - } - - function getRemovePeriodNum() external view returns(uint256) { - return removePeriod.length(); - } - - function getRemovePeriod(uint256 index) external view returns(uint256) { - return removePeriod.at(index); - } -} \ No newline at end of file