- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
Learn how to fully transfer your ERC-20 token balance from one Ethereum wallet to another using MetaMask and raw JavaScript, leaving no residual dust. This tutorial walks you through the process using Chrome DevTools.
Prerequisite: Fund the Wallet With ETH
ERC-20 token transfers require ETH to pay gas fees.
? How Much ETH Do You Need?
To be safe, fund the source wallet with ~0.01 ETH. If gas is expensive, wait for a weekend or late night to execute the transaction.
? Step-by-Step: Transfer Full ERC-20 Balance
Step 1: Open Chrome and Unlock MetaMask
Log in to MetaMask using your source wallet (the one holding the ERC-20 balance and ETH).
Step 2: Open DevTools → Console (F12 or Ctrl+Shift+I)
In the Console tab, type:
window.ethereum
If it's undefined, follow the fixes below.
? Fixing MetaMask Injection Issues
If window.ethereum is not available:
Check MetaMask Extension
Go to chrome://extensions/ and verify:
Step 2: Use a Real HTTPS Page
MetaMask doesn't inject into:
Step 3: Ensure MetaMask Permissions
Step 3: Paste This Script in DevTools
Once window.ethereum is available, paste the following script into the console:
(async () => {
const tokenAddress = "0xERC20ContractAddress"; // Replace with actual ERC-20 contract
const targetAddress = "0xTargetAddress"; // Destination address
const [sourceWallet] = await ethereum.request({ method: 'eth_requestAccounts' });
// 1. Fetch token decimals
const decimalsHex = await ethereum.request({
method: 'eth_call',
params: [{ to: tokenAddress, data: '0x313ce567' }, 'latest']
});
const decimals = parseInt(decimalsHex, 16);
console.log("Decimals:", decimals);
// 2. Fetch token balance
const paddedAddr = sourceWallet.slice(2).padStart(64, '0');
const balanceHex = await ethereum.request({
method: 'eth_call',
params: [{ to: tokenAddress, data: '0x70a08231' + paddedAddr }, 'latest']
});
const balance = BigInt(balanceHex);
if (balance === 0n) return console.log("No ERC-20 token to transfer.");
console.log("Raw balance:", balance.toString());
// 3. Build transfer() data
const toPadded = targetAddress.slice(2).padStart(64, '0');
const valuePadded = balance.toString(16).padStart(64, '0');
const txData = '0xa9059cbb' + toPadded + valuePadded;
// 4. Send transaction
const txHash = await ethereum.request({
method: 'eth_sendTransaction',
params: [{
from: sourceWallet,
to: tokenAddress,
data: txData
}]
});
console.log("
ERC-20 full transfer sent. TX Hash:", txHash);
})();
? Deep Dive: Understanding eth_call, Function Selectors, and ABI Encoding
After you paste and run the script, it may seem magical — but here's how it works under the hood.
? What is eth_call?
eth_call is an Ethereum JSON-RPC method that simulates a contract call locally without broadcasting a real transaction. It lets us read data from smart contracts (like balances or decimals) without paying gas.
For example:
const decimalsHex = await ethereum.request({
method: 'eth_call',
params: [{ to: tokenAddress, data: '0x313ce567' }, 'latest']
});
This retrieves the number of decimals used by the token by calling its decimals() function.
? What is data: 0x313ce567?
Ethereum smart contracts use a 4-byte function selector to identify which function to call. This selector is calculated as the first 4 bytes of the keccak256 hash of the function signature (name + parameter types).
Example:
You can generate these with tools like:
Just paste in the raw signature string, like "balanceOf(address)"
? ABI Argument Encoding
Smart contract arguments must be padded to 32 bytes (64 hex characters). For example:
If calling balanceOf("0x1234...5678"), the full data field is:
0x70a08231
+ 0000000000000000000000001234567890abcdef1234567890abcdef12345678
To get the ERC-20 token balance:
const paddedAddr = wallet.slice(2).padStart(64, '0');
const data = '0x70a08231' + paddedAddr;
To send the full balance via transfer():
const toPadded = target.slice(2).padStart(64, '0');
const amountPadded = balance.toString(16).padStart(64, '0');
const txData = '0xa9059cbb' + toPadded + amountPadded;
Understanding this encoding lets you interact with any smart contract method manually — without relying on third-party libraries.
Confirm and Sign in MetaMask
MetaMask will prompt you to approve the token transfer.
Once confirmed and mined, your ERC-20 balance will be fully transferred, with no dust left behind.
? Why Not Just Use Wallet UIs?
Most wallets (like MetaMask, Rabby, etc.) only transfer ERC20 token up to certain digits such as 6 digits, anything else will be leftover in the source wallet as a small "dust". That’s why they can’t "sweep" everything.
By using raw eth_call and manual ABI encoding, you can:
Whether you're rotating wallets, archiving an old address, or just OCD about balance dust — this technique gives you full control.
? Clean wallet, clean mind.
As a prerequisite, you may want to review my earlier guide: .
ERC-20 token transfers require ETH to pay gas fees.
? How Much ETH Do You Need?
| Action | Gas Estimate | ETH at 25 gwei | ETH at 40 gwei |
|---|---|---|---|
| ERC-20 transfer | ~50,000–60,000 gas | ~0.00125 ETH | ~0.002 ETH |
To be safe, fund the source wallet with ~0.01 ETH. If gas is expensive, wait for a weekend or late night to execute the transaction.
? Step-by-Step: Transfer Full ERC-20 Balance
Log in to MetaMask using your source wallet (the one holding the ERC-20 balance and ETH).
In the Console tab, type:
window.ethereum
If it's undefined, follow the fixes below.
? Fixing MetaMask Injection Issues
If window.ethereum is not available:
Go to chrome://extensions/ and verify:
- MetaMask is installed
- It’s enabled
- Not in an error or paused state
MetaMask doesn't inject into:
- file:// pages
- Some localhost pages
- Blank tabs or popups
- Use a trusted site like:
- Click the MetaMask icon
- If it says "Connect to site", click Connect
- Refresh the page
Once window.ethereum is available, paste the following script into the console:
(async () => {
const tokenAddress = "0xERC20ContractAddress"; // Replace with actual ERC-20 contract
const targetAddress = "0xTargetAddress"; // Destination address
const [sourceWallet] = await ethereum.request({ method: 'eth_requestAccounts' });
// 1. Fetch token decimals
const decimalsHex = await ethereum.request({
method: 'eth_call',
params: [{ to: tokenAddress, data: '0x313ce567' }, 'latest']
});
const decimals = parseInt(decimalsHex, 16);
console.log("Decimals:", decimals);
// 2. Fetch token balance
const paddedAddr = sourceWallet.slice(2).padStart(64, '0');
const balanceHex = await ethereum.request({
method: 'eth_call',
params: [{ to: tokenAddress, data: '0x70a08231' + paddedAddr }, 'latest']
});
const balance = BigInt(balanceHex);
if (balance === 0n) return console.log("No ERC-20 token to transfer.");
console.log("Raw balance:", balance.toString());
// 3. Build transfer() data
const toPadded = targetAddress.slice(2).padStart(64, '0');
const valuePadded = balance.toString(16).padStart(64, '0');
const txData = '0xa9059cbb' + toPadded + valuePadded;
// 4. Send transaction
const txHash = await ethereum.request({
method: 'eth_sendTransaction',
params: [{
from: sourceWallet,
to: tokenAddress,
data: txData
}]
});
console.log("
})();
? Deep Dive: Understanding eth_call, Function Selectors, and ABI Encoding
After you paste and run the script, it may seem magical — but here's how it works under the hood.
? What is eth_call?
eth_call is an Ethereum JSON-RPC method that simulates a contract call locally without broadcasting a real transaction. It lets us read data from smart contracts (like balances or decimals) without paying gas.
For example:
const decimalsHex = await ethereum.request({
method: 'eth_call',
params: [{ to: tokenAddress, data: '0x313ce567' }, 'latest']
});
This retrieves the number of decimals used by the token by calling its decimals() function.
? What is data: 0x313ce567?
Ethereum smart contracts use a 4-byte function selector to identify which function to call. This selector is calculated as the first 4 bytes of the keccak256 hash of the function signature (name + parameter types).
Example:
- Function: decimals()
- Signature: "decimals()"
- keccak256: 0x313ce567add4d438edf58b94ff345d7d38c45b17dfc0f947988d7819dca364f9
- Selector: 0x313ce567 So data: "0x313ce567" tells the contract, “please execute decimals().”
| Function | Signature | Selector |
|---|---|---|
| Get token decimals | decimals() | 0x313ce567 |
| Get token balance | balanceOf(address) | 0x70a08231 |
| Transfer tokens | transfer(address,uint256) | 0xa9059cbb |
You can generate these with tools like:
Just paste in the raw signature string, like "balanceOf(address)"
? ABI Argument Encoding
Smart contract arguments must be padded to 32 bytes (64 hex characters). For example:
If calling balanceOf("0x1234...5678"), the full data field is:
0x70a08231
+ 0000000000000000000000001234567890abcdef1234567890abcdef12345678
- The first part 0x70a08231 is the function selector
- The second part is the address, left-padded with zeros to 32 bytes
To get the ERC-20 token balance:
const paddedAddr = wallet.slice(2).padStart(64, '0');
const data = '0x70a08231' + paddedAddr;
To send the full balance via transfer():
const toPadded = target.slice(2).padStart(64, '0');
const amountPadded = balance.toString(16).padStart(64, '0');
const txData = '0xa9059cbb' + toPadded + amountPadded;
Understanding this encoding lets you interact with any smart contract method manually — without relying on third-party libraries.
MetaMask will prompt you to approve the token transfer.
Once confirmed and mined, your ERC-20 balance will be fully transferred, with no dust left behind.
? Why Not Just Use Wallet UIs?
Most wallets (like MetaMask, Rabby, etc.) only transfer ERC20 token up to certain digits such as 6 digits, anything else will be leftover in the source wallet as a small "dust". That’s why they can’t "sweep" everything.
By using raw eth_call and manual ABI encoding, you can:
- Move the exact token amount
- Pay only the required gas
- Leave nothing behind
Whether you're rotating wallets, archiving an old address, or just OCD about balance dust — this technique gives you full control.
? Clean wallet, clean mind.
As a prerequisite, you may want to review my earlier guide: .