import axios from "axios"; import dotenv from "dotenv"; import { Contract, JsonRpcProvider, Wallet } from "ethers"; import fs from "fs"; import path from "path"; dotenv.config(); type BlockchainConfig = { contractAddress: string; abi: unknown[]; }; export type GenerateWithAuthInput = { userAddress: string; actorLoraId: string; prompt: string; payload: Record; }; export type GenerateWithAuthOutput = { status: "success"; jobId: string; txHash: string; }; const RPC_URL = process.env.RPC_URL ?? "http://127.0.0.1:8545"; const COMFYUI_URL = process.env.COMFYUI_URL ?? "http://127.0.0.1:8188/prompt"; const PRIVATE_KEY = process.env.PRIVATE_KEY ?? ""; const BLOCKCHAIN_CONFIG_PATH = process.env.BLOCKCHAIN_CONFIG_PATH ?? "../blockchain/blockchain_config.json"; let cachedConfig: BlockchainConfig | null = null; function resolveConfigPath(relativeOrAbsolutePath: string): string { if (path.isAbsolute(relativeOrAbsolutePath)) { return relativeOrAbsolutePath; } return path.resolve(process.cwd(), relativeOrAbsolutePath); } function loadBlockchainConfig(): BlockchainConfig { if (cachedConfig) { return cachedConfig; } const primaryPath = resolveConfigPath(BLOCKCHAIN_CONFIG_PATH); const fallbackPath = path.resolve(process.cwd(), "../middleware/blockchain_config.json"); const selectedPath = fs.existsSync(primaryPath) ? primaryPath : fs.existsSync(fallbackPath) ? fallbackPath : null; if (!selectedPath) { throw new Error(`⛔ Access Denied: Blockchain config not found. Checked ${primaryPath} and ${fallbackPath}.`); } const raw = fs.readFileSync(selectedPath, "utf8"); const parsed = JSON.parse(raw) as BlockchainConfig; if (!parsed.contractAddress || !Array.isArray(parsed.abi)) { throw new Error("⛔ Access Denied: Invalid blockchain config format."); } cachedConfig = parsed; return parsed; } function createBotContext(): { contract: Contract; walletAddress: string } { if (!PRIVATE_KEY) { throw new Error("⛔ Access Denied: Bot private key is missing."); } const provider = new JsonRpcProvider(RPC_URL); const wallet = new Wallet(PRIVATE_KEY, provider); const config = loadBlockchainConfig(); return { contract: new Contract(config.contractAddress, config.abi, wallet), walletAddress: wallet.address }; } function isNetworkDownError(error: unknown): boolean { const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase(); return ( message.includes("failed to fetch") || message.includes("connection refused") || message.includes("network error") || message.includes("timeout") || message.includes("could not detect network") ); } export async function generateWithAuth(input: GenerateWithAuthInput): Promise { const { userAddress, actorLoraId, prompt, payload } = input; const { contract, walletAddress } = createBotContext(); let hasAccess = false; try { hasAccess = await contract.checkAccess(userAddress, actorLoraId); } catch (error) { if (isNetworkDownError(error)) { throw new Error("⛔ Access Denied: Blockchain Unavailable."); } throw new Error("⛔ Access Denied: Contract check failed."); } if (!hasAccess) { throw new Error("⛔ Access Denied: Contract Expired or Invalid."); } let botHasAccess = false; try { botHasAccess = await contract.checkAccess(walletAddress, actorLoraId); } catch { throw new Error("⛔ Access Denied: Contract check failed."); } if (!botHasAccess) { throw new Error("⛔ Access Denied: Bot wallet lacks logging permission for this actor."); } let promptId: string; try { const enrichedPayload = { ...payload, extra_data: { ...(typeof payload.extra_data === "object" && payload.extra_data !== null ? payload.extra_data : {}), astral_prompt: prompt } }; const comfyResponse = await axios.post(COMFYUI_URL, enrichedPayload, { timeout: 10000 }); promptId = comfyResponse?.data?.prompt_id; if (!promptId || typeof promptId !== "string") { throw new Error("Missing prompt_id"); } } catch (error) { if (axios.isAxiosError(error)) { if (!error.response) { throw new Error("Engine Offline."); } throw new Error(`Engine Error: ${error.response.status}`); } throw new Error("Engine Offline."); } try { const tx = await contract.logGeneration(actorLoraId, promptId); await tx.wait(); return { status: "success", jobId: promptId, txHash: tx.hash }; } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`Audit Failed: ${message}`); } } export default { generateWithAuth };