# ZkMinterModTriggerV1 - Usage Guide

ZkMinterModTriggerV1 - Usage Guide

Contributors:

Factory Labs:

@Matt_FactoryLabs
@drnick

With thanks to the ZK Team:

@rafa
@theshelb


Date: 14/08/25

0. Introduction

This guide shows how to deploy, configure, and use ZkMinterModTriggerV1 in production or test environments.

To learn more about the genesis of the Trigger Mod, please read this introduction post.

TL;DR: Deploy → set minter → grant role → call mint()

Goal: Ensure the contract is correctly configured, functionally sound, and free of high-risk vulnerabilities whilst meeting the intended business logic.


1. What the Contract Does

ZkMinterModTriggerV1 lets you mint tokens from a trusted IZkCappedMinter and immediately execute multiple function calls-all in one atomic transaction.

Typical use-cases:

  • Funding airdrops (e.g., MerkleDropFactory.addMerkleTree) and bootstrapping liquidity
  • Batch interact with DeFi protocols after minting governance tokens
  1. Confirm the contract adheres to the documented specification
  2. Validate admin configuration and upgrade paths
  3. Verify minting logic and multi-call execution behave as expected

2. Prerequisites

  • Solidity ≥0.8.24 tooling (Foundry/Hardhat)
  • Deployed IZkCappedMinter (e.g., ZkCappedMinterV2)
  • Admin wallet (ideally multisig) to own the trigger
  • Arrays of:
    1. targets - contract addresses to call
    2. functionSignatures - 4-byte selectors (e.g., bytes4(keccak256("transfer(address,uint256)")))
    3. callDatas – ABI-encoded arguments only; do NOT include the 4-byte selector
  • The three arrays must be parallel and equal length

Generating callDatas quickly (script/CreateCallData.js)

  1. Open script/CreateCallData.js (TypeScript-flavoured)

  2. Edit the functionSignatures array - one entry per call (selector inferred automatically from the signature string)

  3. Edit the argsList array so each sub-array matches the arguments for the corresponding signature (use ethers.parseUnits for token amounts)

  4. Run the script:

    npx ts-node script/CreateCallData.js   # or pnpm ts-node ...
    
  5. Copy the printed hex blobs into your callDatas array when deploying or updating the trigger

    • The script already strips the 4-byte selector, so you keep callDatas and functionSignatures separate as required
  6. If you later need different recipients/amounts, re-run the script and update only callDatas (array lengths must still match)


3. Deployment


ZkMinterModTriggerV1 trigger = new ZkMinterModTriggerV1(
    admin,            // address that can re-configure
    targets,          // address[] memory
    functionSignatures,// bytes[] memory (each 4-byte selector)
    callDatas          // bytes[] memory (ABI-encoded args, NO selector)
);
  1. Read docs/ZkMinterModTriggerV1.md (design doc)
  2. Review this workflow and add project specific notes
  3. Identify assumptions or external dependencies (e.g., ERC-20 compatibility of minted token)

4. Post-Deploy Setup

  1. Link the minter

    trigger.setMinter(address(zkCappedMinter));
    
  2. Grant MINTER_ROLE to the trigger on the zkCappedMinter contract after calling setMinter; this ordering is required for successful minting

    zkCappedMinter.grantRole(MINTER_ROLE, address(trigger));
    
Item Questions / Actions
State Vars • Does minter comply with IZkCappedMinter?
• Is admin set to a secure multisig?
Constructor • Arrays lengths equality enforcement
• Initial targets / selectors / calldata sanity
AdminOnly Modifier • No privilege escalation or bypass
Setter Functions • Array length checks present
• Copy-to-memory avoids calldata aliasing
mint() Logic • Calls minter.mint(address(this), _amount); ensure _amount cannot exceed cap
• Iterates over arrays and builds abi.encodePacked(selector, data)
External Calls • Re-entrancy impact minimal (no state written after external calls)
• Revert logic on failure is correct
Gas Usage • Loop cost linear in targets.length; verify realistic limits

Tick off each item during review.


5. Mint-and-Call in One Transaction

Anyone can now execute:

trigger.mint(totalAmountToMint);

Under the hood this will:

  1. minter.mint(address(this), totalAmountToMint)
  2. Loop through each configured target and call it with its calldata

If any call fails, the whole transaction reverts, guaranteeing atomicity.

  1. Run Slither:

    slither . --filter-paths "docs|scripts"
    
  2. Address all HIGH and MEDIUM findings. Document any accepted risks

  3. Optionally run Mythril or Echidna property-based tests


6. Updating Configuration

Only the admin can adjust arrays or admin address:

  • setCallParameters(...) - update all arrays at once
  • setTargets(...), setFunctionSignatures(...), setCallDatas(...) - update individually
  • setAdmin(newAdmin) - transfer admin

Remember to re-grant MINTER_ROLE if you migrate to a new trigger.

  1. Happy Path: Admin configures arrays, grants MINTER_ROLE, user calls mint() and all sub-calls succeed
  2. Array Length Mismatch: Expect revert from setter and mint()
  3. Failing Target Call: Make one target revert; ensure the whole tx reverts and state is unchanged
  4. Zero Targets: Edge case - decide whether allowed. Expect revert or noop per spec

7. Safety Tips

  • Keep admin in a multisig or timelock
  • Validate selectors & calldata off-chain before pushing to mainnet
  • Limit targets.length to stay within block gas limits
  • Monitor emitted events and token balances after each mint()

9. Quick Reference

Action Function
Deploy constructor(admin, targets, functionSignatures, data)
Link minter setMinter(address)
Grant mint permission minter.grantRole(MINTER_ROLE, trigger)
Execute workflow mint(amount)
Update all arrays setCallParameters(...)
Move admin setAdmin(newAdmin)
  • Confirm minted token’s inflation schedule remains within governance limits
  • Ensure multi-call sequence cannot bypass spending caps or time-locks
  • Verify admin address is governed by DAO / multisig with sufficient security controls

10. Post-Deployment Monitoring (Optional)

  • Set up on-chain alerts for calls to mint() and changes to admin/config arrays
  • Track minted token supply vs cap in a dashboard

Annex 1: Full Example Walkthrough

Desired outcome: The ZkMinterModTrigger should distribute 1e18 amount of tokens to its targets, by minting 10e18 from the cappedminter.

Setup: The ZkMinterModTrigger is deployed with the following params:

The callData can be set using a helpful script CreateCallData.js in the repo.

// targets 
[
  "0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f91",
  // ...
  "0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f99",
  "0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f90"
],

// functionSignatures (10 identical selectors for "transfer(address,uint256)")
[
  "0xa9059cbb",
  // ...
  "0xa9059cbb"
],

// callDatas (10 encoded blobs) - Note these are illustrative only
[
  "0x0000000000000000000000007a860e9c0986b5f7b1ab6ae7f0017d793dfcea2e0000000000000000000000000000000000000000000000000de0b6b3a7640000",
  "0x0000000000000000000000005144edf6a2e7677433bbbd04618702c1c9df3c250000000000000000000000000000000000000000000000000de0b6b3a7640000",
  "0x000000000000000000000000ee7d0b1fee97f5cf449f6e6f9d403db445bc93510000000000000000000000000000000000000000000000000de0b6b3a7640000",
  "0x000000000000000000000000bc735044ec62c53a5c0c293191016e9c7788e8810000000000000000000000000000000000000000000000000de0b6b3a7640000",
  "0x000000000000000000000000a453f6337b30798e260c624674846075811589900000000000000000000000000000000000000000000000000de0b6b3a7640000",
  // ...
]

The ZkMinterModTrigger has the MINTER role granted on the ZkCappedMinter.

MINT call: Call the mint function on the ZkMinterModTrigger by passing 10e18 as the param. This will mint 10e18 from the cappedMinter and distribute 1e18 as it was configured.

NOTE: If you now want to mint a separate amount, update the callData accordingly. Either manually or using the script.

NOTE: If the Trigger is setup to distribute 1e18 and you mint 20e18 worth of the tokens, it will distribute the 10 (as it was configured to do so) and the rest of the 10 will just stay in trigger contract.


Annex 2: ZkMinterModTriggerV1 with Drips Integration Example

Full Example Walkthrough

Desired outcome: The ZkMinterModTrigger should distribute tokens to a Drips Drip List, which will automatically split and stream the funds to multiple recipients over time.

Setup: The ZkMinterModTrigger is deployed with the following params:

The callData can be set using the helpful script CreateDripCallData.js in the repo.

// targets (single target - the token contract)
[
  "0x69e5DC39E2bCb1C17053d2A4ee7CAEAAc5D36f96"  // Token contract address
],

// functionSignatures (single selector for "transfer(address,uint256)")
[
  "0xa9059cbb"
],

// callDatas (single encoded blob for Drips transfer)
[
  "0x000000000000000000000000[dripListAddress]0000000000000000000000000000000000000000000000000de0b6b3a7640000"
]

Prerequisites

  1. Create Drip List on Drips UI:

    • Go to Drips Network
    • Create a new Drip List
    • Add your recipients and set their percentages
    • Copy the Drip List address
  2. Generate Call Data:

    node script/CreateDripCallData.js
    

    This will output the encoded callData for transferring tokens to your Drip List.

Deployment Configuration

The ZkMinterModTrigger has the MINTER role granted on the ZkCappedMinter.

MINT call: Call the mint function on the ZkMinterModTrigger by passing 10e18 as the param. This will:

  1. Mint 10e18 from the cappedMinter to the trigger contract
  2. Transfer 1e18 to the Drips Drip List
  3. Drips automatically distributes the funds to your recipients monthly

How Drips Integration Works

  1. Token Transfer: Your contract sends tokens to the Drip List address
  2. Automatic Distribution: Drips handles the monthly splitting based on your configured percentages
  3. Recipient Claims: Recipients can claim their funds from Drips at any time
  4. Dependency Tree: If recipients have their own Drip Lists, funds flow through the dependency tree

Benefits of Drips Integration

  • Automatic Streaming: No need to manage individual transfers
  • Dependency Support: Funds can flow to project dependencies
  • Flexible Recipients: Easy to add/remove recipients via Drips UI
3 Likes

Added this link to the call notes from August 13th for reference!