feat: unify real activity tracking, admin monitoring, and error UX

This commit is contained in:
Stalin
2026-04-19 17:50:53 +05:30
parent cfc159d105
commit 9115fc5ffd
86 changed files with 9002 additions and 2838 deletions
+107
View File
@@ -0,0 +1,107 @@
from datetime import datetime, timezone
from typing import Any, Dict, Optional
import jwt
def _decode_token_unverified(token: str) -> Dict[str, Any]:
try:
return jwt.decode(
token,
options={"verify_signature": False},
algorithms=["HS256", "RS256"],
)
except Exception:
return {}
def resolve_user_identity(request, db=None) -> Dict[str, Optional[str]]:
"""Best-effort identity resolution from auth header, headers, payload, and optional DB lookup."""
token = None
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
token = auth_header.split(" ", 1)[1]
payload = _decode_token_unverified(token) if token else {}
request_json = request.get_json(silent=True) or {}
user_id = (
payload.get("user_id")
or payload.get("sub")
or payload.get("uid")
or request.headers.get("X-User-ID")
or request.args.get("user_id")
or request_json.get("user_id")
)
wallet_address = (
payload.get("wallet_address")
or request.headers.get("X-Wallet-Address")
or request.args.get("wallet_address")
or request_json.get("wallet_address")
)
email = (
payload.get("email")
or request.headers.get("X-User-Email")
or request.args.get("email")
or request_json.get("email")
)
# Prefer wallet as canonical identity when available.
if wallet_address:
wallet_address = str(wallet_address).lower().strip()
user_id = wallet_address
# If only email is known and DB exists, resolve to canonical user id.
if not user_id and email and db is not None:
user = db.users.find_one({"email": str(email).lower().strip()})
if user:
if user.get("wallet_address"):
user_id = str(user.get("wallet_address")).lower().strip()
wallet_address = user_id
elif user.get("_id"):
user_id = str(user.get("_id"))
if user_id:
user_id = str(user_id).strip()
return {
"user_id": user_id,
"wallet_address": wallet_address,
"email": str(email).lower().strip() if email else None,
}
def log_user_activity(
db,
user_id: Optional[str],
activity_type: str,
title: str,
description: str,
metadata: Optional[Dict[str, Any]] = None,
points_earned: int = 0,
) -> bool:
"""Write a user activity event. Returns True on success, False otherwise."""
if not user_id:
return False
now_utc = datetime.now(timezone.utc)
doc = {
"user_id": str(user_id),
"type": activity_type,
"title": title,
"description": description,
"occurred_at": now_utc,
"completed_at": now_utc,
"timestamp_utc": now_utc.strftime("%Y-%m-%d %H:%M:%S UTC"),
"points_earned": int(points_earned or 0),
"metadata": metadata or {},
"source": "user_activity_events",
}
try:
db.user_activity_events.insert_one(doc)
return True
except Exception:
return False
File diff suppressed because one or more lines are too long
+5 -6
View File
@@ -1,8 +1,8 @@
{
"contract_address": "0xC2FE2F49B3a1384aEdFAae127F054FAf216eF684",
"transaction_hash": "0xfe5a433dae316bd2d60b7190c21866a1fde30777f08d9d37e403ed642433fa28",
"contract_address": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"transaction_hash": "973fa79fea65613ef2ccbb35d72ee0cabee2cf3a5bc834a9dc439fef544ace7d",
"deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"network": "local",
"network": "anvil",
"abi": [
{
"type": "constructor",
@@ -684,7 +684,6 @@
"anonymous": false
}
],
"gas_used": 3387337,
"block_number": 22994809,
"status": 1
"gas_used": 3391283,
"block_number": 1
}
+299 -8
View File
@@ -5,7 +5,7 @@ import uuid
import random
import string
from datetime import datetime, timedelta
from flask import Flask, jsonify, request
from flask import Flask, jsonify, request, make_response, g
from flask_cors import CORS
from flask_jwt_extended import JWTManager, jwt_required, get_jwt_identity, create_access_token
from dotenv import load_dotenv
@@ -21,6 +21,14 @@ from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
import secrets
import re
import json
import jwt as pyjwt
try:
import psutil
except Exception:
psutil = None
# Load environment variables
load_dotenv()
@@ -211,29 +219,312 @@ app.config.update(
# ✅ Initialize JWT with your configuration
jwt = JWTManager(app)
# ✅ ENHANCED CORS configuration for professional dashboard
# ✅ ENHANCED CORS configuration - Allow all localhost ports for development
CORS(app, resources={r"/api/*": {
"origins": [
"http://localhost:3000",
"http://localhost:3001",
"http://localhost:3002",
"http://localhost:3003",
"http://localhost:3004",
"http://localhost:3005",
"http://localhost:3006",
"http://127.0.0.1:3000",
"http://localhost:3001", # Development
"https://openlearnx.vercel.app" # Production (if deployed)
"http://127.0.0.1:3001",
"http://127.0.0.1:3002",
"http://127.0.0.1:3003",
"http://127.0.0.1:3004",
"http://127.0.0.1:3005",
"http://127.0.0.1:3006",
"https://openlearnx.vercel.app"
],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"],
"allow_headers": [
"Content-Type",
"Authorization",
"Accept",
"Origin",
"X-Requested-With",
"X-User-ID", # Custom header for user identification
"X-User-ID",
"X-Session-Token",
"X-Firebase-Token" # Firebase authentication
"X-Firebase-Token"
],
"expose_headers": ["Authorization", "X-Total-Count", "X-Rate-Limit", "Content-Type"],
"supports_credentials": True,
"expose_headers": ["Authorization", "X-Total-Count", "X-Rate-Limit"]
"max_age": 3600
}})
# ✅ Handle CORS preflight requests with explicit route
@app.before_request
def handle_preflight():
if request.method == "OPTIONS":
response = make_response()
response.headers.add("Access-Control-Allow-Origin", request.headers.get("Origin", "*"))
response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization,Accept,Origin,X-Requested-With")
response.headers.add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS,PATCH,HEAD")
response.headers.add("Access-Control-Max-Age", "3600")
response.headers.add("Access-Control-Allow-Credentials", "true")
return response, 200
SUSPICIOUS_PAYLOAD_PATTERNS = [
re.compile(r"(<script|javascript:|onerror=|onload=)", re.IGNORECASE),
re.compile(r"(\$where|\$regex|\$ne|\$gt|\$lt|\$or)", re.IGNORECASE),
re.compile(r"(union\s+select|drop\s+table|insert\s+into|delete\s+from)", re.IGNORECASE),
re.compile(r"(\.\./|%2e%2e%2f|/etc/passwd)", re.IGNORECASE)
]
def _detect_suspicious_payload(payload_text):
if not payload_text:
return []
matches = []
for pattern in SUSPICIOUS_PAYLOAD_PATTERNS:
if pattern.search(payload_text):
matches.append(pattern.pattern)
return matches
def _infer_event_type(path, method, status_code, suspicious=False):
if suspicious:
return "suspicious_payload"
if status_code == 403:
return "forbidden_access"
if "/api/auth/register" in path:
return "signup"
if "/api/auth/login" in path or "/api/auth/verify" in path or "/api/auth/wallet-login" in path:
return "signin"
if "/api/admin" in path:
return "admin_panel"
if "join" in path or "enroll" in path:
return "course_join"
if "attend" in path:
return "attendance"
if method == "GET":
return "page_visit"
return "api_activity"
def _firewall_rule_matches(rule, ip, method, path):
rule_ip = (rule.get("ip") or "").strip()
rule_method = (rule.get("method") or "").strip().upper()
path_pattern = (rule.get("path_pattern") or "").strip()
if rule_ip and rule_ip != ip:
return False
if rule_method and rule_method != method.upper():
return False
if path_pattern and path_pattern not in path:
return False
return True
@app.before_request
def enforce_manual_firewall_rules():
"""Apply admin-defined firewall rules only when manually configured."""
if request.method == "OPTIONS":
return None
# Keep firewall rule management reachable so admins can recover from bad rules.
if request.path.startswith("/api/admin/firewall"):
return None
try:
db = get_db()
if db is None:
return None
ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() if request.headers.get("X-Forwarded-For") else (request.remote_addr or "unknown")
method = request.method
path = request.path
rules = list(db.firewall_rules.find({"enabled": True}).sort("created_at", 1))
for rule in rules:
if not _firewall_rule_matches(rule, ip, method, path):
continue
action = (rule.get("action") or "block").strip().lower()
if action == "allow":
return None
# Manual block rule matched.
db.security_logs.insert_one({
"timestamp": datetime.utcnow(),
"event_type": "firewall_block",
"action": f"{method} {path}",
"status_code": 403,
"severity": "warning",
"path": path,
"method": method,
"ip": ip,
"user_agent": request.headers.get("User-Agent", ""),
"metadata": {
"rule_id": str(rule.get("_id")),
"rule_name": rule.get("name", ""),
"reason": "manual_firewall_rule"
}
})
return jsonify({"error": "Blocked by firewall rule"}), 403
except Exception as e:
logger.debug(f"Firewall check skipped: {e}")
return None
@app.before_request
def capture_request_start_and_payload():
g.request_start_time = time.time()
g.suspicious_matches = []
if request.method == "OPTIONS":
return None
try:
raw_payload = request.get_data(cache=True, as_text=True)[:4000]
except Exception:
raw_payload = ""
request_json = None
try:
request_json = request.get_json(silent=True)
except Exception:
request_json = None
request_form = {}
try:
request_form = {k: request.form.get(k) for k in request.form.keys()}
except Exception:
request_form = {}
request_headers = {}
try:
for key, value in request.headers.items():
lower = key.lower()
if lower in {"authorization", "cookie", "set-cookie"}:
request_headers[key] = "[redacted]"
else:
request_headers[key] = value
except Exception:
request_headers = {}
g.request_payload_preview = raw_payload
g.request_json_payload = request_json
g.request_form_payload = request_form
g.request_headers_snapshot = request_headers
g.suspicious_matches = _detect_suspicious_payload(raw_payload)
@app.after_request
def write_request_audit_log(response):
try:
db = get_db()
if db is None:
return response
start = getattr(g, "request_start_time", None)
duration_ms = int((time.time() - start) * 1000) if start else 0
ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() if request.headers.get("X-Forwarded-For") else (request.remote_addr or "unknown")
suspicious_matches = getattr(g, "suspicious_matches", [])
suspicious = len(suspicious_matches) > 0
request_payload_preview = getattr(g, "request_payload_preview", "")
request_json_payload = getattr(g, "request_json_payload", None)
request_form_payload = getattr(g, "request_form_payload", {})
request_headers_snapshot = getattr(g, "request_headers_snapshot", {})
auth_user_id = None
auth_wallet_address = None
auth_email = None
try:
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
token = auth_header.split(" ", 1)[1]
decoded = pyjwt.decode(
token,
options={"verify_signature": False},
algorithms=["HS256", "RS256"],
)
auth_user_id = decoded.get("user_id") or decoded.get("sub") or decoded.get("uid")
auth_wallet_address = decoded.get("wallet_address")
auth_email = decoded.get("email")
except Exception:
auth_user_id = None
response_body_preview = ""
try:
response_body_preview = response.get_data(as_text=True)[:4000]
except Exception:
response_body_preview = ""
response_content_type = response.headers.get("Content-Type", "")
parsed_response_json = None
if response_body_preview and "json" in response_content_type.lower():
try:
parsed_response_json = json.loads(response_body_preview)
except Exception:
parsed_response_json = None
system_usage = {}
if psutil is not None:
try:
vm = psutil.virtual_memory()
system_usage = {
"cpu_percent": psutil.cpu_percent(interval=None),
"memory_percent": vm.percent,
"memory_used_mb": round(vm.used / (1024 * 1024), 2),
"memory_available_mb": round(vm.available / (1024 * 1024), 2),
}
except Exception:
system_usage = {}
event_type = _infer_event_type(request.path, request.method, response.status_code, suspicious=suspicious)
action = f"{request.method} {request.path}"
log_doc = {
"timestamp": datetime.utcnow(),
"event_type": event_type,
"action": action,
"status_code": int(response.status_code),
"severity": "warning" if suspicious or response.status_code >= 400 else "info",
"path": request.path,
"method": request.method,
"query": dict(request.args),
"ip": ip,
"user_agent": request.headers.get("User-Agent", ""),
"origin": request.headers.get("Origin", ""),
"duration_ms": duration_ms,
"metadata": {
"suspicious_matches": suspicious_matches,
"content_type": request.headers.get("Content-Type", ""),
"request_body": request_payload_preview,
"response_body": response_body_preview,
"request_details": {
"query": dict(request.args),
"json": request_json_payload,
"form": request_form_payload,
"headers": request_headers_snapshot,
"content_length": request.content_length,
},
"response_details": {
"content_type": response_content_type,
"content_length": response.calculate_content_length(),
"json": parsed_response_json,
},
"usage": system_usage,
"duration_ms": duration_ms,
"auth_user_id": auth_user_id,
"auth_wallet_address": auth_wallet_address,
"auth_email": auth_email,
}
}
db.security_logs.insert_one(log_doc)
except Exception as e:
logger.debug(f"Audit log write skipped: {e}")
return response
# Enhanced logging with your configuration
logging.basicConfig(
level=logging.INFO,
+1 -1
View File
@@ -1 +1 @@
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220086786c2e82d490ba62f8e12df9157f5736c053e7cdd84543b9d7051b13134e364736f6c634300081e0033","sourceMap":"194:9169:10:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220086786c2e82d490ba62f8e12df9157f5736c053e7cdd84543b9d7051b13134e364736f6c634300081e0033","sourceMap":"194:9169:10:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"Collection of functions related to the address type\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/Address.sol\":\"Address\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931\",\"dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/Address.sol":"Address"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa","urls":["bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931","dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm"],"license":"MIT"}},"version":1},"id":10}
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220f45d6402490a29b9824fef150a4c5d3dced1f2cbcfad209feaeafb4ab56e45d664736f6c63430008210033","sourceMap":"194:9169:10:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220f45d6402490a29b9824fef150a4c5d3dced1f2cbcfad209feaeafb4ab56e45d664736f6c63430008210033","sourceMap":"194:9169:10:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"Collection of functions related to the address type\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/Address.sol\":\"Address\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931\",\"dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/Address.sol":"Address"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa","urls":["bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931","dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm"],"license":"MIT"}},"version":1},"id":10}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"abi":[],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"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.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/Context.sol\":\"Context\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/Context.sol\":{\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6df0ddf21ce9f58271bdfaa85cde98b200ef242a05a3f85c2bc10a8294800a92\",\"dweb:/ipfs/QmRK2Y5Yc6BK7tGKkgsgn3aJEQGi5aakeSPZvS65PV8Xp3\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/Context.sol":"Context"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/Context.sol":{"keccak256":"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7","urls":["bzz-raw://6df0ddf21ce9f58271bdfaa85cde98b200ef242a05a3f85c2bc10a8294800a92","dweb:/ipfs/QmRK2Y5Yc6BK7tGKkgsgn3aJEQGi5aakeSPZvS65PV8Xp3"],"license":"MIT"}},"version":1},"id":11}
{"abi":[],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"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.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/Context.sol\":\"Context\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/Context.sol\":{\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6df0ddf21ce9f58271bdfaa85cde98b200ef242a05a3f85c2bc10a8294800a92\",\"dweb:/ipfs/QmRK2Y5Yc6BK7tGKkgsgn3aJEQGi5aakeSPZvS65PV8Xp3\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/Context.sol":"Context"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/Context.sol":{"keccak256":"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7","urls":["bzz-raw://6df0ddf21ce9f58271bdfaa85cde98b200ef242a05a3f85c2bc10a8294800a92","dweb:/ipfs/QmRK2Y5Yc6BK7tGKkgsgn3aJEQGi5aakeSPZvS65PV8Xp3"],"license":"MIT"}},"version":1},"id":11}
+1 -1
View File
@@ -1 +1 @@
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220a3a42adcb4b001c32aa78ed40d4670ef16c1fc225305d63a33f8b9b9fd68df6d64736f6c634300081e0033","sourceMap":"424:971:12:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220a3a42adcb4b001c32aa78ed40d4670ef16c1fc225305d63a33f8b9b9fd68df6d64736f6c634300081e0033","sourceMap":"424:971:12:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"author\":\"Matt Condon (@shrugs)\",\"details\":\"Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number of elements in a mapping, issuing ERC721 ids, or counting request ids. Include with `using Counters for Counters.Counter;`\",\"kind\":\"dev\",\"methods\":{},\"title\":\"Counters\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/Counters.sol\":\"Counters\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/Counters.sol\":{\"keccak256\":\"0xf0018c2440fbe238dd3a8732fa8e17a0f9dce84d31451dc8a32f6d62b349c9f1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://59e1c62884d55b70f3ae5432b44bb3166ad71ae3acd19c57ab6ddc3c87c325ee\",\"dweb:/ipfs/QmezuXg5GK5oeA4F91EZhozBFekhq5TD966bHPH18cCqhu\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/Counters.sol":"Counters"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/Counters.sol":{"keccak256":"0xf0018c2440fbe238dd3a8732fa8e17a0f9dce84d31451dc8a32f6d62b349c9f1","urls":["bzz-raw://59e1c62884d55b70f3ae5432b44bb3166ad71ae3acd19c57ab6ddc3c87c325ee","dweb:/ipfs/QmezuXg5GK5oeA4F91EZhozBFekhq5TD966bHPH18cCqhu"],"license":"MIT"}},"version":1},"id":12}
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220478c3cfc4853642c2331cfe7c02aa6a34856bcaf4e69011b07e6f8fd812c59a064736f6c63430008210033","sourceMap":"424:971:12:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220478c3cfc4853642c2331cfe7c02aa6a34856bcaf4e69011b07e6f8fd812c59a064736f6c63430008210033","sourceMap":"424:971:12:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"author\":\"Matt Condon (@shrugs)\",\"details\":\"Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number of elements in a mapping, issuing ERC721 ids, or counting request ids. Include with `using Counters for Counters.Counter;`\",\"kind\":\"dev\",\"methods\":{},\"title\":\"Counters\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/Counters.sol\":\"Counters\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/Counters.sol\":{\"keccak256\":\"0xf0018c2440fbe238dd3a8732fa8e17a0f9dce84d31451dc8a32f6d62b349c9f1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://59e1c62884d55b70f3ae5432b44bb3166ad71ae3acd19c57ab6ddc3c87c325ee\",\"dweb:/ipfs/QmezuXg5GK5oeA4F91EZhozBFekhq5TD966bHPH18cCqhu\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/Counters.sol":"Counters"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/Counters.sol":{"keccak256":"0xf0018c2440fbe238dd3a8732fa8e17a0f9dce84d31451dc8a32f6d62b349c9f1","urls":["bzz-raw://59e1c62884d55b70f3ae5432b44bb3166ad71ae3acd19c57ab6ddc3c87c325ee","dweb:/ipfs/QmezuXg5GK5oeA4F91EZhozBFekhq5TD966bHPH18cCqhu"],"license":"MIT"}},"version":1},"id":12}
+1 -1
View File
@@ -1 +1 @@
{"abi":[{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"supportsInterface(bytes4)":"01ffc9a7"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Implementation of the {IERC165} interface. Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check for the additional interface id that will be supported. For example: ```solidity function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); } ``` Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\",\"kind\":\"dev\",\"methods\":{\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol\":\"ERC165\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol\":{\"keccak256\":\"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://fb0048dee081f6fffa5f74afc3fb328483c2a30504e94a0ddd2a5114d731ec4d\",\"dweb:/ipfs/QmZptt1nmYoA5SgjwnSgWqgUSDgm4q52Yos3xhnMv3MV43\"]},\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f\",\"dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{"supportsInterface(bytes4)":{"details":"See {IERC165-supportsInterface}."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol":"ERC165"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol":{"keccak256":"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b","urls":["bzz-raw://fb0048dee081f6fffa5f74afc3fb328483c2a30504e94a0ddd2a5114d731ec4d","dweb:/ipfs/QmZptt1nmYoA5SgjwnSgWqgUSDgm4q52Yos3xhnMv3MV43"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1","urls":["bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f","dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy"],"license":"MIT"}},"version":1},"id":14}
{"abi":[{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"supportsInterface(bytes4)":"01ffc9a7"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Implementation of the {IERC165} interface. Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check for the additional interface id that will be supported. For example: ```solidity function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); } ``` Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\",\"kind\":\"dev\",\"methods\":{\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol\":\"ERC165\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol\":{\"keccak256\":\"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://fb0048dee081f6fffa5f74afc3fb328483c2a30504e94a0ddd2a5114d731ec4d\",\"dweb:/ipfs/QmZptt1nmYoA5SgjwnSgWqgUSDgm4q52Yos3xhnMv3MV43\"]},\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f\",\"dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{"supportsInterface(bytes4)":{"details":"See {IERC165-supportsInterface}."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol":"ERC165"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol":{"keccak256":"0xd10975de010d89fd1c78dc5e8a9a7e7f496198085c151648f20cba166b32582b","urls":["bzz-raw://fb0048dee081f6fffa5f74afc3fb328483c2a30504e94a0ddd2a5114d731ec4d","dweb:/ipfs/QmZptt1nmYoA5SgjwnSgWqgUSDgm4q52Yos3xhnMv3MV43"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1","urls":["bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f","dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy"],"license":"MIT"}},"version":1},"id":14}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"abi":[{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"supportsInterface(bytes4)":"01ffc9a7"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Interface of the ERC165 standard, as defined in the https://eips.ethereum.org/EIPS/eip-165[EIP]. Implementers can declare support of contract interfaces, which can then be queried by others ({ERC165Checker}). For an implementation, see {ERC165}.\",\"kind\":\"dev\",\"methods\":{\"supportsInterface(bytes4)\":{\"details\":\"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":\"IERC165\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f\",\"dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{"supportsInterface(bytes4)":{"details":"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":"IERC165"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1","urls":["bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f","dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy"],"license":"MIT"}},"version":1},"id":15}
{"abi":[{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"supportsInterface(bytes4)":"01ffc9a7"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Interface of the ERC165 standard, as defined in the https://eips.ethereum.org/EIPS/eip-165[EIP]. Implementers can declare support of contract interfaces, which can then be queried by others ({ERC165Checker}). For an implementation, see {ERC165}.\",\"kind\":\"dev\",\"methods\":{\"supportsInterface(bytes4)\":{\"details\":\"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":\"IERC165\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f\",\"dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{"supportsInterface(bytes4)":{"details":"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":"IERC165"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1","urls":["bzz-raw://be161e54f24e5c6fae81a12db1a8ae87bc5ae1b0ddc805d82a1440a68455088f","dweb:/ipfs/QmP7C3CHdY9urF4dEMb9wmsp1wMxHF6nhA2yQE5SKiPAdy"],"license":"MIT"}},"version":1},"id":15}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
{"abi":[{"type":"function","name":"onERC721Received","inputs":[{"name":"operator","type":"address","internalType":"address"},{"name":"from","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes4","internalType":"bytes4"}],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"onERC721Received(address,address,uint256,bytes)":"150b7a02"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"onERC721Received\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"\",\"type\":\"bytes4\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Interface for any contract that wants to support safeTransfers from ERC721 asset contracts.\",\"kind\":\"dev\",\"methods\":{\"onERC721Received(address,address,uint256,bytes)\":{\"details\":\"Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} by `operator` from `from`, this function is called. It must return its Solidity selector to confirm the token transfer. If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.\"}},\"title\":\"ERC721 token receiver interface\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol\":\"IERC721Receiver\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol\":{\"keccak256\":\"0xa82b58eca1ee256be466e536706850163d2ec7821945abd6b4778cfb3bee37da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6e75cf83beb757b8855791088546b8337e9d4684e169400c20d44a515353b708\",\"dweb:/ipfs/QmYvPafLfoquiDMEj7CKHtvbgHu7TJNPSVPSCjrtjV8HjV\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}]}],"devdoc":{"kind":"dev","methods":{"onERC721Received(address,address,uint256,bytes)":{"details":"Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} by `operator` from `from`, this function is called. It must return its Solidity selector to confirm the token transfer. If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol":"IERC721Receiver"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol":{"keccak256":"0xa82b58eca1ee256be466e536706850163d2ec7821945abd6b4778cfb3bee37da","urls":["bzz-raw://6e75cf83beb757b8855791088546b8337e9d4684e169400c20d44a515353b708","dweb:/ipfs/QmYvPafLfoquiDMEj7CKHtvbgHu7TJNPSVPSCjrtjV8HjV"],"license":"MIT"}},"version":1},"id":7}
{"abi":[{"type":"function","name":"onERC721Received","inputs":[{"name":"operator","type":"address","internalType":"address"},{"name":"from","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes4","internalType":"bytes4"}],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"onERC721Received(address,address,uint256,bytes)":"150b7a02"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"onERC721Received\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"\",\"type\":\"bytes4\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Interface for any contract that wants to support safeTransfers from ERC721 asset contracts.\",\"kind\":\"dev\",\"methods\":{\"onERC721Received(address,address,uint256,bytes)\":{\"details\":\"Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} by `operator` from `from`, this function is called. It must return its Solidity selector to confirm the token transfer. If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.\"}},\"title\":\"ERC721 token receiver interface\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol\":\"IERC721Receiver\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol\":{\"keccak256\":\"0xa82b58eca1ee256be466e536706850163d2ec7821945abd6b4778cfb3bee37da\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6e75cf83beb757b8855791088546b8337e9d4684e169400c20d44a515353b708\",\"dweb:/ipfs/QmYvPafLfoquiDMEj7CKHtvbgHu7TJNPSVPSCjrtjV8HjV\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"nonpayable","type":"function","name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}]}],"devdoc":{"kind":"dev","methods":{"onERC721Received(address,address,uint256,bytes)":{"details":"Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} by `operator` from `from`, this function is called. It must return its Solidity selector to confirm the token transfer. If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol":"IERC721Receiver"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol":{"keccak256":"0xa82b58eca1ee256be466e536706850163d2ec7821945abd6b4778cfb3bee37da","urls":["bzz-raw://6e75cf83beb757b8855791088546b8337e9d4684e169400c20d44a515353b708","dweb:/ipfs/QmYvPafLfoquiDMEj7CKHtvbgHu7TJNPSVPSCjrtjV8HjV"],"license":"MIT"}},"version":1},"id":7}
+1 -1
View File
@@ -1 +1 @@
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea26469706673582212207452f0ae8e85d061bad63a9a107ad312b49978d0830aa86d3756c17f6dbcc29c64736f6c634300081e0033","sourceMap":"202:12582:16:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea26469706673582212207452f0ae8e85d061bad63a9a107ad312b49978d0830aa86d3756c17f6dbcc29c64736f6c634300081e0033","sourceMap":"202:12582:16:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"Standard math utilities missing in the Solidity language.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":\"Math\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c\",\"dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/math/Math.sol":"Math"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/math/Math.sol":{"keccak256":"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3","urls":["bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c","dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS"],"license":"MIT"}},"version":1},"id":16}
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220213fb406c5c91a50fee019335c22a97ae8d977051df6576218009afaceea682e64736f6c63430008210033","sourceMap":"202:12582:16:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220213fb406c5c91a50fee019335c22a97ae8d977051df6576218009afaceea682e64736f6c63430008210033","sourceMap":"202:12582:16:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"Standard math utilities missing in the Solidity language.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":\"Math\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c\",\"dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/math/Math.sol":"Math"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/math/Math.sol":{"keccak256":"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3","urls":["bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c","dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS"],"license":"MIT"}},"version":1},"id":16}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220fc3cbe91605fb574b20643e793dece5fabf482e1dfa74fe76bb18c24f02a675e64736f6c634300081e0033","sourceMap":"215:1047:17:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220fc3cbe91605fb574b20643e793dece5fabf482e1dfa74fe76bb18c24f02a675e64736f6c634300081e0033","sourceMap":"215:1047:17:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"Standard signed math utilities missing in the Solidity language.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":\"SignedMath\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7\",\"dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol":"SignedMath"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol":{"keccak256":"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc","urls":["bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7","dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6"],"license":"MIT"}},"version":1},"id":17}
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220976bca310edbc44efb774b7d3260840e2af079bd8d6daf41434a8844ed9cce3a64736f6c63430008210033","sourceMap":"215:1047:17:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea2646970667358221220976bca310edbc44efb774b7d3260840e2af079bd8d6daf41434a8844ed9cce3a64736f6c63430008210033","sourceMap":"215:1047:17:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"Standard signed math utilities missing in the Solidity language.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":\"SignedMath\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7\",\"dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol":"SignedMath"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol":{"keccak256":"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc","urls":["bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7","dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6"],"license":"MIT"}},"version":1},"id":17}
+1 -1
View File
@@ -1 +1 @@
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea26469706673582212201f54765c08e65c3ecf184681909ecdb2961e29431cd7450e467f2e6f1d0606d564736f6c634300081e0033","sourceMap":"220:2559:13:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea26469706673582212201f54765c08e65c3ecf184681909ecdb2961e29431cd7450e467f2e6f1d0606d564736f6c634300081e0033","sourceMap":"220:2559:13:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"String operations.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/Strings.sol\":\"Strings\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/Strings.sol\":{\"keccak256\":\"0x3088eb2868e8d13d89d16670b5f8612c4ab9ff8956272837d8e90106c59c14a0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b81d9ff6559ea5c47fc573e17ece6d9ba5d6839e213e6ebc3b4c5c8fe4199d7f\",\"dweb:/ipfs/QmPCW1bFisUzJkyjroY3yipwfism9RRCigCcK1hbXtVM8n\"]},\"lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c\",\"dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS\"]},\"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7\",\"dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/Strings.sol":"Strings"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/Strings.sol":{"keccak256":"0x3088eb2868e8d13d89d16670b5f8612c4ab9ff8956272837d8e90106c59c14a0","urls":["bzz-raw://b81d9ff6559ea5c47fc573e17ece6d9ba5d6839e213e6ebc3b4c5c8fe4199d7f","dweb:/ipfs/QmPCW1bFisUzJkyjroY3yipwfism9RRCigCcK1hbXtVM8n"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/math/Math.sol":{"keccak256":"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3","urls":["bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c","dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol":{"keccak256":"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc","urls":["bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7","dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6"],"license":"MIT"}},"version":1},"id":13}
{"abi":[],"bytecode":{"object":"0x6055604b600b8282823980515f1a607314603f577f4e487b71000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b305f52607381538281f3fe730000000000000000000000000000000000000000301460806040525f5ffdfea26469706673582212202e07962ed64f0cd88180b20fd760b5a33982ce5cb580902c5da0e2d012315f9264736f6c63430008210033","sourceMap":"220:2559:13:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x730000000000000000000000000000000000000000301460806040525f5ffdfea26469706673582212202e07962ed64f0cd88180b20fd760b5a33982ce5cb580902c5da0e2d012315f9264736f6c63430008210033","sourceMap":"220:2559:13:-:0;;;;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.33+commit.64118f21\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"details\":\"String operations.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts/contracts/utils/Strings.sol\":\"Strings\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/utils/Strings.sol\":{\"keccak256\":\"0x3088eb2868e8d13d89d16670b5f8612c4ab9ff8956272837d8e90106c59c14a0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b81d9ff6559ea5c47fc573e17ece6d9ba5d6839e213e6ebc3b4c5c8fe4199d7f\",\"dweb:/ipfs/QmPCW1bFisUzJkyjroY3yipwfism9RRCigCcK1hbXtVM8n\"]},\"lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c\",\"dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS\"]},\"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7\",\"dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.33+commit.64118f21"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/=lib/openzeppelin-contracts/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/contracts/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"lib/openzeppelin-contracts/contracts/utils/Strings.sol":"Strings"},"evmVersion":"prague","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/utils/Strings.sol":{"keccak256":"0x3088eb2868e8d13d89d16670b5f8612c4ab9ff8956272837d8e90106c59c14a0","urls":["bzz-raw://b81d9ff6559ea5c47fc573e17ece6d9ba5d6839e213e6ebc3b4c5c8fe4199d7f","dweb:/ipfs/QmPCW1bFisUzJkyjroY3yipwfism9RRCigCcK1hbXtVM8n"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/math/Math.sol":{"keccak256":"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3","urls":["bzz-raw://cc8841b3cd48ad125e2f46323c8bad3aa0e88e399ec62acb9e57efa7e7c8058c","dweb:/ipfs/QmSqE4mXHA2BXW58deDbXE8MTcsL5JSKNDbm23sVQxRLPS"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol":{"keccak256":"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc","urls":["bzz-raw://c50fcc459e49a9858b6d8ad5f911295cb7c9ab57567845a250bf0153f84a95c7","dweb:/ipfs/QmcEW85JRzvDkQggxiBBLVAasXWdkhEysqypj9EaB6H2g6"],"license":"MIT"}},"version":1},"id":13}
@@ -1 +1 @@
{"id":"1d1b0b35c357d500","source_id_to_path":{"0":"contracts/CertificateNFT.sol","1":"lib/openzeppelin-contracts/contracts/access/Ownable.sol","2":"lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol","3":"lib/openzeppelin-contracts/contracts/interfaces/IERC4906.sol","4":"lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol","5":"lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol","6":"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol","7":"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol","8":"lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol","9":"lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol","10":"lib/openzeppelin-contracts/contracts/utils/Address.sol","11":"lib/openzeppelin-contracts/contracts/utils/Context.sol","12":"lib/openzeppelin-contracts/contracts/utils/Counters.sol","13":"lib/openzeppelin-contracts/contracts/utils/Strings.sol","14":"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol","15":"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol","16":"lib/openzeppelin-contracts/contracts/utils/math/Math.sol","17":"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol"},"language":"Solidity"}
{"id":"b361ab5df899e494","source_id_to_path":{"0":"contracts/CertificateNFT.sol","1":"lib/openzeppelin-contracts/contracts/access/Ownable.sol","2":"lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol","3":"lib/openzeppelin-contracts/contracts/interfaces/IERC4906.sol","4":"lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol","5":"lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol","6":"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol","7":"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol","8":"lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol","9":"lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol","10":"lib/openzeppelin-contracts/contracts/utils/Address.sol","11":"lib/openzeppelin-contracts/contracts/utils/Context.sol","12":"lib/openzeppelin-contracts/contracts/utils/Counters.sol","13":"lib/openzeppelin-contracts/contracts/utils/Strings.sol","14":"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol","15":"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol","16":"lib/openzeppelin-contracts/contracts/utils/math/Math.sol","17":"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol"},"language":"Solidity"}
+941 -2
View File
File diff suppressed because it is too large Load Diff
+619 -1
View File
@@ -7,6 +7,7 @@ import jwt
import logging
from eth_account.messages import encode_defunct
from web3 import Web3
from activity_logger import log_user_activity
bp = Blueprint('auth', __name__)
logger = logging.getLogger(__name__)
@@ -131,6 +132,8 @@ def verify_signature():
# Create new user
user = {
"wallet_address": wallet_address.lower(),
"role": "student",
"status": "active",
"created_at": datetime.now(),
"last_login": datetime.now(),
"login_count": 1
@@ -138,7 +141,31 @@ def verify_signature():
result = db.users.insert_one(user)
user["_id"] = str(result.inserted_id)
logger.info(f"✅ Created new user: {wallet_address}")
log_user_activity(
db,
wallet_address.lower(),
"auth_register",
"Account registered",
"Created account via wallet authentication",
{"auth_method": "wallet"},
)
else:
account_status = str(user.get("status", "active")).lower().strip()
if account_status == "banned":
logger.warning(f"⛔ Banned wallet login blocked: {wallet_address}")
log_user_activity(
db,
wallet_address.lower(),
"account_status",
"Login blocked",
"Login blocked because account is banned",
{"status": "banned"},
)
return jsonify({
"success": False,
"error": "Your account is banned. Contact admin."
}), 403
# Update existing user
db.users.update_one(
{"wallet_address": wallet_address.lower()},
@@ -149,6 +176,15 @@ def verify_signature():
)
user["_id"] = str(user["_id"])
logger.info(f"✅ Updated existing user: {wallet_address}")
log_user_activity(
db,
wallet_address.lower(),
"auth_login",
"Login successful",
"Wallet login completed successfully",
{"auth_method": "wallet"},
)
# Generate JWT token
token_payload = {
@@ -164,6 +200,12 @@ def verify_signature():
user_response = {
"id": user["wallet_address"],
"wallet_address": user["wallet_address"],
"email": user.get("email", ""),
"name": user.get("name", ""),
"bio": user.get("bio", ""),
"avatar": user.get("avatar", ""),
"role": user.get("role", "student"),
"status": user.get("status", "active"),
"created_at": user["created_at"].isoformat() if isinstance(user["created_at"], datetime) else str(user["created_at"]),
"last_login": user["last_login"].isoformat() if isinstance(user["last_login"], datetime) else str(user["last_login"])
}
@@ -182,4 +224,580 @@ def verify_signature():
return jsonify({
"success": False,
"error": str(e)
}), 500
}), 500
@bp.route('/register', methods=['POST', 'OPTIONS'])
def register():
"""Register a new user with email and password"""
if request.method == "OPTIONS":
return jsonify({'status': 'ok'})
try:
data = request.get_json()
email = data.get('email', '').strip().lower()
password = data.get('password', '')
username = data.get('username', '').strip()
if not email or not password:
return jsonify({
"success": False,
"error": "Email and password are required"
}), 400
if len(password) < 6:
return jsonify({
"success": False,
"error": "Password must be at least 6 characters"
}), 400
# Check if user already exists
existing_user = db.users.find_one({"email": email})
if existing_user:
return jsonify({
"success": False,
"error": "Email already registered"
}), 409
# Hash password using simple approach for development
# TODO: Use werkzeug.security.generate_password_hash for production
import hashlib
password_hash = hashlib.sha256(password.encode()).hexdigest()
# Create new user
user = {
"email": email,
"username": username or email.split("@")[0],
"password_hash": password_hash,
"name": "",
"bio": "",
"avatar": "",
"role": "student",
"status": "active",
"created_at": datetime.now(),
"last_login": datetime.now(),
"login_count": 1,
"auth_method": "email"
}
result = db.users.insert_one(user)
user["_id"] = str(result.inserted_id)
# Generate JWT token
token_payload = {
"user_id": str(result.inserted_id),
"email": email,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(days=7)
}
token = jwt.encode(token_payload, JWT_SECRET, algorithm="HS256")
user_response = {
"id": str(result.inserted_id),
"email": email,
"username": username or email.split("@")[0],
"name": "",
"bio": "",
"avatar": "",
"role": "student",
"status": "active",
"created_at": user["created_at"].isoformat(),
"last_login": user["last_login"].isoformat()
}
log_user_activity(
db,
str(result.inserted_id),
"auth_register",
"Account registered",
"Created account with email and password",
{"auth_method": "email"},
)
logger.info(f"✅ New user registered: {email}")
return jsonify({
"success": True,
"token": token,
"user": user_response,
"message": "Registration successful"
}), 201
except Exception as e:
logger.error(f"❌ Error during registration: {str(e)}")
return jsonify({
"success": False,
"error": str(e)
}), 500
@bp.route('/login', methods=['POST', 'OPTIONS'])
def login():
"""Login with email and password"""
if request.method == "OPTIONS":
return jsonify({'status': 'ok'})
try:
data = request.get_json()
email = data.get('email', '').strip().lower()
password = data.get('password', '')
if not email or not password:
return jsonify({
"success": False,
"error": "Email and password are required"
}), 400
# Find user by email
user = db.users.find_one({"email": email})
if not user:
return jsonify({
"success": False,
"error": "Invalid email or password"
}), 401
account_status = str(user.get("status", "active")).lower().strip()
if account_status == "banned":
logger.warning(f"⛔ Banned email login blocked: {email}")
log_user_activity(
db,
str(user.get("_id")),
"account_status",
"Login blocked",
"Login blocked because account is banned",
{"status": "banned", "email": email},
)
return jsonify({
"success": False,
"error": "Your account is banned. Contact admin."
}), 403
if account_status == "suspended":
log_user_activity(
db,
str(user.get("_id")),
"account_status",
"Login attempted while suspended",
"User logged in while account status is suspended",
{"status": "suspended", "email": email},
)
# Verify password
import hashlib
password_hash = hashlib.sha256(password.encode()).hexdigest()
if password_hash != user.get('password_hash'):
return jsonify({
"success": False,
"error": "Invalid email or password"
}), 401
# Update last login
db.users.update_one(
{"_id": user["_id"]},
{
"$set": {"last_login": datetime.now()},
"$inc": {"login_count": 1}
}
)
# Generate JWT token
token_payload = {
"user_id": str(user["_id"]),
"email": email,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(days=7)
}
token = jwt.encode(token_payload, JWT_SECRET, algorithm="HS256")
user_response = {
"id": str(user["_id"]),
"email": email,
"username": user.get('username', ''),
"name": user.get('name', ''),
"bio": user.get('bio', ''),
"avatar": user.get('avatar', ''),
"role": user.get('role', 'student'),
"status": user.get('status', 'active'),
"created_at": user["created_at"].isoformat() if isinstance(user["created_at"], datetime) else str(user["created_at"]),
"last_login": user["last_login"].isoformat() if isinstance(user["last_login"], datetime) else str(user["last_login"])
}
log_user_activity(
db,
str(user.get("_id")),
"auth_login",
"Login successful",
"Email login completed successfully",
{"auth_method": "email", "email": email},
)
logger.info(f"✅ User logged in: {email}")
return jsonify({
"success": True,
"token": token,
"user": user_response,
"message": "Login successful"
})
except Exception as e:
logger.error(f"❌ Error during login: {str(e)}")
return jsonify({
"success": False,
"error": str(e)
}), 500
@bp.route('/profile/update', methods=['POST', 'OPTIONS'])
def update_profile():
"""Update user profile (name, bio, avatar)"""
if request.method == "OPTIONS":
return jsonify({'status': 'ok'})
try:
# Get token from header
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return jsonify({
"success": False,
"error": "Authorization header required"
}), 401
token = auth_header.split('Bearer ')[1]
# Verify and decode token
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
user_id = payload.get('user_id')
except jwt.InvalidTokenError:
return jsonify({
"success": False,
"error": "Invalid token"
}), 401
data = request.get_json()
name = data.get('name', '').strip()
bio = data.get('bio', '').strip()
avatar = data.get('avatar', '').strip()
# Update user profile
from bson.objectid import ObjectId
result = db.users.update_one(
{"_id": ObjectId(user_id)},
{
"$set": {
"name": name,
"bio": bio,
"avatar": avatar,
"updated_at": datetime.now()
}
}
)
if result.matched_count == 0:
return jsonify({
"success": False,
"error": "User not found"
}), 404
# Get updated user
user = db.users.find_one({"_id": ObjectId(user_id)})
user_response = {
"id": str(user["_id"]),
"email": user.get('email', ''),
"username": user.get('username', ''),
"name": user.get('name', ''),
"bio": user.get('bio', ''),
"avatar": user.get('avatar', ''),
"role": user.get('role', 'student'),
"status": user.get('status', 'active'),
"created_at": user["created_at"].isoformat() if isinstance(user["created_at"], datetime) else str(user["created_at"]),
"last_login": user["last_login"].isoformat() if isinstance(user["last_login"], datetime) else str(user["last_login"])
}
logger.info(f"✅ Profile updated for user: {user_id}")
return jsonify({
"success": True,
"user": user_response,
"message": "Profile updated successfully"
})
except Exception as e:
logger.error(f"❌ Error updating profile: {str(e)}")
return jsonify({
"success": False,
"error": str(e)
}), 500
@bp.route('/metamask/add-email', methods=['POST', 'OPTIONS'])
def add_metamask_email():
"""Store contact email for MetaMask wallet"""
if request.method == "OPTIONS":
return jsonify({'status': 'ok'})
try:
# Get token from header
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return jsonify({
"success": False,
"error": "Authorization header required"
}), 401
token = auth_header.split('Bearer ')[1]
# Verify and decode token
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
wallet_address = payload.get('wallet_address')
if not wallet_address:
wallet_address = payload.get('user_id')
except jwt.InvalidTokenError:
return jsonify({
"success": False,
"error": "Invalid token"
}), 401
data = request.get_json()
email = data.get('email', '').strip().lower()
name = data.get('name', '').strip()
if not email:
return jsonify({
"success": False,
"error": "Email is required"
}), 400
# Validate email format
import re
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
return jsonify({
"success": False,
"error": "Invalid email format"
}), 400
# Check if email already used by different wallet
existing_user = db.users.find_one({"email": email, "wallet_address": {"$ne": wallet_address.lower()}})
if existing_user:
return jsonify({
"success": False,
"error": "Email already associated with another wallet"
}), 409
# Update user with email and name
from bson.objectid import ObjectId
# Try updating by wallet address first (for new users)
result = db.users.update_one(
{"wallet_address": wallet_address.lower()},
{
"$set": {
"email": email,
"name": name or "",
"updated_at": datetime.now()
}
}
)
if result.matched_count == 0:
return jsonify({
"success": False,
"error": "User not found"
}), 404
# Get updated user
user = db.users.find_one({"wallet_address": wallet_address.lower()})
user_response = {
"id": str(user.get("_id", wallet_address)),
"wallet_address": user.get("wallet_address", wallet_address),
"email": user.get("email", ""),
"name": user.get("name", ""),
"bio": user.get("bio", ""),
"avatar": user.get("avatar", ""),
"role": user.get("role", "student"),
"status": user.get("status", "active"),
"created_at": user.get("created_at", datetime.now()).isoformat() if isinstance(user.get("created_at"), datetime) else str(user.get("created_at", datetime.now())),
"last_login": user.get("last_login", datetime.now()).isoformat() if isinstance(user.get("last_login"), datetime) else str(user.get("last_login", datetime.now()))
}
logger.info(f"✅ Email added for MetaMask wallet: {wallet_address}")
return jsonify({
"success": True,
"user": user_response,
"message": "Email saved successfully"
})
except Exception as e:
logger.error(f"❌ Error saving MetaMask email: {str(e)}")
return jsonify({
"success": False,
"error": str(e)
}), 500
@bp.route('/verify-token', methods=['POST', 'OPTIONS'])
def verify_token():
"""Validate JWT token and return the latest user payload."""
if request.method == "OPTIONS":
return jsonify({'status': 'ok'})
try:
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return jsonify({"valid": False, "error": "Authorization header required"}), 401
token = auth_header.split('Bearer ')[1]
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
except jwt.InvalidTokenError:
return jsonify({"valid": False, "error": "Invalid token"}), 401
user = None
wallet_address = payload.get('wallet_address')
email = payload.get('email')
user_id = payload.get('user_id')
if wallet_address:
user = db.users.find_one({"wallet_address": str(wallet_address).lower()})
elif email:
user = db.users.find_one({"email": str(email).lower()})
elif user_id:
try:
from bson.objectid import ObjectId
user = db.users.find_one({"_id": ObjectId(user_id)})
except Exception:
user = None
if not user:
return jsonify({"valid": False, "error": "User not found"}), 404
status = str(user.get("status", "active")).lower().strip()
if status == "banned":
return jsonify({"valid": False, "error": "Account is banned"}), 403
user_response = {
"id": str(user.get("_id", user.get("wallet_address", ""))),
"wallet_address": user.get("wallet_address", ""),
"email": user.get("email", ""),
"username": user.get("username", ""),
"name": user.get("name", ""),
"bio": user.get("bio", ""),
"avatar": user.get("avatar", ""),
"role": user.get("role", "student"),
"status": user.get("status", "active"),
"created_at": user.get("created_at", datetime.now()).isoformat() if isinstance(user.get("created_at"), datetime) else str(user.get("created_at", datetime.now())),
"last_login": user.get("last_login", datetime.now()).isoformat() if isinstance(user.get("last_login"), datetime) else str(user.get("last_login", datetime.now())),
}
return jsonify({"valid": True, "user": user_response})
except Exception as e:
logger.error(f"❌ verify-token error: {str(e)}")
return jsonify({"valid": False, "error": str(e)}), 500
@bp.route('/me', methods=['GET', 'OPTIONS'])
def get_me():
"""Return authenticated user profile for current token."""
if request.method == "OPTIONS":
return jsonify({'status': 'ok'})
verify_resp = verify_token()
try:
body, status = verify_resp
if status != 200:
return body, status
data = body.get_json()
return jsonify({"success": True, "user": data.get("user", {})})
except Exception:
return verify_resp
@bp.route('/upload-image', methods=['POST', 'OPTIONS'])
def upload_image():
"""Upload and convert image (PNG/JPG only) to base64"""
if request.method == "OPTIONS":
return jsonify({'status': 'ok'})
try:
# Get token from header
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return jsonify({
"success": False,
"error": "Authorization header required"
}), 401
token = auth_header.split('Bearer ')[1]
# Verify and decode token
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
user_id = payload.get('user_id')
except jwt.InvalidTokenError:
return jsonify({
"success": False,
"error": "Invalid token"
}), 401
# Check if file is in request
if 'file' not in request.files:
return jsonify({
"success": False,
"error": "No file provided"
}), 400
file = request.files['file']
if file.filename == '':
return jsonify({
"success": False,
"error": "No file selected"
}), 400
# Validate file type - only PNG and JPG
allowed_extensions = {'png', 'jpg', 'jpeg'}
file_ext = file.filename.rsplit('.', 1)[1].lower() if '.' in file.filename else ''
if file_ext not in allowed_extensions:
return jsonify({
"success": False,
"error": "Only PNG and JPG formats are allowed"
}), 400
# Validate file size (max 5MB)
file.seek(0, 2) # Seek to end
file_size = file.tell()
file.seek(0) # Seek back to start
max_size = 5 * 1024 * 1024 # 5MB
if file_size > max_size:
return jsonify({
"success": False,
"error": "File size must be less than 5MB"
}), 400
# Read file and convert to base64
import base64
file_data = file.read()
base64_image = base64.b64encode(file_data).decode('utf-8')
# Create data URL for the image
mime_type = f"image/{file_ext if file_ext != 'jpg' else 'jpeg'}"
data_url = f"data:{mime_type};base64,{base64_image}"
logger.info(f"✅ Image uploaded for user: {user_id}, size: {file_size} bytes")
return jsonify({
"success": True,
"image": data_url,
"size": file_size,
"message": "Image uploaded successfully"
}), 200
except Exception as e:
logger.error(f"❌ Error uploading image: {str(e)}")
return jsonify({
"success": False,
"error": str(e)
}), 500
+51 -5
View File
@@ -8,9 +8,16 @@ import uuid
from datetime import datetime
import docker
import psutil
from pymongo import MongoClient
from activity_logger import log_user_activity, resolve_user_identity
bp = Blueprint('coding', __name__)
# MongoDB connection
mongo_uri = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/')
client = MongoClient(mongo_uri)
db = client.openlearnx
def secure_execution_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@@ -34,6 +41,20 @@ def start_coding_session():
session['start_time'] = datetime.now().isoformat()
session['course_id'] = course_id
session['lesson_id'] = lesson_id
identity = resolve_user_identity(request, db)
log_user_activity(
db,
identity.get("user_id"),
"coding",
"Started coding session",
"Entered secure coding session",
{
"session_id": session_id,
"course_id": course_id,
"lesson_id": lesson_id,
},
)
return jsonify({
"success": True,
@@ -92,6 +113,36 @@ def submit_coding_test():
code,
test_result
)
identity = resolve_user_identity(request, db)
resolved_user_id = identity.get("user_id")
if resolved_user_id:
db.user_submissions.insert_one({
"user_id": resolved_user_id,
"session_id": session.get('coding_session_id'),
"course_id": session.get('course_id'),
"problem_id": problem_id,
"score": test_result.get('score', 0),
"points_earned": int(test_result.get('score', 0)),
"submitted_at": datetime.now(),
"status": "submitted",
})
log_user_activity(
db,
resolved_user_id,
"coding",
"Submitted coding solution",
f"Submitted coding test for problem '{problem_id}'",
{
"submission_id": submission_id,
"problem_id": problem_id,
"score": test_result.get('score', 0),
"passed": test_result.get('passed', 0),
"total": test_result.get('total', 0),
},
points_earned=int(test_result.get('score', 0)),
)
return jsonify({
"success": True,
@@ -184,11 +235,6 @@ def get_run_command(language, filename):
def log_coding_attempt(session_id, code, language):
"""Log all coding attempts for monitoring"""
from pymongo import MongoClient
client = MongoClient(os.getenv('MONGODB_URI', 'mongodb://localhost:27017/'))
db = client.openlearnx
db.coding_logs.insert_one({
"session_id": session_id,
"code": code,
+95 -1
View File
@@ -1,6 +1,8 @@
from flask import Blueprint, jsonify, current_app
from flask import Blueprint, jsonify, current_app, request
from pymongo import MongoClient
import os
from datetime import datetime
from activity_logger import log_user_activity, resolve_user_identity
bp = Blueprint('courses', __name__)
@@ -68,6 +70,38 @@ def get_lesson(course_id, lesson_id):
def mark_lesson_complete(course_id, lesson_id):
"""Mark a lesson as completed for the user"""
try:
identity = resolve_user_identity(request, db)
user_id = identity.get("user_id")
if user_id:
course = db.courses.find_one({"id": course_id}, {"title": 1}) or {}
lesson = db.lessons.find_one({"id": lesson_id, "course_id": course_id}, {"title": 1}) or {}
db.user_courses.update_one(
{"user_id": user_id, "course_id": course_id},
{
"$set": {
"user_id": user_id,
"course_id": course_id,
"last_activity_at": datetime.utcnow(),
"completed_at": datetime.utcnow(),
"completed": True,
},
"$addToSet": {"lessons_completed": lesson_id},
},
upsert=True,
)
log_user_activity(
db,
user_id,
"course",
"Lesson completed",
f"Completed lesson '{lesson.get('title', lesson_id)}' in course '{course.get('title', course_id)}'",
{"course_id": course_id, "lesson_id": lesson_id},
points_earned=10,
)
return jsonify({
"success": True,
"message": f"Lesson {lesson_id} marked as complete",
@@ -76,6 +110,66 @@ def mark_lesson_complete(course_id, lesson_id):
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/<course_id>/activity", methods=["POST"])
def log_course_activity(course_id):
"""Log course interactions like view/start for real dashboard activity."""
try:
identity = resolve_user_identity(request, db)
user_id = identity.get("user_id")
if not user_id:
return jsonify({"success": False, "error": "Authentication required"}), 401
data = request.get_json(silent=True) or {}
action = str(data.get("action") or "view").strip().lower()
lesson_id = str(data.get("lesson_id") or "").strip()
course = db.courses.find_one({"id": course_id}, {"title": 1}) or {}
lesson_title = lesson_id
if lesson_id:
lesson = db.lessons.find_one({"id": lesson_id, "course_id": course_id}, {"title": 1}) or {}
lesson_title = lesson.get("title", lesson_id)
if action == "start":
title = "Course started"
description = f"Started course '{course.get('title', course_id)}'"
elif action == "lesson_view":
title = "Lesson viewed"
description = f"Viewed lesson '{lesson_title}' in course '{course.get('title', course_id)}'"
else:
title = "Course viewed"
description = f"Opened course '{course.get('title', course_id)}'"
log_user_activity(
db,
user_id,
"course",
title,
description,
{"course_id": course_id, "lesson_id": lesson_id, "action": action},
)
db.user_courses.update_one(
{"user_id": user_id, "course_id": course_id},
{
"$set": {
"user_id": user_id,
"course_id": course_id,
"last_activity_at": datetime.utcnow(),
},
"$setOnInsert": {
"started_at": datetime.utcnow(),
"completed": False,
"lessons_completed": [],
},
},
upsert=True,
)
return jsonify({"success": True})
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/<course_id>/progress", methods=["GET"])
def get_course_progress(course_id):
"""Get user's progress in a specific course"""
+160 -4
View File
@@ -1,5 +1,5 @@
from flask import Blueprint, request, jsonify
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from pymongo import MongoClient
import os
from bson import ObjectId
@@ -188,8 +188,11 @@ def get_comprehensive_stats():
"courses_completed": courses_completed,
"coding_problems_solved": coding_problems_solved,
"quiz_accuracy": quiz_accuracy,
"coding_streak": coding_streak,
"longest_streak": max(longest_streak, coding_streak),
"streak_data": {
"current_streak": coding_streak,
"best_streak": max(longest_streak, coding_streak),
"last_active_date": datetime.now().isoformat()
},
"total_courses": len(courses),
"total_quizzes": len(quizzes),
"global_rank": calculate_real_global_rank(user_stats, user_id) if user_stats else 0,
@@ -270,9 +273,143 @@ def get_recent_activity():
}), 401
logger.info(f"📋 Fetching REAL activity for wallet: {user_id}")
identity_candidates = {str(user_id)}
if wallet_address:
identity_candidates.add(str(wallet_address).lower())
# Resolve user identity aliases to avoid missing activity across auth methods.
user_doc = None
try:
maybe_oid = ObjectId(str(user_id))
user_doc = db.users.find_one({"_id": maybe_oid})
except Exception:
user_doc = db.users.find_one({"wallet_address": str(user_id).lower()}) or db.users.find_one({"email": str(user_id).lower()})
if user_doc:
if user_doc.get("_id"):
identity_candidates.add(str(user_doc.get("_id")))
if user_doc.get("wallet_address"):
identity_candidates.add(str(user_doc.get("wallet_address")).lower())
if user_doc.get("email"):
identity_candidates.add(str(user_doc.get("email")).lower())
logger.info(f"📋 Recent activity identity candidates: {sorted(identity_candidates)}")
activities = []
def parse_datetime(value):
if isinstance(value, datetime):
return value
if isinstance(value, str):
candidate = value.replace("Z", "+00:00")
try:
return datetime.fromisoformat(candidate)
except Exception:
return None
return None
def to_utc_display(value):
dt = parse_datetime(value) or datetime.now(timezone.utc)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
else:
dt = dt.astimezone(timezone.utc)
return dt.strftime("%Y-%m-%d %H:%M:%S UTC")
# Primary source: explicit user activity event log.
event_docs = list(
db.user_activity_events.find({"user_id": {"$in": list(identity_candidates)}}).sort("occurred_at", -1).limit(150)
)
for item in event_docs:
occurred_at = item.get("occurred_at") or item.get("completed_at") or datetime.now(timezone.utc)
activities.append({
"id": str(item.get("_id", uuid.uuid4())),
"type": item.get("type", "activity"),
"title": item.get("title", "User Activity"),
"description": item.get("description", "Activity recorded"),
"completed_at": parse_datetime(occurred_at).isoformat() if parse_datetime(occurred_at) else datetime.now(timezone.utc).isoformat(),
"timestamp_utc": item.get("timestamp_utc") or to_utc_display(occurred_at),
"points_earned": int(item.get("points_earned", 0) or 0),
"success_rate": item.get("success_rate", 0),
"difficulty": item.get("difficulty", ""),
"blockchain_verified": item.get("blockchain_verified", False)
})
# Include admin/account status events from security logs as real activity fallback.
admin_status_logs = list(
db.security_logs.find({
"event_type": "admin_user_status",
"metadata.user_id": {"$in": list(identity_candidates)}
}).sort("timestamp", -1).limit(50)
)
for item in admin_status_logs:
occurred_at = item.get("timestamp", datetime.now(timezone.utc))
metadata = item.get("metadata") or {}
new_status = metadata.get("status") or metadata.get("new_status") or "updated"
activities.append({
"id": str(item.get("_id", uuid.uuid4())),
"type": "account_status",
"title": f"Account status changed to {new_status}",
"description": f"Admin changed your account status to {new_status}",
"completed_at": parse_datetime(occurred_at).isoformat() if parse_datetime(occurred_at) else datetime.now(timezone.utc).isoformat(),
"timestamp_utc": to_utc_display(occurred_at),
"points_earned": 0,
"success_rate": 0,
"difficulty": "",
"blockchain_verified": False,
})
# Fallback source: authenticated request audit logs for this user.
audit_logs = list(
db.security_logs.find({
"$or": [
{"metadata.auth_user_id": {"$in": list(identity_candidates)}},
{"metadata.auth_wallet_address": {"$in": list(identity_candidates)}},
{"metadata.auth_email": {"$in": list(identity_candidates)}},
]
}).sort("timestamp", -1).limit(150)
)
for item in audit_logs:
path = str(item.get("path") or "")
method = str(item.get("method") or "")
ts = item.get("timestamp", datetime.now(timezone.utc))
if not any(segment in path for segment in ["/api/quizzes", "/api/exam", "/api/coding", "/api/courses", "/api/auth"]):
continue
log_type = "activity"
title = f"{method} {path}"
description = f"API activity on {path}"
if "/api/quizzes" in path:
log_type = "quiz"
title = "Quiz activity"
description = f"{method} {path}"
elif "/api/exam" in path or "/api/coding" in path:
log_type = "coding"
title = "Coding activity"
description = f"{method} {path}"
elif "/api/courses" in path:
log_type = "course"
title = "Course activity"
description = f"{method} {path}"
elif "/api/auth" in path:
log_type = "auth_login"
title = "Authentication activity"
description = f"{method} {path}"
activities.append({
"id": str(item.get("_id", uuid.uuid4())),
"type": log_type,
"title": title,
"description": description,
"completed_at": parse_datetime(ts).isoformat() if parse_datetime(ts) else datetime.now(timezone.utc).isoformat(),
"timestamp_utc": to_utc_display(ts),
"points_earned": 0,
"success_rate": 0,
"difficulty": "",
"blockchain_verified": False,
})
# ✅ ONLY REAL ACTIVITY SOURCES
activity_sources = [
(db.user_courses, "course", "Course Activity", "completed_at"),
@@ -285,7 +422,7 @@ def get_recent_activity():
try:
# Get ONLY real MongoDB data
recent_items = list(collection.find(
{"user_id": user_id}
{"user_id": {"$in": list(identity_candidates)}}
).sort(date_field, -1).limit(20))
for item in recent_items:
@@ -305,6 +442,7 @@ def get_recent_activity():
"title": item.get('title', item.get('name', default_title)),
"description": format_real_activity_description(item, activity_type),
"completed_at": completed_at.isoformat(),
"timestamp_utc": to_utc_display(completed_at),
"points_earned": item.get('points', item.get('points_earned', 0)),
"success_rate": item.get('score', item.get('completion_percentage', 0)),
"difficulty": item.get('difficulty', ''),
@@ -314,8 +452,26 @@ def get_recent_activity():
logger.warning(f"⚠️ Failed to fetch {activity_type} activities: {e}")
continue
# Exclude known placeholder/demo activity content from older seeded data.
fake_markers = {
"completed react fundamentals",
"scored 95% on javascript quiz",
"7-day learning streak achieved",
"moved up 5 positions in leaderboard",
}
filtered_activities = []
for entry in activities:
entry_text = f"{entry.get('title', '')} {entry.get('description', '')}".strip().lower()
if any(marker in entry_text for marker in fake_markers):
continue
filtered_activities.append(entry)
activities = filtered_activities
# Sort by completion date
activities.sort(key=lambda x: x['completed_at'], reverse=True)
activities = activities[:100]
logger.info(f"✅ Found {len(activities)} REAL activities for wallet {user_id}")
return jsonify({
+16
View File
@@ -5,6 +5,7 @@ import string
from datetime import datetime, timedelta
from pymongo import MongoClient
import os
from activity_logger import log_user_activity, resolve_user_identity
bp = Blueprint('exam', __name__)
@@ -255,6 +256,21 @@ def join_exam():
session['session_id'] = participant['session_id']
print(f"✅ Participant {student_name} joined exam {exam_code}")
identity = resolve_user_identity(request, db)
log_user_activity(
db,
identity.get("user_id"),
"exam",
"Joined coding exam",
f"Joined exam '{exam.get('title', exam_code)}' as {student_name}",
{
"exam_code": exam_code,
"exam_title": exam.get("title"),
"student_name": student_name,
"session_id": participant.get("session_id"),
},
)
return jsonify({
"success": True,
+224
View File
@@ -3,6 +3,7 @@ from datetime import datetime
import uuid
import random
import string
from activity_logger import log_user_activity, resolve_user_identity
bp = Blueprint('quizzes', __name__)
@@ -233,6 +234,24 @@ def join_room():
print(f"✅ User joined room: {username} -> {room_code}")
identity = resolve_user_identity(request, db)
resolved_user_id = identity.get("user_id") or data.get("user_id") or data.get("wallet_address")
if isinstance(resolved_user_id, str):
resolved_user_id = resolved_user_id.strip().lower() if resolved_user_id.startswith("0x") else resolved_user_id.strip()
log_user_activity(
db,
resolved_user_id,
"quiz",
"Joined quiz room",
f"Joined quiz room '{room.get('title', room_code)}' as {username}",
{
"room_code": room_code,
"room_title": room.get("title"),
"username": username,
"session_id": participant_session.get("session_id"),
},
)
return jsonify({
"success": True,
"message": f"Successfully joined quiz room '{room.get('title')}'",
@@ -423,6 +442,50 @@ def submit_answer(session_id):
"updated_at": datetime.now()
}}
)
identity = resolve_user_identity(request, db)
resolved_user_id = identity.get("user_id") or data.get("user_id") or data.get("wallet_address")
if isinstance(resolved_user_id, str):
resolved_user_id = resolved_user_id.strip().lower() if resolved_user_id.startswith("0x") else resolved_user_id.strip()
if resolved_user_id:
db.user_quizzes.update_one(
{
"user_id": resolved_user_id,
"session_id": session_id,
"question_id": question_data.get("question_id"),
},
{
"$set": {
"user_id": resolved_user_id,
"session_id": session_id,
"room_code": room.get("room_code"),
"room_title": room.get("title"),
"question_id": question_data.get("question_id"),
"score": participant.get("score", 0),
"completed_at": datetime.now(),
"is_correct": is_correct,
"difficulty": current_difficulty,
"username": participant.get("username"),
}
},
upsert=True,
)
log_user_activity(
db,
resolved_user_id,
"quiz",
"Answered quiz question",
f"Answered a {current_difficulty} question in '{room.get('title', 'Quiz Room')}'",
{
"session_id": session_id,
"room_code": room.get("room_code"),
"room_title": room.get("title"),
"is_correct": is_correct,
"difficulty": current_difficulty,
},
points_earned=question_data.get('points', 10) if is_correct else 0,
)
# Get AI prediction for comparison (if available)
ai_feedback = None
@@ -479,6 +542,68 @@ def submit_answer(session_id):
# ✅ AI QUESTION GENERATION - IMPROVED VERSION
# ===================================================================
@bp.route('/generate-ai', methods=['POST', 'OPTIONS'])
def generate_ai_quiz():
"""Generate a traditional quiz directly using AI"""
if request.method == "OPTIONS":
response = jsonify({'status': 'ok'})
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
response.headers.add("Access-Control-Allow-Methods", "POST,OPTIONS")
return response
try:
data = request.get_json()
topic = data.get('topic', 'General')
difficulty = data.get('difficulty', 'medium')
num_questions = int(data.get('num_questions', 5))
print(f"🤖 AI Quiz Generation: topic={topic}, difficulty={difficulty}, questions={num_questions}")
ai_service = get_ai_service()
if not ai_service:
return jsonify({
"success": False,
"error": "AI service not available"
}), 503
# Generate questions using AI service
generated_data = ai_service.generate_quiz(
topic=topic,
difficulty=difficulty,
num_questions=num_questions
)
# Save to database
db = get_db()
quiz_result = db.quizzes.insert_one({
"id": str(uuid.uuid4()),
"title": generated_data.get('title', f"AI Quiz - {topic}"),
"description": generated_data.get('description', ''),
"difficulty": difficulty,
"questions": generated_data.get('questions', []),
"created_at": datetime.now().isoformat(),
"total_points": len(generated_data.get('questions', [])) * 10,
"generated_by": "AI",
"topic": topic
})
# Get the saved quiz
saved_quiz = db.quizzes.find_one({"_id": quiz_result.inserted_id})
saved_quiz['_id'] = str(saved_quiz['_id'])
print(f"✅ Quiz generated with {len(saved_quiz.get('questions', []))} questions")
return jsonify({
"success": True,
"quiz": saved_quiz,
"message": f"Generated {len(saved_quiz.get('questions', []))} AI questions on topic: {topic}"
}), 201
except Exception as e:
print(f"❌ AI generation error: {str(e)}")
return jsonify({"success": False, "error": str(e)}), 500
@bp.route('/room/<room_code>/generate-ai-questions', methods=['POST', 'OPTIONS'])
def generate_ai_questions(room_code):
"""Generate AI questions for the quiz room - IMPROVED VERSION"""
@@ -1038,5 +1163,104 @@ def get_quiz_by_id(quiz_id):
"quiz": quiz
})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@bp.route('/<quiz_id>/submit', methods=['POST', 'OPTIONS'])
def submit_traditional_quiz(quiz_id):
"""Submit traditional quiz answers, store result, and log user activity."""
if request.method == "OPTIONS":
response = jsonify({'status': 'ok'})
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
response.headers.add("Access-Control-Allow-Methods", "POST,OPTIONS")
return response
try:
db = get_db()
data = request.get_json() or {}
answers = data.get('answers') or {}
participant_name = (data.get('participant_name') or 'User').strip()
quiz = db.quizzes.find_one({"id": quiz_id})
if not quiz:
return jsonify({"success": False, "error": "Quiz not found"}), 404
questions = quiz.get('questions', [])
total_questions = len(questions)
correct_answers = 0
total_points = 0
ai_feedback = []
for idx, question in enumerate(questions):
question_id = question.get('id') or question.get('question_id') or f"q_{idx}"
expected = str(question.get('correct_answer', '')).strip().lower()
user_answer = str(answers.get(question_id, '')).strip()
is_correct = user_answer.lower() == expected if expected else False
points = int(question.get('points', 10) or 10)
if is_correct:
correct_answers += 1
total_points += points
ai_feedback.append({
"question": question.get('question_text', question.get('question', f"Question {idx + 1}")),
"user_answer": user_answer,
"is_correct": is_correct,
"correct_answer": question.get('correct_answer', ''),
"ai_feedback": {
"feedback": "Correct" if is_correct else "Review this concept and try again"
}
})
score = round((correct_answers / total_questions) * 100, 1) if total_questions > 0 else 0
identity = resolve_user_identity(request, db)
resolved_user_id = identity.get("user_id") or data.get("user_id") or data.get("wallet_address")
if isinstance(resolved_user_id, str):
resolved_user_id = resolved_user_id.strip().lower() if resolved_user_id.startswith("0x") else resolved_user_id.strip()
if resolved_user_id:
db.user_quizzes.insert_one({
"user_id": resolved_user_id,
"quiz_id": quiz_id,
"title": quiz.get('title', 'Quiz Submission'),
"topic": quiz.get('topic', 'General'),
"participant_name": participant_name,
"score": score,
"correct_answers": correct_answers,
"total_questions": total_questions,
"points": total_points,
"completed_at": datetime.now(),
"answers": answers,
})
log_user_activity(
db,
resolved_user_id,
"quiz",
"Completed quiz",
f"Completed quiz '{quiz.get('title', quiz_id)}' with score {score}%",
{
"quiz_id": quiz_id,
"quiz_title": quiz.get('title', 'Quiz'),
"score": score,
"correct_answers": correct_answers,
"total_questions": total_questions,
},
points_earned=total_points,
)
return jsonify({
"success": True,
"results": {
"score": score,
"correct_answers": correct_answers,
"total_questions": total_questions,
"total_points": total_points,
"ai_feedback": ai_feedback,
}
})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
+1 -1
View File
@@ -61,7 +61,7 @@ def deploy_contract():
# Sign and send transaction
signed_txn = w3.eth.account.sign_transaction(transaction, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
print(f"Transaction hash: {tx_hash.hex()}")
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""
Simple deployment script for OpenLearnX smart contracts using Anvil
"""
import os
import json
from pathlib import Path
import subprocess
from web3 import Web3
from dotenv import load_dotenv
# Load environment variables
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / ".env")
def deploy_contract():
provider_url = os.getenv('WEB3_PROVIDER_URL', 'http://127.0.0.1:8545')
# Connect to Web3
w3 = Web3(Web3.HTTPProvider(provider_url))
if not w3.is_connected():
raise Exception(f"Failed to connect to {provider_url}")
print(f"✓ Connected to {provider_url}")
print(f"Chain ID: {w3.eth.chain_id}")
# Use Anvil's first account (well-known test account)
# Get all accounts
accounts = w3.eth.accounts
if not accounts:
raise Exception("No accounts available in Anvil")
deployer = accounts[0]
balance = w3.eth.get_balance(deployer)
print(f"✓ Deployer account: {deployer}")
print(f"✓ Balance: {w3.from_wei(balance, 'ether')} ETH")
# Load contract
contract_path = BASE_DIR / "out" / "CertificateNFT.sol" / "CertificateNFT.json"
if not contract_path.exists():
print("❌ Contract JSON not found. Running forge build...")
result = subprocess.run(["forge", "build"], cwd=BASE_DIR, capture_output=True, text=True)
if result.returncode != 0:
raise Exception(f"forge build failed: {result.stderr}")
with open(contract_path, 'r') as f:
contract_data = json.load(f)
print(f"✓ Loaded contract ABI and bytecode")
# Create contract
contract = w3.eth.contract(
abi=contract_data['abi'],
bytecode=contract_data['bytecode']['object']
)
# Deploy
print("⏳ Deploying contract...")
tx_hash = contract.constructor().transact({
'from': deployer,
'gas': 5000000,
'gasPrice': w3.to_wei('1', 'gwei')
})
print(f"✓ Transaction sent: {tx_hash.hex()}")
print("⏳ Waiting for receipt...")
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=60)
contract_address = receipt.contractAddress
print(f"\n✅ Contract deployed successfully!")
print(f"📍 Contract Address: {contract_address}")
print(f"⛽ Gas Used: {receipt.gasUsed}")
# Save deployment info
deployment_info = {
'contract_address': contract_address,
'transaction_hash': tx_hash.hex(),
'deployer': deployer,
'network': 'anvil',
'abi': contract_data['abi'],
'gas_used': receipt.gasUsed,
'block_number': receipt.blockNumber
}
deployment_file = BASE_DIR / "deployment.json"
with open(deployment_file, 'w') as f:
json.dump(deployment_info, f, indent=2)
print(f"✓ Deployment info saved to: {deployment_file}")
print(f"\n📝 Update your .env file:")
print(f"CONTRACT_ADDRESS={contract_address}")
return contract_address
if __name__ == '__main__':
try:
address = deploy_contract()
print(f"\n✨ Deployment complete!")
except Exception as e:
print(f"❌ Error: {e}")
exit(1)
+12 -4
View File
@@ -1,14 +1,22 @@
import tensorflow as tf
import pickle
import json
import numpy as np
import random
import os
from tensorflow.keras.preprocessing.sequence import pad_sequences
from datetime import datetime
from bson import ObjectId
import uuid
# Optional TensorFlow import with fallback
try:
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import pad_sequences
TENSORFLOW_AVAILABLE = True
except ImportError:
tf = None
pad_sequences = None
TENSORFLOW_AVAILABLE = False
class AdaptiveQuizMasterLLM:
def __init__(self, models_path="./models/"):
"""
@@ -18,13 +26,13 @@ class AdaptiveQuizMasterLLM:
self.model_available = False
try:
# Try to load model files
# Try to load model files only if TensorFlow is available
model_file = f'{models_path}improved_cnn_model.h5'
tokenizer_file = f'{models_path}tokenizer.pickle'
label_encoder_file = f'{models_path}label_encoder.pickle'
data_file = f'{models_path}processed_commonsenseqa_data.json'
if all(os.path.exists(f) for f in [model_file, tokenizer_file, label_encoder_file, data_file]):
if TENSORFLOW_AVAILABLE and all(os.path.exists(f) for f in [model_file, tokenizer_file, label_encoder_file, data_file]):
try:
self.model = tf.keras.models.load_model(model_file)
print("✅ CNN Model loaded successfully")
+31 -3
View File
@@ -13,10 +13,11 @@ import signal
class RealCompilerService:
def __init__(self):
self.client = docker.from_env()
self.client = None # Lazy initialization
self.execution_queue = queue.Queue()
self.active_executions = {}
self.max_concurrent_executions = 5
self.docker_available = False
# Enhanced language configurations with real execution
self.language_configs = {
@@ -97,6 +98,18 @@ class RealCompilerService:
# Start execution worker
self.start_execution_worker()
def _get_docker_client(self):
"""Lazily initialize Docker client"""
if self.client is None:
try:
self.client = docker.from_env()
self.docker_available = True
except Exception as e:
print(f"⚠️ Docker initialization failed: {e}")
self.docker_available = False
self.client = None
return self.client
def start_execution_worker(self):
"""Start background worker for code execution"""
def worker():
@@ -176,6 +189,17 @@ class RealCompilerService:
input_data = context['input_data']
config = context['config']
# Check Docker availability
docker_client = self._get_docker_client()
if docker_client is None or not self.docker_available:
return {
"output": "",
"error": "Docker service is not available. Compiler service cannot execute code.",
"exit_code": -1,
"execution_time": 0,
"memory_used": 0
}
with tempfile.TemporaryDirectory() as temp_dir:
# Prepare code file
filename = f"code{config['file_ext']}" if language != 'java' else "Main.java"
@@ -193,7 +217,7 @@ class RealCompilerService:
start_time = time.time()
# Create and run container
container = self.client.containers.run(
container = docker_client.containers.run(
config['image'],
command=self._build_execution_command(config, filename),
volumes={temp_dir: {'bind': '/app', 'mode': 'rw'}},
@@ -302,4 +326,8 @@ class RealCompilerService:
return False
# Create global instance
real_compiler_service = RealCompilerService()
try:
real_compiler_service = RealCompilerService()
except Exception as e:
print(f"⚠️ Failed to initialize RealCompilerService: {e}")
real_compiler_service = RealCompilerService() # Still create instance for graceful fallback