Dispatch Order Signature Structure
P2PSwap signatures are verified by Core.sol using validateAndConsumeNonce(). Uses P2PSwapHashUtils.hashDataForDispatchOrder() for hash generation. This signature is used for both dispatchOrder_fillPropotionalFee and dispatchOrder_fillFixedFee.
To authorize order fulfillment operations, users must generate a cryptographic signature compliant with the EIP-191 standard using the Ethereum Signed Message format.
Signature Format
{evvmId},{senderExecutor},{hashPayload},{originExecutor},{nonce},{isAsyncExec}
Components:
- evvmId: Network identifier (uint256, typically
1) - senderExecutor: Address that can call the function via msg.sender (
0x0...0for anyone) - hashPayload: Hash of dispatch order parameters (bytes32, from P2PSwapHashUtils)
- originExecutor: EOA that can initiate the transaction via tx.origin (
0x0...0for anyone) - nonce: User's centralized nonce from Core.sol (uint256)
- isAsyncExec: Always
truefor P2PSwap (async execution)
Hash Payload Generation
The hashPayload is generated using P2PSwapHashUtils.hashDataForDispatchOrder():
import {P2PSwapHashUtils} from "@evvm/testnet-contracts/library/signature/P2PSwapHashUtils.sol";
bytes32 hashPayload = P2PSwapHashUtils.hashDataForDispatchOrder(
tokenA, // Token A in market pair (token offered by seller)
tokenB, // Token B in market pair (token requested by seller)
orderId // ID of order to fulfill
);
// Internal implementation
// keccak256(abi.encode("dispatchOrder", tokenA, tokenB, orderId))
Centralized Verification
Core.sol verifies the signature using validateAndConsumeNonce():
// Called internally by P2PSwap.sol.dispatchOrder_fillPropotionalFee() or dispatchOrder_fillFixedFee()
Core(coreAddress).validateAndConsumeNonce(
user, // Buyer's address (order fulfiller)
senderExecutor, // Who can call via msg.sender
hashPayload, // From P2PSwapHashUtils
originExecutor, // Who can initiate via tx.origin
nonce, // User's nonce
true, // Always async for P2PSwap
signature // EIP-191 signature
);
Message Construction
The signature message is constructed using AdvancedStrings.buildSignaturePayload():
import {AdvancedStrings} from "@evvm/testnet-contracts/library/utils/AdvancedStrings.sol";
import {P2PSwapHashUtils} from "@evvm/testnet-contracts/library/signature/P2PSwapHashUtils.sol";
// Step 1: Generate hash payload
bytes32 hashPayload = P2PSwapHashUtils.hashDataForDispatchOrder(
tokenA,
tokenB,
orderId
);
// Step 2: Build signature message
string memory message = AdvancedStrings.buildSignaturePayload(
1, // evvmId
address(0), // senderExecutor (anyone can call)
hashPayload, // Hash from step 1
address(0), // originExecutor (anyone can initiate)
nonce, // User's current nonce
true // isAsyncExec (always true for P2PSwap)
);
// Result: "1,0x0000000000000000000000000000000000000000,0x[hashPayload],0x0000000000000000000000000000000000000000,[nonce],true"
Example
Scenario: User wants to fulfill order #3 in the USDC/ETH market (buying 100 USDC for 0.05 ETH)
Step 1: Generate Hash Payload
import {P2PSwapHashUtils} from "@evvm/testnet-contracts/library/signature/P2PSwapHashUtils.sol";
address tokenA = 0xA0b86a33E6441e6e80D0c4C6C7527d72E1d00000; // USDC (offered by seller)
address tokenB = address(0); // ETH (requested by seller)
uint256 orderId = 3;
bytes32 hashPayload = P2PSwapHashUtils.hashDataForDispatchOrder(
tokenA,
tokenB,
orderId
);
// Result: 0x9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b
Step 2: Construct Signature Message
1,0x0000000000000000000000000000000000000000,0x9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b,0x0000000000000000000000000000000000000000,33,true
Components:
evvmId:1senderExecutor:0x0000...(anyone can call)hashPayload:0x9a8b...(from Step 1)originExecutor:0x0000...(anyone can initiate)nonce:33isAsyncExec:true
Step 3: Sign with Wallet
const message = "1,0x0000000000000000000000000000000000000000,0x9a8b...a8b,0x0000000000000000000000000000000000000000,33,true";
const signature = await signer.signMessage(message);
Function Calls
dispatchOrder_fillPropotionalFee
Uses percentage-based fee calculation:
P2PSwap(p2pSwapAddress).dispatchOrder_fillPropotionalFee(
user, // Buyer's address
tokenA, // Token A in market pair
tokenB, // Token B in market pair
orderId, // Order ID to dispatch
amountOfTokenBToFill, // Amount of tokenB to provide
senderExecutor, // Address(0) for anyone
originExecutor, // Address(0) for anyone
nonce, // User's nonce from Core.sol
signature, // EIP-191 signature of the message
priorityFeePay, // Optional priority fee
noncePay, // Nonce for pay operation
signaturePay // Signature for pay operation
);
dispatchOrder_fillFixedFee
Uses capped fee calculation with maximum limits:
P2PSwap(p2pSwapAddress).dispatchOrder_fillFixedFee(
user, // Buyer's address
tokenA, // Token A in market pair
tokenB, // Token B in market pair
orderId, // Order ID to dispatch
amountOfTokenBToFill, // Amount of tokenB to provide
senderExecutor, // Address(0) for anyone
originExecutor, // Address(0) for anyone
nonce, // User's nonce from Core.sol
signature, // EIP-191 signature of the message
priorityFeePay, // Optional priority fee
noncePay, // Nonce for pay operation
signaturePay // Signature for pay operation
);
Security Considerations
The signature authorizes the dispatch attempt. The contract performs additional validation:
- Signature Verification: Confirms the user signed the dispatch request
- Order Existence: Verifies the order exists and is active
- Market Validation: Confirms the token pair matches an existing market
- Nonce Validation: Ensures the nonce hasn't been used before
- Payment Sufficiency: Validates the user provided enough tokens to cover order + fees
- Dual Signatures: dispatchOrder requires TWO signatures:
- One for the dispatchOrder operation (validates buyer intent)
- One for the pay operation (locks tokenB + fees in Core.sol)
- Hash Independence: The hash payload does NOT include executors (only tokenA, tokenB, orderId)
- Operation Name: "dispatchOrder" is included in hash via P2PSwapHashUtils
- Async Execution: Always uses async nonces (
isAsyncExec: true) - Same Signature for Both Fee Models: Both proportional and fixed fee functions use identical signature format
- Transaction Flow:
- Buyer provides tokenB (+ fees)
- Buyer receives tokenA from order
- Seller receives tokenB payment (+ bonus)
- Validators receive staking rewards
Token Direction Understanding
The signature includes tokenA and tokenB from the seller's perspective:
- tokenA: What the seller is offering (buyer will receive)
- tokenB: What the seller wants (buyer must provide)
- Order ID: Identifies the specific order within the market
Fee Model Independence
The same signature works for both fee models:
- Proportional Fee: Percentage-based calculation
- Fixed Fee: Capped fee with maximum limits
- Fee Choice: Determined by which function is called, not the signature
- Message Format: The final message follows the pattern
"{evvmID},{functionName},{parameters}" - EIP-191 Compliance: Uses
"\x19Ethereum Signed Message:\n"prefix with message length - Hash Function:
keccak256is used for the final message hash before signing - Signature Recovery: Uses
ecrecoverto verify the signature against the expected signer - String Conversion:
AdvancedStrings.addressToStringconverts addresses to lowercase hex with "0x" prefixStrings.toStringconverts numbers to decimal strings
- Universal Signature: Same signature structure works for both proportional and fixed fee dispatch functions
- Order Identification: Token pair and order ID uniquely identify the target order
- Buyer Authorization: Signature proves the buyer authorizes the specific order fulfillment