Files
2026-02-25 00:50:23 +05:30

167 lines
4.7 KiB
TypeScript

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<string, unknown>;
};
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<GenerateWithAuthOutput> {
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
};