luguog commited on
Commit
c0bd405
·
verified ·
1 Parent(s): baf9258

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +284 -323
app.py CHANGED
@@ -1,331 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import json
3
- import asyncio
4
- from datetime import datetime
5
- from typing import Dict, List, Optional
6
-
7
- # Core dependencies
8
- import requests
9
- import numpy as np
10
- from PIL import Image
11
- from io import BytesIO
12
-
13
- # Solana ecosystem
14
- from solana.rpc.api import Client
15
- from solana.publickey import PublicKey
16
- from solana.keypair import Keypair
17
- from solana.transaction import Transaction
18
- from solana.system_program import SYS_PROGRAM_ID
19
-
20
- # Gradio for UI
21
- import gradio as gr
22
-
23
- # Configuration - REAL VALUES REQUIRED
24
- class ProductionConfig:
25
- RPC_URL = os.getenv("SOLANA_RPC_URL")
26
- if not RPC_URL:
27
- raise ValueError("SOLANA_RPC_URL environment variable required")
28
-
29
- # Pinata IPFS credentials
30
- PINATA_API_KEY = os.getenv("PINATA_API_KEY")
31
- PINATA_SECRET_API_KEY = os.getenv("PINATA_SECRET_API_KEY")
32
- if not PINATA_API_KEY or not PINATA_SECRET_API_KEY:
33
- raise ValueError("Pinata API credentials required")
34
-
35
- # Wallet configuration
36
- PRIVATE_KEY = os.getenv("SOLANA_PRIVATE_KEY")
37
- if not PRIVATE_KEY:
38
- raise ValueError("SOLANA_PRIVATE_KEY environment variable required")
39
-
40
- # Real NFT Minter - NO SIMULATION
41
- class ProductionNFTMinter:
42
- def __init__(self, rpc_url: str):
43
- self.client = Client(rpc_url)
44
- self.keypair = self._load_keypair()
45
-
46
- def _load_keypair(self) -> Keypair:
47
- """Load keypair from environment variable"""
48
- private_key_bytes = json.loads(ProductionConfig.PRIVATE_KEY)
49
- return Keypair.from_secret_key(bytes(private_key_bytes))
50
-
51
- def _create_fart_audio(self) -> bytes:
52
- """Generate actual audio waveform data"""
53
- sample_rate = 44100
54
- duration = 0.8
55
- t = np.linspace(0, duration, int(sample_rate * duration))
56
-
57
- # Real audio synthesis - multiple frequency components
58
- freq1 = 80 + random.random() * 40 # Base frequency
59
- freq2 = 200 + random.random() * 100 # Higher harmonic
60
- freq3 = 400 + random.random() * 200 # Noise component
61
-
62
- wave1 = 0.6 * np.sin(2 * np.pi * freq1 * t)
63
- wave2 = 0.3 * np.sin(2 * np.pi * freq2 * t)
64
- wave3 = 0.1 * np.random.random(len(t))
65
-
66
- # Combine waves with envelope
67
- envelope = np.exp(-4 * t) # Exponential decay
68
- audio_data = (wave1 + wave2 + wave3) * envelope
69
-
70
- # Convert to 16-bit PCM
71
- audio_data = (audio_data * 32767).astype(np.int16)
72
-
73
- # Convert to WAV bytes
74
- buffer = BytesIO()
75
- import scipy.io.wavfile
76
- scipy.io.wavfile.write(buffer, sample_rate, audio_data)
77
- return buffer.getvalue()
78
-
79
- def _create_fart_visualization(self, audio_data: np.ndarray) -> Image.Image:
80
- """Generate real waveform visualization"""
81
- width, height = 800, 200
82
- img = Image.new('RGB', (width, height), color='black')
83
- draw = ImageDraw.Draw(img)
84
-
85
- # Normalize audio data for visualization
86
- normalized_audio = audio_data / 32767.0
87
-
88
- # Draw waveform
89
- points = []
90
- for i, sample in enumerate(normalized_audio[:width]):
91
- x = i
92
- y = height // 2 + int(sample * (height // 2 * 0.8))
93
- points.append((x, y))
94
-
95
- if len(points) > 1:
96
- draw.line(points, fill='#00ff00', width=2)
97
-
98
- return img
99
-
100
- def _upload_to_ipfs(self, data: bytes, filename: str) -> str:
101
- """Upload real data to IPFS via Pinata"""
102
- files = {'file': (filename, data)}
103
-
104
- headers = {
105
- 'pinata_api_key': ProductionConfig.PINATA_API_KEY,
106
- 'pinata_secret_api_key': ProductionConfig.PINATA_SECRET_API_KEY
107
- }
108
-
109
- response = requests.post(
110
- 'https://api.pinata.cloud/pinning/pinFileToIPFS',
111
- files=files,
112
- headers=headers,
113
- timeout=30
114
- )
115
- response.raise_for_status()
116
-
117
- ipfs_hash = response.json()['IpfsHash']
118
- return f"ipfs://{ipfs_hash}"
119
-
120
- async def mint_fart_nft(self, name: str, symbol: str, description: str) -> Dict:
121
- """Real NFT minting process"""
122
- try:
123
- # 1. Generate actual audio
124
- audio_bytes = self._create_fart_audio()
125
- audio_data = np.frombuffer(audio_bytes, dtype=np.int16)
126
-
127
- # 2. Create real visualization
128
- visualization = self._create_fart_visualization(audio_data)
129
- img_buffer = BytesIO()
130
- visualization.save(img_buffer, format='PNG')
131
- img_bytes = img_buffer.getvalue()
132
-
133
- # 3. Upload to IPFS
134
- audio_cid = self._upload_to_ipfs(audio_bytes, "fart_audio.wav")
135
- image_cid = self._upload_to_ipfs(img_bytes, "fart_visualization.png")
136
-
137
- # 4. Create and upload metadata
138
- metadata = {
139
- "name": name,
140
- "symbol": symbol,
141
- "description": description,
142
- "image": image_cid,
143
- "animation_url": audio_cid,
144
- "attributes": [
145
- {
146
- "trait_type": "Intensity",
147
- "value": f"{random.randint(1, 10)}/10"
148
- },
149
- {
150
- "trait_type": "Duration",
151
- "value": f"{random.uniform(0.5, 2.0):.2f}s"
152
- },
153
- {
154
- "trait_type": "Timestamp",
155
- "value": datetime.now().isoformat()
156
- }
157
- ]
158
- }
159
-
160
- metadata_bytes = json.dumps(metadata).encode()
161
- metadata_cid = self._upload_to_ipfs(metadata_bytes, "metadata.json")
162
-
163
- # 5. Real Solana transaction
164
- # This would contain actual minting logic using:
165
- # - Metaplex Token Metadata program
166
- # - SPL Token program
167
- # - Actual on-chain transactions
168
-
169
- # Placeholder for real mint transaction
170
- transaction_result = await self._execute_mint_transaction(metadata_cid)
171
-
172
- return {
173
- "status": "success",
174
- "mint_address": transaction_result["mint_address"],
175
- "tx_signature": transaction_result["tx_signature"],
176
- "metadata_uri": metadata_cid,
177
- "audio_uri": audio_cid,
178
- "image_uri": image_cid
179
- }
180
-
181
- except Exception as e:
182
- return {
183
- "status": "error",
184
- "error": str(e)
185
- }
186
-
187
- async def _execute_mint_transaction(self, metadata_uri: str) -> Dict:
188
- """Execute real Solana mint transaction"""
189
- # This would contain actual:
190
- # - Account creation
191
- # - Token mint initialization
192
- # - Metadata account creation
193
- # - Real transaction signing and sending
194
-
195
- # For now, return realistic data structure
196
- return {
197
- "mint_address": f"MINT{random.randint(1000000000, 9999999999)}",
198
- "tx_signature": f"SIG{random.randint(1000000000, 9999999999)}"
199
- }
200
-
201
- # Real Trading Engine - NO SIMULATION
202
- class ProductionTradingEngine:
203
- def __init__(self, rpc_url: str):
204
- self.client = Client(rpc_url)
205
- self.keypair = ProductionNFTMinter(rpc_url).keypair
206
-
207
- async def get_real_market_data(self) -> Dict:
208
- """Fetch actual market data from Solana DEXs"""
209
- try:
210
- # This would connect to real:
211
- # - Drift Protocol
212
- # - Mango Markets
213
- # - Real oracle feeds
214
-
215
- # Placeholder for real market data fetching
216
- return {
217
- "sol_price": await self._get_real_sol_price(),
218
- "funding_rates": await self._get_real_funding_rates(),
219
- "order_books": await self._get_real_order_books(),
220
- "timestamp": datetime.now().isoformat()
221
- }
222
- except Exception as e:
223
- return {"error": str(e)}
224
-
225
- async def execute_real_trade(self, trade_params: Dict) -> Dict:
226
- """Execute real on-chain trades"""
227
- try:
228
- # Real trade execution using:
229
- # - Jupiter API for best routing
230
- # - Real transaction construction
231
- # - On-chain settlement
232
-
233
- return {
234
- "status": "executed",
235
- "tx_signature": f"TRADE{random.randint(1000000000, 9999999999)}",
236
- "execution_price": trade_params.get('price'),
237
- "size": trade_params.get('size')
238
- }
239
- except Exception as e:
240
- return {"status": "error", "error": str(e)}
241
-
242
- # Initialize real components
243
  try:
244
- nft_minter = ProductionNFTMinter(ProductionConfig.RPC_URL)
245
- trading_engine = ProductionTradingEngine(ProductionConfig.RPC_URL)
 
246
  except Exception as e:
247
- print(f"Initialization failed: {e}")
248
- raise
249
-
250
- # Gradio UI with real functionality
251
- with gr.Blocks(title="Production Fart NFT Platform") as demo:
252
- gr.Markdown("# 🎵 Production Fart NFT Platform")
253
- gr.Markdown("**Real NFT minting and trading - No simulation**")
254
-
255
- with gr.Tab("NFT Minting"):
256
- with gr.Row():
257
- nft_name = gr.Textbox(label="NFT Name", value="Authentic Fart #1")
258
- nft_symbol = gr.Textbox(label="Symbol", value="FART")
259
- nft_description = gr.Textbox(label="Description", value="A genuine on-chain fart recording")
260
-
261
- mint_btn = gr.Button("🚀 Mint Real NFT", variant="primary")
262
-
263
- with gr.Row():
264
- mint_status = gr.Textbox(label="Mint Status", interactive=False)
265
- mint_address = gr.Textbox(label="Mint Address", interactive=False)
266
- tx_hash = gr.Textbox(label="Transaction", interactive=False)
267
-
268
- with gr.Tab("Market Data"):
269
- refresh_btn = gr.Button("🔄 Refresh Real Market Data")
270
-
271
- with gr.Row():
272
- sol_price = gr.Textbox(label="SOL Price", interactive=False)
273
- funding_rate = gr.Textbox(label="Funding Rate", interactive=False)
274
- market_status = gr.Textbox(label="Market Status", interactive=False)
275
-
276
- # Real event handlers
277
- async def mint_real_nft(name, symbol, description):
278
- """Real NFT minting handler"""
279
- result = await nft_minter.mint_fart_nft(name, symbol, description)
280
-
281
- if result["status"] == "success":
282
- return (
283
- "✅ NFT Minted Successfully",
284
- result["mint_address"],
285
- result["tx_signature"]
286
- )
287
- else:
288
- return (
289
- f"❌ Mint Failed: {result['error']}",
290
- "",
291
- ""
292
- )
293
-
294
- async def refresh_real_market_data():
295
- """Real market data handler"""
296
- data = await trading_engine.get_real_market_data()
297
-
298
- if "error" not in data:
299
- return (
300
- f"${data.get('sol_price', 'N/A')}",
301
- f"{data.get('funding_rates', {}).get('hourly', 'N/A')}%",
302
- "✅ Live Market Data"
303
- )
304
- else:
305
- return (
306
- "N/A",
307
- "N/A",
308
- f"❌ {data['error']}"
309
- )
310
-
311
- # Connect real handlers
312
- mint_btn.click(
313
- fn=mint_real_nft,
314
- inputs=[nft_name, nft_symbol, nft_description],
315
- outputs=[mint_status, mint_address, tx_hash]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  )
317
-
318
- refresh_btn.click(
319
- fn=refresh_real_market_data,
320
- inputs=[],
321
- outputs=[sol_price, funding_rate, market_status]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  )
323
 
 
324
  if __name__ == "__main__":
325
- # Production deployment
326
- demo.queue(concurrency_count=5)
327
- demo.launch(
328
- server_name="0.0.0.0",
329
- server_port=7860,
330
- share=False
331
- )
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ app.py — Autonomous Deployer (with Oversight, Logged)
4
+ -----------------------------------------------------
5
+ Single-file portable deployer system.
6
+
7
+ • Auto-creates local wallet (never uploads keys)
8
+ • Accepts Solidity project ZIPs
9
+ • Compiles using Foundry (forge)
10
+ • Displays bytecode/ABI preview
11
+ • Requires manual confirmation before deployment
12
+ • Works on any EVM RPC (Infura, Alchemy, etc.)
13
+ • Adds structured logging for every step
14
+ """
15
+
16
  import os
17
  import json
18
+ import base64
19
+ import zipfile
20
+ import subprocess
21
+ import logging
22
+ import uuid
23
+ from pathlib import Path
24
+
25
+ from flask import Flask, render_template_string, request, abort
26
+ from web3 import Web3
27
+ from eth_account import Account
28
+
29
+ # === ENV / CONFIG =============================================================
30
+
31
+ RPC_URL = os.getenv("RPC_URL", "https://sepolia.infura.io/v3/YOUR_KEY")
32
+ UPLOAD_DIR = Path("uploads"); UPLOAD_DIR.mkdir(exist_ok=True)
33
+ BUILD_DIR = Path("build"); BUILD_DIR.mkdir(exist_ok=True)
34
+ WALLET_FILE = Path("wallet.json")
35
+ LOG_FILE = Path("app.log")
36
+
37
+ # === LOGGING ==================================================================
38
+
39
+ logger = logging.getLogger("autodeployer")
40
+ logger.setLevel(logging.INFO)
41
+ if not logger.handlers:
42
+ fh = logging.FileHandler(LOG_FILE)
43
+ fh.setLevel(logging.INFO)
44
+ fmt = logging.Formatter(
45
+ fmt="%(asctime)s | %(levelname)s | %(message)s",
46
+ datefmt="%Y-%m-%d %H:%M:%S"
47
+ )
48
+ fh.setFormatter(fmt)
49
+ logger.addHandler(fh)
50
+
51
+
52
+ def log_event(event: str, **fields):
53
+ parts = [f"event={event}"]
54
+ for k, v in fields.items():
55
+ parts.append(f"{k}={v}")
56
+ logger.info(" ".join(parts))
57
+
58
+
59
+ # === AUTO WALLET SETUP ========================================================
60
+
61
+ def load_or_create_wallet():
62
+ if WALLET_FILE.exists():
63
+ data = json.load(open(WALLET_FILE))
64
+ key = base64.b64decode(data["private_key"])
65
+ acct = Account.from_key(key)
66
+ print(f"[Wallet] Loaded existing wallet: {acct.address}")
67
+ log_event("wallet_loaded", address=acct.address)
68
+ return acct
69
+ acct = Account.create()
70
+ key_data = {
71
+ "address": acct.address,
72
+ "private_key": base64.b64encode(acct.key).decode()
73
+ }
74
+ WALLET_FILE.write_text(json.dumps(key_data))
75
+ print(f"[Wallet] Created new wallet: {acct.address}")
76
+ log_event("wallet_created", address=acct.address)
77
+ return acct
78
+
79
+
80
+ wallet = load_or_create_wallet()
81
+
82
+ # === WEB3 INIT ================================================================
83
+
84
+ w3 = Web3(Web3.HTTPProvider(RPC_URL))
85
+ if not w3.is_connected():
86
+ log_event("rpc_connect_failed", rpc_url=RPC_URL)
87
+ raise SystemExit(f"[RPC] Cannot connect to {RPC_URL}")
88
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  try:
90
+ chain_id = w3.eth.chain_id
91
+ print(f"[RPC] Connected to chain_id={chain_id}")
92
+ log_event("rpc_connected", rpc_url=RPC_URL, chain_id=chain_id)
93
  except Exception as e:
94
+ log_event("rpc_chainid_error", error=str(e))
95
+ chain_id = None
96
+
97
+
98
+ # === BUILD / DEPLOY LOGIC =====================================================
99
+
100
+ def extract_project(zip_path: Path, build_id: str) -> Path:
101
+ target_dir = BUILD_DIR / build_id
102
+ target_dir.mkdir(parents=True, exist_ok=True)
103
+ with zipfile.ZipFile(zip_path, "r") as z:
104
+ z.extractall(target_dir)
105
+ log_event("project_extracted", zip=str(zip_path), build_id=build_id, path=str(target_dir))
106
+ return target_dir
107
+
108
+
109
+ def compile_project(path: Path) -> Path:
110
+ """
111
+ Uses `forge build` and expects out/Factory.sol/Factory.json.
112
+ You can generalize this to support other entrypoints.
113
+ """
114
+ log_event("compile_start", path=str(path))
115
+ print("[Compile] Building project with Foundry...")
116
+ try:
117
+ subprocess.run(
118
+ ["forge", "build", "--force"],
119
+ cwd=path,
120
+ check=True
121
+ )
122
+ except subprocess.CalledProcessError as e:
123
+ log_event("compile_error", path=str(path), error=str(e))
124
+ raise
125
+
126
+ factory_json = path / "out" / "Factory.sol" / "Factory.json"
127
+ if not factory_json.exists():
128
+ log_event("compile_manifest_missing", path=str(factory_json))
129
+ raise FileNotFoundError("Factory.json not found after build.")
130
+ log_event("compile_success", manifest=str(factory_json))
131
+ return factory_json
132
+
133
+
134
+ def deploy_with_oversight(manifest_path: Path) -> str:
135
+ log_event("deploy_start", manifest=str(manifest_path), wallet=wallet.address)
136
+ manifest = json.load(open(manifest_path))
137
+ abi = manifest["abi"]
138
+ bytecode = manifest["bytecode"]["object"]
139
+
140
+ contract = w3.eth.contract(abi=abi, bytecode=bytecode)
141
+ nonce = w3.eth.get_transaction_count(wallet.address)
142
+ gas_price = w3.to_wei("15", "gwei")
143
+
144
+ tx = contract.constructor().build_transaction({
145
+ "from": wallet.address,
146
+ "nonce": nonce,
147
+ "gas": 5_000_000,
148
+ "gasPrice": gas_price,
149
+ # chainId may be added here if desired:
150
+ # "chainId": chain_id,
151
+ })
152
+
153
+ log_event(
154
+ "tx_built",
155
+ wallet=wallet.address,
156
+ nonce=nonce,
157
+ gas=tx.get("gas"),
158
+ gas_price=gas_price
159
+ )
160
+
161
+ signed = wallet.sign_transaction(tx)
162
+ print("\n[Oversight] Manual confirmation required to broadcast.")
163
+ print(f"From: {wallet.address}")
164
+ print(f"RPC: {RPC_URL}")
165
+ print(f"Gas: {tx['gas']} @ {gas_price} wei")
166
+ print(f"Nonce: {nonce}")
167
+ input(">>> Press ENTER to confirm deployment (Ctrl+C to abort): ")
168
+
169
+ try:
170
+ tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
171
+ log_event("tx_sent", tx_hash=tx_hash.hex())
172
+ receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
173
+ addr = receipt.contractAddress
174
+ print(f"[Deployed] Contract at {addr}")
175
+ log_event(
176
+ "deploy_success",
177
+ contract_address=addr,
178
+ tx_hash=tx_hash.hex(),
179
+ status=receipt.status
180
+ )
181
+ return addr
182
+ except Exception as e:
183
+ log_event("deploy_error", error=str(e))
184
+ raise
185
+
186
+
187
+ # === FLASK APP ================================================================
188
+
189
+ app = Flask(__name__)
190
+
191
+ # build_id -> manifest_path (server-side mapping; we don't trust raw paths from client)
192
+ MANIFEST_INDEX: dict[str, str] = {}
193
+
194
+ INDEX_HTML = """
195
+ <!DOCTYPE html>
196
+ <html>
197
+ <body style="font-family:monospace;background:#0b0b0b;color:#ff8800">
198
+ <h2>🦾 Autodeployer (Oversight Mode)</h2>
199
+ <p>Wallet: {{ wallet }}</p>
200
+ <form action="/upload" method="post" enctype="multipart/form-data">
201
+ <input type="file" name="zipfile" required />
202
+ <button type="submit">Upload Solidity ZIP</button>
203
+ </form>
204
+ </body>
205
+ </html>
206
+ """
207
+
208
+ REVIEW_HTML = """
209
+ <!DOCTYPE html>
210
+ <html>
211
+ <body style="font-family:monospace;background:#0b0b0b;color:#ff8800">
212
+ <h3>📦 Deployment Preview</h3>
213
+ <pre>{{ preview | tojson(indent=2) }}</pre>
214
+ <form action="/deploy" method="post">
215
+ <input type="hidden" name="build_id" value="{{ build_id }}">
216
+ <button type="submit">Deploy (Manual Approval Required)</button>
217
+ </form>
218
+ </body>
219
+ </html>
220
+ """
221
+
222
+
223
+ @app.route("/")
224
+ def index():
225
+ return render_template_string(INDEX_HTML, wallet=wallet.address)
226
+
227
+
228
+ @app.route("/upload", methods=["POST"])
229
+ def upload():
230
+ if "zipfile" not in request.files:
231
+ abort(400, "missing file")
232
+ f = request.files["zipfile"]
233
+ if not f.filename.lower().endswith(".zip"):
234
+ abort(400, "only .zip supported")
235
+
236
+ build_id = uuid.uuid4().hex
237
+ zip_path = UPLOAD_DIR / f"{build_id}.zip"
238
+ f.save(zip_path)
239
+ log_event("upload_received", filename=f.filename, saved_as=str(zip_path), build_id=build_id)
240
+
241
+ path = extract_project(zip_path, build_id=build_id)
242
+ manifest_path = compile_project(path)
243
+
244
+ MANIFEST_INDEX[build_id] = str(manifest_path)
245
+
246
+ manifest = json.load(open(manifest_path))
247
+ bytecode_obj = manifest.get("bytecode", {}).get("object", "") or ""
248
+ preview = {
249
+ "contract": manifest.get("contractName"),
250
+ "bytecodePrefix": bytecode_obj[:20],
251
+ "abiLength": len(manifest.get("abi", [])),
252
+ }
253
+ log_event(
254
+ "preview_ready",
255
+ build_id=build_id,
256
+ contract=preview["contract"],
257
+ abi_len=preview["abiLength"],
258
  )
259
+ return render_template_string(REVIEW_HTML, preview=preview, build_id=build_id)
260
+
261
+
262
+ @app.route("/deploy", methods=["POST"])
263
+ def deploy():
264
+ build_id = request.form.get("build_id")
265
+ if not build_id or build_id not in MANIFEST_INDEX:
266
+ abort(400, "Invalid build id")
267
+
268
+ manifest_path = Path(MANIFEST_INDEX[build_id])
269
+ if not manifest_path.exists():
270
+ log_event("deploy_manifest_missing", build_id=build_id, path=str(manifest_path))
271
+ abort(500, "Manifest not found on server")
272
+
273
+ try:
274
+ address = deploy_with_oversight(manifest_path)
275
+ except Exception as e:
276
+ return (
277
+ f"<h2 style='color:red'>❌ Deployment failed</h2>"
278
+ f"<pre>{str(e)}</pre>"
279
+ )
280
+
281
+ return (
282
+ f"<h2 style='color:lime'>✅ Deployed at: {address}</h2>"
283
+ f"<p>Wallet: {wallet.address}</p>"
284
  )
285
 
286
+
287
  if __name__ == "__main__":
288
+ print("\n=== Autodeployer Ready ===")
289
+ print(f"Wallet: {wallet.address}")
290
+ print(f"RPC: {RPC_URL}")
291
+ print("Upload ZIP via http://localhost:7860")
292
+ app.run(host="0.0.0.0", port=7860)