ETH Price: $3,616.74 (+0.01%)

Contract

0x0fF417Acf88a1b8Cc200E3098F8e77971c45864d
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Repay Loan212223712024-11-19 14:27:1114 days ago1732026431IN
0x0fF417Ac...71c45864d
0 ETH0.0057681116.81192131
Repay Loan212131402024-11-18 7:34:3515 days ago1731915275IN
0x0fF417Ac...71c45864d
0 ETH0.003421689.97470635
Repay Loan211773552024-11-13 7:43:5920 days ago1731483839IN
0x0fF417Ac...71c45864d
0 ETH0.0072880821.89718096
Repay Loan208259712024-09-25 6:55:5969 days ago1727247359IN
0x0fF417Ac...71c45864d
0 ETH0.0040097212.3542617
Repay Loan208259672024-09-25 6:55:1169 days ago1727247311IN
0x0fF417Ac...71c45864d
0 ETH0.0035619710.72605045
Repay Loan207791692024-09-18 18:08:1176 days ago1726682891IN
0x0fF417Ac...71c45864d
0 ETH0.0052897515.44612557
Repay Loan205921522024-08-23 15:19:47102 days ago1724426387IN
0x0fF417Ac...71c45864d
0 ETH0.001748315.1
Repay Loan205686912024-08-20 8:38:23105 days ago1724143103IN
0x0fF417Ac...71c45864d
0 ETH0.000733392.42402279
Repay Loan205294772024-08-14 21:12:47110 days ago1723669967IN
0x0fF417Ac...71c45864d
0 ETH0.001199083.6064104
Repay Loan191112652024-01-29 9:23:23309 days ago1706520203IN
0x0fF417Ac...71c45864d
0 ETH0.0052042917.72211848
Repay Loan191112472024-01-29 9:19:47309 days ago1706519987IN
0x0fF417Ac...71c45864d
0 ETH0.0045746214.47071231
Repay Loan190898812024-01-26 9:27:59312 days ago1706261279IN
0x0fF417Ac...71c45864d
0 ETH0.0055797117.37492112
Delegate Voting189766552024-01-10 12:49:23328 days ago1704890963IN
0x0fF417Ac...71c45864d
0 ETH0.0035134928.38777605
Repay Loan188912472023-12-29 12:30:23340 days ago1703853023IN
0x0fF417Ac...71c45864d
0 ETH0.0051075319.08010146
Repay Loan188752052023-12-27 6:26:11342 days ago1703658371IN
0x0fF417Ac...71c45864d
0 ETH0.0068302725.61675156
Repay Loan188752032023-12-27 6:25:47342 days ago1703658347IN
0x0fF417Ac...71c45864d
0 ETH0.0066213424.83317005
Repay Loan188752012023-12-27 6:25:23342 days ago1703658323IN
0x0fF417Ac...71c45864d
0 ETH0.0067163125.18936506
Repay Loan188751992023-12-27 6:24:59342 days ago1703658299IN
0x0fF417Ac...71c45864d
0 ETH0.0064769724.25416865
Repay Loan188751862023-12-27 6:22:23342 days ago1703658143IN
0x0fF417Ac...71c45864d
0 ETH0.0064914424.30415833
Repay Loan188751762023-12-27 6:20:23342 days ago1703658023IN
0x0fF417Ac...71c45864d
0 ETH0.0058791822.03833607
Repay Loan188751732023-12-27 6:19:47342 days ago1703657987IN
0x0fF417Ac...71c45864d
0 ETH0.0058038921.7673565
Repay Loan188751712023-12-27 6:19:23342 days ago1703657963IN
0x0fF417Ac...71c45864d
0 ETH0.0057706621.63154248
Repay Loan188751672023-12-27 6:18:35342 days ago1703657915IN
0x0fF417Ac...71c45864d
0 ETH0.00601122.54412118
Repay Loan188751652023-12-27 6:18:11342 days ago1703657891IN
0x0fF417Ac...71c45864d
0 ETH0.0063553523.82327509
Repay Loan188751622023-12-27 6:17:35342 days ago1703657855IN
0x0fF417Ac...71c45864d
0 ETH0.006101222.8470122
View all transactions

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To
184509232023-10-28 19:57:47402 days ago1698523067  Contract Creation0 ETH
Loading...
Loading

Minimal Proxy Contract for 0xe504ca8a954a757f79a3728e7985f144860af41e

Contract Name:
Cooler

Compiler Version
v0.8.15+commit.e14f2714

Optimization Enabled:
Yes with 10 runs

Other Settings:
london EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 7 : Cooler.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {Clone} from "clones/Clone.sol";

import {CoolerFactory} from "./CoolerFactory.sol";
import {CoolerCallback} from "./CoolerCallback.sol";

// Function sig taken from gOHM contract
interface IDelegate { function delegate(address to_) external; }


/// @title  Cooler Loans.
/// @notice A Cooler is a smart contract escrow that facilitates fixed-duration, peer-to-peer
///         loans for a user-defined debt-collateral pair.
/// @dev    This contract uses Clones (https://github.com/wighawag/clones-with-immutable-args)
///         to save gas on deployment.
contract Cooler is Clone {
    using SafeTransferLib for ERC20;

    // --- ERRORS ----------------------------------------------------

    error OnlyApproved();
    error Deactivated();
    error Default();
    error NotExpired();
    error NotCoolerCallback();

    // --- DATA STRUCTURES -------------------------------------------

    /// @notice A loan begins with a borrow request.
    struct Request {
        uint256 amount;             // Amount to be borrowed.
        uint256 interest;           // Annualized percentage to be paid as interest.
        uint256 loanToCollateral;   // Requested loan-to-collateral ratio.
        uint256 duration;           // Time to repay the loan before it defaults.
        bool active;                // Any lender can clear an active loan request.
        address requester;          // The address that created the request.
    }

    /// @notice A request is converted to a loan when a lender clears it.
    struct Loan {
        Request request;        // Loan terms specified in the request.
        uint256 principal;      // Amount of principal debt owed to the lender.
        uint256 interestDue;    // Interest owed to the lender.
        uint256 collateral;     // Amount of collateral pledged.
        uint256 expiry;         // Time when the loan defaults.
        address lender;         // Lender's address.
        address recipient;      // Recipient of repayments.
        bool callback;          // If this is true, the lender must inherit CoolerCallback.
    }

    // --- IMMUTABLES ------------------------------------------------

    // This makes the code look prettier.
    uint256 private constant DECIMALS_INTEREST = 1e18;

    /// @notice This address owns the collateral in escrow.
    function owner() public pure returns (address _owner) {
        return _getArgAddress(0x0);
    }

    /// @notice This token is borrowed against.
    function collateral() public pure returns (ERC20 _collateral) {
        return ERC20(_getArgAddress(0x14));
    }

    /// @notice This token is lent.
    function debt() public pure returns (ERC20 _debt) {
        return ERC20(_getArgAddress(0x28));
    }
    
    /// @notice This contract created the Cooler
    function factory() public pure returns (CoolerFactory _factory) {
        return CoolerFactory(_getArgAddress(0x3c));
    }

    // --- STATE VARIABLES -------------------------------------------

    /// @notice Arrays stores all the loan requests.
    Request[] public requests;

    /// @notice Arrays stores all the granted loans.
    Loan[] public loans;

    /// @notice Facilitates transfer of lender ownership to new addresses
    mapping(uint256 => address) public approvals;

    // --- BORROWER --------------------------------------------------

    /// @notice Request a loan with given parameters.
    ///         Collateral is taken at time of request.
    /// @param  amount_ of debt tokens to borrow.
    /// @param  interest_ to pay (annualized % of 'amount_'). Expressed in DECIMALS_INTEREST.
    /// @param  loanToCollateral_ debt tokens per collateral token pledged. Expressed in 10**collateral().decimals().
    /// @param  duration_ of loan tenure in seconds.
    /// @return reqID of the created request. Equivalent to the index of request in requests[].
    function requestLoan(
        uint256 amount_,
        uint256 interest_,
        uint256 loanToCollateral_,
        uint256 duration_
    ) external returns (uint256 reqID) {
        reqID = requests.length;
        requests.push(
            Request({
                amount: amount_,
                interest: interest_,
                loanToCollateral: loanToCollateral_,
                duration: duration_,
                active: true,
                requester: msg.sender
            })
        );

        // The collateral is taken upfront. Will be escrowed
        // until the loan is repaid or defaulted.
        collateral().safeTransferFrom(
            msg.sender,
            address(this),
            collateralFor(amount_, loanToCollateral_)
        );

        // Log the event.
        factory().logRequestLoan(reqID);
    }

    /// @notice Cancel a loan request and get the collateral back.
    /// @param  reqID_ index of request in requests[].
    function rescindRequest(uint256 reqID_) external {
        if (msg.sender != owner()) revert OnlyApproved();

        Request storage req = requests[reqID_];

        if (!req.active) revert Deactivated();

        // Update storage and send collateral back to the owner.
        req.active = false;
        collateral().safeTransfer(owner(), collateralFor(req.amount, req.loanToCollateral));

        // Log the event.
        factory().logRescindRequest(reqID_);
    }

    /// @notice Repay a loan to get the collateral back.
    /// @dev    Despite a malicious lender could reenter with the callback, the
    ///         usage of `msg.sender` prevents any economical benefit to the
    ///         attacker, since they would be repaying the loan themselves.
    /// @param  loanID_ index of loan in loans[].
    /// @param  repayment_ debt tokens to be repaid.
    /// @return collateral given back to the borrower.
    function repayLoan(uint256 loanID_, uint256 repayment_) external returns (uint256) {
        Loan memory loan = loans[loanID_];

        if (block.timestamp > loan.expiry) revert Default();

        // Cap the repayment to the total debt of the loan
        uint256 totalDebt = loan.principal + loan.interestDue;
        if (repayment_ > totalDebt) repayment_ = totalDebt;

        // Need to repay interest first, then any extra goes to paying down principal.
        uint256 interestPaid;
        uint256 remainder;
        if (repayment_ >= loan.interestDue) {
            remainder = repayment_ - loan.interestDue;
            interestPaid = loan.interestDue;
            loan.interestDue = 0;
        } else {
            loan.interestDue -= repayment_;
            interestPaid = repayment_;
        }

        // We pay back only if user has paid back principal. This can be 0.
        uint256 decollateralized;
        if (remainder > 0) {
            decollateralized = (loan.collateral * remainder) / loan.principal;
            
            loan.principal -= remainder;
            loan.collateral -= decollateralized;
        }

        // Save updated loan info in storage.
        loans[loanID_] = loan;

        // Transfer repaid debt back to the lender and collateral back to the owner if applicable
        debt().safeTransferFrom(msg.sender, loan.recipient, repayment_);
        if (decollateralized > 0) collateral().safeTransfer(owner(), decollateralized);

        // Log the event.
        factory().logRepayLoan(loanID_, repayment_);

        // If necessary, trigger lender callback.
        if (loan.callback) {
            CoolerCallback(loan.lender).onRepay(loanID_, remainder, interestPaid);
        }

        return decollateralized;
    }

    /// @notice Delegate voting power on collateral.
    /// @param  to_ address to delegate.
    function delegateVoting(address to_) external {
        if (msg.sender != owner()) revert OnlyApproved();
        IDelegate(address(collateral())).delegate(to_);
    }

    // --- LENDER ----------------------------------------------------

    /// @notice Fill a requested loan as a lender.
    /// @param  reqID_ index of request in requests[].
    /// @param  recipient_ address to repay the loan to.
    /// @param  isCallback_ true if the lender implements the CoolerCallback abstract. False otherwise.
    /// @return loanID of the granted loan. Equivalent to the index of loan in loans[].
    function clearRequest(
        uint256 reqID_,
        address recipient_,
        bool isCallback_
    ) external returns (uint256 loanID) {
        Request memory req = requests[reqID_];

        // Loan callbacks are only allowed if:
        //  1. The loan request has been created via a trusted lender.
        //  2. The lender signals that it implements the CoolerCallback Abstract.
        bool callback = (isCallback_ && msg.sender == req.requester);

        // If necessary, ensure lender implements the CoolerCallback abstract.
        if (callback && !CoolerCallback(msg.sender).isCoolerCallback()) revert NotCoolerCallback();
        // Ensure loan request is active. 
        if (!req.active) revert Deactivated();

        // Clear the loan request in memory.
        req.active = false;

        // Calculate and store loan terms.
        uint256 interest = interestFor(req.amount, req.interest, req.duration);
        uint256 collat = collateralFor(req.amount, req.loanToCollateral);
        loanID = loans.length;

        loans.push(
            Loan({
                request: req,
                principal: req.amount,
                interestDue: interest,
                collateral: collat,
                expiry: block.timestamp + req.duration,
                lender: msg.sender,
                recipient: recipient_,
                callback: callback
            })
        );

        // Clear the loan request storage.
        requests[reqID_].active = false;

        // Transfer debt tokens to the owner of the request.
        debt().safeTransferFrom(msg.sender, owner(), req.amount);

        // Log the event.
        factory().logClearRequest(reqID_, loanID);
    }

    /// @notice Allow lender to extend a loan for the borrower. Doesn't require
    ///         borrower permission because it doesn't have a negative impact for them.
    /// @dev    Since this function solely impacts the expiration day, the lender
    ///         should ensure that extension interest payments are done beforehand.
    /// @param  loanID_ index of loan in loans[].
    /// @param  times_ that the fixed-term loan duration is extended.
    function extendLoanTerms(uint256 loanID_, uint8 times_) external {
        Loan memory loan = loans[loanID_];

        if (msg.sender != loan.lender) revert OnlyApproved();
        if (block.timestamp > loan.expiry) revert Default();

        // Update loan terms to reflect the extension.
        loan.expiry += loan.request.duration * times_;

        // Save updated loan info in storage.
        loans[loanID_] = loan;

        // Log the event.
        factory().logExtendLoan(loanID_, times_);
    }

    /// @notice Claim collateral upon loan default.
    /// @param  loanID_ index of loan in loans[].
    /// @return defaulted debt by the borrower, collateral kept by the lender, elapsed time since expiry.
    function claimDefaulted(uint256 loanID_) external returns (uint256, uint256, uint256, uint256) {
        Loan memory loan = loans[loanID_];

        if (block.timestamp <= loan.expiry) revert NotExpired();

        loans[loanID_].principal = 0;
        loans[loanID_].interestDue = 0;
        loans[loanID_].collateral = 0;

        // Transfer defaulted collateral to the lender.
        collateral().safeTransfer(loan.lender, loan.collateral);

        // Log the event.
        factory().logDefaultLoan(loanID_, loan.collateral);

        // If necessary, trigger lender callback.
        if (loan.callback) {
            CoolerCallback(loan.lender).onDefault(loanID_, loan.principal, loan.interestDue, loan.collateral);
        }

        return (loan.principal, loan.interestDue, loan.collateral, block.timestamp - loan.expiry);
    }

    /// @notice Approve transfer of loan ownership rights to a new address.
    /// @param  to_ address to be approved.
    /// @param  loanID_ index of loan in loans[].
    function approveTransfer(address to_, uint256 loanID_) external {
        if (msg.sender != loans[loanID_].lender) revert OnlyApproved();

        // Update transfer approvals.
        approvals[loanID_] = to_;
    }

    /// @notice Execute loan ownership transfer. Must be previously approved by the lender.
    /// @param  loanID_ index of loan in loans[].
    function transferOwnership(uint256 loanID_) external {
        if (msg.sender != approvals[loanID_]) revert OnlyApproved();

        // Update the load lender and the recipient.
        loans[loanID_].lender = msg.sender;
        loans[loanID_].recipient = msg.sender;
        // Callbacks are disabled when transferring ownership.
        loans[loanID_].callback = false;
        // Clear transfer approvals.
        approvals[loanID_] = address(0);
    }

    /// @notice Allow lender to set repayment recipient of a given loan.
    /// @param  loanID_ of lender's loan.
    /// @param  recipient_ reciever of repayments
    function setRepaymentAddress(uint256 loanID_, address recipient_) external {
        if (msg.sender != loans[loanID_].lender) revert OnlyApproved();

        // Update the repayment method.
        loans[loanID_].recipient = recipient_;
    }

    // --- AUX FUNCTIONS ---------------------------------------------

    /// @notice Compute collateral needed for a desired loan amount at given loan to collateral ratio.
    /// @param  principal_ amount of debt tokens.
    /// @param  loanToCollateral_ ratio for loan. Expressed in 10**collateral().decimals().
    function collateralFor(uint256 principal_, uint256 loanToCollateral_) public view returns (uint256) {
        return (principal_ * (10 ** collateral().decimals())) / loanToCollateral_;
    }

    /// @notice Compute interest cost on amount for duration at given annualized rate.
    /// @param  principal_ amount of debt tokens.
    /// @param  rate_ of interest (annualized).
    /// @param  duration_ of the loan in seconds.
    /// @return Interest in debt token terms.
    function interestFor(uint256 principal_, uint256 rate_, uint256 duration_) public pure returns (uint256) {
        uint256 interest = (rate_ * duration_) / 365 days;
        return (principal_ * interest) / DECIMALS_INTEREST;
    }

    /// @notice Check if given loan has expired.
    /// @param  loanID_ index of loan in loans[].
    /// @return Expiration status.
    function hasExpired(uint256 loanID_) external view returns (bool) {
        return block.timestamp > loans[loanID_].expiry;
    }

    /// @notice Check if a given request is active.
    /// @param  reqID_ index of request in requests[].
    /// @return Active status.
    function isActive(uint256 reqID_) external view returns (bool) {
        return requests[reqID_].active;
    }

    /// @notice Getter for Request data as a struct.
    /// @param  reqID_ index of request in requests[].
    /// @return Request struct.
    function getRequest(uint256 reqID_) external view returns (Request memory) {
        return requests[reqID_];
    }

    /// @notice Getter for Loan data as a struct.
    /// @param loanID_ index of loan in loans[].
    /// @return Loan struct.
    function getLoan(uint256 loanID_) external view returns (Loan memory) {
        return loans[loanID_];
    }
}

File 2 of 7 : SafeTransferLib.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

File 3 of 7 : ERC20.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

File 4 of 7 : Clone.sol
// SPDX-License-Identifier: BSD
pragma solidity ^0.8.4;

/// @title Clone
/// @author zefram.eth
/// @notice Provides helper functions for reading immutable args from calldata
contract Clone {
    /// @notice Reads an immutable arg with type address
    /// @param argOffset The offset of the arg in the packed data
    /// @return arg The arg value
    function _getArgAddress(uint256 argOffset)
        internal
        pure
        returns (address arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        assembly {
            arg := shr(0x60, calldataload(add(offset, argOffset)))
        }
    }

    /// @notice Reads an immutable arg with type uint256
    /// @param argOffset The offset of the arg in the packed data
    /// @return arg The arg value
    function _getArgUint256(uint256 argOffset)
        internal
        pure
        returns (uint256 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        // solhint-disable-next-line no-inline-assembly
        assembly {
            arg := calldataload(add(offset, argOffset))
        }
    }

    /// @notice Reads an immutable arg with type uint64
    /// @param argOffset The offset of the arg in the packed data
    /// @return arg The arg value
    function _getArgUint64(uint256 argOffset)
        internal
        pure
        returns (uint64 arg)
    {
        uint256 offset = _getImmutableArgsOffset();
        // solhint-disable-next-line no-inline-assembly
        assembly {
            arg := shr(0xc0, calldataload(add(offset, argOffset)))
        }
    }

    /// @notice Reads an immutable arg with type uint8
    /// @param argOffset The offset of the arg in the packed data
    /// @return arg The arg value
    function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) {
        uint256 offset = _getImmutableArgsOffset();
        // solhint-disable-next-line no-inline-assembly
        assembly {
            arg := shr(0xf8, calldataload(add(offset, argOffset)))
        }
    }

    /// @return offset The offset of the packed immutable args in calldata
    function _getImmutableArgsOffset() internal pure returns (uint256 offset) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            offset := sub(
                calldatasize(),
                add(shr(240, calldataload(sub(calldatasize(), 2))), 2)
            )
        }
    }
}

File 5 of 7 : CoolerFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {ERC20} from "solmate/tokens/ERC20.sol";
import {ClonesWithImmutableArgs} from "clones/ClonesWithImmutableArgs.sol";

import {Cooler} from "./Cooler.sol";

/// @title  Cooler Loans Factory.
/// @notice The Cooler Factory creates new Cooler escrow contracts.
/// @dev    This contract uses Clones (https://github.com/wighawag/clones-with-immutable-args)
///         to save gas on deployment.
contract CoolerFactory {
    using ClonesWithImmutableArgs for address;

    // --- ERRORS ----------------------------------------------------

    error NotFromFactory();
    error DecimalsNot18();

    // --- EVENTS ----------------------------------------------------

    /// @notice A global event when a new loan request is created.
    event RequestLoan(address indexed cooler, address collateral, address debt, uint256 reqID);
    /// @notice A global event when a loan request is rescinded.
    event RescindRequest(address indexed cooler, uint256 reqID);
    /// @notice A global event when a loan request is fulfilled.
    event ClearRequest(address indexed cooler, uint256 reqID, uint256 loanID);
    /// @notice A global event when a loan is repaid.
    event RepayLoan(address indexed cooler, uint256 loanID, uint256 amount);
    /// @notice A global event when a loan is extended.
    event ExtendLoan(address indexed cooler, uint256 loanID, uint8 times);
    /// @notice A global event when the collateral of defaulted loan is claimed.
    event DefaultLoan(address indexed cooler, uint256 loanID, uint256 amount);

    // -- STATE VARIABLES --------------------------------------------

    /// @notice Cooler reference implementation (deployed on creation to clone from).
    Cooler public immutable coolerImplementation;

    /// @notice Mapping to validate deployed coolers.
    mapping(address => bool) public created;

    /// @notice Mapping to prevent duplicate coolers.
    mapping(address => mapping(ERC20 => mapping(ERC20 => address))) private coolerFor;

    /// @notice Mapping to query Coolers for Collateral-Debt pair.
    mapping(ERC20 => mapping(ERC20 => address[])) public coolersFor;

    // --- INITIALIZATION --------------------------------------------

    constructor() {
        coolerImplementation = new Cooler();
    }

    // --- DEPLOY NEW COOLERS ----------------------------------------

    /// @notice creates a new Escrow contract for collateral and debt tokens.
    /// @param  collateral_ the token given as collateral.
    /// @param  debt_ the token to be lent. Interest is denominated in debt tokens.
    /// @return cooler address of the contract.
function generateCooler(ERC20 collateral_, ERC20 debt_) external returns (address cooler) {
    // Return address if cooler exists.
    cooler = coolerFor[msg.sender][collateral_][debt_];

    // Otherwise generate new cooler.
    if (cooler == address(0)) {
        if (collateral_.decimals() != 18 || debt_.decimals() != 18) revert DecimalsNot18();
        // Clone the cooler implementation.
        bytes memory coolerData = abi.encodePacked(
            msg.sender,              // owner
            address(collateral_),    // collateral
            address(debt_),          // debt
            address(this)            // factory
        );
        cooler = address(coolerImplementation).clone(coolerData);

        // Update storage accordingly.
        coolerFor[msg.sender][collateral_][debt_] = cooler;
        coolersFor[collateral_][debt_].push(cooler);
        created[cooler] = true;
    }
}

    // --- EMIT EVENTS -----------------------------------------------

    /// @notice Ensure that the called is a Cooler.
    modifier onlyFromFactory {        
        if (!created[msg.sender]) revert NotFromFactory();
        _;
    }

    /// @notice Emit a global event when a new loan request is created.
    function logRequestLoan(uint256 reqID_) external onlyFromFactory {
        emit RequestLoan(msg.sender, address(Cooler(msg.sender).collateral()), address(Cooler(msg.sender).debt()), reqID_);
    }

    /// @notice Emit a global event when a loan request is rescinded.
    function logRescindRequest(uint256 reqID_) external onlyFromFactory {
        emit RescindRequest(msg.sender, reqID_);
    }

    /// @notice Emit a global event when a loan request is fulfilled.
    function logClearRequest(uint256 reqID_, uint256 loanID_) external onlyFromFactory {
        emit ClearRequest(msg.sender, reqID_, loanID_);
    }

    /// @notice Emit a global event when a loan is repaid.
    function logRepayLoan(uint256 loanID_, uint256 repayment_) external onlyFromFactory {
        emit RepayLoan(msg.sender, loanID_, repayment_);
    }

    /// @notice Emit a global event when a loan is extended.
    function logExtendLoan(uint256 loanID_, uint8 times_) external onlyFromFactory {
        emit ExtendLoan(msg.sender, loanID_, times_);
    }

    /// @notice Emit a global event when the collateral of defaulted loan is claimed.
    function logDefaultLoan(uint256 loanID_, uint256 collateral_) external onlyFromFactory {
        emit DefaultLoan(msg.sender, loanID_, collateral_);
    }

    // --- AUX FUNCTIONS ---------------------------------------------

    /// @notice Getter function to get an existing cooler for a given user <> collateral <> debt combination.
    function getCoolerFor(address user_, address collateral_, address debt_) public view returns (address) {
        return coolerFor[user_][ERC20(collateral_)][ERC20(debt_)];
    }
}

File 6 of 7 : CoolerCallback.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {CoolerFactory} from "./CoolerFactory.sol";

/// @notice Allows for debt issuers to execute logic when a loan is repaid, rolled, or defaulted.
/// @dev    The three callback functions must be implemented if `isCoolerCallback()` is set to true.
abstract contract CoolerCallback {
    // --- ERRORS ----------------------------------------------------

    error OnlyFromFactory();

    // --- INITIALIZATION --------------------------------------------

    CoolerFactory public immutable factory;

    constructor(address coolerFactory_) {
        factory = CoolerFactory(coolerFactory_);
    }

    // --- EXTERNAL FUNCTIONS ------------------------------------------------

    /// @notice Informs to Cooler that this contract can handle its callbacks.
    function isCoolerCallback() external pure returns (bool) {
        return true;
    }

    /// @notice Callback function that handles repayments.
    function onRepay(uint256 loanID_, uint256 principlePaid_, uint256 interestPaid_) external {
        if (!factory.created(msg.sender)) revert OnlyFromFactory();
        _onRepay(loanID_, principlePaid_, interestPaid_);
    }

    /// @notice Callback function that handles defaults.
    function onDefault(
        uint256 loanID_,
        uint256 principle,
        uint256 interest,
        uint256 collateral
    ) external {
        if (!factory.created(msg.sender)) revert OnlyFromFactory();
        _onDefault(loanID_, principle, interest, collateral);
    }

    // --- INTERNAL FUNCTIONS ------------------------------------------------

    /// @notice Callback function that handles repayments. Override for custom logic.
    function _onRepay(
        uint256 loanID_,
        uint256 principlePaid_,
        uint256 interestPaid_
    ) internal virtual;

    /// @notice Callback function that handles defaults.
    function _onDefault(
        uint256 loanID_,
        uint256 principle_,
        uint256 interestDue_,
        uint256 collateral
    ) internal virtual;
}

File 7 of 7 : ClonesWithImmutableArgs.sol
// SPDX-License-Identifier: BSD

pragma solidity ^0.8.4;

/// @title ClonesWithImmutableArgs
/// @author wighawag, zefram.eth
/// @notice Enables creating clone contracts with immutable args
library ClonesWithImmutableArgs {
    error CreateFail();

    /// @notice Creates a clone proxy of the implementation contract, with immutable args
    /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
    /// @param implementation The implementation contract to clone
    /// @param data Encoded immutable args
    /// @return instance The address of the created clone
    function clone(address implementation, bytes memory data)
        internal
        returns (address instance)
    {
        // unrealistic for memory ptr or data length to exceed 256 bits
        unchecked {
            uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call
            uint256 creationSize = 0x43 + extraLength;
            uint256 runSize = creationSize - 11;
            uint256 dataPtr;
            uint256 ptr;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                ptr := mload(0x40)

                // -------------------------------------------------------------------------------------------------------------
                // CREATION (11 bytes)
                // -------------------------------------------------------------------------------------------------------------

                // 3d          | RETURNDATASIZE        | 0                       | –
                // 61 runtime  | PUSH2 runtime (r)     | r 0                     | –
                mstore(
                    ptr,
                    0x3d61000000000000000000000000000000000000000000000000000000000000
                )
                mstore(add(ptr, 0x02), shl(240, runSize)) // size of the contract running bytecode (16 bits)

                // creation size = 0b
                // 80          | DUP1                  | r r 0                   | –
                // 60 creation | PUSH1 creation (c)    | c r r 0                 | –
                // 3d          | RETURNDATASIZE        | 0 c r r 0               | –
                // 39          | CODECOPY              | r 0                     | [0-2d]: runtime code
                // 81          | DUP2                  | 0 c  0                  | [0-2d]: runtime code
                // f3          | RETURN                | 0                       | [0-2d]: runtime code
                mstore(
                    add(ptr, 0x04),
                    0x80600b3d3981f300000000000000000000000000000000000000000000000000
                )

                // -------------------------------------------------------------------------------------------------------------
                // RUNTIME
                // -------------------------------------------------------------------------------------------------------------

                // 36          | CALLDATASIZE          | cds                     | –
                // 3d          | RETURNDATASIZE        | 0 cds                   | –
                // 3d          | RETURNDATASIZE        | 0 0 cds                 | –
                // 37          | CALLDATACOPY          | –                       | [0, cds] = calldata
                // 61          | PUSH2 extra           | extra                   | [0, cds] = calldata
                mstore(
                    add(ptr, 0x0b),
                    0x363d3d3761000000000000000000000000000000000000000000000000000000
                )
                mstore(add(ptr, 0x10), shl(240, extraLength))

                // 60 0x38     | PUSH1 0x38            | 0x38 extra              | [0, cds] = calldata // 0x38 (56) is runtime size - data
                // 36          | CALLDATASIZE          | cds 0x38 extra          | [0, cds] = calldata
                // 39          | CODECOPY              | _                       | [0, cds] = calldata
                // 3d          | RETURNDATASIZE        | 0                       | [0, cds] = calldata
                // 3d          | RETURNDATASIZE        | 0 0                     | [0, cds] = calldata
                // 3d          | RETURNDATASIZE        | 0 0 0                   | [0, cds] = calldata
                // 36          | CALLDATASIZE          | cds 0 0 0               | [0, cds] = calldata
                // 61 extra    | PUSH2 extra           | extra cds 0 0 0         | [0, cds] = calldata
                mstore(
                    add(ptr, 0x12),
                    0x603836393d3d3d36610000000000000000000000000000000000000000000000
                )
                mstore(add(ptr, 0x1b), shl(240, extraLength))

                // 01          | ADD                   | cds+extra 0 0 0         | [0, cds] = calldata
                // 3d          | RETURNDATASIZE        | 0 cds 0 0 0             | [0, cds] = calldata
                // 73 addr     | PUSH20 0x123…         | addr 0 cds 0 0 0        | [0, cds] = calldata
                mstore(
                    add(ptr, 0x1d),
                    0x013d730000000000000000000000000000000000000000000000000000000000
                )
                mstore(add(ptr, 0x20), shl(0x60, implementation))

                // 5a          | GAS                   | gas addr 0 cds 0 0 0    | [0, cds] = calldata
                // f4          | DELEGATECALL          | success 0               | [0, cds] = calldata
                // 3d          | RETURNDATASIZE        | rds success 0           | [0, cds] = calldata
                // 82          | DUP3                  | 0 rds success 0         | [0, cds] = calldata
                // 80          | DUP1                  | 0 0 rds success 0       | [0, cds] = calldata
                // 3e          | RETURNDATACOPY        | success 0               | [0, rds] = return data (there might be some irrelevant leftovers in memory [rds, cds] when rds < cds)
                // 90          | SWAP1                 | 0 success               | [0, rds] = return data
                // 3d          | RETURNDATASIZE        | rds 0 success           | [0, rds] = return data
                // 91          | SWAP2                 | success 0 rds           | [0, rds] = return data
                // 60 0x36     | PUSH1 0x36            | 0x36 sucess 0 rds       | [0, rds] = return data
                // 57          | JUMPI                 | 0 rds                   | [0, rds] = return data
                // fd          | REVERT                | –                       | [0, rds] = return data
                // 5b          | JUMPDEST              | 0 rds                   | [0, rds] = return data
                // f3          | RETURN                | –                       | [0, rds] = return data

                mstore(
                    add(ptr, 0x34),
                    0x5af43d82803e903d91603657fd5bf30000000000000000000000000000000000
                )
            }

            // -------------------------------------------------------------------------------------------------------------
            // APPENDED DATA (Accessible from extcodecopy)
            // (but also send as appended data to the delegatecall)
            // -------------------------------------------------------------------------------------------------------------

            extraLength -= 2;
            uint256 counter = extraLength;
            uint256 copyPtr = ptr + 0x43;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                dataPtr := add(data, 32)
            }
            for (; counter >= 32; counter -= 32) {
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    mstore(copyPtr, mload(dataPtr))
                }

                copyPtr += 32;
                dataPtr += 32;
            }
            uint256 mask = ~(256**(32 - counter) - 1);
            // solhint-disable-next-line no-inline-assembly
            assembly {
                mstore(copyPtr, and(mload(dataPtr), mask))
            }
            copyPtr += counter;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                mstore(copyPtr, shl(240, extraLength))
            }
            // solhint-disable-next-line no-inline-assembly
            assembly {
                instance := create(0, ptr, creationSize)
            }
            if (instance == address(0)) {
                revert CreateFail();
            }
        }
    }
}

Settings
{
  "remappings": [
    "ds-test/=lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "interfaces/=src/interfaces/",
    "modules/=src/modules/",
    "policies/=src/policies/",
    "libraries/=src/libraries/",
    "solmate/=lib/solmate/src/",
    "layer-zero/=lib/solidity-examples/contracts/",
    "bonds/=lib/bonds/src/",
    "test/=src/test/",
    "clones/=lib/clones-with-immutable-args/src/",
    "cooler/=lib/Cooler/src/",
    "balancer-v2/=lib/balancer-v2/",
    "@openzeppelin/=lib/openzeppelin-contracts/",
    "Cooler/=lib/Cooler/src/",
    "clones-with-immutable-args/=lib/clones-with-immutable-args/src/",
    "olympus-v3/=lib/Cooler/lib/olympus-v3/src/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/",
    "solidity-examples/=lib/solidity-examples/contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 10
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "libraries": {}
}

Contract ABI

[{"inputs":[],"name":"Deactivated","type":"error"},{"inputs":[],"name":"Default","type":"error"},{"inputs":[],"name":"NotCoolerCallback","type":"error"},{"inputs":[],"name":"NotExpired","type":"error"},{"inputs":[],"name":"OnlyApproved","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"approvals","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to_","type":"address"},{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"approveTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"claimDefaulted","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"reqID_","type":"uint256"},{"internalType":"address","name":"recipient_","type":"address"},{"internalType":"bool","name":"isCallback_","type":"bool"}],"name":"clearRequest","outputs":[{"internalType":"uint256","name":"loanID","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collateral","outputs":[{"internalType":"contract ERC20","name":"_collateral","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"principal_","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral_","type":"uint256"}],"name":"collateralFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"debt","outputs":[{"internalType":"contract ERC20","name":"_debt","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"to_","type":"address"}],"name":"delegateVoting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"},{"internalType":"uint8","name":"times_","type":"uint8"}],"name":"extendLoanTerms","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract CoolerFactory","name":"_factory","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"getLoan","outputs":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interest","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"address","name":"requester","type":"address"}],"internalType":"struct Cooler.Request","name":"request","type":"tuple"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"interestDue","type":"uint256"},{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address","name":"lender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"callback","type":"bool"}],"internalType":"struct Cooler.Loan","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"reqID_","type":"uint256"}],"name":"getRequest","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interest","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"address","name":"requester","type":"address"}],"internalType":"struct Cooler.Request","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"hasExpired","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"principal_","type":"uint256"},{"internalType":"uint256","name":"rate_","type":"uint256"},{"internalType":"uint256","name":"duration_","type":"uint256"}],"name":"interestFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"reqID_","type":"uint256"}],"name":"isActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"loans","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interest","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"address","name":"requester","type":"address"}],"internalType":"struct Cooler.Request","name":"request","type":"tuple"},{"internalType":"uint256","name":"principal","type":"uint256"},{"internalType":"uint256","name":"interestDue","type":"uint256"},{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"address","name":"lender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"callback","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"},{"internalType":"uint256","name":"repayment_","type":"uint256"}],"name":"repayLoan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"uint256","name":"interest_","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral_","type":"uint256"},{"internalType":"uint256","name":"duration_","type":"uint256"}],"name":"requestLoan","outputs":[{"internalType":"uint256","name":"reqID","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"requests","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"interest","type":"uint256"},{"internalType":"uint256","name":"loanToCollateral","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"address","name":"requester","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"reqID_","type":"uint256"}],"name":"rescindRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"},{"internalType":"address","name":"recipient_","type":"address"}],"name":"setRepaymentAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanID_","type":"uint256"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.