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": {
"src/modules/payment-module/PaymentModule.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.26;
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { Lockup } from "@sablier/lockup/src/types/DataTypes.sol";
import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol";
import { UD60x18 } from "@prb/math/src/ud60x18/ValueType.sol";
import { StreamManager } from "./sablier-lockup/StreamManager.sol";
import { Types } from "./libraries/Types.sol";
import { Errors } from "./libraries/Errors.sol";
import { IPaymentModule } from "./interfaces/IPaymentModule.sol";
import { Helpers } from "./libraries/Helpers.sol";
/// @title PaymentModule
/// @notice See the documentation in {IPaymentModule}
contract PaymentModule is IPaymentModule, StreamManager, UUPSUpgradeable {
using SafeERC20 for IERC20;
using Strings for uint256;
/// @dev Version identifier for the current implementation of the contract
string public constant VERSION = "1.0.0";
/// @dev The address of the native token (ETH) following the ERC-7528 standard
address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/*//////////////////////////////////////////////////////////////////////////
NAMESPACED STORAGE LAYOUT
//////////////////////////////////////////////////////////////////////////*/
/// @custom:storage-location erc7201:werk.storage.PaymentModule
struct PaymentModuleStorage {
/// @notice Payment requests details mapped by the `id` payment request ID
mapping(uint256 id => Types.PaymentRequest) requests;
/// @notice Counter to keep track of the next ID used to create a new payment request
uint256 nextRequestId;
}
// keccak256(abi.encode(uint256(keccak256("werk.storage.PaymentModule")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PAYMENT_MODULE_STORAGE_LOCATION =
0x69242e762af97d314866e2398c5d39d67197520146b0e3b1471c97ebda768e00;
/// @dev Retrieves the storage of the {PaymentModule} contract
function _getPaymentModuleStorage() internal pure returns (PaymentModuleStorage storage $) {
assembly {
$.slot := PAYMENT_MODULE_STORAGE_LOCATION
}
}
/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/
/// @dev Deploys and locks the implementation contract
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() StreamManager() {
_disableInitializers();
}
/// @dev Initializes the proxy and the {Ownable} contract
function initialize(
ISablierLockup _sablierLinear,
address _initialOwner,
address _brokerAccount,
UD60x18 _brokerFee
)
public
initializer
{
__StreamManager_init(_sablierLinear, _initialOwner, _brokerAccount, _brokerFee);
__UUPSUpgradeable_init();
// Retrieve the contract storage
PaymentModuleStorage storage $ = _getPaymentModuleStorage();
// Start the first payment request ID from 1
$.nextRequestId = 1;
}
/// @dev Allows only the owner to upgrade the contract
function _authorizeUpgrade(address newImplementation) internal override onlyOwner { }
/*//////////////////////////////////////////////////////////////////////////
CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc IPaymentModule
function getRequest(uint256 requestId) external view returns (Types.PaymentRequest memory request) {
// Retrieve the contract storage
PaymentModuleStorage storage $ = _getPaymentModuleStorage();
return $.requests[requestId];
}
/// @inheritdoc IPaymentModule
function statusOf(uint256 requestId) public view returns (Types.Status status) {
status = _statusOf(requestId);
}
/*//////////////////////////////////////////////////////////////////////////
NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc IPaymentModule
function createRequest(Types.PaymentRequest calldata request) public returns (uint256 requestId) {
// Checks: the recipient address is not the zero address
if (request.recipient == address(0)) {
revert Errors.InvalidZeroAddressRecipient();
}
// Checks: the amount is non-zero
if (request.config.amount == 0) {
revert Errors.ZeroPaymentAmount();
}
// Checks: the start time is stricly lower than the end time
if (request.startTime > request.endTime) {
revert Errors.StartTimeGreaterThanEndTime();
}
// Checks: end time is not in the past
uint40 currentTime = uint40(block.timestamp);
if (currentTime >= request.endTime) {
revert Errors.EndTimeInThePast();
}
// Checks: the recurrence type is not equal to one-off if dealing with a tranched stream-based request
if (request.config.method == Types.Method.TranchedStream) {
// The recurrence cannot be set to one-off
if (request.config.recurrence == Types.Recurrence.OneOff) {
revert Errors.TranchedStreamInvalidOneOffRecurence();
}
}
// Checks: the payment method is transfer-based if the recurrence type is `Custom`
if (request.config.recurrence == Types.Recurrence.Custom) {
if (request.config.method != Types.Method.Transfer) {
revert Errors.OnlyTransferAllowedForCustomRecurrence();
}
}
// Checks: the asset is not the native token if dealing with either a linear or tranched stream-based payment
if (request.config.method != Types.Method.Transfer) {
if (request.config.asset == NATIVE_TOKEN) {
revert Errors.OnlyERC20StreamsAllowed();
}
}
// Effects: set the number of payments based on the recurrence type
//
// Notes:
// - For `Custom` recurrence, the number of payments is set to the one provided in the request config `paymentsLeft` input
// - The number of payments is validated only for requests with a payment method set to Tranched Stream or recurring Transfer
// - There should be only one payment when dealing with a one-off transfer-based request
// - When dealing with a recurring transfer, the number of payments must be calculated based
// on the payment interval (endTime - startTime) and recurrence type
uint40 numberOfPayments;
if (request.config.recurrence == Types.Recurrence.Custom) {
numberOfPayments = request.config.paymentsLeft;
} else {
numberOfPayments = 1;
// Validates the payment request interval (endTime - startTime) and returns the number of payments
// based on the payment method, interval and recurrence type only if the recurrence is not `Custom`
if (
request.config.method != Types.Method.LinearStream
&& request.config.recurrence != Types.Recurrence.OneOff
) {
numberOfPayments = _checkIntervalPayments({
recurrence: request.config.recurrence,
startTime: request.startTime,
endTime: request.endTime
});
}
// Set the number of payments back to one if dealing with a tranched-based request
// The `_checkIntervalPayment` method is still called for a tranched-based request just
// to validate the interval and ensure it can support multiple payments based on the chosen recurrence
if (request.config.method == Types.Method.TranchedStream) numberOfPayments = 1;
}
// Retrieve the contract storage
PaymentModuleStorage storage $ = _getPaymentModuleStorage();
// Get the next payment request ID
requestId = $.nextRequestId;
// Effects: create the payment request
$.requests[requestId] = Types.PaymentRequest({
wasCanceled: false,
wasAccepted: false,
startTime: request.startTime,
endTime: request.endTime,
recipient: request.recipient,
config: Types.Config({
canExpire: request.config.canExpire,
recurrence: request.config.recurrence,
method: request.config.method,
paymentsLeft: numberOfPayments,
amount: request.config.amount,
asset: request.config.asset,
streamId: 0
}),
sender: request.sender
});
// Effects: increment the next payment request ID
// Use unchecked because the request id cannot realistically overflow
unchecked {
++$.nextRequestId;
}
// Log the payment request creation
emit RequestCreated({
requestId: requestId,
sender: request.sender,
recipient: request.recipient,
startTime: request.startTime,
endTime: request.endTime,
config: request.config
});
}
/// @inheritdoc IPaymentModule
function payRequest(uint256 requestId) external payable {
// Retrieve the contract storage
PaymentModuleStorage storage $ = _getPaymentModuleStorage();
// Load the payment request state from storage
Types.PaymentRequest storage request = $.requests[requestId];
// Checks: the payment request is not null
if (request.recipient == address(0)) {
revert Errors.NullRequest();
}
// Retrieve the request status
Types.Status requestStatus = _statusOf(requestId);
// Checks: the payment request is not expired
if (requestStatus == Types.Status.Expired) {
revert Errors.RequestExpired();
}
// Checks: the payment request is not already paid or canceled
// Note: for stream-based requests the `status` changes to `Paid` only after the funds are fully streamed
if (
requestStatus == Types.Status.Paid
|| (requestStatus == Types.Status.Ongoing && request.config.streamId != 0)
) {
revert Errors.RequestPaid();
} else if (requestStatus == Types.Status.Canceled) {
revert Errors.RequestCanceled();
}
// Checks: `request.sender` is uninitialized, otherwise set it to `msg.sender`
// Note: may already be set if the payer was specified when the request was created
if (request.sender == address(0)) {
request.sender = msg.sender;
}
// Effects: decrease the number of payments left
// Using unchecked because the number of payments left cannot underflow:
// - For transfer-based requests, the status will be updated to `Paid` when `paymentsLeft` reaches zero;
// - For stream-based requests, `paymentsLeft` is validated before decrementing;
uint40 paymentsLeft;
unchecked {
paymentsLeft = request.config.paymentsLeft - 1;
$.requests[requestId].config.paymentsLeft = paymentsLeft;
}
// Effects: mark the payment request as accepted
$.requests[requestId].wasAccepted = true;
// Handle the payment workflow depending on the payment method type
if (request.config.method == Types.Method.Transfer) {
// Effects: pay the request and update its status to `Paid` or `Ongoing` depending on the payment type
_payByTransfer(request);
} else {
uint256 streamId;
// Check to see whether the request must be paid through a linear or tranched stream
if (request.config.method == Types.Method.LinearStream) {
streamId = _payByLinearStream(request);
} else {
streamId = _payByTranchedStream(request);
}
// Effects: set the stream ID of the payment request
$.requests[requestId].config.streamId = streamId;
}
// Log the payment transaction
emit RequestPaid({ requestId: requestId, payer: msg.sender, config: $.requests[requestId].config });
}
/// @inheritdoc IPaymentModule
function cancelRequest(uint256 requestId) external {
// Retrieve the contract storage
PaymentModuleStorage storage $ = _getPaymentModuleStorage();
// Load the payment request state from storage
Types.PaymentRequest memory request = $.requests[requestId];
// Checks: the payment request exists
if (request.recipient == address(0)) {
revert Errors.NullRequest();
}
// Retrieve the request status
Types.Status requestStatus = _statusOf(requestId);
// Checks: the payment request is already paid or canceled
if (requestStatus == Types.Status.Paid) {
revert Errors.RequestPaid();
} else if (requestStatus == Types.Status.Canceled) {
revert Errors.RequestCanceled();
}
// Checks: `msg.sender` is the recipient or the sender when:
// - the request status is `Pending`, OR
// - the status is `Ongoing` or `Expired` and the payment method is transfer-based
if (requestStatus == Types.Status.Pending || request.config.method == Types.Method.Transfer) {
if (msg.sender != request.recipient && msg.sender != request.sender) {
revert Errors.OnlyRequestSenderOrRecipient();
}
}
// Checks, Effects, Interactions: cancel the stream if payment request has already been accepted
// and the payment method is either linear or tranched stream
//
// Notes:
// - Once a linear or tranched stream is created, the `msg.sender` is checked in the
// {SablierV2Lockup} `cancel` method
// - A linear or tranched stream MUST be canceled by calling the `cancel` method on the according
// {ISablierV2Lockup} contract
else {
_cancelStream({ streamId: request.config.streamId });
}
// Effects: mark the payment request as canceled
$.requests[requestId].wasCanceled = true;
// Log the payment request cancelation
emit RequestCanceled(requestId);
}
/// @inheritdoc IPaymentModule
function withdrawRequestStream(uint256 requestId) public returns (uint128 withdrawnAmount) {
// Retrieve the contract storage
PaymentModuleStorage storage $ = _getPaymentModuleStorage();
// Load the payment request state from storage
Types.PaymentRequest memory request = $.requests[requestId];
// Check, Effects, Interactions: withdraw from the stream
withdrawnAmount = _withdrawStream({ streamId: request.config.streamId, to: request.recipient });
// Log the stream withdrawal
emit RequestStreamWithdrawn(requestId, withdrawnAmount);
}
/*//////////////////////////////////////////////////////////////////////////
INTERNAL-METHODS
//////////////////////////////////////////////////////////////////////////*/
/// @dev Pays the `id` request by transfer
function _payByTransfer(Types.PaymentRequest memory request) internal {
// Check if the payment must be done in native token (ETH) or an ERC-20 token
if (request.config.asset == NATIVE_TOKEN) {
// Checks: the payment amount matches the request value
if (msg.value < request.config.amount) {
revert Errors.PaymentAmountLessThanRequestedAmount({ amount: request.config.amount });
}
// Interactions: pay the recipient with native token (ETH)
(bool success,) = payable(request.recipient).call{ value: request.config.amount }("");
if (!success) revert Errors.NativeTokenPaymentFailed();
} else {
// Interactions: pay the recipient with the ERC-20 token
IERC20(request.config.asset).safeTransferFrom({
from: msg.sender,
to: request.recipient,
value: request.config.amount
});
}
}
/// @dev Create the linear stream payment
function _payByLinearStream(Types.PaymentRequest memory request) internal returns (uint256 streamId) {
streamId = _createLinearStream({
asset: IERC20(request.config.asset),
totalAmount: request.config.amount,
startTime: request.startTime,
endTime: request.endTime,
recipient: request.recipient
});
}
/// @dev Create the tranched stream payment
function _payByTranchedStream(Types.PaymentRequest memory request) internal returns (uint256 streamId) {
uint40 numberOfTranches =
Helpers.computeNumberOfPayments(request.config.recurrence, request.endTime - request.startTime);
streamId = _createTranchedStream({
asset: IERC20(request.config.asset),
totalAmount: request.config.amount,
startTime: request.startTime,
recipient: request.recipient,
numberOfTranches: numberOfTranches,
recurrence: request.config.recurrence
});
}
/// @notice Calculates the number of payments to be made for a recurring transfer and tranched stream-based request
/// @dev Reverts if the number of payments is zero, indicating that either the interval or recurrence type was set incorrectly
function _checkIntervalPayments(
Types.Recurrence recurrence,
uint40 startTime,
uint40 endTime
)
internal
pure
returns (uint40 numberOfPayments)
{
// Checks: the request payment interval matches the recurrence type
// This cannot underflow as the start time is stricly lower than the end time when this call executes
uint40 interval;
unchecked {
interval = endTime - startTime;
}
// Check and calculate the expected number of payments based on the recurrence and payment interval
numberOfPayments = Helpers.computeNumberOfPayments(recurrence, interval);
// Revert if there are zero payments to be made since the payment method due to invalid interval and recurrence type
if (numberOfPayments == 0) {
revert Errors.PaymentIntervalTooShortForSelectedRecurrence();
}
}
/// @notice Retrieves the status of the `requestId` payment request
/// Note:
/// - The status of a payment request is determined by the `wasCanceled` and `wasAccepted` flags and:
/// - For a stream-based payment request, by the status of the underlying stream;
/// - For a transfer-based payment request, by the number of payments left;
function _statusOf(uint256 requestId) internal view returns (Types.Status status) {
// Retrieve the contract storage
PaymentModuleStorage storage $ = _getPaymentModuleStorage();
// Load the payment request state from storage
Types.PaymentRequest memory request = $.requests[requestId];
// Check if the payment request has expired (if it's expirable) or is pending
if (!request.wasAccepted && !request.wasCanceled) {
if (request.config.canExpire && uint40(block.timestamp) > request.endTime) {
return Types.Status.Expired;
}
return Types.Status.Pending;
}
// Check if dealing with a stream-based payment request
if (request.config.streamId != 0) {
Lockup.Status statusOfStream = statusOfStream({ streamId: request.config.streamId });
if (statusOfStream == Lockup.Status.SETTLED) {
return Types.Status.Paid;
} else if (statusOfStream == Lockup.Status.DEPLETED) {
// Retrieve the total streamed amount until now
uint128 streamedAmount = streamedAmountOf({ streamId: request.config.streamId });
// Check if the payment request is canceled or paid
return streamedAmount < request.config.amount ? Types.Status.Canceled : Types.Status.Paid;
} else if (statusOfStream == Lockup.Status.CANCELED) {
return Types.Status.Canceled;
} else {
return Types.Status.Ongoing;
}
}
// Otherwise, the payment request is transfer-based
if (request.wasCanceled) {
return Types.Status.Canceled;
} else if (request.config.paymentsLeft == 0) {
return Types.Status.Paid;
}
return Types.Status.Ongoing;
}
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
}
"
},
"node_modules/@openzeppelin/contracts/utils/Strings.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
using SafeCast for *;
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev The string being parsed contains characters that are not in scope of the given base.
*/
error StringsInvalidChar();
/**
* @dev The string being parsed is not a properly formatted address.
*/
error StringsInvalidAddressFormat();
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly ("memory-safe") {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
assembly ("memory-safe") {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
* representation, according to EIP-55.
*/
function toChecksumHexString(address addr) internal pure returns (string memory) {
bytes memory buffer = bytes(toHexString(addr));
// hash the hex part of buffer (skip length + 2 bytes, length 40)
uint256 hashValue;
assembly ("memory-safe") {
hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
}
for (uint256 i = 41; i > 1; --i) {
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
// case shift by xoring with 0x20
buffer[i] ^= 0x20;
}
hashValue >>= 4;
}
return string(buffer);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
/**
* @dev Parse a decimal string and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input) internal pure returns (uint256) {
return parseUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseUint} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
uint256 result = 0;
for (uint256 i = begin; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 9) return (false, 0);
result *= 10;
result += chr;
}
return (true, result);
}
/**
* @dev Parse a decimal string and returns the value as a `int256`.
*
* Requirements:
* - The string must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input) internal pure returns (int256) {
return parseInt(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
(bool success, int256 value) = tryParseInt(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
* the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
}
uint256 private constant ABS_MIN_INT256 = 2 ** 255;
/**
* @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character or if the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, int256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseIntUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseInt} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseIntUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, int256 value) {
bytes memory buffer = bytes(input);
// Check presence of a negative sign.
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
bool positiveSign = sign == bytes1("+");
bool negativeSign = sign == bytes1("-");
uint256 offset = (positiveSign || negativeSign).toUint();
(bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);
if (absSuccess && absValue < ABS_MIN_INT256) {
return (true, negativeSign ? -int256(absValue) : int256(absValue));
} else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
return (true, type(int256).min);
} else return (false, 0);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input) internal pure returns (uint256) {
return parseHexUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseHexUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
* invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseHexUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseHexUint} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseHexUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
// skip 0x prefix if present
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 offset = hasPrefix.toUint() * 2;
uint256 result = 0;
for (uint256 i = begin + offset; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 15) return (false, 0);
result *= 16;
unchecked {
// Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
// This guaratees that adding a value < 16 will not cause an overflow, hence the unchecked.
result += chr;
}
}
return (true, result);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input) internal pure returns (address) {
return parseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
(bool success, address value) = tryParseAddress(input, begin, end);
if (!success) revert StringsInvalidAddressFormat();
return value;
}
/**
* @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
* formatted address. See {parseAddress} requirements.
*/
function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
return tryParseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
* formatted address. See {parseAddress} requirements.
*/
function tryParseAddress(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, address value) {
if (end > bytes(input).length || begin > end) return (false, address(0));
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
// check that input is the correct length
if (end - begin == expectedLength) {
// length guarantees that this does not overflow, and value is at most type(uint160).max
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
return (s, address(uint160(v)));
} else {
return (false, address(0));
}
}
function _tryParseChr(bytes1 chr) private pure returns (uint8) {
uint8 value = uint8(chr);
// Try to parse `chr`:
// - Case 1: [0-9]
// - Case 2: [a-f]
// - Case 3: [A-F]
// - otherwise not supported
unchecked {
if (value > 47 && value < 58) value -= 48;
else if (value > 96 && value < 103) value -= 87;
else if (value > 64 && value < 71) value -= 55;
else return type(uint8).max;
}
return value;
}
/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
* assembly block as such would prevent some optimizations.
*/
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := mload(add(buffer, add(0x20, offset)))
}
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.22;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
function __UUPSUpgradeable_init() internal onlyInitializing {
}
function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
* See {_onlyProxy}.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
"
},
"node_modules/@sablier/lockup/src/types/DataTypes.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { UD2x18 } from "@prb/math/src/UD2x18.sol";
import { UD60x18 } from "@prb/math/src/UD60x18.sol";
// This file defines all structs used in Lockup, most of which are organized under three namespaces:
//
// - BatchLockup
// - Lockup
// - LockupDynamic
// - LockupLinear
// - LockupTranched
//
// You will notice that some structs contain "slot" annotations - they are used to indicate the
// storage layout of the struct. It is more gas efficient to group small data types together so
// that they fit in a single 32-byte slot.
/// @dev Namespace for the structs used in `BatchLockup` contract.
library BatchLockup {
/// @notice A struct encapsulating all parameters of {SablierLockup.createWithDurationsLD} except for the token.
struct CreateWithDurationsLD {
address sender;
address recipient;
uint128 totalAmount;
bool cancelable;
bool transferable;
LockupDynamic.SegmentWithDuration[] segmentsWithDuration;
string shape;
Broker broker;
}
/// @notice A struct encapsulating all parameters of {SablierLockup.createWithDurationsLL} except for the token.
struct CreateWithDurationsLL {
address sender;
address recipient;
uint128 totalAmount;
bool cancelable;
bool transferable;
LockupLinear.Durations durations;
LockupLinear.UnlockAmounts unlockAmounts;
string shape;
Broker broker;
}
/// @notice A struct encapsulating all parameters of {SablierLockup.createWithDurationsLT} except for the token.
struct CreateWithDurationsLT {
address sender;
address recipient;
uint128 totalAmount;
bool cancelable;
bool transferable;
LockupTranched.TrancheWithDuration[] tranchesWithDuration;
string shape;
Broker broker;
}
/// @notice A struct encapsulating all parameters of {SablierLockup.createWithTimestampsLD} except for the token.
struct CreateWithTimestampsLD {
address sender;
address recipient;
uint128 totalAmount;
bool cancelable;
bool transferable;
uint40 startTime;
LockupDynamic.Segment[] segments;
string shape;
Broker broker;
}
/// @notice A struct encapsulating all parameters of {SablierLockup.createWithTimestampsLL} except for the token.
struct CreateWithTimestampsLL {
address sender;
address recipient;
uint128 totalAmount;
bool cancelable;
bool transferable;
Lockup.Timestamps timestamps;
uint40 cliffTime;
LockupLinear.UnlockAmounts unlockAmounts;
string shape;
Broker broker;
}
/// @notice A struct encapsulating all parameters of {SablierLockup.createWithTimestampsLT} except for the token.
struct CreateWithTimestampsLT {
address sender;
address recipient;
uint128 totalAmount;
bool cancelable;
bool transferable;
uint40 startTime;
LockupTranched.Tranche[] tranches;
string shape;
Broker broker;
}
}
/// @notice Struct encapsulating the broker parameters passed to the create functions. Both can be set to zero.
/// @param account The address receiving the broker's fee.
/// @param fee The broker's percentage fee from the total amount, denoted as a fixed-point number where 1e18 is 100%.
struct Broker {
address account;
UD60x18 fee;
}
/// @notice Namespace for the structs used in all Lockup models.
library Lockup {
/// @notice Struct encapsulating the deposit, withdrawn, and refunded amounts, all denoted in units of the token's
/// decimals.
/// @dev Because the deposited and the withdrawn amount are often read together, declaring them in the same slot
/// saves gas.
/// @param deposited The initial amount deposited in the stream, net of broker fee.
/// @param withdrawn The cumulative amount withdrawn from the stream.
/// @param refunded The amount refunded to the sender. Unless the stream was canceled, this is always zero.
struct Amounts {
// slot 0
uint128 deposited;
uint128 withdrawn;
// slot 1
uint128 refunded;
}
/// @notice Struct encapsulating (i) the deposit amount and (ii) the broker fee amount, both denoted in units of the
/// token's decimals.
/// @param deposit The amount to deposit in the stream.
/// @param brokerFee The broker fee amount.
struct CreateAmounts {
uint128 deposit;
uint128 brokerFee;
}
/// @notice Struct encapsulating the common parameters emitted in the `Create` event.
/// @param funder The address which has funded the stream.
/// @param sender The address distributing the tokens, which is able to cancel the stream.
/// @param recipient The address receiving the tokens, as well as the NFT owner.
/// @param amounts Struct encapsulating (i) the deposit amount, and (ii) the broker fee amount, both denoted
/// in units of the token's decimals.
/// @param token The contract address of the ERC-20 token to be distributed.
/// @param cancelable Boolean indicating whether the stream is cancelable or not.
/// @param transferable Boolean indicating whether the stream NFT is transferable or not.
/// @param timestamps Struct encapsulating (i) the stream's start time and (ii) end time, all as Unix timestamps.
/// @param shape An optional parameter to specify the shape of the distribution function. This helps differentiate
/// streams in the UI.
/// @param broker The address of the broker who has helped create the stream, e.g. a front-end website.
struct CreateEventCommon {
address funder;
address sender;
address recipient;
Lockup.CreateAmounts amounts;
IERC20 token;
bool cancelable;
bool transferable;
Lockup.Timestamps timestamps;
string shape;
address broker;
}
/// @notice Struct encapsulating the parameters of the `createWithDurations` functions.
/// @param sender The addres
Submitted on: 2025-09-26 11:21:28
Comments
Log in to comment.
No comments yet.