When I use web3js and Truffle, I cannot send tokens to another wallet without the Token Contract ABI. But Metamask only requires the token contract address to recognize and send the ERC20 tokens. How do they do it?
Metamask uses internally a simple ERC20 compatible ABI, defined on the basis of the standard. As you may know, any
function transfer(address, uint)
is described by the same ABI element for any possible contract existing incorporating it, being it (the ABI element) nothing more that an hashed description of the call name and of the parameters type.
So if you suppose that the contract to connect with has, at least, one transfer, one balanceOf, one approve, one transferFrom and so on, you can write down a usable ABI that shall give you access to those functions in the contract without hassle.
Of course you shall not be able to access any other function existing in the specific contract that you did not described in advance using that approach.