Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b3079f4
Add attribute, setter and event for payment expiration time
maximopalopoli Dec 11, 2025
42a7330
Add attribute, setter and event for the amount to pay in wei
maximopalopoli Dec 11, 2025
05dd72b
Add a withdrawal method to send the funds to a recipient
maximopalopoli Dec 11, 2025
28e8610
Update the ABI used to fetch the payment events
maximopalopoli Dec 12, 2025
89e9e30
Add a TODO for extra check on withdrawal method
maximopalopoli Dec 12, 2025
f75cd1d
Update the deployed anvil state
maximopalopoli Dec 12, 2025
820ab24
Move the amount of eth to pay to the config files
maximopalopoli Dec 12, 2025
c046dac
Move the payment expiration time to the config files
maximopalopoli Dec 12, 2025
b185ee3
Remove unnecessary TODO
maximopalopoli Dec 12, 2025
ca81965
Add subscription limit to the Aggregation Mode Payment Service contract
maximopalopoli Dec 12, 2025
454f144
Add a onlyOwner method to add a list of addresses with a custom expiracy
maximopalopoli Dec 12, 2025
e600923
fix: replace expiracy mentions in addArbitraryExpirationSubscriptions…
maximopalopoli Dec 15, 2025
b6931c9
fix: increase the monthly subscriptions counter on each receive
maximopalopoli Dec 15, 2025
3a56b3e
fix: use the iterated address instead of the sender when adding arbit…
maximopalopoli Dec 15, 2025
3481449
Remove TODO after thinking on possible issue
maximopalopoli Dec 15, 2025
23a863f
Merge branch 'staging' into feataggmode/add-setters-and-subscriptions
maximopalopoli Dec 16, 2025
84d07af
Merge branch 'feataggmode/add-setters-and-subscriptions' into featagg…
maximopalopoli Dec 16, 2025
3ac2414
increase the monthly subscription counter on the addSubscriptions method
maximopalopoli Dec 16, 2025
17c66a5
Convert the resetSubscriptions method in a setter for monthlySubscrip…
maximopalopoli Dec 16, 2025
d73600f
Rework the subscription logic to avoid subscribing for more than N mo…
maximopalopoli Dec 16, 2025
21d2878
Create a separate admin role only for adding subscriptions
maximopalopoli Dec 16, 2025
f6d4155
Merge branch 'staging' into feataggmode/add-subscription-limit-and-ar…
maximopalopoli Dec 16, 2025
59f8ab2
Use upgradeable version of access control on the agg mode payment ser…
maximopalopoli Dec 17, 2025
06410ba
Update contracts/src/core/AggregationModePaymentService.sol
maximopalopoli Dec 17, 2025
ede8fa8
Rename the subscription limit var to remark the month period
maximopalopoli Dec 17, 2025
a90de4b
Add note about adding subscriptions on only admin method and surpassi…
maximopalopoli Dec 17, 2025
445bc04
Apply suggestions from code review
maximopalopoli Dec 17, 2025
c0f9aff
Update the newtork params and deployed state
maximopalopoli Dec 17, 2025
8004004
Fix the expiration date extension logic
maximopalopoli Dec 17, 2025
c18a988
Rename the monthly subscriptions limit var to remove the monthly part
maximopalopoli Dec 17, 2025
13fd25e
Rename the monthly subscriptions amount to rename the monthly part
maximopalopoli Dec 17, 2025
e5dee48
update the contracts deployed state
maximopalopoli Dec 17, 2025
9595158
Improve the subscriptions amount name to active ones
maximopalopoli Dec 17, 2025
fc620c4
Improve the if branches comments on receive method
maximopalopoli Dec 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,25 @@ contract AggregationModePaymentServiceDeployer is Script {
string memory configData = vm.readFile(configPath);

address owner = stdJson.readAddress(configData, ".permissions.paymentServiceOwner");
address recipient = stdJson.readAddress(configData, ".permissions.recipient");
uint256 amountToPay = stdJson.readUint(configData, ".amounts.amountToPayInWei");
uint256 paymentExpirationTimeSeconds = stdJson.readUint(configData, ".amounts.paymentExpirationTimeSeconds");
uint256 subscriptionLimit = stdJson.readUint(configData, ".amounts.subscriptionLimit");

vm.startBroadcast();

AggregationModePaymentService implementation = new AggregationModePaymentService();
ERC1967Proxy proxy =
new ERC1967Proxy(address(implementation), abi.encodeWithSignature("initialize(address)", owner));
ERC1967Proxy proxy = new ERC1967Proxy(
address(implementation),
abi.encodeWithSignature(
"initialize(address,address,uint256,uint256,uint256)",
owner,
recipient,
amountToPay,
paymentExpirationTimeSeconds,
subscriptionLimit
)
);

vm.stopBroadcast();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
"sp1AggregationProgramVKHash": "0x00d6e32a34f68ea643362b96615591c94ee0bf99ee871740ab2337966a4f77af",
"risc0AggregationProgramImageId": "0x8908f01022827e80a5de71908c16ee44f4a467236df20f62e7c994491629d74c"
},
"amounts": {
"amountToPayInWei": 1000000000000000000,
"paymentExpirationTimeSeconds": 86400,
"subscriptionLimit": 5
},
"permissions": {
"owner": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
"paymentServiceOwner": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"
"paymentServiceOwner": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
"recipient": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f"
}
}

Large diffs are not rendered by default.

162 changes: 154 additions & 8 deletions contracts/src/core/AggregationModePaymentService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,24 @@ import {UUPSUpgradeable} from "@openzeppelin-upgrades/contracts/proxy/utils/UUPS
* @notice Handles deposits that grant time-limited access to aggregation services.
*/
contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUPSUpgradeable {
/// @notice for how much time the payment is valid in seconds (86400s = 24hs)
uint256 public constant PAYMENT_VALID_UNTIL_SECONDS = 86400;
/// @notice for how much time the payment is valid in seconds
uint256 public paymentExpirationTimeSeconds;

/// @notice The amount to pay for a subscription in wei.
uint256 public amountToPayInWei;

/// @notice The address where the payment funds will be sent.
address public paymentFundsRecipient;

/// @notice The limit of subscriptions for different addresses per month
uint256 public subscriptionLimit;
Comment thread
MarcosNicolau marked this conversation as resolved.
Outdated

/// @notice The amount of subscriptions for the current month
Comment thread
maximopalopoli marked this conversation as resolved.
Outdated
uint256 public monthlySubscriptionsAmount;

/// @notice The amount of addresses currently subscribed. expirationTime is UTC seconds, to be
/// compared against block timestamps
Comment thread
maximopalopoli marked this conversation as resolved.
Outdated
mapping(address subscriber => uint256 expirationTime) public subscribedAddresses;

/**
* @notice Emitted when a user deposits funds to purchase service time.
Expand All @@ -23,7 +39,32 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
*/
event UserPayment(address user, uint256 indexed amount, uint256 indexed from, uint256 indexed until);

error InvalidDepositAmount(uint256 amount);
/// @notice Event emitted when the payment expiration time is updated
/// @param newExpirationTime the new expiration time in seconds
event PaymentExpirationTimeUpdated(uint256 indexed newExpirationTime);

/// @notice Event emitted when the amount to pay for subscription is updated
/// @param newAmountToPay the new amount to pay for a subscription in wei.
event AmountToPayUpdated(uint256 indexed newAmountToPay);

/// @notice Event emitted when the subscription limit is updated
/// @param newSubscriptionLimit the new monthly subscription limit.
event SubscriptionLimitUpdated(uint256 indexed newSubscriptionLimit);

/// @notice Event emitted when the funds recipient is updated
/// @param newFundsRecipient the new address for receiving the funds on withdrawal.
event FundsRecipientUpdated(address indexed newFundsRecipient);

/// @notice Event emitted when the balance is withdrawn to the recipient address
/// @param recipient the address where the funds will be sent
/// @param amount the amont send to the recipient address
event FundsWithdrawn(address indexed recipient, uint256 amount);

error InvalidDepositAmount(uint256 amountReceived, uint256 amountRequired);

error SubscriptionLimitReached(uint256 subscriptionLimit);

error SubscriptionNotExpired(uint256 expiration, uint256 currentTime);

/**
* @notice Disables initializers for the implementation contract.
Expand All @@ -35,11 +76,27 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
/**
* @notice Initializes the contract and transfers ownership to the provided address.
* @param _owner Address that becomes the contract owner.
* @param _paymentFundsRecipient Address that will receive the withdrawal funds.
* @param _amountToPayInWei Amount to pay in wei for the subscription.
* @param _paymentExpirationTimeSeconds The time in seconds that the subscription takes to expire.
* @param _subscriptionLimit The maximum subscribers that can be subscribed at the same time.
*
*/
function initialize(address _owner) public initializer {
function initialize(
address _owner,
address _paymentFundsRecipient,
uint256 _amountToPayInWei,
uint256 _paymentExpirationTimeSeconds,
uint256 _subscriptionLimit
) public initializer {
__Ownable_init();
__UUPSUpgradeable_init();
_transferOwnership(_owner);

paymentExpirationTimeSeconds = _paymentExpirationTimeSeconds;
amountToPayInWei = _amountToPayInWei;
paymentFundsRecipient = _paymentFundsRecipient;
subscriptionLimit = _subscriptionLimit;
}

/**
Expand All @@ -52,17 +109,106 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
onlyOwner // solhint-disable-next-line no-empty-blocks
{}

/**
* @notice Sets the new expiration time. Only callable by the owner
* @param newExpirationTimeInSeconds The new expiration time for the users payments in seconds.
*/
function setPaymentExpirationTimeSeconds(uint256 newExpirationTimeInSeconds) public onlyOwner() {
paymentExpirationTimeSeconds = newExpirationTimeInSeconds;

emit PaymentExpirationTimeUpdated(newExpirationTimeInSeconds);
}

/**
* @notice Sets the new amount to pay. Only callable by the owner
* @param newRecipient The new address for receiving the funds on withdrawal.
*/
function setFundsRecipientAddress(address newRecipient) public onlyOwner() {
paymentFundsRecipient = newRecipient;

emit FundsRecipientUpdated(newRecipient);
}

/**
* @notice Sets the new amount to pay. Only callable by the owner
* @param newAmountToPay The new amount to pay for subscription in wei.
*/
function setAmountToPay(uint256 newAmountToPay) public onlyOwner() {
amountToPayInWei = newAmountToPay;

emit AmountToPayUpdated(newAmountToPay);
}

/**
* @notice Sets the new subscription limit. Only callable by the owner
* @param newSubscriptionLimit The new monthly subscription limit.
*/
function setSubscriptionLimit(uint256 newSubscriptionLimit) public onlyOwner() {
subscriptionLimit = newSubscriptionLimit;

emit SubscriptionLimitUpdated(newSubscriptionLimit);
}

/**
* @notice Resets the monthly subscriptions mapping counter to zero.
*/
function resetSubscriptions() public onlyOwner() {
Comment thread
JuArce marked this conversation as resolved.
Outdated
monthlySubscriptionsAmount = 0;
}

/**
* @notice Adds an array of addresses to the payment map and emits the Payment event.
* @param addressesToAdd the addresses to be subscribed
* @param expirationTimestamp the expiration timestamp (UTC seconds) for that subscriptions
*/
Comment thread
MarcosNicolau marked this conversation as resolved.
function addArbitraryExpirationSubscriptions(address[] memory addressesToAdd, uint256 expirationTimestamp) public onlyOwner() {
for (uint256 i=0; i < addressesToAdd.length; ++i) {
address addressToAdd = addressesToAdd[i];

subscribedAddresses[addressToAdd] = expirationTimestamp;
Comment thread
JuArce marked this conversation as resolved.

emit UserPayment(addressToAdd, amountToPayInWei, block.timestamp, expirationTimestamp);
}
}

/**
* @notice Accepts payments and validates they meet the minimum requirement.
*/
receive() external payable {
uint256 amount = msg.value;

// 1 eth
if (amount < 1000000000000000000) {
revert InvalidDepositAmount(amount);
if (amount < amountToPayInWei) {
revert InvalidDepositAmount(amount, amountToPayInWei);
}

emit UserPayment(msg.sender, amount, block.timestamp, block.timestamp + PAYMENT_VALID_UNTIL_SECONDS);
if (monthlySubscriptionsAmount == subscriptionLimit) {
revert SubscriptionLimitReached(subscriptionLimit);
}
Comment thread
maximopalopoli marked this conversation as resolved.
Outdated

// Check if the user has already payed a subscription for this month
uint256 insertedExpiration = subscribedAddresses[msg.sender];
if (insertedExpiration == 0) {
// this means the sender has not payed for a subscription before
subscribedAddresses[msg.sender] = block.timestamp + paymentExpirationTimeSeconds;
} else if (insertedExpiration > block.timestamp) {
// this means the sender has an expired subscription
subscribedAddresses[msg.sender] = block.timestamp + paymentExpirationTimeSeconds;
} else {
// this means the sender has a subscription that has not expired yet
revert SubscriptionNotExpired(insertedExpiration, block.timestamp);
Comment thread
JuArce marked this conversation as resolved.
Outdated
}

++monthlySubscriptionsAmount;

emit UserPayment(msg.sender, amount, block.timestamp, block.timestamp + paymentExpirationTimeSeconds);
}

/**
* @notice Withdraws the contract balance to the recipient address.
*/
function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
payable(paymentFundsRecipient).transfer(balance);
emit FundsWithdrawn(paymentFundsRecipient, balance);
}
}
Loading
Loading