aere-contracts/contracts/AereAppPaymasterFactory.sol
Aere Network acac2f00a6
Some checks failed
contracts-ci / Install (lockfile) → compile → full test suite (push) Has been cancelled
contracts-ci / Ethereum interop (EIP-2537 BLS, prague hardfork) (push) Has been cancelled
contracts-ci / PQC known-answer tests (NIST vectors) (push) Has been cancelled
contracts-ci / Coverage (scoped, with artifacts) (push) Has been cancelled
The unpublished line of work joins the sanitized public line
The published line and the local line had no common ancestor: the public one
carried the redaction pass, the local one carried three weeks of corrections
that never shipped. This commit ports the local work onto the public line,
keeps every public redaction, and extends the same discretion to seven client
mentions that were still named in published comments.

Carried: LICENSE year and LICENSING.md; the measured burn figures replacing
the deflation claim (the vault holds ~0.137 AERE of 2.8 billion, and burn is
a share of validator coinbase revenue, which is zero today); 'audited' removed
from next to Bouncy Castle; citation paths rewritten to published form with
CITATIONS-UNRESOLVED.md remeasured 2026-08-11; VERIFY-POLICY.md; slashing and
ownership comments brought down to what the code does; the AerePyth repair;
the shutter test helper the tests cite; runnable package.json entries; the CI
file split into a GitHub/Gitea twin pair with a real measured test-run status;
and the .gitignore hardening written after a compiled artifact leaked a local
path in a sibling repository. A false '2-of-3 multisig' description of the
owner account is corrected to what the chain measures: an externally owned
account. The self-audit findings catalog stays unpublished pending an explicit
decision.
2026-08-15 13:59:30 +03:00

113 lines
4.1 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "./paymaster/PaymasterBase.sol";
/**
* @title AereAppPaymaster
* @notice Generic dApp-funded paymaster. Each dApp on AERE deploys one of these
* and funds it from their own treasury. The paymaster sponsors gas only
* for UserOps calling contracts on the dApp's whitelist, and (optionally)
* only for senders on the dApp's allowlist.
*
* Use case: a consumer wallet application deploys its own AereAppPaymaster,
* funds it from its own treasury, whitelists its contracts as sponsorship
* targets, and optionally allowlists the senders it has authenticated in its
* app. Foundation contributes zero.
*
* Same pattern works for any dApp: NFT marketplaces, games, DeFi apps.
*/
contract AereAppPaymaster is PaymasterBase {
/// Optional sender allowlist. If enabled, only allowlisted senders can be sponsored.
bool public senderAllowlistEnabled;
mapping(address => bool) public senderAllowlist;
/// Required target whitelist. UserOps must invoke an allowed target contract.
mapping(address => bool) public targetWhitelist;
/// Per-sender lifetime sponsorship cap. 0 = unlimited.
uint256 public maxOpsPerSender;
mapping(address => uint256) public opsCountBySender;
event SenderAllowlistChanged(bool enabled);
event SenderAllowlist(address indexed sender, bool allowed);
event TargetWhitelist(address indexed target, bool allowed);
event ConfigChanged(uint256 maxOpsPerSender);
event Sponsored(address indexed sender, address indexed target, uint256 newSenderCount);
constructor(IEntryPoint _ep, address _owner) PaymasterBase(_ep) {
_transferOwnership(_owner);
}
function setSenderAllowlist(bool enabled) external onlyOwner {
senderAllowlistEnabled = enabled;
emit SenderAllowlistChanged(enabled);
}
function setAllowedSender(address sender, bool allowed) external onlyOwner {
senderAllowlist[sender] = allowed;
emit SenderAllowlist(sender, allowed);
}
function setTargetWhitelist(address target, bool allowed) external onlyOwner {
targetWhitelist[target] = allowed;
emit TargetWhitelist(target, allowed);
}
function setMaxOpsPerSender(uint256 max) external onlyOwner {
maxOpsPerSender = max;
emit ConfigChanged(max);
}
function _validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 /*userOpHash*/,
uint256 /*maxCost*/
) internal override returns (bytes memory context, uint256 validationData) {
address sender = userOp.sender;
if (senderAllowlistEnabled) {
require(senderAllowlist[sender], "AppPM: sender not allowed");
}
if (maxOpsPerSender > 0) {
require(opsCountBySender[sender] < maxOpsPerSender, "AppPM: sender quota exhausted");
}
// Decode target from SimpleAccount-compatible execute(target, value, data) callData layout.
bytes calldata cd = userOp.callData;
require(cd.length >= 36, "AppPM: bad callData");
address target;
assembly {
target := shr(96, calldataload(add(cd.offset, 16)))
}
require(targetWhitelist[target], "AppPM: target not allowed");
opsCountBySender[sender] += 1;
emit Sponsored(sender, target, opsCountBySender[sender]);
return ("", 0);
}
}
/**
* @title AereAppPaymasterFactory
* @notice One-call factory for any dApp to spin up its own AereAppPaymaster.
* The dApp's deployer ends up as owner of the new paymaster.
* No registration fee. Anyone can deploy.
*/
contract AereAppPaymasterFactory {
IEntryPoint public immutable entryPoint;
event PaymasterCreated(address indexed deployer, address indexed paymaster);
constructor(IEntryPoint _ep) {
entryPoint = _ep;
}
function createPaymaster() external returns (address paymaster) {
AereAppPaymaster p = new AereAppPaymaster(entryPoint, msg.sender);
paymaster = address(p);
emit PaymasterCreated(msg.sender, paymaster);
}
}