Skip to main content

How to Create an EVVM Service

Learn how to build and deploy your own smart contract service within the EVVM ecosystem. EVVM services are smart contracts that connect to the core EVVM infrastructure and other services, enabling you to leverage existing payment systems, staking rewards, name resolution, and more.

What is an EVVM Service?

An EVVM Service is a smart contract deployed within an EVVM instance that integrates with the ecosystem's core infrastructure. Following EVVM's "Build Within an EVVM" approach, these smart contracts can leverage existing services and infrastructure while providing specialized functionality.

Smart Contract Integration Model

EVVM services are smart contracts that:

  • Connect to EVVM Core: Interface with the central payment processing system
  • Integrate with ecosystem services: Access Staking, Name Service, and other deployed contracts
  • Operate within the virtual blockchain: Deployed in the same EVVM instance as other services
  • Follow EVVM standards: Use ERC-191 signatures and established nonce patterns
  • Benefit from fisher execution: Stakers execute your functions and earn rewards
  • Enable gasless experiences: Users can interact without paying gas fees

EVVM Ecosystem Architecture

EVVM Instance (Your Virtual Blockchain)
├── EVVM Core Contract (payment processor)
├── Staking Service Contract
├── Name Service Contract
├── Your Custom Service Contract ←── What you'll create
└── Other Service Contracts

Your smart contract can connect to:

  • EVVM Core: Central payment processing using token abstractions and ERC-191 signatures
  • Staking Contract: Reward earning, fisher coordination, and governance participation
  • Name Service: Human-readable identity resolution and metadata management
  • Other Services: Any custom smart contracts deployed in the same EVVM instance

Why Build EVVM Services?

Creating services within EVVM provides unique advantages aligned with EVVM's core value propositions:

Why Build EVVM Services?

Creating services within EVVM provides unique advantages aligned with EVVM's core value propositions:

  • Abstract Blockchain Benefits: Deploy on virtual blockchain infrastructure without managing physical nodes
  • Existing Infrastructure: Leverage pre-built payment systems, identity services, and staking mechanisms
  • Gasless Communication: Enable users to interact through fishing spots without gas fees
  • Fisher Network: Tap into the staker ecosystem for transaction execution and validation
  • Token Abstractions: Use internal token representations instead of managing separate ERC-20 contracts
  • Instant Deployment: Quick setup within existing EVVM instances without complex infrastructure

Real-World Example: EVVM Name Service

The EVVM Name Service perfectly demonstrates how a smart contract integrates within the EVVM ecosystem. This contract shows how to leverage EVVM's infrastructure while providing specialized functionality.

Name Service: A Model EVVM Service

The Name Service contract demonstrates:

  • EVVM Core Integration: Uses token abstractions and signature-based payments
  • Fisher Coordination: Rewards stakers who execute username registrations
  • Service Interconnection: Other contracts can resolve usernames through direct calls
  • Economic Integration: Dynamic pricing based on EVVM's reward mechanisms

Service Architecture Overview

The Name Service is a smart contract that provides:

  • Username Registration: Secure identity management with anti-front-running protection
  • Custom Metadata System: Extensible metadata following EVVM patterns
  • Username Marketplace: Trading system with integrated fee mechanisms
  • Dynamic Pricing: Market-based pricing using EVVM's reward structures
  • Time-Delayed Governance: Secure administrative controls
/**
* @title EVVM Name Service Contract
* @notice Smart contract providing human-readable identity services within EVVM
* @dev Demonstrates proper EVVM service integration patterns
*
* EVVM Integration Features:
* - Token abstraction usage for payments (no separate ERC-20 contracts)
* - ERC-191 signature validation for all operations
* - Fisher reward distribution for transaction execution
* - Dual nonce system (service + EVVM nonces)
* - Integration with EVVM's staking and reward systems
*
* Service Features:
* - Anti-front-running username registration (commit-reveal)
* - Custom metadata with schema-based validation
* - Username marketplace with integrated economics
* - Time-delayed governance for security
*
* Ecosystem Connections:
* - EVVM Core: Payment processing and reward distribution
* - Staking System: Fisher coordination and reward earning
* - Other Services: Username resolution for external contracts
*/
contract NameService {
// Example of proper EVVM service implementation
}

How the Name Service Demonstrates EVVM Patterns

  1. Token Abstraction Integration: Uses EVVM's internal token system instead of separate contracts
  2. ERC-191 Signature Standards: Implements EVVM's signature validation patterns
  3. Dual Nonce Management: Manages service nonces while EVVM handles payment nonces
  4. Fisher Reward System: Automatically distributes rewards to stakers who execute functions
  5. Service Interconnection: Provides functions that other contracts can call directly
  6. Virtual Blockchain Benefits: Operates without gas fees through fishing spot communication

Core Requirements for Smart Contract Integration

To create a smart contract that connects to EVVM and other services, you need to implement:

1. Dual Signature Structure (ERC-191)

EVVM services require two separate signatures for each operation:

  1. Service Signature: Validates the service operation and its parameters
  2. EVVM Payment Signature: Authorizes the payment execution within the EVVM contract

This dual signature approach ensures both service operation authorization and payment security. Let's examine the Name Service implementation to understand this pattern.

Required Dependencies

First, import the EVVM signature validation libraries:

import {SignatureRecover} from "@EVVM/testnet/lib/SignatureRecover.sol";
import {AdvancedStrings} from "@EVVM/testnet/lib/AdvancedStrings.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

You can download the SignatureRecover library from: EVVM Testnet Contracts

Name Service Dual Signature Example

The Name Service's preRegistrationUsername function perfectly demonstrates the dual signature pattern:

function preRegistrationUsername(
address user,
bytes32 hashPreRegisteredUsername,
uint256 nonce, // Service nonce
bytes memory signature, // Service signature
uint256 priorityFee_EVVM, // EVVM priority fee
uint256 nonce_EVVM, // EVVM nonce
bool priorityFlag_EVVM, // EVVM async/sync flag
bytes memory signature_EVVM // EVVM payment signature
) public verifyIfNonceIsAvailable(user, nonce) {
// 1. Validate service signature
require(
SignatureUtils.verifyMessageSignedForPreRegistrationUsername(
user,
hashPreRegisteredUsername,
nonce,
signature
),
"Invalid service signature"
);

// 2. Process payment with EVVM signature
makePay(
user,
getPricePerRegistration(),
priorityFee_EVVM,
nonce_EVVM,
priorityFlag_EVVM,
signature_EVVM
);

// 3. Execute service logic
// ... pre-registration logic
}

Understanding the Two Signatures

Service Signature (signature):

  • Validates user authorization for the specific service operation
  • Includes service-specific parameters (hashPreRegisteredUsername, nonce)
  • Uses service-specific function identifier ("5d232a55" for pre-registration)
  • Prevents unauthorized service operations

EVVM Payment Signature (signature_EVVM):

  • Authorizes the payment execution within the EVVM contract
  • Includes payment parameters (amount, priorityFee_EVVM, nonce_EVVM)
  • Uses EVVM's internal signature validation
  • Prevents unauthorized payment transactions

EVVM Standard Service Signature Validation

Create a library for your service signature validation following EVVM patterns. The Name Service demonstrates this with its SignatureUtils library:

library SignatureUtils {
/**
* @dev using EIP-191 (https://eips.ethereum.org/EIPS/eip-191) can be used to sign and
* verify messages, the next functions are used to verify the messages signed
* by the users for SERVICE OPERATIONS (not EVVM payments)
*/

function verifyMessageSignedForPreRegistrationUsername(
address signer,
bytes32 _hashUsername,
uint256 _nameServiceNonce,
bytes memory signature
) internal pure returns (bool) {
return
SignatureRecover.signatureVerification(
string.concat(
"5d232a55", // Function identifier
",",
AdvancedStrings.bytes32ToString(_hashUsername),
",",
Strings.toString(_nameServiceNonce)
),
signature,
signer
);
}

function verifyMessageSignedForRegistrationUsername(
address signer,
string memory _username,
uint256 _clowNumber,
uint256 _nameServiceNonce,
bytes memory signature
) internal pure returns (bool) {
return
SignatureRecover.signatureVerification(
string.concat(
"afabc8db", // Unique function identifier
",",
_username,
",",
Strings.toString(_clowNumber),
",",
Strings.toString(_nameServiceNonce)
),
signature,
signer
);
}

// Additional signature validation functions for all service operations...
}

Create Your Service Dual Signature System

Following the Name Service pattern, implement both signature validations in your service:

library MyServiceSignatureUtils {
// Function identifier for your service operation (generate unique 8-character hex)
string constant SERVICE_FUNCTION_ID = "a1b2c3d4"; // Replace with your unique ID

/**
* @dev Validates SERVICE signature (not EVVM payment signature)
* @param signer Expected signer address
* @param data Service-specific data
* @param nonce Service nonce for replay protection
* @param signature User's SERVICE signature
*/
function verifyServiceOperationSignature(
address signer,
string memory data,
uint256 nonce,
bytes memory signature
) internal pure returns (bool) {
return SignatureRecover.signatureVerification(
string.concat(
SERVICE_FUNCTION_ID,
",",
Strings.toHexString(uint256(uint160(signer)), 20),
",",
data,
",",
Strings.toString(nonce)
),
signature,
signer
);
}
}

contract MyEVVMService {
using MyServiceSignatureUtils for *;

/**
* @notice Execute service with dual signature validation
* @param user User address
* @param data Service-specific data
* @param nonce Service nonce
* @param signature SERVICE signature (for service operation)
* @param priorityFee_EVVM EVVM priority fee
* @param nonce_EVVM EVVM nonce
* @param priorityFlag_EVVM EVVM async/sync flag
* @param signature_EVVM EVVM payment signature (for payment authorization)
*/
function executeService(
address user,
string memory data,
uint256 nonce,
bytes memory signature, // SERVICE signature
uint256 priorityFee_EVVM,
uint256 nonce_EVVM,
bool priorityFlag_EVVM,
bytes memory signature_EVVM // EVVM payment signature
) external verifyIfNonceIsAvailable(user, nonce) {
// 1. Validate SERVICE signature
require(
MyServiceSignatureUtils.verifyServiceOperationSignature(
user,
data,
nonce,
signature
),
"Invalid service signature"
);

// 2. Process payment with EVVM signature
makeServicePayment(
user,
PRINCIPAL_TOKEN_ADDRESS, // Token for payment
SERVICE_FEE, // Service fee amount
priorityFee_EVVM, // Priority fee
priorityFlag_EVVM, // Async/sync flag
nonce_EVVM, // EVVM nonce
signature_EVVM // EVVM payment signature
);

// 3. Execute service logic
_performServiceLogic(user, data);

// 4. Mark service nonce as used
serviceNonce[user][nonce] = true;
}
}
Signature Documentation

For detailed signature implementation patterns, see Signature Structures.

2. Dual Nonce Management System

EVVM services require two separate nonce systems:

  1. Service Nonces: For service operation replay protection
  2. EVVM Nonces: For EVVM payment replay protection

The Name Service demonstrates this dual nonce pattern perfectly.

Name Service Dual Nonce Implementation

contract NameService {
/// @dev SERVICE nonce mapping - tracks used nonces per address for service operations
mapping(address => mapping(uint256 => bool)) private nameServiceNonce;

/// @dev Ensures the SERVICE nonce hasn't been used before
modifier verifyIfNonceIsAvailable(address _user, uint256 _nonce) {
if (nameServiceNonce[_user][_nonce])
revert ErrorsLib.NonceAlreadyUsed();
_;
}

function preRegistrationUsername(
address user,
bytes32 hashPreRegisteredUsername,
uint256 nonce, // SERVICE nonce
bytes memory signature, // SERVICE signature
uint256 priorityFee_EVVM,
uint256 nonce_EVVM, // EVVM nonce (handled by EVVM contract)
bool priorityFlag_EVVM,
bytes memory signature_EVVM // EVVM signature (validated by EVVM contract)
) public verifyIfNonceIsAvailable(user, nonce) {
// Validate SERVICE signature with SERVICE nonce
require(
SignatureUtils.verifyMessageSignedForPreRegistrationUsername(
user,
hashPreRegisteredUsername,
nonce, // SERVICE nonce used in service signature
signature // SERVICE signature
),
"Invalid service signature"
);

// EVVM handles its own nonce validation when processing payment
makePay(
user,
getPricePerRegistration(),
priorityFee_EVVM,
nonce_EVVM, // EVVM nonce - validated by EVVM contract
priorityFlag_EVVM,
signature_EVVM // EVVM signature - validated by EVVM contract
);

// Mark SERVICE nonce as used
nameServiceNonce[user][nonce] = true;
}

/**
* @notice Checks if a SERVICE nonce has been used by a specific user
* @dev Prevents replay attacks by tracking used service nonces per user
* @param _user Address of the user to check
* @param _nonce Service nonce value to verify
* @return True if the nonce has been used, false if still available
*/
function checkIfNameServiceNonceIsAvailable(
address _user,
uint256 _nonce
) public view returns (bool) {
return nameServiceNonce[_user][_nonce];
}
}

Key Points about Dual Nonces:

  • Service Nonce: Managed by your service contract, prevents service operation replay
  • EVVM Nonce: Managed by EVVM contract, prevents payment replay
  • Independent Systems: Each nonce system operates independently
  • Different Validation: Service validates service nonce, EVVM validates EVVM nonce

Implement Your Service Nonce System

Choose between synchronous or asynchronous nonce types based on your service needs.

Synchronous Nonces (Sequential)

mapping(address => uint256) public userNonces;

function validateSyncNonce(address user, uint256 nonce) internal returns (bool) {
if (nonce != userNonces[user] + 1) {
return false;
}
userNonces[user] = nonce;
return true;
}

Asynchronous Nonces (Non-Sequential)

mapping(address => mapping(uint256 => bool)) public usedNonces;

function validateAsyncNonce(address user, uint256 nonce) internal returns (bool) {
if (usedNonces[user][nonce]) {
return false;
}
usedNonces[user][nonce] = true;
return true;
}

The Name Service uses asynchronous nonces with modifier-based validation:

contract MyEVVMService {
/// @dev Mapping to track used nonces per address to prevent replay attacks
mapping(address => mapping(uint256 => bool)) private serviceNonce;

/// @dev Ensures the nonce hasn't been used before to prevent replay attacks
modifier verifyIfNonceIsAvailable(address _user, uint256 _nonce) {
if (serviceNonce[_user][_nonce])
revert("Nonce already used");
_;
}

function executeService(
address user,
uint256 nonce,
bytes memory signature
// ... other parameters
) public verifyIfNonceIsAvailable(user, nonce) {
// Validate signature first
// Process payment through EVVM
// Execute service logic

// Mark nonce as used at the end
serviceNonce[user][nonce] = true;
}

/**
* @notice Checks if a nonce has been used by a specific user
* @dev Public function to check nonce availability before submitting transactions
* @param _user Address of the user to check
* @param _nonce Nonce value to verify
* @return True if the nonce has been used, false if still available
*/
function checkIfServiceNonceIsAvailable(
address _user,
uint256 _nonce
) public view returns (bool) {
return serviceNonce[_user][_nonce];
}
}

**Important**: Remember that SERVICE nonces and EVVM nonces are completely independent. Your service validates SERVICE nonces, while EVVM validates EVVM nonces internally during payment processing.

:::info[Nonce Types Documentation]
Learn more about nonce implementation patterns in [Nonce Types](./04-EVVM/02-NonceTypes.md).
:::

### 3. EVVM Contract Integration

Connect your smart contract to the EVVM core contract and other services in the ecosystem. The Name Service provides an excellent example of how a smart contract integrates with multiple services.

#### EVVM Contract Import

Import the EVVM contract directly to connect your smart contract:

```solidity
import {Evvm} from "@EVVM/testnet/contracts/evvm/Evvm.sol";

Name Service Smart Contract Integration Pattern

The Name Service demonstrates how a smart contract connects to EVVM:

contract NameService {
/// @dev Constant address representing the principal token in the EVVM ecosystem
address private constant PRINCIPAL_TOKEN_ADDRESS =
0x0000000000000000000000000000000000000001;

/**
* @notice Internal function to handle payments through the EVVM contract
* @dev Supports both synchronous and asynchronous payment modes
* @param user Address making the payment
* @param ammount Amount to pay in principal tokens
* @param priorityFee Additional priority fee for faster processing
* @param nonce Nonce for the EVVM transaction
* @param priorityFlag True for async payment, false for sync payment
* @param signature Signature authorizing the payment
*/
function makePay(
address user,
uint256 ammount,
uint256 priorityFee,
uint256 nonce,
bool priorityFlag,
bytes memory signature
) internal {
if (priorityFlag) {
Evvm(evvmAddress.current).payStaker_async(
user,
address(this),
"",
PRINCIPAL_TOKEN_ADDRESS,
ammount,
priorityFee,
nonce,
address(this),
signature
);
} else {
Evvm(evvmAddress.current).payStaker_sync(
user,
address(this),
"",
PRINCIPAL_TOKEN_ADDRESS,
ammount,
priorityFee,
address(this),
signature
);
}
}

/**
* @notice Internal function to distribute MATE tokens to users
* @dev Calls the EVVM contract's caPay function for token distribution
* @param user Address to receive the tokens
* @param amount Amount of MATE tokens to distribute
*/
function makeCaPay(address user, uint256 amount) internal {
Evvm(evvmAddress.current).caPay(user, PRINCIPAL_TOKEN_ADDRESS, amount);
}
}

Staker Reward Distribution

Notice how the Name Service rewards stakers who execute transactions:

// Example from Name Service registration function
function registrationUsername(
// ... parameters
) public verifyIfNonceIsAvailable(user, nonce) {
// Validate signature and process payment
makePay(user, getPricePerRegistration(), priorityFee_EVVM, nonce_EVVM, priorityFlag_EVVM, signature_EVVM);

// Register the username
identityDetails[username] = IdentityBaseMetadata({
owner: user,
expireDate: block.timestamp + 366 days,
customMetadataMaxSlots: 0,
offerMaxSlots: 0,
flagNotAUsername: 0x00
});

nameServiceNonce[user][nonce] = true;

// Reward stakers automatically - this is the key pattern!
if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
makeCaPay(
msg.sender,
(50 * Evvm(evvmAddress.current).getRewardAmount()) + priorityFee_EVVM
);
}
}

Your Smart Contract Implementation with EVVM Integration

contract MyEVVMService {
address public immutable EVVM_ADDRESS;
address public immutable STAKING_CONTRACT_ADDRESS;
address public immutable NAME_SERVICE_ADDRESS;
address public immutable PRINCIPAL_TOKEN_ADDRESS;

constructor(
address _evvm,
address _staking,
address _nameService,
address _principalToken
) {
EVVM_ADDRESS = _evvm;
STAKING_CONTRACT_ADDRESS = _staking;
NAME_SERVICE_ADDRESS = _nameService;
PRINCIPAL_TOKEN_ADDRESS = _principalToken;
}

/**
* @notice Connect to EVVM for payment processing
* @dev Your smart contract calls EVVM contract functions directly
*/
function makeServicePayment(
address user,
address tokenAddress,
uint256 amount,
uint256 priorityFee,
bool useAsyncNonce,
uint256 nonce,
bytes memory signature
) internal {
if (useAsyncNonce) {
// Direct contract call to EVVM
Evvm(EVVM_ADDRESS).payStaker_async(
user, // from
address(this), // to_address (this smart contract)
"", // to_identity (empty for direct address)
tokenAddress, // token (any supported token)
amount, // amount
priorityFee, // priorityFee (paid in same token)
nonce, // nonce
address(this), // executor (this smart contract)
signature // signature
);
} else {
// Direct contract call to EVVM
Evvm(EVVM_ADDRESS).payStaker_sync(
user, // from
address(this), // to_address (this smart contract)
"", // to_identity (empty for direct address)
tokenAddress, // token (any supported token)
amount, // amount
priorityFee, // priorityFee (paid in same token)
address(this), // executor (this smart contract)
signature // signature
);
}
}

/**
* @notice Connect to other services in the same EVVM
* @dev Example: Use Name Service from your smart contract
*/
function executeServiceByUsername(
string memory username,
string memory data,
uint256 nonce,
bytes memory signature,
uint256 priorityFee_EVVM,
uint256 nonce_EVVM,
bool priorityFlag_EVVM,
bytes memory signature_EVVM
) external {
// Direct contract call to Name Service
(bool success, bytes memory result) = NAME_SERVICE_ADDRESS.call(
abi.encodeWithSignature(
"verifyStrictAndGetOwnerOfIdentity(string)",
username
)
);
require(success, "Name Service connection failed");

address userAddress = abi.decode(result, (address));

// Continue with your service logic
executeService(userAddress, data, nonce, signature, priorityFee_EVVM, nonce_EVVM, priorityFlag_EVVM, signature_EVVM);
}

/**
* @notice Connect to Staking contract for service staking
* @dev Your smart contract can stake tokens to earn rewards
*/
function stakeForService(
uint256 stakingAmount,
uint256 nonce,
bytes memory signature,
uint256 priorityFee_EVVM,
uint256 nonce_EVVM,
bool priorityFlag_EVVM,
bytes memory signature_EVVM
) external {
// Direct contract call to Staking contract
(bool success, ) = STAKING_CONTRACT_ADDRESS.call(
abi.encodeWithSignature(
"publicServiceStaking(address,bool,uint256,uint256,bytes,uint256,uint256,bool,bytes)",
address(this), // This smart contract stakes for itself
true, // isStaking = true
stakingAmount, // Amount to stake
nonce, // Service nonce
signature, // Service signature
priorityFee_EVVM, // EVVM priority fee
nonce_EVVM, // EVVM nonce
priorityFlag_EVVM, // EVVM priority flag
signature_EVVM // EVVM signature
)
);
require(success, "Staking contract connection failed");
}
}
contract MyEVVMService {
address public immutable EVVM_ADDRESS;
address public immutable PRINCIPAL_TOKEN_ADDRESS;
address public immutable serviceAddress;

constructor(address _evvm, address _principalToken) {
EVVM_ADDRESS = _evvm;
PRINCIPAL_TOKEN_ADDRESS = _principalToken;
serviceAddress = address(this);
}

/**
* @notice Internal function to handle service payments through EVVM contract
* @dev Supports both synchronous and asynchronous payment modes for stakers
* @param user Address of the user making the payment
* @param tokenAddress Address of the token to be used for payment (can be any token supported by EVVM)
* @param amount Amount to be paid in the specified token
* @param priorityFee Additional priority fee for the transaction (paid in the same token)
* @param useAsyncNonce True for async payment, false for sync payment
* @param nonce Nonce for async EVVM transactions (ignored for sync)
* @param signature Signature authorizing the payment
* @dev When using payStaker functions, users automatically receive staking rewards in the principal token
*/
function makeServicePayment(
address user,
address tokenAddress,
uint256 amount,
uint256 priorityFee,
bool useAsyncNonce,
uint256 nonce,
bytes memory signature
) internal {
if (useAsyncNonce) {
Evvm(EVVM_ADDRESS).payStaker_async(
user, // from
serviceAddress, // to_address
"", // to_identity (empty for direct address)
tokenAddress, // token (any supported token, not necessarily principal)
amount, // amount
priorityFee, // priorityFee (paid in same token as amount)
nonce, // nonce
address(this), // executor (service contract)
signature // signature
);
// Note: User automatically receives _giveReward(msg.sender, 1) in principal token
} else {
Evvm(EVVM_ADDRESS).payStaker_sync(
user, // from
serviceAddress, // to_address
"", // to_identity (empty for direct address)
tokenAddress, // token (any supported token, not necessarily principal)
amount, // amount
priorityFee, // priorityFee (paid in same token as amount)
address(this), // executor (service contract)
signature // signature
);
// Note: User automatically receives _giveReward(msg.sender, 1) in principal token
}
}

/**
* @notice Internal function to handle non-staker payments through EVVM contract
* @dev Used when the service wants to accept payments from non-stakers (no staking rewards)
* @param user Address of the user making the payment
* @param tokenAddress Address of the token to be used for payment
* @param amount Amount to be paid in the specified token
* @param priorityFee Additional priority fee for the transaction (paid in the same token)
* @param useAsyncNonce True for async payment, false for sync payment
* @param nonce Nonce for async EVVM transactions (ignored for sync)
* @param signature Signature authorizing the payment
* @dev Non-staker payments do not receive automatic staking rewards
*/
function makeNonStakerPayment(
address user,
address tokenAddress,
uint256 amount,
uint256 priorityFee,
bool useAsyncNonce,
uint256 nonce,
bytes memory signature
) internal {
if (useAsyncNonce) {
Evvm(EVVM_ADDRESS).payNoStaker_async(
user, // from
serviceAddress, // to_address
"", // to_identity (empty for direct address)
tokenAddress, // token (any supported token)
amount, // amount
priorityFee, // priorityFee (paid in same token as amount)
nonce, // nonce
address(this), // executor (service contract)
signature // signature
);
} else {
Evvm(EVVM_ADDRESS).payNoStaker_sync(
user, // from
serviceAddress, // to_address
"", // to_identity (empty for direct address)
tokenAddress, // token (any supported token)
amount, // amount
priorityFee, // priorityFee (paid in same token as amount)
address(this), // executor (service contract)
signature // signature
);
}
}

/**
* @notice Internal function to distribute rewards or refunds through EVVM contract
* @dev Used for service reward distributions or refunds to users
* @param tokenAddress Address of the token to be distributed
* @param user Address of the recipient
* @param amount Amount of tokens to distribute
*/
function makeServiceDistribution(
address tokenAddress,
address user,
uint256 amount
) internal {
Evvm(EVVM_ADDRESS).caPay(user, tokenAddress, amount);
}

function executeService(
ServiceSignature memory sig,
bytes memory userSignature
) external {
// 1. Validate user signature using EVVM standard
require(
MyServiceSignatureUtils.verifyServiceOperationSignature(
sig.user,
sig.data,
sig.tokenAddress,
sig.nonce,
userSignature
),
"Invalid service signature"
);

// 2. Validate and update nonce
require(validateNonce(sig.user, sig.nonce), "Invalid nonce");

// 3. Process payment through EVVM - Use appropriate payment method
makeServicePayment(
sig.user,
sig.tokenAddress, // Use the token specified in signature
SERVICE_FEE,
sig.priorityFee_EVVM,
sig.useAsyncNonce,
sig.nonce_EVVM,
sig.signature_EVVM
);

// 4. Execute your service logic
_performServiceLogic(sig);

// 5. Reward stakers like Name Service does
if (Evvm(EVVM_ADDRESS).isAddressStaker(msg.sender)) {
makeServiceDistribution(
PRINCIPAL_TOKEN_ADDRESS,
msg.sender,
Evvm(EVVM_ADDRESS).getRewardAmount() + sig.priorityFee_EVVM
);
}

// 6. Optional: Distribute rewards back to user if applicable
if (shouldDistributeRewards(sig)) {
uint256 rewardAmount = calculateReward(sig);
makeServiceDistribution(
sig.tokenAddress,
sig.user,
rewardAmount
);
}
}
}
Smart Contract Integration

For complete details on connecting your smart contract to EVVM, see EVVM Core Contract.

Fisher Incentive Model

EVVM's Fisher Network consists of stakers who execute smart contract functions, enabling the ecosystem's gasless communication and transaction processing. Understanding how to incentivize fishers is crucial for creating successful EVVM services.

EVVM's Gasless Communication Architecture

Following EVVM's core principle of gasless communication, services can enable users to interact without traditional gas fees by:

  1. Leveraging Fisher Execution: Stakers execute transactions and earn rewards
  2. Implementing Zero-Amount Payments: Users pay only optional priority fees
  3. Service Staking: Smart contracts stake tokens to earn rewards that cover operation costs
  4. Fishing Spot Integration: Users can submit transactions through various communication channels

Fisher Reward Distribution Pattern

The staking contract demonstrates EVVM's proven fisher incentive model:

function stakingBaseProcess(
address userAccount,
address stakingAccount,
bool isStaking,
uint256 amountOfStaking,
uint256 priorityFee_EVVM,
uint256 nonce_EVVM,
bool priorityFlag_EVVM,
bytes memory signature_EVVM
) internal {
// ... staking logic ...

if (!isStaking) { // Unstaking operations
// User only pays priority fee if they choose to (amount = 0)
if (userAccount == stakingAccount && priorityFee_EVVM != 0) {
makePay(
userAccount,
priorityFee_EVVM, // Only priority fee as amount
0, // No additional priority fee
priorityFlag_EVVM,
nonce_EVVM,
signature_EVVM
);
}

// Service handles the main operation cost
makeCaPay(
PRINCIPAL_TOKEN_ADDRESS,
stakingAccount,
(PRICE_OF_STAKING * amountOfStaking) // Service pays from its reserves
);
}

// Reward fishers who execute the transaction
if (Evvm(EVVM_ADDRESS).isAddressStaker(msg.sender)) {
makeCaPay(
PRINCIPAL_TOKEN_ADDRESS,
msg.sender,
(Evvm(EVVM_ADDRESS).getRewardAmount() * 2) + priorityFee_EVVM
);
}
}

Key Pattern Elements

  1. Optional Priority Fee: Users can choose to pay priority fees (priorityFee_EVVM != 0)
  2. Zero Main Amount: Service operations can use amount: 0 for gasless transactions
  3. Service Staking: Service stakes tokens to earn rewards that cover operation costs
  4. Fisher Rewards: Fishers receive getRewardAmount() + priorityFee in principal token
  5. Principal Token Only: All rewards distributed in principal token for consistency

Implementing Fisher Incentives in Your Service

contract MyEVVMService {
address public immutable EVVM_ADDRESS;
address public immutable PRINCIPAL_TOKEN_ADDRESS;
uint256 public serviceStakeReserve; // Service's staked amount for covering costs

/**
* @notice Execute service operation with fisher incentive model
* @param user User address
* @param data Service-specific data
* @param nonce Service nonce
* @param signature SERVICE signature
* @param priorityFee_EVVM Optional priority fee (can be 0)
* @param nonce_EVVM EVVM nonce
* @param priorityFlag_EVVM EVVM async/sync flag
* @param signature_EVVM EVVM payment signature
*/
function executeServiceWithIncentives(
address user,
string memory data,
uint256 nonce,
bytes memory signature,
uint256 priorityFee_EVVM, // User can set to 0 for gasless
uint256 nonce_EVVM,
bool priorityFlag_EVVM,
bytes memory signature_EVVM
) external verifyIfNonceIsAvailable(user, nonce) {
// 1. Validate SERVICE signature
require(
MyServiceSignatureUtils.verifyServiceOperationSignature(
user,
data,
nonce,
signature
),
"Invalid service signature"
);

// 2. Optional priority fee payment (gasless if priorityFee_EVVM = 0)
if (priorityFee_EVVM > 0) {
makeServicePayment(
user,
PRINCIPAL_TOKEN_ADDRESS,
priorityFee_EVVM, // Only priority fee as amount
0, // No additional priority fee
priorityFlag_EVVM,
nonce_EVVM,
signature_EVVM
);
}

// 3. Execute service logic (service covers costs from staking rewards)
_performServiceLogic(user, data);

// 4. Mark SERVICE nonce as used
serviceNonce[user][nonce] = true;

// 5. CRITICAL: Reward fishers to incentivize execution
if (Evvm(EVVM_ADDRESS).isAddressStaker(msg.sender)) {
uint256 fisherReward = Evvm(EVVM_ADDRESS).getRewardAmount() + priorityFee_EVVM;

makeServiceDistribution(
PRINCIPAL_TOKEN_ADDRESS,
msg.sender,
fisherReward
);
}
}

/**
* @notice Service stakes tokens to earn rewards for covering operation costs
* @param stakingAmount Amount to stake for the service
*/
function stakeForServiceOperations(
uint256 stakingAmount,
uint256 nonce,
bytes memory signature,
uint256 priorityFee_EVVM,
uint256 nonce_EVVM,
bool priorityFlag_EVVM,
bytes memory signature_EVVM
) external onlyServiceAdmin {
// Service stakes tokens to earn rewards
(bool success, ) = stakingContractAddress.call(
abi.encodeWithSignature(
"publicServiceStaking(address,bool,uint256,uint256,bytes,uint256,uint256,bool,bytes)",
address(this), // Service stakes for itself
true, // isStaking = true
stakingAmount, // Amount to stake
nonce, // Service nonce
signature, // Service signature
priorityFee_EVVM, // EVVM priority fee
nonce_EVVM, // EVVM nonce
priorityFlag_EVVM, // EVVM priority flag
signature_EVVM // EVVM signature
)
);
require(success, "Service staking failed");

serviceStakeReserve += stakingAmount;
}
}

Benefits of Fisher Incentive Model

  1. Gasless User Experience: Users can use services without paying main fees
  2. Fisher Motivation: Fishers earn rewards for executing transactions
  3. Service Sustainability: Service earns from staking to cover operation costs
  4. Network Security: Encourages staker participation in transaction execution
  5. Ecosystem Growth: Reduces barriers to service adoption

1. Service Staking for Rewards

Your service can participate in the staking system to earn execution rewards that can be distributed to service operators or users.

Understanding EVVM Token Economics

  • Multi-Token Support: Services can accept payments in any token supported by the EVVM, not just the principal token
  • Payment Token: The tokenAddress parameter specifies which token to use for both the payment amount and priority fee
  • Staker Rewards: When using payStaker_* functions, users automatically receive _giveReward(msg.sender, 1) in the principal token
  • Priority Fees: Paid in the same token as the transaction amount, distributed to stakers and service operators
  • Non-Staker Payments: Use payNoStaker_* functions for users who don't receive staking rewards

Name Service Economic Model

The Name Service demonstrates sophisticated economic design:

// Dynamic pricing based on EVVM rewards and market demand
function getPricePerRegistration() public view returns (uint256) {
return Evvm(evvmAddress.current).getRewardAmount() * 100;
}

function getPriceToAddCustomMetadata() public view returns (uint256 price) {
price = 10 * Evvm(evvmAddress.current).getRewardAmount();
}

// Market-driven renewal pricing
function seePriceToRenew(string memory _identity) public view returns (uint256 price) {
if (identityDetails[_identity].expireDate >= block.timestamp) {
// Check for active offers to determine market value
for (uint256 i = 0; i < identityDetails[_identity].offerMaxSlots; i++) {
if (usernameOffers[_identity][i].expireDate > block.timestamp &&
usernameOffers[_identity][i].offerer != address(0)) {
if (usernameOffers[_identity][i].amount > price) {
price = usernameOffers[_identity][i].amount;
}
}
}
if (price == 0) {
price = 500 * 10 ** 18; // Base price
} else {
uint256 mateReward = Evvm(evvmAddress.current).getRewardAmount();
price = ((price * 5) / 1000) > (500000 * mateReward)
? (500000 * mateReward)
: ((price * 5) / 1000);
}
} else {
price = 500_000 * Evvm(evvmAddress.current).getRewardAmount();
}
}

Name Service Staker Reward Distribution

Notice how the Name Service automatically rewards stakers:

// From makeOffer function
if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
makeCaPay(
msg.sender,
Evvm(evvmAddress.current).getRewardAmount() +
((amount * 125) / 100_000) + // 0.125% of offer amount
priorityFee_EVVM
);
}

// From registrationUsername function
if (Evvm(evvmAddress.current).isAddressStaker(msg.sender)) {
makeCaPay(
msg.sender,
(50 * Evvm(evvmAddress.current).getRewardAmount()) +
priorityFee_EVVM
);
}

Staking Integration

contract MyEVVMService {
address public immutable stakingContractAddress;

function stakeForService(
uint256 stakingAmount,
uint256 nonce,
bytes memory signature,
uint256 priorityFee_EVVM,
uint256 nonce_EVVM,
bool priorityFlag_EVVM,
bytes memory signature_EVVM
) external {
// Direct call to staking contract
(bool success, ) = stakingContractAddress.call(
abi.encodeWithSignature(
"publicServiceStaking(address,bool,uint256,uint256,bytes,uint256,uint256,bool,bytes)",
address(this), // Service stakes on its own behalf
true, // isStaking = true
stakingAmount, // Amount to stake
nonce, // Service nonce
signature, // Service signature
priorityFee_EVVM, // EVVM priority fee
nonce_EVVM, // EVVM nonce
priorityFlag_EVVM, // EVVM priority flag
signature_EVVM // EVVM signature
)
);
require(success, "Staking failed");
}
}
Service Staking

Services can earn rewards through staking. See Public Service Staking for details.

2. Priority Fee Collection

Services can collect priority fees from EVVM transactions and decide how to distribute them. Priority fees are always paid in the same token as the transaction amount.

Fee Distribution Strategies

contract MyEVVMService {
mapping(address => uint256) public operatorRewards;
uint256 public serviceReserve;

function distributePriorityFees(uint256 totalFees) internal {
// Strategy 1: Keep all fees for service development
serviceReserve += totalFees;

// Strategy 2: Share with service operators
uint256 operatorShare = totalFees * 70 / 100; // 70% to operators
uint256 serviceShare = totalFees * 30 / 100; // 30% to service

operatorRewards[msg.sender] += operatorShare;
serviceReserve += serviceShare;

// Strategy 3: Pass through to users (gasless transactions)
// No fee collection - provide free service
}

function choosePaymentMethod(
address user,
address tokenAddress,
uint256 amount,
uint256 priorityFee,
bool isStaker,
bool useAsyncNonce,
uint256 nonce,
bytes memory signature
) internal {
if (isStaker) {
// Use payStaker functions - user gets automatic staking rewards in principal token
makeServicePayment(user, tokenAddress, amount, priorityFee, useAsyncNonce, nonce, signature);
} else {
// Use payNoStaker functions - no automatic staking rewards
makeNonStakerPayment(user, tokenAddress, amount, priorityFee, useAsyncNonce, nonce, signature);
}
}
}

Service Design Patterns from Name Service

The EVVM Name Service demonstrates several key design patterns that every EVVM service should implement:

1. Time-Delayed Governance

The Name Service implements time-delayed administrative functions for security:

/**
* @dev Struct for managing address change proposals with time delay
* @param current Currently active address
* @param proposal Proposed new address waiting for approval
* @param timeToAccept Timestamp when the proposal can be accepted
*/
struct AddressTypeProposal {
address current;
address proposal;
uint256 timeToAccept;
}

// Example: Admin change with 1-day delay
function proposeAdmin(address _adminToPropose) public onlyAdmin {
admin.proposal = _adminToPropose;
admin.timeToAccept = block.timestamp + 1 days;
}

function acceptProposeAdmin() public {
if (admin.proposal != msg.sender) revert();
if (block.timestamp < admin.timeToAccept) revert();

admin = AddressTypeProposal({
current: admin.proposal,
proposal: address(0),
timeToAccept: 0
});
}

2. Comprehensive Access Control

Multiple layers of security and ownership verification:

/// @dev Restricts function access to the current admin address only
modifier onlyAdmin() {
if (msg.sender != admin.current) revert ErrorsLib.SenderIsNotAdmin();
_;
}

/// @dev Verifies that the caller owns the specified identity/username
modifier onlyOwnerOfIdentity(address _user, string memory _identity) {
if (identityDetails[_identity].owner != _user)
revert ErrorsLib.UserIsNotOwnerOfIdentity();
_;
}

3. Anti-Front-Running Protection

The Name Service implements a commit-reveal scheme for username registration:

// Step 1: Pre-register with hash commitment
function preRegistrationUsername(
address user,
bytes32 hashPreRegisteredUsername, // Hash of username + random number
// ... other parameters
) public {
string memory key = string.concat(
"@",
AdvancedStrings.bytes32ToString(hashPreRegisteredUsername)
);

identityDetails[key] = IdentityBaseMetadata({
owner: user,
expireDate: block.timestamp + 30 minutes, // 30-minute window
customMetadataMaxSlots: 0,
offerMaxSlots: 0,
flagNotAUsername: 0x01 // Mark as pre-registration
});
}

// Step 2: Reveal and register within 30 minutes
function registrationUsername(
address user,
string memory username, // Actual username revealed
uint256 clowNumber, // Random number from hash
// ... other parameters
) public {
string memory _key = string.concat(
"@",
AdvancedStrings.bytes32ToString(hashUsername(username, clowNumber))
);

// Verify the reveal matches the commitment
if (identityDetails[_key].owner != user ||
identityDetails[_key].expireDate > block.timestamp) {
revert ErrorsLib.PreRegistrationNotValid();
}

// Complete registration
identityDetails[username] = IdentityBaseMetadata({
owner: user,
expireDate: block.timestamp + 366 days,
customMetadataMaxSlots: 0,
offerMaxSlots: 0,
flagNotAUsername: 0x00
});

delete identityDetails[_key]; // Clean up pre-registration
}

4. Marketplace and Economic Mechanisms

The Name Service includes a sophisticated marketplace:

struct OfferMetadata {
address offerer;
uint256 expireDate;
uint256 amount; // After marketplace fee deduction
}

function makeOffer(
address user,
string memory username,
uint256 expireDate,
uint256 amount,
// ... other parameters
) public {
// Lock the offer amount
usernameOffers[username][offerID] = OfferMetadata({
offerer: user,
expireDate: expireDate,
amount: ((amount * 995) / 1000) // Deduct 0.5% marketplace fee
});

// Track locked tokens for accounting
mateTokenLockedForWithdrawOffers +=
((amount * 995) / 1000) + (amount / 800);
}

5. Data Validation and Schema Standards

The Name Service implements comprehensive data validation:

/**
* @notice Validates username format according to system rules
* @dev Username must be at least 4 characters, start with a letter, and contain only letters/digits
*/
function isValidUsername(string memory username) internal pure {
bytes memory usernameBytes = bytes(username);

if (usernameBytes.length < 4) revert ErrorsLib.InvalidUsername(0x01);
if (!_isLetter(usernameBytes[0])) revert ErrorsLib.InvalidUsername(0x02);

for (uint256 i = 0; i < usernameBytes.length; i++) {
if (!_isDigit(usernameBytes[i]) && !_isLetter(usernameBytes[i])) {
revert ErrorsLib.InvalidUsername(0x03);
}
}
}

// Standardized metadata format: [schema]:[subschema]>[value]
// Examples:
// - memberOf:>EVVM
// - socialMedia:x>jistro
// - email:dev>jistro[at]evvm.org

6. Gas-Efficient Data Management

Smart data organization for cost efficiency:

struct IdentityBaseMetadata {
address owner; // 20 bytes
uint256 expireDate; // 32 bytes
uint256 customMetadataMaxSlots; // 32 bytes
uint256 offerMaxSlots; // 32 bytes
bytes1 flagNotAUsername; // 1 byte - packed efficiently
}

// Nested mappings for efficient access
mapping(string username => mapping(uint256 numberKey => string customValue))
private identityCustomMetadata;

Integration with Existing Services

Since EVVM services are smart contracts living in the same ecosystem, they can easily connect to and interact with each other.

1. Connecting to Name Service

Your smart contract can directly call Name Service functions:

contract MyEVVMService {
address public immutable nameServiceAddress;

function executeServiceByUsername(
string memory username,
ServiceSignature memory sig,
bytes memory userSignature
) external {
// Use Name Service's verifyStrictAndGetOwnerOfIdentity function
(bool success, bytes memory result) = nameServiceAddress.call(
abi.encodeWithSignature(
"verifyStrictAndGetOwnerOfIdentity(string)",
username
)
);
require(success, "Name Service call failed");

address userAddress = abi.decode(result, (address));
require(userAddress == sig.user, "Username mismatch");

executeService(sig, userSignature);
}

function getUsernameByAddress(address evvmAddress) external view returns (string memory) {
// Note: Direct reverse lookup not available in current NameService
// You would need to implement your own mapping or indexing system
revert("Reverse lookup not implemented in NameService");
}

function checkIfUsernameExists(string memory username) external view returns (bool) {
(bool success, bytes memory result) = nameServiceAddress.staticcall(
abi.encodeWithSignature(
"verifyIfIdentityExists(string)",
username
)
);

if (!success) return false;
return abi.decode(result, (bool));
}
}

2. Connecting to Other Smart Contracts

Your smart contract can interact with any other contract in the same EVVM instance:

contract MyEVVMService {

// Connect to any other smart contract in the same EVVM
function callOtherContract(
address otherContractAddress,
bytes memory callData
) external returns (bool success, bytes memory result) {
(success, result) = otherContractAddress.call(callData);
}

// Check if another smart contract is active
function checkOtherContractStatus(address otherContractAddress) external view returns (bool) {
(bool success, bytes memory result) = otherContractAddress.staticcall(
abi.encodeWithSignature("isServiceActive()")
);

if (!success) return false;
return abi.decode(result, (bool));
}
}

Complete Smart Contract Example

Here's a complete example of a smart contract that connects to EVVM and other services:

pragma solidity ^0.8.20;

import {SignatureRecover} from "@EVVM/testnet/lib/SignatureRecover.sol";
import {AdvancedStrings} from "@EVVM/testnet/lib/AdvancedStrings.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {Evvm} from "@EVVM/testnet/contracts/evvm/Evvm.sol";

contract DataProcessingService {
// Smart contract configuration - connections to other contracts
address public immutable EVVM_ADDRESS;
address public immutable STAKING_CONTRACT_ADDRESS;
address public immutable NAME_SERVICE_ADDRESS;
address public immutable PRINCIPAL_TOKEN_ADDRESS;

// Service state
mapping(address => uint256) public userNonces;
mapping(address => mapping(uint256 => bool)) public usedAsyncNonces;
mapping(address => string) public userData;

// Service economics
uint256 public constant SERVICE_FEE = 1000; // 1000 MATE tokens
mapping(address => uint256) public operatorRewards;
uint256 public serviceReserve;

// Function identifier for data processing service
string constant DATA_PROCESSING_FUNCTION_ID = "e8f7c6d5";

struct DataProcessingSignature {
address user;
string data;
address tokenAddress;
uint256 nonce;
bool useAsyncNonce;
uint256 priorityFee_EVVM;
uint256 nonce_EVVM;
bytes signature_EVVM;
}

constructor(address _evvm, address _stakingContract, address _nameService, address _principalToken) {
EVVM_ADDRESS = _evvm;
STAKING_CONTRACT_ADDRESS = _stakingContract;
NAME_SERVICE_ADDRESS = _nameService;
PRINCIPAL_TOKEN_ADDRESS = _principalToken;
}

/**
* @dev Validates SERVICE signature for data processing operation using EVVM standard
* @param signer Expected signer address
* @param data Service-specific data
* @param nonce SERVICE nonce for replay protection
* @param signature User's SERVICE signature
*/
function verifyDataProcessingSignature(
address signer,
string memory data,
uint256 nonce,
bytes memory signature
) internal pure returns (bool) {
return SignatureRecover.signatureVerification(
string.concat(
DATA_PROCESSING_FUNCTION_ID,
",",
Strings.toHexString(uint256(uint160(signer)), 20),
",",
data,
",",
Strings.toString(nonce)
),
signature,
signer
);
}

/**
* @notice Internal function to connect to EVVM for payment processing
* @dev This smart contract calls EVVM contract functions directly
*/
function makeDataProcessingPayment(
address user,
address tokenAddress,
uint256 amount,
uint256 priorityFee,
bool priorityFlag,
uint256 nonce_EVVM,
bytes memory signature_EVVM
) internal {
if (priorityFlag) {
// Direct contract call to EVVM
Evvm(EVVM_ADDRESS).payStaker_async(
user, // from
address(this), // to_address (this smart contract)
"", // to_identity (empty)
tokenAddress, // token (can be any supported token)
amount, // amount
priorityFee, // priorityFee (paid in same token)
nonce_EVVM, // EVVM nonce
address(this), // executor (this smart contract)
signature_EVVM // EVVM signature
);
} else {
// Direct contract call to EVVM
Evvm(EVVM_ADDRESS).payStaker_sync(
user, // from
address(this), // to_address (this smart contract)
"", // to_identity (empty)
tokenAddress, // token (can be any supported token)
amount, // amount
priorityFee, // priorityFee (paid in same token)
address(this), // executor (this smart contract)
signature_EVVM // EVVM signature
);
}
}

function processUserData(
address user,
string memory data,
uint256 nonce, // SERVICE nonce
bytes memory signature, // SERVICE signature
uint256 priorityFee_EVVM,
uint256 nonce_EVVM, // EVVM nonce
bool priorityFlag_EVVM,
bytes memory signature_EVVM // EVVM signature
) external verifyIfNonceIsAvailable(user, nonce) {
// 1. Validate SERVICE signature using EVVM standard
require(
verifyDataProcessingSignature(
user,
data,
nonce,
signature
),
"Invalid service signature"
);

// 2. Optional priority fee payment (gasless if priorityFee_EVVM = 0)
if (priorityFee_EVVM > 0) {
makeDataProcessingPayment(
user,
PRINCIPAL_TOKEN_ADDRESS, // Token for payment
priorityFee_EVVM, // Only priority fee as amount
0, // No additional priority fee
priorityFlag_EVVM,
nonce_EVVM, // EVVM nonce (validated by EVVM)
signature_EVVM // EVVM signature (validated by EVVM)
);
}

// 3. Execute service logic (service covers costs from staking rewards)
userData[user] = data;

// 4. Mark SERVICE nonce as used
usedAsyncNonces[user][nonce] = true;

// 5. CRITICAL: Reward fishers following staking contract pattern
if (Evvm(EVVM_ADDRESS).isAddressStaker(msg.sender)) {
uint256 fisherReward = Evvm(EVVM_ADDRESS).getRewardAmount() + priorityFee_EVVM;

// Direct contract call to EVVM for reward distribution
Evvm(EVVM_ADDRESS).caPay(
msg.sender, // Fisher who executed this smart contract function
PRINCIPAL_TOKEN_ADDRESS, // Always in principal token
fisherReward // Base reward + priority fee
);
}

emit DataProcessed(user, data);
}

event DataProcessed(address indexed user, string data);
}

Understanding Dual Signatures in EVVM Services

EVVM services require two separate signatures for every operation that involves payment:

1. Service Signature

  • Purpose: Authorizes the specific service operation
  • Contains: Service-specific parameters and service nonce
  • Validated by: Your service contract
  • Format: functionId,userAddress,serviceData,serviceNonce

2. EVVM Payment Signature

  • Purpose: Authorizes the payment execution within EVVM
  • Contains: Payment parameters and EVVM nonce
  • Validated by: EVVM contract internally
  • Format: EVVM's internal signature format for payments

Example Signature Flow

// Frontend: User signs both signatures for gasless transaction
const serviceSignature = await user.signMessage(
`e8f7c6d5,${userAddress},${serviceData},${serviceNonce}`
);

// EVVM payment signature for optional priority fee (amount can be 0)
const evvmSignature = await user.signMessage(
// EVVM's internal payment signature format
evvmPaymentMessage // Amount = priorityFee (or 0 for completely gasless)
);

// Call service function with both signatures
await dataProcessingService.processUserData(
userAddress,
serviceData,
serviceNonce, // SERVICE nonce
serviceSignature, // SERVICE signature
priorityFee, // Can be 0 for gasless transaction
evvmNonce, // EVVM nonce
priorityFlag,
evvmSignature // EVVM signature
);

Gasless Transaction Model:

  • Users can set priorityFee_EVVM = 0 for completely gasless transactions
  • Service covers operation costs through staking rewards
  • Fishers still earn base rewards (getRewardAmount()) even with zero priority fee
  • This creates a sustainable gasless experience while incentivizing fisher participation

This dual signature approach ensures:

  • Service Authorization: User explicitly authorizes the specific service operation
  • Payment Security: User explicitly authorizes the payment with separate nonce management
  • Replay Protection: Independent nonce systems prevent replay attacks on both levels
  • Clear Separation: Service logic and payment logic remain decoupled and secure

Best Practices

1. Security Considerations

  • Always validate signatures before processing any user request
  • Implement proper nonce management to prevent replay attacks
  • Use time locks for administrative functions
  • Validate all EVVM payment parameters before calling EVVM functions

2. Gas Optimization

  • Use immutable variables for contract addresses
  • Implement efficient data structures for nonce tracking
  • Consider using packed structs for signature data
  • Batch operations when possible

3. User Experience

  • Provide clear error messages for failed validations
  • Support both sync and async nonces for different use cases
  • Offer gasless transactions through priority fee handling
  • Integrate with Name Service for user-friendly addressing

4. Economic Model & Fisher Incentives

  • Stake tokens to earn execution rewards
  • Set competitive service fees while maintaining profitability
  • Implement fisher incentive model with gasless transactions
  • Always reward fishers in principal token to maintain ecosystem consistency
  • Consider zero-amount payments with optional priority fees for better UX
  • Use service staking to cover operation costs and sustain gasless model

Getting Started

  1. Design your smart contract functionality and determine which EVVM services you need
  2. Import EVVM contracts and other service contracts you want to connect to
  3. Implement signature validation and nonce management for your contract
  4. Add EVVM integration for payment processing and fisher rewards
  5. Connect to other services like Staking, Name Service, or custom contracts
  6. Deploy your smart contract to the same EVVM instance as other services
  7. Test contract interactions with EVVM and other services

Ready to build within an EVVM? Follow EVVM's "Build Within an EVVM" approach by creating smart contracts that leverage the virtual blockchain infrastructure. Start with signature validation and nonce management, integrate with EVVM Core for payments, and connect to other ecosystem services as needed.

EVVM provides everything you need to deploy powerful services on abstract blockchain infrastructure without managing physical nodes or complex infrastructure – just focus on your smart contract logic and let EVVM handle the rest.