276 lines
10 KiB
Python
276 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
WETH Unwrap Script - Convert WETH back to ETH on Arbitrum
|
|
Use this script if your WETH wrapping transaction failed or timed out
|
|
|
|
Prerequisites:
|
|
- Python 3.7+
|
|
- pip install web3 eth-account python-dotenv
|
|
|
|
Instructions:
|
|
1. Ensure your .env file contains MAINNET_RPC_URL and PRIVATE_KEY
|
|
2. Run: python unwrap_weth.py
|
|
3. Follow the prompts to unwrap your WETH
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import time
|
|
|
|
# Try to import required libraries
|
|
try:
|
|
from web3 import Web3
|
|
from eth_account import Account
|
|
except ImportError as e:
|
|
print(f"[ERROR] Missing required library: {e}")
|
|
print("Please install with: pip install web3 eth-account python-dotenv")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
from dotenv import load_dotenv
|
|
except ImportError:
|
|
print("[WARNING] python-dotenv not found, will use environment variables directly")
|
|
def load_dotenv(override=True):
|
|
pass
|
|
|
|
def setup_logging():
|
|
"""Setup logging for the unwrap script"""
|
|
import logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.StreamHandler(),
|
|
logging.FileHandler('unwrap_weth.log', encoding='utf-8')
|
|
]
|
|
)
|
|
return logging.getLogger(__name__)
|
|
|
|
logger = setup_logging()
|
|
|
|
def get_weth_balance(w3, account_address):
|
|
"""Get current WETH balance"""
|
|
weth_address = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
|
erc20_abi = json.loads('''
|
|
[
|
|
{"inputs": [], "name": "decimals", "outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}], "stateMutability": "view", "type": "function"},
|
|
{"inputs": [], "name": "symbol", "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "view", "type": "function"},
|
|
{"inputs": [{"internalType": "address", "name": "account", "type": "address"}], "name": "balanceOf", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}
|
|
]
|
|
''')
|
|
|
|
try:
|
|
weth_contract = w3.eth.contract(address=weth_address, abi=erc20_abi)
|
|
balance = weth_contract.functions.balanceOf(account_address).call()
|
|
decimals = weth_contract.functions.decimals().call()
|
|
symbol = weth_contract.functions.symbol().call()
|
|
|
|
return balance, decimals, symbol
|
|
except Exception as e:
|
|
logger.error(f"Error getting WETH balance: {e}")
|
|
return 0, 18, "WETH"
|
|
|
|
def get_eth_balance(w3, account_address):
|
|
"""Get current ETH balance"""
|
|
try:
|
|
return w3.eth.get_balance(account_address)
|
|
except Exception as e:
|
|
logger.error(f"Error getting ETH balance: {e}")
|
|
return 0
|
|
|
|
def unwrap_weth(w3, account, amount_wei):
|
|
"""Unwrap WETH to ETH"""
|
|
weth_address = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
|
weth_abi = json.loads('''
|
|
[
|
|
{"constant": false, "inputs": [{"name": "wad", "type": "uint256"}], "name": "withdraw", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function"}
|
|
]
|
|
''')
|
|
|
|
try:
|
|
weth_contract = w3.eth.contract(address=weth_address, abi=weth_abi)
|
|
|
|
# Build transaction with higher gas parameters
|
|
nonce = w3.eth.get_transaction_count(account.address)
|
|
gas_price = w3.eth.gas_price
|
|
|
|
txn = weth_contract.functions.withdraw(amount_wei).build_transaction({
|
|
'from': account.address,
|
|
'nonce': nonce,
|
|
'gas': 150000, # Higher gas limit for safety
|
|
'maxFeePerGas': gas_price * 3, # 3x gas price for faster processing
|
|
'maxPriorityFeePerGas': w3.eth.max_priority_fee * 2,
|
|
'chainId': w3.eth.chain_id
|
|
})
|
|
|
|
logger.info(f"Sending WETH unwrap transaction...")
|
|
logger.info(f"Amount: {amount_wei / 10**18:.6f} WETH")
|
|
logger.info(f"Gas Price: {gas_price / 10**9:.2f} gwei")
|
|
logger.info(f"Max Fee: {txn['maxFeePerGas'] / 10**9:.2f} gwei")
|
|
|
|
# Sign and send transaction
|
|
signed_txn = w3.eth.account.sign_transaction(txn, private_key=account.key)
|
|
tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
|
|
|
logger.info(f"Transaction sent: {tx_hash.hex()}")
|
|
logger.info(f"Arbiscan: https://arbiscan.io/tx/{tx_hash.hex()}")
|
|
|
|
# Wait for confirmation with longer timeout
|
|
logger.info("Waiting for transaction confirmation...")
|
|
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=600) # 10 minutes
|
|
|
|
if receipt.status == 1:
|
|
logger.info("[SUCCESS] WETH unwrap successful!")
|
|
return True
|
|
else:
|
|
logger.error(f"[ERROR] Transaction failed. Status: {receipt.status}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"[ERROR] Error during unwrap transaction: {str(e)}")
|
|
return False
|
|
|
|
def check_pending_transaction(w3, tx_hash_hex):
|
|
"""Check if a pending transaction exists and its status"""
|
|
try:
|
|
receipt = w3.eth.get_transaction_receipt(tx_hash_hex)
|
|
return receipt.status if receipt else None
|
|
except:
|
|
return None
|
|
|
|
def main():
|
|
logger.info("=== WETH Unwrap Script ===")
|
|
logger.info("This script will convert your WETH back to ETH on Arbitrum")
|
|
|
|
# Load environment variables
|
|
load_dotenv(override=True)
|
|
|
|
# Get configuration from environment
|
|
rpc_url = os.environ.get("MAINNET_RPC_URL")
|
|
private_key = os.environ.get("MAIN_WALLET_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY")
|
|
|
|
if not rpc_url or not private_key:
|
|
logger.error("[ERROR] Missing RPC URL or Private Key")
|
|
logger.error("Please ensure MAINNET_RPC_URL and PRIVATE_KEY are set in your .env file")
|
|
logger.error("Example .env file:")
|
|
logger.error("MAINNET_RPC_URL=https://arbitrum-one.public.blastapi.io")
|
|
logger.error("PRIVATE_KEY=0x...")
|
|
return
|
|
|
|
# Connect to Arbitrum
|
|
try:
|
|
w3 = Web3(Web3.HTTPProvider(rpc_url))
|
|
if not w3.is_connected():
|
|
logger.error("[ERROR] Failed to connect to Arbitrum RPC")
|
|
return
|
|
logger.info(f"[SUCCESS] Connected to Chain ID: {w3.eth.chain_id}")
|
|
except Exception as e:
|
|
logger.error(f"[ERROR] Connection error: {e}")
|
|
return
|
|
|
|
# Setup account
|
|
try:
|
|
account = Account.from_key(private_key)
|
|
w3.eth.default_account = account.address
|
|
logger.info(f"Wallet: {account.address}")
|
|
except Exception as e:
|
|
logger.error(f"[ERROR] Account setup error: {e}")
|
|
return
|
|
|
|
# Check current balances
|
|
weth_balance, weth_decimals, weth_symbol = get_weth_balance(w3, account.address)
|
|
eth_balance = get_eth_balance(w3, account.address)
|
|
|
|
logger.info(f"Current WETH Balance: {weth_balance / 10**weth_decimals:.6f} {weth_symbol}")
|
|
logger.info(f"Current ETH Balance: {eth_balance / 10**18:.6f} ETH")
|
|
|
|
if weth_balance == 0:
|
|
logger.info("No WETH balance to unwrap. Exiting.")
|
|
return
|
|
|
|
# Check if there's a pending transaction from the error
|
|
pending_tx = "0x12c38f98938481f89c556e32e652218d1e44e61c8ad320943368ad42b22cd591"
|
|
logger.info(f"\nChecking your failed transaction: {pending_tx}")
|
|
|
|
pending_status = check_pending_transaction(w3, pending_tx)
|
|
if pending_status is not None:
|
|
if pending_status == 1:
|
|
logger.info("[SUCCESS] Your previous WETH wrap transaction actually succeeded!")
|
|
logger.info("Your WETH balance should be available now.")
|
|
else:
|
|
logger.warning("[WARNING] Your previous transaction failed")
|
|
else:
|
|
logger.info("Transaction not found - it may still be pending")
|
|
|
|
# Ask user how much to unwrap
|
|
weth_amount_human = weth_balance / 10**weth_decimals
|
|
|
|
print(f"\nYou have {weth_amount_human:.6f} WETH available")
|
|
print("Options:")
|
|
print("1. Unwrap all WETH")
|
|
print("2. Unwrap specific amount")
|
|
print("3. Exit")
|
|
|
|
try:
|
|
choice = input("\nEnter your choice (1, 2, or 3): ").strip()
|
|
|
|
if choice == "3":
|
|
logger.info("Exiting script")
|
|
return
|
|
elif choice == "1":
|
|
amount_to_unwrap = weth_balance
|
|
logger.info(f"Unwrapping all WETH: {amount_to_unwrap / 10**weth_decimals:.6f} WETH")
|
|
elif choice == "2":
|
|
amount_str = input(f"Enter amount to unwrap (max: {weth_amount_human:.6f}): ").strip()
|
|
try:
|
|
amount_float = float(amount_str)
|
|
if amount_float <= 0:
|
|
logger.error("[ERROR] Amount must be greater than 0")
|
|
return
|
|
amount_to_unwrap = int(amount_float * (10 ** weth_decimals))
|
|
|
|
if amount_to_unwrap > weth_balance:
|
|
logger.error("[ERROR] Amount exceeds WETH balance")
|
|
return
|
|
except ValueError:
|
|
logger.error("[ERROR] Invalid amount")
|
|
return
|
|
else:
|
|
logger.error("[ERROR] Invalid choice")
|
|
return
|
|
except KeyboardInterrupt:
|
|
logger.info("\nOperation cancelled by user")
|
|
return
|
|
except Exception as e:
|
|
logger.error(f"[ERROR] Input error: {e}")
|
|
return
|
|
|
|
# Confirm before executing
|
|
confirm = input(f"\nConfirm unwrap {amount_to_unwrap / 10**weth_decimals:.6f} WETH? (y/N): ").strip().lower()
|
|
if confirm != 'y':
|
|
logger.info("Operation cancelled")
|
|
return
|
|
|
|
# Execute unwrap
|
|
try:
|
|
success = unwrap_weth(w3, account, amount_to_unwrap)
|
|
|
|
if success:
|
|
# Check final balances
|
|
time.sleep(5) # Brief pause to let blockchain update
|
|
final_weth_balance, _, _ = get_weth_balance(w3, account.address)
|
|
final_eth_balance = get_eth_balance(w3, account.address)
|
|
|
|
logger.info(f"\nFinal WETH Balance: {final_weth_balance / 10**weth_decimals:.6f} WETH")
|
|
logger.info(f"Final ETH Balance: {final_eth_balance / 10**18:.6f} ETH")
|
|
logger.info("[SUCCESS] Unwrap operation completed successfully!")
|
|
else:
|
|
logger.error("[ERROR] Unwrap operation failed")
|
|
|
|
except Exception as e:
|
|
logger.error(f"[ERROR] Error during unwrap: {str(e)}")
|
|
logger.error("This might be due to network issues or insufficient gas")
|
|
|
|
if __name__ == "__main__":
|
|
main() |