import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
//#region extensions/crypto/src/services/budget-service.ts
/**
* Budget Enforcement Service — per-operation gas+slippage budget tracking.
*
* Inspired by Lemon's BudgetTracker/BudgetEnforcer pattern.
* Tracks cumulative costs across multi-step compound operations and
* halts execution when a budget threshold is exceeded.
*
* Designed to solve the "agent spends too much gas on a failed swap chain"
* problem: if step 1 of a 3-step compound action already burned $X in gas,
* stop before step 2 if the remaining budget is insufficient.
*
* Usage:
*   const session = budgetService.startSession({ maxGasUsd: 5, maxSlippagePercent: 2 });
*   budgetService.recordCost(session.id, { gasUsd: 0.42, slippageUsd: 1.20, stepLabel: 'swap ETH→USDC' });
*   const check = budgetService.checkBudget(session.id);
*   if (!check.ok) { /* halt operation * / }
*   budgetService.endSession(session.id);
*/
const DEFAULT_LIMITS = {
	maxGasUsd: 10,
	maxSlippageUsd: Infinity,
	maxSlippagePercent: 5,
	maxTotalCostUsd: 25,
	maxTransactions: 10
};
var BudgetService = class {
	sessions = /* @__PURE__ */ new Map();
	userActiveSessions = /* @__PURE__ */ new Map();
	/**
	* Start a new budget tracking session for a compound operation.
	*/
	startSession(opts) {
		const existingId = this.userActiveSessions.get(opts.userId);
		if (existingId) this.endSession(existingId, "completed");
		const id = `budget_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
		const session = {
			id,
			userId: opts.userId,
			limits: {
				...DEFAULT_LIMITS,
				...opts.limits
			},
			costs: [],
			status: "active",
			startedAt: Date.now(),
			label: opts.label
		};
		this.sessions.set(id, session);
		this.userActiveSessions.set(opts.userId, id);
		return session;
	}
	/**
	* Record a cost incurred by a step in the operation.
	*/
	recordCost(sessionId, cost) {
		const session = this.sessions.get(sessionId);
		if (!session || session.status !== "active") return;
		session.costs.push({
			timestamp: Date.now(),
			stepLabel: cost.stepLabel,
			gasUsd: cost.gasUsd ?? 0,
			slippageUsd: cost.slippageUsd ?? 0,
			feesUsd: cost.feesUsd ?? 0,
			tradeValueUsd: cost.tradeValueUsd ?? 0,
			txHash: cost.txHash
		});
		const totalGas = session.costs.reduce((s, c) => s + c.gasUsd, 0);
		const totalSlippage = session.costs.reduce((s, c) => s + c.slippageUsd, 0);
		const totalFees = session.costs.reduce((s, c) => s + c.feesUsd, 0);
		const totalCost = totalGas + totalSlippage + totalFees;
		const txCount = session.costs.filter((c) => c.txHash).length;
		if (totalGas > session.limits.maxGasUsd || totalCost > session.limits.maxTotalCostUsd || totalSlippage > session.limits.maxSlippageUsd || txCount >= session.limits.maxTransactions) session.status = "exceeded";
	}
	/**
	* Check whether the session is still within budget.
	* Returns detailed breakdown with warnings and blockers.
	*/
	checkBudget(sessionId) {
		const session = this.sessions.get(sessionId);
		if (!session) return {
			ok: true,
			totalGasUsd: 0,
			totalSlippageUsd: 0,
			totalFeesUsd: 0,
			totalCostUsd: 0,
			transactionCount: 0,
			remainingGasUsd: Infinity,
			remainingTotalUsd: Infinity,
			remainingTransactions: Infinity,
			warnings: [],
			blockers: []
		};
		const totalGasUsd = session.costs.reduce((sum, c) => sum + c.gasUsd, 0);
		const totalSlippageUsd = session.costs.reduce((sum, c) => sum + c.slippageUsd, 0);
		const totalFeesUsd = session.costs.reduce((sum, c) => sum + c.feesUsd, 0);
		const totalCostUsd = totalGasUsd + totalSlippageUsd + totalFeesUsd;
		const transactionCount = session.costs.filter((c) => c.txHash).length;
		const warnings = [];
		const blockers = [];
		if (totalGasUsd > session.limits.maxGasUsd) blockers.push(`Gas budget exceeded: $${totalGasUsd.toFixed(2)} spent (limit: $${session.limits.maxGasUsd.toFixed(2)})`);
		else if (totalGasUsd > session.limits.maxGasUsd * .8) warnings.push(`Gas budget 80% consumed: $${totalGasUsd.toFixed(2)} of $${session.limits.maxGasUsd.toFixed(2)}`);
		if (totalCostUsd > session.limits.maxTotalCostUsd) blockers.push(`Total cost budget exceeded: $${totalCostUsd.toFixed(2)} spent (limit: $${session.limits.maxTotalCostUsd.toFixed(2)})`);
		else if (totalCostUsd > session.limits.maxTotalCostUsd * .8) warnings.push(`Total cost budget 80% consumed: $${totalCostUsd.toFixed(2)} of $${session.limits.maxTotalCostUsd.toFixed(2)}`);
		if (totalSlippageUsd > session.limits.maxSlippageUsd) blockers.push(`Slippage budget exceeded: $${totalSlippageUsd.toFixed(2)} lost (limit: $${session.limits.maxSlippageUsd.toFixed(2)})`);
		for (const cost of session.costs) if (cost.tradeValueUsd > 0 && cost.slippageUsd > 0) {
			const slippagePct = cost.slippageUsd / cost.tradeValueUsd * 100;
			if (slippagePct > session.limits.maxSlippagePercent) warnings.push(`High slippage on "${cost.stepLabel}": ${slippagePct.toFixed(1)}% (limit: ${session.limits.maxSlippagePercent}%)`);
		}
		if (transactionCount >= session.limits.maxTransactions) blockers.push(`Transaction limit reached: ${transactionCount} of ${session.limits.maxTransactions}`);
		return {
			ok: blockers.length === 0,
			totalGasUsd,
			totalSlippageUsd,
			totalFeesUsd,
			totalCostUsd,
			transactionCount,
			remainingGasUsd: Math.max(0, session.limits.maxGasUsd - totalGasUsd),
			remainingTotalUsd: Math.max(0, session.limits.maxTotalCostUsd - totalCostUsd),
			remainingTransactions: Math.max(0, session.limits.maxTransactions - transactionCount),
			warnings,
			blockers
		};
	}
	/**
	* End a budget session.
	*/
	endSession(sessionId, status = "completed") {
		const session = this.sessions.get(sessionId);
		if (!session) return null;
		if (session.status === "active") session.status = status;
		session.endedAt = Date.now();
		if (this.userActiveSessions.get(session.userId) === sessionId) this.userActiveSessions.delete(session.userId);
		this.persistSession(session);
		return session;
	}
	/**
	* Get the active budget session for a user (if any).
	*/
	getActiveSession(userId) {
		const sessionId = this.userActiveSessions.get(userId);
		if (!sessionId) return null;
		const session = this.sessions.get(sessionId);
		if (!session || session.status !== "active") return null;
		return session;
	}
	/**
	* Get a session by ID.
	*/
	getSession(sessionId) {
		return this.sessions.get(sessionId) ?? null;
	}
	/**
	* Format a budget check result as a human-readable string.
	*/
	formatBudgetCheck(check) {
		const lines = [];
		lines.push(`Cost so far: $${check.totalCostUsd.toFixed(2)} (gas: $${check.totalGasUsd.toFixed(2)}, slippage: $${check.totalSlippageUsd.toFixed(2)}, fees: $${check.totalFeesUsd.toFixed(2)})`);
		lines.push(`Transactions: ${check.transactionCount}`);
		lines.push(`Remaining budget: $${check.remainingTotalUsd.toFixed(2)} total, $${check.remainingGasUsd.toFixed(2)} gas`);
		if (check.warnings.length > 0) {
			lines.push("", "Warnings:");
			for (const w of check.warnings) lines.push(`  - ${w}`);
		}
		if (check.blockers.length > 0) {
			lines.push("", "BLOCKED:");
			for (const b of check.blockers) lines.push(`  - ${b}`);
		}
		return lines.join("\n");
	}
	getAuditDir() {
		return process.env.OPENCLAWNCH_TX_DIR ? join(process.env.OPENCLAWNCH_TX_DIR, "..", "budget-audit") : join(process.env.HOME ?? "/tmp", ".openclawnch", "budget-audit");
	}
	persistSession(session) {
		try {
			const dir = this.getAuditDir();
			if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
			writeFileSync(join(dir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf8");
		} catch {}
	}
};
let _instance = null;
function getBudgetService() {
	if (!_instance) _instance = new BudgetService();
	return _instance;
}
function resetBudgetService() {
	_instance = null;
}
//#endregion
export { getBudgetService, resetBudgetService };

//# sourceMappingURL=budget-service.mjs.map