167 lines
4.7 KiB
TypeScript
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
|
|
};
|