feat(cast): erc20 safe preflight
Motivation
Closes #12402
Enhance UX and prevent accidental transfers with incorrect amounts. Users may misunderstand the token's decimal places and accidentally send far more (or less) tokens than intended.
Also, approve() requires identical feature, so I decided to add the same.
At the other hand, transferFrom() - doesn't need that, because this method is rarely called by users implicitly, more often him calls other smartcontracts (like swapRouter).
Solution
Added interactive confirmation prompts to cast erc20 transfer and cast erc20 approve commands that:
-
Fetch token metadata (symbol and decimals) from the ERC20 contract
-
Display human-readable amounts in the confirmation prompt: Example: Confirm transfer of 100 USDC to address 0x666...666 instead of raw 100000000
-
Provide --yes flag to skip confirmation for non-interactive usage (scripts, CI/CD)
-
Handle edge cases gracefully: -Falls back to raw amount if decimals/symbol can't be fetched -Shows warning if token metadata is unavailable
Demonstration
Let's create a test token first:
forge create crates/cast/tests/fixtures/TestToken.sol:TestToken --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://localhost:8545 --broadcast
Then we can test behavior:
./target/debug/cast erc20 transfer 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 100000000000000000000 --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
---
Confirm transfer of 100 TEST to address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 [y/n]
In case y (yes):
Confirm transfer of 100 TEST to address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 yes
---
0x6a6b22e5ff88c63d47527d7d8ff35c3d412388ba7392b1bbc6985f30ccf0e87c
In case n (no):
Confirm transfer of 100 TEST to address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 no
---
Error: Transfer cancelled by user
With skip promt that would be:
./target/debug/cast erc20 transfer \
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 \
0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \
50000000000000000000 \
--yes \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
---
0x3de20e9150169dfe8e7a0c40ecbba21d99587337c3de52d864cd871ed431c0e7
In case where contract address aren't correct/contract doesn't support ERC20 interface for decimals/symbol, we'll get a warning:
./target/debug/cast erc20 transfer \
0x1234567890123456789012345678901234567890 \
0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \
100 \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Warning: Warning: Could not fetch token metadata (decimals/symbol). The address may not be a valid ERC20 token contract.
Confirm transfer of 100 TOKEN (raw amount) to address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 [y/n]
The same behavior has been added to approve():
./target/debug/cast erc20 approve \
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 \
0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \
75000000000000000000 \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Confirm approval for 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 to spend 75 TEST from your account [y/n]
PR Checklist
- [x] Added Tests
- [x] Added Documentation
- [ ] Breaking changes
Also, I would like to update Foundry book, according to a new flag
Hmmm, CI / deny fails. But looks like it isn't my fault
i know you can pipe in
yes, but let's also add a--yes/-yas this is pretty common for clis
Hi there, @onbjerg , I actually agree with the --yes flag approach - that was my initial implementation (you can see it in the PR history above - https://github.com/foundry-rs/foundry/pull/12499#discussion_r2527576406).
However, other maintainers had different perspectives:
- @grandizzy (the original issue author) suggested removing the
--yesflag, saying "don't think we need it?" https://github.com/foundry-rs/foundry/pull/12499#discussion_r2526285635 - @0xrusowsky also seemed to prefer the pipe approach https://github.com/foundry-rs/foundry/pull/12499#discussion_r2536408180
Their reasoning was avoiding adding new arguments to keep the CLI simpler and easier to maintain and following Unix philosophy with yes | pipe being a standard pattern.
I'm happy to implement either approach, but before making changes again, I think it would be helpful if the maintainers could align on the preferred solution:
Option 1: --yes/-y flag (more explicit, common in modern CLIs) (I prefer this)
Option 2: yes | pipe only (Unix philosophy, no new flags)
@grandizzy @0xrusowsky @onbjerg - could you please discuss and let me know which direction you'd like me to take? I want to make sure we're all aligned before implementing the final version.
Other maintainers are also invited to the conversation @mattsse , @zerosnacks , @DaniPopes
@Syzygy106 sorry for back and forth, we'll discuss and make a final call re how to add this. thank you!
@Syzygy106 sorry, my bad, should have been get full consensus on it. Let's stick with your initial impl, that is adding the --yes/-y arg. Thank you
@Syzygy106 sorry, my bad, should have been get full consensus on it. Let's stick with your initial impl, that is adding the --yes/-y arg. Thank you
Yep, no problem!
@onbjerg , @grandizzy , done
@onbjerg , @grandizzy , done
@grandizzy, could you please review the PR when you have a moment?