├── src ├── abis ├── index.ts ├── common.ts ├── ethers.ts └── web3.ts ├── abis ├── SafeMath.abi.json ├── Token.abi.json ├── BalanceChecker.abi.json ├── Migrations.abi.json ├── ERC20Basic.abi.json ├── BasicToken.abi.json ├── ERC20.abi.json ├── StandardToken.abi.json └── TestToken.abi.json ├── .gitignore ├── .envexample ├── .env ├── migrations ├── 1_initial_migrations.js └── 2_deploy_contracts.js ├── contracts ├── TestToken.sol ├── Migrations.sol └── BalanceChecker.sol ├── tsconfig.json ├── .github └── workflows │ └── npm-publish.yml ├── LICENSE ├── scripts └── export-abi.js ├── package.json ├── test └── BalanceCheckerTest.js ├── truffle-config.js └── README.md /src/abis: -------------------------------------------------------------------------------- 1 | ../abis/ -------------------------------------------------------------------------------- /abis/SafeMath.abi.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | lib -------------------------------------------------------------------------------- /.envexample: -------------------------------------------------------------------------------- 1 | INFURA_KEY=infura key goes here 2 | MNEMONIC=mnemonic phrase goes here -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | INFURA_KEY=b2e4ff09cc2b4fa7ac1616cf1cbac54b 2 | MNEMONIC=cupboard page tone train nominee crew dragon urge empty similar bone wine -------------------------------------------------------------------------------- /migrations/1_initial_migrations.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require('./Migrations.sol'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as web3 from './web3'; 2 | import * as ethers from './ethers'; 3 | export { Options, BalanceMap, AddressBalanceMap } from './common'; 4 | export { web3, ethers }; -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const BalanceChecker = artifacts.require('./BalanceChecker.sol'); 2 | const TestToken = artifacts.require('./TestToken.sol'); 3 | 4 | module.exports = function(deployer, network) { 5 | deployer.deploy(BalanceChecker); 6 | if (network === 'development') { 7 | deployer.deploy(TestToken); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /abis/Token.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "name": "", 7 | "type": "address" 8 | } 9 | ], 10 | "name": "balanceOf", 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "uint256" 15 | } 16 | ], 17 | "payable": false, 18 | "stateMutability": "view", 19 | "type": "function" 20 | } 21 | ] -------------------------------------------------------------------------------- /contracts/TestToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol"; 4 | 5 | contract TestToken is StandardToken { 6 | string public name = "TestToken"; 7 | string public symbol = "TT"; 8 | uint8 public decimals = 2; 9 | uint public INITIAL_SUPPLY = 1000; 10 | 11 | constructor() public { 12 | totalSupply_ = INITIAL_SUPPLY; 13 | balances[msg.sender] = INITIAL_SUPPLY; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "outDir": "lib", 6 | "rootDir": "src/", 7 | "declaration": true, 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitAny": true, 12 | "resolveJsonModule": true, 13 | "esModuleInterop": true, 14 | "skipLibCheck": true, 15 | "lib": ["es2017"] 16 | }, 17 | "include": ["src"], 18 | "exclude": ["node_modules"] 19 | } -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: NPM Publish 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v2 11 | with: 12 | node-version: '14.x' 13 | registry-url: 'https://registry.npmjs.org' 14 | cache: 'yarn' 15 | - run: 'yarn' 16 | - run: 'yarn prepublish' 17 | - run: 'npm publish --access public' 18 | env: 19 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | export interface Options { 2 | contractAddress?: string; 3 | } 4 | 5 | export type BalanceMap = { 6 | [tokenAddress: string]: string; 7 | } 8 | 9 | export type AddressBalanceMap = { 10 | [address: string]: BalanceMap; 11 | } 12 | 13 | export const DEFAULT_CONTRACT_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'; 14 | 15 | export function formatAddressBalances string }>(values: T[], addresses: string[], tokens: string[]) { 16 | const balances: AddressBalanceMap = {}; 17 | addresses.forEach((addr, addrIdx) => { 18 | balances[addr] = {}; 19 | tokens.forEach((tokenAddr, tokenIdx) => { 20 | const balance = values[addrIdx * tokens.length + tokenIdx]; 21 | balances[addr][tokenAddr] = balance.toString(); 22 | }); 23 | }); 24 | return balances; 25 | } -------------------------------------------------------------------------------- /abis/BalanceChecker.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "payable": true, 4 | "stateMutability": "payable", 5 | "type": "fallback" 6 | }, 7 | { 8 | "constant": true, 9 | "inputs": [ 10 | { 11 | "name": "user", 12 | "type": "address" 13 | }, 14 | { 15 | "name": "token", 16 | "type": "address" 17 | } 18 | ], 19 | "name": "tokenBalance", 20 | "outputs": [ 21 | { 22 | "name": "", 23 | "type": "uint256" 24 | } 25 | ], 26 | "payable": false, 27 | "stateMutability": "view", 28 | "type": "function" 29 | }, 30 | { 31 | "constant": true, 32 | "inputs": [ 33 | { 34 | "name": "users", 35 | "type": "address[]" 36 | }, 37 | { 38 | "name": "tokens", 39 | "type": "address[]" 40 | } 41 | ], 42 | "name": "balances", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256[]" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | } 53 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /scripts/export-abi.js: -------------------------------------------------------------------------------- 1 | const { readdir, access, constants, mkdir, writeFile } = require("fs"); 2 | const { resolve, join } = require("path"); 3 | const { promisify } = require("util"); 4 | const ROOT_DIR = `${__dirname}/..`; 5 | const ABI_DIR = resolve(ROOT_DIR, "abis"); 6 | 7 | async function getAbis() { 8 | const readdirAsync = promisify(readdir); 9 | const CONTRACTS_DIR = resolve(ROOT_DIR, "build/contracts"); 10 | const fileNames = await readdirAsync(CONTRACTS_DIR); 11 | for (const file of fileNames) { 12 | const jsonAbi = require(join(CONTRACTS_DIR, file)); 13 | await writeAbiToFile(jsonAbi); 14 | } 15 | } 16 | 17 | function fileExists(file) { 18 | const accessAsync = promisify(access); 19 | return accessAsync(file, constants.F_OK) 20 | .then(() => true) 21 | .catch(e => false); 22 | } 23 | 24 | async function writeAbiToFile({ abi, contractName }) { 25 | const mkdirAsync = promisify(mkdir); 26 | const writeFileAsync = promisify(writeFile); 27 | if (!(await fileExists(ABI_DIR))) { 28 | await mkdirAsync(ABI_DIR); 29 | } 30 | writeFileAsync( 31 | resolve(ABI_DIR, `${contractName}.abi.json`), 32 | JSON.stringify(abi, null, 1) 33 | ); 34 | } 35 | 36 | module.exports = getAbis(); 37 | -------------------------------------------------------------------------------- /abis/Migrations.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "last_completed_migration", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "uint256" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [], 19 | "name": "owner", 20 | "outputs": [ 21 | { 22 | "name": "", 23 | "type": "address" 24 | } 25 | ], 26 | "payable": false, 27 | "stateMutability": "view", 28 | "type": "function" 29 | }, 30 | { 31 | "inputs": [], 32 | "payable": false, 33 | "stateMutability": "nonpayable", 34 | "type": "constructor" 35 | }, 36 | { 37 | "constant": false, 38 | "inputs": [ 39 | { 40 | "name": "completed", 41 | "type": "uint256" 42 | } 43 | ], 44 | "name": "setCompleted", 45 | "outputs": [], 46 | "payable": false, 47 | "stateMutability": "nonpayable", 48 | "type": "function" 49 | }, 50 | { 51 | "constant": false, 52 | "inputs": [ 53 | { 54 | "name": "new_address", 55 | "type": "address" 56 | } 57 | ], 58 | "name": "upgrade", 59 | "outputs": [], 60 | "payable": false, 61 | "stateMutability": "nonpayable", 62 | "type": "function" 63 | } 64 | ] -------------------------------------------------------------------------------- /src/ethers.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, Contract, providers, Signer } from 'ethers'; 2 | import { 3 | DEFAULT_CONTRACT_ADDRESS, 4 | Options, 5 | formatAddressBalances 6 | } from './common'; 7 | import BalanceCheckerABI from './abis/BalanceChecker.abi.json'; 8 | 9 | type Provider = providers.Provider; 10 | 11 | function getContract(provider: Provider | Signer, address?: string) { 12 | return new Contract( 13 | address || DEFAULT_CONTRACT_ADDRESS, 14 | BalanceCheckerABI, 15 | provider 16 | ); 17 | } 18 | 19 | export async function getAddressBalances( 20 | provider: Provider | Signer, 21 | address: string, 22 | tokens: string[], 23 | options: Options = {}, 24 | ) { 25 | const contract = getContract(provider, options.contractAddress); 26 | const balances = await contract.balances([address], tokens); 27 | return formatAddressBalances(balances, [address], tokens)[address]; 28 | } 29 | 30 | export async function getAddressesBalances( 31 | provider: Provider | Signer, 32 | addresses: string[], 33 | tokens: string[], 34 | options: Options = {}, 35 | ) { 36 | const contract = getContract(provider, options.contractAddress); 37 | const balances = await contract.balances(addresses, tokens); 38 | return formatAddressBalances(balances, addresses, tokens); 39 | } -------------------------------------------------------------------------------- /src/web3.ts: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import BN from 'bn.js'; 3 | import { 4 | DEFAULT_CONTRACT_ADDRESS, 5 | Options, 6 | formatAddressBalances, 7 | } from './common'; 8 | // https://github.com/ChainSafe/web3.js/issues/3310#issuecomment-701590114 9 | const BalanceCheckerABI = require('./abis/BalanceChecker.abi.json'); 10 | 11 | function getContract(provider: Web3, address?: string) { 12 | return new provider.eth.Contract( 13 | BalanceCheckerABI, 14 | address || DEFAULT_CONTRACT_ADDRESS, 15 | ); 16 | } 17 | 18 | export async function getAddressBalances( 19 | provider: Web3, 20 | address: string, 21 | tokens: string[], 22 | options: Options = {}, 23 | ) { 24 | const contract = getContract(provider, options.contractAddress); 25 | const balances = await contract.methods.balances([address], tokens).call(); 26 | return formatAddressBalances(balances, [address], tokens)[address]; 27 | } 28 | 29 | export async function getAddressesBalances( 30 | provider: Web3, 31 | addresses: string[], 32 | tokens: string[], 33 | options: Options = {}, 34 | ) { 35 | const contract = getContract(provider, options.contractAddress); 36 | const balances = await contract.methods.balances(addresses, tokens).call(); 37 | return formatAddressBalances(balances, addresses, tokens); 38 | } 39 | -------------------------------------------------------------------------------- /abis/ERC20Basic.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "name": "from", 8 | "type": "address" 9 | }, 10 | { 11 | "indexed": true, 12 | "name": "to", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": false, 17 | "name": "value", 18 | "type": "uint256" 19 | } 20 | ], 21 | "name": "Transfer", 22 | "type": "event" 23 | }, 24 | { 25 | "constant": true, 26 | "inputs": [], 27 | "name": "totalSupply", 28 | "outputs": [ 29 | { 30 | "name": "", 31 | "type": "uint256" 32 | } 33 | ], 34 | "payable": false, 35 | "stateMutability": "view", 36 | "type": "function" 37 | }, 38 | { 39 | "constant": true, 40 | "inputs": [ 41 | { 42 | "name": "_who", 43 | "type": "address" 44 | } 45 | ], 46 | "name": "balanceOf", 47 | "outputs": [ 48 | { 49 | "name": "", 50 | "type": "uint256" 51 | } 52 | ], 53 | "payable": false, 54 | "stateMutability": "view", 55 | "type": "function" 56 | }, 57 | { 58 | "constant": false, 59 | "inputs": [ 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transfer", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | } 80 | ] -------------------------------------------------------------------------------- /abis/BasicToken.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "name": "from", 8 | "type": "address" 9 | }, 10 | { 11 | "indexed": true, 12 | "name": "to", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": false, 17 | "name": "value", 18 | "type": "uint256" 19 | } 20 | ], 21 | "name": "Transfer", 22 | "type": "event" 23 | }, 24 | { 25 | "constant": true, 26 | "inputs": [], 27 | "name": "totalSupply", 28 | "outputs": [ 29 | { 30 | "name": "", 31 | "type": "uint256" 32 | } 33 | ], 34 | "payable": false, 35 | "stateMutability": "view", 36 | "type": "function" 37 | }, 38 | { 39 | "constant": false, 40 | "inputs": [ 41 | { 42 | "name": "_to", 43 | "type": "address" 44 | }, 45 | { 46 | "name": "_value", 47 | "type": "uint256" 48 | } 49 | ], 50 | "name": "transfer", 51 | "outputs": [ 52 | { 53 | "name": "", 54 | "type": "bool" 55 | } 56 | ], 57 | "payable": false, 58 | "stateMutability": "nonpayable", 59 | "type": "function" 60 | }, 61 | { 62 | "constant": true, 63 | "inputs": [ 64 | { 65 | "name": "_owner", 66 | "type": "address" 67 | } 68 | ], 69 | "name": "balanceOf", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "uint256" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "view", 78 | "type": "function" 79 | } 80 | ] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-balance-checker", 3 | "version": "0.2.0", 4 | "description": "Ethereum smart contract and library for efficient ERC20 and Ether balance checks.", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "sideEffects": false, 8 | "author": "Will O'Beirne ", 9 | "contributors": [ 10 | "Henry Nguyen" 11 | ], 12 | "license": "MIT", 13 | "homepage": "https://github.com/wbobeirne/eth-balance-checker", 14 | "bugs": { 15 | "url": "https://github.com/wbobeirne/eth-balance-checker/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/wbobeirne/eth-balance-checker.git" 20 | }, 21 | "keywords": [ 22 | "ethereum", 23 | "ether", 24 | "erc20", 25 | "balance", 26 | "solidity" 27 | ], 28 | "scripts": { 29 | "build": "npm run build:contract && npm run build:lib", 30 | "build:lib": "tsc", 31 | "build:contract": "truffle compile && node scripts/export-abi", 32 | "test": "npm run test:lib && npm run test:contract", 33 | "test:lib": "echo 'no tests yet'", 34 | "test:contract": "truffle test", 35 | "prepublish": "npm run build" 36 | }, 37 | "devDependencies": { 38 | "@truffle/hdwallet-provider": "^2.0.4", 39 | "chai": "^4.3.6", 40 | "dotenv": "^16.0.0", 41 | "eth-gas-reporter": "^0.2.24", 42 | "ethers": "^5.6.0", 43 | "openzeppelin-solidity": "^1.12.0", 44 | "truffle": "^5.5.4", 45 | "typescript": "^4.6.2", 46 | "web3": "^1.7.1" 47 | }, 48 | "peerDependencies": { 49 | "ethers": "^5.4.0", 50 | "web3": "^1.4.0" 51 | }, 52 | "files": [ 53 | "lib", 54 | "abis", 55 | "contracts" 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /test/BalanceCheckerTest.js: -------------------------------------------------------------------------------- 1 | const BalanceChecker = artifacts.require("BalanceChecker"); 2 | const TestToken = artifacts.require("TestToken"); 3 | 4 | contract("BalanceChecker", accounts => { 5 | let balanceChecker; 6 | let testToken; 7 | 8 | before(async () => { 9 | balanceChecker = await BalanceChecker.new({ from: accounts[0] }); 10 | testToken = await TestToken.new({ from: accounts[0] }); 11 | }); 12 | 13 | it("deploys BalanceChecker", async () => { 14 | assert.ok(balanceChecker); 15 | }); 16 | 17 | it("Correctly checks an ETH balance", async () => { 18 | const balance = web3.eth.getBalance(accounts[0]); 19 | const balances = await balanceChecker.balances.call( 20 | [accounts[0], accounts[1]], 21 | ["0x0"] 22 | ); 23 | assert.ok(balances[0]); 24 | assert.equal( 25 | balance.toString(), 26 | web3.eth.getBalance(accounts[0]).toString() 27 | ); 28 | }); 29 | 30 | it("Correctly checks a token balance", async () => { 31 | const tokenBalance = await testToken.balanceOf(accounts[0]); 32 | const balances = await balanceChecker.balances.call( 33 | [accounts[0]], 34 | [testToken.address], 35 | ); 36 | assert.ok(balances[0]); 37 | assert.equal(tokenBalance.toString(), balances[0].toString()); 38 | }); 39 | 40 | it("Returns zero balance for a non-contract address", async () => { 41 | const tokenBalance = await testToken.balanceOf(accounts[0]); 42 | const balances = await balanceChecker.balances.call( 43 | [accounts[0]], 44 | [accounts[0]], 45 | ); 46 | assert.ok(balances[0].isZero()); 47 | }); 48 | 49 | it("Returns zero balance for a contract that doesn't implement balanceOf", async () => { 50 | const tokenBalance = await testToken.balanceOf(accounts[0]); 51 | const balances = await balanceChecker.balances.call( 52 | [accounts[0]], 53 | [balanceChecker.address], 54 | ); 55 | assert.ok(balances[0].isZero()); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /contracts/BalanceChecker.sol: -------------------------------------------------------------------------------- 1 | // Built off of https://github.com/DeltaBalances/DeltaBalances.github.io/blob/master/smart_contract/deltabalances.sol 2 | pragma solidity ^0.4.21; 3 | 4 | // ERC20 contract interface 5 | contract Token { 6 | function balanceOf(address) public view returns (uint); 7 | } 8 | 9 | contract BalanceChecker { 10 | /* Fallback function, don't accept any ETH */ 11 | function() public payable { 12 | revert("BalanceChecker does not accept payments"); 13 | } 14 | 15 | /* 16 | Check the token balance of a wallet in a token contract 17 | 18 | Returns the balance of the token for user. Avoids possible errors: 19 | - return 0 on non-contract address 20 | - returns 0 if the contract doesn't implement balanceOf 21 | */ 22 | function tokenBalance(address user, address token) public view returns (uint) { 23 | // check if token is actually a contract 24 | uint256 tokenCode; 25 | assembly { tokenCode := extcodesize(token) } // contract code size 26 | 27 | // is it a contract and does it implement balanceOf 28 | if (tokenCode > 0 && token.call(bytes4(0x70a08231), user)) { 29 | return Token(token).balanceOf(user); 30 | } else { 31 | return 0; 32 | } 33 | } 34 | 35 | /* 36 | Check the token balances of a wallet for multiple tokens. 37 | Pass 0x0 as a "token" address to get ETH balance. 38 | 39 | Possible error throws: 40 | - extremely large arrays for user and or tokens (gas cost too high) 41 | 42 | Returns a one-dimensional that's user.length * tokens.length long. The 43 | array is ordered by all of the 0th users token balances, then the 1th 44 | user, and so on. 45 | */ 46 | function balances(address[] users, address[] tokens) external view returns (uint[]) { 47 | uint[] memory addrBalances = new uint[](tokens.length * users.length); 48 | 49 | for(uint i = 0; i < users.length; i++) { 50 | for (uint j = 0; j < tokens.length; j++) { 51 | uint addrIdx = j + tokens.length * i; 52 | if (tokens[j] != address(0x0)) { 53 | addrBalances[addrIdx] = tokenBalance(users[i], tokens[j]); 54 | } else { 55 | addrBalances[addrIdx] = users[i].balance; // ETH balance 56 | } 57 | } 58 | } 59 | 60 | return addrBalances; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /abis/ERC20.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "totalSupply", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "uint256" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [ 19 | { 20 | "name": "_who", 21 | "type": "address" 22 | } 23 | ], 24 | "name": "balanceOf", 25 | "outputs": [ 26 | { 27 | "name": "", 28 | "type": "uint256" 29 | } 30 | ], 31 | "payable": false, 32 | "stateMutability": "view", 33 | "type": "function" 34 | }, 35 | { 36 | "constant": false, 37 | "inputs": [ 38 | { 39 | "name": "_to", 40 | "type": "address" 41 | }, 42 | { 43 | "name": "_value", 44 | "type": "uint256" 45 | } 46 | ], 47 | "name": "transfer", 48 | "outputs": [ 49 | { 50 | "name": "", 51 | "type": "bool" 52 | } 53 | ], 54 | "payable": false, 55 | "stateMutability": "nonpayable", 56 | "type": "function" 57 | }, 58 | { 59 | "anonymous": false, 60 | "inputs": [ 61 | { 62 | "indexed": true, 63 | "name": "owner", 64 | "type": "address" 65 | }, 66 | { 67 | "indexed": true, 68 | "name": "spender", 69 | "type": "address" 70 | }, 71 | { 72 | "indexed": false, 73 | "name": "value", 74 | "type": "uint256" 75 | } 76 | ], 77 | "name": "Approval", 78 | "type": "event" 79 | }, 80 | { 81 | "anonymous": false, 82 | "inputs": [ 83 | { 84 | "indexed": true, 85 | "name": "from", 86 | "type": "address" 87 | }, 88 | { 89 | "indexed": true, 90 | "name": "to", 91 | "type": "address" 92 | }, 93 | { 94 | "indexed": false, 95 | "name": "value", 96 | "type": "uint256" 97 | } 98 | ], 99 | "name": "Transfer", 100 | "type": "event" 101 | }, 102 | { 103 | "constant": true, 104 | "inputs": [ 105 | { 106 | "name": "_owner", 107 | "type": "address" 108 | }, 109 | { 110 | "name": "_spender", 111 | "type": "address" 112 | } 113 | ], 114 | "name": "allowance", 115 | "outputs": [ 116 | { 117 | "name": "", 118 | "type": "uint256" 119 | } 120 | ], 121 | "payable": false, 122 | "stateMutability": "view", 123 | "type": "function" 124 | }, 125 | { 126 | "constant": false, 127 | "inputs": [ 128 | { 129 | "name": "_from", 130 | "type": "address" 131 | }, 132 | { 133 | "name": "_to", 134 | "type": "address" 135 | }, 136 | { 137 | "name": "_value", 138 | "type": "uint256" 139 | } 140 | ], 141 | "name": "transferFrom", 142 | "outputs": [ 143 | { 144 | "name": "", 145 | "type": "bool" 146 | } 147 | ], 148 | "payable": false, 149 | "stateMutability": "nonpayable", 150 | "type": "function" 151 | }, 152 | { 153 | "constant": false, 154 | "inputs": [ 155 | { 156 | "name": "_spender", 157 | "type": "address" 158 | }, 159 | { 160 | "name": "_value", 161 | "type": "uint256" 162 | } 163 | ], 164 | "name": "approve", 165 | "outputs": [ 166 | { 167 | "name": "", 168 | "type": "bool" 169 | } 170 | ], 171 | "payable": false, 172 | "stateMutability": "nonpayable", 173 | "type": "function" 174 | } 175 | ] -------------------------------------------------------------------------------- /abis/StandardToken.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "totalSupply", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "uint256" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [ 19 | { 20 | "name": "_owner", 21 | "type": "address" 22 | } 23 | ], 24 | "name": "balanceOf", 25 | "outputs": [ 26 | { 27 | "name": "", 28 | "type": "uint256" 29 | } 30 | ], 31 | "payable": false, 32 | "stateMutability": "view", 33 | "type": "function" 34 | }, 35 | { 36 | "constant": false, 37 | "inputs": [ 38 | { 39 | "name": "_to", 40 | "type": "address" 41 | }, 42 | { 43 | "name": "_value", 44 | "type": "uint256" 45 | } 46 | ], 47 | "name": "transfer", 48 | "outputs": [ 49 | { 50 | "name": "", 51 | "type": "bool" 52 | } 53 | ], 54 | "payable": false, 55 | "stateMutability": "nonpayable", 56 | "type": "function" 57 | }, 58 | { 59 | "anonymous": false, 60 | "inputs": [ 61 | { 62 | "indexed": true, 63 | "name": "owner", 64 | "type": "address" 65 | }, 66 | { 67 | "indexed": true, 68 | "name": "spender", 69 | "type": "address" 70 | }, 71 | { 72 | "indexed": false, 73 | "name": "value", 74 | "type": "uint256" 75 | } 76 | ], 77 | "name": "Approval", 78 | "type": "event" 79 | }, 80 | { 81 | "anonymous": false, 82 | "inputs": [ 83 | { 84 | "indexed": true, 85 | "name": "from", 86 | "type": "address" 87 | }, 88 | { 89 | "indexed": true, 90 | "name": "to", 91 | "type": "address" 92 | }, 93 | { 94 | "indexed": false, 95 | "name": "value", 96 | "type": "uint256" 97 | } 98 | ], 99 | "name": "Transfer", 100 | "type": "event" 101 | }, 102 | { 103 | "constant": false, 104 | "inputs": [ 105 | { 106 | "name": "_from", 107 | "type": "address" 108 | }, 109 | { 110 | "name": "_to", 111 | "type": "address" 112 | }, 113 | { 114 | "name": "_value", 115 | "type": "uint256" 116 | } 117 | ], 118 | "name": "transferFrom", 119 | "outputs": [ 120 | { 121 | "name": "", 122 | "type": "bool" 123 | } 124 | ], 125 | "payable": false, 126 | "stateMutability": "nonpayable", 127 | "type": "function" 128 | }, 129 | { 130 | "constant": false, 131 | "inputs": [ 132 | { 133 | "name": "_spender", 134 | "type": "address" 135 | }, 136 | { 137 | "name": "_value", 138 | "type": "uint256" 139 | } 140 | ], 141 | "name": "approve", 142 | "outputs": [ 143 | { 144 | "name": "", 145 | "type": "bool" 146 | } 147 | ], 148 | "payable": false, 149 | "stateMutability": "nonpayable", 150 | "type": "function" 151 | }, 152 | { 153 | "constant": true, 154 | "inputs": [ 155 | { 156 | "name": "_owner", 157 | "type": "address" 158 | }, 159 | { 160 | "name": "_spender", 161 | "type": "address" 162 | } 163 | ], 164 | "name": "allowance", 165 | "outputs": [ 166 | { 167 | "name": "", 168 | "type": "uint256" 169 | } 170 | ], 171 | "payable": false, 172 | "stateMutability": "view", 173 | "type": "function" 174 | }, 175 | { 176 | "constant": false, 177 | "inputs": [ 178 | { 179 | "name": "_spender", 180 | "type": "address" 181 | }, 182 | { 183 | "name": "_addedValue", 184 | "type": "uint256" 185 | } 186 | ], 187 | "name": "increaseApproval", 188 | "outputs": [ 189 | { 190 | "name": "", 191 | "type": "bool" 192 | } 193 | ], 194 | "payable": false, 195 | "stateMutability": "nonpayable", 196 | "type": "function" 197 | }, 198 | { 199 | "constant": false, 200 | "inputs": [ 201 | { 202 | "name": "_spender", 203 | "type": "address" 204 | }, 205 | { 206 | "name": "_subtractedValue", 207 | "type": "uint256" 208 | } 209 | ], 210 | "name": "decreaseApproval", 211 | "outputs": [ 212 | { 213 | "name": "", 214 | "type": "bool" 215 | } 216 | ], 217 | "payable": false, 218 | "stateMutability": "nonpayable", 219 | "type": "function" 220 | } 221 | ] -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * trufflesuite.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | require('dotenv').config(); 22 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 23 | const infuraKey = process.env.INFURA_KEY; 24 | const mnemonic = process.env.MNEMONIC; 25 | 26 | module.exports = { 27 | /** 28 | * Networks define how you connect to your ethereum client and let you set the 29 | * defaults web3 uses to send transactions. If you don't specify one truffle 30 | * will spin up a development blockchain for you on port 9545 when you 31 | * run `develop` or `test`. You can ask a truffle command to use a specific 32 | * network from the command line, e.g 33 | * 34 | * $ truffle test --network 35 | */ 36 | 37 | networks: { 38 | // Useful for testing. The `development` name is special - truffle uses it by default 39 | // if it's defined here and no other network is specified at the command line. 40 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 41 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 42 | // options below to some value. 43 | // 44 | development: { 45 | host: "127.0.0.1", // Localhost (default: none) 46 | port: 8545, // Standard Ethereum port (default: none) 47 | network_id: "*", // Any network (default: none) 48 | }, 49 | // Another network with more advanced options... 50 | // advanced: { 51 | // port: 8777, // Custom port 52 | // network_id: 1342, // Custom network 53 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 54 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 55 | // from:
, // Account to send txs from (default: accounts[0]) 56 | // websocket: true // Enable EventEmitter interface for web3 (default: false) 57 | // }, 58 | // Useful for deploying to a public network. 59 | // NB: It's important to wrap the provider as a function. 60 | // ropsten: { 61 | // provider: function () { return new HDWallet(mnemonic, 'https://ropsten.infura.io/' + infuraKey) }, 62 | // network_id: 3, // Ropsten's id 63 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 64 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 65 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 66 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 67 | // }, 68 | // Useful for private networks 69 | // private: { 70 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 71 | // network_id: 2111, // This network is yours, in the cloud. 72 | // production: true // Treats this network as if it was a public net. (default: false) 73 | // } 74 | }, 75 | 76 | // Set default mocha options here, use special reporters etc. 77 | mocha: { 78 | reporter: "eth-gas-reporter", 79 | reporterOptions: { 80 | currency: "USD", 81 | gasPrice: 21 82 | } 83 | }, 84 | 85 | // Configure your compilers 86 | compilers: { 87 | solc: { 88 | version: "0.4.24", // Fetch exact version from solc-bin (default: truffle's version) 89 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 90 | // settings: { // See the solidity docs for advice about optimization and evmVersion 91 | // optimizer: { 92 | // enabled: false, 93 | // runs: 200 94 | // }, 95 | // evmVersion: "byzantium" 96 | // } 97 | } 98 | }, 99 | 100 | // Truffle DB is currently disabled by default; to enable it, change enabled: false to enabled: true 101 | // 102 | // Note: if you migrated your contracts prior to enabling this field in your Truffle project and want 103 | // those previously migrated contracts available in the .db directory, you will need to run the following: 104 | // $ truffle migrate --reset --compile-all 105 | 106 | db: { 107 | enabled: false 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /abis/TestToken.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "INITIAL_SUPPLY", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint256" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [], 97 | "name": "decimals", 98 | "outputs": [ 99 | { 100 | "name": "", 101 | "type": "uint8" 102 | } 103 | ], 104 | "payable": false, 105 | "stateMutability": "view", 106 | "type": "function" 107 | }, 108 | { 109 | "constant": false, 110 | "inputs": [ 111 | { 112 | "name": "_spender", 113 | "type": "address" 114 | }, 115 | { 116 | "name": "_subtractedValue", 117 | "type": "uint256" 118 | } 119 | ], 120 | "name": "decreaseApproval", 121 | "outputs": [ 122 | { 123 | "name": "", 124 | "type": "bool" 125 | } 126 | ], 127 | "payable": false, 128 | "stateMutability": "nonpayable", 129 | "type": "function" 130 | }, 131 | { 132 | "constant": true, 133 | "inputs": [ 134 | { 135 | "name": "_owner", 136 | "type": "address" 137 | } 138 | ], 139 | "name": "balanceOf", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "uint256" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "view", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [], 153 | "name": "symbol", 154 | "outputs": [ 155 | { 156 | "name": "", 157 | "type": "string" 158 | } 159 | ], 160 | "payable": false, 161 | "stateMutability": "view", 162 | "type": "function" 163 | }, 164 | { 165 | "constant": false, 166 | "inputs": [ 167 | { 168 | "name": "_to", 169 | "type": "address" 170 | }, 171 | { 172 | "name": "_value", 173 | "type": "uint256" 174 | } 175 | ], 176 | "name": "transfer", 177 | "outputs": [ 178 | { 179 | "name": "", 180 | "type": "bool" 181 | } 182 | ], 183 | "payable": false, 184 | "stateMutability": "nonpayable", 185 | "type": "function" 186 | }, 187 | { 188 | "constant": false, 189 | "inputs": [ 190 | { 191 | "name": "_spender", 192 | "type": "address" 193 | }, 194 | { 195 | "name": "_addedValue", 196 | "type": "uint256" 197 | } 198 | ], 199 | "name": "increaseApproval", 200 | "outputs": [ 201 | { 202 | "name": "", 203 | "type": "bool" 204 | } 205 | ], 206 | "payable": false, 207 | "stateMutability": "nonpayable", 208 | "type": "function" 209 | }, 210 | { 211 | "constant": true, 212 | "inputs": [ 213 | { 214 | "name": "_owner", 215 | "type": "address" 216 | }, 217 | { 218 | "name": "_spender", 219 | "type": "address" 220 | } 221 | ], 222 | "name": "allowance", 223 | "outputs": [ 224 | { 225 | "name": "", 226 | "type": "uint256" 227 | } 228 | ], 229 | "payable": false, 230 | "stateMutability": "view", 231 | "type": "function" 232 | }, 233 | { 234 | "inputs": [], 235 | "payable": false, 236 | "stateMutability": "nonpayable", 237 | "type": "constructor" 238 | }, 239 | { 240 | "anonymous": false, 241 | "inputs": [ 242 | { 243 | "indexed": true, 244 | "name": "owner", 245 | "type": "address" 246 | }, 247 | { 248 | "indexed": true, 249 | "name": "spender", 250 | "type": "address" 251 | }, 252 | { 253 | "indexed": false, 254 | "name": "value", 255 | "type": "uint256" 256 | } 257 | ], 258 | "name": "Approval", 259 | "type": "event" 260 | }, 261 | { 262 | "anonymous": false, 263 | "inputs": [ 264 | { 265 | "indexed": true, 266 | "name": "from", 267 | "type": "address" 268 | }, 269 | { 270 | "indexed": true, 271 | "name": "to", 272 | "type": "address" 273 | }, 274 | { 275 | "indexed": false, 276 | "name": "value", 277 | "type": "uint256" 278 | } 279 | ], 280 | "name": "Transfer", 281 | "type": "event" 282 | } 283 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

Ethereum Balance Checker

4 |

5 | 6 | A smart contract and library pair that allows you to check for multiple ERC20 7 | and Ether balances across multiple addresses in a single RPC call. 8 | 9 | ## Demo 10 | 11 | You can find a demo over here: https://wbobeirne.github.io/eth-balance-checker-demo 12 | 13 | The source for that demo is available here: https://github.com/wbobeirne/eth-balance-checker-demo 14 | 15 | ## NPM Package 16 | 17 | ``` 18 | npm install --save eth-balance-checker 19 | # OR 20 | yarn add eth-balance-checker 21 | ``` 22 | 23 | ### Contract 24 | 25 | The main contract is in `contracts/BalanceChecker.sol`. Its associated ABI is in `abis/BalanceChecker.abi.json`. Both of these are included in the NPM package if you want to compile them yourself, or extend them with another 26 | contract. 27 | 28 | #### Deployed Addresses 29 | 30 | - mainnet: `0xb1f8e55c7f64d203c1400b9d8555d050f94adf39` 31 | - ropsten: `0x8D9708f3F514206486D7E988533f770a16d074a7` 32 | - rinkeby: `0x3183B673f4816C94BeF53958BaF93C671B7F8Cf2` 33 | - kovan: `0x55ABBa8d669D60A10c104CC493ec5ef389EC92bb` 34 | - goerli: `0x9788C4E93f9002a7ad8e72633b11E8d1ecd51f9b` 35 | - binance smart chain mainnet: `0x2352c63A83f9Fd126af8676146721Fa00924d7e4` 36 | - binance smart chain testnet: `0x2352c63A83f9Fd126af8676146721Fa00924d7e4` 37 | - polygon: `0x2352c63A83f9Fd126af8676146721Fa00924d7e4` 38 | - mumbai: `0x2352c63A83f9Fd126af8676146721Fa00924d7e4` 39 | - Optimism: `0xB1c568e9C3E6bdaf755A60c7418C269eb11524FC` 40 | - Optimism Kovan: `0xB1c568e9C3E6bdaf755A60c7418C269eb11524FC` 41 | - arbitrum: `0x151E24A486D7258dd7C33Fb67E4bB01919B7B32c` 42 | - avalanche: `0xD023D153a0DFa485130ECFdE2FAA7e612EF94818` and `0xE0baF851F839874141bB73327f9C606147a52358` 43 | - fantom: `0x07f697424ABe762bB808c109860c04eA488ff92B` and `0xfC701A6b65e1BcF59fb3BDbbe5cb41f35FC7E009` 44 | - ether-classic: `0xfC701A6b65e1BcF59fb3BDbbe5cb41f35FC7E009` 45 | - Tron: `TN8RtFXeQZyFHGmH1iiSRm5r4CRz1yWkCf` --> Yes, it also works in Tron! Use hex addresses format to interact with it (41...). 46 | 47 | ### Library 48 | 49 | There are separate libraries for [web3.js](https://github.com/ethereum/web3.js/) 50 | and [ethers.js](https://github.com/ethers-io/ethers.js/), both with identical 51 | APIs. Just import the functions from either `eth-balance-checker/lib/web3` or 52 | `eth-balance-checker/lib/ethers`. For all functions, pass `"0x0"` as the "token" 53 | address to get the ether balance of an address. 54 | 55 | All functions also take in an optional 4th options parameter, those options are as follows: 56 | 57 | ```typescript 58 | interface Options { 59 | // Choose a custom contract address. Must be provided to run the 60 | // code on non-mainnet network. 61 | contractAddress?: string; 62 | } 63 | ``` 64 | 65 | #### getAddressBalances 66 | 67 | ##### Parameters 68 | * `provider: Web3 | Ethers.Provider` - The provider to use for the contract call. 69 | * `address: string` - The address to lookup balances for 70 | * `tokens: string[]` - Array of token contract addresses. Only supports ERC20 tokens. 71 | * `options?: Options` - Options for the contract, see above for options. 72 | 73 | ##### Returns 74 | ```js 75 | Promise<{ 76 | // Ether balance 77 | "0x0": "100", 78 | // Token balances 79 | "0x123...": "500", 80 | "0x456...": "100000", 81 | ... 82 | }> 83 | ``` 84 | 85 | ##### Example 86 | ```ts 87 | import Web3 from 'web3'; 88 | import { getAddressBalances } from 'eth-balance-checker/lib/web3'; 89 | 90 | const web3 = new Web3(...); 91 | const address = '0x123...'; 92 | const tokens = ['0x0', '0x456...']; 93 | getAddressBalances(web3, address, tokens).then(balances => { 94 | console.log(balances); // { "0x0": "100", "0x456...": "200" } 95 | }); 96 | ``` 97 | 98 | #### getAddressesBalances 99 | 100 | ##### Parameters 101 | * `provider: Web3 | Ethers.Provider` - The provider to use for the contract call. 102 | * `addresses: string[]` - Array of addresses to lookup balances for. 103 | * `tokens: string[]` - Array of token contract addresses. Only supports ERC20 tokens. 104 | * `options?: Options` - Options for the contract, see above for options. 105 | 106 | ##### Returns 107 | ```js 108 | Promise<{ 109 | // Address as the key 110 | "0x123...": { 111 | // Ether balance 112 | "0x0": "100", 113 | // Token balances 114 | "0x456...": "500", 115 | "0x789...": "10000", 116 | ... 117 | }, 118 | ... 119 | }> 120 | ``` 121 | 122 | ##### Example 123 | ```ts 124 | import * as Ethers from 'ethers'; 125 | import { getAddressesBalances } from 'eth-balance-checker/lib/ethers'; 126 | 127 | const ethers = Ethers.getDefaultProvider(); 128 | const addresses = ['0x123...', '0x456...']; 129 | const tokens = ['0x0', '0x789...']; 130 | getAddressesBalances(ethers, addresses, tokens).then(balances => { 131 | console.log(balances); // { "0x123...": { "0x0": "100", ... }, ... } 132 | }); 133 | ``` 134 | 135 | ## Development 136 | 137 | ### Setup 138 | 139 | Requires node 8+. Just install packages, then use commands as needed: 140 | 141 | ```bash 142 | npm install 143 | # OR 144 | yarn 145 | ``` 146 | 147 | ### Commands 148 | 149 | * `build:contract` - Runs `truffle compile` on the contract, and extracts ABI. 150 | * `build:lib` - Runs `tsc` and outputs js and typedefs to `lib/` 151 | * `build` - Runs `build:contract` then `build:lib` 152 | * `test:contract` - Runs `truffle test` 153 | * `test:lib` - No tests implemented yet 154 | * `test` - Runs `tst:contract` and `test:lib` 155 | 156 | ## Credits 157 | 158 | * Thanks to [@henrynguyen5](https://github.com/henrynguyen5) for adapting 159 | [@DeltaBalances](https://github.com/DeltaBalances)' smart contract for this 160 | * This library came out of EthSanFrancisco from the 161 | [Safu Chrome Extension](https://github.com/grant-project/safu-extension) project. 162 | --------------------------------------------------------------------------------