One way that MetaMask can look up a user's ETH or ERC20 balance is to call a balance checking contract: https://etherscan.io/address/0xb1f8e55c7f64d203c1400b9d8555d050f94adf39#code
Specifically, we call the function balances(address[] users, address[] tokens) external view returns (uint[]). We make the call directly on a Contract object instantiated from the ABI.
To check a user's ETH balance, the contract makes a special case for the address 0x0 (note that this is not intended to be the null/0-address) when encountered in the tokens array. ethers, however, treats 0x0 as the invalid address it is. This is very reasonable, but can we bypass the type checking somehow?
(Alternatively, if anyone knows of another balance checking contract, that would also be helpful.)
Hmmm. I don鈥檛 think the coder cares about the zero address as an exceptional case. Do you have an example where it fails for the zero address?
I have been thinking of deploying a similar Swiss-army knife contract, but may just use this one for now. :)
I don鈥檛 think the coder cares about the zero address as an exceptional case.
You are correct, but 0x0 is not the zero address! I get an error saying it's an _invalid_ address.
Oh!!! You should use ethers.constants.AddressZero then. The important thing to the coder is that the passed value is 20 bytes long.
Does that make sense?
Ah, it does, but I don't want the zero address either 馃槄
Here are the relevant lines from the contract:
if (tokens[j] != address(0x0)) {
addrBalances[addrIdx] = tokenBalance(users[i], tokens[j]);
} else {
addrBalances[addrIdx] = users[i].balance; // ETH balance
}
Whatever address(0x0) comes out to, it's not the zero address, because when I pass the zero address as a parameter, I get what I think is the number of ERC20 tokens held by or managed by that address.
There is definitely something else happening then, because the solidity compiler will create EVM code where address(0) gets compiled to a stack item for the evaluation, which is treated the same way as a normal uint, so the zero address will match.
Are you sure you are decoupling the output correctly? Each address will have multiple sequential outputs, 1 for each token. Code snippet? :)
this.ethersProvider = new ethers.providers.Web3Provider(this._provider)
const ethContract = new ethers.Contract(
deployedContractAddress, SINGLE_CALL_BALANCES_ABI, this.ethersProvider
)
await ethContract.balances(addresses, ['0x0'])
Error: invalid input argument (arg="tokens", reason="invalid address", value=null, version=4.0.39)
{
"name": "balances",
"constant": true,
"inputs": [
{
"name": "users",
"type": "address[]"
},
{
"name": "tokens",
"type": "address[]"
}
],
"outputs": [
{
"name": "",
"type": "uint256[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
If you use the AddressZero, it seems to work fine for me. :)
const deployedContractAddress = "0xb1F8e55c7f64D203C1400B9D8555d050F94aDF39";
const SINGLE_CALL_BALANCES_ABI=[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"token","type":"address"}],"name":"tokenBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"users","type":"address[]"},{"name":"tokens","type":"address[]"}],"name":"balances","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}];
const provider = ethers.getDefaultProvider();
const c = new ethers.Contract(deployedContractAddress, SINGLE_CALL_BALANCES_ABI, provider);
c.balances([ "0x8ba1f109551bD432803012645Ac136ddd64DBA72" ], [ ethers.constants.AddressZero ]).then(console.log)
> [ BigNumber { _hex: '0x1cdd9cc432e6807e' } ]
provider.getBalance("0x8ba1f109551bD432803012645Ac136ddd64DBA72").then(console.log)
> BigNumber { _hex: '0x1cdd9cc432e6807e' }
Using ethers.constants.AddressZero, I get a resulting balance of: 185823.4453
ETH
What address are you looking up? Which network?
Hah! I investigated further, and we were not handling the ethers BigNumber type correctly. It works perfectly. Thank you for your help!