Skip to content
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
277c4c0
first shot
d10r Jan 28, 2026
6d571b2
minimal implementation
d10r Jan 28, 2026
4d99c7b
smoke test passing
d10r Jan 28, 2026
6ee7437
solhint: don't check for updates when not asked to
d10r Jan 28, 2026
3b54b90
updated foundry to v1.3.6 (1.3 improves eip712 support)
d10r Jan 30, 2026
3f3e74d
disable new linter rules (for now)
d10r Jan 30, 2026
53f953f
more testing
d10r Jan 30, 2026
5ee90cb
added EIP-4337-like nonce
d10r Feb 3, 2026
06d230e
added provider auth
d10r Feb 3, 2026
458cbec
Merge branch 'dev' into 2026-01-only712
d10r Feb 4, 2026
35c55b1
fix mishap
d10r Feb 4, 2026
4359446
added validity time window validation
d10r Feb 4, 2026
d2ff27f
updated to revised simplified schema
d10r Feb 9, 2026
17a38f1
fix msgSender
d10r Feb 10, 2026
bd5a524
remove unneeded constructor arg
d10r Feb 10, 2026
8093074
added encodeParams to forwarder
d10r Feb 11, 2026
2f847fc
change naming convention for encode view functions
d10r Feb 13, 2026
7a5ee29
rearrange code
d10r Feb 13, 2026
61c13d3
added scripts for managing the 712 macro forwarder and related ACL
d10r Feb 13, 2026
05efaf0
flattened Security fields
d10r Mar 4, 2026
5c42300
added Permit2MacroForwarder
d10r Mar 4, 2026
1c29d42
working PoC
d10r Mar 9, 2026
a5f82de
allow self-relaying
d10r Mar 9, 2026
8b0711f
major refactoring of ClearSigning related code
d10r Mar 10, 2026
0619c6b
Merge branch 'dev' into 2026-03-permit2_and_macro
d10r Mar 17, 2026
0b0689b
renamed ClearSigningMacro -> ClearMacro
d10r Mar 17, 2026
68a1028
renamed IUserDefinedMacro -> IMacro
d10r Mar 17, 2026
94e2336
Merge branch 'dev' into 2026-03-permit2_and_macro
d10r Mar 17, 2026
c354fe9
updated CHANGELOG
d10r Mar 17, 2026
077c054
add attribution
d10r Mar 17, 2026
97f2c44
fix solc version
d10r Mar 17, 2026
0aa11c3
added Base contract for ClearMacro
d10r Mar 19, 2026
3bd4c27
added missing lib
d10r Mar 20, 2026
3b8532b
rename
d10r Mar 27, 2026
3800d24
renamings
d10r Mar 27, 2026
9b1cc5c
Merge branch 'dev' into 2026-03-permit2_and_macro
d10r Mar 27, 2026
51f03d2
more test coverage
d10r Mar 27, 2026
d4f3ab0
simplified ClearMacroBase
d10r Mar 28, 2026
cd60029
Merge branch 'dev' into 2026-03-permit2_and_macro
hellwolf Apr 1, 2026
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
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions packages/ethereum-contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ All notable changes to the ethereum-contracts will be documented in this file.

This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [UNRELEASED]

### Added

- `Permit2ClearMacroForwarder`: a new macro forwarder that executes EIP-712-signed meta-transactions with properties giving it additional security guarantees, optionally with Permit2 support (allowing to have underlying upgraded + a macro executed, all with one wallet action).
- `ClearMacroBase`: a new base contract for clear macro implementations that support multiple actions.

### Breaking
- `IUserDefinedMacro` renamed to `IMacro`.

## [v1.15.0]

### Added

- `[Permit2]ClearMacroForwarder`: a new macro forwarder that executes EIP-712-signed meta-transactions with properties giving it additional security guarantees, optionally with Permit2 support (allowing to have underlying upgraded + a macro executed, all with one wallet action).
- `SuperToken`: the contract admin can enable/disable a _Yield Backend_ in order to generate a yield on the underlying asset.
- `SuperToken`: added `VERSION()` which returns the version string of the logic contract set for the SuperToken, and inline CHANGELOG.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice Minimal interface for Uniswap Permit2 (SignatureTransfer).
/// @dev Canonical address: 0x000000000022D473030F116dDEE9F6B43aC78BA3
/// For the full Permit2 interface, see https://github.com/Uniswap/permit2/blob/main/src/interfaces/IPermit2.sol
interface IPermit2 {
struct TokenPermissions {
address token;
uint256 amount;
}
struct PermitTransferFrom {
TokenPermissions permitted;
uint256 nonce;
uint256 deadline;
}
struct SignatureTransferDetails {
address to;
uint256 requestedAmount;
}

function DOMAIN_SEPARATOR() external view returns (bytes32);

function permitWitnessTransferFrom(
PermitTransferFrom calldata permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes32 witness,
string calldata witnessTypeString,
bytes calldata signature
) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.11;

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

/**
* @dev Interface for a macro used with the ClearMacroForwarder.
* Implementations provide the EIP-712 metadata and hashing logic for the
* macro-specific action encoded in `params`.
*/
interface IClearMacro is IMacro {
/**
* @dev Returns the primary EIP-712 type name.
* This is usually rendered prominently by wallets and should concisely
* describe the action or intent to be signed.
* @param params Encoded macro-specific parameters.
* @return name The primary type name.
*/
function getPrimaryTypeName(bytes memory params) external view returns (string memory);

/**
* @dev Returns the EIP-712 type definition of the action.
* The type name must be `Action`; only the fields are implementation-specific.
* @param params Encoded macro-specific parameters.
* @return typeDef The `Action(...)` type definition.
*/
function getActionTypeDefinition(bytes memory params) external view returns (string memory);

/**
* @dev Returns the EIP-712 struct hash of the action encoded in `params`.
* The hash must be constructed from the `Action` type definition and the
* underlying action data according to the EIP-712 standard.
* @param params Encoded macro-specific parameters.
* @return structHash The EIP-712 struct hash of the action.
*/
function getActionStructHash(bytes memory params) external view returns (bytes32);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.11;

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

/**
* @dev Interface for ClearMacro forwarders.
* A ClearMacro forwarder executes EIP-712 signed meta-transactions whose
* payload consists of macro-specific action data and additional security
* parameters.
*/
interface IClearMacroForwarder {
/**
* @dev Opaque macro-specific action payload, ABI-encoded for transport.
* The forwarder does not decode these fields itself; the macro defines the
* actual EIP-712 `Action` type and computes its struct hash from these bytes.
*/
struct EncodedAction {
bytes params;
}

/**
* @dev Top-level wire format passed to `runMacro`.
* Callers typically build this using `encodeParams`, but it can also be
* constructed manually and ABI-encoded as `bytes`.
*/
struct Payload {
EncodedAction action;
Security security;
}

/**
* @dev Security parameters for a ClearMacro payload.
* Includes the provider identifier, validity window, and ERC-4337-style nonce.
*/
struct Security {
string domain;
string provider;
uint256 validAfter;
uint256 validBefore;
uint256 nonce;
}

/**
* @dev Runs the macro with an EIP-712 signed payload.
* Reverts if the signature is invalid or if the payload fails security checks.
* @param m Target macro contract.
* @param params ABI-encoded `Payload`.
* @param signer Address which signed the payload and on whose behalf the macro runs.
* @param signature EIP-712 signature over the payload digest.
* @return success True if the macro execution succeeded.
*/
function runMacro(
IClearMacro m,
bytes calldata params,
address signer,
bytes calldata signature
) external payable returns (bool);

/**
* @dev Encodes the action and security data into the payload bytes expected by `runMacro`.
* @param params ABI-encoded macro-specific parameters, opaque to the forwarder.
* @param security Security parameters (domain, provider, validAfter, validBefore, nonce).
* @return payload ABI-encoded `Payload` to pass to `runMacro`.
*/
function encodeParams(
bytes calldata params,
Security calldata security
) external pure returns (bytes memory);

/**
* @dev Returns the full EIP-712 type definition string for the given macro and payload.
* @param m Target macro contract.
* @param params ABI-encoded `Payload`.
* @return typeDef Full EIP-712 type definition string.
*/
function getTypeDefinition(IClearMacro m, bytes calldata params)
external
view
returns (string memory);

/**
* @dev Returns the keccak256 hash of the EIP-712 type definition.
* @param m Target macro contract.
* @param params ABI-encoded `Payload`.
* @return typeHash keccak256 hash of the type definition.
*/
function getTypeHash(IClearMacro m, bytes calldata params)
external
view
returns (bytes32);

/**
* @dev Returns the EIP-712 struct hash of the payload.
* @param m Target macro contract.
* @param params ABI-encoded `Payload`.
* @return structHash EIP-712 struct hash of the payload.
*/
function getStructHash(IClearMacro m, bytes calldata params)
external
view
returns (bytes32);

/**
* @dev Returns the EIP-712 digest of the payload.
* This is the value which should be signed off-chain.
* @param m Target macro contract.
* @param params ABI-encoded `Payload`.
* @return digest EIP-712 digest of the payload.
*/
function getDigest(IClearMacro m, bytes calldata params)
external
view
returns (bytes32);

/**
* @dev Returns the next nonce for the given sender and key.
* Nonces follow ERC-4337-style semantics: `(uint256(key) << 64) | sequence`.
* @param sender Address for which the nonce is queried.
* @param key Nonce key.
* @return nonce The next nonce for (`sender`, `key`).
*/
function getNonce(address sender, uint192 key) external view returns (uint256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pragma solidity >=0.8.11;
import { ISuperfluid } from "../superfluid/ISuperfluid.sol";

/**
* @dev User-defined macro used in implementations of TrustedMacros.
* @dev Macro used in implementations of TrustedMacros.
*/
interface IUserDefinedMacro {
interface IMacro {
/**
* @dev Build batch operations according to the parameters provided.
* It's up to the macro contract to map the provided params (can also be empty) to any
Expand All @@ -33,7 +33,7 @@ interface IUserDefinedMacro {
/*
* Additional to the required interface, we recommend to implement one or multiple view functions
* which take operation specific typed arguments and return the abi encoded bytes.
* As a convention, the name of those functions shall start with `params`.
* As a convention, the name of those functions shall start with `encode`.
*
* Implementing this view function(s) has several advantages:
* - Allows to build more complex macros with internally encapsulated dispatching logic
Expand Down
111 changes: 111 additions & 0 deletions packages/ethereum-contracts/contracts/utils/ClearMacroBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import { IClearMacro } from "../interfaces/utils/IClearMacro.sol";
import { IClearMacroForwarder } from "../interfaces/utils/IClearMacroForwarder.sol";
import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol";

/**
* @dev Abstract base for ClearMacro implementations that support multiple actions.
* The forwarder handles EIP-712 and signature verification; this base only provides
* dispatch by action code.
* Wire format: `abi.encode(uint8 actionCode, bytes32 lang, bytes actionParams)`.
* Caller must specify lang; it is passed to getActionStructHash for i18n descriptions.
*/
abstract contract ClearMacroBase is IClearMacro {

error UnknownActionCode(uint8 actionCode);
error UnsupportedLanguage();

/**
* @dev Per-action handler. All function pointers use the same signatures so they can be stored and dispatched.
*/
struct Action {
uint8 actionCode;
bool exists;
function(bytes memory) internal view returns (string memory) getPrimaryTypeName;
function(bytes memory) internal view returns (string memory) getActionTypeDefinition;
function(bytes memory, bytes32) internal view returns (bytes32)
getActionStructHash;
function(ISuperfluid, bytes memory, address) internal view
returns (ISuperfluid.Operation[] memory)
buildOperations;
bool skipPostCheck;
function(ISuperfluid, bytes memory, address) internal view postCheckHandler;
}

mapping(uint8 => Action) internal _actionHandlers;

constructor() {
Action[] memory actions = _getActions();
for (uint256 i = 0; i < actions.length; i++) {
_actionHandlers[actions[i].actionCode] = actions[i];
}
}

/**
* @dev Override to return the list of actions supported by this macro.
*/
function _getActions() internal view virtual returns (Action[] memory);

/**
* @dev Decode full Payload and then action code + lang + inner params. Used when the forwarder passes full params.
*/
function _decodePayloadAndAction(bytes memory params)
internal
pure
returns (uint8 actionCode, bytes32 lang, bytes memory actionParams)
{
IClearMacroForwarder.Payload memory payload = abi.decode(params, (IClearMacroForwarder.Payload));
(actionCode, lang, actionParams) = abi.decode(payload.action.params, (uint8, bytes32, bytes));
}

/**
* @dev Decode action params (actionCode, lang, actionParams). Used when the
* forwarder passes only payload.action.params.
*/
function _decodeActionParams(bytes memory params)
internal
pure
returns (uint8 actionCode, bytes32 lang, bytes memory actionParams)
{
(actionCode, lang, actionParams) = abi.decode(params, (uint8, bytes32, bytes));
}

function getPrimaryTypeName(bytes memory params) external view override returns (string memory) {
(uint8 actionCode, , bytes memory actionParams) = _decodePayloadAndAction(params);
if (!_actionHandlers[actionCode].exists) revert UnknownActionCode(actionCode);
return _actionHandlers[actionCode].getPrimaryTypeName(actionParams);
}

function getActionTypeDefinition(bytes memory params) external view override returns (string memory) {
(uint8 actionCode, , bytes memory actionParams) = _decodePayloadAndAction(params);
if (!_actionHandlers[actionCode].exists) revert UnknownActionCode(actionCode);
return _actionHandlers[actionCode].getActionTypeDefinition(actionParams);
}

function getActionStructHash(bytes memory params) external view override returns (bytes32) {
(uint8 actionCode, bytes32 lang, bytes memory actionParams) = _decodeActionParams(params);
if (!_actionHandlers[actionCode].exists) revert UnknownActionCode(actionCode);
return _actionHandlers[actionCode].getActionStructHash(actionParams, lang);
}

function buildBatchOperations(ISuperfluid host, bytes memory params, address msgSender)
external
view
override
returns (ISuperfluid.Operation[] memory)
{
(uint8 actionCode, , bytes memory actionParams) = _decodeActionParams(params);
if (!_actionHandlers[actionCode].exists) revert UnknownActionCode(actionCode);
return _actionHandlers[actionCode].buildOperations(host, actionParams, msgSender);
}

function postCheck(ISuperfluid host, bytes memory params, address msgSender) external view override {
(uint8 actionCode, , bytes memory actionParams) = _decodeActionParams(params);
if (_actionHandlers[actionCode].skipPostCheck) return;
if (!_actionHandlers[actionCode].exists) revert UnknownActionCode(actionCode);
_actionHandlers[actionCode].postCheckHandler(host, actionParams, msgSender);
}
}
Loading
Loading