For a dapp, I'm currently focused on the Approval event to track and list the tokens approvals for a given address. ERC20 and ERC721 are quite similar. They offer Approval events :
ERC20 : Approval(address indexed _owner, address indexed _spender, uint256 _value)
ERC721: Approval(address indexed _owner, address indexed _approved, uint256 _tokenId)
Looking at the logs traces, the log topic is exactly the same (hash=0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925), because the event signature is exactly the same. Still, the third argument is different in terms of meaning : numbers allowed for ERC20, and serial number allowed for ERC721. The internal behavior is also different : For ERC20 only the last one gives the number allowed, and to reset one sends 0 as value. For ERC721 there is one single address allowed by serial (last approval for a given serial gives the allowed address for this id) and to reset the zero address should be provided as approved (for the given serial/id). The EVM are running the relevant code, and knows what to do internally. My issue is about differentiating the 2 different event despite having the same signature.
For example collecting the event logs with this even signature "topic" for an address (first log argument), how can I differentiate between ERC20 from ERC721 ? This is crucial as the meaning of the 3rd argument is different, and then what to do to reconstruct the wallet approvals are very different if ERC20 or ERC721. Looking at the blockchain/RPC/node, how can I tell this contract is an ERC20 or an ERC721? So I can correctly interpret and decode the logs, so the software can know how to compute the allowance.
To me, this is a system flaw that 2 different logs inputs have the exact same signature, but their argument meaning and internal behavior is different. So how can we circumvent this flaw?
One idea is to read the call methods. But for the approve() call has exactly the same issue : same signature but different meaning for arguments. So it is useless to look at the method cal when Approval is triggered. All I can do is looking at different txs calling the token contract and see if it is an ERC20 (transfer(address,uint256),...) or an ERC721.
An other idea would be to call public-read methods of the contract to discriminate it from ERC20 or ERC721. I think about totalSupply() for ERC20 which normally is not used for ERC721, same for decimals(). Similarly, ownerOf() and getApproved() are not defined for ECR20. What would be the best discrimination method ?