Imposta app desktop Electron/Vue con backend FastAPI
- Aggiunge scaffold frontend Vue con UI base e build verso renderer - Introduce backend FastAPI con endpoint generazione e salvataggio JSON - Sposta i generatori in backend e aggiorna CLI main.py - Aggiorna README, requirements e .gitignore per artefatti e config - Configura Electron (main/preload) e script npm per dev/build
This commit is contained in:
15
.gitignore
vendored
15
.gitignore
vendored
@@ -8,3 +8,18 @@ venv/
|
|||||||
|
|
||||||
# JSON files (default ignore)
|
# JSON files (default ignore)
|
||||||
*.json
|
*.json
|
||||||
|
|
||||||
|
# Keep essential config JSON tracked
|
||||||
|
!package.json
|
||||||
|
!package-lock.json
|
||||||
|
|
||||||
|
# Python caches/build
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
backend/build/
|
||||||
|
backend/dist/
|
||||||
|
|
||||||
|
# Node/Electron outputs
|
||||||
|
node_modules/
|
||||||
|
desktop/renderer/
|
||||||
|
release/
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Dopo aver selezionato un'opzione, lo script dedicato verrà eseguito e guida l'u
|
|||||||
- Eventuale utilizzo di chiavi compresse/non compresse
|
- Eventuale utilizzo di chiavi compresse/non compresse
|
||||||
- Visualizzazione e salvataggio dei dati in un file `.json`
|
- Visualizzazione e salvataggio dei dati in un file `.json`
|
||||||
|
|
||||||
Ogni script è indipendente (`p2pk.py`, `p2pkh.py`, `p2sh.py`, `p2wpkh.py`, `p2tr.py`) e implementa le regole specifiche del relativo standard Bitcoin.
|
Ogni script è indipendente (`backend/p2pk.py`, `backend/p2pkh.py`, `backend/p2sh.py`, `backend/p2wpkh.py`, `backend/p2tr.py`) e implementa le regole specifiche del relativo standard Bitcoin.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -139,4 +139,4 @@ I dati saranno salvati in un file `.json` leggibile e riutilizzabile.
|
|||||||
---
|
---
|
||||||
|
|
||||||
## LICENZA
|
## LICENZA
|
||||||
Questo progetto è rilasciato sotto licenza MIT
|
Questo progetto è rilasciato sotto licenza MIT
|
||||||
|
|||||||
0
backend/__init__.py
Normal file
0
backend/__init__.py
Normal file
177
backend/app.py
Normal file
177
backend/app.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
if ROOT_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, ROOT_DIR)
|
||||||
|
|
||||||
|
import p2pk
|
||||||
|
import p2pkh
|
||||||
|
import p2sh
|
||||||
|
import p2tr
|
||||||
|
import p2wpkh
|
||||||
|
|
||||||
|
app = FastAPI(title="AddressGen API", version="0.1.0")
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class P2PKRequest(BaseModel):
|
||||||
|
network: str = "mainnet"
|
||||||
|
compressed: bool = False
|
||||||
|
save_to_file: bool = False
|
||||||
|
filename: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class P2PKHRequest(BaseModel):
|
||||||
|
network: str = "mainnet"
|
||||||
|
compressed: bool = True
|
||||||
|
save_to_file: bool = False
|
||||||
|
filename: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class P2WPKHRequest(BaseModel):
|
||||||
|
network: str = "mainnet"
|
||||||
|
compressed: bool = True
|
||||||
|
save_to_file: bool = False
|
||||||
|
filename: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class P2TRRequest(BaseModel):
|
||||||
|
network: str = "mainnet"
|
||||||
|
save_to_file: bool = False
|
||||||
|
filename: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class P2SHRequest(BaseModel):
|
||||||
|
network: str = "mainnet"
|
||||||
|
m: int = 2
|
||||||
|
n: int = 3
|
||||||
|
compressed: bool = True
|
||||||
|
sort_pubkeys: bool = True
|
||||||
|
save_to_file: bool = False
|
||||||
|
filename: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class SaveRequest(BaseModel):
|
||||||
|
data: dict
|
||||||
|
filename: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
def _save_json(data: dict, filename: Optional[str] = None) -> str:
|
||||||
|
wallets_dir = os.path.join(ROOT_DIR, "wallets")
|
||||||
|
os.makedirs(wallets_dir, exist_ok=True)
|
||||||
|
|
||||||
|
if filename:
|
||||||
|
safe = os.path.basename(filename)
|
||||||
|
if not safe.endswith(".json"):
|
||||||
|
safe += ".json"
|
||||||
|
out_name = safe
|
||||||
|
else:
|
||||||
|
script_type = data.get("script_type", "wallet")
|
||||||
|
network = data.get("network", "net")
|
||||||
|
stamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
|
||||||
|
out_name = f"{script_type}_{network}_{stamp}.json"
|
||||||
|
|
||||||
|
out_path = os.path.join(wallets_dir, out_name)
|
||||||
|
with open(out_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=4)
|
||||||
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/p2pk")
|
||||||
|
def create_p2pk(req: P2PKRequest):
|
||||||
|
try:
|
||||||
|
result = p2pk.generate_p2pk(req.network, req.compressed)
|
||||||
|
if req.save_to_file:
|
||||||
|
result["saved_path"] = _save_json(result, req.filename)
|
||||||
|
return result
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/p2pkh")
|
||||||
|
def create_p2pkh(req: P2PKHRequest):
|
||||||
|
try:
|
||||||
|
result = p2pkh.generate_legacy_address(req.network, req.compressed)
|
||||||
|
if req.save_to_file:
|
||||||
|
result["saved_path"] = _save_json(result, req.filename)
|
||||||
|
return result
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/p2wpkh")
|
||||||
|
def create_p2wpkh(req: P2WPKHRequest):
|
||||||
|
try:
|
||||||
|
result = p2wpkh.generate_segwit_address(req.network, req.compressed)
|
||||||
|
if req.save_to_file:
|
||||||
|
result["saved_path"] = _save_json(result, req.filename)
|
||||||
|
return result
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/p2tr")
|
||||||
|
def create_p2tr(req: P2TRRequest):
|
||||||
|
try:
|
||||||
|
result = p2tr.generate_p2tr_address(req.network)
|
||||||
|
if req.save_to_file:
|
||||||
|
result["saved_path"] = _save_json(result, req.filename)
|
||||||
|
return result
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/p2sh")
|
||||||
|
def create_p2sh(req: P2SHRequest):
|
||||||
|
try:
|
||||||
|
result = p2sh.generate_p2sh_multisig(
|
||||||
|
network=req.network,
|
||||||
|
m=req.m,
|
||||||
|
n=req.n,
|
||||||
|
compressed=req.compressed,
|
||||||
|
sort_pubkeys=req.sort_pubkeys,
|
||||||
|
)
|
||||||
|
if req.save_to_file:
|
||||||
|
result["saved_path"] = _save_json(result, req.filename)
|
||||||
|
return result
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/save")
|
||||||
|
def save_result(req: SaveRequest):
|
||||||
|
try:
|
||||||
|
saved_path = _save_json(req.data, req.filename)
|
||||||
|
return {"saved_path": saved_path}
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
port = int(os.getenv("ADDRESSGEN_PORT", "8732"))
|
||||||
|
uvicorn.run("backend.app:app", host="127.0.0.1", port=port, log_level="info")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run()
|
||||||
2
backend/requirements.txt
Normal file
2
backend/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn[standard]
|
||||||
66
desktop/main.js
Normal file
66
desktop/main.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
const { app, BrowserWindow } = require("electron");
|
||||||
|
const path = require("path");
|
||||||
|
const { spawn } = require("child_process");
|
||||||
|
|
||||||
|
const BACKEND_PORT = process.env.ADDRESSGEN_PORT || "8732";
|
||||||
|
const API_BASE = `http://127.0.0.1:${BACKEND_PORT}`;
|
||||||
|
|
||||||
|
let backendProcess = null;
|
||||||
|
|
||||||
|
function startBackend() {
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
ADDRESSGEN_PORT: BACKEND_PORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.BACKEND_BIN) {
|
||||||
|
backendProcess = spawn(process.env.BACKEND_BIN, [], { env, stdio: "inherit" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.isPackaged) {
|
||||||
|
const binPath = path.join(process.resourcesPath, "backend", "addressgen-backend");
|
||||||
|
backendProcess = spawn(binPath, [], { env, stdio: "inherit" });
|
||||||
|
} else {
|
||||||
|
const python = process.env.PYTHON || "python3";
|
||||||
|
const script = path.join(__dirname, "..", "backend", "app.py");
|
||||||
|
backendProcess = spawn(python, [script], { env, stdio: "inherit" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
process.env.ADDRESSGEN_API_BASE = API_BASE;
|
||||||
|
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 1100,
|
||||||
|
height: 720,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, "preload.js"),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (app.isPackaged) {
|
||||||
|
win.loadFile(path.join(__dirname, "renderer", "index.html"));
|
||||||
|
} else {
|
||||||
|
win.loadURL("http://localhost:5173");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
startBackend();
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
app.on("activate", () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("before-quit", () => {
|
||||||
|
if (backendProcess) {
|
||||||
|
backendProcess.kill();
|
||||||
|
}
|
||||||
|
});
|
||||||
5
desktop/preload.js
Normal file
5
desktop/preload.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const { contextBridge } = require("electron");
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld("addressgen", {
|
||||||
|
apiBase: process.env.ADDRESSGEN_API_BASE || "http://127.0.0.1:8732",
|
||||||
|
});
|
||||||
12
frontend/index.html
Normal file
12
frontend/index.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>AddressGen</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
265
frontend/src/App.vue
Normal file
265
frontend/src/App.vue
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page">
|
||||||
|
<header class="hero">
|
||||||
|
<h1>AddressGen</h1>
|
||||||
|
<p>Genera indirizzi Bitcoin localmente usando il backend Python.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<div class="row">
|
||||||
|
<label>Tipo</label>
|
||||||
|
<select v-model="form.type">
|
||||||
|
<option value="p2pk">P2PK</option>
|
||||||
|
<option value="p2pkh">P2PKH</option>
|
||||||
|
<option value="p2wpkh">P2WPKH</option>
|
||||||
|
<option value="p2tr">P2TR</option>
|
||||||
|
<option value="p2sh">P2SH Multisig</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>Network</label>
|
||||||
|
<select v-model="form.network">
|
||||||
|
<option value="mainnet">mainnet</option>
|
||||||
|
<option value="testnet">testnet</option>
|
||||||
|
<option value="regtest">regtest</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="form.type !== 'p2tr'">
|
||||||
|
<label>Chiavi compresse</label>
|
||||||
|
<input type="checkbox" v-model="form.compressed" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="form.type === 'p2sh'">
|
||||||
|
<label>m-of-n</label>
|
||||||
|
<div class="inline">
|
||||||
|
<input type="number" min="1" max="16" v-model.number="form.m" />
|
||||||
|
<span>/</span>
|
||||||
|
<input type="number" min="1" max="16" v-model.number="form.n" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>Nome file</label>
|
||||||
|
<input type="text" v-model="form.filename" placeholder="wallet.json (opzionale)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="primary" @click="generate" :disabled="loading">
|
||||||
|
{{ loading ? "Genero..." : "Genera" }}
|
||||||
|
</button>
|
||||||
|
<button class="secondary" @click="save" :disabled="loading || !result">
|
||||||
|
Salva JSON
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card" v-if="result">
|
||||||
|
<h2>Risultato</h2>
|
||||||
|
<pre>{{ result }}</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card" v-if="savedPath">
|
||||||
|
<h2>Salvato</h2>
|
||||||
|
<pre>{{ savedPath }}</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card" v-if="error">
|
||||||
|
<h2>Errore</h2>
|
||||||
|
<pre>{{ error }}</pre>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const apiBase = window?.addressgen?.apiBase || "http://127.0.0.1:8732";
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
type: "p2pkh",
|
||||||
|
network: "mainnet",
|
||||||
|
compressed: true,
|
||||||
|
m: 2,
|
||||||
|
n: 3,
|
||||||
|
filename: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = ref("");
|
||||||
|
const error = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
const savedPath = ref("");
|
||||||
|
|
||||||
|
const generate = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
result.value = "";
|
||||||
|
error.value = "";
|
||||||
|
savedPath.value = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const endpoint = `${apiBase}/${form.value.type}`;
|
||||||
|
const payload = {
|
||||||
|
network: form.value.network,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (form.value.type !== "p2tr") {
|
||||||
|
payload.compressed = form.value.compressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.value.type === "p2sh") {
|
||||||
|
payload.m = form.value.m;
|
||||||
|
payload.n = form.value.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(data?.detail || "Errore sconosciuto");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.value = JSON.stringify(data, null, 2);
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err?.message || String(err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
if (!result.value) return;
|
||||||
|
loading.value = true;
|
||||||
|
error.value = "";
|
||||||
|
savedPath.value = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
data: JSON.parse(result.value),
|
||||||
|
};
|
||||||
|
if (form.value.filename?.trim()) {
|
||||||
|
payload.filename = form.value.filename.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${apiBase}/save`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(data?.detail || "Errore sconosciuto");
|
||||||
|
}
|
||||||
|
|
||||||
|
savedPath.value = data.saved_path || "";
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err?.message || String(err);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:root {
|
||||||
|
font-family: "Fira Sans", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #0f172a, #1e293b);
|
||||||
|
color: #e2e8f0;
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: rgba(15, 23, 42, 0.7);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 720px;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.3);
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
background: #38bdf8;
|
||||||
|
color: #0f172a;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: #e2e8f0;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.5);
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: #0b1120;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
4
frontend/src/main.js
Normal file
4
frontend/src/main.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
|
||||||
|
createApp(App).mount("#app");
|
||||||
17
frontend/vite.config.js
Normal file
17
frontend/vite.config.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: path.resolve(__dirname),
|
||||||
|
base: "./",
|
||||||
|
plugins: [vue()],
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(__dirname, "../desktop/renderer"),
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
strictPort: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
39
main.py
39
main.py
@@ -1,5 +1,8 @@
|
|||||||
import subprocess
|
import backend.p2pk as p2pk
|
||||||
import sys
|
import backend.p2pkh as p2pkh
|
||||||
|
import backend.p2sh as p2sh
|
||||||
|
import backend.p2wpkh as p2wpkh
|
||||||
|
import backend.p2tr as p2tr
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("=== GENERATORE INDIRIZZI BITCOIN ===")
|
print("=== GENERATORE INDIRIZZI BITCOIN ===")
|
||||||
@@ -12,23 +15,23 @@ def main():
|
|||||||
|
|
||||||
choice = input("Inserisci la tua scelta: ").strip()
|
choice = input("Inserisci la tua scelta: ").strip()
|
||||||
|
|
||||||
scripts = {
|
actions = {
|
||||||
'1': 'p2pk.py',
|
'1': p2pk.main,
|
||||||
'2': 'p2pkh.py',
|
'2': p2pkh.main,
|
||||||
'3': 'p2sh.py',
|
'3': p2sh.main,
|
||||||
'4': 'p2wpkh.py',
|
'4': p2wpkh.main,
|
||||||
'5': 'p2tr.py'
|
'5': p2tr.main,
|
||||||
}
|
}
|
||||||
|
|
||||||
if choice in scripts:
|
action = actions.get(choice)
|
||||||
try:
|
if not action:
|
||||||
subprocess.run([sys.executable, scripts[choice]], check=True)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"Errore nell'esecuzione dello script: {e}")
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nOperazione interrotta.")
|
|
||||||
else:
|
|
||||||
print("Scelta non valida.")
|
print("Scelta non valida.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
action()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nOperazione interrotta.")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
5125
package-lock.json
generated
Normal file
5125
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
package.json
Normal file
45
package.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "addressgen",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Desktop app for generating Bitcoin addresses",
|
||||||
|
"main": "desktop/main.js",
|
||||||
|
"scripts": {
|
||||||
|
"setup:py": "python3 -m pip install -r requirements.txt",
|
||||||
|
"dev:backend": "python3 backend/app.py",
|
||||||
|
"dev:frontend": "vite --config frontend/vite.config.js",
|
||||||
|
"dev:electron": "wait-on http://localhost:5173 && electron .",
|
||||||
|
"dev": "concurrently -k \"npm:dev:backend\" \"npm:dev:frontend\" \"npm:dev:electron\"",
|
||||||
|
"build:frontend": "vite build --config frontend/vite.config.js",
|
||||||
|
"build:backend": "pyinstaller --name addressgen-backend --onefile backend/app.py --distpath backend/dist --workpath backend/build --specpath backend",
|
||||||
|
"build": "npm run build:frontend && npm run build:backend && electron-builder --linux"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.0.0",
|
||||||
|
"concurrently": "^8.2.2",
|
||||||
|
"electron": "^30.0.0",
|
||||||
|
"electron-builder": "^24.13.3",
|
||||||
|
"vite": "^5.4.0",
|
||||||
|
"vue": "^3.4.0",
|
||||||
|
"wait-on": "^7.2.0"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "com.addressgen.app",
|
||||||
|
"productName": "AddressGen",
|
||||||
|
"directories": {
|
||||||
|
"output": "release"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"desktop/**",
|
||||||
|
"frontend/**"
|
||||||
|
],
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": "backend/dist/addressgen-backend",
|
||||||
|
"to": "backend/addressgen-backend"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"linux": {
|
||||||
|
"target": ["AppImage"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,3 +2,5 @@ base58==2.1.1
|
|||||||
bech32==1.2.0
|
bech32==1.2.0
|
||||||
ecdsa==0.19.0
|
ecdsa==0.19.0
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
|
fastapi==0.115.0
|
||||||
|
uvicorn[standard]==0.30.6
|
||||||
|
|||||||
Reference in New Issue
Block a user