Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
"
},
"contracts/refinancing/refinancingAdapters/gondi/IGondi.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
/// @title Multi Source Loan Interface
/// @author Florida St
/// @notice A multi source loan is one with multiple tranches.
interface IGondi {
/// @notice Borrowers receive offers that are then validated.
/// @dev Setting the nftCollateralTokenId to 0 triggers validation through `validators`.
/// @param offerId Offer ID. Used for canceling/setting as executed.
/// @param lender Lender of the offer.
/// @param fee Origination fee.
/// @param capacity Capacity of the offer.
/// @param nftCollateralAddress Address of the NFT collateral.
/// @param nftCollateralTokenId NFT collateral token ID.
/// @param principalAddress Address of the principal.
/// @param principalAmount Principal amount of the loan.
/// @param aprBps APR in BPS.
/// @param expirationTime Expiration time of the offer.
/// @param duration Duration of the loan in seconds.
/// @param maxSeniorRepayment Max amount of senior capital ahead (principal + interest).
/// @param validators Arbitrary contract to validate offers implementing `IBaseOfferValidator`.
struct LoanOffer {
uint256 offerId;
address lender;
uint256 fee;
uint256 capacity;
address nftCollateralAddress;
uint256 nftCollateralTokenId;
address principalAddress;
uint256 principalAmount;
uint256 aprBps;
uint256 expirationTime;
uint256 duration;
uint256 maxSeniorRepayment;
OfferValidator[] validators;
}
/// @notice Arbitrary contract to validate offers implementing `IBaseOfferValidator`.
/// @param validator Address of the validator contract.
/// @param arguments Arguments to pass to the validator.
struct OfferValidator {
address validator;
bytes arguments;
}
/// @notice Offer + how much will be filled (always <= principalAmount).
/// @param offer Offer.
/// @param amount Amount to be filled.
struct OfferExecution {
LoanOffer offer;
uint256 amount;
bytes lenderOfferSignature;
}
/// @notice Offer + necessary fields to execute a specific loan. This has a separate expirationTime to avoid
/// someone holding an offer and executing much later, without the borrower's awareness.
/// @dev It's advised that borrowers only set an expirationTime close to the actual time they will execute the loan
/// to avoid replays.
/// @param offerExecution List of offers to be filled and amount for each.
/// @param tokenId NFT collateral token ID.
/// @param amount The amount the borrower is willing to take (must be <= _loanOffer principalAmount)
/// @param expirationTime Expiration time of the signed offer by the borrower.
/// @param callbackData Data to pass to the callback.
struct ExecutionData {
OfferExecution[] offerExecution;
uint256 tokenId;
uint256 duration;
uint256 expirationTime;
address principalReceiver;
bytes callbackData;
}
/// @param executionData Execution data.
/// @param borrower Address that owns the NFT and will take over the loan.
/// @param borrowerOfferSignature Signature of the offer (signed by borrower).
/// @param callbackData Whether to call the afterPrincipalTransfer callback
struct LoanExecutionData {
ExecutionData executionData;
address borrower;
bytes borrowerOfferSignature;
}
/// @param loanId Loan ID.
/// @param callbackData Whether to call the afterNFTTransfer callback
/// @param shouldDelegate Whether to delegate ownership of the NFT (avoid seaport flags).
struct SignableRepaymentData {
uint256 loanId;
bytes callbackData;
bool shouldDelegate;
}
/// @param loan Loan.
/// @param borrowerLoanSignature Signature of the loan (signed by borrower).
struct LoanRepaymentData {
SignableRepaymentData data;
Loan loan;
bytes borrowerSignature;
}
/// @notice Tranches have different seniority levels.
/// @param loanId Loan ID.
/// @param floor Amount of principal more senior to this tranche.
/// @param principalAmount Total principal in this tranche.
/// @param lender Lender for this given tranche.
/// @param accruedInterest Accrued Interest.
/// @param startTime Start Time. Either the time at which the loan initiated / was refinanced.
/// @param aprBps APR in basis points.
struct Tranche {
uint256 loanId;
uint256 floor;
uint256 principalAmount;
address lender;
uint256 accruedInterest;
uint256 startTime;
uint256 aprBps;
}
/// @dev Principal Amount is equal to the sum of all tranches principalAmount.
/// We keep it for caching purposes. Since we are not saving this on chain but the hash,
/// it does not have a huge impact on gas.
/// @param borrower Borrower.
/// @param nftCollateralTokenId NFT Collateral Token ID.
/// @param nftCollateralAddress NFT Collateral Address.
/// @param principalAddress Principal Address.
/// @param principalAmount Principal Amount.
/// @param startTime Start Time.
/// @param duration Duration.
/// @param tranche Tranches.
/// @param protocolFee Protocol Fee.
struct Loan {
address borrower;
uint256 nftCollateralTokenId;
address nftCollateralAddress;
address principalAddress;
uint256 principalAmount;
uint256 startTime;
uint256 duration;
Tranche[] tranche;
uint256 protocolFee;
}
/// @notice Recipient address and fraction of gains charged by the protocol.
struct ProtocolFee {
address recipient;
uint256 fraction;
}
function updateProtocolFee(ProtocolFee calldata _newProtocolFee) external;
function getProtocolFee() external view returns (ProtocolFee memory);
function owner() external view returns (address);
function setProtocolFee() external;
// solhint-disable-next-line func-name-mixedcase
function VERSION() external view returns (bytes calldata);
function name() external view returns (string calldata);
/// @notice Call by the borrower when emiting a new loan.
/// @param _loanExecutionData Loan execution data.
/// @return loanId Loan ID.
/// @return loan Loan.
function emitLoan(LoanExecutionData calldata _loanExecutionData) external returns (uint256, Loan memory);
/// @notice Repay loan. Interest is calculated pro-rata based on time. Lender is defined by nft ownership.
/// @param _repaymentData Repayment data.
function repayLoan(LoanRepaymentData calldata _repaymentData) external;
event LoanEmitted(uint256 loanId, uint256[] offerId, Loan loan, uint256 fee);
error CancelledOrExecutedOfferError(address _lender, uint256 _offerId);
error ExpiredOfferError(uint256 _expirationTime);
error LowOfferIdError(address _lender, uint256 _newMinOfferId, uint256 _minOfferId);
error LowRenegotiationOfferIdError(address _lender, uint256 _newMinRenegotiationOfferId, uint256 _minOfferId);
error ZeroInterestError();
error InvalidSignatureError();
error CurrencyNotWhitelistedError();
error CollectionNotWhitelistedError();
error MaxCapacityExceededError();
error InvalidLoanError(uint256 _loanId);
error NotStrictlyImprovedError();
error InvalidAmountError(uint256 _amount, uint256 _principalAmount);
error InvalidParametersError();
error MismatchError();
error InvalidCollateralIdError();
error InvalidMethodError();
error InvalidAddressesError();
error InvalidCallerError();
error InvalidTrancheError();
error InvalidRenegotiationOfferError();
error TooManyTranchesError();
error LoanExpiredError();
error NFTNotReturnedError();
error TrancheCannotBeRefinancedError(uint256 minTimestamp);
error LoanLockedError();
error AddressAlreadyAddedError(address _address);
error AddressNotAddedError(address _address);
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
error InvalidCallbackError();
error MulticallFailed();
error InvalidOwnerError();
error AddressZeroError();
error LiquidatorOnlyError(address _liquidator);
error LoanNotDueError(uint256 _expirationTime);
error InvalidDurationError();
error StringsInsufficientHexLength(uint256 value, uint256 length);
}
"
},
"contracts/refinancing/refinancingAdapters/GondiRefinancingAdapter.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {IRefinancingAdapter} from "./IRefinancingAdapter.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IGondi} from "./gondi/IGondi.sol";
/**
* @title GondiRefinancingAdapter
* @author NFTfi
* @dev This contract is an implementation of the IRefinancingAdapter for the Gondi platform.
* It handles operations related to refinancing Gondi loans such as transferring the borrower role,
* paying off loans, and retrieving loan and collateral details.
*/
contract GondiRefinancingAdapter is IRefinancingAdapter {
/// @notice Precision used for calculating interests.
uint256 internal constant PRECISION = 10000;
uint256 private constant SECONDS_PER_YEAR = 31536000;
uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
error transferBorrowerRoleFailed();
error LoanIdMismatch();
/**
* @dev Gets the address of the borrower for a specific Gondi loan.
* @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
* This data must be retrieved from events as Gondi does not provide a direct query method.
* @return address of the borrower from the encoded Lien struct.
*/
function getBorrowerAddress(address, uint256, bytes calldata _extraData) external pure override returns (address) {
IGondi.LoanRepaymentData memory loanRepaymentData = _decodeExtraData(_extraData);
return loanRepaymentData.loan.borrower;
}
/**
* @dev Handles the borrower role transfer for Gondi loans.
* @notice For Gondi, no borrower role transfer is possible, unused function
* @return Always returns true as no additional action is required.
*/
function transferBorrowerRole(address, uint256, bytes calldata) external pure override returns (bool) {
return true;
}
/**
* @dev Pays off a Gondi loan as part of the refinancing process.
* @param _loanContract The address of the Gondi contract.
* @param _payBackAmount The amount of WETH tokens used to pay back the Gondi loan.
* @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
* @return A boolean value indicating whether the operation was successful.
* @notice This function calls Gondi's repay function with the loan repayment details.
*/
function payOffRefinancable(
address _loanContract,
uint256 _loanId,
address _payBackToken,
uint256 _payBackAmount,
bytes calldata _extraData
) external override returns (bool) {
IGondi.LoanRepaymentData memory loanRepaymentData = _decodeExtraData(_extraData);
if (loanRepaymentData.loan.tranche[0].loanId != _loanId) {
revert LoanIdMismatch();
}
IERC20(_payBackToken).transfer(loanRepaymentData.loan.borrower, _payBackAmount);
IGondi(_loanContract).repayLoan(loanRepaymentData);
return true;
}
/**
* @dev Gets the NFT collateral information for a specific Gondi loan.
* @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
* @return nftCollateralContract The address of the NFT collection contract.
* @return nftCollateralId The token ID of the NFT used as collateral.
*/
function getCollateral(
address,
uint256,
bytes calldata _extraData
) external pure override returns (address, uint256) {
IGondi.Loan memory loan = _decodeExtraData(_extraData).loan;
return (loan.nftCollateralAddress, loan.nftCollateralTokenId);
}
/**
* @dev Gets the payoff details for a specific Gondi loan.
* @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
* @return loanERC20Denomination The address of the payoff token (always WETH for Gondi).
* @return maximumRepaymentAmount The total amount required to pay off the loan, calculated based
* on the principal, interest rate, and time elapsed since loan creation.
* @notice This function calculates the current debt including accrued interest at the moment of calling.
* We are using the exact logic from Gondi's computeCurrentDebt function
*/
function getPayoffDetails(
address,
uint256,
bytes calldata _extraData
) external view override returns (address, uint256) {
IGondi.Loan memory loan = _decodeExtraData(_extraData).loan;
address payoffToken = loan.principalAddress;
uint256 payOffAmount = _calculateRepayment(loan);
return (payoffToken, payOffAmount);
}
/**
* @dev Helper function to decode the Lien struct from the encoded extraData.
* @param _extraData The ABI-encoded LoanRepaymentData struct containing loan details from Gondi
* @return loanRepaymentData The decoded Lien struct containing all loan details.
*/
function _decodeExtraData(
bytes calldata _extraData
) internal pure returns (IGondi.LoanRepaymentData memory loanRepaymentData) {
loanRepaymentData = abi.decode(_extraData, (IGondi.LoanRepaymentData));
}
function _calculateRepayment(IGondi.Loan memory loan) private view returns (uint256) {
uint256 totalRepayment = 0;
uint256 totalTranches = loan.tranche.length;
for (uint256 i; i < totalTranches; ) {
IGondi.Tranche memory tranche = loan.tranche[i];
uint256 newInterest = _getInterest(
tranche.principalAmount,
tranche.aprBps,
block.timestamp - tranche.startTime
);
uint256 repayment = tranche.principalAmount + tranche.accruedInterest + newInterest;
unchecked {
totalRepayment += repayment;
}
unchecked {
++i;
}
}
return totalRepayment;
}
function _getInterest(uint256 _amount, uint256 _aprBps, uint256 _duration) private pure returns (uint256) {
uint256 interest = mulDivUp(_amount, _aprBps * _duration, PRECISION * SECONDS_PER_YEAR);
return interest;
}
function mulDivUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 z) {
// solhint-disable-next-line no-inline-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
}
"
},
"contracts/refinancing/refinancingAdapters/IRefinancingAdapter.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
/**
* @title IRefinancingAdapter
* @author NFTfi
*
* @dev This is the interface for Refinancing Adapters. It provides several methods for managing and retrieving
* information about contracts that are eligible for refinancing.
*
* Adapters should implement this interface
*/
interface IRefinancingAdapter {
/**
* @dev Returns the borrower's address for a specific refinancable
*
* @param _refinanceableContract Address of the contract containing the refinanceable
* @param _refinancableIdentifier Unique identifier for the refinanceable.
*
* @return Address of the borrower.
*/
function getBorrowerAddress(
address _refinanceableContract,
uint256 _refinancableIdentifier,
bytes memory _extraData
) external returns (address);
/**
* @dev Transfers the role of borrower to refinancing contract for a specific refinanceable.
*
* @param _refinanceableContract Address of the contract containing the refinanceable
* @param _refinancableIdentifier Unique identifier for the loan.
*
* @return True if the operation was successful.
*/
function transferBorrowerRole(
address _refinanceableContract,
uint256 _refinancableIdentifier,
bytes memory _extraData
) external returns (bool);
/**
* @dev Pays off a refinanceable with a specified amount of a specified token.
*
* @param _refinanceableContract Address of the contract containing the refinanceable
* @param _refinancableIdentifier Unique identifier for the refinanceable.
* @param _payBackToken Token used to pay back the refinanceable.
* @param _payBackAmount Amount of tokens used to pay back the refinanceable.
*
* @return True if the operation was successful.
*/
function payOffRefinancable(
address _refinanceableContract,
uint256 _refinancableIdentifier,
address _payBackToken,
uint256 _payBackAmount,
bytes memory _extraData
) external returns (bool);
/**
* @dev Returns the collateral information for a specific refinancable.
*
* @param _refinanceableContract Address of the contract containing the refinanceable
* @param _refinancableIdentifier Unique identifier for the refinanceable.
*
* @return The address of the collateral token and the amount of collateral.
*/
function getCollateral(
address _refinanceableContract,
uint256 _refinancableIdentifier,
bytes memory _extraData
) external view returns (address, uint256);
/**
* @dev Returns the payoff details for a specific refinancable.
*
* @param _refinanceableContract Address of the contract containing the refinanceable
* @param _refinancableIdentifier Unique identifier for the loan.
*
* @return The address of the payoff token and the required payoff amount.
*/
function getPayoffDetails(
address _refinanceableContract,
uint256 _refinancableIdentifier,
bytes memory _extraData
) external view returns (address, uint256);
}
"
}
},
"settings": {
"metadata": {
"bytecodeHash": "none",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 900
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-10-03 15:03:11
Comments
Log in to comment.
No comments yet.