dispersePay Function
Function Type: external
Function Signature: dispersePay(address,(uint256,address,string)[],address,uint256,uint256,address,address,uint256,bool,bytes)
Distributes tokens from a single sender to multiple recipients with efficient single-source multi-recipient payment distribution. This function uses a single signature to authorize distribution to multiple recipients, supports both direct addresses and identity-based recipients, and includes integrated priority fee and staker reward systems.
The signature structure for these payments is detailed in the Disperse Payment Signature Structure section.
Parameters
| Parameter | Type | Description |
|---|---|---|
from | address | The address of the payment sender whose funds will be distributed. |
toData | DispersePayMetadata[] | An array detailing each recipient's address/identity and the amount they should receive. See struct below. |
token | address | The token address to be distributed. |
amount | uint256 | The total amount of tokens to distribute across all recipients. Must equal the sum of individual amounts in toData. |
priorityFee | uint256 | Fee amount for the transaction executor (distributed to stakers as reward). |
senderExecutor | address | Address authorized to execute this transaction (msg.sender). Use address(0) to allow any service with the correct signature to execute. When set to a specific address, only that executor can consume the nonce. |
originExecutor | address | Address restriction for transaction origin (tx.origin). Use address(0) to allow any origin. Provides additional security layer for multi-service transaction flows. |
nonce | uint256 | Transaction nonce for replay protection managed by Core.sol. Usage depends on isAsyncExec. |
isAsyncExec | bool | Determines nonce type: true for asynchronous (parallel), false for synchronous (sequential). |
signature | bytes | Cryptographic signature (EIP-191) from the from address authorizing the distribution. |
If you want to know more about the signature structure, refer to the Disperse Payment Signature Structure section.
DispersePayMetadata Struct
Defines the payment details for a single recipient within the toData array.
struct DispersePayMetadata {
uint256 amount;
address to_address;
string to_identity;
}
| Field | Type | Description |
|---|---|---|
amount | uint256 | The amount of tokens to be sent to this recipient. |
to_address | address | Direct recipient address. Used when to_identity is an empty string (""). |
to_identity | string | Username/identity of the recipient. If provided, the contract resolves it to an address via the NameService. |
If to_identity is an empty string (""), the to_address field will be used as the recipient's destination address. Otherwise, the contract attempts to resolve the to_identity to its owner address using the NameService.
Execution Methods
This function can be executed by any address, with different behavior depending on whether the executor is a staker:
Fisher Execution
- A fisher collects multiple disperse payment requests with valid signatures from users through fishing spots.
- The fisher submits the transaction and receives priority fees and principal token rewards if they are a staker.
Direct Execution
- A user or service directly calls
dispersePaywith appropriate authorization. - Staker executors receive priority fees and principal token rewards for processing.
The dual-executor model provides flexible security:
senderExecutor = address(0): Any service with the correct signature can execute - useful for competitive fisher marketssenderExecutor = specific address: Only that specific service can execute - provides deterministic execution guaranteesoriginExecutor: Additionally restrictstx.originfor extra security in multi-service flows
When building services that dispatch payments, both senderExecutor and originExecutor should be set to the service address for clear accountability.
Workflow
-
Signature Verification: Validates the
signatureusing Core.sol's centralized signature verification:- Constructs signature payload:
buildSignaturePayload(evvmId, senderExecutor, hashPayload, originExecutor, nonce, isAsyncExec) hashPayloadis generated viaCoreHashUtils.hashDataForDispersePay(toData, token, amount, priorityFee)- Recovers signer and compares with
fromaddress. Reverts withInvalidSignatureon failure.
- Constructs signature payload:
-
Sender Executor Validation: If
senderExecutoris notaddress(0), validates thatmsg.sendermatchessenderExecutor. Reverts withSenderMismatchif they don't match. Whenaddress(0), any service can execute. -
Origin Executor Validation: If
originExecutoris notaddress(0), validates thattx.originmatchesoriginExecutor. Reverts withOriginMismatchif they don't match. Provides additional security for multi-service transactions. -
User Validation: Checks if the user is allowed to execute transactions using
canExecuteUserTransaction(from). Reverts withUserCannotExecuteTransactionif not allowed. -
Nonce Management: Core.sol handles nonce verification and updates based on
isAsyncExec:- Async (isAsyncExec = true): Checks nonce status via
asyncNonceStatus(from, nonce). IfsenderExecutorwasaddress(0)in signature, any service can consume it; otherwise only the specified service. Reverts withAsyncNonceAlreadyUsedif already used, orAsyncNonceIsReservedByAnotherServiceif reserved by another service. - Sync (isAsyncExec = false): Verifies the nonce matches
nextSyncNonce[from], then increments it. Reverts withSyncNonceMismatchon mismatch.
- Async (isAsyncExec = true): Checks nonce status via
-
Staker Check: Determines if the executor (
msg.sender) is a registered staker usingisAddressStaker. -
Balance Verification: Checks that the
fromaddress has sufficient balance. The required balance depends on staker status:- If executor is a staker:
amount + priorityFee - If executor is not a staker:
amountonly (priorityFee is not collected)
Reverts with
InsufficientBalanceif insufficient. - If executor is a staker:
-
Balance Deduction: Subtracts the required amount from the sender's balance upfront:
- If executor is a staker: deducts
amount + priorityFee - If executor is not a staker: deducts
amountonly
- If executor is a staker: deducts
-
Distribution Loop: Iterates through each recipient in the
toDataarray:- Amount Tracking: Maintains a running total (
accumulatedAmount) of distributed amounts - Recipient Resolution:
- If
to_identityis provided, verifies the identity exists usingstrictVerifyIfIdentityExistand resolves it to an owner address usinggetOwnerOfIdentity - If
to_identityis empty, usesto_address
- If
- Token Distribution: Adds the specified amount to the recipient's balance
- Amount Tracking: Maintains a running total (
-
Amount Validation: Verifies that the total distributed amount (
accumulatedAmount) exactly matches the specifiedamountparameter. Reverts withInvalidAmountif mismatch. -
Staker Benefits: If the executor is a staker (
isAddressStaker(msg.sender)):
- Grants 1 principal token reward using
_giveReward - Transfers the
priorityFeeto the executor's balance