Ver3.0 ๐ ํ์ด์ฌ์ผ๋ก ๋ธ๋ก์ฒด์ธ ๊ฒฐ์ ์์์ฆ PDF ์๋ ์์ฑ ์์คํ ๋ง๋ค๊ธฐ

๐ ํ์ด์ฌ์ผ๋ก ๋ธ๋ก์ฒด์ธ ๊ฒฐ์ ์์์ฆ PDF ์๋ ์์ฑ ์์คํ ๋ง๋ค๊ธฐ
Web3 ์๋, ์ค๋งํธํ ๊ฒฐ์ ์์คํ ๊ตฌ์ถ ์๋ฒฝ ๊ฐ์ด๋ ๐
์๋
! ๐ ์ค๋์ ์ ๋ง ํซํ ์ฃผ์ ๋ฅผ ๊ฐ์ง๊ณ ์์ด. ๋ธ๋ก์ฒด์ธ ๊ธฐ์ ์ด ์ ์ ์ฐ๋ฆฌ ์ํ ์์ผ๋ก ๋ค์ด์ค๋ฉด์, ์ํธํํ ๊ฒฐ์ ๋ NFT ๊ฑฐ๋๊ฐ ์ผ์์ด ๋์ด๊ฐ๊ณ ์์์? ๊ทธ๋ฐ๋ฐ ๋ฌธ์ ๋ ์ด๋ฐ ๊ฑฐ๋๋ค์ ์์์ฆ์ ์ด๋ป๊ฒ ๊ด๋ฆฌํ๋๋๋ ๊ฑฐ์ผ. ๐
๊ธฐ์กด์ ์ค์ํ๋ ์์คํ
๊ณผ ๋ฌ๋ฆฌ, ๋ธ๋ก์ฒด์ธ ๊ฑฐ๋๋ ํฌ๋ช
ํ๊ณ ๋ณ์กฐ ๋ถ๊ฐ๋ฅํ๋ค๋ ์ฅ์ ์ด ์์ง๋ง, ์ ์ ์ฌ์ฉ์ ์นํ์ ์ธ ์์์ฆ ์์คํ
์ ๋ถ์กฑํ ๊ฒ ํ์ค์ด์ผ. ๊ทธ๋์ ์ค๋์ ํ์ด์ฌ์ ํ์ฉํด์ ๋ธ๋ก์ฒด์ธ ๊ฒฐ์ ๋ด์ญ์ ์๋์ผ๋ก PDF ์์์ฆ์ผ๋ก ๋ง๋ค์ด์ฃผ๋ ์์คํ
์ ํจ๊ป ๋ง๋ค์ด๋ณผ ๊ฑฐ์ผ! ๐
๐ฏ ์ ๋ธ๋ก์ฒด์ธ ์์์ฆ ์๋ํ๊ฐ ํ์ํ ๊น?
๋ธ๋ก์ฒด์ธ ๊ธฐ์ ์ด ๋ฐ์ ํ๋ฉด์ ์ํธํํ ๊ฒฐ์ , NFT ๊ฑฐ๋, DeFi ์๋น์ค ์ด์ฉ์ด ๊ธ์ฆํ๊ณ ์์ด. ํ์ง๋ง ์ด๋ฐ ๊ฑฐ๋๋ค์ ๋๋ถ๋ถ ํธ๋์ญ์
ํด์์ ๋ธ๋ก ๋ฒํธ๋ก๋ง ๊ธฐ๋ก๋๊ธฐ ๋๋ฌธ์, ์ผ๋ฐ ์ฌ์ฉ์๋ค์ด ์ดํดํ๊ธฐ ์ด๋ ต๊ณ ํ๊ณ ์ฒ๋ฆฌ๋ ์ธ๊ธ ์ ๊ณ ์ ํ์ฉํ๊ธฐ๋ ๊น๋ค๋ก์. ๐ฐ
ํนํ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๋ธ๋ก์ฒด์ธ ๊ธฐ๋ฐ ๊ฒฐ์ ์์คํ
์ ๋์
ํ๋ค๋ฉด, ์ฌ์ฉ์๋ค์๊ฒ ์น์ํ ํํ์ ์์์ฆ์ ์ ๊ณตํ๋ ๊ฒ์ด ํ์์ ์ด์ผ. ๊ทธ๋์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ค ์์คํ
์ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๋ค์ ํด๊ฒฐํด์ค ๊ฑฐ์ผ!
๐ ํด๊ฒฐํ ์ ์๋ ๋ฌธ์ ๋ค
1. ๊ฐ๋
์ฑ ๋ฌธ์ ๐
๋ณต์กํ ํธ๋์ญ์
ํด์๋ฅผ ์ฌ๋์ด ์ฝ๊ธฐ ์ฌ์ด ํํ๋ก ๋ณํ
2. ํ๊ณ ์ฒ๋ฆฌ ๐ผ
์ธ๊ธ ์ ๊ณ ๋ ์ฅ๋ถ ๊ธฐ๋ก์ ์ํ ํ์คํ๋ ์์์ฆ ์ ๊ณต
3. ์ฆ๋น ์๋ฃ ๐
๊ฑฐ๋ ์ฆ๋ช
์ด ํ์ํ ๊ฒฝ์ฐ ์ฆ์ PDF๋ก ์ถ๋ ฅ ๊ฐ๋ฅ
4. ์๋ํ โก
์๋์ผ๋ก ์์์ฆ์ ๋ง๋๋ ๋ฒ๊ฑฐ๋ก์ ์ ๊ฑฐ
๐ ๏ธ ์์คํ ์ํคํ ์ฒ ์ค๊ณ
๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋ฉ์ ๋ค์ด๊ฐ๊ธฐ ์ ์, ์ฐ๋ฆฌ๊ฐ ๋ง๋ค ์์คํ ์ ์ ์ฒด ๊ตฌ์กฐ๋ฅผ ๋จผ์ ์ดํดํด๋ณด์. ํฌ๊ฒ 4๊ฐ์ ์ฃผ์ ์ปดํฌ๋ํธ๋ก ๊ตฌ์ฑ๋ ๊ฑฐ์ผ! ๐๏ธ
์์คํ ๊ตฌ์ฑ ์์
| ์ปดํฌ๋ํธ | ์ญํ | ์ฃผ์ ๊ธฐ์ |
|---|---|---|
| ๋ธ๋ก์ฒด์ธ ์ฐ๋ | ํธ๋์ญ์ ๋ฐ์ดํฐ ์กฐํ | Web3.py, Etherscan API |
| ๋ฐ์ดํฐ ์ฒ๋ฆฌ | ๊ฑฐ๋ ์ ๋ณด ํ์ฑ ๋ฐ ๊ฒ์ฆ | Python, JSON |
| PDF ์์ฑ | ์์์ฆ ๋ ์ด์์ ์์ฑ | ReportLab, PyPDF2 |
| ์๋ํ ์์ง | ์ฃผ๊ธฐ์ ๋ชจ๋ํฐ๋ง ๋ฐ ์์ฑ | APScheduler, Celery |
๐ฆ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
์, ์ด์ ์ค์ ์ผ๋ก ๋ค์ด๊ฐ๋ณด์! ๋จผ์ ํ์ํ ํ์ด์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์ค์นํด์ผ ํด. ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ด๋ค ์ญํ ์ ํ๋์ง ํจ๊ป ์์๋ณด๋ฉด์ ์ค์นํด๋ณผ๊ฒ. ๐
web3==6.11.3
reportlab==4.0.7
Pillow==10.1.0
python-dotenv==1.0.0
requests==2.31.0
qrcode==7.4.2
APScheduler==3.10.4
pandas==2.1.3
ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด๋ก ํ ๋ฒ์ ์ค์นํ ์ ์์ด:
pip install web3 reportlab Pillow python-dotenv requests qrcode APScheduler pandas
๐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ธ ์ค๋ช
web3.py ๐
์ด๋๋ฆฌ์ ๋ธ๋ก์ฒด์ธ๊ณผ ์ํธ์์ฉํ๊ธฐ ์ํ ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ผ. ํธ๋์ญ์
์กฐํ, ์ค๋งํธ ์ปจํธ๋ํธ ํธ์ถ, ๋ธ๋ก ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ ๋ฑ ๋ชจ๋ ๋ธ๋ก์ฒด์ธ ์์
์ ๊ธฐ๋ฐ์ด ๋ผ.
ReportLab ๐
PDF ์์ฑ์ ๊ฐ์! ํ
์คํธ, ์ด๋ฏธ์ง, ํ, ๊ทธ๋ํ ๋ฑ์ ์์ ๋กญ๊ฒ ๋ฐฐ์นํด์ ์ ๋ฌธ์ ์ธ PDF ๋ฌธ์๋ฅผ ๋ง๋ค ์ ์์ด. ์ฐ๋ฆฌ์ ์์์ฆ ๋์์ธ์ ์ฑ
์์ง ์น๊ตฌ์ผ.
Pillow ๐ผ๏ธ
์ด๋ฏธ์ง ์ฒ๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ๋ก๊ณ ์ฝ์
์ด๋ QR ์ฝ๋ ์ด๋ฏธ์ง๋ฅผ PDF์ ๋ฃ์ ๋ ์ฌ์ฉํด.
python-dotenv ๐
API ํค๋ ํ๋ผ์ด๋น ํค ๊ฐ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ํ๊ฒฝ ๋ณ์๋ก ๊ด๋ฆฌํ๊ฒ ํด์ค. ๋ณด์์ ์ํด ํ์์ผ!
qrcode ๐ฑ
ํธ๋์ญ์
ํด์๋ ์ง๊ฐ ์ฃผ์๋ฅผ QR ์ฝ๋๋ก ๋ณํํด์ ์์์ฆ์ ๋ฃ์ ์ ์์ด. ๋ชจ๋ฐ์ผ ๊ฒ์ฆ์ด ํธํด์ ธ!
APScheduler โฐ
์ฃผ๊ธฐ์ ์ผ๋ก ์๋ก์ด ํธ๋์ญ์
์ ์ฒดํฌํ๊ณ ์๋์ผ๋ก ์์์ฆ์ ์์ฑํ๋ ์ค์ผ์ค๋ฌ์ผ.
๐ ๋ธ๋ก์ฒด์ธ ์ฐ๋ ๋ชจ๋ ๊ตฌํ
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํด๋ณผ ์๊ฐ์ด์ผ! ๋จผ์ ๋ธ๋ก์ฒด์ธ์์ ํธ๋์ญ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ชจ๋๋ถํฐ ๋ง๋ค์ด๋ณด์. ์ด๋๋ฆฌ์ ๋ฉ์ธ๋ท๊ณผ ํ ์คํธ๋ท(Sepolia) ๋ชจ๋ ์ง์ํ๋๋ก ๊ตฌํํ ๊ฑฐ์ผ. ๐
๋จผ์ ํ๋ก์ ํธ ๋ฃจํธ์ .env ํ์ผ์ ๋ง๋ค์ด์ API ํค์ ์ค์ ๊ฐ๋ค์ ์ ์ฅํ์:
# Infura ๋๋ Alchemy API ํค
INFURA_PROJECT_ID=your_infura_project_id_here
ALCHEMY_API_KEY=your_alchemy_api_key_here
# Etherscan API ํค (ํธ๋์ญ์
์์ธ ์ ๋ณด์ฉ)
ETHERSCAN_API_KEY=your_etherscan_api_key_here
# ๋ชจ๋ํฐ๋งํ ์ง๊ฐ ์ฃผ์
WALLET_ADDRESS=0xYourWalletAddressHere
# ๋คํธ์ํฌ ์ค์ (mainnet, sepolia, goerli)
NETWORK=sepolia
# PDF ์ ์ฅ ๊ฒฝ๋ก
PDF_OUTPUT_PATH=./receipts/
.env ํ์ผ์ ์ ๋ Git์ ์ปค๋ฐํ๋ฉด ์ ๋ผ! .gitignore์ ๋ฐ๋์ ์ถ๊ฐํด์ค. API ํค๊ฐ ๋
ธ์ถ๋๋ฉด ํฐ์ผ๋!
์ด์ Web3.py๋ฅผ ์ฌ์ฉํด์ ๋ธ๋ก์ฒด์ธ๊ณผ ํต์ ํ๋ ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณด์:
from web3 import Web3
from web3.middleware import geth_poa_middleware
import os
from dotenv import load_dotenv
import requests
from datetime import datetime
import json
# ํ๊ฒฝ ๋ณ์ ๋ก๋
load_dotenv()
class BlockchainConnector:
"""๋ธ๋ก์ฒด์ธ ์ฐ๋์ ๋ด๋นํ๋ ํด๋์ค"""
def __init__(self, network='sepolia'):
self.network = network
self.setup_web3()
self.etherscan_api_key = os.getenv('ETHERSCAN_API_KEY')
def setup_web3(self):
"""Web3 ์ธ์คํด์ค ์ค์ """
if self.network == 'mainnet':
infura_url = f"https://mainnet.infura.io/v3/{os.getenv('INFURA_PROJECT_ID')}"
self.etherscan_base = "https://api.etherscan.io/api"
elif self.network == 'sepolia':
infura_url = f"https://sepolia.infura.io/v3/{os.getenv('INFURA_PROJECT_ID')}"
self.etherscan_base = "https://api-sepolia.etherscan.io/api"
else:
raise ValueError(f"์ง์ํ์ง ์๋ ๋คํธ์ํฌ: {self.network}")
self.w3 = Web3(Web3.HTTPProvider(infura_url))
# PoA ๋คํธ์ํฌ๋ฅผ ์ํ ๋ฏธ๋ค์จ์ด ์ถ๊ฐ (ํ
์คํธ๋ท์ฉ)
if self.network != 'mainnet':
self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
# ์ฐ๊ฒฐ ํ์ธ
if not self.w3.is_connected():
raise ConnectionError(f"{self.network} ๋คํธ์ํฌ์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.")
print(f"โ
{self.network} ๋คํธ์ํฌ์ ์ฑ๊ณต์ ์ผ๋ก ์ฐ๊ฒฐ๋์์ต๋๋ค!")
def get_transaction(self, tx_hash):
"""ํธ๋์ญ์
์ ๋ณด ์กฐํ"""
try:
# ํธ๋์ญ์
๊ธฐ๋ณธ ์ ๋ณด
tx = self.w3.eth.get_transaction(tx_hash)
# ํธ๋์ญ์
์์์ฆ (๊ฐ์ค ์ฌ์ฉ๋, ์ํ ๋ฑ)
receipt = self.w3.eth.get_transaction_receipt(tx_hash)
# ๋ธ๋ก ์ ๋ณด (ํ์์คํฌํ)
block = self.w3.eth.get_block(tx['blockNumber'])
# ๋ฐ์ดํฐ ์ ๋ฆฌ
transaction_data = {
'hash': tx_hash,
'from': tx['from'],
'to': tx['to'],
'value': self.w3.from_wei(tx['value'], 'ether'),
'gas_price': self.w3.from_wei(tx['gasPrice'], 'gwei'),
'gas_used': receipt['gasUsed'],
'total_gas_fee': self.w3.from_wei(
receipt['gasUsed'] * tx['gasPrice'], 'ether'
),
'block_number': tx['blockNumber'],
'timestamp': datetime.fromtimestamp(block['timestamp']),
'status': 'Success' if receipt['status'] == 1 else 'Failed',
'nonce': tx['nonce']
}
return transaction_data
except Exception as e:
print(f"โ ํธ๋์ญ์
์กฐํ ์คํจ: {e}")
return None
def get_wallet_transactions(self, wallet_address, start_block=0, end_block='latest'):
"""ํน์ ์ง๊ฐ์ ํธ๋์ญ์
๋ชฉ๋ก ์กฐํ (Etherscan API ์ฌ์ฉ)"""
try:
params = {
'module': 'account',
'action': 'txlist',
'address': wallet_address,
'startblock': start_block,
'endblock': end_block,
'sort': 'desc',
'apikey': self.etherscan_api_key
}
response = requests.get(self.etherscan_base, params=params)
data = response.json()
if data['status'] == '1':
transactions = []
for tx in data['result']:
transactions.append({
'hash': tx['hash'],
'from': tx['from'],
'to': tx['to'],
'value': float(self.w3.from_wei(int(tx['value']), 'ether')),
'timestamp': datetime.fromtimestamp(int(tx['timeStamp'])),
'block_number': int(tx['blockNumber']),
'gas_used': int(tx['gasUsed']),
'gas_price': float(self.w3.from_wei(int(tx['gasPrice']), 'gwei'))
})
return transactions
else:
print(f"โ ๏ธ Etherscan API ์ค๋ฅ: {data.get('message', 'Unknown error')}")
return []
except Exception as e:
print(f"โ ์ง๊ฐ ํธ๋์ญ์
์กฐํ ์คํจ: {e}")
return []
def get_eth_price(self):
"""ํ์ฌ ETH ๊ฐ๊ฒฉ ์กฐํ (USD)"""
try:
params = {
'module': 'stats',
'action': 'ethprice',
'apikey': self.etherscan_api_key
}
response = requests.get(self.etherscan_base, params=params)
data = response.json()
if data['status'] == '1':
return float(data['result']['ethusd'])
return None
except Exception as e:
print(f"โ ETH ๊ฐ๊ฒฉ ์กฐํ ์คํจ: {e}")
return None
def verify_transaction(self, tx_hash):
"""ํธ๋์ญ์
๊ฒ์ฆ (๋ธ๋ก์ฒด์ธ์ ์ค์ ๋ก ์กด์ฌํ๋์ง)"""
try:
receipt = self.w3.eth.get_transaction_receipt(tx_hash)
return receipt['status'] == 1
except Exception:
return False
1. setup_web3(): Infura๋ฅผ ํตํด ์ด๋๋ฆฌ์ ๋ ธ๋์ ์ฐ๊ฒฐํด. ์ง์ ๋ ธ๋๋ฅผ ์ด์ํ์ง ์์๋ ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ์ ์ ๊ทผํ ์ ์์ด!
2. get_transaction(): ํธ๋์ญ์ ํด์๋ก ์์ธ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์. Wei ๋จ์๋ฅผ Ether๋ก ๋ณํํ๊ณ , ํ์์คํฌํ๋ฅผ ์ฌ๋์ด ์ฝ์ ์ ์๋ ๋ ์ง๋ก ๋ฐ๊ฟ์ค.
3. get_wallet_transactions(): Etherscan API๋ฅผ ์ฌ์ฉํด์ ํน์ ์ง๊ฐ์ ๋ชจ๋ ๊ฑฐ๋ ๋ด์ญ์ ๊ฐ์ ธ์. ์ด๊ฒ ์์ด์ผ ์๋ํ๊ฐ ๊ฐ๋ฅํด!
4. get_eth_price(): ์์์ฆ์ USD ํ์ฐ ๊ธ์ก์ ํ์ํ๊ธฐ ์ํด ํ์ฌ ETH ๊ฐ๊ฒฉ์ ์กฐํํด.
๐ PDF ์์์ฆ ์์ฑ ๋ชจ๋
์ด์ ๊ฐ์ฅ ์ฌ๋ฏธ์๋ ๋ถ๋ถ์ด์ผ! ๋ธ๋ก์ฒด์ธ ๋ฐ์ดํฐ๋ฅผ ์์ PDF ์์์ฆ์ผ๋ก ๋ง๋ค์ด๋ณด์. ReportLab์ ์ฌ์ฉํด์ ์ ๋ฌธ์ ์ธ ๋์์ธ์ ์์์ฆ์ ์์ฑํ ๊ฑฐ์ผ. ๐จ
from reportlab.lib.pagesizes import A4, letter
from reportlab.lib import colors
from reportlab.lib.units import inch, mm
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT
from reportlab.pdfgen import canvas
import qrcode
from io import BytesIO
from datetime import datetime
import os
class ReceiptPDFGenerator:
"""๋ธ๋ก์ฒด์ธ ์์์ฆ PDF ์์ฑ ํด๋์ค"""
def __init__(self, output_path='./receipts/'):
self.output_path = output_path
# ์ถ๋ ฅ ๋๋ ํ ๋ฆฌ๊ฐ ์์ผ๋ฉด ์์ฑ
os.makedirs(output_path, exist_ok=True)
# ์คํ์ผ ์ค์
self.styles = getSampleStyleSheet()
self.setup_custom_styles()
def setup_custom_styles(self):
"""์ปค์คํ
์คํ์ผ ์ ์"""
# ์ ๋ชฉ ์คํ์ผ
self.title_style = ParagraphStyle(
'CustomTitle',
parent=self.styles['Heading1'],
fontSize=24,
textColor=colors.HexColor('#667eea'),
spaceAfter=30,
alignment=TA_CENTER,
fontName='Helvetica-Bold'
)
# ๋ถ์ ๋ชฉ ์คํ์ผ
self.subtitle_style = ParagraphStyle(
'CustomSubtitle',
parent=self.styles['Normal'],
fontSize=12,
textColor=colors.HexColor('#718096'),
spaceAfter=20,
alignment=TA_CENTER
)
# ์น์
ํค๋ ์คํ์ผ
self.section_header_style = ParagraphStyle(
'SectionHeader',
parent=self.styles['Heading2'],
fontSize=14,
textColor=colors.HexColor('#2d3748'),
spaceAfter=12,
spaceBefore=20,
fontName='Helvetica-Bold'
)
# ์ผ๋ฐ ํ
์คํธ ์คํ์ผ
self.normal_style = ParagraphStyle(
'CustomNormal',
parent=self.styles['Normal'],
fontSize=10,
textColor=colors.HexColor('#4a5568'),
spaceAfter=6
)
def create_qr_code(self, data, size=100):
"""QR ์ฝ๋ ์ด๋ฏธ์ง ์์ฑ"""
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# BytesIO๋ก ์ด๋ฏธ์ง๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ
buffer = BytesIO()
img.save(buffer, format='PNG')
buffer.seek(0)
return buffer
def generate_receipt(self, transaction_data, eth_price=None):
"""์์์ฆ PDF ์์ฑ"""
# ํ์ผ๋ช
์์ฑ
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"receipt_{transaction_data['hash'][:10]}_{timestamp}.pdf"
filepath = os.path.join(self.output_path, filename)
# PDF ๋ฌธ์ ์์ฑ
doc = SimpleDocTemplate(
filepath,
pagesize=A4,
rightMargin=30*mm,
leftMargin=30*mm,
topMargin=20*mm,
bottomMargin=20*mm
)
# ๋ฌธ์ ์์๋ค์ ๋ด์ ๋ฆฌ์คํธ
elements = []
# ํค๋ ์น์
elements.append(Paragraph("๐ BLOCKCHAIN RECEIPT", self.title_style))
elements.append(Paragraph(
f"Transaction Verification Document",
self.subtitle_style
))
elements.append(Spacer(1, 20))
# ๊ฑฐ๋ ์ ๋ณด ํ
์ด๋ธ
elements.append(Paragraph("๐ Transaction Details", self.section_header_style))
tx_data = [
['Transaction Hash:', transaction_data['hash']],
['Block Number:', str(transaction_data['block_number'])],
['Date & Time:', transaction_data['timestamp'].strftime('%Y-%m-%d %H:%M:%S UTC')],
['Status:', transaction_data['status']],
['Network:', transaction_data.get('network', 'Ethereum')]
]
tx_table = Table(tx_data, colWidths=[120, 350])
tx_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f7fafc')),
('TEXTCOLOR', (0, 0), (-1, -1), colors.HexColor('#2d3748')),
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
('ALIGN', (1, 0), (1, -1), 'LEFT'),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
('TOPPADDING', (0, 0), (-1, -1), 8),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#e2e8f0'))
]))
elements.append(tx_table)
elements.append(Spacer(1, 20))
# ๊ธ์ก ์ ๋ณด
elements.append(Paragraph("๐ฐ Payment Information", self.section_header_style))
amount_data = [
['From Address:', transaction_data['from']],
['To Address:', transaction_data['to']],
['Amount (ETH):', f"{transaction_data['value']:.6f} ETH"],
]
# USD ํ์ฐ ๊ธ์ก ์ถ๊ฐ
if eth_price:
usd_value = float(transaction_data['value']) * eth_price
amount_data.append(['Amount (USD):', f"${usd_value:,.2f} USD"])
amount_table = Table(amount_data, colWidths=[120, 350])
amount_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f7fafc')),
('TEXTCOLOR', (0, 0), (-1, -1), colors.HexColor('#2d3748')),
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
('ALIGN', (1, 0), (1, -1), 'LEFT'),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
('TOPPADDING', (0, 0), (-1, -1), 8),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#e2e8f0'))
]))
elements.append(amount_table)
elements.append(Spacer(1, 20))
# ๊ฐ์ค๋น ์ ๋ณด
elements.append(Paragraph("โฝ Gas Fee Details", self.section_header_style))
gas_data = [
['Gas Price:', f"{transaction_data['gas_price']:.2f} Gwei"],
['Gas Used:', f"{transaction_data['gas_used']:,}"],
['Total Gas Fee:', f"{transaction_data['total_gas_fee']:.6f} ETH"],
]
gas_table = Table(gas_data, colWidths=[120, 350])
gas_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f7fafc')),
('TEXTCOLOR', (0, 0), (-1, -1), colors.HexColor('#2d3748')),
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
('ALIGN', (1, 0), (1, -1), 'LEFT'),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 9),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
('TOPPADDING', (0, 0), (-1, -1), 8),
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#e2e8f0'))
]))
elements.append(gas_table)
elements.append(Spacer(1, 30))
# QR ์ฝ๋ ์ถ๊ฐ
qr_buffer = self.create_qr_code(transaction_data['hash'])
qr_image = Image(qr_buffer, width=100, height=100)
qr_data = [[qr_image, Paragraph(
f"<b>Scan to Verify</b><br/>"
f"<font size=8>Scan this QR code to view<br/>"
f"the transaction on blockchain explorer</font>",
self.normal_style
)]]
qr_table = Table(qr_data, colWidths=[120, 350])
qr_table.setStyle(TableStyle([
('ALIGN', (0, 0), (0, 0), 'CENTER'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
]))
elements.append(qr_table)
elements.append(Spacer(1, 30))
# ํธํฐ
footer_text = Paragraph(
f"<font size=8 color='#a0aec0'>"
f"This receipt is automatically generated from blockchain data.<br/>"
f"Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}<br/>"
f"Powered by Python & Web3 Technology"
f"</font>",
ParagraphStyle('Footer', parent=self.normal_style, alignment=TA_CENTER)
)
elements.append(footer_text)
# PDF ๋น๋
doc.build(elements)
print(f"โ
PDF ์์์ฆ์ด ์์ฑ๋์์ต๋๋ค: {filepath}")
return filepath
def generate_batch_receipts(self, transactions_list, eth_price=None):
"""์ฌ๋ฌ ํธ๋์ญ์
์ ์์์ฆ์ ์ผ๊ด ์์ฑ"""
generated_files = []
for tx in transactions_list:
try:
filepath = self.generate_receipt(tx, eth_price)
generated_files.append(filepath)
except Exception as e:
print(f"โ ์์์ฆ ์์ฑ ์คํจ (TX: {tx.get('hash', 'Unknown')}): {e}")
print(f"\n๐ ์ด {len(generated_files)}๊ฐ์ ์์์ฆ์ด ์์ฑ๋์์ต๋๋ค.")
return generated_files
๐จ PDF ๋์์ธ ํฌ์ธํธ
1. ์์ ํ๋ ํธ ๐จ
๋ณด๋ผ์ ๊ณ์ด(#667eea)์ ๋ฉ์ธ ์ปฌ๋ฌ๋ก ์ฌ์ฉํด์ ๋ธ๋ก์ฒด์ธ์ ๋ฏธ๋์งํฅ์ ์ธ ์ด๋ฏธ์ง๋ฅผ ํํํ์ด. ํ์ ํค์ผ๋ก ๊ฐ๋
์ฑ์ ๋์๊ณ !
2. ์ ๋ณด ๊ณ์ธต ๊ตฌ์กฐ ๐
๊ฑฐ๋ ์ ๋ณด โ ๊ธ์ก ์ ๋ณด โ ๊ฐ์ค๋น ์ ๋ณด ์์ผ๋ก ์ค์๋์ ๋ฐ๋ผ ๋ฐฐ์นํ์ด. ๊ฐ ์น์
์ ๋ช
ํํ ํค๋๋ก ๊ตฌ๋ถ๋ผ.
3. QR ์ฝ๋ ๐ฑ
ํธ๋์ญ์
ํด์๋ฅผ QR ์ฝ๋๋ก ๋ณํํด์ ๋ชจ๋ฐ์ผ๋ก ์ฝ๊ฒ ๊ฒ์ฆํ ์ ์๊ฒ ํ์ด. Etherscan ๊ฐ์ ๋ธ๋ก ์ต์คํ๋ก๋ฌ๋ก ๋ฐ๋ก ์ฐ๊ฒฐ ๊ฐ๋ฅ!
4. ํ
์ด๋ธ ์คํ์ผ๋ง ๐
๊ต์ฐจ ๋ฐฐ๊ฒฝ์๊ณผ ๊ทธ๋ฆฌ๋๋ก ์ ๋ณด๋ฅผ ๋ช
ํํ๊ฒ ๊ตฌ๋ถํ์ด. ์ข์ธก์ ๋ ์ด๋ธ, ์ฐ์ธก์ ๊ฐ์ผ๋ก ์ผ๊ด์ฑ ์๊ฒ ๋ฐฐ์น!
๐ค ์๋ํ ์์คํ ๊ตฌํ
์ด์ ๋ง์ง๋ง ํผ์ฆ ์กฐ๊ฐ์ด์ผ! ์๋ก์ด ํธ๋์ญ์ ์ ์๋์ผ๋ก ๊ฐ์งํ๊ณ ์์์ฆ์ ์์ฑํ๋ ์๋ํ ์์คํ ์ ๋ง๋ค์ด๋ณด์. APScheduler๋ฅผ ์ฌ์ฉํด์ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ธ๋ก์ฒด์ธ์ ๋ชจ๋ํฐ๋งํ ๊ฑฐ์ผ. โก
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.interval import IntervalTrigger
from datetime import datetime, timedelta
import json
import os
from blockchain_connector import BlockchainConnector
from pdf_generator import ReceiptPDFGenerator
class ReceiptAutomationEngine:
"""์์์ฆ ์๋ ์์ฑ ์์ง"""
def __init__(self, wallet_address, network='sepolia', check_interval=60):
self.wallet_address = wallet_address
self.network = network
self.check_interval = check_interval # ์ด ๋จ์
# ์ปดํฌ๋ํธ ์ด๊ธฐํ
self.blockchain = BlockchainConnector(network=network)
self.pdf_generator = ReceiptPDFGenerator()
# ์ฒ๋ฆฌ๋ ํธ๋์ญ์
์ถ์
self.processed_file = 'processed_transactions.json'
self.processed_txs = self.load_processed_transactions()
# ์ค์ผ์ค๋ฌ ์ด๊ธฐํ
self.scheduler = BlockingScheduler()
print(f"๐ค ์๋ํ ์์ง์ด ์ด๊ธฐํ๋์์ต๋๋ค.")
print(f"๐ ๋ชจ๋ํฐ๋ง ์ง๊ฐ: {wallet_address}")
print(f"๐ ๋คํธ์ํฌ: {network}")
print(f"โฑ๏ธ ์ฒดํฌ ์ฃผ๊ธฐ: {check_interval}์ด")
def load_processed_transactions(self):
"""์ด๋ฏธ ์ฒ๋ฆฌ๋ ํธ๋์ญ์
๋ชฉ๋ก ๋ก๋"""
if os.path.exists(self.processed_file):
with open(self.processed_file, 'r') as f:
return set(json.load(f))
return set()
def save_processed_transactions(self):
"""์ฒ๋ฆฌ๋ ํธ๋์ญ์
๋ชฉ๋ก ์ ์ฅ"""
with open(self.processed_file, 'w') as f:
json.dump(list(self.processed_txs), f, indent=2)
def check_new_transactions(self):
"""์๋ก์ด ํธ๋์ญ์
ํ์ธ ๋ฐ ์ฒ๋ฆฌ"""
try:
print(f"\n๐ [{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ์๋ก์ด ํธ๋์ญ์
ํ์ธ ์ค...")
# ์ต๊ทผ ํธ๋์ญ์
์กฐํ (์ต๊ทผ 100๊ฐ)
transactions = self.blockchain.get_wallet_transactions(
self.wallet_address,
start_block=0,
end_block='latest'
)
if not transactions:
print(" โน๏ธ ์กฐํ๋ ํธ๋์ญ์
์ด ์์ต๋๋ค.")
return
# ์๋ก์ด ํธ๋์ญ์
ํํฐ๋ง
new_transactions = [
tx for tx in transactions
if tx['hash'] not in self.processed_txs
]
if not new_transactions:
print(f" โ
์๋ก์ด ํธ๋์ญ์
์ด ์์ต๋๋ค. (์ด {len(transactions)}๊ฐ ํ์ธ)")
return
print(f" ๐ {len(new_transactions)}๊ฐ์ ์๋ก์ด ํธ๋์ญ์
๋ฐ๊ฒฌ!")
# ETH ๊ฐ๊ฒฉ ์กฐํ
eth_price = self.blockchain.get_eth_price()
if eth_price:
print(f" ๐ต ํ์ฌ ETH ๊ฐ๊ฒฉ: ${eth_price:,.2f}")
# ๊ฐ ํธ๋์ญ์
์ ๋ํด ์์์ฆ ์์ฑ
for tx in new_transactions:
try:
# ์์ธ ์ ๋ณด ์กฐํ
detailed_tx = self.blockchain.get_transaction(tx['hash'])
if detailed_tx:
# ๋คํธ์ํฌ ์ ๋ณด ์ถ๊ฐ
detailed_tx['network'] = self.network.capitalize()
# PDF ์์ฑ
pdf_path = self.pdf_generator.generate_receipt(
detailed_tx,
eth_price
)
# ์ฒ๋ฆฌ ์๋ฃ ํ์
self.processed_txs.add(tx['hash'])
print(f" โ
์์์ฆ ์์ฑ ์๋ฃ: {tx['hash'][:16]}...")
except Exception as e:
print(f" โ ์์์ฆ ์์ฑ ์คํจ ({tx['hash'][:16]}...): {e}")
# ์ฒ๋ฆฌ๋ ํธ๋์ญ์
๋ชฉ๋ก ์ ์ฅ
self.save_processed_transactions()
print(f" ๐ ์ฒ๋ฆฌ ์๋ฃ: {len(new_transactions)}๊ฐ์ ์์์ฆ ์์ฑ๋จ")
except Exception as e:
print(f"โ ํธ๋์ญ์
ํ์ธ ์ค ์ค๋ฅ ๋ฐ์: {e}")
def manual_generate(self, tx_hash):
"""ํน์ ํธ๋์ญ์
์ ์์์ฆ์ ์๋์ผ๋ก ์์ฑ"""
try:
print(f"\n๐ ์๋ ์์์ฆ ์์ฑ ์์: {tx_hash}")
# ํธ๋์ญ์
์ ๋ณด ์กฐํ
tx_data = self.blockchain.get_transaction(tx_hash)
if not tx_data:
print("โ ํธ๋์ญ์
์ ์ฐพ์ ์ ์์ต๋๋ค.")
return None
# ๋คํธ์ํฌ ์ ๋ณด ์ถ๊ฐ
tx_data['network'] = self.network.capitalize()
# ETH ๊ฐ๊ฒฉ ์กฐํ
eth_price = self.blockchain.get_eth_price()
# PDF ์์ฑ
pdf_path = self.pdf_generator.generate_receipt(tx_data, eth_price)
# ์ฒ๋ฆฌ ์๋ฃ ํ์
self.processed_txs.add(tx_hash)
self.save_processed_transactions()
return pdf_path
except Exception as e:
print(f"โ ์๋ ์์ฑ ์คํจ: {e}")
return None
def start_monitoring(self):
"""์๋ ๋ชจ๋ํฐ๋ง ์์"""
print("\n" + "="*60)
print("๐ ๋ธ๋ก์ฒด์ธ ์์์ฆ ์๋ ์์ฑ ์์คํ
์์!")
print("="*60)
# ์ด๊ธฐ ์ฒดํฌ
self.check_new_transactions()
# ์ฃผ๊ธฐ์ ์ฒดํฌ ์ค์ผ์ค ๋ฑ๋ก
self.scheduler.add_job(
self.check_new_transactions,
trigger=IntervalTrigger(seconds=self.check_interval),
id='transaction_checker',
name='ํธ๋์ญ์
์ฒดํฌ',
replace_existing=True
)
print(f"\nโฐ {self.check_interval}์ด๋ง๋ค ์๋์ผ๋ก ์ฒดํฌํฉ๋๋ค.")
print(" ์ข
๋ฃํ๋ ค๋ฉด Ctrl+C๋ฅผ ๋๋ฅด์ธ์.\n")
try:
self.scheduler.start()
except (KeyboardInterrupt, SystemExit):
print("\n\n๐ ์๋ํ ์์คํ
์ ์ข
๋ฃํฉ๋๋ค...")
self.scheduler.shutdown()
def generate_historical_receipts(self, days=7):
"""๊ณผ๊ฑฐ ํธ๋์ญ์
์ ์์์ฆ์ ์ผ๊ด ์์ฑ"""
print(f"\n๐ ์ต๊ทผ {days}์ผ๊ฐ์ ํธ๋์ญ์
์์์ฆ ์์ฑ ์ค...")
# ํธ๋์ญ์
์กฐํ
transactions = self.blockchain.get_wallet_transactions(
self.wallet_address,
start_block=0,
end_block='latest'
)
# ๋ ์ง ํํฐ๋ง
cutoff_date = datetime.now() - timedelta(days=days)
recent_txs = [
tx for tx in transactions
if tx['timestamp'] >= cutoff_date
]
print(f" ๐ ์ด {len(recent_txs)}๊ฐ์ ํธ๋์ญ์
๋ฐ๊ฒฌ")
# ETH ๊ฐ๊ฒฉ ์กฐํ
eth_price = self.blockchain.get_eth_price()
# ์ผ๊ด ์์ฑ
generated = 0
for tx in recent_txs:
try:
detailed_tx = self.blockchain.get_transaction(tx['hash'])
if detailed_tx:
detailed_tx['network'] = self.network.capitalize()
self.pdf_generator.generate_receipt(detailed_tx, eth_price)
self.processed_txs.add(tx['hash'])
generated += 1
except Exception as e:
print(f" โ ๏ธ ๊ฑด๋๋ ({tx['hash'][:16]}...): {e}")
self.save_processed_transactions()
print(f"\nโ
์๋ฃ! {generated}๊ฐ์ ์์์ฆ์ด ์์ฑ๋์์ต๋๋ค.")
return generated
1. ์ค๋ณต ๋ฐฉ์ง: processed_transactions.json ํ์ผ๋ก ์ด๋ฏธ ์ฒ๋ฆฌํ ํธ๋์ญ์ ์ ์ถ์ ํด์ ์ค๋ณต ์์ฑ์ ๋ฐฉ์งํด.
2. ์ฃผ๊ธฐ์ ๋ชจ๋ํฐ๋ง: APScheduler๋ก ์ค์ ํ ์ฃผ๊ธฐ๋ง๋ค ์๋์ผ๋ก ์ ํธ๋์ญ์ ์ ์ฒดํฌํด.
3. ์๋ ์์ฑ: ํน์ ํธ๋์ญ์ ์ ์์์ฆ์ด ํ์ํ ๋ ์ฆ์ ์์ฑํ ์ ์์ด.
4. ํ์คํ ๋ฆฌ ์์ฑ: ๊ณผ๊ฑฐ ํธ๋์ญ์ ๋ค์ ์์์ฆ์ ํ ๋ฒ์ ์์ฑํ ์ ์์ด.
๐ฎ ๋ฉ์ธ ์คํ ์คํฌ๋ฆฝํธ
์ด์ ๋ชจ๋ ์ปดํฌ๋ํธ๋ฅผ ํตํฉํด์ ์ค์ ๋ก ์คํํ ์ ์๋ ๋ฉ์ธ ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ค์ด๋ณด์! ์ฌ์ฉ์๊ฐ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก CLI ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๊ฑฐ์ผ. ๐ฏ
#!/usr/bin/env python3
"""
๋ธ๋ก์ฒด์ธ ๊ฒฐ์ ์์์ฆ PDF ์๋ ์์ฑ ์์คํ
Blockchain Receipt PDF Auto-Generator
"""
import os
import sys
from dotenv import load_dotenv
from automation_engine import ReceiptAutomationEngine
import argparse
# ํ๊ฒฝ ๋ณ์ ๋ก๋
load_dotenv()
def print_banner():
"""์์ ๋ฐฐ๋ ์ถ๋ ฅ"""
banner = """
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ ๐ ๋ธ๋ก์ฒด์ธ ์์์ฆ ์๋ ์์ฑ ์์คํ
โ
โ Blockchain Receipt PDF Auto-Generator โ
โ โ
โ Powered by Python, Web3, & ReportLab โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
"""
print(banner)
def main():
"""๋ฉ์ธ ์คํ ํจ์"""
print_banner()
# ๋ช
๋ น์ค ์ธ์ ํ์ ์ค์
parser = argparse.ArgumentParser(
description='๋ธ๋ก์ฒด์ธ ํธ๋์ญ์
์์์ฆ ์๋ ์์ฑ ์์คํ
'
)
parser.add_argument(
'--mode',
choices=['auto', 'manual', 'history'],
default='auto',
help='์คํ ๋ชจ๋ ์ ํ (auto: ์๋ ๋ชจ๋ํฐ๋ง, manual: ์๋ ์์ฑ, history: ๊ณผ๊ฑฐ ๋ด์ญ ์์ฑ)'
)
parser.add_argument(
'--wallet',
type=str,
default=os.getenv('WALLET_ADDRESS'),
help='๋ชจ๋ํฐ๋งํ ์ง๊ฐ ์ฃผ์'
)
parser.add_argument(
'--network',
choices=['mainnet', 'sepolia', 'goerli'],
default=os.getenv('NETWORK', 'sepolia'),
help='๋ธ๋ก์ฒด์ธ ๋คํธ์ํฌ ์ ํ'
)
parser.add_argument(
'--interval',
type=int,
default=60,
help='์๋ ๋ชจ๋์์ ์ฒดํฌ ์ฃผ๊ธฐ (์ด)'
)
parser.add_argument(
'--tx-hash',
type=str,
help='์๋ ๋ชจ๋์์ ์์ฑํ ํธ๋์ญ์
ํด์'
)
parser.add_argument(
'--days',
type=int,
default=7,
help='ํ์คํ ๋ฆฌ ๋ชจ๋์์ ์กฐํํ ์ผ์'
)
args = parser.parse_args()
# ์ง๊ฐ ์ฃผ์ ํ์ธ
if not args.wallet:
print("โ ์ค๋ฅ: ์ง๊ฐ ์ฃผ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.")
print(" .env ํ์ผ์ WALLET_ADDRESS๋ฅผ ์ค์ ํ๊ฑฐ๋ --wallet ์ต์
์ ์ฌ์ฉํ์ธ์.")
sys.exit(1)
# ์๋ํ ์์ง ์ด๊ธฐํ
try:
engine = ReceiptAutomationEngine(
wallet_address=args.wallet,
network=args.network,
check_interval=args.interval
)
except Exception as e:
print(f"โ ์ด๊ธฐํ ์คํจ: {e}")
sys.exit(1)
# ๋ชจ๋๋ณ ์คํ
if args.mode == 'auto':
# ์๋ ๋ชจ๋ํฐ๋ง ๋ชจ๋
print("\n๐ค ์๋ ๋ชจ๋ํฐ๋ง ๋ชจ๋๋ก ์์ํฉ๋๋ค...")
engine.start_monitoring()
elif args.mode == 'manual':
# ์๋ ์์ฑ ๋ชจ๋
if not args.tx_hash:
print("โ ์ค๋ฅ: ์๋ ๋ชจ๋์์๋ --tx-hash ์ต์
์ด ํ์ํฉ๋๋ค.")
sys.exit(1)
print(f"\n๐ ์๋ ์์ฑ ๋ชจ๋: {args.tx_hash}")
pdf_path = engine.manual_generate(args.tx_hash)
if pdf_path:
print(f"\nโ
์ฑ๊ณต! ์์์ฆ์ด ์์ฑ๋์์ต๋๋ค: {pdf_path}")
else:
print("\nโ ์์์ฆ ์์ฑ์ ์คํจํ์ต๋๋ค.")
sys.exit(1)
elif args.mode == 'history':
# ํ์คํ ๋ฆฌ ์์ฑ ๋ชจ๋
print(f"\n๐ ํ์คํ ๋ฆฌ ๋ชจ๋: ์ต๊ทผ {args.days}์ผ")
count = engine.generate_historical_receipts(days=args.days)
if count > 0:
print(f"\nโ
์ฑ๊ณต! {count}๊ฐ์ ์์์ฆ์ด ์์ฑ๋์์ต๋๋ค.")
else:
print("\nโ ๏ธ ์์ฑ๋ ์์์ฆ์ด ์์ต๋๋ค.")
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print("\n\n๐ ํ๋ก๊ทธ๋จ์ ์ข
๋ฃํฉ๋๋ค.")
sys.exit(0)
except Exception as e:
print(f"\nโ ์์์น ๋ชปํ ์ค๋ฅ ๋ฐ์: {e}")
sys.exit(1)
๐ฎ ์ฌ์ฉ ๋ฐฉ๋ฒ
1. ์๋ ๋ชจ๋ํฐ๋ง ๋ชจ๋ (๊ธฐ๋ณธ)
python main.py --mode auto --interval 60
60์ด๋ง๋ค ์๋์ผ๋ก ์ ํธ๋์ญ์
์ ์ฒดํฌํ๊ณ ์์์ฆ์ ์์ฑํด.
2. ์๋ ์์ฑ ๋ชจ๋
python main.py --mode manual --tx-hash 0x123abc...ํน์ ํธ๋์ญ์ ์ ์์์ฆ์ ์ฆ์ ์์ฑํด.
3. ํ์คํ ๋ฆฌ ์์ฑ ๋ชจ๋
python main.py --mode history --days 30์ต๊ทผ 30์ผ๊ฐ์ ๋ชจ๋ ํธ๋์ญ์ ์์์ฆ์ ์ผ๊ด ์์ฑํด.
4. ๋คํธ์ํฌ ๋ณ๊ฒฝ
python main.py --network mainnet๋ฉ์ธ๋ท์ผ๋ก ์ ํํด์ ์ค์ ๊ฑฐ๋ ๋ด์ญ์ ์ฒ๋ฆฌํด.
๐ง ๊ณ ๊ธ ๊ธฐ๋ฅ ์ถ๊ฐํ๊ธฐ
๊ธฐ๋ณธ ์์คํ ์ ์์ฑํ์ด! ์ด์ ๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๋ค์ ์ถ๊ฐํด๋ณด์. ์ค๋ฌด์์ ์ ๋ง ์ ์ฉํ ๊ธฐ๋ฅ๋ค์ด์ผ. ๐ช
์์์ฆ์ด ์์ฑ๋๋ฉด ์๋์ผ๋ก ์ด๋ฉ์ผ๋ก ๋ฐ์กํ๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ณด์:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import os
from dotenv import load_dotenv
load_dotenv()
class EmailSender:
"""์์์ฆ ์ด๋ฉ์ผ ๋ฐ์ก ํด๋์ค"""
def __init__(self):
self.smtp_server = os.getenv('SMTP_SERVER', 'smtp.gmail.com')
self.smtp_port = int(os.getenv('SMTP_PORT', '587'))
self.sender_email = os.getenv('SENDER_EMAIL')
self.sender_password = os.getenv('SENDER_PASSWORD')
def send_receipt(self, recipient_email, pdf_path, transaction_data):
"""์์์ฆ PDF๋ฅผ ์ด๋ฉ์ผ๋ก ๋ฐ์ก"""
try:
# ์ด๋ฉ์ผ ๋ฉ์์ง ์์ฑ
msg = MIMEMultipart()
msg['From'] = self.sender_email
msg['To'] = recipient_email
msg['Subject'] = f"๐ Blockchain Receipt - TX: {transaction_data['hash'][:16]}..."
# ์ด๋ฉ์ผ ๋ณธ๋ฌธ
body = f"""
์๋
ํ์ธ์!
๋ธ๋ก์ฒด์ธ ํธ๋์ญ์
์์์ฆ์ ๋ณด๋ด๋๋ฆฝ๋๋ค.
๐ ๊ฑฐ๋ ์ ๋ณด:
- ํธ๋์ญ์
ํด์: {transaction_data['hash']}
- ๊ธ์ก: {transaction_data['value']} ETH
- ๋ ์ง: {transaction_data['timestamp']}
- ์ํ: {transaction_data['status']}
์ฒจ๋ถ๋ PDF ํ์ผ์ ํ์ธํด์ฃผ์ธ์.
๊ฐ์ฌํฉ๋๋ค!
---
์ด ์ด๋ฉ์ผ์ ์๋์ผ๋ก ์์ฑ๋์์ต๋๋ค.
"""
msg.attach(MIMEText(body, 'plain'))
# PDF ์ฒจ๋ถ
with open(pdf_path, 'rb') as attachment:
part = MIMEBase('application', 'octet-stream')
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
'Content-Disposition',
f'attachment; filename= {os.path.basename(pdf_path)}'
)
msg.attach(part)
# SMTP ์๋ฒ ์ฐ๊ฒฐ ๋ฐ ๋ฐ์ก
with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
server.starttls()
server.login(self.sender_email, self.sender_password)
server.send_message(msg)
print(f"โ
์ด๋ฉ์ผ ๋ฐ์ก ์๋ฃ: {recipient_email}")
return True
except Exception as e:
print(f"โ ์ด๋ฉ์ผ ๋ฐ์ก ์คํจ: {e}")
return False
ํธ๋์ญ์ ์ ๋ณด๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํด์ ๋์ค์ ๊ฒ์ํ๊ณ ๋ถ์ํ ์ ์๊ฒ ํด๋ณด์:
import sqlite3
from datetime import datetime
import json
class DatabaseManager:
"""ํธ๋์ญ์
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ด๋ฆฌ ํด๋์ค"""
def __init__(self, db_path='receipts.db'):
self.db_path = db_path
self.init_database()
def init_database(self):
"""๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๊ธฐํ"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tx_hash TEXT UNIQUE NOT NULL,
from_address TEXT NOT NULL,
to_address TEXT NOT NULL,
value REAL NOT NULL,
gas_price REAL,
gas_used INTEGER,
total_gas_fee REAL,
block_number INTEGER,
timestamp DATETIME,
status TEXT,
network TEXT,
pdf_path TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_tx_hash ON transactions(tx_hash)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_timestamp ON transactions(timestamp)
''')
conn.commit()
conn.close()
print("โ
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๊ธฐํ ์๋ฃ")
def save_transaction(self, transaction_data, pdf_path):
"""ํธ๋์ญ์
์ ๋ณด ์ ์ฅ"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO transactions
(tx_hash, from_address, to_address, value, gas_price,
gas_used, total_gas_fee, block_number, timestamp,
status, network, pdf_path)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
transaction_data['hash'],
transaction_data['from'],
transaction_data['to'],
float(transaction_data['value']),
float(transaction_data['gas_price']),
transaction_data['gas_used'],
float(transaction_data['total_gas_fee']),
transaction_data['block_number'],
transaction_data['timestamp'],
transaction_data['status'],
transaction_data.get('network', 'Unknown'),
pdf_path
))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"โ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฅ ์คํจ: {e}")
return False
def get_transaction(self, tx_hash):
"""ํธ๋์ญ์
์กฐํ"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute(
'SELECT * FROM transactions WHERE tx_hash = ?',
(tx_hash,)
)
result = cursor.fetchone()
conn.close()
return result
def get_transactions_by_date(self, start_date, end_date):
"""๋ ์ง ๋ฒ์๋ก ํธ๋์ญ์
์กฐํ"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM transactions
WHERE timestamp BETWEEN ? AND ?
ORDER BY timestamp DESC
''', (start_date, end_date))
results = cursor.fetchall()
conn.close()
return results
def get_statistics(self):
"""ํต๊ณ ์ ๋ณด ์กฐํ"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# ์ด ํธ๋์ญ์
์
cursor.execute('SELECT COUNT(*) FROM transactions')
total_count = cursor.fetchone()[0]
# ์ด ๊ฑฐ๋ ๊ธ์ก
cursor.execute('SELECT SUM(value) FROM transactions')
total_value = cursor.fetchone()[0] or 0
# ์ด ๊ฐ์ค๋น
cursor.execute('SELECT SUM(total_gas_fee) FROM transactions')
total_gas = cursor.fetchone()[0] or 0
# ๋คํธ์ํฌ๋ณ ํต๊ณ
cursor.execute('''
SELECT network, COUNT(*), SUM(value)
FROM transactions
GROUP BY network
''')
network_stats = cursor.fetchall()
conn.close()
return {
'total_transactions': total_count,
'total_value': total_value,
'total_gas_fee': total_gas,
'network_stats': network_stats
}
Flask๋ฅผ ์ฌ์ฉํด์ ๊ฐ๋จํ ์น ๋์๋ณด๋๋ฅผ ๋ง๋ค์ด๋ณด์. ๋ธ๋ผ์ฐ์ ์์ ์์์ฆ์ ํ์ธํ๊ณ ๋ค์ด๋ก๋ํ ์ ์์ด!
from flask import Flask, render_template, send_file, jsonify, request
from database_manager import DatabaseManager
from automation_engine import ReceiptAutomationEngine
import os
app = Flask(__name__)
db = DatabaseManager()
@app.route('/')
def index():
"""๋ฉ์ธ ๋์๋ณด๋"""
stats = db.get_statistics()
return render_template('dashboard.html', stats=stats)
@app.route('/api/transactions')
def get_transactions():
"""ํธ๋์ญ์
๋ชฉ๋ก API"""
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 20, type=int)
# ํ์ด์ง ์ฒ๋ฆฌ๋ ํธ๋์ญ์
์กฐํ
# (์ค์ ๊ตฌํ์์๋ LIMIT, OFFSET ์ฌ์ฉ)
return jsonify({
'transactions': [], # ์ค์ ๋ฐ์ดํฐ
'total': 0,
'page': page,
'per_page': per_page
})
@app.route('/download/<tx_hash>')
def download_receipt(tx_hash):
"""์์์ฆ ๋ค์ด๋ก๋"""
tx = db.get_transaction(tx_hash)
if tx and tx[12]: # pdf_path ์ปฌ๋ผ
return send_file(tx[12], as_attachment=True)
return "์์์ฆ์ ์ฐพ์ ์ ์์ต๋๋ค.", 404
@app.route('/api/generate', methods=['POST'])
def generate_receipt():
"""์์์ฆ ์๋ ์์ฑ API"""
data = request.json
tx_hash = data.get('tx_hash')
if not tx_hash:
return jsonify({'error': 'ํธ๋์ญ์
ํด์๊ฐ ํ์ํฉ๋๋ค'}), 400
# ์์์ฆ ์์ฑ ๋ก์ง
# (์ค์ ๊ตฌํ)
return jsonify({'success': True, 'message': '์์์ฆ์ด ์์ฑ๋์์ต๋๋ค'})
if __name__ == '__main__':
app.run(debug=True, port=5000)
๐ก๏ธ ๋ณด์ ๋ฐ ์ต์ ํ
์ค์ ํ๋ก๋์ ํ๊ฒฝ์์ ์ฌ์ฉํ๋ ค๋ฉด ๋ณด์๊ณผ ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์์ผ. ์ค์ํ ํฌ์ธํธ๋ค์ ์ง์ด๋ณผ๊ฒ! ๐
๐ ๋ณด์ ์ฒดํฌ๋ฆฌ์คํธ
1. API ํค ๊ด๋ฆฌ ๐
- ์ ๋ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ๋ง ๊ฒ
- .env ํ์ผ ์ฌ์ฉํ๊ณ .gitignore์ ์ถ๊ฐ
- ํ๋ก๋์
์์๋ AWS Secrets Manager๋ HashiCorp Vault ์ฌ์ฉ
2. ์
๋ ฅ ๊ฒ์ฆ โ
- ํธ๋์ญ์
ํด์ ํ์ ๊ฒ์ฆ (0x๋ก ์์ํ๋ 66์๋ฆฌ 16์ง์)
- ์ง๊ฐ ์ฃผ์ ์ฒดํฌ์ฌ ๊ฒ์ฆ
- SQL ์ธ์ ์
๋ฐฉ์ง (parameterized queries ์ฌ์ฉ)
3. ๋ ์ดํธ ๋ฆฌ๋ฏธํ
โฑ๏ธ
- API ํธ์ถ ํ์ ์ ํ (Infura, Etherscan ๋ฌด๋ฃ ํ๋ ์ ํ ๊ณ ๋ ค)
- ์บ์ฑ ์ ๋ต ๊ตฌํ (๊ฐ์ ํธ๋์ญ์
์ค๋ณต ์กฐํ ๋ฐฉ์ง)
4. ์๋ฌ ํธ๋ค๋ง ๐ ๏ธ
- ๋ฏผ๊ฐํ ์ ๋ณด๊ฐ ์๋ฌ ๋ฉ์์ง์ ๋
ธ์ถ๋์ง ์๋๋ก
- ๋ก๊น
์์คํ
๊ตฌ์ถ (sentry, cloudwatch ๋ฑ)
- ์ฌ์๋ ๋ก์ง ๊ตฌํ (๋คํธ์ํฌ ์ค๋ฅ ๋์)
โก ์ฑ๋ฅ ์ต์ ํ ํ
| ํญ๋ชฉ | ๋ฌธ์ ์ | ํด๊ฒฐ ๋ฐฉ๋ฒ |
|---|---|---|
| API ํธ์ถ | ๋งค๋ฒ ๋ธ๋ก์ฒด์ธ ์กฐํ๋ ๋๋ฆผ | Redis ์บ์ฑ, ๋ก์ปฌ DB ํ์ฉ |
| PDF ์์ฑ | ๋๊ธฐ ์ฒ๋ฆฌ๋ก ๋ณ๋ชฉ ๋ฐ์ | Celery๋ก ๋น๋๊ธฐ ์ฒ๋ฆฌ |
| ๋๋ ์ฒ๋ฆฌ | ์์ฒ ๊ฐ ํธ๋์ญ์ ์ฒ๋ฆฌ ์ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ | ๋ฐฐ์น ์ฒ๋ฆฌ, ์คํธ๋ฆฌ๋ฐ ๋ฐฉ์ |
| ํ์ผ ์ ์ฅ | ๋ก์ปฌ ๋์คํฌ ์ฉ๋ ์ ํ | S3, Google Cloud Storage ํ์ฉ |
import redis
import json
from functools import wraps
class CacheManager:
"""Redis ์บ์ ๋งค๋์ """
def __init__(self, host='localhost', port=6379, db=0):
self.redis_client = redis.Redis(
host=host,
port=port,
db=db,
decode_responses=True
)
def cache_transaction(self, tx_hash, data, expire=3600):
"""ํธ๋์ญ์
๋ฐ์ดํฐ ์บ์ฑ (1์๊ฐ)"""
key = f"tx:{tx_hash}"
self.redis_client.setex(
key,
expire,
json.dumps(data, default=str)
)
def get_cached_transaction(self, tx_hash):
"""์บ์๋ ํธ๋์ญ์
์กฐํ"""
key = f"tx:{tx_hash}"
data = self.redis_client.get(key)
return json.loads(data) if data else None
# ๋ฐ์ฝ๋ ์ดํฐ๋ก ์บ์ฑ ์ ์ฉ
def cached(expire=3600):
def decorator(func):
@wraps(func)
def wrapper(self, tx_hash, *args, **kwargs):
cache = CacheManager()
# ์บ์ ํ์ธ
cached_data = cache.get_cached_transaction(tx_hash)
if cached_data:
print(f"โ
์บ์ ํํธ: {tx_hash[:16]}...")
return cached_data
# ์บ์ ๋ฏธ์ค - ์ค์ ์กฐํ
result = func(self, tx_hash, *args, **kwargs)
# ์บ์ ์ ์ฅ
if result:
cache.cache_transaction(tx_hash, result, expire)
return result
return wrapper
return decorator
๐ ์ค์ ๋ฐฐํฌ ๊ฐ์ด๋
๊ฐ๋ฐ์ ๋๋ฌ์ด! ์ด์ ์ค์ ์๋ฒ์ ๋ฐฐํฌํด์ ์ด์ํด๋ณด์. Docker๋ฅผ ์ฌ์ฉํ ๋ฐฐํฌ ๋ฐฉ๋ฒ์ ์๋ ค์ค๊ฒ. ๐ณ
FROM python:3.11-slim
# ์์
๋๋ ํ ๋ฆฌ ์ค์
WORKDIR /app
# ์์คํ
ํจํค์ง ์
๋ฐ์ดํธ ๋ฐ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Python ์์กด์ฑ ์ค์น
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋ ๋ณต์ฌ
COPY . .
# ์์์ฆ ์ ์ฅ ๋๋ ํ ๋ฆฌ ์์ฑ
RUN mkdir -p /app/receipts
# ํ๊ฒฝ ๋ณ์ ์ค์
ENV PYTHONUNBUFFERED=1
# ์คํ ๊ถํ ๋ถ์ฌ
RUN chmod +x main.py
# ๊ธฐ๋ณธ ๋ช
๋ น์ด
CMD ["python", "main.py", "--mode", "auto"]
version: '3.8'
services:
receipt-generator:
build: .
container_name: blockchain-receipt-generator
env_file:
- .env
volumes:
- ./receipts:/app/receipts
- ./data:/app/data
restart: unless-stopped
networks:
- receipt-network
redis:
image: redis:7-alpine
container_name: receipt-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- receipt-network
postgres:
image: postgres:15-alpine
container_name: receipt-db
environment:
POSTGRES_DB: receipts
POSTGRES_USER: receipt_user
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- receipt-network
volumes:
redis-data:
postgres-data:
networks:
receipt-network:
driver: bridge
# Docker ์ด๋ฏธ์ง ๋น๋
docker-compose build
# ์ปจํ
์ด๋ ์์
docker-compose up -d
# ๋ก๊ทธ ํ์ธ
docker-compose logs -f receipt-generator
# ์ปจํ
์ด๋ ์ค์ง
docker-compose down
# ์ ์ฒด ์ฌ์์ (๋ฐ์ดํฐ ์ ์ง)
docker-compose restart
- โ ํ๊ฒฝ ๋ณ์ ๋ชจ๋ ์ค์ ํ์ธ
- โ ๋ฐฉํ๋ฒฝ ๊ท์น ์ค์ (ํ์ํ ํฌํธ๋ง ์คํ)
- โ SSL/TLS ์ธ์ฆ์ ์ค์ (์น ๋์๋ณด๋์ฉ)
- โ ๋ก๊ทธ ๋กํ ์ด์ ์ค์
- โ ๋ชจ๋ํฐ๋ง ๋๊ตฌ ์ฐ๋ (Prometheus, Grafana)
- โ ๋ฐฑ์ ์ ๋ต ์๋ฆฝ (DB, PDF ํ์ผ)
- โ ์๋ฆผ ์์คํ ๊ตฌ์ถ (์๋ฌ ๋ฐ์ ์ Slack, Discord ์๋ฆผ)
๐ก ์ค์ ํ์ฉ ์ฌ๋ก
์ด ์์คํ ์ ์ค์ ๋ก ์ด๋ป๊ฒ ํ์ฉํ ์ ์์๊น? ๋ค์ํ ๋น์ฆ๋์ค ์๋๋ฆฌ์ค๋ฅผ ์ดํด๋ณด์! ๐ฏ
๐ช E-์ปค๋จธ์ค ํ๋ซํผ
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ํธํํ ๊ฒฐ์ ๋ฅผ ๋์
ํ๋ค๋ฉด?
์๋๋ฆฌ์ค: ์ฌ์ฉ์๊ฐ ์ฌ๋ฅ ๊ฑฐ๋ ๋๊ธ์ ETH๋ก ๊ฒฐ์
์๋ํ: ๊ฒฐ์ ์๋ฃ ์ฆ์ ๊ตฌ๋งค์์ ํ๋งค์์๊ฒ ์์์ฆ ์ด๋ฉ์ผ ๋ฐ์ก
์ฅ์ : ํฌ๋ช
ํ ๊ฑฐ๋ ๊ธฐ๋ก, ๋ถ์ ํด๊ฒฐ ์ ์ฆ๋น ์๋ฃ ํ์ฉ
๐ผ ํ๋ฆฌ๋์ ํ๋ซํผ
์๋๋ฆฌ์ค: ํด์ธ ํด๋ผ์ด์ธํธ๊ฐ ํ๋ฆฌ๋์์๊ฒ ์ํธํํ๋ก ๋ณด์ ์ง๊ธ
์๋ํ: ๋งค์ ๋ง ์๋์ผ๋ก ์๊ฐ ์์์ฆ ๋ฆฌํฌํธ ์์ฑ
์ฅ์ : ์ธ๊ธ ์ ๊ณ ๊ฐํธํ, ์์
์ฆ๋น ์๋ฃ ์๋ ์์ฑ
๐ฎ NFT ๋ง์ผํ๋ ์ด์ค
์๋๋ฆฌ์ค: NFT ๊ตฌ๋งค ์ ์์ ๊ถ ์ฆ๋ช
์ ๊ฒธ ์์์ฆ ๋ฐ๊ธ
์๋ํ: ๋ฏผํ
๋๋ ๊ฑฐ๋ ์๋ฃ ์ ์๋์ผ๋ก ์ฆ๋ช
์ ์์ฑ
์ฅ์ : ์ํ ๊ฐ์น ์ฆ๋ช
, ์ฌํ๋งค ์ ์ด๋ ฅ ์ถ์
๐ข ๊ธฐ์ ํ๊ณ ์์คํ
์๋๋ฆฌ์ค: ๋ธ๋ก์ฒด์ธ ๊ธฐ๋ฐ B2B ๊ฒฐ์ ์์คํ
์๋ํ: ERP ์์คํ
๊ณผ ์ฐ๋ํ์ฌ ์๋ ์ฅ๋ถ ๊ธฐ๋ก
์ฅ์ : ๊ฐ์ฌ ๋์ ์ฉ์ด, ์ค์๊ฐ ์ฌ๋ฌด ํํฉ ํ์
๐ ํ์ต ๋ฆฌ์์ค ๋ฐ ๋ค์ ๋จ๊ณ
์ฌ๊ธฐ๊น์ง ๋ฐ๋ผ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐ ํ์ง๋ง ๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ์ ๋์ด ์์ด. ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ๊ฒ๋ค์ ์ถ์ฒํด:
๐ ์ถ์ฒ ํ์ต ์๋ฃ
๊ณต์ ๋ฌธ์ ๐
- Web3.py Documentation: https://web3py.readthedocs.io/
- Ethereum Developer Docs: https://ethereum.org/developers
- ReportLab User Guide: https://www.reportlab.com/docs/
์จ๋ผ์ธ ๊ฐ์ ๐ฅ
- Udemy: "Ethereum and Solidity: The Complete Developer's Guide"
- Coursera: "Blockchain Specialization"
- YouTube: "Dapp University" ์ฑ๋
์ปค๋ฎค๋ํฐ ๐ฅ
- Ethereum Stack Exchange
- Reddit: r/ethdev
- Discord: Web3 Python ์ปค๋ฎค๋ํฐ
๐ ๋ค์ ๋จ๊ณ ํ๋ก์ ํธ ์์ด๋์ด
1. ์ค๋งํธ ์ปจํธ๋ํธ ์ฐ๋ ๐ค
ERC-20 ํ ํฐ ๊ฑฐ๋๋ DeFi ํ๋กํ ์ฝ ์ํธ์์ฉ ๋ด์ญ๋ ์์์ฆ์ผ๋ก ๋ง๋ค์ด๋ณด์.
2. ๋ฉํฐ์ฒด์ธ ์ง์ ๐
๋นํธ์ฝ์ธ, ํด๋ฆฌ๊ณค, ์๋ผ๋ ๋ฑ ๋ค์ํ ๋ธ๋ก์ฒด์ธ ์ง์์ ์ถ๊ฐํด๋ณด์.
3. AI ๋ถ์ ๊ธฐ๋ฅ ๐ค
๊ฑฐ๋ ํจํด ๋ถ์, ์ด์ ๊ฑฐ๋ ํ์ง, ์ธ๊ธ ์ต์ ํ ์ ์ ๋ฑ AI ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ณด์.
4. ๋ชจ๋ฐ์ผ ์ฑ ๐ฑ
React Native๋ Flutter๋ก ๋ชจ๋ฐ์ผ ์ฑ์ ๋ง๋ค์ด์ ์ธ์ ์ด๋์๋ ์์์ฆ์ ํ์ธํ๊ณ ์์ฑํ ์ ์๊ฒ ํด๋ณด์.
5. NFT ์์์ฆ ๐จ
์์์ฆ ์์ฒด๋ฅผ NFT๋ก ๋ฏผํ
ํด์ ๋ธ๋ก์ฒด์ธ์ ์๊ตฌ ๋ณด๊ดํ๋ ์์คํ
์ ๋ง๋ค์ด๋ณด์!
๐ ํธ๋ฌ๋ธ์ํ ๊ฐ์ด๋
๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ๊ผญ ๋ง์ฃผ์น๋ ๋ฌธ์ ๋ค์ด ์์ด. ์์ฃผ ๋ฐ์ํ๋ ์ด์์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํด๋ดค์ด! ๐ ๏ธ
โ ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค
๋ฌธ์ 1: "Web3 connection failed"
์์ธ: Infura API ํค๊ฐ ์๋ชป๋์๊ฑฐ๋, ๋คํธ์ํฌ ์ฐ๊ฒฐ ๋ฌธ์
ํด๊ฒฐ:
- .env ํ์ผ์ INFURA_PROJECT_ID ํ์ธ
- Infura ๋์๋ณด๋์์ API ํค ์ํ ํ์ธ
- ๋ฐฉํ๋ฒฝ์ด HTTPS ์ฐ๊ฒฐ์ ์ฐจ๋จํ๋์ง ํ์ธ
๋ฌธ์ 2: "Transaction not found"
์์ธ: ํธ๋์ญ์
์ด ์์ง ๋ธ๋ก์ ํฌํจ๋์ง ์์๊ฑฐ๋, ์๋ชป๋ ๋คํธ์ํฌ
ํด๊ฒฐ:
- ํธ๋์ญ์
์ด ์ปจํ๋ ๋๊น์ง ๋๊ธฐ (๋ณดํต 15์ด~1๋ถ)
- ๋คํธ์ํฌ ์ค์ ํ์ธ (mainnet vs testnet)
- Etherscan์์ ํธ๋์ญ์
์กด์ฌ ์ฌ๋ถ ํ์ธ
๋ฌธ์ 3: "PDF generation error - Font not found"
์์ธ: ์์คํ
์ ํ์ํ ํฐํธ๊ฐ ์ค์น๋์ง ์์
ํด๊ฒฐ:
- ReportLab ๊ธฐ๋ณธ ํฐํธ ์ฌ์ฉ (Helvetica, Times-Roman)
- ์ปค์คํ
ํฐํธ ์ฌ์ฉ ์ .ttf ํ์ผ ๊ฒฝ๋ก ํ์ธ
๋ฌธ์ 4: "Rate limit exceeded"
์์ธ: API ํธ์ถ ํ์ ์ ํ ์ด๊ณผ
ํด๊ฒฐ:
- ์ฒดํฌ ์ฃผ๊ธฐ๋ฅผ ๋๋ฆผ (60์ด โ 300์ด)
- ์บ์ฑ ์์คํ
๋์
- ์ ๋ฃ ํ๋์ผ๋ก ์
๊ทธ๋ ์ด๋
๋ฌธ์ 5: "Memory error during batch processing"
์์ธ: ๋๋์ ํธ๋์ญ์
์ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ ค๊ณ ์๋
ํด๊ฒฐ:
- ๋ฐฐ์น ํฌ๊ธฐ๋ฅผ ์ค์ (100๊ฐ์ฉ ์ฒ๋ฆฌ)
- ์ ๋๋ ์ดํฐ ํจํด ์ฌ์ฉ
- ๋ฉ๋ชจ๋ฆฌ ํ๋กํ์ผ๋ง ๋๊ตฌ๋ก ๋ณ๋ชฉ ์ง์ ์ฐพ๊ธฐ
๐ฏ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ๐ ์ฐ๋ฆฌ๋ ๋ธ๋ก์ฒด์ธ ๊ธฐ์ ๊ณผ ํ์ด์ฌ์ ๊ฒฐํฉํด์ ์ค์ฉ์ ์ธ ์๋ํ ์์คํ ์ ๋ง๋ค์ด๋ดค์ด. ์ด ํ๋ก์ ํธ๋ฅผ ํตํด ๋ฐฐ์ด ๊ฒ๋ค์ ์ ๋ฆฌํด๋ณด์:
โจ ํต์ฌ ์์ฝ
1. ๋ธ๋ก์ฒด์ธ ์ฐ๋ ๐
Web3.py๋ฅผ ์ฌ์ฉํด์ ์ด๋๋ฆฌ์ ๋คํธ์ํฌ์ ํต์ ํ๊ณ , ํธ๋์ญ์
๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ด.
2. PDF ์์ฑ ๐
ReportLab์ผ๋ก ์ ๋ฌธ์ ์ธ ๋์์ธ์ PDF ๋ฌธ์๋ฅผ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์์ฑํ๋ ๊ธฐ์ ์ ์ตํ์ด.
3. ์๋ํ ๐ค
APScheduler๋ฅผ ํ์ฉํด์ ์ฃผ๊ธฐ์ ์ผ๋ก ์์
์ ์คํํ๋ ์๋ํ ์์คํ
์ ๊ตฌ์ถํ์ด.
4. ์์คํ
์ค๊ณ ๐๏ธ
๋ชจ๋ํ๋ ๊ตฌ์กฐ๋ก ํ์ฅ ๊ฐ๋ฅํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ์์คํ
์ ์ค๊ณํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ด.
5. ์ค์ ๋ฐฐํฌ ๐
Docker๋ฅผ ์ฌ์ฉํ ์ปจํ
์ด๋ํ์ ํ๋ก๋์
ํ๊ฒฝ ๋ฐฐํฌ ์ ๋ต์ ํ์ตํ์ด.
์ด ์์คํ ์ ์์์ ์ผ ๋ฟ์ด์ผ. ์ฌ๊ธฐ์ ๋ฐฐ์ด ๊ฐ๋ ๋ค์ ์์ฉํด์ ์์ ๋ง์ ๋ ํนํ ๋ธ๋ก์ฒด์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณด๊ธธ ๋ฐ๋ผ! ์๋ฅผ ๋ค์ด DeFi ํฌํธํด๋ฆฌ์ค ํธ๋์ปค, NFT ๊ฐค๋ฌ๋ฆฌ ๊ด๋ฆฌ ์์คํ , DAO ํฌํ ๊ธฐ๋ก ์์คํ ๋ฑ ๋ฌด๊ถ๋ฌด์งํ ๊ฐ๋ฅ์ฑ์ด ์์ด.
๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ์ ๋น ๋ฅด๊ฒ ๋ณํํ๋ ๋ถ์ผ์ผ. ํญ์ ์ต์ ํธ๋ ๋๋ฅผ ๋ฐ๋ผ๊ฐ๊ณ , ์ปค๋ฎค๋ํฐ์ ์ฐธ์ฌํด์ ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค๊ณผ ์ง์์ ๊ณต์ ํ๋ ๊ฒ์ด ์ค์ํด. ๊ทธ๋ฆฌ๊ณ ๋ณด์์ ์ ๋ ํํํ๋ฉด ์ ๋ผ! ๐
์ด ํ๋ก์ ํธ๊ฐ ๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ ์ฌ์ ์ ๋์์ด ๋์์ผ๋ฉด ์ข๊ฒ ์ด. ๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋ ๋ฐ์ ์ํจ ๋ฒ์ ์ ๋ง๋ค์๋ค๋ฉด ์ปค๋ฎค๋ํฐ์ ๊ณต์ ํด์ค! ํจ๊ป ์ฑ์ฅํ๋ ๊ฒ ๊ฐ๋ฐ์ ๋ฌธํ์ ์๋ฆ๋ค์์ด๋๊น. ๐
๊ทธ๋ฆฌ๊ณ ์ฌ๋ฅ๋ท์์ ๋ธ๋ก์ฒด์ธ ๊ด๋ จ ์ฌ๋ฅ์ ๊ณต์ ํ๊ฑฐ๋, ์ด๋ฐ ์์คํ
๊ฐ๋ฐ์ ์๋ขฐ๋ฐ์ ์๋ ์์ผ๋ ํ๋ฒ ๋๋ฌ๋ณด๋ ๊ฒ๋ ์ถ์ฒํด!
์, ์ด์ ๋น์ ์ฐจ๋ก์ผ! ์ฝ๋๋ฅผ ์คํํ๊ณ , ์คํํ๊ณ , ์คํจํ๊ณ , ๋ฐฐ์ฐ๊ณ , ์ฑ๊ณตํ๋ ๊ณผ์ ์ ์ฆ๊ฒจ๋ด. ํดํผ ์ฝ๋ฉ! ๐โจ
๐ ์ด ๊ฐ์ด๋๊ฐ ๋์์ด ๋์๋์? ๐
๋ธ๋ก์ฒด์ธ ๊ธฐ์ ๋ก ๋ ๋์ ๋ฏธ๋๋ฅผ ๋ง๋ค์ด๊ฐ์!
Happy Coding & Keep Building! ๐ช
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.