Execute as a role member
A member does not send transactions to the Safe. The member sends them to the Roles Modifier. The Roles Modifier checks the call and forwards it to the Safe.
The execution functions
function execTransactionWithRole(
address to,
uint256 value,
bytes calldata data,
Operation operation, // 0 = call, 1 = delegate call
bytes32 roleKey,
bool shouldRevert
) public returns (bool success);execTransactionWithRoleReturnData has the same parameters and also returns the return
data of the call.
Set shouldRevert = true in almost all cases. Then the whole transaction reverts when the
inner call fails. With shouldRevert = false, a failed inner call only returns
success = false.
Two more functions execute with the member’s default role:
execTransactionFromModule and execTransactionFromModuleReturnData. They exist so that
any contract that speaks the IAvatar interface can sit behind a Roles Modifier. The
owner sets the default role with setDefaultRole.
Example with viem
This script sends 1,000 USDC from the Safe to the payroll address, through the manager
role:
import {
createWalletClient,
encodeFunctionData,
erc20Abi,
http,
parseAbi,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'
import { encodeKey } from 'zodiac-roles-sdk'
const ROLES_MODIFIER = '0x...' // your Roles Modifier instance
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
const PAYROLL = '0x...'
const rolesAbi = parseAbi([
'function execTransactionWithRole(address to, uint256 value, bytes data, uint8 operation, bytes32 roleKey, bool shouldRevert) returns (bool success)',
])
const member = privateKeyToAccount(process.env.MEMBER_KEY as `0x${string}`)
const client = createWalletClient({
account: member,
chain: mainnet,
transport: http(),
})
const transferCalldata = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [PAYROLL, 1_000_000_000n], // 1,000 USDC (6 decimals)
})
const hash = await client.writeContract({
address: ROLES_MODIFIER,
abi: rolesAbi,
functionName: 'execTransactionWithRole',
args: [USDC, 0n, transferCalldata, 0, encodeKey('manager'), true],
})For humans, the easier path is Zodiac Pilot. The browser extension routes normal dapp interactions through the role automatically. See Execute a transaction.
Read the revert reason
When a call violates the permissions, the Roles Modifier reverts with a typed error:
| Error | Cause |
|---|---|
NoMembership() | The sender does not hold the role, or the role key is wrong. |
ConditionViolation(Status status, bytes32 info) | The call does not match the permissions. The status value names the failed check. |
ModuleTransactionFailed() | The permissions passed, but the inner call reverted (with shouldRevert = true). |
The Status values in ConditionViolation:
| Status | Meaning |
|---|---|
TargetAddressNotAllowed | The role has no clearance for this target. |
FunctionNotAllowed | The function is not allowed on this target. |
SendNotAllowed | The call sends Ether, but the permission has no send option. |
DelegateCallNotAllowed | The call is a delegate call, but the permission has no delegatecall option. |
ParameterNotAllowed | An EqualTo check failed. |
ParameterLessThanAllowed / ParameterGreaterThanAllowed | A GreaterThan / LessThan check failed. |
ParameterNotAMatch | A Matches structure check failed. |
OrViolation / NorViolation | A logic check failed. |
NotEveryArrayElementPasses / NoArrayElementPasses / ParameterNotSubsetOfAllowed | An array check failed. |
BitmaskOverflow / BitmaskNotAllowed | A Bitmask check failed. |
AllowanceExceeded / EtherAllowanceExceeded / CallAllowanceExceeded | The referenced allowance has not enough balance. |
CustomConditionViolation | A custom condition adapter returned false. info carries its reason code. |
Debug a rejected transaction
- Decode the revert data with the errors above. Explorers and viem show the error name when the ABI is known.
- Open the Roles Modifier in the Zodiac App . Check the role: is the sender a member? Is the target listed? Is the function scoped as you expect?
- Check allowance balances, when the status names an allowance.
- For batch transactions (MultiSend), make sure an unwrapper is configured. Without it,
the batch call reverts with
FunctionNotAllowed. See Unwrapping. - Record the intended interaction with Zodiac Pilot. The Zodiac App can compare a recorded call against the current permissions and show the difference.
Gas note
Permission checks add gas costs in proportion to the size of the condition tree. Deep trees over long calldata cost more. Measure before you automate high-frequency calls.