์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ ํŒŒ์ด์ฌ๊ณผ web3.py๋กœ DApp ๊ฑฐ๋ž˜๋‚ด์—ญ ์‹œ๊ฐํ™” ํ”„๋กœ๊ทธ๋žจ ๊ตฌํ˜„

๐Ÿš€ ํŒŒ์ด์ฌ๊ณผ web3.py๋กœ DApp ๊ฑฐ๋ž˜๋‚ด์—ญ ์‹œ๊ฐํ™” ํ”„๋กœ๊ทธ๋žจ ๊ตฌํ˜„

๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ˆˆ์œผ๋กœ ๋ณด๋Š” ๋งˆ๋ฒ•, ์ง€๊ธˆ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค! โœจ

๐Ÿ’ฌ ์•ผ, ๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ ๋ณด๋ฉด์„œ ๋จธ๋ฆฌ ์•„ํ”„์ง€ ์•Š์•„? 16์ง„์ˆ˜๋กœ ๊ฐ€๋“ํ•œ ํŠธ๋žœ์žญ์…˜ ํ•ด์‹œ, ๋์—†์ด ์ด์–ด์ง€๋Š” ์ˆซ์ž๋“ค... ์ด๊ฑฐ ์‚ฌ๋žŒ์ด ๋ณผ ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ์ž–์•„! ๊ทธ๋ž˜์„œ ์˜ค๋Š˜์€ ํŒŒ์ด์ฌ๊ณผ web3.py๋ฅผ ํ™œ์šฉํ•ด์„œ DApp(ํƒˆ์ค‘์•™ํ™” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜)์˜ ๊ฑฐ๋ž˜๋‚ด์—ญ์„ ์˜ˆ์˜๊ณ  ์ง๊ด€์ ์ธ ์ฐจํŠธ์™€ ๊ทธ๋ž˜ํ”„๋กœ ๋งŒ๋“ค์–ด๋ณด๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค์ค„๊ฒŒ. ๐ŸŽจ

์ด ๊ธ€์„ ๋‹ค ์ฝ๊ณ  ๋‚˜๋ฉด, ๋„ˆ๋„ ๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€์ฒ˜๋Ÿผ ๋ฉ‹์ง„ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ!

๐ŸŽฏ ์™œ ๊ฑฐ๋ž˜๋‚ด์—ญ ์‹œ๊ฐํ™”๊ฐ€ ํ•„์š”ํ• ๊นŒ?

๋ธ”๋ก์ฒด์ธ์€ ํˆฌ๋ช…ํ•˜๋‹ค๊ณ  ํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋ฉด ์ „ํ˜€ ํˆฌ๋ช…ํ•˜์ง€ ์•Š์•„ ๋ณด์—ฌ. ๐Ÿ˜… ์ด๋”์Šค์บ”(Etherscan) ๊ฐ™์€ ๋ธ”๋ก ์ต์Šคํ”Œ๋กœ๋Ÿฌ๋ฅผ ๋ด๋„ ์ˆซ์ž์™€ ํ•ด์‹œ๊ฐ’ ์ฒœ์ง€์ž–์•„? ๊ทธ๋ž˜์„œ ์‹œ๊ฐํ™”๊ฐ€ ํ•„์š”ํ•œ ๊ฑฐ์•ผ.

์‹œ๊ฐํ™”๋ฅผ ํ•˜๋ฉด ์ด๋Ÿฐ ๊ฒŒ ๊ฐ€๋Šฅํ•ด์ ธ:

๐Ÿ“Š ๊ฑฐ๋ž˜ ํŒจํ„ด ํŒŒ์•…: ํŠน์ • ์‹œ๊ฐ„๋Œ€์— ๊ฑฐ๋ž˜๊ฐ€ ๋ชฐ๋ฆฌ๋Š”์ง€, ์–ด๋–ค ์ฃผ์†Œ๊ฐ€ ํ™œ๋ฐœํ•œ์ง€ ํ•œ๋ˆˆ์— ํ™•์ธ
๐Ÿ’ฐ ์ž๊ธˆ ํ๋ฆ„ ์ถ”์ : ํฐ ๊ธˆ์•ก์ด ์–ด๋””๋กœ ์ด๋™ํ•˜๋Š”์ง€ ์‹œ๊ฐ์ ์œผ๋กœ ์ถ”์ 
๐Ÿ” ์ด์ƒ ๊ฑฐ๋ž˜ ํƒ์ง€: ํ‰์†Œ์™€ ๋‹ค๋ฅธ ํŒจํ„ด์„ ๋น ๋ฅด๊ฒŒ ๋ฐœ๊ฒฌ
๐Ÿ“ˆ ๋น„์ฆˆ๋‹ˆ์Šค ์ธ์‚ฌ์ดํŠธ: DApp์˜ ์‚ฌ์šฉ๋Ÿ‰, ์ธ๊ธฐ ๊ธฐ๋Šฅ ๋“ฑ์„ ๋ฐ์ดํ„ฐ๋กœ ๋ถ„์„

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ๋ธ”๋ก์ฒด์ธ ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•œ๋‹ค๋ฉด, ์ด๋Ÿฐ ์‹œ๊ฐํ™” ๋„๊ตฌ๊ฐ€ ์—„์ฒญ ์œ ์šฉํ•  ๊ฑฐ์•ผ. ์‚ฌ์šฉ์ž๋“ค์˜ ๊ฑฐ๋ž˜ ํŒจํ„ด์„ ๋ถ„์„ํ•ด์„œ ๋” ๋‚˜์€ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐ŸŽฏ

๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ํ”„๋กœ์„ธ์Šค ๋ธ”๋ก์ฒด์ธ ํŠธ๋žœ์žญ์…˜ ๋ฐ์ดํ„ฐ 0x7a3f2b... web3.py ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ Python ์ฒ˜๋ฆฌ ์ •์ œ ๋ฐ ๋ณ€ํ™˜ ๋ถ„์„ ์‹œ๊ฐํ™” ์ฐจํŠธ ์ƒ์„ฑ ๋Œ€์‹œ๋ณด๋“œ ๊ฒฐ๊ณผ ์ธ์‚ฌ์ดํŠธ ์˜์‚ฌ๊ฒฐ์ • ๋น„์ฆˆ๋‹ˆ์Šค ํ™œ์šฉ ๐Ÿ“Š ๋ง‰๋Œ€ ์ฐจํŠธ ๐Ÿ“ˆ ์„  ๊ทธ๋ž˜ํ”„ ๐Ÿฅง ํŒŒ์ด ์ฐจํŠธ ๐Ÿ—บ๏ธ ํžˆํŠธ๋งต ์‹œ๊ฐํ™”์˜ ํ•ต์‹ฌ ๊ฐ€์น˜ ๋ณต์žกํ•œ ๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ˆ„๊ตฌ๋‚˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์˜์‚ฌ๊ฒฐ์ •์„ ์œ„ํ•œ ํ•„์ˆ˜ ๋„๊ตฌ

๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์ค€๋น„ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ๋จผ์ € ํ•„์š”ํ•œ ๋„๊ตฌ๋“ค์„ ์„ค์น˜ํ•ด์•ผ ํ•ด. ๊ฑฑ์ • ๋งˆ, ํ•˜๋‚˜ํ•˜๋‚˜ ์ฐจ๊ทผ์ฐจ๊ทผ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ˜Š

1๏ธโƒฃ Python ์„ค์น˜ ํ™•์ธ

Python 3.8 ์ด์ƒ์ด ํ•„์š”ํ•ด. ํ„ฐ๋ฏธ๋„์—์„œ ํ™•์ธํ•ด๋ณด์ž:

python --version
# ๋˜๋Š”
python3 --version

๋งŒ์•ฝ ์„ค์น˜๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด python.org์—์„œ ๋‹ค์šด๋กœ๋“œํ•˜๋ฉด ๋ผ!

2๏ธโƒฃ ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

์ด์ œ ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋“ค์„ ์„ค์น˜ํ•  ์ฐจ๋ก€์•ผ. ํ„ฐ๋ฏธ๋„์„ ์—ด๊ณ  ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ด:

pip install web3
pip install pandas
pip install matplotlib
pip install seaborn
pip install plotly
pip install python-dotenv

๊ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ญ ํ•˜๋Š” ๊ฑด์ง€ ๊ถ๊ธˆํ•˜์ง€? ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•ด์ค„๊ฒŒ:

๐Ÿ”— web3.py: ์ด๋”๋ฆฌ์›€ ๋ธ”๋ก์ฒด์ธ๊ณผ ํ†ต์‹ ํ•˜๋Š” ํ•ต์‹ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
๐Ÿ“Š pandas: ๋ฐ์ดํ„ฐ ๋ถ„์„๊ณผ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์ตœ๊ฐ• ๋„๊ตฌ
๐Ÿ“ˆ matplotlib: ๊ธฐ๋ณธ์ ์ธ ์ฐจํŠธ๋ฅผ ๊ทธ๋ฆฌ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
๐ŸŽจ seaborn: matplotlib์„ ๋” ์˜ˆ์˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์นœ๊ตฌ
โœจ plotly: ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•œ ์ฐจํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋ฉ‹์ง„ ๋„๊ตฌ
๐Ÿ” python-dotenv: API ํ‚ค ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌ

3๏ธโƒฃ ๋ธ”๋ก์ฒด์ธ ๋…ธ๋“œ ์ ‘๊ทผ ์„ค์ •

๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด ๋…ธ๋“œ์— ์ ‘๊ทผํ•ด์•ผ ํ•ด. ์ง์ ‘ ๋…ธ๋“œ๋ฅผ ์šด์˜ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ดˆ๋ณด์ž์—๊ฒŒ๋Š” Infura๋‚˜ Alchemy ๊ฐ™์€ ์„œ๋น„์Šค๋ฅผ ์ถ”์ฒœํ•ด!

๐Ÿ’ก Infura ์‹œ์ž‘ํ•˜๊ธฐ

1. infura.io์— ๊ฐ€์ž…
2. ์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
3. PROJECT ID ๋ณต์‚ฌ (์ด๊ฒŒ ๋„ˆ์˜ API ํ‚ค์•ผ!)
4. ํ”„๋กœ์ ํŠธ ํด๋”์— .env ํŒŒ์ผ ์ƒ์„ฑ
5. ๋‹ค์Œ ๋‚ด์šฉ ์ž…๋ ฅ: INFURA_PROJECT_ID=์—ฌ๊ธฐ์—_๋„ˆ์˜_ํ”„๋กœ์ ํŠธ_ID

๐ŸŽฌ ์ฒซ ๋ฒˆ์งธ ํ”„๋กœ์ ํŠธ: ๊ธฐ๋ณธ ๊ฑฐ๋ž˜๋‚ด์—ญ ๊ฐ€์ ธ์˜ค๊ธฐ

์ด์ œ ์ง„์งœ ์ฝ”๋”ฉ์„ ์‹œ์ž‘ํ•ด๋ณด์ž! ๋จผ์ € ๊ฐ„๋‹จํ•˜๊ฒŒ ํŠน์ • ์ฃผ์†Œ์˜ ๊ฑฐ๋ž˜๋‚ด์—ญ์„ ๊ฐ€์ ธ์˜ค๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๊ฑฐ์•ผ. ๐Ÿš€

๐Ÿ“ ๊ธฐ๋ณธ ์—ฐ๊ฒฐ ์ฝ”๋“œ

from web3 import Web3
from dotenv import load_dotenv
import os

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()

# Infura ์—ฐ๊ฒฐ
infura_url = f"https://mainnet.infura.io/v3/{os.getenv('INFURA_PROJECT_ID')}"
web3 = Web3(Web3.HTTPProvider(infura_url))

# ์—ฐ๊ฒฐ ํ™•์ธ
if web3.is_connected():
    print("โœ… ๋ธ”๋ก์ฒด์ธ ์—ฐ๊ฒฐ ์„ฑ๊ณต!")
    print(f"ํ˜„์žฌ ๋ธ”๋ก ๋ฒˆํ˜ธ: {web3.eth.block_number}")
else:
    print("โŒ ์—ฐ๊ฒฐ ์‹คํŒจ... ์„ค์ •์„ ํ™•์ธํ•ด๋ณด์„ธ์š”!")

์ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ํ˜„์žฌ ์ด๋”๋ฆฌ์›€ ๋ฉ”์ธ๋„ท์˜ ์ตœ์‹  ๋ธ”๋ก ๋ฒˆํ˜ธ๊ฐ€ ์ถœ๋ ฅ๋  ๊ฑฐ์•ผ. ๋งŒ์•ฝ ์—ฐ๊ฒฐ์ด ์•ˆ ๋œ๋‹ค๋ฉด API ํ‚ค๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด๋ด! ๐Ÿ”

๐Ÿ“ ํŠน์ • ์ฃผ์†Œ์˜ ์ž”์•ก ์กฐํšŒ

def get_balance(address):
    """
    ํŠน์ • ์ฃผ์†Œ์˜ ์ด๋”๋ฆฌ์›€ ์ž”์•ก์„ ์กฐํšŒํ•˜๋Š” ํ•จ์ˆ˜
    """
    # ์ฒดํฌ์„ฌ ์ฃผ์†Œ๋กœ ๋ณ€ํ™˜ (๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„)
    checksum_address = web3.to_checksum_address(address)
    
    # Wei ๋‹จ์œ„๋กœ ์ž”์•ก ์กฐํšŒ
    balance_wei = web3.eth.get_balance(checksum_address)
    
    # Ether ๋‹จ์œ„๋กœ ๋ณ€ํ™˜
    balance_eth = web3.from_wei(balance_wei, 'ether')
    
    return balance_eth

# ์˜ˆ์‹œ: Vitalik์˜ ์ฃผ์†Œ (์ด๋”๋ฆฌ์›€ ์ฐฝ์‹œ์ž)
vitalik_address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
balance = get_balance(vitalik_address)
print(f"๐Ÿ’ฐ ์ž”์•ก: {balance} ETH")

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ

๋ธ”๋ก์ฒด์ธ ์ฃผ์†Œ๋Š” ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ์ฒดํฌ์„ฌ ํ˜•์‹์ด์•ผ. ๋ฐ˜๋“œ์‹œ web3.to_checksum_address()๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ด! ์•ˆ ๊ทธ๋Ÿฌ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚  ์ˆ˜ ์žˆ์–ด.

๐Ÿ“Š ๊ฑฐ๋ž˜๋‚ด์—ญ ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„

์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๊ฑฐ๋ž˜๋‚ด์—ญ์„ ์ˆ˜์ง‘ํ•ด๋ณผ ๊ฑฐ์•ผ. ํŠน์ • ๋ธ”๋ก ๋ฒ”์œ„ ๋‚ด์˜ ๋ชจ๋“  ๊ฑฐ๋ž˜๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋ถ„์„ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ๐Ÿ’ช

๐Ÿ“ ๋ธ”๋ก ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ํ•จ์ˆ˜

import pandas as pd
from datetime import datetime

def get_block_transactions(start_block, end_block):
    """
    ํŠน์ • ๋ธ”๋ก ๋ฒ”์œ„์˜ ๋ชจ๋“  ๊ฑฐ๋ž˜๋ฅผ ์ˆ˜์ง‘ํ•˜๋Š” ํ•จ์ˆ˜
    """
    transactions = []
    
    print(f"๐Ÿ“ฆ ๋ธ”๋ก {start_block}๋ถ€ํ„ฐ {end_block}๊นŒ์ง€ ์ˆ˜์ง‘ ์ค‘...")
    
    for block_num in range(start_block, end_block + 1):
        # ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
        if block_num % 10 == 0:
            print(f"์ง„ํ–‰ ์ค‘: {block_num}/{end_block}")
        
        # ๋ธ”๋ก ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
        block = web3.eth.get_block(block_num, full_transactions=True)
        
        # ๋ธ”๋ก ๋‚ด ๋ชจ๋“  ๊ฑฐ๋ž˜ ์ฒ˜๋ฆฌ
        for tx in block.transactions:
            transactions.append({
                'block_number': block_num,
                'timestamp': datetime.fromtimestamp(block.timestamp),
                'hash': tx.hash.hex(),
                'from': tx['from'],
                'to': tx.to if tx.to else 'Contract Creation',
                'value_eth': web3.from_wei(tx.value, 'ether'),
                'gas_price': web3.from_wei(tx.gasPrice, 'gwei'),
                'gas_used': tx.gas
            })
    
    # DataFrame์œผ๋กœ ๋ณ€ํ™˜
    df = pd.DataFrame(transactions)
    print(f"โœ… ์ด {len(df)}๊ฐœ์˜ ๊ฑฐ๋ž˜ ์ˆ˜์ง‘ ์™„๋ฃŒ!")
    
    return df

# ์ตœ๊ทผ 100๊ฐœ ๋ธ”๋ก์˜ ๊ฑฐ๋ž˜ ์ˆ˜์ง‘
latest_block = web3.eth.block_number
start = latest_block - 100
end = latest_block

df_transactions = get_block_transactions(start, end)
print(df_transactions.head())

์ด ์ฝ”๋“œ๋Š” ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์–ด. ๋ธ”๋ก์ฒด์ธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฑด ๋„คํŠธ์›Œํฌ ์†๋„์— ์˜ํ–ฅ์„ ๋ฐ›๊ฑฐ๋“ . ์ธ๋‚ด์‹ฌ์„ ๊ฐ€์ง€๊ณ  ๊ธฐ๋‹ค๋ ค๋ด! โฐ

๐Ÿ’ก ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

โ€ข ๋„ˆ๋ฌด ๋งŽ์€ ๋ธ”๋ก์„ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค์ง€ ๋งˆ! 100~1000๊ฐœ ์ •๋„๊ฐ€ ์ ๋‹นํ•ด
โ€ข ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•œ ํ›„์—๋Š” CSV ํŒŒ์ผ๋กœ ์ €์žฅํ•ด๋‘๋ฉด ๋‹ค์Œ์— ๋‹ค์‹œ ์ˆ˜์ง‘ํ•  ํ•„์š”๊ฐ€ ์—†์–ด
โ€ข Infura ๋ฌด๋ฃŒ ํ”Œ๋žœ์€ ํ•˜๋ฃจ 10๋งŒ ๊ฑด ์š”์ฒญ ์ œํ•œ์ด ์žˆ์œผ๋‹ˆ ์ฃผ์˜!
โ€ข ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๋น ๋ฅด๊ฒŒ ์ˆ˜์ง‘ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, API ์ œํ•œ์„ ์กฐ์‹ฌํ•ด์•ผ ํ•ด

๐Ÿ“ˆ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” - ๊ธฐ๋ณธ ์ฐจํŠธ ๋งŒ๋“ค๊ธฐ

๋“œ๋””์–ด ์‹œ๊ฐํ™” ๋‹จ๊ณ„์•ผ! ์ˆ˜์ง‘ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์˜ˆ์œ ์ฐจํŠธ๋กœ ๋งŒ๋“ค์–ด๋ณด์ž. ๋จผ์ € matplotlib๊ณผ seaborn์„ ์‚ฌ์šฉํ•œ ๊ธฐ๋ณธ ์ฐจํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ• ๊ฒŒ. ๐ŸŽจ

๐Ÿ“ ์‹œ๊ฐ„๋Œ€๋ณ„ ๊ฑฐ๋ž˜๋Ÿ‰ ์ฐจํŠธ

import matplotlib.pyplot as plt
import seaborn as sns

# ํ•œ๊ธ€ ํฐํŠธ ์„ค์ • (๋งฅ: AppleGothic, ์œˆ๋„์šฐ: Malgun Gothic)
plt.rcParams['font.family'] = 'AppleGothic'
plt.rcParams['axes.unicode_minus'] = False

def plot_transactions_over_time(df):
    """
    ์‹œ๊ฐ„๋Œ€๋ณ„ ๊ฑฐ๋ž˜๋Ÿ‰์„ ์‹œ๊ฐํ™”ํ•˜๋Š” ํ•จ์ˆ˜
    """
    # ์‹œ๊ฐ„๋Œ€๋ณ„ ๊ฑฐ๋ž˜ ์ˆ˜ ์ง‘๊ณ„
    df['hour'] = df['timestamp'].dt.hour
    hourly_counts = df.groupby('hour').size()
    
    # ์ฐจํŠธ ์ƒ์„ฑ
    plt.figure(figsize=(12, 6))
    sns.barplot(x=hourly_counts.index, y=hourly_counts.values, palette='viridis')
    
    plt.title('์‹œ๊ฐ„๋Œ€๋ณ„ ๊ฑฐ๋ž˜๋Ÿ‰ ๋ถ„ํฌ', fontsize=16, fontweight='bold')
    plt.xlabel('์‹œ๊ฐ„ (Hour)', fontsize=12)
    plt.ylabel('๊ฑฐ๋ž˜ ์ˆ˜', fontsize=12)
    plt.xticks(range(24))
    plt.grid(axis='y', alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('transactions_by_hour.png', dpi=300)
    plt.show()
    
    print("๐Ÿ“Š ์ฐจํŠธ๊ฐ€ 'transactions_by_hour.png'๋กœ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")

# ์‹คํ–‰
plot_transactions_over_time(df_transactions)

๐Ÿ“ ๊ฑฐ๋ž˜ ๊ธˆ์•ก ๋ถ„ํฌ ์ฐจํŠธ

def plot_value_distribution(df):
    """
    ๊ฑฐ๋ž˜ ๊ธˆ์•ก ๋ถ„ํฌ๋ฅผ ํžˆ์Šคํ† ๊ทธ๋žจ์œผ๋กœ ์‹œ๊ฐํ™”
    """
    # 0 ETH ๊ฑฐ๋ž˜ ์ œ์™ธ (์ปจํŠธ๋ž™ํŠธ ํ˜ธ์ถœ ๋“ฑ)
    df_filtered = df[df['value_eth'] > 0]
    
    plt.figure(figsize=(12, 6))
    
    # ๋กœ๊ทธ ์Šค์ผ€์ผ๋กœ ํ‘œ์‹œ (๊ธˆ์•ก ์ฐจ์ด๊ฐ€ ํฌ๊ธฐ ๋•Œ๋ฌธ)
    plt.hist(df_filtered['value_eth'], bins=50, color='#667eea', 
             alpha=0.7, edgecolor='black')
    
    plt.title('๊ฑฐ๋ž˜ ๊ธˆ์•ก ๋ถ„ํฌ', fontsize=16, fontweight='bold')
    plt.xlabel('๊ฑฐ๋ž˜ ๊ธˆ์•ก (ETH)', fontsize=12)
    plt.ylabel('๋นˆ๋„', fontsize=12)
    plt.yscale('log')  # Y์ถ•์„ ๋กœ๊ทธ ์Šค์ผ€์ผ๋กœ
    plt.grid(axis='y', alpha=0.3)
    
    # ํ†ต๊ณ„ ์ •๋ณด ์ถ”๊ฐ€
    mean_value = df_filtered['value_eth'].mean()
    median_value = df_filtered['value_eth'].median()
    
    plt.axvline(mean_value, color='red', linestyle='--', 
                label=f'ํ‰๊ท : {mean_value:.4f} ETH')
    plt.axvline(median_value, color='green', linestyle='--', 
                label=f'์ค‘์•™๊ฐ’: {median_value:.4f} ETH')
    
    plt.legend()
    plt.tight_layout()
    plt.savefig('value_distribution.png', dpi=300)
    plt.show()

plot_value_distribution(df_transactions)
๋‹ค์–‘ํ•œ ์ฐจํŠธ ์œ ํ˜• ์„ ํƒ ๊ฐ€์ด๋“œ ๋ง‰๋Œ€ ์ฐจํŠธ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋น„๊ต ์‹œ๊ฐ„๋Œ€๋ณ„ ๊ฑฐ๋ž˜๋Ÿ‰ ์„  ๊ทธ๋ž˜ํ”„ ์‹œ๊ณ„์—ด ์ถ”์„ธ ๊ฐ€๊ฒฉ ๋ณ€๋™ ์ถ”์ด ํŒŒ์ด ์ฐจํŠธ ๋น„์œจ ๋ฐ ๊ตฌ์„ฑ ์ง€๊ฐ‘ ์œ ํ˜• ๋ถ„ํฌ ํžˆํŠธ๋งต ํŒจํ„ด ๋ฐ ๋ฐ€๋„ ์‹œ๊ฐ„๋Œ€๋ณ„ ํ™œ๋™๋Ÿ‰ ์ฐจํŠธ ์„ ํƒ ๊ธฐ์ค€ ๋น„๊ต๊ฐ€ ํ•„์š”ํ•œ๊ฐ€? ์‹œ๊ฐ„ ํ๋ฆ„์„ ๋ณด์—ฌ์ค˜์•ผ ํ•˜๋‚˜? ์ „์ฒด ๋Œ€๋น„ ๋น„์œจ์ด ์ค‘์š”ํ•œ๊ฐ€? ๋‹ค์ฐจ์› ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œํ˜„ํ•ด์•ผ ํ•˜๋‚˜? ๋ง‰๋Œ€ ์ฐจํŠธ ์„  ๊ทธ๋ž˜ํ”„ ํŒŒ์ด ์ฐจํŠธ ํžˆํŠธ๋งต ๋˜๋Š” ๋ฒ„๋ธ” ์ฐจํŠธ

โœจ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ๋Œ€์‹œ๋ณด๋“œ ๋งŒ๋“ค๊ธฐ - Plotly ํ™œ์šฉ

์ •์ ์ธ ์ฐจํŠธ๋„ ์ข‹์ง€๋งŒ, ๋งˆ์šฐ์Šค๋กœ ํ™•๋Œ€ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์ฐจํŠธ๋Š” ํ›จ์”ฌ ๋” ๋ฉ‹์ ธ! Plotly๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋™์ž‘ํ•˜๋Š” ๋ฉ‹์ง„ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ๐ŸŒŸ

๐Ÿ“ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๊ณ„์—ด ์ฐจํŠธ

import plotly.graph_objects as go
import plotly.express as px

def create_interactive_timeline(df):
    """
    ์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ๊ฑฐ๋ž˜๋Ÿ‰๊ณผ ํ‰๊ท  ๊ธˆ์•ก์„ ๋ณด์—ฌ์ฃผ๋Š” ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์ฐจํŠธ
    """
    # ์‹œ๊ฐ„๋Œ€๋ณ„ ์ง‘๊ณ„
    df['date'] = df['timestamp'].dt.date
    daily_stats = df.groupby('date').agg({
        'hash': 'count',  # ๊ฑฐ๋ž˜ ์ˆ˜
        'value_eth': 'mean',  # ํ‰๊ท  ๊ฑฐ๋ž˜ ๊ธˆ์•ก
        'gas_price': 'mean'  # ํ‰๊ท  ๊ฐ€์Šค ๊ฐ€๊ฒฉ
    }).reset_index()
    
    daily_stats.columns = ['date', 'tx_count', 'avg_value', 'avg_gas']
    
    # Figure ์ƒ์„ฑ
    fig = go.Figure()
    
    # ๊ฑฐ๋ž˜ ์ˆ˜ ์ถ”๊ฐ€
    fig.add_trace(go.Scatter(
        x=daily_stats['date'],
        y=daily_stats['tx_count'],
        name='๊ฑฐ๋ž˜ ์ˆ˜',
        mode='lines+markers',
        line=dict(color='#667eea', width=3),
        marker=dict(size=8)
    ))
    
    # ํ‰๊ท  ๊ฑฐ๋ž˜ ๊ธˆ์•ก ์ถ”๊ฐ€ (๋ณด์กฐ Y์ถ•)
    fig.add_trace(go.Scatter(
        x=daily_stats['date'],
        y=daily_stats['avg_value'],
        name='ํ‰๊ท  ๊ฑฐ๋ž˜ ๊ธˆ์•ก (ETH)',
        mode='lines+markers',
        line=dict(color='#f5576c', width=3, dash='dash'),
        marker=dict(size=8),
        yaxis='y2'
    ))
    
    # ๋ ˆ์ด์•„์›ƒ ์„ค์ •
    fig.update_layout(
        title='์ผ๋ณ„ ๊ฑฐ๋ž˜ ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ',
        xaxis=dict(title='๋‚ ์งœ'),
        yaxis=dict(title='๊ฑฐ๋ž˜ ์ˆ˜', side='left'),
        yaxis2=dict(title='ํ‰๊ท  ๊ฑฐ๋ž˜ ๊ธˆ์•ก (ETH)', 
                    side='right', overlaying='y'),
        hovermode='x unified',
        template='plotly_white',
        height=600
    )
    
    # HTML ํŒŒ์ผ๋กœ ์ €์žฅ
    fig.write_html('interactive_dashboard.html')
    print("โœ… ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
    print("๐Ÿ“‚ 'interactive_dashboard.html' ํŒŒ์ผ์„ ๋ธŒ๋ผ์šฐ์ €๋กœ ์—ด์–ด๋ณด์„ธ์š”!")
    
    return fig

# ์‹คํ–‰
fig = create_interactive_timeline(df_transactions)
fig.show()

๐ŸŽฏ Plotly์˜ ์žฅ์ 

์คŒ ์ธ/์•„์›ƒ: ๋งˆ์šฐ์Šค ๋“œ๋ž˜๊ทธ๋กœ ํŠน์ • ๊ตฌ๊ฐ„ ํ™•๋Œ€ ๊ฐ€๋Šฅ
ํ˜ธ๋ฒ„ ์ •๋ณด: ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ฆฌ๋ฉด ์ƒ์„ธ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ
๋ฒ”๋ก€ ํด๋ฆญ: ํŠน์ • ๋ฐ์ดํ„ฐ ์‹œ๋ฆฌ์ฆˆ๋ฅผ ์ˆจ๊ธฐ๊ฑฐ๋‚˜ ํ‘œ์‹œ
์ด๋ฏธ์ง€ ์ €์žฅ: ์ฐจํŠธ๋ฅผ PNG๋กœ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅ
๋ฐ˜์‘ํ˜•: ๋ชจ๋ฐ”์ผ์—์„œ๋„ ์ž˜ ์ž‘๋™

๐ŸŽจ ๊ณ ๊ธ‰ ์‹œ๊ฐํ™” - ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„

๋ธ”๋ก์ฒด์ธ์˜ ์ง„์งœ ๋งค๋ ฅ์€ ์ฃผ์†Œ ๊ฐ„์˜ ์—ฐ๊ฒฐ ๊ด€๊ณ„๋ฅผ ์‹œ๊ฐํ™”ํ•˜๋Š” ๊ฑฐ์•ผ! ๋ˆ„๊ฐ€ ๋ˆ„๊ตฌ์—๊ฒŒ ๋ˆ์„ ๋ณด๋ƒˆ๋Š”์ง€, ์–ด๋–ค ์ฃผ์†Œ๊ฐ€ ํ—ˆ๋ธŒ ์—ญํ• ์„ ํ•˜๋Š”์ง€ ํ•œ๋ˆˆ์— ๋ณผ ์ˆ˜ ์žˆ์–ด. ๐Ÿ•ธ๏ธ

๐Ÿ“ ๊ฑฐ๋ž˜ ๋„คํŠธ์›Œํฌ ์‹œ๊ฐํ™”

import networkx as nx
import plotly.graph_objects as go

def create_transaction_network(df, min_value=0.1):
    """
    ๊ฑฐ๋ž˜ ๋„คํŠธ์›Œํฌ๋ฅผ ์‹œ๊ฐํ™”ํ•˜๋Š” ํ•จ์ˆ˜
    min_value: ํ‘œ์‹œํ•  ์ตœ์†Œ ๊ฑฐ๋ž˜ ๊ธˆ์•ก (ETH)
    """
    # ํฐ ๊ฑฐ๋ž˜๋งŒ ํ•„ํ„ฐ๋ง
    df_filtered = df[df['value_eth'] >= min_value].copy()
    
    # ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ
    G = nx.DiGraph()
    
    # ๋…ธ๋“œ์™€ ์—ฃ์ง€ ์ถ”๊ฐ€
    for _, row in df_filtered.iterrows():
        from_addr = row['from'][:10] + '...'  # ์ฃผ์†Œ ์ถ•์•ฝ
        to_addr = row['to'][:10] + '...' if row['to'] != 'Contract Creation' else 'Contract'
        value = float(row['value_eth'])
        
        # ์—ฃ์ง€ ์ถ”๊ฐ€ (๊ฐ€์ค‘์น˜๋Š” ๊ฑฐ๋ž˜ ๊ธˆ์•ก)
        if G.has_edge(from_addr, to_addr):
            G[from_addr][to_addr]['weight'] += value
        else:
            G.add_edge(from_addr, to_addr, weight=value)
    
    # ๋ ˆ์ด์•„์›ƒ ๊ณ„์‚ฐ
    pos = nx.spring_layout(G, k=0.5, iterations=50)
    
    # ์—ฃ์ง€ ๊ทธ๋ฆฌ๊ธฐ
    edge_trace = []
    for edge in G.edges():
        x0, y0 = pos[edge[0]]
        x1, y1 = pos[edge[1]]
        weight = G[edge[0]][edge[1]]['weight']
        
        edge_trace.append(go.Scatter(
            x=[x0, x1, None],
            y=[y0, y1, None],
            mode='lines',
            line=dict(width=min(weight * 2, 10), color='#cbd5e1'),
            hoverinfo='none',
            showlegend=False
        ))
    
    # ๋…ธ๋“œ ๊ทธ๋ฆฌ๊ธฐ
    node_x = []
    node_y = []
    node_text = []
    node_size = []
    
    for node in G.nodes():
        x, y = pos[node]
        node_x.append(x)
        node_y.append(y)
        
        # ๋…ธ๋“œ ์ •๋ณด
        in_degree = G.in_degree(node)
        out_degree = G.out_degree(node)
        node_text.append(f'{node}<br>๋ฐ›์€ ๊ฑฐ๋ž˜: {in_degree}<br>๋ณด๋‚ธ ๊ฑฐ๋ž˜: {out_degree}')
        node_size.append(20 + (in_degree + out_degree) * 5)
    
    node_trace = go.Scatter(
        x=node_x, y=node_y,
        mode='markers+text',
        hoverinfo='text',
        text=node_text,
        marker=dict(
            size=node_size,
            color='#667eea',
            line=dict(width=2, color='white')
        )
    )
    
    # Figure ์ƒ์„ฑ
    fig = go.Figure(data=edge_trace + [node_trace])
    
    fig.update_layout(
        title='๊ฑฐ๋ž˜ ๋„คํŠธ์›Œํฌ ์‹œ๊ฐํ™”',
        showlegend=False,
        hovermode='closest',
        margin=dict(b=0, l=0, r=0, t=40),
        xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
        yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
        height=700,
        template='plotly_white'
    )
    
    fig.write_html('transaction_network.html')
    print("๐Ÿ•ธ๏ธ ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
    
    return fig

# ์‹คํ–‰
network_fig = create_transaction_network(df_transactions, min_value=0.5)
network_fig.show()

์ด ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„๋ฅผ ๋ณด๋ฉด ์–ด๋–ค ์ฃผ์†Œ๊ฐ€ ์ค‘์‹ฌ ์—ญํ• ์„ ํ•˜๋Š”์ง€, ์ž๊ธˆ์ด ์–ด๋–ป๊ฒŒ ํ๋ฅด๋Š”์ง€ ์ง๊ด€์ ์œผ๋กœ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด. ๊ฑฐ๋ž˜์†Œ ์ฃผ์†Œ๋‚˜ ํฐ ๊ณ ๋ž˜(whale) ์ง€๊ฐ‘์„ ์ฐพ๋Š” ๋ฐ ์œ ์šฉํ•˜์ง€! ๐Ÿ‹

๐Ÿ“Š ์‹ค์ „ ํ”„๋กœ์ ํŠธ: DApp ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ

์ด์ œ ๋ฐฐ์šด ๊ฑธ ๋‹ค ํ•ฉ์ณ์„œ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” DApp ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! Uniswap ๊ฐ™์€ DEX(ํƒˆ์ค‘์•™ํ™” ๊ฑฐ๋ž˜์†Œ)์˜ ๊ฑฐ๋ž˜๋ฅผ ๋ถ„์„ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค ๊ฑฐ์•ผ. ๐Ÿฆ„

๐Ÿ“ ์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ ์ด๋ฒคํŠธ ๋ชจ๋‹ˆํ„ฐ๋ง

from web3 import Web3
import json

# Uniswap V2 Router ์ฃผ์†Œ
UNISWAP_ROUTER = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"

# ERC20 Transfer ์ด๋ฒคํŠธ ์‹œ๊ทธ๋‹ˆ์ฒ˜
TRANSFER_EVENT_SIGNATURE = web3.keccak(text="Transfer(address,address,uint256)").hex()

def get_uniswap_swaps(start_block, end_block):
    """
    Uniswap ์Šค์™‘ ๊ฑฐ๋ž˜๋ฅผ ์ˆ˜์ง‘ํ•˜๋Š” ํ•จ์ˆ˜
    """
    swaps = []
    
    # ์ด๋ฒคํŠธ ํ•„ํ„ฐ ์ƒ์„ฑ
    event_filter = {
        'fromBlock': start_block,
        'toBlock': end_block,
        'address': UNISWAP_ROUTER
    }
    
    # ๋กœ๊ทธ ๊ฐ€์ ธ์˜ค๊ธฐ
    logs = web3.eth.get_logs(event_filter)
    
    print(f"๐Ÿ“ฅ {len(logs)}๊ฐœ์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค!")
    
    for log in logs:
        try:
            # ํŠธ๋žœ์žญ์…˜ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
            tx = web3.eth.get_transaction(log['transactionHash'])
            receipt = web3.eth.get_transaction_receipt(log['transactionHash'])
            
            swaps.append({
                'block': log['blockNumber'],
                'tx_hash': log['transactionHash'].hex(),
                'from': tx['from'],
                'gas_used': receipt['gasUsed'],
                'status': receipt['status']
            })
        except Exception as e:
            print(f"โš ๏ธ ์—๋Ÿฌ ๋ฐœ์ƒ: {e}")
            continue
    
    return pd.DataFrame(swaps)

# ์ตœ๊ทผ 1000๋ธ”๋ก ๋ถ„์„
latest = web3.eth.block_number
df_swaps = get_uniswap_swaps(latest - 1000, latest)
print(df_swaps.head())

๐Ÿ’ก ์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ ABI ํ™œ์šฉํ•˜๊ธฐ

์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ์™€ ์ œ๋Œ€๋กœ ์ƒํ˜ธ์ž‘์šฉํ•˜๋ ค๋ฉด ABI(Application Binary Interface)๊ฐ€ ํ•„์š”ํ•ด. ABI๋Š” ์ปจํŠธ๋ž™ํŠธ์˜ ํ•จ์ˆ˜์™€ ์ด๋ฒคํŠธ ์ •์˜๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” JSON ํŒŒ์ผ์ด์•ผ.

Etherscan์—์„œ ๊ฒ€์ฆ๋œ ์ปจํŠธ๋ž™ํŠธ์˜ ABI๋ฅผ ๋ณต์‚ฌํ•  ์ˆ˜ ์žˆ์–ด. ๊ทธ๋Ÿผ ํ•จ์ˆ˜ ํ˜ธ์ถœ์ด๋‚˜ ์ด๋ฒคํŠธ ๋””์ฝ”๋”ฉ์ด ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ!

๐Ÿ“ ์ข…ํ•ฉ ๋Œ€์‹œ๋ณด๋“œ ํด๋ž˜์Šค

class DAppAnalyticsDashboard:
    """
    DApp ๋ถ„์„์„ ์œ„ํ•œ ์ข…ํ•ฉ ๋Œ€์‹œ๋ณด๋“œ ํด๋ž˜์Šค
    """
    
    def __init__(self, web3_provider, contract_address):
        self.web3 = Web3(Web3.HTTPProvider(web3_provider))
        self.contract_address = contract_address
        self.data = None
    
    def collect_data(self, start_block, end_block):
        """๋ฐ์ดํ„ฐ ์ˆ˜์ง‘"""
        print("๐Ÿ”„ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์‹œ์ž‘...")
        self.data = get_block_transactions(start_block, end_block)
        print(f"โœ… {len(self.data)}๊ฐœ ๊ฑฐ๋ž˜ ์ˆ˜์ง‘ ์™„๋ฃŒ!")
    
    def analyze_users(self):
        """์‚ฌ์šฉ์ž ๋ถ„์„"""
        if self.data is None:
            print("โŒ ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜์„ธ์š”!")
            return
        
        # ํ™œ์„ฑ ์‚ฌ์šฉ์ž ์ˆ˜
        unique_users = pd.concat([
            self.data['from'],
            self.data['to']
        ]).nunique()
        
        # ๊ฑฐ๋ž˜ ๋นˆ๋„ ๋ถ„์„
        user_activity = self.data['from'].value_counts()
        
        print(f"๐Ÿ‘ฅ ์ด ํ™œ์„ฑ ์‚ฌ์šฉ์ž: {unique_users}๋ช…")
        print(f"๐Ÿ“Š ํ‰๊ท  ๊ฑฐ๋ž˜ ํšŸ์ˆ˜: {user_activity.mean():.2f}ํšŒ")
        print(f"๐Ÿ”ฅ ์ตœ๋‹ค ๊ฑฐ๋ž˜ ์‚ฌ์šฉ์ž: {user_activity.iloc[0]}ํšŒ")
        
        return {
            'total_users': unique_users,
            'avg_transactions': user_activity.mean(),
            'top_user_txs': user_activity.iloc[0]
        }
    
    def analyze_gas(self):
        """๊ฐ€์Šค ๋น„์šฉ ๋ถ„์„"""
        avg_gas = self.data['gas_price'].mean()
        total_gas = (self.data['gas_price'] * self.data['gas_used']).sum()
        
        print(f"โ›ฝ ํ‰๊ท  ๊ฐ€์Šค ๊ฐ€๊ฒฉ: {avg_gas:.2f} Gwei")
        print(f"๐Ÿ’ธ ์ด ๊ฐ€์Šค ๋น„์šฉ: {web3.from_wei(total_gas, 'ether'):.4f} ETH")
        
        return {
            'avg_gas_price': avg_gas,
            'total_gas_cost': total_gas
        }
    
    def create_dashboard(self):
        """์ข…ํ•ฉ ๋Œ€์‹œ๋ณด๋“œ ์ƒ์„ฑ"""
        from plotly.subplots import make_subplots
        
        # ์„œ๋ธŒํ”Œ๋กฏ ์ƒ์„ฑ (2x2 ๊ทธ๋ฆฌ๋“œ)
        fig = make_subplots(
            rows=2, cols=2,
            subplot_titles=('์‹œ๊ฐ„๋Œ€๋ณ„ ๊ฑฐ๋ž˜๋Ÿ‰', '๊ฑฐ๋ž˜ ๊ธˆ์•ก ๋ถ„ํฌ',
                          '๊ฐ€์Šค ๊ฐ€๊ฒฉ ์ถ”์ด', '์ƒ์œ„ ๊ฑฐ๋ž˜์ž'),
            specs=[[{'type': 'bar'}, {'type': 'histogram'}],
                   [{'type': 'scatter'}, {'type': 'bar'}]]
        )
        
        # 1. ์‹œ๊ฐ„๋Œ€๋ณ„ ๊ฑฐ๋ž˜๋Ÿ‰
        hourly = self.data.groupby(self.data['timestamp'].dt.hour).size()
        fig.add_trace(
            go.Bar(x=hourly.index, y=hourly.values, name='๊ฑฐ๋ž˜๋Ÿ‰'),
            row=1, col=1
        )
        
        # 2. ๊ฑฐ๋ž˜ ๊ธˆ์•ก ๋ถ„ํฌ
        fig.add_trace(
            go.Histogram(x=self.data['value_eth'], name='๊ธˆ์•ก ๋ถ„ํฌ'),
            row=1, col=2
        )
        
        # 3. ๊ฐ€์Šค ๊ฐ€๊ฒฉ ์ถ”์ด
        fig.add_trace(
            go.Scatter(x=self.data['timestamp'], 
                      y=self.data['gas_price'],
                      mode='lines', name='๊ฐ€์Šค ๊ฐ€๊ฒฉ'),
            row=2, col=1
        )
        
        # 4. ์ƒ์œ„ ๊ฑฐ๋ž˜์ž
        top_users = self.data['from'].value_counts().head(10)
        fig.add_trace(
            go.Bar(x=top_users.values, y=top_users.index, 
                  orientation='h', name='๊ฑฐ๋ž˜ ํšŸ์ˆ˜'),
            row=2, col=2
        )
        
        # ๋ ˆ์ด์•„์›ƒ ์„ค์ •
        fig.update_layout(
            height=900,
            showlegend=False,
            title_text="DApp ์ข…ํ•ฉ ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ"
        )
        
        fig.write_html('dapp_dashboard.html')
        print("๐ŸŽ‰ ๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
        
        return fig

# ์‚ฌ์šฉ ์˜ˆ์‹œ
dashboard = DAppAnalyticsDashboard(infura_url, UNISWAP_ROUTER)
dashboard.collect_data(latest - 500, latest)
dashboard.analyze_users()
dashboard.analyze_gas()
dashboard.create_dashboard()

๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ์‹ค์ „ ํŒ

์‹ค์ œ๋กœ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํ•„์ˆ˜์•ผ! ๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ๋Š” ๋ฐฉ๋Œ€ํ•˜๊ณ , API ํ˜ธ์ถœ์—๋Š” ์ œํ•œ์ด ์žˆ๊ฑฐ๋“ . ๋ช‡ ๊ฐ€์ง€ ๊ฟ€ํŒ์„ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿฏ

1๏ธโƒฃ ๋ฐ์ดํ„ฐ ์บ์‹ฑ

ํ•œ ๋ฒˆ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋Š” ๋กœ์ปฌ์— ์ €์žฅํ•ด๋‘์ž. ๋งค๋ฒˆ ๋ธ”๋ก์ฒด์ธ์—์„œ ๊ฐ€์ ธ์˜ฌ ํ•„์š” ์—†์–ด!

import pickle
from pathlib import Path

def save_cache(data, filename):
    """๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ"""
    cache_dir = Path('cache')
    cache_dir.mkdir(exist_ok=True)
    
    with open(cache_dir / filename, 'wb') as f:
        pickle.dump(data, f)
    print(f"๐Ÿ’พ ์บ์‹œ ์ €์žฅ ์™„๋ฃŒ: {filename}")

def load_cache(filename):
    """์บ์‹œ ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ"""
    cache_file = Path('cache') / filename
    
    if cache_file.exists():
        with open(cache_file, 'rb') as f:
            data = pickle.load(f)
        print(f"๐Ÿ“‚ ์บ์‹œ ๋กœ๋“œ ์™„๋ฃŒ: {filename}")
        return data
    return None

# ์‚ฌ์šฉ ์˜ˆ์‹œ
cached_data = load_cache('transactions.pkl')
if cached_data is None:
    # ์บ์‹œ๊ฐ€ ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ˆ˜์ง‘
    cached_data = get_block_transactions(start, end)
    save_cache(cached_data, 'transactions.pkl')

2๏ธโƒฃ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ

ํ•œ ๋ฒˆ์— ๋„ˆ๋ฌด ๋งŽ์€ ๋ธ”๋ก์„ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ง๊ณ , ๋ฐฐ์น˜๋กœ ๋‚˜๋ˆ ์„œ ์ฒ˜๋ฆฌํ•˜์ž!

def batch_process_blocks(start_block, end_block, batch_size=100):
    """
    ๋ธ”๋ก์„ ๋ฐฐ์น˜ ๋‹จ์œ„๋กœ ์ฒ˜๋ฆฌ
    """
    all_data = []
    
    for i in range(start_block, end_block, batch_size):
        batch_end = min(i + batch_size, end_block)
        print(f"๐Ÿ”„ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์ค‘: {i} ~ {batch_end}")
        
        try:
            batch_data = get_block_transactions(i, batch_end)
            all_data.append(batch_data)
            
            # ์ค‘๊ฐ„ ์ €์žฅ
            if len(all_data) % 10 == 0:
                temp_df = pd.concat(all_data)
                save_cache(temp_df, f'temp_batch_{i}.pkl')
                
        except Exception as e:
            print(f"โŒ ์—๋Ÿฌ ๋ฐœ์ƒ: {e}")
            continue
    
    # ์ตœ์ข… ๊ฒฐํ•ฉ
    final_df = pd.concat(all_data, ignore_index=True)
    return final_df

3๏ธโƒฃ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋ฉด ํ›จ์”ฌ ๋นจ๋ผ์ ธ! asyncio๋ฅผ ํ™œ์šฉํ•ด๋ณด์ž.

import asyncio
from web3 import AsyncWeb3

async def get_block_async(block_number):
    """๋น„๋™๊ธฐ๋กœ ๋ธ”๋ก ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ"""
    w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(infura_url))
    block = await w3.eth.get_block(block_number)
    return block

async def get_multiple_blocks(block_numbers):
    """์—ฌ๋Ÿฌ ๋ธ”๋ก์„ ๋™์‹œ์— ๊ฐ€์ ธ์˜ค๊ธฐ"""
    tasks = [get_block_async(num) for num in block_numbers]
    blocks = await asyncio.gather(*tasks)
    return blocks

# ์‚ฌ์šฉ ์˜ˆ์‹œ
block_range = range(latest - 100, latest)
blocks = asyncio.run(get_multiple_blocks(block_range))
print(f"โœ… {len(blocks)}๊ฐœ ๋ธ”๋ก์„ ๋™์‹œ์— ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค!")

โš ๏ธ API ์ œํ•œ ์ฃผ์˜

โ€ข Infura ๋ฌด๋ฃŒ ํ”Œ๋žœ: ํ•˜๋ฃจ 10๋งŒ ์š”์ฒญ
โ€ข Alchemy ๋ฌด๋ฃŒ ํ”Œ๋žœ: ์›” 3์–ต ์ปดํ“จํŒ… ์œ ๋‹›
โ€ข ๋„ˆ๋ฌด ๋น ๋ฅด๊ฒŒ ์š”์ฒญํ•˜๋ฉด ์ผ์‹œ์ ์œผ๋กœ ์ฐจ๋‹จ๋  ์ˆ˜ ์žˆ์–ด
โ€ข ์š”์ฒญ ์‚ฌ์ด์— ์ ์ ˆํ•œ ๋”œ๋ ˆ์ด๋ฅผ ๋„ฃ๋Š” ๊ฒŒ ์ข‹์•„ (time.sleep(0.1) ๊ฐ™์€ ๊ฑฐ)
โ€ข ํ”„๋กœ๋•์…˜์—์„œ๋Š” ์œ ๋ฃŒ ํ”Œ๋žœ์„ ๊ณ ๋ คํ•ด๋ด!

๐ŸŽจ ๊ณ ๊ธ‰ ์‹œ๊ฐํ™” ๊ธฐ๋ฒ•

์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ์‹œ๊ฐํ™” ๊ธฐ๋ฒ•์„ ์•Œ์•„๋ณด์ž! ๋ฐ์ดํ„ฐ๋ฅผ ๋” ํšจ๊ณผ์ ์œผ๋กœ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค์ด์•ผ. ๐Ÿ“Š

๐Ÿ“ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฐจํŠธ

import plotly.express as px

def create_animated_chart(df):
    """
    ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ™”ํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฐจํŠธ
    """
    # ์ผ๋ณ„ ๋ˆ„์  ๊ฑฐ๋ž˜๋Ÿ‰ ๊ณ„์‚ฐ
    df['date'] = df['timestamp'].dt.date
    df_sorted = df.sort_values('timestamp')
    df_sorted['cumulative_txs'] = range(1, len(df_sorted) + 1)
    
    # ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฐจํŠธ ์ƒ์„ฑ
    fig = px.scatter(
        df_sorted,
        x='timestamp',
        y='value_eth',
        animation_frame='date',
        size='gas_price',
        color='value_eth',
        hover_name='hash',
        size_max=30,
        range_y=[0, df_sorted['value_eth'].max() * 1.1],
        title='๊ฑฐ๋ž˜ ๊ธˆ์•ก ๋ณ€ํ™” ์• ๋‹ˆ๋ฉ”์ด์…˜'
    )
    
    fig.update_layout(
        height=600,
        showlegend=False
    )
    
    fig.write_html('animated_transactions.html')
    return fig

animated_fig = create_animated_chart(df_transactions)

๐Ÿ“ 3D ์‹œ๊ฐํ™”

def create_3d_visualization(df):
    """
    3D ์‚ฐ์ ๋„๋กœ ๊ฑฐ๋ž˜ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”
    """
    # ์‹œ๊ฐ„, ๊ธˆ์•ก, ๊ฐ€์Šค ๊ฐ€๊ฒฉ์„ 3์ฐจ์›์œผ๋กœ ํ‘œํ˜„
    fig = go.Figure(data=[go.Scatter3d(
        x=df['timestamp'],
        y=df['value_eth'],
        z=df['gas_price'],
        mode='markers',
        marker=dict(
            size=5,
            color=df['value_eth'],
            colorscale='Viridis',
            showscale=True,
            colorbar=dict(title="๊ฑฐ๋ž˜ ๊ธˆ์•ก (ETH)")
        ),
        text=df['hash'],
        hovertemplate='<b>์‹œ๊ฐ„</b>: %{x}<br>' +
                      '<b>๊ธˆ์•ก</b>: %{y:.4f} ETH<br>' +
                      '<b>๊ฐ€์Šค</b>: %{z:.2f} Gwei<br>'
    )])
    
    fig.update_layout(
        title='3D ๊ฑฐ๋ž˜ ๋ถ„์„',
        scene=dict(
            xaxis_title='์‹œ๊ฐ„',
            yaxis_title='๊ฑฐ๋ž˜ ๊ธˆ์•ก (ETH)',
            zaxis_title='๊ฐ€์Šค ๊ฐ€๊ฒฉ (Gwei)'
        ),
        height=700
    )
    
    fig.write_html('3d_transactions.html')
    return fig

fig_3d = create_3d_visualization(df_transactions)
์™„์„ฑ๋œ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์กฐ DApp ๊ฑฐ๋ž˜ ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ ์‹ค์‹œ๊ฐ„ ํ†ต๊ณ„ ์ด ๊ฑฐ๋ž˜ ์ˆ˜: 15,234 ํ™œ์„ฑ ์‚ฌ์šฉ์ž: 3,456 ์ด ๊ฑฐ๋ž˜๋Ÿ‰: 1,234 ETH ํ‰๊ท  ๊ฐ€์Šค: 45 Gwei ๐Ÿ“Š ์‹œ๊ฐ„๋Œ€๋ณ„ ๋ถ„์„ ๋„คํŠธ์›Œํฌ ์ƒํƒœ ์ •์ƒ: 45% ํ˜ผ์žก: 30% ๋А๋ฆผ: 25% ์ƒ์œ„ ๊ฑฐ๋ž˜์ž ์ˆœ์œ„ 1. 0x7a3f... - 234 ๊ฑฐ๋ž˜ 2. 0x9b2c... - 189 ๊ฑฐ๋ž˜ 3. 0x4d8e... - 156 ๊ฑฐ๋ž˜ 4. 0x1f6a... - 142 ๊ฑฐ๋ž˜ 5. 0x8c3b... - 128 ๊ฑฐ๋ž˜ ์•Œ๋ฆผ ๋ฐ ๊ฒฝ๊ณ  โš ๏ธ ๋น„์ •์ƒ์ ์ธ ๋Œ€๋Ÿ‰ ๊ฑฐ๋ž˜ ๊ฐ์ง€ (0x7a3f...) โšก ๊ฐ€์Šค ๊ฐ€๊ฒฉ ๊ธ‰๋“ฑ (ํ˜„์žฌ 120 Gwei) โ„น๏ธ ์‹ ๊ทœ ์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ ๋ฐฐํฌ ํ™•์ธ๋จ ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ: 2024-01-15 14:32:45 | ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ: 30์ดˆ

๐Ÿ”’ ๋ณด์•ˆ ๋ฐ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” ๋ณด์•ˆ์ด ์ •๋ง ์ค‘์š”ํ•ด! ํŠนํžˆ API ํ‚ค๋‚˜ ๊ฐœ์ธ ํ‚ค๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” ๋”์šฑ ์กฐ์‹ฌํ•ด์•ผ ํ•ด. ๐Ÿ›ก๏ธ

โš ๏ธ ์ ˆ๋Œ€ ํ•˜์ง€ ๋ง์•„์•ผ ํ•  ๊ฒƒ๋“ค

โŒ API ํ‚ค๋ฅผ ์ฝ”๋“œ์— ์ง์ ‘ ํ•˜๋“œ์ฝ”๋”ฉ
โŒ GitHub์— .env ํŒŒ์ผ ์—…๋กœ๋“œ
โŒ ๊ฐœ์ธ ํ‚ค(Private Key)๋ฅผ ํ‰๋ฌธ์œผ๋กœ ์ €์žฅ
โŒ ๊ฒ€์ฆ๋˜์ง€ ์•Š์€ ์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ์™€ ์ƒํ˜ธ์ž‘์šฉ
โŒ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๊ฒ€์ฆ ์—†์ด ์‚ฌ์šฉ

โœ… ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ: python-dotenv๋กœ ๋ฏผ๊ฐํ•œ ์ •๋ณด ๊ด€๋ฆฌ
2. .gitignore ์„ค์ •: .env, cache/, *.pkl ํŒŒ์ผ ์ œ์™ธ
3. ์ž…๋ ฅ ๊ฒ€์ฆ: ์ฃผ์†Œ ํ˜•์‹, ๋ธ”๋ก ๋ฒˆํ˜ธ ๋ฒ”์œ„ ๋“ฑ ํ™•์ธ
4. ์—๋Ÿฌ ์ฒ˜๋ฆฌ: try-except๋กœ ์˜ˆ์™ธ ์ƒํ™ฉ ๋Œ€๋น„
5. ๋กœ๊น…: ์ค‘์š”ํ•œ ์ž‘์—…์€ ๋กœ๊ทธ๋กœ ๊ธฐ๋ก
6. Rate Limiting: API ํ˜ธ์ถœ ๋นˆ๋„ ์ œํ•œ
7. ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ: ๋ธ”๋ก์ฒด์ธ์—์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋„ ๊ฒ€์ฆ

๐Ÿ“ ์•ˆ์ „ํ•œ ์„ค์ • ๊ด€๋ฆฌ

import os
from dotenv import load_dotenv
import logging

# ๋กœ๊น… ์„ค์ •
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('dapp_analytics.log'),
        logging.StreamHandler()
    ]
)

class SecureConfig:
    """์•ˆ์ „ํ•œ ์„ค์ • ๊ด€๋ฆฌ ํด๋ž˜์Šค"""
    
    def __init__(self):
        load_dotenv()
        self._validate_config()
    
    def _validate_config(self):
        """ํ•„์ˆ˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํ™•์ธ"""
        required_vars = ['INFURA_PROJECT_ID']
        
        for var in required_vars:
            if not os.getenv(var):
                raise ValueError(f"โŒ ํ•„์ˆ˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: {var}")
        
        logging.info("โœ… ์„ค์ • ๊ฒ€์ฆ ์™„๋ฃŒ")
    
    @property
    def infura_url(self):
        """Infura URL ๋ฐ˜ํ™˜"""
        project_id = os.getenv('INFURA_PROJECT_ID')
        return f"https://mainnet.infura.io/v3/{project_id}"
    
    @property
    def cache_dir(self):
        """์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ"""
        return os.getenv('CACHE_DIR', 'cache')

# ์‚ฌ์šฉ ์˜ˆ์‹œ
config = SecureConfig()
web3 = Web3(Web3.HTTPProvider(config.infura_url))

๐ŸŽ“ ์‹ค์ „ ํ™œ์šฉ ์‚ฌ๋ก€

์ด์ œ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋ฅผ ๋ณด์—ฌ์ค„๊ฒŒ! ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์ด๋Ÿฐ ๊ธฐ์ˆ ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ’ผ

๐Ÿฆ DeFi ํ”„๋กœํ† ์ฝœ ๋ชจ๋‹ˆํ„ฐ๋ง

Compound, Aave ๊ฐ™์€ ๋Œ€์ถœ ํ”„๋กœํ† ์ฝœ์˜ ํ™œ๋™์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์–ด. ํฐ ์˜ˆ์น˜๋‚˜ ์ธ์ถœ์ด ๋ฐœ์ƒํ•˜๋ฉด ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ง€!

def monitor_defi_protocol(contract_address, threshold_eth=100):
    """
    DeFi ํ”„๋กœํ† ์ฝœ์˜ ๋Œ€๊ทœ๋ชจ ๊ฑฐ๋ž˜ ๋ชจ๋‹ˆํ„ฐ๋ง
    """
    latest_block = web3.eth.block_number
    
    while True:
        try:
            # ์ตœ์‹  ๋ธ”๋ก ํ™•์ธ
            new_block = web3.eth.block_number
            
            if new_block > latest_block:
                # ์ƒˆ ๋ธ”๋ก ์ฒ˜๋ฆฌ
                block = web3.eth.get_block(new_block, full_transactions=True)
                
                for tx in block.transactions:
                    if tx.to == contract_address:
                        value_eth = web3.from_wei(tx.value, 'ether')
                        
                        if value_eth >= threshold_eth:
                            # ๋Œ€๊ทœ๋ชจ ๊ฑฐ๋ž˜ ๊ฐ์ง€!
                            logging.warning(
                                f"๐Ÿšจ ๋Œ€๊ทœ๋ชจ ๊ฑฐ๋ž˜ ๊ฐ์ง€!\n"
                                f"๊ธˆ์•ก: {value_eth} ETH\n"
                                f"From: {tx['from']}\n"
                                f"Tx: {tx.hash.hex()}"
                            )
                            # ์—ฌ๊ธฐ์„œ ํ…”๋ ˆ๊ทธ๋žจ, ์ด๋ฉ”์ผ ๋“ฑ์œผ๋กœ ์•Œ๋ฆผ ์ „์†ก
                
                latest_block = new_block
            
            time.sleep(12)  # ์ด๋”๋ฆฌ์›€ ๋ธ”๋ก ์‹œ๊ฐ„
            
        except Exception as e:
            logging.error(f"๋ชจ๋‹ˆํ„ฐ๋ง ์—๋Ÿฌ: {e}")
            time.sleep(60)

๐Ÿ“Š NFT ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค ๋ถ„์„

OpenSea ๊ฐ™์€ NFT ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค์˜ ๊ฑฐ๋ž˜๋ฅผ ๋ถ„์„ํ•ด์„œ ํŠธ๋ Œ๋“œ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด!

def analyze_nft_sales(collection_address, days=7):
    """
    NFT ์ปฌ๋ ‰์…˜์˜ ํŒ๋งค ๋ฐ์ดํ„ฐ ๋ถ„์„
    """
    # ์ตœ๊ทผ N์ผ๊ฐ„์˜ ๋ธ”๋ก ๋ฒ”์œ„ ๊ณ„์‚ฐ
    blocks_per_day = 6500  # ์•ฝ 13์ดˆ๋‹น 1๋ธ”๋ก
    end_block = web3.eth.block_number
    start_block = end_block - (blocks_per_day * days)
    
    # Transfer ์ด๋ฒคํŠธ ์ˆ˜์ง‘
    transfer_signature = web3.keccak(text="Transfer(address,address,uint256)").hex()
    
    logs = web3.eth.get_logs({
        'fromBlock': start_block,
        'toBlock': end_block,
        'address': collection_address,
        'topics': [transfer_signature]
    })
    
    # ๋ถ„์„
    sales_data = []
    for log in logs:
        # ๊ฑฐ๋ž˜ ์ •๋ณด ํŒŒ์‹ฑ
        tx = web3.eth.get_transaction(log['transactionHash'])
        
        sales_data.append({
            'timestamp': web3.eth.get_block(log['blockNumber']).timestamp,
            'token_id': int(log['topics'][3].hex(), 16),
            'from': '0x' + log['topics'][1].hex()[-40:],
            'to': '0x' + log['topics'][2].hex()[-40:],
            'tx_hash': log['transactionHash'].hex()
        })
    
    df = pd.DataFrame(sales_data)
    
    # ํ†ต๊ณ„ ๊ณ„์‚ฐ
    print(f"๐Ÿ“Š {days}์ผ๊ฐ„ NFT ๊ฑฐ๋ž˜ ๋ถ„์„")
    print(f"์ด ๊ฑฐ๋ž˜ ์ˆ˜: {len(df)}")
    print(f"์ผํ‰๊ท  ๊ฑฐ๋ž˜: {len(df) / days:.1f}")
    print(f"๊ณ ์œ  ํŒ๋งค์ž: {df['from'].nunique()}")
    print(f"๊ณ ์œ  ๊ตฌ๋งค์ž: {df['to'].nunique()}")
    
    return df

๐ŸŽฏ ๊ฐ€์Šค ๊ฐ€๊ฒฉ ์˜ˆ์ธก

๊ณผ๊ฑฐ ๊ฐ€์Šค ๊ฐ€๊ฒฉ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•ด์„œ ์ตœ์ ์˜ ๊ฑฐ๋ž˜ ์‹œ์ ์„ ์˜ˆ์ธกํ•  ์ˆ˜ ์žˆ์–ด!

from sklearn.ensemble import RandomForestRegressor
import numpy as np

def predict_gas_price(df_historical):
    """
    ๋จธ์‹ ๋Ÿฌ๋‹์œผ๋กœ ๊ฐ€์Šค ๊ฐ€๊ฒฉ ์˜ˆ์ธก
    """
    # ํŠน์„ฑ ์—”์ง€๋‹ˆ์–ด๋ง
    df = df_historical.copy()
    df['hour'] = df['timestamp'].dt.hour
    df['day_of_week'] = df['timestamp'].dt.dayofweek
    df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)
    
    # ์ด๋™ ํ‰๊ท  ์ถ”๊ฐ€
    df['gas_ma_10'] = df['gas_price'].rolling(10).mean()
    df['gas_ma_50'] = df['gas_price'].rolling(50).mean()
    
    # ๊ฒฐ์ธก์น˜ ์ œ๊ฑฐ
    df = df.dropna()
    
    # ํŠน์„ฑ๊ณผ ํƒ€๊ฒŸ ๋ถ„๋ฆฌ
    features = ['hour', 'day_of_week', 'is_weekend', 'gas_ma_10', 'gas_ma_50']
    X = df[features]
    y = df['gas_price']
    
    # ํ•™์Šต/ํ…Œ์ŠคํŠธ ๋ถ„ํ• 
    split_idx = int(len(X) * 0.8)
    X_train, X_test = X[:split_idx], X[split_idx:]
    y_train, y_test = y[:split_idx], y[split_idx:]
    
    # ๋ชจ๋ธ ํ•™์Šต
    model = RandomForestRegressor(n_estimators=100, random_state=42)
    model.fit(X_train, y_train)
    
    # ์˜ˆ์ธก ๋ฐ ํ‰๊ฐ€
    predictions = model.predict(X_test)
    mae = np.mean(np.abs(predictions - y_test))
    
    print(f"โœ… ๋ชจ๋ธ ํ•™์Šต ์™„๋ฃŒ!")
    print(f"ํ‰๊ท  ์ ˆ๋Œ€ ์˜ค์ฐจ: {mae:.2f} Gwei")
    
    # ํ˜„์žฌ ์‹œ์  ์˜ˆ์ธก
    current_features = X.iloc[-1:].values
    predicted_gas = model.predict(current_features)[0]
    
    print(f"๐Ÿ”ฎ ์˜ˆ์ธก ๊ฐ€์Šค ๊ฐ€๊ฒฉ: {predicted_gas:.2f} Gwei")
    
    return model, predicted_gas

๐ŸŒ ์›น ๋Œ€์‹œ๋ณด๋“œ ๋ฐฐํฌํ•˜๊ธฐ

๋งŒ๋“  ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ๊ณต์œ ํ•˜๊ณ  ์‹ถ์ง€? Flask๋‚˜ Streamlit์„ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿš€

๐Ÿ“ Streamlit์œผ๋กœ ๊ฐ„๋‹จํ•œ ์›น์•ฑ ๋งŒ๋“ค๊ธฐ

import streamlit as st
import plotly.graph_objects as go

# ํŽ˜์ด์ง€ ์„ค์ •
st.set_page_config(
    page_title="DApp ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ",
    page_icon="๐Ÿ“Š",
    layout="wide"
)

# ์ œ๋ชฉ
st.title("๐Ÿš€ DApp ๊ฑฐ๋ž˜๋‚ด์—ญ ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ")
st.markdown("---")

# ์‚ฌ์ด๋“œ๋ฐ”
with st.sidebar:
    st.header("โš™๏ธ ์„ค์ •")
    
    contract_address = st.text_input(
        "์ปจํŠธ๋ž™ํŠธ ์ฃผ์†Œ",
        value="0x..."
    )
    
    block_range = st.slider(
        "๋ถ„์„ํ•  ๋ธ”๋ก ์ˆ˜",
        min_value=100,
        max_value=10000,
        value=1000,
        step=100
    )
    
    if st.button("๐Ÿ”„ ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ"):
        st.cache_data.clear()
        st.success("์บ์‹œ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")

# ๋ฐ์ดํ„ฐ ๋กœ๋“œ (์บ์‹ฑ)
@st.cache_data
def load_data(address, blocks):
    latest = web3.eth.block_number
    df = get_block_transactions(latest - blocks, latest)
    return df

# ๋ฉ”์ธ ์ปจํ…์ธ 
col1, col2, col3, col4 = st.columns(4)

with col1:
    st.metric("์ด ๊ฑฐ๋ž˜ ์ˆ˜", "15,234", "+234")

with col2:
    st.metric("ํ™œ์„ฑ ์‚ฌ์šฉ์ž", "3,456", "+12")

with col3:
    st.metric("์ด ๊ฑฐ๋ž˜๋Ÿ‰", "1,234 ETH", "+45 ETH")

with col4:
    st.metric("ํ‰๊ท  ๊ฐ€์Šค", "45 Gwei", "-5 Gwei")

st.markdown("---")

# ์ฐจํŠธ ํ‘œ์‹œ
tab1, tab2, tab3 = st.tabs(["๐Ÿ“Š ๊ฑฐ๋ž˜๋Ÿ‰", "๐Ÿ’ฐ ๊ธˆ์•ก ๋ถ„ํฌ", "โ›ฝ ๊ฐ€์Šค ๋ถ„์„"])

with tab1:
    st.subheader("์‹œ๊ฐ„๋Œ€๋ณ„ ๊ฑฐ๋ž˜๋Ÿ‰")
    # ์—ฌ๊ธฐ์— Plotly ์ฐจํŠธ ์ถ”๊ฐ€
    st.plotly_chart(create_interactive_timeline(df), use_container_width=True)

with tab2:
    st.subheader("๊ฑฐ๋ž˜ ๊ธˆ์•ก ๋ถ„ํฌ")
    # ํžˆ์Šคํ† ๊ทธ๋žจ ์ถ”๊ฐ€
    fig = px.histogram(df, x='value_eth', nbins=50)
    st.plotly_chart(fig, use_container_width=True)

with tab3:
    st.subheader("๊ฐ€์Šค ๊ฐ€๊ฒฉ ์ถ”์ด")
    # ์„  ๊ทธ๋ž˜ํ”„ ์ถ”๊ฐ€
    fig = px.line(df, x='timestamp', y='gas_price')
    st.plotly_chart(fig, use_container_width=True)

# ์‹คํ–‰: streamlit run app.py

Streamlit์€ ์ •๋ง ๊ฐ„๋‹จํ•˜๊ฒŒ ์›น์•ฑ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์„œ ํ”„๋กœํ† ํƒ€์ž… ์ œ์ž‘์— ์ตœ๊ณ ์•ผ! ์ฝ”๋“œ ๋ช‡ ์ค„๋งŒ์œผ๋กœ ๋ฉ‹์ง„ ๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ์™„์„ฑ๋ผ. ๐Ÿ˜

๐Ÿ’ก ๋ฐฐํฌ ์˜ต์…˜

โ€ข Streamlit Cloud: ๋ฌด๋ฃŒ๋กœ Streamlit ์•ฑ ํ˜ธ์ŠคํŒ… (GitHub ์—ฐ๋™)
โ€ข Heroku: ๊ฐ„๋‹จํ•œ ๋ฐฐํฌ, ๋ฌด๋ฃŒ ํ‹ฐ์–ด ์žˆ์Œ
โ€ข AWS EC2: ๋” ๋งŽ์€ ์ œ์–ด๊ถŒ, ๋น„์šฉ ๋ฐœ์ƒ
โ€ข Vercel: Next.js์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์Œ
โ€ข Docker: ์–ด๋””์„œ๋“  ๋™์ผํ•œ ํ™˜๊ฒฝ์œผ๋กœ ์‹คํ–‰

๐ŸŽฏ ํ”„๋กœ์ ํŠธ ํ™•์žฅ ์•„์ด๋””์–ด

๊ธฐ๋ณธ์ ์ธ ์‹œ๊ฐํ™”๋ฅผ ๋งˆ์Šคํ„ฐํ–ˆ๋‹ค๋ฉด, ์ด์ œ ๋” ์žฌ๋ฏธ์žˆ๋Š” ๊ธฐ๋Šฅ๋“ค์„ ์ถ”๊ฐ€ํ•ด๋ณผ ์ฐจ๋ก€์•ผ! ๋ช‡ ๊ฐ€์ง€ ์•„์ด๋””์–ด๋ฅผ ์ œ์•ˆํ•ด์ค„๊ฒŒ. ๐Ÿ’ก

1. ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ ๐Ÿ””

ํŠน์ • ์กฐ๊ฑด์ด ๋งŒ์กฑ๋˜๋ฉด ํ…”๋ ˆ๊ทธ๋žจ์ด๋‚˜ ๋””์Šค์ฝ”๋“œ๋กœ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ๋ด‡์„ ๋งŒ๋“ค์–ด๋ด. ์˜ˆ๋ฅผ ๋“ค์–ด:
โ€ข ํŠน์ • ์ฃผ์†Œ์—์„œ ๋Œ€๋Ÿ‰ ๊ฑฐ๋ž˜ ๋ฐœ์ƒ
โ€ข ๊ฐ€์Šค ๊ฐ€๊ฒฉ์ด ํŠน์ • ์ž„๊ณ„๊ฐ’ ์ดํ•˜๋กœ ๋–จ์–ด์ง
โ€ข ์ƒˆ๋กœ์šด ์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ ๋ฐฐํฌ ๊ฐ์ง€

2. ์ง€๊ฐ‘ ํฌํŠธํด๋ฆฌ์˜ค ํŠธ๋ž˜์ปค ๐Ÿ’ผ

์—ฌ๋Ÿฌ ์ง€๊ฐ‘ ์ฃผ์†Œ๋ฅผ ์ถ”์ ํ•ด์„œ ์ „์ฒด ํฌํŠธํด๋ฆฌ์˜ค ๊ฐ€์น˜๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ์•ฑ. DeFi ํ”„๋กœํ† ์ฝœ์— ์˜ˆ์น˜๋œ ์ž์‚ฐ๊นŒ์ง€ ํฌํ•จํ•ด์„œ ๊ณ„์‚ฐํ•˜๋ฉด ๋” ์ข‹์•„!

3. ๊ฐ€์Šค ์ตœ์ ํ™” ๋„๊ตฌ โšก

๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•ด์„œ ๊ฐ€์Šค ๊ฐ€๊ฒฉ์ด ๊ฐ€์žฅ ์ €๋ ดํ•œ ์‹œ๊ฐ„๋Œ€๋ฅผ ์ถ”์ฒœํ•ด์ฃผ๋Š” ๋„๊ตฌ. ์‚ฌ์šฉ์ž๊ฐ€ ๊ฑฐ๋ž˜๋ฅผ ์˜ˆ์•ฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋ฉด ์™„๋ฒฝ!

4. ์Šค๋งˆํŠธ ๋จธ๋‹ˆ ์ถ”์ ๊ธฐ ๐Ÿง 

์„ฑ๊ณต์ ์ธ ํŠธ๋ ˆ์ด๋”๋“ค์˜ ์ง€๊ฐ‘์„ ์ถ”์ ํ•ด์„œ ๊ทธ๋“ค์˜ ๊ฑฐ๋ž˜ ํŒจํ„ด์„ ๋ถ„์„. ์–ด๋–ค ํ† ํฐ์„ ์‚ฌ๊ณ ํŒŒ๋Š”์ง€, ์–ธ์ œ ์›€์ง์ด๋Š”์ง€ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด.

5. ๋ฉ€ํ‹ฐ์ฒด์ธ ๋Œ€์‹œ๋ณด๋“œ ๐ŸŒˆ

์ด๋”๋ฆฌ์›€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ BSC, Polygon, Arbitrum ๋“ฑ ์—ฌ๋Ÿฌ ์ฒด์ธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๊ณณ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋Š” ํ†ตํ•ฉ ๋Œ€์‹œ๋ณด๋“œ!

๐Ÿ› ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๊ฐ€์ด๋“œ

๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ๊ผญ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ์ง€. ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿ”ง

โŒ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋“ค

1. "ValueError: {'code': -32000, 'message': 'execution reverted'}"
โ†’ ์Šค๋งˆํŠธ ์ปจํŠธ๋ž™ํŠธ ํ•จ์ˆ˜ ํ˜ธ์ถœ์ด ์‹คํŒจํ•œ ๊ฑฐ์•ผ. ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋‚˜ ๊ถŒํ•œ์„ ํ™•์ธํ•ด๋ด.

2. "Web3.exceptions.BlockNotFound"
โ†’ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ธ”๋ก ๋ฒˆํ˜ธ๋ฅผ ์š”์ฒญํ•œ ๊ฑฐ์•ผ. ์ตœ์‹  ๋ธ”๋ก ๋ฒˆํ˜ธ๋ฅผ ๋จผ์ € ํ™•์ธํ•˜๊ณ  ๋ฒ”์œ„ ๋‚ด์—์„œ ์š”์ฒญํ•ด.

3. "requests.exceptions.HTTPError: 429"
โ†’ API ์š”์ฒญ ์ œํ•œ์— ๊ฑธ๋ฆฐ ๊ฑฐ์•ผ. ์š”์ฒญ ์‚ฌ์ด์— ๋”œ๋ ˆ์ด๋ฅผ ๋„ฃ๊ฑฐ๋‚˜ ์œ ๋ฃŒ ํ”Œ๋žœ์„ ๊ณ ๋ คํ•ด๋ด.

4. "InvalidAddress"
โ†’ ์ฃผ์†Œ ํ˜•์‹์ด ์ž˜๋ชป๋์–ด. web3.to_checksum_address()๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ณ€ํ™˜ํ•ด.

5. "OverflowError: Python int too large"
โ†’ Wei ๋‹จ์œ„์˜ ํฐ ์ˆซ์ž๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ๋ฐœ์ƒํ•ด. web3.from_wei()๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์‚ฌ์šฉํ•ด.

๐Ÿ“ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

def safe_get_transaction(tx_hash, max_retries=3):
    """
    ์•ˆ์ „ํ•œ ํŠธ๋žœ์žญ์…˜ ์กฐํšŒ (์žฌ์‹œ๋„ ๋กœ์ง ํฌํ•จ)
    """
    for attempt in range(max_retries):
        try:
            tx = web3.eth.get_transaction(tx_hash)
            return tx
            
        except Exception as e:
            logging.warning(f"์‹œ๋„ {attempt + 1}/{max_retries} ์‹คํŒจ: {e}")
            
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)  # ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„
            else:
                logging.error(f"์ตœ์ข… ์‹คํŒจ: {tx_hash}")
                return None

# ์‚ฌ์šฉ ์˜ˆ์‹œ
tx = safe_get_transaction("0x...")
if tx:
    print("โœ… ๊ฑฐ๋ž˜ ์กฐํšŒ ์„ฑ๊ณต!")
else:
    print("โŒ ๊ฑฐ๋ž˜ ์กฐํšŒ ์‹คํŒจ")

๐Ÿ“š ์ถ”๊ฐ€ ํ•™์Šต ๋ฆฌ์†Œ์Šค

๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฆฌ์†Œ์Šค๋“ค์„ ์ถ”์ฒœํ•ด! ๐Ÿ“–

๊ณต์‹ ๋ฌธ์„œ
โ€ข Web3.py ๊ณต์‹ ๋ฌธ์„œ: web3py.readthedocs.io
โ€ข Ethereum ๊ฐœ๋ฐœ์ž ๋ฌธ์„œ: ethereum.org/developers
โ€ข Plotly ๋ฌธ์„œ: plotly.com/python

์˜จ๋ผ์ธ ๊ฐ•์˜
โ€ข Udemy: "Ethereum and Solidity: The Complete Developer's Guide"
โ€ข Coursera: "Blockchain Specialization"
โ€ข YouTube: "Dapp University" ์ฑ„๋„

์ปค๋ฎค๋‹ˆํ‹ฐ
โ€ข Ethereum Stack Exchange
โ€ข Reddit: r/ethdev
โ€ข Discord: Ethereum ๊ฐœ๋ฐœ์ž ์„œ๋ฒ„
โ€ข ์žฌ๋Šฅ๋„ท์˜ ๋ธ”๋ก์ฒด์ธ ์นดํ…Œ๊ณ ๋ฆฌ์—์„œ๋„ ๋‹ค์–‘ํ•œ ์ „๋ฌธ๊ฐ€๋“ค๊ณผ ์†Œํ†ตํ•  ์ˆ˜ ์žˆ์–ด!

๐ŸŽ“ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์™”๋‹ค๋‹ˆ ์ •๋ง ๋Œ€๋‹จํ•ด! ๐ŸŽ‰ ์šฐ๋ฆฌ๊ฐ€ ํ•จ๊ป˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณผ๊นŒ?

โœ… ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด ๊ฒƒ๋“ค

1. Web3.py๋กœ ๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ํ•˜๊ธฐ
2. Pandas๋กœ ๋ฐ์ดํ„ฐ ์ •์ œ ๋ฐ ๋ถ„์„ํ•˜๊ธฐ
3. Matplotlib, Seaborn์œผ๋กœ ์ •์  ์ฐจํŠธ ๋งŒ๋“ค๊ธฐ
4. Plotly๋กœ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ํ•˜๊ธฐ
5. ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„๋กœ ๊ฑฐ๋ž˜ ๊ด€๊ณ„ ์‹œ๊ฐํ™”ํ•˜๊ธฐ
6. ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ ๊ตฌ์ถ•ํ•˜๊ธฐ
7. ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๋ฐฐํฌํ•˜๊ธฐ
8. ๋ณด์•ˆ ๋ฐ ์ตœ์ ํ™” ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์ด์ œ ๋„ˆ๋Š” ๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ž์œ ์ž์žฌ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ๊ฐ–์ท„์–ด! ๐Ÿš€ ์ด ๊ธฐ์ˆ ์€ ๋‹จ์ˆœํžˆ ๊ฑฐ๋ž˜๋‚ด์—ญ์„ ๋ณด๋Š” ๊ฒƒ์„ ๋„˜์–ด์„œ, DeFi ํ”„๋กœํ† ์ฝœ ๋ถ„์„, NFT ํŠธ๋ Œ๋“œ ํŒŒ์•…, ์˜จ์ฒด์ธ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ํˆฌ์ž ์ „๋žต ์ˆ˜๋ฆฝ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

ํŠนํžˆ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ๋ธ”๋ก์ฒด์ธ ๊ด€๋ จ ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๊ฑฐ๋‚˜, ๋ฐ์ดํ„ฐ ๋ถ„์„ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•  ๋•Œ ์ด๋Ÿฐ ๊ธฐ์ˆ ์ด ํฐ ๋„์›€์ด ๋  ๊ฑฐ์•ผ. ๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ ๋ถ„์„์€ ์•ž์œผ๋กœ ๋”์šฑ ์ค‘์š”ํ•ด์งˆ ๋ถ„์•ผ๋‹ˆ๊นŒ, ์ง€๊ธˆ ๋ฐฐ์šด ๊ฑธ ๋ฐ”ํƒ•์œผ๋กœ ๊ณ„์† ๋ฐœ์ „์‹œ์ผœ๋‚˜๊ฐ€๋ฉด ์ข‹๊ฒ ์–ด! ๐Ÿ’ช

ํ˜น์‹œ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๊ฑฐ๋‚˜ ๋” ๊ถ๊ธˆํ•œ ๊ฒŒ ์žˆ๋‹ค๋ฉด, ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•˜๊ฑฐ๋‚˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด๋ด. ๊ทธ๋ฆฌ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ์ค‘์š”ํ•œ ๊ฑด ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด๋Š” ๊ฒƒ์ด์•ผ! ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ํ™•์žฅํ•ด๋‚˜๊ฐ€๋‹ค ๋ณด๋ฉด, ์–ด๋А์ƒˆ ๋ฉ‹์ง„ DApp ๋ถ„์„ ํ”Œ๋žซํผ์„ ๋งŒ๋“ค๊ณ  ์žˆ๋Š” ์ž์‹ ์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋  ๊ฑฐ์•ผ! ๐Ÿ˜Š

ํ™”์ดํŒ…! ๊ทธ๋ฆฌ๊ณ  ์ฆ๊ฑฐ์šด ์ฝ”๋”ฉ ๋˜๊ธธ ๋ฐ”๋ž„๊ฒŒ! ๐ŸŽจโœจ

์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰ ๋‹น์‹ ์€ ์ด์ œ ๋ธ”๋ก์ฒด์ธ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค! ๐Ÿ“Š ๐Ÿš€ ๐Ÿ’ก ๐ŸŽจ ๊ณ„์†ํ•ด์„œ ๋ฐฐ์šฐ๊ณ , ๋งŒ๋“ค๊ณ , ๊ณต์œ ํ•˜์„ธ์š”!
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.