MTX
MTX is a transaction standard developed by Semantic Layer Labs that works with SL-Chain to enforce Verifiable Sequencing Rules (VSR) and Verifiable Aggregation Rules (VAR), enabling enhanced control for dApps over transaction execution.
There are two types of MTX: UserMTX and SolverMTX.
- UserMTX: It includes ERC-4337 User Operations signed by the user, along with dApp operations, VSR, and VAR defined by dApp developers.
- SolverMTX: It contains solver operations for a given UserMTX.
There are three types of operations: user operations, dApp operations, and solver operations. User and dApp operations are defined in the UserMTX, generated at the dApp frontend, while solver operations are defined in the SolverMTX, submitted by solvers.
- User Operations: These are ERC-4337 user operations that describe a user's interaction with the dApp, such as swapping on a DEX.
- dApp Operations: These are operations the dApp wants to execute before and after user and solver operations, such as redistributing MEV revenue back to users or implementing security circuit breakers.
- Solver Operations: These are operations the solver wants to execute before and after user operations, such as back-running or liquidation.
UserMTX
A UserMTX is the core transaction structure initiated by dApps on behalf of users and processed by Semantic Layer. It includes ERC-4337 User Operations signed by the user, dApp operations, along with specific configurations and VSR and VAR defined by the dApp.
/**
* @notice UserMTX struct
* Contains UserOperations, dApp operations, and configuration for VSR and VAR rules.
*/
struct UserMTX {
PackedUserOperation[] userOperations; // Array of ERC4337 UserOperations to execute.
address from; // The EOA address signing the UserMTX.
uint256 nonce; // Unique value used by `from` to prevent replay attacks.
address sponsor; // Address of the sponsor covering dApp operation costs; leave empty if no sponsor.
uint256 maxSponsorship; // Maximum gas fee the `sponsor` will cover.
uint8 noSOs; // a bit map restricts before and after solver operations. `00`:no restriction. `01` no after solver operations. `10`:no before solver operations. `11`: no solver operations.
VSRStruct VSR; // Verifiable Sequencing Rule struct; orders transactions by solver bid amount if non-empty.
VARStruct VAR; // Verifiable Aggregation Rule struct; no aggregation occurs if empty.
DAppOP dappOP; // Struct for dApp operations executed before and after UserOperations and solver operations.
bytes signature; // UserMTX signature signed by `from`.
}
A UserMTX contains the following fields:
UserMTX.userOperations
This field defines an array of ERC-4337 user operations, which are generated at dApp frontend based on user intents.
// ERC-4337 User Operations
struct PackedUserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}
ERC-4337 account abstraction standard is used for the following reasons:
- Easier adoption: ERC-4337 is a battle-tested and widely accepted standards. Semantic Layer can easily integrate with existing infrastructure and dApps that support ERC-4337 standard.
- Minimal trust requirement: By using ERC-4337 standard, user operations within an MTX are authenticated by and executed in the ERC-4337 entry point contract. Other operations, such as dApp and solver operations, which are executed before and after user operations, are isolated from user operation execution. This reduces the need to trust Semantic Layer nodes and solvers, as isolated execution prevents them from performing unauthorized operations on behalf of the user.
User operations are eventually passed to the ERC-4337 entry point contract for final execution, with gas costs paid by users. dApps can also implement the ERC-4337 paymaster to sponsor these gas costs.
UserMTX.from
This field specifies an Externally Owned Account (EOA) address. This address signs the UserMTX and is responsible for covering the gas costs of executing the dApp operations.
UserMTX.nonce
This field contains the nonce for UserMTX.from, which is checked by the SL execution contract to prevent replay attacks. It also allows users to cancel or batch-cancel unsettled MTXs.
UserMTX.sponsor
This field specifies the sponsor address for covering gas costs associated with executing dApp operations. Leave it empty if no sponsorship is applied.
The SL execution contract includes a deposit manager to manage gas costs for dApp operations. dApp developers can deposit funds to cover gas costs for their own dApp operations.
When UserMTX.sponsor is specified, the SL execution contract will deduct gas costs from the sponsor's account. If the sponsor cannot fully cover the costs, UserMTX.from will pay the remaining amount.
See here for detailed information on gas compensations.
UserMTX.maxSponsorship
This field specifies the maximum amount the UserMTX.sponsor is willing to contribute to cover dApp operational costs. Leave it empty if no sponsorship is applied.
UserMTX.noSOs
This field is a bitmap set by the dApp to indicate whether solver operations are restricted.
00: Default state. Solvers can perform both before and after operations.01: Restricts after solver operation.10: Restricts before solver operation.11: Disables all solver operations. UserMTX will be executed directly without SolverMTX.
UserMTX.VSR
This field provides the Verifiable Sequencing Rule (VSR) contract information for the UserMTX.
Developers can deploy a VSR as a smart contract on the SL-Chain. The contract exposes a view function that returns the VSR score based on the solver's bid amount and other relevant data.
SolverMTXs for a given UserMTX are processed and selected on the SL-Chain based on the VSR score, with the SolverMTX that produces the highest score being added to the MTX bundle with the UserMTX. The MTX bundle will then be executed in the destination execution environment.
If this field is not provided, SolverMTXs are selected based on the solver bid amount by default, with the highest bid being chosen.
See here for detailed notes for Verifiable Sequencing Rule (VSR).
interface IVSR {
/**
*
* @param bid solver bid amount
* @param data other data
*/
function score(uint256 bid, bytes calldata data) external view returns (uint256);
}
struct VSRStruct {
IVSR vsrContract;
bytes data;
}
UserMTX.VAR
This field provides the Verifiable Aggregation Rule (VAR) contract information for the UserMTX.
Developers can deploy VAR as a smart contract on the SL-Chain. The VAR contract exposes functions for the SL-Chain to aggregate MTXs.
See here for detailed notes for Verifiable Aggregation Rule (VAR).
interface IVAR {
/**
* @dev It returns the UserMTXs queueing for processing.
* @param id the id of the queue. MTX needs to be bundled together can be added to queue with the same id.
*/
function mtxQueue(uint256 id) external view returns (UserMTX[] memory);
/**
* @dev It signals whether a mtx queue is ready for aggregation.
* @param id the id of the queue. MTX needs to be bundled together can be added to queue with the same id.
*/
function canAggregate(uint256 id) external view returns (bool);
/**
* @dev Call this function to process the MTXs queueing for processing.
* @param id the id of the queue. MTX needs to be bundled together can be added to queue with the same id.
* @param batchSize the number of the mtx to aggregated
*/
function aggregateCurrentQueue(uint256 id, uint256 batchSize) external;
}
struct VARStruct {
IVAR varContract;
}
UserMTX.dappOP
This field defines the dApp operations, which are actions the dApp intends to execute before and after user and solver operations. The dApp can specify which operations to execute using a bitmap format.
The isAuthorized() in the dApp operation contract checks if the specified address (UserMTX.from) is authorized to use this dApp operation contract in the UserMTX.
To minimize the hurdles for dApp integration with Semantic Layer, the dApp operation contracts are optional. If provided:
- the solver bid payment will be sent to the
bidReceiverdefined in the dApp operation.
If not provided:
- the solver bid payment will be sent to the
UserMTX.fromaddress, which is the address authorizingUserMTX.userOperationsand the entireUserMTX.
/**
* @notice DAppOperation struct
* Contains data and hooks for dApp operations, with a bitmap specifying which hooks should be executed.
*/
struct DAppOP {
IDAppHook dAppHook; // dApp operation contract.
uint8 hookBitmap; // Bitmap to indicate which hooks to call; 1 = call, 0 = skip.
// Bitmap format: 1010 (bit 3: preSO, bit 2: preUO, bit 1: postUO, bit 0: postSO).
bytes preSOHookData; // Data required for the pre solver operation; leave empty if not applicable.
bytes preUOHookData; // Data required for the pre user operation; leave empty if not applicable.
bytes postUOHookData; // Data required for the post user operation; leave empty if not applicable.
bytes postSOHookData; // Data required for the post solver operation; leave empty if not applicable.
}
/**
* @notice interface for dapp operation contract
*/
interface IDAppHook {
/**
* @notice Checks if the specified address (UserMTX.from) is authorized to use this dApp operation contract in the UserMTX
* @param user The address to check for authorization.
* @return A boolean indicating if the address is authorized (true) or not (false).
*/
function isAuthorized(address user) external view returns (bool);
/**
* @notice Returns the address designated to receive the Solver's bid.
* @param data Additional data required for determining the receiver; leave empty if not applicable.
* @return The address that should receive the bid.
*/
function bidReceiver(bytes memory data) external view returns (address);
/**
* execution order for all operations
* 1. DApp Operation: pre-solver operations
* 2. Solver Operation: pre-user operations
* 3. DApp Operation: pre-user operations
* 4. User operations
* 5. DApp Operation: post-user operations
* 6. Solver Operation: post-user operations
* 7. DApp Operation: post-solver operations
*
*/
/**
* @notice dApp operation hook to execute before solver operations.
* @dev Should only be called by the SL execution contract.
* @param hookData Data needed for the hook function.
*/
function preSolverOperationHook(bytes memory hookData) external;
/**
* @notice dApp operation hook to execute before user operations.
* @dev Should only be called by the SL execution contract.
* @param hookData Data needed for the hook function.
*/
function preUserOperationHook(bytes memory hookData) external;
/**
* @notice dApp operation hook to execute after user operations.
* @dev Should only be called by the SL execution contract.
* @param hookData Data needed for the hook function.
*/
function postUserOperationHook(bytes memory hookData) external;
/**
* @notice dApp operation hook to execute after solver operations.
* @dev Should only be called by the SL execution contract.
* @param hookData Data needed for the hook function.
*/
function postSolverOperationHook(bytes memory hookData) external;
}
UserMTX.signature
This field contains the UserMTX signature signed by UserMTX.from.
SolverMTX
A SolverMTX is the core transaction structure initiated by solvers. It includes the bid amount the solver is willing to offer and the operations they intend to execute before and after user operations.
/**
* @notice SolverMTX struct
* Contains Solver operations linked to a specific UserMTX.
*/
struct SolverMTX {
address from; // The solver's EOA address signing the SolverMTX.
uint256 nonce; // Unique value used by `from` to prevent replay attacks.
bytes32 userMTXHash; // Hash representing the associated UserMTX for verification.
SolverOP solverOP; // Struct for solver operations executed before and after UserOperations.
bytes signature; // SolverMTX signature signed by `from`.
}
A SolverMTX contains the following fields:
SolverMTX.from
This field specifies an Externally Owned Account (EOA) address, which signs the SolverMTX and covers the gas costs for executing solver operations.
The SL execution contract includes a deposit manager to handle gas costs for dApp and solver operations. Solvers can deposit funds in advance or sign a permit allowing the contract to transfer funds from their addresses to cover gas costs for solver operations.
SolverMTX.nonce
This field contains the nonce for SolverMTX.from, which is checked by the SL execution contract to prevent replay attacks. It also allows solvers to cancel or batch-cancel unsettled MTXs.
SolverMTX.userMTXHash
This field contains a unique hash that identifies the corresponding UserMTX within Semantic Layer. The solver includes this hash to indicate the UserMTX they intend to solve.
SolverMTX.solverOP
This field contains the SolverOP object. It contains the bid amount the solver is willing to offer and solver operations that the solver intends to execute before and after user operations. Solver can specify which operations to execute using a bitmap format.
The solver operation contract must be deployed by the solver in advance. The payBid() function will be called by SL execution contract to enforce the solver bid payment.
/**
* @notice SolverOP struct
* Contains data for solver operations, including bid amount and hooks for pre- and post-operation.
*/
struct SolverOP {
uint256 bidAmount; // Amount bid by the solver is willing to offer
ISolverHook solverHook; // Contract for solver operation hooks.
uint8 hookBitmap; // Bitmap to indicate which hooks to call; 1 = call, 0 = skip.
// Bitmap format: 10 (bit 1: preUO, bit 0: postUO).
bytes preUOHookData; // Data required for the pre user operation; leave empty if not applicable.
bytes postUOHookData; // Data required for the post user operation; leave empty if not applicable.
}
/**
* @notice Interface for solver operation contract.
*/
interface ISolverHook {
/**
* @notice Checks if the specified address (SolverMTX.from) is authorized to use this solver operation contract in the SolverMTX
* @param user The address to check for authorization.
* @return A boolean indicating if the address is authorized (true) or not (false).
*/
function isAuthorized(address user) external view returns (bool);
/**
* execution order for all operations
* 1. DApp Operation: pre-solver operations
* 2. Solver Operation: pre-user operations
* 3. DApp Operation: pre-user operations
* 4. User operations
* 5. DApp Operation: post-user operations
* 6. Solver Operation: post-user operations
* 7. DApp Operation: post-solver operations
*
*/
/**
* @notice Executes before the user operation.
* @dev Should only be called by the SL execution contract.
* @param hookData Data required for the pre-user operation.
*/
function preUserOperation(bytes memory hookData) external;
/**
* @notice Executes after the user operation.
* @dev Should only be called by the SL execution contract.
* @param hookData Data required for the post-user operation.
*/
function postUserOperation(bytes memory hookData) external;
/**
* @notice pay the specified bid amount to the given address.
* @dev Should only be called by the SL execution contract.
* @param to Address to receive the bid amount.
* @param value bid Amount to be paid.
*/
function payBid(address to, uint256 value) external;
}
SolverMTX.signature
This field contains the SolverMTX signature signed by SolverMTX.from.
The life cycle of an MTX
Starting from being initiated on the dApp front end, to eventual execution on the destination execution environment, an MTX will go through the following 5 stages. Developers (e.g., dApp developers and solvers) can use the Semantic Layer SDK to initialize the MTX object.
Stage 0 - frontend packs UserOperation Array
At this stage, the dApp frontend generates a set of user operations based on user intents and interactions. After obtaining the user's signature, the dApp frontend packs these operations into an array, which is then included in the UserMTX.
Stage 1 - frontend adds VSR, VAR, and dApp operations
At this stage, the dApp frontend adds the VSR and VAR contract information for the UserMTX. Optionally, the dApp frontend can also add dApp operations, which are operations to be executed before and after user and solver operations, to the UserMTX.
Stage 2 - user signs the UserMTX
The user must sign the final UserMTX before it is sent to Semantic Layer.
The SL execution contract verifies that the signer is UserMTX.from and checks the nonce to prevent tampering or spoofing. The signed UserMTX is then sent to SL nodes via an API endpoint.
Stage 3 - Semantic Layer nodes process the MTX
Once a Semantic Layer node receives the UserMTX, it broadcasts the UserMTX to the entire network and begins processing it. Each UserMTX has an auction window during which solvers can submit SolverMTXs to the Semantic Layer nodes.
During the auction window, each Semantic Layer node runs the VSR score function on received SolverMTXs and keeps track of the SolverMTX with the highest score. After the auction window expires, the node submits the local best MTX bundle—containing the SolverMTX with the highest score seen by that node—to the SL-Chain.
If no SolverMTX is submitted to the Semantic Layer node, and the auction window passes, the Semantic Layer node will submit the UserMTX to Semantic Layer chain with a null SolverMTX and a score of 0.
Some UserMTXs may set the bitmap noSOs to have more granular restrictions on solver operations. We can explicitly set whether we allow solvers to do front-running, back-running, or sandwiching by setting the noSOs bitmap. These UserMTXs will be submitted to the Semantic Layer chain directly as soon as the Semantic Layer node receives such a UserMTX. For UserMTXs that do not allow any solver operations, this type of UserMTX does not have a VSR specified for them. We expect this type of UserMTX to be either time-sensitive or contain no MEV revenue to be extracted. These UserMTXs will be executed directly without waiting for a SolverMTX.
Each Semantic Layer node might be connected with different sets of solvers, therefore, for a given UserMTX(n), there might be multiple local best MTX bundles submitted to Semantic Layer chain, each being submitted by different Semantic Layer nodes with locally observed most optimal SolverMTX.
Stage 4 - Semantic Layer nodes pack the final MTX
At each block of the SL-Chain, the leader Semantic Layer node uses the VSR specified in the UserMTX to select the globally observed most optimal SolverMTX. This SolverMTX is added to the UserMTX to construct the final PackedMTX, which is serialized and executed on the Semantic Layer execution contract deployed on the destination execution environment.
The final MTX executed on the SL execution contract is a packed MTX containing the UserMTX, the selected SolverMTX, and the VSR/VAR data.
/**
* @notice PackedMTX struct
* Represents the final MTX bundle to be executed in the SL execution contract.
*/
struct PackedMTX {
address payable beneficiary; // Address to receive gas compensation, typically the caller submitting the PackedMTX for on-chain execution.
UserMTX userMTX; // Contains user transaction details in the UserMTX struct.
SolverMTX solverMTX; // Contains SolverMTX details; leave empty if no SolverMTX is included.
VSRInfo vsrInfo; // Execution details for the Verifiable Sequencing Rule (VSR); leave empty if VSR is not applied.
VARInfo varInfo; // Execution details for the Verifiable Aggregation Rule (VAR); leave empty if VAR is not applied.
}
The packed MTX contains the following fields:
PackedMTX.beneficiaryThis field specifies the address that receives gas compensation for on-chain execution. The MTX executor submitting the final PackedMTX for on-chain execution on the destination execution environment must cover upfront transaction costs. The SL execution contract calculates the gas used for involved operations, deducts the costs from the accounts responsible for the respective gas costs, and transfers these funds to thePackedMTX.beneficiaryaddress. See here for more detailed information on gas compensations.PackedMTX.userMTXThis field specifies the UserMTX object that will be executed on the destination chain.PackedMTX.solverMTXThis field specifies the SolverMTX object that will be executed on the destination chain.PackedMTX.vsrInfoThis field specifies the execution details for VSR, such as the VSR score and the block number when the VSR score is calculated. This field is purely for on-chain record purposes and can be omitted if not necessary.PackedMTX.varInfoThis field specifies the execution details for VAR, such as the block number when the VAR aggregation bundle is constructed. This field is purely for on-chain record purposes and can be omitted if not necessary.
Stage 5 - on-chain execution
The final packed MTX is executed by the SL execution contract on the destination chain, which verifies the MTX through several key steps:
- Verifies the ERC-4337 UserOperation Array: This field checks if all ERC-4337 operations in the array are from the same user address.
- Verifies the UserMTX Signer and Signature: This field checks if the signer matches the
fromfield in the UserMTX, verifies the signer nonce, and checks if the signer is authorized to use the specified dApp operation contract. - Verifies the Sponsorship in UserMTX: This field checks if the sponsor allows the
fromaddress to use its funds to cover dApp operation gas costs. - Verifies the SolverMTX Signer and Signature: This field checks if the signer matches the
fromfield in the SolverMTX, verifies the signer nonce, and checks if the signer is authorized to use the specified solver operation contract.
The ERC-4337 UserOperations are forwarded to the ERC-4337 entry point contract for user operation execution. The dApp and solver operations are executed directly by the SL execution contract.
User, dApp, and solver operations are executed in the following order:
/**
* execution order for all operations
* 1. DApp Operation: pre-solver operations
* 2. Solver Operation: pre-user operations
* 3. DApp Operation: pre-user operations
* 4. User Operations
* 5. DApp Operation: post-user operations
* 6. Solver Operation: post-user operations
* 7. DApp Operation: post-solver operations
*/
If the specified solver bid payment in SolverMTX is not zero, the SL execution contract enforces the solver bid payment by calling the payBid() function in the solver operation contract. It transfers the bid payment to the SL execution contract first to verify that the received amount matches the specified bid amount. Then, the SL execution contract transfers the payment to the bid receiver address.