Spaces:
Build error
Build error
| // SPDX-License-Identifier: MIT | |
| pragma solidity ^0.8.24; | |
| import {FHE, euint64, ebool, externalEuint64} from "@fhevm/solidity/lib/FHE.sol"; | |
| import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol"; | |
| interface IReputationBadge { | |
| function mintOrUpgrade(uint256 agentId, address operator, uint8 tier) external; | |
| function tierOf(uint256 agentId) external view returns (uint8); | |
| } | |
| interface IInsurancePool { | |
| function receivePenalty(uint256 agentId) external payable; | |
| function delegateCredit(uint256 agentId, uint256 amount) external; | |
| function repayCredit(uint256 agentId) external payable; | |
| } | |
| /** | |
| * @title CipherTrust | |
| * @notice Confidential underwriting protocol for autonomous agents & robots. | |
| * | |
| * Autonomous AI trading bots, delivery robots, drone fleets, and DePIN devices | |
| * increasingly hold funds and execute tasks without human supervision. There is | |
| * no confidential way today to score their reliability and price the | |
| * collateral/insurance they must post -- any naive on-chain reputation system | |
| * leaks competitively sensitive operational data (uptime, error rates, routes, | |
| * strategy performance) to rivals, because blockchains are public by default. | |
| * | |
| * CipherTrust computes a rolling trust score and a required collateral bond | |
| * entirely under Fully Homomorphic Encryption. Operators, insurers, and task | |
| * marketplaces can rely on the *outcome* (bond tier, sufficiency check) | |
| * without ever seeing the raw encrypted telemetry that produced it. | |
| * | |
| * v0.2 additions (see docs/COMPETITIVE_ANALYSIS.md for why these were added): | |
| * - Multi-oracle quorum: telemetry only affects the score once N independent | |
| * oracles agree within a round, reducing single-oracle trust assumptions. | |
| * - Async confidential slashing: an oracle can request a confidential SLA | |
| * breach check; the breach flag is decrypted via Zama's public-decrypt + | |
| * signature-verification flow before any penalty is applied on-chain. | |
| * - Optional composability hooks into a soulbound ReputationBadge (public, | |
| * selectively-revealed trust tier) and an InsurancePool (LP yield funded | |
| * by slashing penalties), so other protocols can build on CipherTrust's | |
| * output without ever touching an agent's raw telemetry. | |
| * | |
| * NOTE: this is an MVP scaffold. Verify every FHE.* call (especially the | |
| * makePubliclyDecryptable / checkSignatures async-decrypt flow) against the | |
| * exact current version of fhevm-solidity pinned in package.json before | |
| * deploying to a live network -- the FHE Solidity API surface evolves | |
| * between releases, and this flow has not yet been compiled/tested. | |
| */ | |
| contract CipherTrust is SepoliaConfig { | |
| address public admin; | |
| uint256 public nextAgentId; | |
| struct Agent { | |
| address operator; | |
| bool registered; | |
| bool active; | |
| uint256 identityId; // optional link into AgentIdentityRegistry, 0 if unset | |
| euint64 trustScore; // encrypted, 0-1000 scale | |
| euint64 requiredBond; // encrypted, wei | |
| uint256 postedBond; // public collateral currently deposited (wei) | |
| ebool bondSufficient; // encrypted boolean: postedBond >= requiredBond | |
| uint256 breachCount; // public count of confirmed SLA breaches | |
| uint256 trustScoreVar; // estimation uncertainty variance, initialized to 100 (public) | |
| euint64 liquidationThreshold; // encrypted minimum trust score before liquidation | |
| uint256 delegatedBond; // public delegated bond amount borrowed from the pool (wei) | |
| uint256 interestAccumulated; // public interest accumulated (wei) | |
| uint256 lastInterestUpdateTime; // timestamp of the last yield accrual | |
| } | |
| struct Task { | |
| uint256 agentId; | |
| address client; | |
| uint256 coverageLimit; // maximum ETH coverage (wei) | |
| bool active; | |
| } | |
| struct Lease { | |
| address lessee; | |
| uint256 agentId; | |
| uint256 hardwareId; | |
| uint256 requiredBond; // underwriting bond (wei) | |
| uint256 startTimestamp; | |
| bool active; | |
| } | |
| uint256 public nextTaskId = 1; | |
| mapping(uint256 => Task) public tasks; | |
| uint256 public nextLeaseId = 1; | |
| mapping(uint256 => Lease) public leases; | |
| mapping(address => uint256) public userActiveLeaseId; | |
| mapping(uint256 => uint256) public claimRequestTask; // decryption requestId => taskId | |
| mapping(uint256 => uint256) public agentActiveTaskId; // agentId => active taskId (0 if none) | |
| struct PendingRound { | |
| uint32 count; | |
| bool initialized; | |
| euint64 sumCompletion; | |
| euint64 sumUptime; | |
| euint64 sumLatency; | |
| euint64 sumError; | |
| } | |
| mapping(uint256 => Agent) private _agents; | |
| mapping(address => bool) public authorizedOracles; | |
| mapping(address => bool) public authorizedUnderwriters; | |
| // FHE-Stream: Confidential Staking Yields & Payroll Streams | |
| struct SalaryStream { | |
| euint64 flowRate; | |
| uint256 lastClaimBlock; | |
| bool active; | |
| } | |
| mapping(address => SalaryStream) private _salaryStreams; | |
| mapping(uint256 => address) public streamRequests; | |
| mapping(uint256 => PendingRound) private _pendingRounds; // agentId => in-flight quorum round | |
| mapping(uint256 => uint256) public currentRoundId; // agentId => round id | |
| mapping(uint256 => mapping(address => uint256)) private _oracleLastRound; // agentId => oracle => last round id + 1 submitted | |
| uint32 public quorumThreshold = 1; // number of independent oracles required per round | |
| uint256 public nextTierRequestId = 1; | |
| mapping(uint256 => uint256) public tierRequestAgent; | |
| mapping(uint256 => bytes32) public tierRequestHandle; | |
| uint256 public nextSlashRequestId = 1; | |
| mapping(uint256 => uint256) public slashRequestAgent; | |
| mapping(uint256 => bytes32) public slashRequestHandle; | |
| uint256 public nextLiquidationRequestId = 1; | |
| mapping(uint256 => uint256) public liquidationRequestAgent; | |
| IReputationBadge public reputationBadge; | |
| IInsurancePool public insurancePool; | |
| // FHE-ML Neural Perceptron Weights (Underwriter Configurable) | |
| uint32 public weightCompletion = 40; | |
| uint32 public weightUptime = 30; | |
| uint32 public weightLatency = 15; | |
| uint32 public weightError = 80; | |
| uint32 public neuronBias = 200; | |
| uint32 public maxNeuralRiskThreshold = 1200; // ReLU risk limit | |
| uint64 private constant W_COMPLETION = 40; | |
| uint64 private constant W_UPTIME = 30; | |
| uint64 private constant W_LATENCY = 15; | |
| uint64 private constant W_ERROR = 15; | |
| uint64 private constant HIGH_TRUST_THRESHOLD = 750; | |
| uint64 private constant MED_TRUST_THRESHOLD = 400; | |
| uint64 private constant HIGH_TRUST_BOND = uint64(0.1 ether); | |
| uint64 private constant MED_TRUST_BOND = uint64(1 ether); | |
| uint64 private constant LOW_TRUST_BOND = uint64(5 ether); | |
| uint256 private constant SLASH_BPS = 1000; // 10% of posted bond | |
| uint256 private constant ORACLE_VAR = 50; | |
| uint256 private constant PREMIUM_PER_VAR_WEI = 0.04 ether; // 0.04 ETH per unit of variance | |
| event AgentRegistered(uint256 indexed agentId, address indexed operator, uint256 identityId); | |
| event OracleAuthorized(address indexed oracle); | |
| event UnderwriterAuthorized(address indexed underwriter); | |
| event TelemetrySubmitted(uint256 indexed agentId, address indexed oracle, uint256 roundId); | |
| event ScoreUpdated(uint256 indexed agentId, uint256 roundId); | |
| event BondDeposited(uint256 indexed agentId, uint256 amount, uint256 totalPosted); | |
| event BondWithdrawn(uint256 indexed agentId, uint256 amount); | |
| event TierRevealRequested(uint256 indexed agentId, uint256 indexed requestId); | |
| event TierRevealed(uint256 indexed agentId, uint64 tierCode); | |
| event SlashCheckRequested(uint256 indexed agentId, uint256 indexed requestId); | |
| event SlashCheckFulfilled(uint256 indexed agentId, bool breached); | |
| event AgentSlashed(uint256 indexed agentId, uint256 penalty); | |
| event AgentLiquidated(uint256 indexed agentId, uint256 slashedAmount); | |
| event LiquidationCheckRequested(uint256 indexed agentId, uint256 indexed requestId); | |
| event TaskRegistered(uint256 indexed taskId, uint256 indexed agentId, address indexed client, uint256 coverageLimit); | |
| event ClaimPaid(uint256 indexed taskId, uint256 indexed agentId, address indexed client, uint256 payoutAmount); | |
| event NeuronWeightsUpdated(uint32 wComp, uint32 wUpt, uint32 wLat, uint32 wErr, uint32 bias, uint32 threshold); | |
| event ReputationBadgeSet(address indexed badge); | |
| event InsurancePoolSet(address indexed pool); | |
| event LeaseRequested(uint256 indexed leaseId, address indexed lessee, uint256 hardwareId, uint256 requiredBond); | |
| event LeaseSettled(uint256 indexed leaseId, address indexed lessee, uint256 hardwareId, bool success, uint256 payout); | |
| event SalaryStreamCreated(address indexed recipient); | |
| event StreamClaimRequested(address indexed recipient, uint256 indexed requestId); | |
| event StreamClaimed(address indexed recipient, uint256 amount); | |
| modifier onlyAdmin() { | |
| require(msg.sender == admin); | |
| _; | |
| } | |
| modifier onlyOracle() { | |
| require(authorizedOracles[msg.sender]); | |
| _; | |
| } | |
| modifier onlyAgentOperator(uint256 agentId) { | |
| require(_agents[agentId].operator == msg.sender); | |
| _; | |
| } | |
| constructor() { | |
| admin = msg.sender; | |
| } | |
| function setQuorumThreshold(uint32 threshold) external onlyAdmin { | |
| require(threshold >= 1); | |
| quorumThreshold = threshold; | |
| } | |
| function setReputationBadge(address badge) external onlyAdmin { | |
| require(address(reputationBadge) == address(0)); | |
| reputationBadge = IReputationBadge(badge); | |
| emit ReputationBadgeSet(badge); | |
| } | |
| function setInsurancePool(address pool) external onlyAdmin { | |
| require(address(insurancePool) == address(0)); | |
| insurancePool = IInsurancePool(pool); | |
| emit InsurancePoolSet(pool); | |
| } | |
| function authorizeOracle(address oracle) external onlyAdmin { | |
| authorizedOracles[oracle] = true; | |
| emit OracleAuthorized(oracle); | |
| } | |
| function authorizeUnderwriter(address underwriter) external onlyAdmin { | |
| authorizedUnderwriters[underwriter] = true; | |
| emit UnderwriterAuthorized(underwriter); | |
| } | |
| /// @notice Register a new autonomous agent/robot under a given operator. | |
| /// @param identityId optional AgentIdentityRegistry id (0 if not using the registry). | |
| function registerAgent(address operator, uint256 identityId) external onlyAdmin returns (uint256 agentId) { | |
| agentId = nextAgentId++; | |
| Agent storage a = _agents[agentId]; | |
| a.operator = operator; | |
| a.registered = true; | |
| a.active = true; | |
| a.identityId = identityId; | |
| a.trustScore = FHE.asEuint64(500); // neutral starting score | |
| a.trustScoreVar = 100; | |
| a.liquidationThreshold = FHE.asEuint64(300); | |
| a.requiredBond = _deriveBond(a.trustScore, 100); | |
| a.delegatedBond = 0; | |
| a.interestAccumulated = 0; | |
| a.lastInterestUpdateTime = block.timestamp; | |
| FHE.allowThis(a.trustScore); | |
| FHE.allowThis(a.requiredBond); | |
| FHE.allowThis(a.liquidationThreshold); | |
| FHE.allow(a.trustScore, operator); | |
| FHE.allow(a.requiredBond, operator); | |
| FHE.allow(a.liquidationThreshold, operator); | |
| emit AgentRegistered(agentId, operator, identityId); | |
| } | |
| /// @notice Submit fully-encrypted telemetry for a completed task. Only | |
| /// authorized oracles may call this. The submission only affects the | |
| /// agent's score once `quorumThreshold` independent oracles have | |
| /// submitted within the current round. | |
| function submitTelemetry( | |
| uint256 agentId, | |
| externalEuint64 completionScoreA, | |
| externalEuint64 completionScoreB, | |
| externalEuint64 uptimeScore, | |
| externalEuint64 latencyScore, | |
| externalEuint64 errorScore, | |
| bytes calldata inputProof | |
| ) external onlyOracle { | |
| Agent storage a = _agents[agentId]; | |
| require(a.registered && a.active); | |
| uint256 roundId = currentRoundId[agentId]; | |
| require(_oracleLastRound[agentId][msg.sender] != roundId + 1); | |
| _oracleLastRound[agentId][msg.sender] = roundId + 1; | |
| euint64 compA = FHE.fromExternal(completionScoreA, inputProof); | |
| euint64 compB = FHE.fromExternal(completionScoreB, inputProof); | |
| euint64 uptime = FHE.fromExternal(uptimeScore, inputProof); | |
| euint64 latency = FHE.fromExternal(latencyScore, inputProof); | |
| euint64 errorP = FHE.fromExternal(errorScore, inputProof); | |
| // Compute absolute differences for anomaly detection (Completion only) | |
| ebool compAltB = FHE.lt(compA, compB); | |
| euint64 compDiff = FHE.select(compAltB, FHE.sub(compB, compA), FHE.sub(compA, compB)); | |
| ebool compAnomaly = FHE.gt(compDiff, FHE.asEuint64(2)); | |
| // Apply sensor fusion outlier filter | |
| euint64 completion = FHE.select(compAnomaly, FHE.asEuint64(0), FHE.div(FHE.add(compA, compB), 2)); | |
| uptime = FHE.select(compAnomaly, FHE.asEuint64(0), uptime); | |
| latency = FHE.select(compAnomaly, FHE.asEuint64(0), latency); | |
| errorP = FHE.select(compAnomaly, FHE.asEuint64(10), errorP); | |
| PendingRound storage round = _pendingRounds[agentId]; | |
| if (!round.initialized) { | |
| round.sumCompletion = completion; | |
| round.sumUptime = uptime; | |
| round.sumLatency = latency; | |
| round.sumError = errorP; | |
| round.initialized = true; | |
| } else { | |
| round.sumCompletion = FHE.add(round.sumCompletion, completion); | |
| round.sumUptime = FHE.add(round.sumUptime, uptime); | |
| round.sumLatency = FHE.add(round.sumLatency, latency); | |
| round.sumError = FHE.add(round.sumError, errorP); | |
| } | |
| round.count += 1; | |
| FHE.allowThis(round.sumCompletion); | |
| FHE.allowThis(round.sumUptime); | |
| FHE.allowThis(round.sumLatency); | |
| FHE.allowThis(round.sumError); | |
| emit TelemetrySubmitted(agentId, msg.sender, roundId); | |
| if (round.count >= quorumThreshold) { | |
| euint64 avgCompletion = FHE.div(round.sumCompletion, quorumThreshold); | |
| euint64 avgUptime = FHE.div(round.sumUptime, quorumThreshold); | |
| euint64 avgLatency = FHE.div(round.sumLatency, quorumThreshold); | |
| euint64 avgError = FHE.div(round.sumError, quorumThreshold); | |
| _applyScoreUpdate(agentId, avgCompletion, avgUptime, avgLatency, avgError); | |
| delete _pendingRounds[agentId]; | |
| currentRoundId[agentId] = roundId + 1; | |
| emit ScoreUpdated(agentId, roundId); | |
| } | |
| } | |
| function _applyScoreUpdate( | |
| uint256 agentId, | |
| euint64 completion, | |
| euint64 uptime, | |
| euint64 latency, | |
| euint64 errorP | |
| ) private { | |
| Agent storage a = _agents[agentId]; | |
| euint64 weightedObs = FHE.add( | |
| FHE.add(FHE.mul(completion, W_COMPLETION), FHE.mul(uptime, W_UPTIME)), | |
| FHE.mul(latency, W_LATENCY) | |
| ); | |
| euint64 penalty = FHE.mul(errorP, W_ERROR); | |
| ebool obsUnderflow = FHE.lt(weightedObs, penalty); | |
| euint64 x_obs = FHE.select(obsUnderflow, FHE.asEuint64(0), FHE.sub(weightedObs, penalty)); | |
| // Bayesian Update for variance and weights | |
| uint256 oldVar = a.trustScoreVar; | |
| uint256 newVar = (oldVar * ORACLE_VAR) / (oldVar + ORACLE_VAR); | |
| if (newVar < 10) { | |
| newVar = 10; | |
| } | |
| a.trustScoreVar = newVar; | |
| uint256 alpha = (ORACLE_VAR * 100) / (oldVar + ORACLE_VAR); | |
| uint256 beta = (oldVar * 100) / (oldVar + ORACLE_VAR); | |
| // Weighted FHE score update | |
| euint64 term1 = FHE.mul(a.trustScore, uint64(alpha)); | |
| euint64 term2 = FHE.mul(x_obs, uint64(beta)); | |
| euint64 newScore = FHE.div(FHE.add(term1, term2), 100); | |
| a.trustScore = newScore; | |
| a.requiredBond = _deriveBond(newScore, newVar); | |
| uint256 dt = block.timestamp - a.lastInterestUpdateTime; | |
| a.lastInterestUpdateTime = block.timestamp; | |
| if (dt > 0 && a.delegatedBond > 0) { | |
| uint256 apr = 500; // default 5% APR | |
| if (address(reputationBadge) != address(0)) { | |
| uint8 tier = reputationBadge.tierOf(agentId); | |
| if (tier == 3) apr = 100; | |
| else if (tier == 2) apr = 500; | |
| else if (tier == 1) apr = 2500; | |
| } | |
| uint256 interestAcc = (a.delegatedBond * apr * dt) / 8640000000; | |
| a.interestAccumulated += interestAcc; | |
| } | |
| euint64 totalCollateral = FHE.add(FHE.asEuint64(uint64(_clampToU64(a.postedBond))), FHE.asEuint64(uint64(_clampToU64(a.delegatedBond)))); | |
| a.bondSufficient = FHE.ge(totalCollateral, a.requiredBond); | |
| FHE.allowThis(a.trustScore); | |
| FHE.allowThis(a.requiredBond); | |
| FHE.allowThis(a.bondSufficient); | |
| FHE.allow(a.trustScore, a.operator); | |
| FHE.allow(a.requiredBond, a.operator); | |
| // On-chain FHE Perceptron (Confidential AI Model Inference) | |
| euint64 positiveRisk = FHE.add( | |
| FHE.add(FHE.mul(latency, weightLatency), FHE.mul(errorP, weightError)), | |
| FHE.asEuint64(neuronBias) | |
| ); | |
| euint64 negativeRisk = FHE.add( | |
| FHE.mul(completion, weightCompletion), | |
| FHE.mul(uptime, weightUptime) | |
| ); | |
| ebool riskUnderflow = FHE.lt(positiveRisk, negativeRisk); | |
| euint64 neuralRisk = FHE.select(riskUnderflow, FHE.asEuint64(0), FHE.sub(positiveRisk, negativeRisk)); | |
| ebool isNeuralBreach = FHE.gt(neuralRisk, FHE.asEuint64(maxNeuralRiskThreshold)); | |
| ebool breachedLimit = FHE.or(FHE.lt(newScore, a.liquidationThreshold), isNeuralBreach); | |
| euint64 severity = FHE.sub(FHE.asEuint64(1000), newScore); | |
| bytes32[] memory cts = new bytes32[](2); | |
| cts[0] = ebool.unwrap(breachedLimit); | |
| cts[1] = euint64.unwrap(severity); | |
| uint256 reqId = FHE.requestDecryption(cts, this.fulfillLiquidation.selector); | |
| liquidationRequestAgent[reqId] = agentId; | |
| emit LiquidationCheckRequested(agentId, reqId); | |
| } | |
| /// @dev Confidential decision-tree: three bond tiers selected entirely | |
| /// under encryption via FHE.select, plus a dynamic uncertainty premium. | |
| function _deriveBond(euint64 score, uint256 variance) private returns (euint64) { | |
| ebool highTrust = FHE.ge(score, FHE.asEuint64(HIGH_TRUST_THRESHOLD)); | |
| ebool medTrust = FHE.ge(score, FHE.asEuint64(MED_TRUST_THRESHOLD)); | |
| euint64 baseBond = FHE.select(medTrust, FHE.asEuint64(MED_TRUST_BOND), FHE.asEuint64(LOW_TRUST_BOND)); | |
| baseBond = FHE.select(highTrust, FHE.asEuint64(HIGH_TRUST_BOND), baseBond); | |
| uint256 premium = variance * PREMIUM_PER_VAR_WEI; | |
| return FHE.add(baseBond, FHE.asEuint64(uint64(premium))); | |
| } | |
| function _clampToU64(uint256 value) private pure returns (uint256) { | |
| uint256 maxU64 = type(uint64).max; | |
| return value > maxU64 ? maxU64 : value; | |
| } | |
| /// @notice Operator posts native-token collateral for an agent. | |
| function depositBond(uint256 agentId) external payable onlyAgentOperator(agentId) { | |
| require(msg.value > 0); | |
| Agent storage a = _agents[agentId]; | |
| a.postedBond += msg.value; | |
| a.bondSufficient = FHE.ge(FHE.asEuint64(uint64(_clampToU64(a.postedBond))), a.requiredBond); | |
| FHE.allowThis(a.bondSufficient); | |
| FHE.allow(a.bondSufficient, a.operator); | |
| emit BondDeposited(agentId, msg.value, a.postedBond); | |
| } | |
| /// @notice Operator withdraws excess collateral. Confidential sufficiency | |
| /// should be re-checked off-chain via the relayer SDK before withdrawing, | |
| /// since the exact required bond stays encrypted on-chain. | |
| function withdrawBond(uint256 agentId, uint256 amount) external onlyAgentOperator(agentId) { | |
| Agent storage a = _agents[agentId]; | |
| require(amount <= a.postedBond); | |
| a.postedBond -= amount; | |
| a.bondSufficient = FHE.ge(FHE.asEuint64(uint64(_clampToU64(a.postedBond))), a.requiredBond); | |
| FHE.allowThis(a.bondSufficient); | |
| FHE.allow(a.bondSufficient, a.operator); | |
| payable(msg.sender).transfer(amount); | |
| emit BondWithdrawn(agentId, amount); | |
| } | |
| /// @notice Grant an authorized underwriter/insurer read access to an | |
| /// agent's encrypted trust score, required bond, and sufficiency flag -- | |
| /// without exposing the raw telemetry that produced them. | |
| function grantUnderwriterAccess(uint256 agentId, address underwriter) external onlyAgentOperator(agentId) { | |
| require(authorizedUnderwriters[underwriter]); | |
| Agent storage a = _agents[agentId]; | |
| FHE.allow(a.trustScore, underwriter); | |
| FHE.allow(a.requiredBond, underwriter); | |
| FHE.allow(a.bondSufficient, underwriter); | |
| } | |
| /// @notice Operator opts in to publicly reveal only the *tier* (Low/Medium/High) | |
| /// of their agent's trust score -- never the exact score -- so a soulbound | |
| /// ReputationBadge can be minted/upgraded. This is a selective disclosure, | |
| /// not a default: the raw score stays encrypted unless the operator calls this. | |
| function requestTierReveal(uint256 agentId) external onlyAgentOperator(agentId) returns (uint256 requestId) { | |
| Agent storage a = _agents[agentId]; | |
| ebool highTrust = FHE.ge(a.trustScore, FHE.asEuint64(HIGH_TRUST_THRESHOLD)); | |
| ebool medTrust = FHE.ge(a.trustScore, FHE.asEuint64(MED_TRUST_THRESHOLD)); | |
| euint64 tierCode = FHE.select(highTrust, FHE.asEuint64(3), FHE.select(medTrust, FHE.asEuint64(2), FHE.asEuint64(1))); | |
| bytes32[] memory cts = new bytes32[](1); | |
| cts[0] = euint64.unwrap(tierCode); | |
| requestId = FHE.requestDecryption(cts, this.fulfillTierReveal.selector); | |
| tierRequestAgent[requestId] = agentId; | |
| emit TierRevealRequested(agentId, requestId); | |
| } | |
| /// @notice Called with the Zama KMS's decrypted cleartext + proof (via the | |
| /// relayer SDK's public-decrypt flow) to finalize a tier reveal. | |
| function fulfillTierReveal(uint256 requestId, bytes memory cleartexts, bytes memory decryptionProof) external { | |
| FHE.checkSignatures(requestId, cleartexts, decryptionProof); | |
| uint64 tierCode = abi.decode(cleartexts, (uint64)); | |
| uint256 agentId = tierRequestAgent[requestId]; | |
| delete tierRequestAgent[requestId]; | |
| if (address(reputationBadge) != address(0)) { | |
| reputationBadge.mintOrUpgrade(agentId, _agents[agentId].operator, uint8(tierCode)); | |
| } | |
| emit TierRevealed(agentId, tierCode); | |
| } | |
| /// @notice An authorized oracle flags a possible SLA breach with an | |
| /// encrypted 0/1 signal. Nothing happens on-chain until the flag is | |
| /// confidentially checked and revealed via fulfillSlashCheck. | |
| function requestSlashCheck( | |
| uint256 agentId, | |
| externalEuint64 breachSignal, | |
| bytes calldata inputProof | |
| ) external onlyOracle returns (uint256 requestId) { | |
| Agent storage a = _agents[agentId]; | |
| require(a.registered); | |
| euint64 signal = FHE.fromExternal(breachSignal, inputProof); | |
| ebool breached = FHE.eq(signal, FHE.asEuint64(1)); | |
| bytes32[] memory cts = new bytes32[](1); | |
| cts[0] = ebool.unwrap(breached); | |
| requestId = FHE.requestDecryption(cts, this.fulfillSlashCheck.selector); | |
| slashRequestAgent[requestId] = agentId; | |
| emit SlashCheckRequested(agentId, requestId); | |
| } | |
| /// @notice Finalizes a slash check using the Zama KMS's decrypted | |
| /// cleartext + proof. If breached, 10% of the posted bond is slashed and | |
| /// forwarded to the InsurancePool (if configured) as LP yield. | |
| function fulfillSlashCheck(uint256 requestId, bytes memory cleartexts, bytes memory decryptionProof) external { | |
| FHE.checkSignatures(requestId, cleartexts, decryptionProof); | |
| bool breached = abi.decode(cleartexts, (bool)); | |
| uint256 agentId = slashRequestAgent[requestId]; | |
| delete slashRequestAgent[requestId]; | |
| if (breached) { | |
| Agent storage a = _agents[agentId]; | |
| uint256 penalty = (a.postedBond * SLASH_BPS) / 10000; | |
| if (penalty > 0) { | |
| a.postedBond -= penalty; | |
| a.breachCount += 1; | |
| a.bondSufficient = FHE.ge(FHE.asEuint64(uint64(_clampToU64(a.postedBond))), a.requiredBond); | |
| FHE.allowThis(a.bondSufficient); | |
| FHE.allow(a.bondSufficient, a.operator); | |
| if (address(insurancePool) != address(0)) { | |
| insurancePool.receivePenalty{value: penalty}(agentId); | |
| } | |
| emit AgentSlashed(agentId, penalty); | |
| } | |
| } | |
| emit SlashCheckFulfilled(agentId, breached); | |
| } | |
| /// @notice Finalizes a liquidation check using the Zama KMS's decrypted | |
| /// cleartext + proof. If breached, the agent is deactivated and its remaining | |
| /// posted bond is fully slashed to the InsurancePool. | |
| function fulfillLiquidation(uint256 requestId, bytes memory cleartexts, bytes memory decryptionProof) external { | |
| FHE.checkSignatures(requestId, cleartexts, decryptionProof); | |
| (bool breached, uint256 severity) = abi.decode(cleartexts, (bool, uint256)); | |
| uint256 agentId = liquidationRequestAgent[requestId]; | |
| delete liquidationRequestAgent[requestId]; | |
| if (breached) { | |
| Agent storage a = _agents[agentId]; | |
| a.active = false; | |
| uint256 selfBond = a.postedBond; | |
| uint256 borrowedBond = a.delegatedBond; | |
| a.postedBond = 0; | |
| a.delegatedBond = 0; | |
| a.bondSufficient = FHE.asEbool(false); | |
| FHE.allowThis(a.bondSufficient); | |
| FHE.allow(a.bondSufficient, a.operator); | |
| uint256 totalBond = selfBond + borrowedBond; | |
| uint256 payoutAmount = 0; | |
| uint256 taskId = agentActiveTaskId[agentId]; | |
| if (taskId > 0 && tasks[taskId].active) { | |
| payoutAmount = (tasks[taskId].coverageLimit * severity) / 1000; | |
| if (payoutAmount > totalBond) { | |
| payoutAmount = totalBond; | |
| } | |
| Task storage t = tasks[taskId]; | |
| t.active = false; | |
| agentActiveTaskId[agentId] = 0; | |
| if (payoutAmount > 0) { | |
| payable(t.client).transfer(payoutAmount); | |
| emit ClaimPaid(taskId, agentId, t.client, payoutAmount); | |
| } | |
| } | |
| uint256 remainder = totalBond - payoutAmount; | |
| if (remainder > 0) { | |
| if (address(insurancePool) != address(0)) { | |
| insurancePool.receivePenalty{value: remainder}(agentId); | |
| } | |
| emit AgentLiquidated(agentId, remainder); | |
| } | |
| } | |
| } | |
| function getAgent(uint256 agentId) | |
| external | |
| view | |
| returns (address operator, bool registered, bool active, uint256 postedBond, uint256 breachCount, uint256 identityId, uint256 trustScoreVar) | |
| { | |
| Agent storage a = _agents[agentId]; | |
| return (a.operator, a.registered, a.active, a.postedBond, a.breachCount, a.identityId, a.trustScoreVar); | |
| } | |
| function getEncryptedTrustScore(uint256 agentId) external view returns (euint64) { | |
| return _agents[agentId].trustScore; | |
| } | |
| function getEncryptedRequiredBond(uint256 agentId) external view returns (euint64) { | |
| return _agents[agentId].requiredBond; | |
| } | |
| function getEncryptedBondSufficiency(uint256 agentId) external view returns (ebool) { | |
| return _agents[agentId].bondSufficient; | |
| } | |
| function getDelegatedBond(uint256 agentId) external view returns (uint256) { | |
| return _agents[agentId].delegatedBond; | |
| } | |
| function getInterestAccumulated(uint256 agentId) external view returns (uint256) { | |
| return _agents[agentId].interestAccumulated; | |
| } | |
| event CreditDelegated(uint256 indexed agentId, uint256 amount); | |
| event InterestRepaid(uint256 indexed agentId, uint256 amount); | |
| function requestCreditDelegation(uint256 agentId, uint256 amount) external { | |
| Agent storage a = _agents[agentId]; | |
| require(msg.sender == a.operator); | |
| require(a.registered && a.active); | |
| require(address(insurancePool) != address(0)); | |
| insurancePool.delegateCredit(agentId, amount); | |
| a.delegatedBond += amount; | |
| euint64 totalCollateral = FHE.add(FHE.asEuint64(uint64(_clampToU64(a.postedBond))), FHE.asEuint64(uint64(_clampToU64(a.delegatedBond)))); | |
| a.bondSufficient = FHE.ge(totalCollateral, a.requiredBond); | |
| FHE.allowThis(a.bondSufficient); | |
| FHE.allow(a.bondSufficient, a.operator); | |
| emit CreditDelegated(agentId, amount); | |
| } | |
| function repayInterest(uint256 agentId) external payable { | |
| Agent storage a = _agents[agentId]; | |
| require(a.registered && a.active); | |
| require(msg.value > 0); | |
| if (msg.value >= a.interestAccumulated) { | |
| a.interestAccumulated = 0; | |
| } else { | |
| a.interestAccumulated -= msg.value; | |
| } | |
| insurancePool.repayCredit{value: msg.value}(agentId); | |
| emit InterestRepaid(agentId, msg.value); | |
| } | |
| function registerUnderwrittenTask(uint256 agentId, address client, uint256 coverageLimit) external returns (uint256 taskId) { | |
| Agent storage a = _agents[agentId]; | |
| require(msg.sender == a.operator || msg.sender == admin); | |
| require(a.registered && a.active); | |
| require(agentActiveTaskId[agentId] == 0); | |
| uint256 totalCollateral = a.postedBond + a.delegatedBond; | |
| require(totalCollateral >= coverageLimit); | |
| taskId = nextTaskId++; | |
| Task storage t = tasks[taskId]; | |
| t.agentId = agentId; | |
| t.client = client; | |
| t.coverageLimit = coverageLimit; | |
| t.active = true; | |
| agentActiveTaskId[agentId] = taskId; | |
| emit TaskRegistered(taskId, agentId, client, coverageLimit); | |
| } | |
| function deactivateAgent(uint256 agentId) external onlyAdmin { | |
| _agents[agentId].active = false; | |
| } | |
| function updateNeuronWeights( | |
| uint32 wComp, | |
| uint32 wUpt, | |
| uint32 wLat, | |
| uint32 wErr, | |
| uint32 bias, | |
| uint32 threshold | |
| ) external onlyAdmin { | |
| weightCompletion = wComp; | |
| weightUptime = wUpt; | |
| weightLatency = wLat; | |
| weightError = wErr; | |
| neuronBias = bias; | |
| maxNeuralRiskThreshold = threshold; | |
| emit NeuronWeightsUpdated(wComp, wUpt, wLat, wErr, bias, threshold); | |
| } | |
| function requestLeaseHardware( | |
| uint256 agentId, | |
| uint256 hardwareId, | |
| uint256 leaseBond | |
| ) external returns (uint256 leaseId) { | |
| Agent storage a = _agents[agentId]; | |
| require(msg.sender == a.operator); | |
| require(a.registered && a.active); | |
| require(address(reputationBadge) != address(0)); | |
| require(address(insurancePool) != address(0)); | |
| require(userActiveLeaseId[msg.sender] == 0); | |
| uint8 tier = reputationBadge.tierOf(agentId); | |
| require(tier >= 2); | |
| insurancePool.delegateCredit(agentId, leaseBond); | |
| leaseId = nextLeaseId++; | |
| Lease storage l = leases[leaseId]; | |
| l.lessee = msg.sender; | |
| l.agentId = agentId; | |
| l.hardwareId = hardwareId; | |
| l.requiredBond = leaseBond; | |
| l.startTimestamp = block.timestamp; | |
| l.active = true; | |
| userActiveLeaseId[msg.sender] = leaseId; | |
| emit LeaseRequested(leaseId, msg.sender, hardwareId, leaseBond); | |
| } | |
| function settleLeaseHardware(uint256 leaseId, bool success) external onlyAdmin { | |
| Lease storage l = leases[leaseId]; | |
| require(l.active); | |
| l.active = false; | |
| userActiveLeaseId[l.lessee] = 0; | |
| uint256 payout = 0; | |
| if (!success) { | |
| payout = l.requiredBond; | |
| payable(admin).transfer(payout); | |
| } else { | |
| insurancePool.repayCredit{value: l.requiredBond}(l.agentId); | |
| } | |
| emit LeaseSettled(leaseId, l.lessee, l.hardwareId, success, payout); | |
| } | |
| // FHE-Stream: Confidential Salary & Yield Streaming Implementation | |
| function createSalaryStream( | |
| address recipient, | |
| externalEuint64 hRate, | |
| bytes calldata inputProof | |
| ) external onlyAdmin { | |
| require(!_salaryStreams[recipient].active); | |
| _salaryStreams[recipient].flowRate = FHE.fromExternal(hRate, inputProof); | |
| _salaryStreams[recipient].lastClaimBlock = block.number; | |
| _salaryStreams[recipient].active = true; | |
| FHE.allow(_salaryStreams[recipient].flowRate, recipient); | |
| FHE.allowThis(_salaryStreams[recipient].flowRate); | |
| emit SalaryStreamCreated(recipient); | |
| } | |
| function claimSalaryStream() external returns (uint256 requestId) { | |
| address recipient = msg.sender; | |
| SalaryStream storage stream = _salaryStreams[recipient]; | |
| require(stream.active); | |
| require(block.number > stream.lastClaimBlock); | |
| uint256 blocksAccrued = block.number - stream.lastClaimBlock; | |
| stream.lastClaimBlock = block.number; | |
| euint64 accrued = FHE.mul(stream.flowRate, uint64(blocksAccrued)); | |
| FHE.allowThis(accrued); | |
| bytes32[] memory cts = new bytes32[](1); | |
| cts[0] = euint64.unwrap(accrued); | |
| requestId = FHE.requestDecryption(cts, this.fulfillStreamClaim.selector); | |
| streamRequests[requestId] = recipient; | |
| emit StreamClaimRequested(recipient, requestId); | |
| } | |
| function fulfillStreamClaim( | |
| uint256 requestId, | |
| bytes memory cleartexts, | |
| bytes memory decryptionProof | |
| ) external { | |
| FHE.checkSignatures(requestId, cleartexts, decryptionProof); | |
| uint256 amount = abi.decode(cleartexts, (uint256)); | |
| address recipient = streamRequests[requestId]; | |
| delete streamRequests[requestId]; | |
| if (amount > 0) { | |
| payable(recipient).transfer(amount); | |
| } | |
| emit StreamClaimed(recipient, amount); | |
| } | |
| receive() external payable {} | |
| } | |