// SPDX-License-Identifier: MIT pragma solidity ^0.8.30; import {LendingIntent, CollateralEntry, Rule} from "../types/DataTypes.sol"; /// @title IntentHash /// @notice EIP-712 hashing for Amish intents plus `matchId` derivation. /// @dev The signed struct is flat: the collateral and rules arrays are committed as `bytes32` /// hashes (`collateralHash`, `rulesHash`) rather than nested EIP-712 structs. This keeps /// the type cheap and means the on-chain verifier only recomputes two keccak256 digests to /// bind the full arrays to the signature. The off-chain signer MUST use the exact same /// encodings defined here: /// - collateralHash = keccak256(abi.encode(CollateralEntry[])) /// - rulesHash = keccak256(abi.encode(Rule[])) /// - the LendingIntent struct hash is the standard EIP-712 encodeData over the 14 fields /// This library returns the EIP-712 *struct hash*; the domain separator (and the final /// `_hashTypedDataV4` digest) is applied by the verifying contract (AmishHub), which owns /// the domain. library IntentHash { /// @notice EIP-712 typehash for `LendingIntent`. Field order MUST match the struct exactly; /// changing it invalidates every previously signed intent. bytes32 internal constant LENDING_INTENT_TYPEHASH = keccak256( "LendingIntent(bytes32 intentType,address creator,uint256 nonce,uint256 deadline,address principalToken,uint256 principalChainId,uint256 principalAmount,uint256 minFillAmount,bytes32 collateralHash,uint256 duration,bool partialFill,bytes32 rulesHash,bool apnTransferable,bool strict)" ); /// @notice Commitment to the off-chain collateral array bound into `LendingIntent.collateralHash`. function hashCollateral(CollateralEntry[] memory entries) internal pure returns (bytes32) { return keccak256(abi.encode(entries)); } /// @notice Commitment to the off-chain rules array bound into `LendingIntent.rulesHash`. /// An empty array yields the canonical hash of an empty encoding. function hashRules(Rule[] memory rules) internal pure returns (bytes32) { return keccak256(abi.encode(rules)); } /// @notice EIP-712 struct hash of an intent (before domain separation). function hashStruct(LendingIntent memory intent) internal pure returns (bytes32) { return keccak256( abi.encode( LENDING_INTENT_TYPEHASH, intent.intentType, intent.creator, intent.nonce, intent.deadline, intent.principalToken, intent.principalChainId, intent.principalAmount, intent.minFillAmount, intent.collateralHash, intent.duration, intent.partialFill, intent.rulesHash, intent.apnTransferable, intent.strict ) ); } /// @notice Derive the on-chain loan key from the two matched intents' struct hashes. /// @dev Order matters: the borrower struct hash is always first. Because each struct hash /// binds creator + nonce + all terms, distinct intent pairs produce distinct ids, and a /// partially-filled intent matched against different counterparties yields distinct ids. function deriveMatchId(bytes32 borrowerStructHash, bytes32 lenderStructHash) internal pure returns (bytes32) { return keccak256(abi.encode(borrowerStructHash, lenderStructHash)); } }