I looked at https://github.com/Exim/exim/wiki/FastGrayListMiniTutorialPGSQL and https://github.com/Exim/exim/wiki/FastGrayListMiniTutorial for a solution having tried another first.
If you are using Postgres, follow the first link and then look at the second, because there are two bits you'll need for the ACLs from there that does not show in the first.
If you do not want to mess around with /24 netmasks for IPv6 networks, change the SQL macros to this:
GREYLIST_TEST = SELECT CASE WHEN now() > block_expires THEN 'accepted' \ ELSE 'deferred' END AS result, id FROM GREYLIST_TABLE \ WHERE now() < record_expires \ AND sender_type ILIKE ${if def:sender_address_domain{'NORMAL'}{'BOUNCE'}} \ AND sender ILIKE '${quote_pgsql:${if def:sender_address_domain{$sender_address_domain}{${domain:$h_from:}} }}' \ AND recipient ILIKE '${quote_pgsql:${if def:domain{$domain}{${domain:$h_to:}} }}' \ AND relay_ip ILIKE '${quote_pgsql:$sender_host_address}' \ ORDER BY result DESC LIMIT 1 GREYLIST_ADD = DELETE FROM greylist WHERE relay_ip = '${quote_pgsql:$sender_host_address}'; \ INSERT INTO greylist (relay_ip, sender_type, sender, recipient, block_expires, record_expires, create_time, type) VALUES \ ('${quote_pgsql:$sender_host_address}', ${if def:sender_address_domain{'NORMAL'}{'BOUNCE'}}, \ '${quote_pgsql:${if def:sender_address_domain{$sender_address_domain}{${domain:$h_from:}} }}', \ '${quote_pgsql:${if def:domain{$domain}{${domain:$h_to:}} }}', \ now() + 'GREYLIST_INITIAL_DELAY'::interval, now() + 'GREYLIST_INITIAL_LIFETIME'::interval,now(), 'AUTO');
Note that I changed ${mask:$sender_host_address/24} to just $sender_host_address in the three places it occurs. Yes, it means that you will greylist individual IPs rather than netblocks, but a /24 on IPv6 is... suboptimal. And I do not know enough SQL to make a case statement that does /24 for IPv4 and a /64 or /96 for IPv6.
The greylisting does work, even with my changes.