CoreExecution
CoreExecution is an abstract contract that provides a convenient interface for services to process payments through Core.sol. It handles both user-initiated payments (via signatures) and contract-authorized payments.
Overview
Contract Type: Abstract Contract
License: EVVM-NONCOMMERCIAL-1.0
Import Path: @evvm/testnet-contracts/library/utils/service/CoreExecution.sol
Key Features
- Direct integration with Core.sol for payment processing
- Support for both signed and contract-authorized payments
- Batch payment capabilities
- Governance-controlled Core.sol address updates
State Variables
Core public core;
Reference to the EVVM Core.sol contract for balance operations.
Functions
requestPay
function requestPay(
address from,
address token,
uint256 amount,
uint256 priorityFee,
address originExecutor,
uint256 nonce,
bool isAsyncExec,
bytes memory signature
) internal
Requests payment from a user via Core.sol with signature validation.
Parameters:
from- User address making the payment (signer)token- Token address (oraddress(0)for native token)amount- Payment amountpriorityFee- Executor fee (paid to transaction sender)originExecutor- Original executor address (user or relayer who initiated the transaction)nonce- User's nonce for Core.sol (sequential sync or user-chosen async)isAsyncExec- Whether to use async (true) or sync (false) nonce systemsignature- User's EIP-191 signature authorizing the payment
Implementation: Calls core.pay(from, address(this), "", token, amount, priorityFee, address(this), originExecutor, nonce, isAsyncExec, signature)
Signature Format: User must sign centralized payload:
{evvmId},{senderExecutor},{hashPayload},{originExecutor},{nonce},{isAsyncExec}
Where hashPayload = CoreHashUtils.hashDataForPay(address(this), "", token, amount, priorityFee)
Usage: Call this from your service function when a user needs to pay.
Example:
// User wants to pay service
requestPay(
userAddress, // from
address(0), // token (ETH)
1 ether, // amount
0.001 ether, // priorityFee
msg.sender, // originExecutor (relayer or user)
nonce, // User's nonce
true, // isAsyncExec
signature // User's signature
);
requestDispersePay
function requestDispersePay(
CoreStructs.DispersePayMetadata[] memory toData,
address token,
uint256 amount,
uint256 priorityFee,
address originExecutor,
uint256 nonce,
bool isAsyncExec,
bytes memory signature
) internal
Requests batch payment from a user via Core.sol (multi-recipient distribution).
Parameters:
toData- Array ofDispersePayMetadatastructs (amount, to_address, to_identity)token- Token addressamount- Total amount (must equal sum of toData amounts)priorityFee- Executor feeoriginExecutor- Original executor addressnonce- User's nonceisAsyncExec- Nonce typesignature- User's EIP-191 signature
Implementation: Calls core.dispersePay(address(this), toData, token, amount, priorityFee, address(this), originExecutor, nonce, isAsyncExec, signature)
Usage: When service needs to distribute user's funds to multiple recipients in one transaction.
Example:
CoreStructs.DispersePayMetadata[] memory recipients =
new CoreStructs.DispersePayMetadata[](2);
recipients[0] = CoreStructs.DispersePayMetadata({
amount: 0.5 ether,
to_address: address(0x123),
to_identity: ""
});
recipients[1] = CoreStructs.DispersePayMetadata({
amount: 0.5 ether,
to_address: address(0),
to_identity: "bob"
});
requestDispersePay(
recipients,
address(0), // ETH
1 ether, // total (must match sum)
0.01 ether,
msg.sender,
nonce,
true,
signature
);
makeCaPay
function makeCaPay(
address to,
address token,
uint256 amount
) internal
Sends tokens from service's balance to recipient via contract authorization (no signature required).
Parameters:
to- Recipient addresstoken- Token address (oraddress(0)for native token)amount- Amount to send
Implementation: Calls core.caPay(to, token, amount)
Requirements:
- Service must have sufficient balance in Core.sol
- Called contract must be detected as contract (not EOA)
Usage: Use this when your service needs to pay users from its own balance (e.g., rewards, refunds).
Example:
// Refund customer from service balance
makeCaPay(
customerAddress,
address(0), // ETH
1 ether // refund amount
);
makeDisperseCaPay
function makeDisperseCaPay(
CoreStructs.DisperseCaPayMetadata[] memory toData,
address token,
uint256 amount
) internal
Sends tokens to multiple recipients via contract authorization (batch version).
Parameters:
toData- Array ofDisperseCaPayMetadatastructs (to_address, amount)token- Token addressamount- Total amount (must equal sum of toData amounts)
Implementation: Calls core.disperseCaPay(toData, token, amount)
Usage: Efficient way to distribute payments to multiple users in a single transaction.
Example:
CoreStructs.DisperseCaPayMetadata[] memory payouts =
new CoreStructs.DisperseCaPayMetadata[](3);
payouts[0] = CoreStructs.DisperseCaPayMetadata({
to_address: winner1,
amount: 1 ether
});
payouts[1] = CoreStructs.DisperseCaPayMetadata({
to_address: winner2,
amount: 0.5 ether
});
payouts[2] = CoreStructs.DisperseCaPayMetadata({
to_address: winner3,
amount: 0.5 ether
});
makeDisperseCaPay(
payouts,
address(0), // ETH
2 ether // total (must match sum)
);
getNextCurrentSyncNonce
function getNextCurrentSyncNonce(
address user
) external view returns (uint256)
Gets the next sequential sync nonce for a user.
Parameters:
user- User address to query
Returns: Next sync nonce value (auto-increments after each use)
Usage: Query this to get the nonce for sync signature generation.
getIfUsedAsyncNonce
function getIfUsedAsyncNonce(
address user,
uint256 nonce
) external view returns (bool)
Checks if an async nonce has been consumed.
Parameters:
user- User addressnonce- Async nonce to check
Returns: true if consumed, false if available/reserved
Usage: Query before using an async nonce in signatures.
Usage Example
import {CoreExecution} from "@evvm/testnet-contracts/library/utils/service/CoreExecution.sol";
import {Admin} from "@evvm/testnet-contracts/library/utils/governance/Admin.sol";
import {CoreStructs} from "@evvm/testnet-contracts/library/structs/CoreStructs.sol";
contract CoffeeShop is CoreExecution, Admin {
uint256 public constant COFFEE_PRICE = 0.001 ether;
constructor(address coreAddress, address initialAdmin)
CoreExecution(coreAddress)
Admin(initialAdmin)
{}
function buyCoffee(
address buyer,
uint256 priorityFee,
address originExecutor,
uint256 nonce,
bool isAsyncExec,
bytes memory signature
) external {
// Request payment from buyer
requestPay(
buyer,
address(0), // ETH payment
COFFEE_PRICE,
priorityFee,
originExecutor, // Original executor (relayer or user)
nonce,
isAsyncExec,
signature
);
// Process coffee order...
}
function refundCustomer(address customer, uint256 amount) external onlyAdmin {
// Send refund from service balance
makeCaPay(customer, address(0), amount);
}
function distributeRewards(
address[] memory winners,
uint256[] memory amounts
) external onlyAdmin {
// Prepare batch payment data
CoreStructs.DisperseCaPayMetadata[] memory payouts =
new CoreStructs.DisperseCaPayMetadata[](winners.length);
uint256 total = 0;
for (uint256 i = 0; i < winners.length; i++) {
payouts[i] = CoreStructs.DisperseCaPayMetadata({
to_address: winners[i],
amount: amounts[i]
});
total += amounts[i];
}
// Batch payment to multiple users
makeDisperseCaPay(payouts, address(0), total);
}
}
Integration with EvvmService
EvvmService internally inherits from CoreExecution, providing these payment functions automatically:
import {EvvmService} from "@evvm/testnet-contracts/library/EvvmService.sol";
contract MyService is EvvmService {
// Inherits requestPay, makeCaPay, makeCaBatchPay automatically
}
Payment Flow
User-to-Service Payment (requestPay)
- User generates async nonce or queries sync nonce from Core.sol
- User signs payment authorization using centralized signature format:
- Payload:
{evvmId},{serviceAddress},{hashPayload},{originExecutor},{nonce},{isAsyncExec} - Where
hashPayload = CoreHashUtils.hashDataForPay(serviceAddress, "", token, amount, priorityFee)
- Payload:
- Service calls
requestPay()with signature and originExecutor - Core.sol:
- Validates signature matches user and reconstructs payload
- Validates and consumes nonce via
validateAndConsumeNonce() - Transfers tokens from user to service
- Service processes the transaction
User-to-Multiple Recipients (requestDispersePay)
- User signs batch payment with multiple recipients
- Service calls
requestDispersePay()with toData array - Core.sol validates signature and distributes funds to all recipients
- Single nonce consumed for entire batch
Service-to-User Payment (makeCaPay)
- Service calls
makeCaPay()(no signature needed) - Core.sol verifies caller is a contract (not EOA)
- Core.sol transfers from service balance to user
- Uses contract address authorization (
msg.sender)
Service-to-Multiple Recipients (makeDisperseCaPay)
- Service calls
makeDisperseCaPay()with batch data - Core.sol distributes funds to all recipients in one transaction
- More gas-efficient than multiple
makeCaPay()calls
Security Considerations
-
Origin Executor Parameter: Always set
originExecutorto the actual EOA or address that initiated the transaction:// Good - use msg.sender as originExecutor for relayed transactions
requestPay(user, token, amount, fee, msg.sender, nonce, true, sig);
// Also valid - user directly calling
requestPay(user, token, amount, fee, user, nonce, true, sig); -
Nonce Management:
- Sync nonces: Auto-increment, query with
getNextCurrentSyncNonce(user) - Async nonces: User-chosen, check availability with
getIfUsedAsyncNonce(user, nonce) - Never reuse nonces - causes signature failure
- Sync nonces: Auto-increment, query with
-
Signature Validation:
- Core.sol validates signatures using centralized format
- Signature must be from
fromparameter - Don't bypass or skip signature validation
-
Balance Checks:
- User must have sufficient balance in Core.sol for
requestPay() - Service must have sufficient balance for
makeCaPay() - Check balances before attempting payments
- User must have sufficient balance in Core.sol for
-
Contract Authorization:
makeCaPay()only works from contract addresses- Core.sol uses
CAUtils.verifyIfCA()to verify caller - EOAs cannot use contract-authorized payments
-
Batch Payment Validation:
- Total amount must EXACTLY match sum of individual amounts
- Mismatch causes transaction revert
- Validate before calling
requestDispersePay()ormakeDisperseCaPay()
Related Components
- Core.sol - Main payment contract
- EvvmService - Includes CoreExecution functionality
- SignatureUtil - For manual signature verification
- CoreHashUtils - For generating payment hashes
Recommendation: Use CoreExecution when building services that need Core.sol payment integration without the full EvvmService stack.