1+ import logging
2+ from web3 import Web3
3+ from web3 .exceptions import ContractLogicError
4+
5+ from web3 .middleware import ExtraDataToPOAMiddleware
6+
7+ # Configure logging
8+ logging .basicConfig (
9+ level = logging .DEBUG ,
10+ format = '%(asctime)s - %(levelname)s - %(message)s' ,
11+ handlers = [logging .StreamHandler ()]
12+ )
13+ logger = logging .getLogger (__name__ )
14+
15+ # Constants
16+ FACTORY_ADDRESSES = [
17+ "0x8A2578d23d4C532cC9A98FaD91C0523f5efDE652" ,
18+ ]
19+ UNIVERSAL_ROUTER_ADDRESS = "0x8a1E35F5c98C4E85B36B7B253222eE17773b2781"
20+ WFLR_ADDRESS = "0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d"
21+ USDC_ADDRESS = "0xFbDa5F676cB37624f28265A144A48B0d6e87d3b6" # USDC.e
22+ FEE_TIER = 500
23+ USER_ADDRESS = "0x1812C40b5785AeD831EC4a0d675f30c5461Fd42E" # Replace with your address
24+ RPC_URL = "https://flare-api.flare.network/ext/C/rpc"
25+ FALLBACK_POOL_ADDRESS = None # Replace with "0x..." if known, else None
26+
27+ # ABIs
28+ FACTORY_ABI = [{"inputs" : [{"internalType" : "address" , "name" : "tokenA" , "type" : "address" }, {"internalType" : "address" , "name" : "tokenB" , "type" : "address" }, {"internalType" : "uint24" , "name" : "fee" , "type" : "uint24" }], "name" : "getPool" , "outputs" : [{"internalType" : "address" , "name" : "pool" , "type" : "address" }], "stateMutability" : "view" , "type" : "function" }]
29+ POOL_ABI = [{"inputs" : [], "name" : "token0" , "outputs" : [{"internalType" : "address" , "name" : "" , "type" : "address" }], "stateMutability" : "view" , "type" : "function" }, {"inputs" : [], "name" : "token1" , "outputs" : [{"internalType" : "address" , "name" : "" , "type" : "address" }], "stateMutability" : "view" , "type" : "function" }, {"inputs" : [], "name" : "fee" , "outputs" : [{"internalType" : "uint24" , "name" : "" , "type" : "uint24" }], "stateMutability" : "view" , "type" : "function" }, {"inputs" : [], "name" : "liquidity" , "outputs" : [{"internalType" : "uint128" , "name" : "" , "type" : "uint128" }], "stateMutability" : "view" , "type" : "function" }, {"inputs" : [], "name" : "slot0" , "outputs" : [{"internalType" : "uint160" , "name" : "sqrtPriceX96" , "type" : "uint160" }, {"internalType" : "int24" , "name" : "tick" , "type" : "int24" }, {"internalType" : "uint16" , "name" : "observationIndex" , "type" : "uint16" }, {"internalType" : "uint16" , "name" : "observationCardinality" , "type" : "uint16" }, {"internalType" : "uint16" , "name" : "observationCardinalityNext" , "type" : "uint16" }, {"internalType" : "uint8" , "name" : "feeProtocol" , "type" : "uint8" }, {"internalType" : "bool" , "name" : "unlocked" , "type" : "bool" }], "stateMutability" : "view" , "type" : "function" }]
30+ ERC20_ABI = [{"inputs" : [], "name" : "decimals" , "outputs" : [{"internalType" : "uint8" , "name" : "" , "type" : "uint8" }], "stateMutability" : "view" , "type" : "function" }, {"inputs" : [{"internalType" : "address" , "name" : "account" , "type" : "address" }], "name" : "balanceOf" , "outputs" : [{"internalType" : "uint256" , "name" : "" , "type" : "uint256" }], "stateMutability" : "view" , "type" : "function" }]
31+ UNIVERSAL_ROUTER_ABI = [{"inputs" : [{"components" : [{"internalType" : "address" , "name" : "tokenIn" , "type" : "address" }, {"internalType" : "address" , "name" : "tokenOut" , "type" : "address" }, {"internalType" : "uint24" , "name" : "fee" , "type" : "uint24" }, {"internalType" : "address" , "name" : "recipient" , "type" : "address" }, {"internalType" : "uint256" , "name" : "deadline" , "type" : "uint256" }, {"internalType" : "uint256" , "name" : "amountIn" , "type" : "uint256" }, {"internalType" : "uint256" , "name" : "amountOutMinimum" , "type" : "uint256" }, {"internalType" : "uint160" , "name" : "sqrtPriceLimitX96" , "type" : "uint160" }], "internalType" : "struct ISwapRouter.ExactInputSingleParams" , "name" : "params" , "type" : "tuple" }], "name" : "exactInputSingle" , "outputs" : [{"internalType" : "uint256" , "name" : "amountOut" , "type" : "uint256" }], "stateMutability" : "payable" , "type" : "function" }]
32+
33+ # Connect to Flare
34+ logger .info ("=== STEP 1: CONNECTING TO FLARE NETWORK ===" )
35+ w3 = Web3 (Web3 .HTTPProvider (RPC_URL ))
36+ if not w3 .is_connected ():
37+ logger .error ("Failed to connect to Flare RPC" )
38+ exit (1 )
39+ logger .info ("Successfully connected to Flare network" )
40+ w3 .middleware_onion .inject (ExtraDataToPOAMiddleware , layer = 0 )
41+
42+ # Check factory code
43+ logger .info ("=== STEP 2: FINDING VALID FACTORY ADDRESS ===" )
44+ for factory_addr in FACTORY_ADDRESSES :
45+ code = w3 .eth .get_code (factory_addr )
46+ logger .debug (f"Factory { factory_addr } bytecode length: { len (code )} " )
47+ if len (code ) > 0 :
48+ logger .info (f"Found factory with code at { factory_addr } " )
49+ FACTORY_ADDRESS = factory_addr
50+ break
51+ else :
52+ logger .error ("No factory with bytecode found. Please provide SparkDEX factory address." )
53+ if FALLBACK_POOL_ADDRESS :
54+ logger .info (f"Using fallback pool address: { FALLBACK_POOL_ADDRESS } " )
55+ pool_address = FALLBACK_POOL_ADDRESS
56+ else :
57+ logger .error ("No fallback pool address provided. Exiting." )
58+ exit (1 )
59+
60+ # Contracts
61+ factory = w3 .eth .contract (address = FACTORY_ADDRESS , abi = FACTORY_ABI )
62+ wflr_contract = w3 .eth .contract (address = WFLR_ADDRESS , abi = ERC20_ABI )
63+ usdc_contract = w3 .eth .contract (address = USDC_ADDRESS , abi = ERC20_ABI )
64+ universal_router = w3 .eth .contract (address = UNIVERSAL_ROUTER_ADDRESS , abi = UNIVERSAL_ROUTER_ABI )
65+
66+ # Step 3: Get pool address
67+ logger .info ("=== STEP 3: FETCHING POOL ADDRESS ===" )
68+ if not FALLBACK_POOL_ADDRESS :
69+ try :
70+ pool_address = factory .functions .getPool (WFLR_ADDRESS , USDC_ADDRESS , FEE_TIER ).call ()
71+ logger .debug (f"WFLR/USDC.e pool address at fee { FEE_TIER } : { pool_address } " )
72+ if pool_address == "0x0000000000000000000000000000000000000000" :
73+ logger .error ("No pool found for WFLR/USDC.e at fee tier 500" )
74+ exit (1 )
75+ except Exception as e :
76+ logger .error (f"Failed to get pool: { str (e )} " )
77+ exit (1 )
78+ else :
79+ pool_address = FALLBACK_POOL_ADDRESS
80+ logger .info (f"Using provided fallback pool address: { pool_address } " )
81+
82+ # Step 4: Verify pool details
83+ logger .info ("=== STEP 4: VERIFYING POOL DETAILS ===" )
84+ pool = w3 .eth .contract (address = pool_address , abi = POOL_ABI )
85+ token0 = pool .functions .token0 ().call ()
86+ token1 = pool .functions .token1 ().call ()
87+ fee = pool .functions .fee ().call ()
88+ logger .debug (f"Pool tokens: token0={ token0 } , token1={ token1 } , fee={ fee } " )
89+ if {token0 , token1 } != {WFLR_ADDRESS , USDC_ADDRESS } or fee != FEE_TIER :
90+ logger .error ("Pool mismatch: tokens or fee incorrect" )
91+ exit (1 )
92+ logger .info ("Pool verified successfully" )
93+
94+ # Step 5: Check pool state
95+ logger .info ("=== STEP 5: CHECKING POOL STATE ===" )
96+ slot0 = pool .functions .slot0 ().call ()
97+ liquidity = pool .functions .liquidity ().call ()
98+ logger .debug (f"Slot0: { slot0 } " )
99+ logger .debug (f"Liquidity: { liquidity } " )
100+
101+ # Step 6: Get decimals and balance
102+ logger .info ("=== STEP 6: FETCHING TOKEN DETAILS AND BALANCE ===" )
103+ decimals_wflr = wflr_contract .functions .decimals ().call ()
104+ decimals_usdc = usdc_contract .functions .decimals ().call ()
105+ balance_wflr = wflr_contract .functions .balanceOf (USER_ADDRESS ).call ()
106+ logger .debug (f"WFLR decimals: { decimals_wflr } , USDC.e decimals: { decimals_usdc } " )
107+ logger .debug (f"User WFLR balance: { balance_wflr } wei ({ balance_wflr / 10 ** decimals_wflr } WFLR)" )
108+
109+ # Step 7: Test swap (1 WFLR -> USDC.e)
110+ logger .info ("=== STEP 7: TESTING SWAP (1 WFLR -> USDC.e) ===" )
111+ amount_in = 1 * 10 ** decimals_wflr
112+ if balance_wflr < amount_in :
113+ logger .error (f"Insufficient balance: { balance_wflr } < { amount_in } " )
114+ exit (1 )
115+
116+ deadline = w3 .eth .get_block ("latest" )["timestamp" ] + 300
117+ params = (WFLR_ADDRESS , USDC_ADDRESS , FEE_TIER , USER_ADDRESS , deadline , amount_in , 1 , 0 )
118+ logger .debug (f"Swap params: { params } " )
119+
120+ try :
121+ amount_out = universal_router .functions .exactInputSingle (params ).call ()
122+ logger .info (f"Swap successful! Expected USDC.e output: { amount_out } wei ({ amount_out / 10 ** decimals_usdc } USDC.e)" )
123+ except ContractLogicError as e :
124+ logger .error (f"Swap failed: { str (e )} " )
125+ except Exception as e :
126+ logger .error (f"Unexpected error: { str (e )} " )
127+
128+ logger .info ("=== CHECK COMPLETE ===" )
129+
130+
131+
132+
133+ swap_tx = universal_router .functions .exactInputSingle (params ).build_transaction ({
134+ 'from' : USER_ADDRESS ,
135+ 'nonce' : 76 ,
136+ "maxFeePerGas" : w3 .eth .gas_price * w3 .eth .max_priority_fee ,
137+ "maxPriorityFeePerGas" : w3 .eth .max_priority_fee ,
138+ 'chainId' : 14 ,
139+ "type" : 2 ,
140+ })
0 commit comments