Deploy the following contract (you need to do this only once):
contract BalanceChecker {
function checkBalances (address [] memory addresses, uint [] memory previousBalances)
public view
returns (address [] memory changedAddresses, uint [] memory currentBalances) {
uint count = addresses.length;
require (previousBalances.length == count);
address [] memory tempAddresses = new address [] (count);
uint [] memory tempBalances = new uint [] (count);
uint changedCount = 0;
for (uint i = 0; i < count; i++) {
address a = addresses [i];
uint pb = previousBalances [i];
uint cb = a.balance;
if (cb != pb) {
tempAddresses [changedCount] = a;
tempBalances [changedCount] = cb;
changedCount += 1;
}
}
changedAddresses = new address [] (changedCount);
currentBalances = new uint [] (changedCount);
for (uint i = 0; i < changedCount; i++) {
changedAddresses [i] = tempAddresses [i];
currentBalances [i] = tempBalances [i];
}
}
}
After every block, call the checkBalances
function on the contract, passing array of addresses and array of previously observed balances for these addresses (the arrays must be of the same size). The function will return you array of addresses whose balance was changed and array of new balances for theses addresses. The call will be executed off-chain, so it will not cost you any gas.
This approach should work with Infura. If your array of addresses is too large and Infura will complain about this, just split the array into several chunks.