I’m only really upset about losing a few specific NFTs. That, and kicking myself for not preventing this from happening at all!
A few weeks ago, we were all together at our weekly virtual happy hour. This particular week, instead of discussing what everyone was building, we discussed how someone had lost access to their wallet by leaking their private key. This loss of control was very disorienting, the normal security you feel when you hold your own keys and self-custody is inverted and that very system betrays you. Sentiment was low.
No one wants to admit when they’ve made a stupid mistake, especially something as fundamental as protecting your private keys. But this can be a useful lesson on what not to do, and ideally light at the end of the tunnel on how you might recover. Bots watch for private keys that are pushed to Github, which is a higher risk of happening with the new trend of vibe coding. If Cursor gives you some code, do you review it thoroughly or just check to see if it works? Do you make sure your .gitignore
file includes any files which might have sensitive information or just git push
to github? This use is particularly dangerous with solidity development because a private key is part of the environment needed to deploy contracts.
Once an attacker gets ahold of our private keys, he can use them to act as if he is us, wherever our wallet address can be used for authentication. Before we even know we’ve been compromised, the attacker has access to all the work that has been invested in this wallet; assets accumulated can be drained, NFTs can be transferred or sold, and DAO membership can be exercised (votes, ragequit, etc). The control you once had over your wallet is gone, and because of drainer bots, you can’t even perform transactions as usual because money for gas is gone before you can use it.
During Happy Hour, when we discussed the compromised wallet and what was lost, I was attracted to the puzzle of getting around the attacker and its bots, and also wanted to help out a friend. Over the weekend I did some research and came up with the following process to recover what I could.
- Set up recovery scaffolding
- Create a new wallet and fund it with enough to perform the recovery. This wallet will have the private key in the scripting environment, so you wouldn’t want to make the same mistake to have it compromised as well.
- Deploy an NFT sweeper contract for convenience (warning: not audited or anything of the like)
pragma solidity ^0.8.0;
interface IERC721 {
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external;
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
interface IERC1155 {
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
function setApprovalForAll(address _operator, bool _approved) external;
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
// Create a contract for the receiver to simplify approval process
contract SweepNFTs {
address public sweepTo;
constructor(address _to) {
sweepTo = _to;
}
function sweep721(address _nft, address _from, uint256[] calldata _ids) public {
IERC721 nft = IERC721(_nft);
require(nft.isApprovedForAll(_from, address(this)), "not approved");
for (uint i=0; i < _ids.length; i++) {
nft.safeTransferFrom(_from, sweepTo, _ids[i]);
}
}
function sweep1155(address _nft, address _from, uint256[] calldata _ids, uint256[] calldata _values) public {
IERC1155 nft = IERC1155(_nft);
require(nft.isApprovedForAll(_from, address(this)), "not approved");
nft.safeBatchTransferFrom(_from, sweepTo, _ids, _values, "");
}
}
- Submit bundles, using flashbots RPC
- Send a small amount of ETH (enough for below txs) to the compromised wallet
- Perform recovery transactions
safeTransferFrom()
if not using sweepersetApprovalForAll()
to approve sweeper otherwise- Transactions here will be specific to what is being recovered
- Clean up
- Using a non-compromised wallet, perform the necessary sweep transactions on the contract deployed above.
At the next Happy Hour, we went over the recovery efforts and the mood was a bit better than the previous week. We not only talked about how wallets can be compromised, we also talked about how to avoid getting into this situation in the first place. These tips might make a good future post, but for now take this as a warning to be careful with your keys.
If you’re building something and want to discuss any challenges, or get feedback from savvy alpha users, join us for Happy Hour.
To see the code used for this, go to our github
Leave a Reply