1212from fastapi import APIRouter , Depends , HTTPException , Query , status
1313from models .blockchain import BlockchainNetwork , ContractEvent , SmartContract
1414from models .user import User
15- from schemas .blockchain import ContractResponse , EventResponse , NetworkResponse
15+ from schemas .blockchain import (
16+ ContractResponse ,
17+ DeployedContractResponse ,
18+ DeployedContractsResponse ,
19+ EventResponse ,
20+ NetworkResponse ,
21+ )
22+ from services .blockchain import BlockchainUnavailableError , web3_client
1623from sqlalchemy import desc , select
1724from sqlalchemy .ext .asyncio import AsyncSession
1825
@@ -232,19 +239,30 @@ async def verify_blockchain_address(
232239 Verify blockchain address format and validity
233240 """
234241 try :
235- # Basic validation
236- is_valid = False
242+ # web3's is_address covers what a hand-rolled `len(address) == 42`
243+ # check doesn't: non-hex characters, and (via is_checksum_address)
244+ # a mixed-case address whose checksum doesn't match its digits -
245+ # both silently passed the old length-only check.
246+ is_valid = web3_client .is_valid_address (address )
237247 address_type = "unknown"
238-
239- if network .lower () in ["ethereum" , "polygon" , "bsc" ]:
240- # Check if it's a valid Ethereum-style address
241- if address .startswith ("0x" ) and len (address ) == 42 :
242- is_valid = True
243- address_type = "EOA" # Externally Owned Account
244- # Could check if it's a contract by querying the network
248+ checksum_address = None
249+
250+ if is_valid :
251+ checksum_address = web3_client .to_checksum_address (address )
252+ try :
253+ is_contract = await web3_client .is_contract_address (address )
254+ address_type = "contract" if is_contract else "EOA"
255+ except BlockchainUnavailableError as exc :
256+ # Format is still valid even if we can't reach the chain to
257+ # tell EOA from contract - report that distinctly from an
258+ # actually-invalid address instead of returning "unknown"
259+ # silently for both cases.
260+ logger .info (f"Could not classify address { address } : { exc } " )
261+ address_type = "unknown (RPC unavailable)"
245262
246263 return {
247264 "address" : address ,
265+ "checksum_address" : checksum_address ,
248266 "network" : network ,
249267 "is_valid" : is_valid ,
250268 "address_type" : address_type ,
@@ -267,20 +285,33 @@ async def get_address_balance(
267285 db : AsyncSession = Depends (get_async_session ),
268286) -> Any :
269287 """
270- Get balance for a blockchain address
288+ Get the native-currency balance for a blockchain address on the
289+ configured RPC network (see ETH_RPC_URL). "network" is currently
290+ informational only - this backend talks to a single configured chain;
291+ per-network routing (Polygon, BSC, ...) would need a
292+ network -> Web3Client mapping in services.blockchain.
271293 """
294+ if not web3_client .is_valid_address (address ):
295+ raise HTTPException (
296+ status_code = status .HTTP_400_BAD_REQUEST ,
297+ detail = "Invalid blockchain address" ,
298+ )
272299 try :
273- # In a real implementation, this would query the blockchain
274- # For now, return a mock response
300+ balance_wei = await web3_client .get_balance (address )
275301 return {
276302 "address" : address ,
277303 "network" : network ,
278- "balance" : "0" ,
279- "balance_usd " : "0" ,
304+ "balance" : str ( balance_wei / 10 ** 18 ) ,
305+ "balance_wei " : str ( balance_wei ) ,
280306 "tokens" : [],
281307 "last_updated" : datetime .now (timezone .utc ).isoformat (),
282308 }
283-
309+ except BlockchainUnavailableError as e :
310+ logger .warning (f"Blockchain unavailable while fetching balance: { e } " )
311+ raise HTTPException (
312+ status_code = status .HTTP_503_SERVICE_UNAVAILABLE ,
313+ detail = "Blockchain RPC endpoint is currently unavailable" ,
314+ )
284315 except Exception as e :
285316 logger .error (f"Error getting address balance: { e } " )
286317 raise HTTPException (
@@ -295,13 +326,32 @@ async def get_gas_price(
295326 db : AsyncSession = Depends (get_async_session ),
296327) -> Any :
297328 """
298- Get current gas price for a network
329+ Get the current gas price from the configured RPC network. Falls back to
330+ a clearly-labeled estimate (rather than erroring the whole request) when
331+ the RPC endpoint isn't reachable, since this is typically a secondary
332+ display widget rather than something a transaction is submitted from.
299333 """
300334 try :
301- # Mock gas price response
335+ gas_price_wei = await web3_client .get_gas_price ()
336+ gas_price_gwei = gas_price_wei / 10 ** 9
337+ return {
338+ "network" : network ,
339+ "timestamp" : datetime .now (timezone .utc ).isoformat (),
340+ "live" : True ,
341+ "gas_prices" : {
342+ "slow" : str (round (gas_price_gwei * 0.9 , 2 )),
343+ "standard" : str (round (gas_price_gwei , 2 )),
344+ "fast" : str (round (gas_price_gwei * 1.2 , 2 )),
345+ "rapid" : str (round (gas_price_gwei * 1.5 , 2 )),
346+ },
347+ "unit" : "gwei" ,
348+ }
349+ except BlockchainUnavailableError as e :
350+ logger .info (f"Gas price RPC call failed, returning estimate: { e } " )
302351 return {
303352 "network" : network ,
304353 "timestamp" : datetime .now (timezone .utc ).isoformat (),
354+ "live" : False ,
305355 "gas_prices" : {
306356 "slow" : "20" ,
307357 "standard" : "25" ,
@@ -310,7 +360,6 @@ async def get_gas_price(
310360 },
311361 "unit" : "gwei" ,
312362 }
313-
314363 except Exception as e :
315364 logger .error (f"Error getting gas price: { e } " )
316365 raise HTTPException (
@@ -319,6 +368,38 @@ async def get_gas_price(
319368 )
320369
321370
371+ @router .get ("/deployed-contracts" , response_model = DeployedContractsResponse )
372+ async def get_deployed_contracts () -> Any :
373+ """
374+ Address book for ChainFinity's own protocol contracts (AssetVault,
375+ CrossChainManager, InstitutionalDeFiProtocol, GovernanceToken,
376+ InstitutionalGovernance) on the connected network. This is the
377+ integration point clients (web/mobile) use to know what to call - see
378+ web-frontend/src/services/api.js's blockchainAPI.getDeployedContracts.
379+
380+ `connected: false` with an empty/partial contract list means the RPC
381+ endpoint (ETH_RPC_URL) isn't reachable right now, not that the contracts
382+ don't exist - addresses resolved from BLOCKCHAIN_DEPLOYMENT_FILE or the
383+ explicit *_ADDRESS settings are still returned either way.
384+ """
385+ contracts = web3_client .get_deployed_contracts ()
386+ connected = await web3_client .is_connected ()
387+ chain_id = await web3_client .get_chain_id () if connected else None
388+
389+ return DeployedContractsResponse (
390+ chain_id = chain_id ,
391+ connected = connected ,
392+ contracts = [
393+ DeployedContractResponse (
394+ name = name ,
395+ address = contract .address ,
396+ has_abi = bool (contract .abi ),
397+ )
398+ for name , contract in sorted (contracts .items ())
399+ ],
400+ )
401+
402+
322403# ── Frontend convenience endpoints ───────────────────────────────────────────
323404# The web and mobile clients call these portfolio/transaction/eth-balance
324405# routes. They return blockchain-derived views in the shapes the clients
@@ -442,18 +523,36 @@ async def get_eth_balance(
442523 db : AsyncSession = Depends (get_async_session ),
443524) -> Any :
444525 """
445- Return the current user's native ETH balance. Uses the user's primary
446- wallet address when available .
526+ Return the current user's native ETH balance, read live from the
527+ configured RPC network for their linked primary_wallet_address .
447528 """
529+ wallet = getattr (current_user , "primary_wallet_address" , None )
530+
531+ if not wallet :
532+ return {
533+ "address" : None ,
534+ "network" : "ethereum" ,
535+ "balance" : None ,
536+ "balance_wei" : None ,
537+ "message" : "No wallet address linked to this account" ,
538+ "last_updated" : datetime .now (timezone .utc ).isoformat (),
539+ }
540+
448541 try :
449- wallet = getattr ( current_user , "primary_wallet_address" , None )
542+ balance_wei = await web3_client . get_balance ( wallet )
450543 return {
451544 "address" : wallet ,
452545 "network" : "ethereum" ,
453- "balance" : "4.2" ,
454- "balance_usd " : "12600.00" ,
546+ "balance" : str ( balance_wei / 10 ** 18 ) ,
547+ "balance_wei " : str ( balance_wei ) ,
455548 "last_updated" : datetime .now (timezone .utc ).isoformat (),
456549 }
550+ except BlockchainUnavailableError as e :
551+ logger .warning (f"Blockchain unavailable while fetching ETH balance: { e } " )
552+ raise HTTPException (
553+ status_code = status .HTTP_503_SERVICE_UNAVAILABLE ,
554+ detail = "Blockchain RPC endpoint is currently unavailable" ,
555+ )
457556 except Exception as e :
458557 logger .error (f"Error getting ETH balance: { e } " )
459558 raise HTTPException (
0 commit comments