
Features implemented: - User authentication with JWT tokens and role-based access (developer/buyer) - Blockchain wallet linking and management with Ethereum integration - Carbon project creation and management for developers - Marketplace for browsing and purchasing carbon offsets - Transaction tracking with blockchain integration - Database models for users, projects, offsets, and transactions - Comprehensive API with authentication, wallet, project, and trading endpoints - Health check endpoint and platform information - SQLite database with Alembic migrations - Full API documentation with OpenAPI/Swagger Technical stack: - FastAPI with Python - SQLAlchemy ORM with SQLite - Web3.py for blockchain integration - JWT authentication with bcrypt - CORS enabled for frontend integration - Comprehensive error handling and validation Environment variables required: - SECRET_KEY (JWT secret) - BLOCKCHAIN_RPC_URL (optional, defaults to localhost)
168 lines
6.4 KiB
Python
168 lines
6.4 KiB
Python
from web3 import Web3
|
|
from eth_account import Account
|
|
from typing import Optional, Dict, Any
|
|
import os
|
|
from datetime import datetime
|
|
|
|
class BlockchainService:
|
|
def __init__(self):
|
|
# Use environment variable for RPC URL (defaults to local for development)
|
|
self.rpc_url = os.getenv("BLOCKCHAIN_RPC_URL", "http://localhost:8545")
|
|
self.w3 = Web3(Web3.HTTPProvider(self.rpc_url))
|
|
self.contract_abi = self._get_carbon_token_abi()
|
|
|
|
def _get_carbon_token_abi(self) -> list:
|
|
# Simplified ABI for a carbon credit token contract
|
|
return [
|
|
{
|
|
"inputs": [
|
|
{"name": "to", "type": "address"},
|
|
{"name": "tokenId", "type": "uint256"},
|
|
{"name": "credits", "type": "uint256"}
|
|
],
|
|
"name": "mintCarbonCredit",
|
|
"outputs": [{"name": "", "type": "bool"}],
|
|
"type": "function"
|
|
},
|
|
{
|
|
"inputs": [
|
|
{"name": "from", "type": "address"},
|
|
{"name": "to", "type": "address"},
|
|
{"name": "tokenId", "type": "uint256"}
|
|
],
|
|
"name": "transferFrom",
|
|
"outputs": [{"name": "", "type": "bool"}],
|
|
"type": "function"
|
|
},
|
|
{
|
|
"inputs": [{"name": "tokenId", "type": "uint256"}],
|
|
"name": "ownerOf",
|
|
"outputs": [{"name": "", "type": "address"}],
|
|
"type": "function"
|
|
}
|
|
]
|
|
|
|
def validate_wallet_address(self, address: str) -> bool:
|
|
"""Validate if the provided address is a valid Ethereum address"""
|
|
try:
|
|
return Web3.is_address(address) and Web3.is_checksum_address(Web3.to_checksum_address(address))
|
|
except:
|
|
return False
|
|
|
|
def generate_wallet(self) -> Dict[str, str]:
|
|
"""Generate a new wallet for testing purposes"""
|
|
account = Account.create()
|
|
return {
|
|
"address": account.address,
|
|
"private_key": account.key.hex(),
|
|
"public_key": account.address # In Ethereum, address is derived from public key
|
|
}
|
|
|
|
def get_wallet_balance(self, address: str) -> Optional[float]:
|
|
"""Get ETH balance for a wallet address"""
|
|
try:
|
|
if not self.validate_wallet_address(address):
|
|
return None
|
|
|
|
balance_wei = self.w3.eth.get_balance(Web3.to_checksum_address(address))
|
|
balance_eth = self.w3.from_wei(balance_wei, 'ether')
|
|
return float(balance_eth)
|
|
except Exception as e:
|
|
print(f"Error getting balance for {address}: {e}")
|
|
return None
|
|
|
|
def create_carbon_token_transaction(
|
|
self,
|
|
contract_address: str,
|
|
from_address: str,
|
|
to_address: str,
|
|
token_id: int,
|
|
private_key: str = None
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""Create a transaction to transfer carbon credits"""
|
|
try:
|
|
if not all([
|
|
self.validate_wallet_address(contract_address),
|
|
self.validate_wallet_address(from_address),
|
|
self.validate_wallet_address(to_address)
|
|
]):
|
|
return None
|
|
|
|
contract = self.w3.eth.contract(
|
|
address=Web3.to_checksum_address(contract_address),
|
|
abi=self.contract_abi
|
|
)
|
|
|
|
# Build transaction
|
|
transaction = contract.functions.transferFrom(
|
|
Web3.to_checksum_address(from_address),
|
|
Web3.to_checksum_address(to_address),
|
|
token_id
|
|
).build_transaction({
|
|
'from': Web3.to_checksum_address(from_address),
|
|
'gas': 200000,
|
|
'gasPrice': self.w3.to_wei('20', 'gwei'),
|
|
'nonce': self.w3.eth.get_transaction_count(Web3.to_checksum_address(from_address))
|
|
})
|
|
|
|
return {
|
|
"transaction": transaction,
|
|
"contract_address": contract_address,
|
|
"from_address": from_address,
|
|
"to_address": to_address,
|
|
"token_id": token_id,
|
|
"created_at": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
except Exception as e:
|
|
print(f"Error creating transaction: {e}")
|
|
return None
|
|
|
|
def sign_and_send_transaction(self, transaction_data: Dict[str, Any], private_key: str) -> Optional[str]:
|
|
"""Sign and send a transaction to the blockchain"""
|
|
try:
|
|
transaction = transaction_data["transaction"]
|
|
signed_txn = self.w3.eth.account.sign_transaction(transaction, private_key)
|
|
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
|
|
return tx_hash.hex()
|
|
except Exception as e:
|
|
print(f"Error signing/sending transaction: {e}")
|
|
return None
|
|
|
|
def get_transaction_receipt(self, tx_hash: str) -> Optional[Dict[str, Any]]:
|
|
"""Get transaction receipt from blockchain"""
|
|
try:
|
|
receipt = self.w3.eth.get_transaction_receipt(tx_hash)
|
|
return {
|
|
"transaction_hash": receipt["transactionHash"].hex(),
|
|
"block_number": receipt["blockNumber"],
|
|
"gas_used": receipt["gasUsed"],
|
|
"status": receipt["status"] # 1 for success, 0 for failure
|
|
}
|
|
except Exception as e:
|
|
print(f"Error getting transaction receipt: {e}")
|
|
return None
|
|
|
|
def verify_token_ownership(self, contract_address: str, token_id: int, owner_address: str) -> bool:
|
|
"""Verify if an address owns a specific token"""
|
|
try:
|
|
if not all([
|
|
self.validate_wallet_address(contract_address),
|
|
self.validate_wallet_address(owner_address)
|
|
]):
|
|
return False
|
|
|
|
contract = self.w3.eth.contract(
|
|
address=Web3.to_checksum_address(contract_address),
|
|
abi=self.contract_abi
|
|
)
|
|
|
|
actual_owner = contract.functions.ownerOf(token_id).call()
|
|
return actual_owner.lower() == owner_address.lower()
|
|
|
|
except Exception as e:
|
|
print(f"Error verifying ownership: {e}")
|
|
return False
|
|
|
|
# Global instance
|
|
blockchain_service = BlockchainService() |