Ver3.0 ๐จ ํ์ด์ฌ๊ณผ SQLite๋ก NFT ํ๋งค ๋ฐ์ดํฐ ๊ด๋ฆฌ ์์คํ ๊ตฌ์ถํ๊ธฐ: ๋ธ๋ก์ฒด์ธ ์๋์ ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ

๐จ ํ์ด์ฌ๊ณผ SQLite๋ก NFT ํ๋งค ๋ฐ์ดํฐ ๊ด๋ฆฌ ์์คํ ๊ตฌ์ถํ๊ธฐ: ๋ธ๋ก์ฒด์ธ ์๋์ ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ
Web3 ์๋, NFT ๊ฑฐ๋ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ์๋ ค๋๋ฆฝ๋๋ค! ๐
๐ NFT ์์ฅ๊ณผ ๋ฐ์ดํฐ ๊ด๋ฆฌ์ ์ค์์ฑ
์๋ ! ์์ฆ NFT ์์ฅ ์ ๋ง ํซํ์ง? ๐ฅ ๋์งํธ ์ํธ๋ถํฐ ๊ฒ์ ์์ดํ , ์ฌ์ง์ด ๋ถ๋์ฐ๊น์ง NFT๋ก ๊ฑฐ๋๋๋ ์๋๊ฐ ์์ด. ๊ทธ๋ฐ๋ฐ ๋ง์ด์ผ, NFT ํ๋ก์ ํธ๋ฅผ ์ด์ํ๊ฑฐ๋ ๋ง์ผํ๋ ์ด์ค๋ฅผ ๋ง๋ค ๋ ๊ฐ์ฅ ์ค์ํ ๊ฒ ๋ญ์ง ์์? ๋ฐ๋ก ํ๋งค ๋ฐ์ดํฐ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๊ฑฐ์ผ!
๋ธ๋ก์ฒด์ธ์๋ ๊ฑฐ๋ ๊ธฐ๋ก์ด ๋จ์ง๋ง, ์ค์ ๋ก ๋น์ฆ๋์ค๋ฅผ ์ด์ํ๋ ค๋ฉด ๋ ๋ง์ ์ ๋ณด๊ฐ ํ์ํด. ๋๊ฐ ์ผ๋ง์ ์๋์ง, ์ด๋ค NFT๊ฐ ์ธ๊ธฐ ์๋์ง, ์์๋ฃ๋ ์ผ๋ง๋ ๋ฐ์ํ๋์ง... ์ด๋ฐ ๋ฐ์ดํฐ๋ค์ ํจ์จ์ ์ผ๋ก ์ ์ฅํ๊ณ ๋ถ์ํ ์ ์์ด์ผ ํ๊ฑฐ๋ . ๐ฏ
๊ทธ๋์ ์ค๋์ ํ์ด์ฌ๊ณผ SQLite๋ฅผ ํ์ฉํด์ NFT ํ๋งค ๋ฐ์ดํฐ ๊ด๋ฆฌ ์์คํ ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์๋ ค์ค๊ฒ. ๋ณต์กํด ๋ณด์ด์ง๋ง ํ๋์ฉ ๋ฐ๋ผ์ค๋ฉด ์๊ฐ๋ณด๋ค ์ฌ์! ์ฌ๋ฅ๋ท์์ ๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ๋ ๋ถ๋ค๋ ์ด๋ฐ ์์คํ ๊ตฌ์ถ ๋ ธํ์ฐ๋ฅผ ๋ง์ด ์ฐพ๋๋ผ๊ณ . ๐
๐ก ์ SQLite๋ฅผ ์ ํํด์ผ ํ ๊น?
NFT ๋ฐ์ดํฐ ๊ด๋ฆฌ ์์คํ ์ ๋ง๋ค ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ํ์ด ์ ๋ง ์ค์ํด. MySQL, PostgreSQL, MongoDB ๋ฑ ์ฌ๋ฌ ์ ํ์ง๊ฐ ์์ง๋ง, ๋๋ SQLite๋ฅผ ์ถ์ฒํด. ์๋๊ณ ? ๐ค
๐ฏ SQLite์ ๊ฐ๋ ฅํ ์ฅ์ ๋ค
1. ์ค์น๊ฐ ํ์ ์์ด! ๐
SQLite๋ ์๋ฒ๋ฆฌ์ค(Serverless) ๋ฐ์ดํฐ๋ฒ ์ด์ค์ผ. ๋ณ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ๋ฅผ ์ค์นํ๊ณ ๊ด๋ฆฌํ ํ์๊ฐ ์์ด. ํ์ด์ฌ์ ๊ธฐ๋ณธ์ผ๋ก ๋ด์ฅ๋์ด ์์ด์ import๋ง ํ๋ฉด ๋ฐ๋ก ์ฌ์ฉํ ์ ์์ง. ์ด๋ณด์๋ค์๊ฒ ์ ๋ง ์นํ์ ์ด์ผ!
2. ๊ฐ๋ณ๊ณ ๋น ๋ฅด๋ค! โก
ํ์ผ ํ๋๋ก ์ ์ฒด ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๊ด๋ฆฌ๋ผ. ์์ ํ๋ก์ ํธ๋ ์ค๊ฐ ๊ท๋ชจ์ NFT ๋ง์ผํ๋ ์ด์ค์๋ ์ถฉ๋ถํ ์ฑ๋ฅ์ ์ ๊ณตํด. ์๋ฐฑ๋ง ๊ฑด์ ๊ฑฐ๋ ๋ฐ์ดํฐ๋ ๋ฌธ์ ์์ด ์ฒ๋ฆฌํ ์ ์์ด.
3. ์ด์์ฑ์ด ๋ฐ์ด๋! ๐ฆ
๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ผ ํ๋๋ง ๋ณต์ฌํ๋ฉด ๋ค๋ฅธ ํ๊ฒฝ์ผ๋ก ์ฝ๊ฒ ์ด๋ํ ์ ์์ด. ๊ฐ๋ฐ ํ๊ฒฝ์์ ํ
์คํธํ๊ณ ํ๋ก๋์
์ผ๋ก ์ฎ๊ธฐ๊ธฐ๋ ๊ฐํธํ์ง.
4. ํธ๋์ญ์
์ ์๋ฒฝํ๊ฒ ์ง์! ๐
ACID(์์์ฑ, ์ผ๊ด์ฑ, ๊ณ ๋ฆฝ์ฑ, ์ง์์ฑ) ์์ฑ์ ์๋ฒฝํ๊ฒ ์ง์ํด์ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ด ๋ณด์ฅ๋ผ. NFT ๊ฑฐ๋์ฒ๋ผ ์ค์ํ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ๋ ํ์์ ์ธ ๊ธฐ๋ฅ์ด์ผ.
๐๏ธ ์์คํ ์ํคํ ์ฒ ์ค๊ณํ๊ธฐ
๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋ฉํ๊ธฐ ์ ์ ์์คํ ๊ตฌ์กฐ๋ฅผ ๋จผ์ ์ค๊ณํด๋ณด์. ์ข์ ์ค๊ณ๋ ๋์ค์ ์ ์ง๋ณด์๋ฅผ ํจ์ฌ ์ฝ๊ฒ ๋ง๋ค์ด์ค! ๐
๐ ํ์ํ ๋ฐ์ดํฐ ํ ์ด๋ธ ๊ตฌ์กฐ
NFT ํ๋งค ๋ฐ์ดํฐ ๊ด๋ฆฌ ์์คํ ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด๋ฐ ํ ์ด๋ธ๋ค์ด ํ์ํด:
1. NFT ์ปฌ๋ ์
ํ
์ด๋ธ (collections)
NFT ํ๋ก์ ํธ์ ๊ธฐ๋ณธ ์ ๋ณด๋ฅผ ์ ์ฅํด. ์ปฌ๋ ์
์ด๋ฆ, ์ปจํธ๋ํธ ์ฃผ์, ์์ฑ์ผ, ์ด ๋ฐํ๋ ๋ฑ์ ๊ด๋ฆฌํ์ง.
2. NFT ์์ดํ
ํ
์ด๋ธ (nft_items)
๊ฐ๋ณ NFT์ ์์ธ ์ ๋ณด์ผ. ํ ํฐ ID, ๋ฉํ๋ฐ์ดํฐ URI, ํ์ฌ ์์ ์, ํฌ๊ท๋ ๋ฑ์ ์ ์ฅํด.
3. ํ๋งค ๊ฑฐ๋ ํ
์ด๋ธ (sales_transactions)
์ค์ ํ๋งค ๊ธฐ๋ก์ ์ ์ฅํ๋ ํต์ฌ ํ
์ด๋ธ์ด์ผ. ํ๋งค์, ๊ตฌ๋งค์, ๊ฐ๊ฒฉ, ๊ฑฐ๋ ์๊ฐ, ๋ธ๋ก์ฒด์ธ ํธ๋์ญ์
ํด์ ๋ฑ์ ๊ธฐ๋กํด.
4. ์ฌ์ฉ์ ํ
์ด๋ธ (users)
NFT๋ฅผ ๊ฑฐ๋ํ๋ ์ฌ์ฉ์๋ค์ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํด. ์ง๊ฐ ์ฃผ์, ๋๋ค์, ๊ฐ์
์ผ ๋ฑ์ ์ ์ฅํ์ง.
5. ๊ฐ๊ฒฉ ํ์คํ ๋ฆฌ ํ
์ด๋ธ (price_history)
NFT์ ๊ฐ๊ฒฉ ๋ณ๋์ ์ถ์ ํด. ์๊ฐ์ ๋ฐ๋ฅธ ๊ฐ๊ฒฉ ๋ณํ๋ฅผ ๋ถ์ํ ์ ์์ด.
๐ ํ ์ด๋ธ ๊ฐ ๊ด๊ณ ์ค์
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ์์ ๊ฐ์ฅ ์ค์ํ ๊ฑด ํ ์ด๋ธ ๊ฐ์ ๊ด๊ณ๋ฅผ ๋ช ํํ ํ๋ ๊ฑฐ์ผ. ์ฐ๋ฆฌ ์์คํ ์์๋ ์ด๋ฐ ๊ด๊ณ๋ค์ด ์์ด:
๐ธ ์ปฌ๋ ์
โ NFT ์์ดํ
: 1:N ๊ด๊ณ (ํ๋์ ์ปฌ๋ ์
์ ์ฌ๋ฌ NFT๊ฐ ์ํจ)
๐ธ NFT ์์ดํ
โ ํ๋งค ๊ฑฐ๋: 1:N ๊ด๊ณ (ํ๋์ NFT๊ฐ ์ฌ๋ฌ ๋ฒ ๊ฑฐ๋๋ ์ ์์)
๐ธ ์ฌ์ฉ์ โ ํ๋งค ๊ฑฐ๋: 1:N ๊ด๊ณ (ํ ์ฌ์ฉ์๊ฐ ์ฌ๋ฌ ๊ฑฐ๋๋ฅผ ํ ์ ์์)
๐ธ NFT ์์ดํ
โ ๊ฐ๊ฒฉ ํ์คํ ๋ฆฌ: 1:N ๊ด๊ณ (ํ๋์ NFT์ ์ฌ๋ฌ ๊ฐ๊ฒฉ ๊ธฐ๋ก)
๐ ๏ธ ๊ฐ๋ฐ ํ๊ฒฝ ์ธํ ํ๊ธฐ
์, ์ด์ ์ค์ ๋ก ์ฝ๋๋ฅผ ์์ฑํด๋ณผ ์๊ฐ์ด์ผ! ๋จผ์ ๊ฐ๋ฐ ํ๊ฒฝ์ ์ค๋นํด๋ณด์. ๐
Step 1: ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
ํ์ด์ฌ 3.7 ์ด์ ๋ฒ์ ์ด ์ค์น๋์ด ์๋ค๋ฉด SQLite๋ ์ด๋ฏธ ๋ด์ฅ๋์ด ์์ด. ์ถ๊ฐ๋ก ๋ช ๊ฐ์ง ์ ์ฉํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง ์ค์นํ๋ฉด ๋ผ:
pip install web3
pip install python-dotenv
pip install pandas
pip install matplotlib
๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ญํ :
โข web3: ์ด๋๋ฆฌ์ ๋ธ๋ก์ฒด์ธ๊ณผ ์ํธ์์ฉํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข python-dotenv: ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ (API ํค, ํ๋ผ์ด๋น ํค ๋ฑ)
โข pandas: ๋ฐ์ดํฐ ๋ถ์ ๋ฐ ์ฒ๋ฆฌ
โข matplotlib: ๋ฐ์ดํฐ ์๊ฐํ
Step 2: ํ๋ก์ ํธ ๊ตฌ์กฐ ๋ง๋ค๊ธฐ
๊น๋ํ ํ๋ก์ ํธ ๊ตฌ์กฐ๋ ์ ์ง๋ณด์๋ฅผ ์ฝ๊ฒ ๋ง๋ค์ด์ค. ์ด๋ ๊ฒ ํด๋๋ฅผ ๊ตฌ์ฑํด๋ณด์:
nft_sales_manager/
โ
โโโ database/
โ โโโ __init__.py
โ โโโ db_manager.py
โ โโโ models.py
โ
โโโ services/
โ โโโ __init__.py
โ โโโ nft_service.py
โ โโโ sales_service.py
โ
โโโ utils/
โ โโโ __init__.py
โ โโโ helpers.py
โ
โโโ config/
โ โโโ settings.py
โ
โโโ main.py
โโโ requirements.txt
โโโ .env
๐ป ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ ๊ตฌํํ๊ธฐ
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํด๋ณด์! ๋จผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ์ ์ํ๋ ๋ถ๋ถ๋ถํฐ ์์ํ ๊ฒ. ๐จ
๐ database/db_manager.py - ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋งค๋์ ํด๋์ค
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ๊ณผ ๊ธฐ๋ณธ ์์ ์ ๊ด๋ฆฌํ๋ ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณด์:
import sqlite3
from datetime import datetime
import logging
class DatabaseManager:
def __init__(self, db_path='nft_sales.db'):
"""
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋งค๋์ ์ด๊ธฐํ
Args:
db_path: SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ผ ๊ฒฝ๋ก
"""
self.db_path = db_path
self.connection = None
self.cursor = None
self.logger = logging.getLogger(__name__)
def connect(self):
"""๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ"""
try:
self.connection = sqlite3.connect(self.db_path)
self.connection.row_factory = sqlite3.Row # ๋์
๋๋ฆฌ ํํ๋ก ๊ฒฐ๊ณผ ๋ฐํ
self.cursor = self.connection.cursor()
self.logger.info(f"๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ฑ๊ณต: {self.db_path}")
return True
except sqlite3.Error as e:
self.logger.error(f"๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์คํจ: {e}")
return False
def disconnect(self):
"""๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ข
๋ฃ"""
if self.connection:
self.connection.close()
self.logger.info("๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ข
๋ฃ")
def create_tables(self):
"""๋ชจ๋ ํ
์ด๋ธ ์์ฑ"""
try:
# ์ปฌ๋ ์
ํ
์ด๋ธ
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS collections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
contract_address TEXT UNIQUE NOT NULL,
description TEXT,
creator_address TEXT,
total_supply INTEGER,
floor_price REAL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# NFT ์์ดํ
ํ
์ด๋ธ
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS nft_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection_id INTEGER NOT NULL,
token_id TEXT NOT NULL,
name TEXT,
description TEXT,
metadata_uri TEXT,
image_uri TEXT,
current_owner TEXT,
rarity_score REAL,
attributes TEXT, -- JSON ํํ๋ก ์ ์ฅ
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (collection_id) REFERENCES collections(id),
UNIQUE(collection_id, token_id)
)
''')
# ์ฌ์ฉ์ ํ
์ด๋ธ
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
wallet_address TEXT UNIQUE NOT NULL,
username TEXT,
email TEXT,
profile_image TEXT,
total_purchases INTEGER DEFAULT 0,
total_sales INTEGER DEFAULT 0,
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# ํ๋งค ๊ฑฐ๋ ํ
์ด๋ธ
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS sales_transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nft_item_id INTEGER NOT NULL,
seller_id INTEGER NOT NULL,
buyer_id INTEGER NOT NULL,
price REAL NOT NULL,
currency TEXT DEFAULT 'ETH',
platform_fee REAL DEFAULT 0,
royalty_fee REAL DEFAULT 0,
transaction_hash TEXT UNIQUE,
block_number INTEGER,
sale_type TEXT, -- 'direct', 'auction', 'offer'
status TEXT DEFAULT 'completed',
sold_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (nft_item_id) REFERENCES nft_items(id),
FOREIGN KEY (seller_id) REFERENCES users(id),
FOREIGN KEY (buyer_id) REFERENCES users(id)
)
''')
# ๊ฐ๊ฒฉ ํ์คํ ๋ฆฌ ํ
์ด๋ธ
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS price_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nft_item_id INTEGER NOT NULL,
price REAL NOT NULL,
currency TEXT DEFAULT 'ETH',
event_type TEXT, -- 'sale', 'listing', 'offer'
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (nft_item_id) REFERENCES nft_items(id)
)
''')
# ์ธ๋ฑ์ค ์์ฑ (๊ฒ์ ์ฑ๋ฅ ํฅ์)
self.cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_nft_collection
ON nft_items(collection_id)
''')
self.cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_sales_nft
ON sales_transactions(nft_item_id)
''')
self.cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_sales_date
ON sales_transactions(sold_at)
''')
self.connection.commit()
self.logger.info("๋ชจ๋ ํ
์ด๋ธ ์์ฑ ์๋ฃ")
return True
except sqlite3.Error as e:
self.logger.error(f"ํ
์ด๋ธ ์์ฑ ์คํจ: {e}")
return False
def execute_query(self, query, params=None):
"""์ฟผ๋ฆฌ ์คํ (SELECT)"""
try:
if params:
self.cursor.execute(query, params)
else:
self.cursor.execute(query)
return self.cursor.fetchall()
except sqlite3.Error as e:
self.logger.error(f"์ฟผ๋ฆฌ ์คํ ์คํจ: {e}")
return None
def execute_update(self, query, params=None):
"""์ฟผ๋ฆฌ ์คํ (INSERT, UPDATE, DELETE)"""
try:
if params:
self.cursor.execute(query, params)
else:
self.cursor.execute(query)
self.connection.commit()
return self.cursor.lastrowid
except sqlite3.Error as e:
self.logger.error(f"์
๋ฐ์ดํธ ์คํจ: {e}")
self.connection.rollback()
return None
์ด ํด๋์ค๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ชจ๋ ๊ธฐ๋ณธ ์์ ์ ๊ด๋ฆฌํด. ์ฐ๊ฒฐ, ํ ์ด๋ธ ์์ฑ, ์ฟผ๋ฆฌ ์คํ ๋ฑ์ ๊น๋ํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ง! ๐ฏ
๐จ NFT ์๋น์ค ๋ ์ด์ด ๊ตฌํํ๊ธฐ
๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ธฐ๋ฐ์ด ์์ฑ๋์ผ๋, ์ด์ ์ค์ ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ ์๋น์ค ๋ ์ด์ด๋ฅผ ๋ง๋ค์ด๋ณด์! ์ด ๋ถ๋ถ์ด ์ง์ง ์ฌ๋ฏธ์์ด. ๐
๐ผ๏ธ services/nft_service.py - NFT ๊ด๋ฆฌ ์๋น์ค
import json
from datetime import datetime
from typing import Optional, List, Dict
class NFTService:
def __init__(self, db_manager):
"""
NFT ์๋น์ค ์ด๊ธฐํ
Args:
db_manager: DatabaseManager ์ธ์คํด์ค
"""
self.db = db_manager
def create_collection(self, name: str, contract_address: str,
description: str = None, creator_address: str = None,
total_supply: int = None) -> Optional[int]:
"""
์๋ก์ด NFT ์ปฌ๋ ์
์์ฑ
Args:
name: ์ปฌ๋ ์
์ด๋ฆ
contract_address: ์ค๋งํธ ์ปจํธ๋ํธ ์ฃผ์
description: ์ปฌ๋ ์
์ค๋ช
creator_address: ์ ์์ ์ง๊ฐ ์ฃผ์
total_supply: ์ด ๋ฐํ๋
Returns:
์์ฑ๋ ์ปฌ๋ ์
ID ๋๋ None
"""
query = '''
INSERT INTO collections
(name, contract_address, description, creator_address, total_supply)
VALUES (?, ?, ?, ?, ?)
'''
params = (name, contract_address, description, creator_address, total_supply)
collection_id = self.db.execute_update(query, params)
if collection_id:
print(f"โ
์ปฌ๋ ์
'{name}' ์์ฑ ์๋ฃ! (ID: {collection_id})")
else:
print(f"โ ์ปฌ๋ ์
์์ฑ ์คํจ")
return collection_id
def add_nft_item(self, collection_id: int, token_id: str,
name: str = None, description: str = None,
metadata_uri: str = None, image_uri: str = None,
current_owner: str = None, rarity_score: float = None,
attributes: Dict = None) -> Optional[int]:
"""
NFT ์์ดํ
์ถ๊ฐ
Args:
collection_id: ์ปฌ๋ ์
ID
token_id: ํ ํฐ ID
name: NFT ์ด๋ฆ
description: NFT ์ค๋ช
metadata_uri: ๋ฉํ๋ฐ์ดํฐ URI
image_uri: ์ด๋ฏธ์ง URI
current_owner: ํ์ฌ ์์ ์ ์ง๊ฐ ์ฃผ์
rarity_score: ํฌ๊ท๋ ์ ์
attributes: ์์ฑ ์ ๋ณด (๋์
๋๋ฆฌ)
Returns:
์์ฑ๋ NFT ์์ดํ
ID ๋๋ None
"""
# ์์ฑ์ JSON ๋ฌธ์์ด๋ก ๋ณํ
attributes_json = json.dumps(attributes) if attributes else None
query = '''
INSERT INTO nft_items
(collection_id, token_id, name, description, metadata_uri,
image_uri, current_owner, rarity_score, attributes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
params = (collection_id, token_id, name, description, metadata_uri,
image_uri, current_owner, rarity_score, attributes_json)
nft_id = self.db.execute_update(query, params)
if nft_id:
print(f"โ
NFT '{name}' (Token ID: {token_id}) ์ถ๊ฐ ์๋ฃ!")
else:
print(f"โ NFT ์ถ๊ฐ ์คํจ")
return nft_id
def get_collection_by_address(self, contract_address: str) -> Optional[Dict]:
"""
์ปจํธ๋ํธ ์ฃผ์๋ก ์ปฌ๋ ์
์กฐํ
Args:
contract_address: ์ค๋งํธ ์ปจํธ๋ํธ ์ฃผ์
Returns:
์ปฌ๋ ์
์ ๋ณด ๋์
๋๋ฆฌ ๋๋ None
"""
query = '''
SELECT * FROM collections
WHERE contract_address = ?
'''
results = self.db.execute_query(query, (contract_address,))
if results:
return dict(results[0])
return None
def get_nft_by_token(self, collection_id: int, token_id: str) -> Optional[Dict]:
"""
ํ ํฐ ID๋ก NFT ์กฐํ
Args:
collection_id: ์ปฌ๋ ์
ID
token_id: ํ ํฐ ID
Returns:
NFT ์ ๋ณด ๋์
๋๋ฆฌ ๋๋ None
"""
query = '''
SELECT n.*, c.name as collection_name, c.contract_address
FROM nft_items n
JOIN collections c ON n.collection_id = c.id
WHERE n.collection_id = ? AND n.token_id = ?
'''
results = self.db.execute_query(query, (collection_id, token_id))
if results:
nft_data = dict(results[0])
# JSON ๋ฌธ์์ด์ ๋์
๋๋ฆฌ๋ก ๋ณํ
if nft_data.get('attributes'):
nft_data['attributes'] = json.loads(nft_data['attributes'])
return nft_data
return None
def update_nft_owner(self, nft_id: int, new_owner: str) -> bool:
"""
NFT ์์ ์ ์
๋ฐ์ดํธ
Args:
nft_id: NFT ์์ดํ
ID
new_owner: ์๋ก์ด ์์ ์ ์ง๊ฐ ์ฃผ์
Returns:
์ฑ๊ณต ์ฌ๋ถ
"""
query = '''
UPDATE nft_items
SET current_owner = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
'''
result = self.db.execute_update(query, (new_owner, nft_id))
if result is not None:
print(f"โ
NFT ์์ ์ ์
๋ฐ์ดํธ ์๋ฃ!")
return True
else:
print(f"โ NFT ์์ ์ ์
๋ฐ์ดํธ ์คํจ")
return False
def get_collection_stats(self, collection_id: int) -> Dict:
"""
์ปฌ๋ ์
ํต๊ณ ์กฐํ
Args:
collection_id: ์ปฌ๋ ์
ID
Returns:
ํต๊ณ ์ ๋ณด ๋์
๋๋ฆฌ
"""
# ์ด NFT ์
query_total = '''
SELECT COUNT(*) as total_nfts FROM nft_items
WHERE collection_id = ?
'''
total_result = self.db.execute_query(query_total, (collection_id,))
total_nfts = total_result[0]['total_nfts'] if total_result else 0
# ๊ณ ์ ์์ ์ ์
query_owners = '''
SELECT COUNT(DISTINCT current_owner) as unique_owners
FROM nft_items
WHERE collection_id = ? AND current_owner IS NOT NULL
'''
owners_result = self.db.execute_query(query_owners, (collection_id,))
unique_owners = owners_result[0]['unique_owners'] if owners_result else 0
# ํ๊ท ํฌ๊ท๋ ์ ์
query_rarity = '''
SELECT AVG(rarity_score) as avg_rarity
FROM nft_items
WHERE collection_id = ? AND rarity_score IS NOT NULL
'''
rarity_result = self.db.execute_query(query_rarity, (collection_id,))
avg_rarity = rarity_result[0]['avg_rarity'] if rarity_result else 0
return {
'total_nfts': total_nfts,
'unique_owners': unique_owners,
'avg_rarity_score': round(avg_rarity, 2) if avg_rarity else 0
}
def search_nfts(self, collection_id: int = None, owner: str = None,
min_rarity: float = None, max_rarity: float = None,
limit: int = 100) -> List[Dict]:
"""
NFT ๊ฒ์
Args:
collection_id: ์ปฌ๋ ์
ID (์ ํ)
owner: ์์ ์ ์ฃผ์ (์ ํ)
min_rarity: ์ต์ ํฌ๊ท๋ (์ ํ)
max_rarity: ์ต๋ ํฌ๊ท๋ (์ ํ)
limit: ๊ฒฐ๊ณผ ์ ํ
Returns:
NFT ๋ฆฌ์คํธ
"""
query = 'SELECT * FROM nft_items WHERE 1=1'
params = []
if collection_id:
query += ' AND collection_id = ?'
params.append(collection_id)
if owner:
query += ' AND current_owner = ?'
params.append(owner)
if min_rarity is not None:
query += ' AND rarity_score >= ?'
params.append(min_rarity)
if max_rarity is not None:
query += ' AND rarity_score <= ?'
params.append(max_rarity)
query += f' LIMIT {limit}'
results = self.db.execute_query(query, tuple(params))
nfts = []
if results:
for row in results:
nft_data = dict(row)
if nft_data.get('attributes'):
nft_data['attributes'] = json.loads(nft_data['attributes'])
nfts.append(nft_data)
return nfts
NFT ์๋น์ค ํด๋์ค๋ NFT์ ์ปฌ๋ ์ ๊ด๋ จ๋ ๋ชจ๋ ์์ ์ ์ฒ๋ฆฌํด. ์์ฑ, ์กฐํ, ์ ๋ฐ์ดํธ, ๊ฒ์๊น์ง ๋ค ๊ฐ๋ฅํ์ง! ๐
๐ฐ ํ๋งค ๊ฑฐ๋ ์๋น์ค ๊ตฌํํ๊ธฐ
์ด์ ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ, ํ๋งค ๊ฑฐ๋๋ฅผ ๊ด๋ฆฌํ๋ ์๋น์ค๋ฅผ ๋ง๋ค์ด๋ณด์! ์ด๊ฒ ๋ฐ๋ก ์ฐ๋ฆฌ ์์คํ ์ ํต์ฌ์ด์ผ. ๐
๐ธ services/sales_service.py - ํ๋งค ๊ด๋ฆฌ ์๋น์ค
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Tuple
import statistics
class SalesService:
def __init__(self, db_manager):
"""
ํ๋งค ์๋น์ค ์ด๊ธฐํ
Args:
db_manager: DatabaseManager ์ธ์คํด์ค
"""
self.db = db_manager
def create_or_get_user(self, wallet_address: str,
username: str = None) -> Optional[int]:
"""
์ฌ์ฉ์ ์์ฑ ๋๋ ์กฐํ
Args:
wallet_address: ์ง๊ฐ ์ฃผ์
username: ์ฌ์ฉ์ ์ด๋ฆ (์ ํ)
Returns:
์ฌ์ฉ์ ID
"""
# ๊ธฐ์กด ์ฌ์ฉ์ ํ์ธ
query_check = 'SELECT id FROM users WHERE wallet_address = ?'
result = self.db.execute_query(query_check, (wallet_address,))
if result:
return result[0]['id']
# ์ ์ฌ์ฉ์ ์์ฑ
query_insert = '''
INSERT INTO users (wallet_address, username)
VALUES (?, ?)
'''
user_id = self.db.execute_update(query_insert, (wallet_address, username))
if user_id:
print(f"โ
์ ์ฌ์ฉ์ ์์ฑ ์๋ฃ! (ID: {user_id})")
return user_id
def record_sale(self, nft_item_id: int, seller_address: str,
buyer_address: str, price: float, currency: str = 'ETH',
platform_fee: float = 0, royalty_fee: float = 0,
transaction_hash: str = None, block_number: int = None,
sale_type: str = 'direct') -> Optional[int]:
"""
ํ๋งค ๊ฑฐ๋ ๊ธฐ๋ก
Args:
nft_item_id: NFT ์์ดํ
ID
seller_address: ํ๋งค์ ์ง๊ฐ ์ฃผ์
buyer_address: ๊ตฌ๋งค์ ์ง๊ฐ ์ฃผ์
price: ํ๋งค ๊ฐ๊ฒฉ
currency: ํตํ (๊ธฐ๋ณธ: ETH)
platform_fee: ํ๋ซํผ ์์๋ฃ
royalty_fee: ๋ก์ดํฐ ์์๋ฃ
transaction_hash: ๋ธ๋ก์ฒด์ธ ํธ๋์ญ์
ํด์
block_number: ๋ธ๋ก ๋ฒํธ
sale_type: ํ๋งค ์ ํ (direct, auction, offer)
Returns:
๊ฑฐ๋ ID ๋๋ None
"""
# ํ๋งค์์ ๊ตฌ๋งค์ ID ๊ฐ์ ธ์ค๊ธฐ
seller_id = self.create_or_get_user(seller_address)
buyer_id = self.create_or_get_user(buyer_address)
if not seller_id or not buyer_id:
print("โ ์ฌ์ฉ์ ์ ๋ณด ์ฒ๋ฆฌ ์คํจ")
return None
# ํ๋งค ๊ฑฐ๋ ๊ธฐ๋ก
query = '''
INSERT INTO sales_transactions
(nft_item_id, seller_id, buyer_id, price, currency,
platform_fee, royalty_fee, transaction_hash, block_number, sale_type)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
params = (nft_item_id, seller_id, buyer_id, price, currency,
platform_fee, royalty_fee, transaction_hash, block_number, sale_type)
sale_id = self.db.execute_update(query, params)
if sale_id:
# NFT ์์ ์ ์
๋ฐ์ดํธ
update_query = '''
UPDATE nft_items
SET current_owner = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
'''
self.db.execute_update(update_query, (buyer_address, nft_item_id))
# ๊ฐ๊ฒฉ ํ์คํ ๋ฆฌ ๊ธฐ๋ก
history_query = '''
INSERT INTO price_history (nft_item_id, price, currency, event_type)
VALUES (?, ?, ?, 'sale')
'''
self.db.execute_update(history_query, (nft_item_id, price, currency))
# ์ฌ์ฉ์ ํต๊ณ ์
๋ฐ์ดํธ
self._update_user_stats(seller_id, buyer_id)
print(f"โ
ํ๋งค ๊ฑฐ๋ ๊ธฐ๋ก ์๋ฃ! (ID: {sale_id})")
print(f" ๊ฐ๊ฒฉ: {price} {currency}")
print(f" ํ๋ซํผ ์์๋ฃ: {platform_fee} {currency}")
print(f" ๋ก์ดํฐ: {royalty_fee} {currency}")
else:
print("โ ํ๋งค ๊ฑฐ๋ ๊ธฐ๋ก ์คํจ")
return sale_id
def _update_user_stats(self, seller_id: int, buyer_id: int):
"""์ฌ์ฉ์ ํต๊ณ ์
๋ฐ์ดํธ (๋ด๋ถ ๋ฉ์๋)"""
# ํ๋งค์ ํต๊ณ
query_seller = '''
UPDATE users
SET total_sales = total_sales + 1
WHERE id = ?
'''
self.db.execute_update(query_seller, (seller_id,))
# ๊ตฌ๋งค์ ํต๊ณ
query_buyer = '''
UPDATE users
SET total_purchases = total_purchases + 1
WHERE id = ?
'''
self.db.execute_update(query_buyer, (buyer_id,))
def get_nft_sales_history(self, nft_item_id: int) -> List[Dict]:
"""
NFT ํ๋งค ์ด๋ ฅ ์กฐํ
Args:
nft_item_id: NFT ์์ดํ
ID
Returns:
ํ๋งค ์ด๋ ฅ ๋ฆฌ์คํธ
"""
query = '''
SELECT
st.*,
seller.wallet_address as seller_address,
buyer.wallet_address as buyer_address,
n.name as nft_name,
n.token_id
FROM sales_transactions st
JOIN users seller ON st.seller_id = seller.id
JOIN users buyer ON st.buyer_id = buyer.id
JOIN nft_items n ON st.nft_item_id = n.id
WHERE st.nft_item_id = ?
ORDER BY st.sold_at DESC
'''
results = self.db.execute_query(query, (nft_item_id,))
return [dict(row) for row in results] if results else []
def get_user_sales(self, wallet_address: str,
as_seller: bool = True) -> List[Dict]:
"""
์ฌ์ฉ์์ ํ๋งค/๊ตฌ๋งค ์ด๋ ฅ ์กฐํ
Args:
wallet_address: ์ง๊ฐ ์ฃผ์
as_seller: True๋ฉด ํ๋งค ์ด๋ ฅ, False๋ฉด ๊ตฌ๋งค ์ด๋ ฅ
Returns:
๊ฑฐ๋ ์ด๋ ฅ ๋ฆฌ์คํธ
"""
role = 'seller' if as_seller else 'buyer'
query = f'''
SELECT
st.*,
n.name as nft_name,
n.token_id,
n.image_uri,
c.name as collection_name,
other_user.wallet_address as counterparty_address
FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
JOIN collections c ON n.collection_id = c.id
JOIN users u ON st.{role}_id = u.id
JOIN users other_user ON st.{'buyer' if as_seller else 'seller'}_id = other_user.id
WHERE u.wallet_address = ?
ORDER BY st.sold_at DESC
'''
results = self.db.execute_query(query, (wallet_address,))
return [dict(row) for row in results] if results else []
def get_collection_sales_stats(self, collection_id: int,
days: int = 30) -> Dict:
"""
์ปฌ๋ ์
ํ๋งค ํต๊ณ
Args:
collection_id: ์ปฌ๋ ์
ID
days: ์กฐํ ๊ธฐ๊ฐ (์ผ)
Returns:
ํต๊ณ ์ ๋ณด ๋์
๋๋ฆฌ
"""
start_date = datetime.now() - timedelta(days=days)
query = '''
SELECT
COUNT(*) as total_sales,
SUM(st.price) as total_volume,
AVG(st.price) as avg_price,
MIN(st.price) as floor_price,
MAX(st.price) as highest_sale
FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
WHERE n.collection_id = ?
AND st.sold_at >= ?
AND st.status = 'completed'
'''
result = self.db.execute_query(query, (collection_id, start_date))
if result and result[0]['total_sales']:
stats = dict(result[0])
# ๊ฐ๊ฒฉ ๋ณ๋ ๊ณ์ฐ
price_trend = self._calculate_price_trend(collection_id, days)
stats['price_trend'] = price_trend
# ๊ณ ์ ๊ฑฐ๋์ ์
unique_traders = self._get_unique_traders(collection_id, days)
stats['unique_traders'] = unique_traders
return stats
return {
'total_sales': 0,
'total_volume': 0,
'avg_price': 0,
'floor_price': 0,
'highest_sale': 0,
'price_trend': 0,
'unique_traders': 0
}
def _calculate_price_trend(self, collection_id: int, days: int) -> float:
"""๊ฐ๊ฒฉ ์ถ์ธ ๊ณ์ฐ (๋ด๋ถ ๋ฉ์๋)"""
query = '''
SELECT st.price, st.sold_at
FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
WHERE n.collection_id = ?
AND st.sold_at >= datetime('now', '-' || ? || ' days')
ORDER BY st.sold_at ASC
'''
results = self.db.execute_query(query, (collection_id, days))
if not results or len(results) < 2:
return 0
# ์ฒซ ๊ฑฐ๋์ ๋ง์ง๋ง ๊ฑฐ๋ ๊ฐ๊ฒฉ ๋น๊ต
first_price = results[0]['price']
last_price = results[-1]['price']
if first_price == 0:
return 0
trend = ((last_price - first_price) / first_price) * 100
return round(trend, 2)
def _get_unique_traders(self, collection_id: int, days: int) -> int:
"""๊ณ ์ ๊ฑฐ๋์ ์ ๊ณ์ฐ (๋ด๋ถ ๋ฉ์๋)"""
query = '''
SELECT COUNT(DISTINCT u.wallet_address) as unique_count
FROM (
SELECT seller_id as user_id FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
WHERE n.collection_id = ?
AND st.sold_at >= datetime('now', '-' || ? || ' days')
UNION
SELECT buyer_id as user_id FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
WHERE n.collection_id = ?
AND st.sold_at >= datetime('now', '-' || ? || ' days')
) traders
JOIN users u ON traders.user_id = u.id
'''
result = self.db.execute_query(query, (collection_id, days, collection_id, days))
return result[0]['unique_count'] if result else 0
def get_top_sales(self, limit: int = 10, days: int = 7) -> List[Dict]:
"""
์ต๊ณ ํ๋งค ๊ฑฐ๋ ์กฐํ
Args:
limit: ๊ฒฐ๊ณผ ๊ฐ์
days: ์กฐํ ๊ธฐ๊ฐ (์ผ)
Returns:
์ต๊ณ ํ๋งค ๋ฆฌ์คํธ
"""
start_date = datetime.now() - timedelta(days=days)
query = '''
SELECT
st.*,
n.name as nft_name,
n.token_id,
n.image_uri,
c.name as collection_name,
seller.wallet_address as seller_address,
buyer.wallet_address as buyer_address
FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
JOIN collections c ON n.collection_id = c.id
JOIN users seller ON st.seller_id = seller.id
JOIN users buyer ON st.buyer_id = buyer.id
WHERE st.sold_at >= ?
AND st.status = 'completed'
ORDER BY st.price DESC
LIMIT ?
'''
results = self.db.execute_query(query, (start_date, limit))
return [dict(row) for row in results] if results else []
def get_price_distribution(self, collection_id: int) -> Dict:
"""
๊ฐ๊ฒฉ ๋ถํฌ ๋ถ์
Args:
collection_id: ์ปฌ๋ ์
ID
Returns:
๊ฐ๊ฒฉ ๋ถํฌ ์ ๋ณด
"""
query = '''
SELECT price
FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
WHERE n.collection_id = ?
AND st.status = 'completed'
'''
results = self.db.execute_query(query, (collection_id,))
if not results:
return {}
prices = [row['price'] for row in results]
return {
'min': min(prices),
'max': max(prices),
'mean': round(statistics.mean(prices), 4),
'median': round(statistics.median(prices), 4),
'stdev': round(statistics.stdev(prices), 4) if len(prices) > 1 else 0,
'total_samples': len(prices)
}
ํ๋งค ์๋น์ค๋ ๊ฑฐ๋ ๊ธฐ๋ก๋ถํฐ ํต๊ณ ๋ถ์๊น์ง ๋ชจ๋ ๊ฑธ ์ฒ๋ฆฌํด. ํนํ ๊ฐ๊ฒฉ ์ถ์ธ๋ ๋ถํฌ ๋ถ์ ๊ฐ์ ๊ธฐ๋ฅ์ NFT ํฌ์์๋ค์๊ฒ ์ ๋ง ์ ์ฉํ ์ ๋ณด๋ฅผ ์ ๊ณตํ์ง! ๐
๐ฏ ์ค์ ํ์ฉ ์์
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ๋ก ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์ ๋ฅผ ํตํด ์์๋ณด์! ๐
๐ฑ main.py - ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์
import logging
from database.db_manager import DatabaseManager
from services.nft_service import NFTService
from services.sales_service import SalesService
# ๋ก๊น
์ค์
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
def initialize_system():
"""์์คํ
์ด๊ธฐํ"""
print("๐ NFT ํ๋งค ๋ฐ์ดํฐ ๊ด๋ฆฌ ์์คํ
์์!\n")
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋งค๋์ ์์ฑ
db = DatabaseManager('nft_sales.db')
# ์ฐ๊ฒฐ ๋ฐ ํ
์ด๋ธ ์์ฑ
if db.connect():
db.create_tables()
return db
return None
def demo_collection_creation(nft_service):
"""์ปฌ๋ ์
์์ฑ ๋ฐ๋ชจ"""
print("\n" + "="*50)
print("๐ฆ ์ปฌ๋ ์
์์ฑ ๋ฐ๋ชจ")
print("="*50 + "\n")
# CryptoPunks ์คํ์ผ ์ปฌ๋ ์
์์ฑ
collection_id = nft_service.create_collection(
name="CyberPunks",
contract_address="0x1234567890abcdef1234567890abcdef12345678",
description="10,000๊ฐ์ ๋
ํนํ ์ฌ์ด๋ฒํํฌ ์บ๋ฆญํฐ NFT",
creator_address="0xCreator123456789",
total_supply=10000
)
return collection_id
def demo_nft_creation(nft_service, collection_id):
"""NFT ์์ฑ ๋ฐ๋ชจ"""
print("\n" + "="*50)
print("๐จ NFT ์์ดํ
์์ฑ ๋ฐ๋ชจ")
print("="*50 + "\n")
# ์ฌ๋ฌ NFT ์์ฑ
nfts = [
{
"token_id": "1",
"name": "CyberPunk #1",
"description": "ํฌ๊ทํ ๋ ์ด์ ๋์ ๊ฐ์ง ์ฌ์ด๋ฒํํฌ",
"metadata_uri": "ipfs://QmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/1",
"image_uri": "ipfs://QmYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY/1.png",
"current_owner": "0xOwner111111111",
"rarity_score": 95.5,
"attributes": {
"background": "Purple",
"eyes": "Laser",
"hair": "Mohawk",
"accessories": "Cybernetic Implant"
}
},
{
"token_id": "2",
"name": "CyberPunk #2",
"description": "ํด๋์ ์ ๊ธ๋ผ์ค๋ฅผ ์ฐฉ์ฉํ ์ฌ์ด๋ฒํํฌ",
"metadata_uri": "ipfs://QmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/2",
"image_uri": "ipfs://QmYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY/2.png",
"current_owner": "0xOwner222222222",
"rarity_score": 78.3,
"attributes": {
"background": "Blue",
"eyes": "Sunglasses",
"hair": "Bald",
"accessories": "Earring"
}
},
{
"token_id": "3",
"name": "CyberPunk #3",
"description": "๋ค์จ ๋ง์คํฌ๋ฅผ ์ฐฉ์ฉํ ์ ๋น๋ก์ด ์ฌ์ด๋ฒํํฌ",
"metadata_uri": "ipfs://QmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/3",
"image_uri": "ipfs://QmYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY/3.png",
"current_owner": "0xOwner333333333",
"rarity_score": 88.7,
"attributes": {
"background": "Black",
"eyes": "Neon Mask",
"hair": "Long",
"accessories": "Chain"
}
}
]
nft_ids = []
for nft_data in nfts:
nft_id = nft_service.add_nft_item(
collection_id=collection_id,
**nft_data
)
nft_ids.append(nft_id)
return nft_ids
def demo_sales_recording(sales_service, nft_ids):
"""ํ๋งค ๊ธฐ๋ก ๋ฐ๋ชจ"""
print("\n" + "="*50)
print("๐ฐ ํ๋งค ๊ฑฐ๋ ๊ธฐ๋ก ๋ฐ๋ชจ")
print("="*50 + "\n")
# ์ฒซ ๋ฒ์งธ NFT ํ๋งค
sale1 = sales_service.record_sale(
nft_item_id=nft_ids[0],
seller_address="0xOwner111111111",
buyer_address="0xBuyer111111111",
price=5.5,
currency="ETH",
platform_fee=0.275, # 5% ํ๋ซํผ ์์๋ฃ
royalty_fee=0.55, # 10% ๋ก์ดํฐ
transaction_hash="0xTxHash1111111111111111111111111111111111111111111111111111111111",
block_number=15000000,
sale_type="direct"
)
# ๋ ๋ฒ์งธ NFT ํ๋งค
sale2 = sales_service.record_sale(
nft_item_id=nft_ids[1],
seller_address="0xOwner222222222",
buyer_address="0xBuyer222222222",
price=3.2,
currency="ETH",
platform_fee=0.16,
royalty_fee=0.32,
transaction_hash="0xTxHash2222222222222222222222222222222222222222222222222222222222",
block_number=15000100,
sale_type="auction"
)
# ์ธ ๋ฒ์งธ NFT ํ๋งค
sale3 = sales_service.record_sale(
nft_item_id=nft_ids[2],
seller_address="0xOwner333333333",
buyer_address="0xBuyer333333333",
price=7.8,
currency="ETH",
platform_fee=0.39,
royalty_fee=0.78,
transaction_hash="0xTxHash3333333333333333333333333333333333333333333333333333333333",
block_number=15000200,
sale_type="direct"
)
return [sale1, sale2, sale3]
def demo_statistics(nft_service, sales_service, collection_id, nft_ids):
"""ํต๊ณ ์กฐํ ๋ฐ๋ชจ"""
print("\n" + "="*50)
print("๐ ํต๊ณ ๋ถ์ ๋ฐ๋ชจ")
print("="*50 + "\n")
# ์ปฌ๋ ์
ํต๊ณ
print("๐ฆ ์ปฌ๋ ์
ํต๊ณ:")
collection_stats = nft_service.get_collection_stats(collection_id)
print(f" ์ด NFT ์: {collection_stats['total_nfts']}")
print(f" ๊ณ ์ ์์ ์: {collection_stats['unique_owners']}")
print(f" ํ๊ท ํฌ๊ท๋: {collection_stats['avg_rarity_score']}")
# ํ๋งค ํต๊ณ
print("\n๐ฐ ํ๋งค ํต๊ณ (์ต๊ทผ 30์ผ):")
sales_stats = sales_service.get_collection_sales_stats(collection_id, days=30)
print(f" ์ด ๊ฑฐ๋ ์: {sales_stats['total_sales']}")
print(f" ์ด ๊ฑฐ๋๋: {sales_stats['total_volume']:.2f} ETH")
print(f" ํ๊ท ๊ฐ๊ฒฉ: {sales_stats['avg_price']:.2f} ETH")
print(f" ๋ฐ๋ฅ ๊ฐ๊ฒฉ: {sales_stats['floor_price']:.2f} ETH")
print(f" ์ต๊ณ ํ๋งค๊ฐ: {sales_stats['highest_sale']:.2f} ETH")
print(f" ๊ฐ๊ฒฉ ์ถ์ธ: {sales_stats['price_trend']:+.2f}%")
print(f" ๊ณ ์ ๊ฑฐ๋์: {sales_stats['unique_traders']}")
# NFT ํ๋งค ์ด๋ ฅ
print(f"\n๐ NFT #{nft_ids[0]} ํ๋งค ์ด๋ ฅ:")
history = sales_service.get_nft_sales_history(nft_ids[0])
for sale in history:
print(f" {sale['sold_at']}: {sale['price']} {sale['currency']}")
print(f" ํ๋งค์: {sale['seller_address'][:10]}...")
print(f" ๊ตฌ๋งค์: {sale['buyer_address'][:10]}...")
print()
# ์ต๊ณ ํ๋งค ๊ฑฐ๋
print("๐ ์ต๊ณ ํ๋งค ๊ฑฐ๋ (์ต๊ทผ 7์ผ):")
top_sales = sales_service.get_top_sales(limit=5, days=7)
for i, sale in enumerate(top_sales, 1):
print(f" {i}. {sale['nft_name']}: {sale['price']} {sale['currency']}")
# ๊ฐ๊ฒฉ ๋ถํฌ
print("\n๐ ๊ฐ๊ฒฉ ๋ถํฌ ๋ถ์:")
price_dist = sales_service.get_price_distribution(collection_id)
if price_dist:
print(f" ์ต์: {price_dist['min']:.2f} ETH")
print(f" ์ต๋: {price_dist['max']:.2f} ETH")
print(f" ํ๊ท : {price_dist['mean']:.2f} ETH")
print(f" ์ค์๊ฐ: {price_dist['median']:.2f} ETH")
print(f" ํ์คํธ์ฐจ: {price_dist['stdev']:.2f} ETH")
print(f" ์ํ ์: {price_dist['total_samples']}")
def demo_search(nft_service, collection_id):
"""๊ฒ์ ๊ธฐ๋ฅ ๋ฐ๋ชจ"""
print("\n" + "="*50)
print("๐ NFT ๊ฒ์ ๋ฐ๋ชจ")
print("="*50 + "\n")
# ํฌ๊ท๋ ๊ธฐ์ค ๊ฒ์
print("๋์ ํฌ๊ท๋ NFT ๊ฒ์ (85์ ์ด์):")
rare_nfts = nft_service.search_nfts(
collection_id=collection_id,
min_rarity=85.0
)
for nft in rare_nfts:
print(f" {nft['name']}: ํฌ๊ท๋ {nft['rarity_score']}")
print(f" ์์ ์: {nft['current_owner'][:10]}...")
print()
def main():
"""๋ฉ์ธ ํจ์"""
# ์์คํ
์ด๊ธฐํ
db = initialize_system()
if not db:
print("โ ์์คํ
์ด๊ธฐํ ์คํจ")
return
# ์๋น์ค ์ธ์คํด์ค ์์ฑ
nft_service = NFTService(db)
sales_service = SalesService(db)
try:
# ๋ฐ๋ชจ ์คํ
collection_id = demo_collection_creation(nft_service)
nft_ids = demo_nft_creation(nft_service, collection_id)
sale_ids = demo_sales_recording(sales_service, nft_ids)
demo_statistics(nft_service, sales_service, collection_id, nft_ids)
demo_search(nft_service, collection_id)
print("\n" + "="*50)
print("โ
๋ชจ๋ ๋ฐ๋ชจ ์๋ฃ!")
print("="*50)
except Exception as e:
print(f"\nโ ์ค๋ฅ ๋ฐ์: {e}")
logging.error(f"์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
finally:
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ข
๋ฃ
db.disconnect()
print("\n๐ ์์คํ
์ข
๋ฃ")
if __name__ == "__main__":
main()
์ด ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ค์ ๋ก ์ฌ์ฉํ๋ ์์ ์ผ. ์ปฌ๋ ์ ์์ฑ๋ถํฐ NFT ์ถ๊ฐ, ํ๋งค ๊ธฐ๋ก, ํต๊ณ ๋ถ์๊น์ง ์ ์ฒด ์ํฌํ๋ก์ฐ๋ฅผ ๋ณด์ฌ์ฃผ์ง! ๐ฏ
๐ ๋ฐ์ดํฐ ๋ถ์ ๋ฐ ์๊ฐํ
๋ฐ์ดํฐ๋ฅผ ์์งํ๋ ๊ฒ๋ ์ค์ํ์ง๋ง, ๊ทธ๊ฑธ ๋ถ์ํ๊ณ ์๊ฐํํ๋ ๊ฒ๋ ์ ๋ง ์ค์ํด! ํฌ์์๋ค์ด๋ ํ๋ก์ ํธ ์ด์์๋ค์๊ฒ ์ ์ฉํ ์ธ์ฌ์ดํธ๋ฅผ ์ ๊ณตํ ์ ์๊ฑฐ๋ . ๐
๐ utils/analytics.py - ๋ถ์ ์ ํธ๋ฆฌํฐ
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
from typing import List, Dict
import json
class NFTAnalytics:
def __init__(self, db_manager):
"""
NFT ๋ถ์ ๋๊ตฌ ์ด๊ธฐํ
Args:
db_manager: DatabaseManager ์ธ์คํด์ค
"""
self.db = db_manager
def get_sales_dataframe(self, collection_id: int = None,
days: int = 30) -> pd.DataFrame:
"""
ํ๋งค ๋ฐ์ดํฐ๋ฅผ Pandas DataFrame์ผ๋ก ๋ณํ
Args:
collection_id: ์ปฌ๋ ์
ID (์ ํ)
days: ์กฐํ ๊ธฐ๊ฐ
Returns:
DataFrame
"""
start_date = datetime.now() - timedelta(days=days)
query = '''
SELECT
st.id,
st.price,
st.currency,
st.platform_fee,
st.royalty_fee,
st.sale_type,
st.sold_at,
n.name as nft_name,
n.token_id,
n.rarity_score,
c.name as collection_name
FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
JOIN collections c ON n.collection_id = c.id
WHERE st.sold_at >= ?
'''
params = [start_date]
if collection_id:
query += ' AND n.collection_id = ?'
params.append(collection_id)
query += ' ORDER BY st.sold_at ASC'
results = self.db.execute_query(query, tuple(params))
if not results:
return pd.DataFrame()
# DataFrame ์์ฑ
df = pd.DataFrame([dict(row) for row in results])
df['sold_at'] = pd.to_datetime(df['sold_at'])
return df
def calculate_daily_volume(self, df: pd.DataFrame) -> pd.DataFrame:
"""
์ผ๋ณ ๊ฑฐ๋๋ ๊ณ์ฐ
Args:
df: ํ๋งค ๋ฐ์ดํฐ DataFrame
Returns:
์ผ๋ณ ํต๊ณ DataFrame
"""
if df.empty:
return pd.DataFrame()
df['date'] = df['sold_at'].dt.date
daily_stats = df.groupby('date').agg({
'price': ['sum', 'mean', 'count'],
'platform_fee': 'sum',
'royalty_fee': 'sum'
}).reset_index()
daily_stats.columns = ['date', 'total_volume', 'avg_price',
'sales_count', 'total_platform_fee', 'total_royalty']
return daily_stats
def analyze_rarity_price_correlation(self, collection_id: int) -> Dict:
"""
ํฌ๊ท๋์ ๊ฐ๊ฒฉ์ ์๊ด๊ด๊ณ ๋ถ์
Args:
collection_id: ์ปฌ๋ ์
ID
Returns:
๋ถ์ ๊ฒฐ๊ณผ ๋์
๋๋ฆฌ
"""
query = '''
SELECT
n.rarity_score,
AVG(st.price) as avg_price,
COUNT(*) as sales_count
FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
WHERE n.collection_id = ?
AND n.rarity_score IS NOT NULL
GROUP BY n.rarity_score
ORDER BY n.rarity_score
'''
results = self.db.execute_query(query, (collection_id,))
if not results:
return {}
df = pd.DataFrame([dict(row) for row in results])
# ์๊ด๊ณ์ ๊ณ์ฐ
correlation = df['rarity_score'].corr(df['avg_price'])
return {
'correlation': round(correlation, 4),
'data': df.to_dict('records'),
'interpretation': self._interpret_correlation(correlation)
}
def _interpret_correlation(self, corr: float) -> str:
"""์๊ด๊ณ์ ํด์"""
if corr > 0.7:
return "๊ฐํ ์์ ์๊ด๊ด๊ณ - ํฌ๊ท๋๊ฐ ๋์์๋ก ๊ฐ๊ฒฉ์ด ๋์"
elif corr > 0.3:
return "์ค๊ฐ ์์ ์๊ด๊ด๊ณ - ํฌ๊ท๋๊ฐ ๊ฐ๊ฒฉ์ ์ํฅ์ ์ค"
elif corr > -0.3:
return "์ฝํ ์๊ด๊ด๊ณ - ํฌ๊ท๋์ ๊ฐ๊ฒฉ์ ๊ด๊ณ๊ฐ ๋ถ๋ช
ํ"
elif corr > -0.7:
return "์ค๊ฐ ์์ ์๊ด๊ด๊ณ - ํฌ๊ท๋๊ฐ ๋ฎ์์๋ก ๊ฐ๊ฒฉ์ด ๋์"
else:
return "๊ฐํ ์์ ์๊ด๊ด๊ณ - ํฌ๊ท๋์ ๊ฐ๊ฒฉ์ด ๋ฐ๋๋ก ์์ง์"
def get_holder_distribution(self, collection_id: int) -> Dict:
"""
์์ ์ ๋ถํฌ ๋ถ์
Args:
collection_id: ์ปฌ๋ ์
ID
Returns:
๋ถํฌ ์ ๋ณด
"""
query = '''
SELECT
current_owner,
COUNT(*) as nft_count
FROM nft_items
WHERE collection_id = ?
AND current_owner IS NOT NULL
GROUP BY current_owner
ORDER BY nft_count DESC
'''
results = self.db.execute_query(query, (collection_id,))
if not results:
return {}
df = pd.DataFrame([dict(row) for row in results])
total_nfts = df['nft_count'].sum()
total_holders = len(df)
# ์์ ์์ ์ ๋ถ์
top_10_count = df.head(10)['nft_count'].sum()
top_10_percentage = (top_10_count / total_nfts) * 100
# ๊ณ ๋(whale) ์ ์: ์ ์ฒด์ 5% ์ด์ ๋ณด์
whale_threshold = total_nfts * 0.05
whales = df[df['nft_count'] >= whale_threshold]
return {
'total_holders': total_holders,
'total_nfts': total_nfts,
'avg_nfts_per_holder': round(total_nfts / total_holders, 2),
'top_10_holders_percentage': round(top_10_percentage, 2),
'whale_count': len(whales),
'whale_holdings': int(whales['nft_count'].sum()),
'distribution_data': df.head(20).to_dict('records')
}
def analyze_sale_types(self, collection_id: int) -> Dict:
"""
ํ๋งค ์ ํ๋ณ ๋ถ์
Args:
collection_id: ์ปฌ๋ ์
ID
Returns:
ํ๋งค ์ ํ ํต๊ณ
"""
query = '''
SELECT
st.sale_type,
COUNT(*) as count,
AVG(st.price) as avg_price,
SUM(st.price) as total_volume
FROM sales_transactions st
JOIN nft_items n ON st.nft_item_id = n.id
WHERE n.collection_id = ?
GROUP BY st.sale_type
'''
results = self.db.execute_query(query, (collection_id,))
if not results:
return {}
return {
'by_type': [dict(row) for row in results]
}
def get_price_momentum(self, collection_id: int,
window_days: int = 7) -> Dict:
"""
๊ฐ๊ฒฉ ๋ชจ๋ฉํ
๋ถ์
Args:
collection_id: ์ปฌ๋ ์
ID
window_days: ์ด๋ํ๊ท ์๋์ฐ (์ผ)
Returns:
๋ชจ๋ฉํ
์ ๋ณด
"""
df = self.get_sales_dataframe(collection_id, days=30)
if df.empty:
return {}
daily_stats = self.calculate_daily_volume(df)
# ์ด๋ํ๊ท ๊ณ์ฐ
daily_stats['ma_price'] = daily_stats['avg_price'].rolling(
window=window_days, min_periods=1
).mean()
# ํ์ฌ ๊ฐ๊ฒฉ๊ณผ ์ด๋ํ๊ท ๋น๊ต
latest_price = daily_stats.iloc[-1]['avg_price']
latest_ma = daily_stats.iloc[-1]['ma_price']
momentum = ((latest_price - latest_ma) / latest_ma) * 100
return {
'current_avg_price': round(latest_price, 4),
'moving_average': round(latest_ma, 4),
'momentum_percentage': round(momentum, 2),
'signal': 'bullish' if momentum > 5 else 'bearish' if momentum < -5 else 'neutral',
'daily_data': daily_stats.to_dict('records')
}
def export_report(self, collection_id: int, filename: str = None):
"""
๋ถ์ ๋ฆฌํฌํธ ์์ฑ ๋ฐ ์ ์ฅ
Args:
collection_id: ์ปฌ๋ ์
ID
filename: ์ ์ฅํ ํ์ผ๋ช
"""
if not filename:
filename = f"nft_report_{collection_id}_{datetime.now().strftime('%Y%m%d')}.json"
report = {
'generated_at': datetime.now().isoformat(),
'collection_id': collection_id,
'rarity_analysis': self.analyze_rarity_price_correlation(collection_id),
'holder_distribution': self.get_holder_distribution(collection_id),
'sale_types': self.analyze_sale_types(collection_id),
'price_momentum': self.get_price_momentum(collection_id)
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
print(f"โ
๋ฆฌํฌํธ ์ ์ฅ ์๋ฃ: {filename}")
์ด ๋ถ์ ๋๊ตฌ๋ NFT ์ปฌ๋ ์ ์ ๋ค์ํ ์ธก๋ฉด์ ๋ถ์ํด์ค. ๊ฐ๊ฒฉ ์ถ์ธ, ์์ ์ ๋ถํฌ, ํฌ๊ท๋์ ๊ฐ๊ฒฉ์ ์๊ด๊ด๊ณ ๋ฑ์ ํ์ ํ ์ ์์ง! ๐
๐ ๋ณด์ ๋ฐ ์ต์ ํ ๊ณ ๋ ค์ฌํญ
์ค์ ํ๋ก๋์ ํ๊ฒฝ์์ ์ฌ์ฉํ๋ ค๋ฉด ๋ณด์๊ณผ ์ฑ๋ฅ ์ต์ ํ๊ฐ ์ ๋ง ์ค์ํด. ๋ช ๊ฐ์ง ํต์ฌ ํฌ์ธํธ๋ฅผ ์๋ ค์ค๊ฒ! ๐ก๏ธ
๐ ๋ณด์ ๊ฐํ ๋ฐฉ๋ฒ
1. SQL Injection ๋ฐฉ์ง ๐ซ
์ฐ๋ฆฌ๊ฐ ์์ฑํ ์ฝ๋์์๋ ์ด๋ฏธ ํ๋ผ๋ฏธํฐํ๋ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ ์์ด. ์ด๊ฒ SQL Injection์ ๋ฐฉ์งํ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ด๊ณ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ด์ผ:
# โ ๋์ ์ - SQL Injection ์ทจ์ฝ
query = f"SELECT * FROM users WHERE wallet_address = '{address}'"
# โ
์ข์ ์ - ํ๋ผ๋ฏธํฐํ๋ ์ฟผ๋ฆฌ
query = "SELECT * FROM users WHERE wallet_address = ?"
self.db.execute_query(query, (address,))
2. ๋ฏผ๊ฐํ ์ ๋ณด ์ํธํ ๐
์ง๊ฐ ์ฃผ์๋ ๊ฐ์ธ ์ ๋ณด๋ ์ํธํํด์ ์ ์ฅํ๋ ๊ฒ ์ข์:
from cryptography.fernet import Fernet
import os
class SecurityManager:
def __init__(self):
# ํ๊ฒฝ ๋ณ์์์ ์ํธํ ํค ๋ก๋
key = os.getenv('ENCRYPTION_KEY')
if not key:
key = Fernet.generate_key()
print(f"์ ์ํธํ ํค ์์ฑ: {key.decode()}")
else:
key = key.encode()
self.cipher = Fernet(key)
def encrypt_data(self, data: str) -> str:
"""๋ฐ์ดํฐ ์ํธํ"""
return self.cipher.encrypt(data.encode()).decode()
def decrypt_data(self, encrypted_data: str) -> str:
"""๋ฐ์ดํฐ ๋ณตํธํ"""
return self.cipher.decrypt(encrypted_data.encode()).decode()
3. ์ ๊ทผ ์ ์ด ๋ฐ ๊ถํ ๊ด๋ฆฌ ๐ฅ
์ฌ์ฉ์๋ณ๋ก ๋ค๋ฅธ ๊ถํ์ ๋ถ์ฌํด์ ๋ฐ์ดํฐ ์ ๊ทผ์ ์ ํํด์ผ ํด:
class AccessControl:
ROLES = {
'admin': ['read', 'write', 'delete', 'manage_users'],
'user': ['read', 'write'],
'viewer': ['read']
}
def check_permission(self, user_role: str, action: str) -> bool:
"""๊ถํ ํ์ธ"""
return action in self.ROLES.get(user_role, [])
4. ํธ๋์ญ์
ํด์ ๊ฒ์ฆ โ
๋ธ๋ก์ฒด์ธ ํธ๋์ญ์
ํด์๊ฐ ์ค์ ๋ก ์ ํจํ์ง ๊ฒ์ฆํ๋ ๊ฒ ์ค์ํด:
from web3 import Web3
class BlockchainVerifier:
def __init__(self, provider_url):
self.w3 = Web3(Web3.HTTPProvider(provider_url))
def verify_transaction(self, tx_hash: str) -> bool:
"""ํธ๋์ญ์
๊ฒ์ฆ"""
try:
tx = self.w3.eth.get_transaction(tx_hash)
receipt = self.w3.eth.get_transaction_receipt(tx_hash)
# ํธ๋์ญ์
์ด ์ฑ๊ณตํ๋์ง ํ์ธ
return receipt['status'] == 1
except Exception as e:
print(f"ํธ๋์ญ์
๊ฒ์ฆ ์คํจ: {e}")
return False
โก ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
1. ์ธ๋ฑ์ค ์ต์ ํ ๐
์์ฃผ ๊ฒ์ํ๋ ์ปฌ๋ผ์๋ ๋ฐ๋์ ์ธ๋ฑ์ค๋ฅผ ์์ฑํด์ผ ํด. ์ฐ๋ฆฌ๊ฐ ์ด๋ฏธ ๋ช ๊ฐ ๋ง๋ค์์ง๋ง, ๋ ์ถ๊ฐํ ์ ์์ด:
# ๋ณตํฉ ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_sales_collection_date
ON sales_transactions(nft_item_id, sold_at);
# ๋ถ๋ถ ์ธ๋ฑ์ค (์กฐ๊ฑด๋ถ ์ธ๋ฑ์ค)
CREATE INDEX idx_active_sales
ON sales_transactions(nft_item_id)
WHERE status = 'completed';
2. ์ฟผ๋ฆฌ ์ต์ ํ ๐ฏ
๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค์ง ์๋๋ก ์ฟผ๋ฆฌ๋ฅผ ์ต์ ํํด์ผ ํด:
# โ ๋นํจ์จ์ - ๋ชจ๋ ์ปฌ๋ผ ๊ฐ์ ธ์ค๊ธฐ
SELECT * FROM nft_items WHERE collection_id = ?
# โ
ํจ์จ์ - ํ์ํ ์ปฌ๋ผ๋ง ์ ํ
SELECT id, name, token_id, current_owner
FROM nft_items
WHERE collection_id = ?
3. ๋ฐฐ์น ์ฒ๋ฆฌ ๐ฆ
์ฌ๋ฌ ๊ฐ์ ๋ ์ฝ๋๋ฅผ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ฉด ์ฑ๋ฅ์ด ํฌ๊ฒ ํฅ์๋ผ:
def batch_insert_nfts(self, nfts_data: List[Dict]) -> List[int]:
"""NFT ๋ฐฐ์น ์ฝ์
"""
query = '''
INSERT INTO nft_items
(collection_id, token_id, name, description, current_owner)
VALUES (?, ?, ?, ?, ?)
'''
# executemany ์ฌ์ฉ
params_list = [
(nft['collection_id'], nft['token_id'], nft['name'],
nft['description'], nft['current_owner'])
for nft in nfts_data
]
try:
self.cursor.executemany(query, params_list)
self.connection.commit()
print(f"โ
{len(nfts_data)}๊ฐ NFT ๋ฐฐ์น ์ฝ์
์๋ฃ!")
return True
except sqlite3.Error as e:
print(f"โ ๋ฐฐ์น ์ฝ์
์คํจ: {e}")
self.connection.rollback()
return False
4. ์บ์ฑ ์ ๋ต ๐พ
์์ฃผ ์กฐํ๋๋ ๋ฐ์ดํฐ๋ ์บ์์ ์ ์ฅํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ๋ฅผ ์ค์ฌ:
from functools import lru_cache
from datetime import datetime, timedelta
class CachedNFTService:
def __init__(self, nft_service):
self.service = nft_service
self.cache_expiry = {}
@lru_cache(maxsize=128)
def get_collection_cached(self, contract_address: str):
"""์ปฌ๋ ์
์ ๋ณด ์บ์ฑ (5๋ถ)"""
cache_key = f"collection_{contract_address}"
# ์บ์ ๋ง๋ฃ ํ์ธ
if cache_key in self.cache_expiry:
if datetime.now() < self.cache_expiry[cache_key]:
return self.service.get_collection_by_address(contract_address)
# ์บ์ ๊ฐฑ์
result = self.service.get_collection_by_address(contract_address)
self.cache_expiry[cache_key] = datetime.now() + timedelta(minutes=5)
return result
5. ์ฐ๊ฒฐ ํ๋ง ๐
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ์ ์ฌ์ฌ์ฉํ๋ฉด ์ค๋ฒํค๋๋ฅผ ์ค์ผ ์ ์์ด:
import sqlite3
from contextlib import contextmanager
class ConnectionPool:
def __init__(self, db_path, pool_size=5):
self.db_path = db_path
self.pool = [sqlite3.connect(db_path) for _ in range(pool_size)]
self.available = self.pool.copy()
@contextmanager
def get_connection(self):
"""์ฐ๊ฒฐ ํ์์ ์ฐ๊ฒฐ ๊ฐ์ ธ์ค๊ธฐ"""
if not self.available:
# ํ์ด ๋น์ด์์ผ๋ฉด ์ ์ฐ๊ฒฐ ์์ฑ
conn = sqlite3.connect(self.db_path)
else:
conn = self.available.pop()
try:
yield conn
finally:
# ์ฐ๊ฒฐ ๋ฐํ
if len(self.available) < len(self.pool):
self.available.append(conn)
else:
conn.close()
๐ ์ค์ ๋ฐฐํฌ ๊ฐ์ด๋
๊ฐ๋ฐ์ด ๋๋ฌ์ผ๋ฉด ์ด์ ์ค์ ๋ก ๋ฐฐํฌํด์ผ๊ฒ ์ง? ํ๋ก๋์ ํ๊ฒฝ์์ ์์ ์ ์ผ๋ก ์ด์ํ๋ ๋ฐฉ๋ฒ์ ์๋ ค์ค๊ฒ! ๐
๐ฆ ๋ฐฐํฌ ์ค๋นํ๊ธฐ
1. ํ๊ฒฝ ์ค์ ํ์ผ (.env)
# .env ํ์ผ
DATABASE_PATH=./data/nft_sales.db
ENCRYPTION_KEY=your-encryption-key-here
WEB3_PROVIDER_URL=https://mainnet.infura.io/v3/YOUR-PROJECT-ID
LOG_LEVEL=INFO
MAX_CONNECTIONS=10
CACHE_TTL=300
2. ์ค์ ๊ด๋ฆฌ (config/settings.py)
import os
from dotenv import load_dotenv
load_dotenv()
class Settings:
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
DATABASE_PATH = os.getenv('DATABASE_PATH', 'nft_sales.db')
# ๋ณด์ ์ค์
ENCRYPTION_KEY = os.getenv('ENCRYPTION_KEY')
# ๋ธ๋ก์ฒด์ธ ์ค์
WEB3_PROVIDER_URL = os.getenv('WEB3_PROVIDER_URL')
# ๋ก๊น
์ค์
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
# ์ฑ๋ฅ ์ค์
MAX_CONNECTIONS = int(os.getenv('MAX_CONNECTIONS', 10))
CACHE_TTL = int(os.getenv('CACHE_TTL', 300))
@classmethod
def validate(cls):
"""์ค์ ์ ํจ์ฑ ๊ฒ์ฌ"""
required = ['ENCRYPTION_KEY', 'WEB3_PROVIDER_URL']
missing = [key for key in required if not getattr(cls, key)]
if missing:
raise ValueError(f"ํ์ ์ค์ ๋๋ฝ: {', '.join(missing)}")
settings = Settings()
settings.validate()
3. ๋ก๊น ์ค์
import logging
from logging.handlers import RotatingFileHandler
import os
def setup_logging():
"""๋ก๊น
์ค์ """
log_dir = 'logs'
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# ๋ก๊ฑฐ ์์ฑ
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# ํ์ผ ํธ๋ค๋ฌ (10MB ํฌ๊ธฐ, 5๊ฐ ๋ฐฑ์
)
file_handler = RotatingFileHandler(
f'{log_dir}/nft_system.log',
maxBytes=10*1024*1024,
backupCount=5
)
file_handler.setLevel(logging.INFO)
# ์ฝ์ ํธ๋ค๋ฌ
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# ํฌ๋งท ์ค์
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# ํธ๋ค๋ฌ ์ถ๊ฐ
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
4. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐฑ์ ์คํฌ๋ฆฝํธ
import shutil
from datetime import datetime
import os
def backup_database(db_path: str, backup_dir: str = 'backups'):
"""๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐฑ์
"""
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_path = f"{backup_dir}/nft_sales_backup_{timestamp}.db"
try:
shutil.copy2(db_path, backup_path)
print(f"โ
๋ฐฑ์
์๋ฃ: {backup_path}")
# ์ค๋๋ ๋ฐฑ์
์ญ์ (7์ผ ์ด์)
cleanup_old_backups(backup_dir, days=7)
return backup_path
except Exception as e:
print(f"โ ๋ฐฑ์
์คํจ: {e}")
return None
def cleanup_old_backups(backup_dir: str, days: int = 7):
"""์ค๋๋ ๋ฐฑ์
ํ์ผ ์ญ์ """
cutoff_time = datetime.now().timestamp() - (days * 24 * 60 * 60)
for filename in os.listdir(backup_dir):
filepath = os.path.join(backup_dir, filename)
if os.path.isfile(filepath):
if os.path.getmtime(filepath) < cutoff_time:
os.remove(filepath)
print(f"๐๏ธ ์ค๋๋ ๋ฐฑ์
์ญ์ : {filename}")
๐ณ Docker๋ก ๋ฐฐํฌํ๊ธฐ
Docker๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ค ํ๊ฒฝ์์๋ ๋์ผํ๊ฒ ์คํํ ์ ์์ด! ๐ฏ
Dockerfile
FROM python:3.9-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/data /app/logs /app/backups
# ํ๊ฒฝ ๋ณ์ ์ค์
ENV PYTHONUNBUFFERED=1
# ํฌํธ ๋
ธ์ถ (API ์๋ฒ์ฉ)
EXPOSE 8000
# ์คํ ๋ช
๋ น
CMD ["python", "main.py"]
docker-compose.yml
version: '3.8'
services:
nft-system:
build: .
container_name: nft-sales-manager
volumes:
- ./data:/app/data
- ./logs:/app/logs
- ./backups:/app/backups
environment:
- DATABASE_PATH=/app/data/nft_sales.db
- LOG_LEVEL=INFO
restart: unless-stopped
networks:
- nft-network
networks:
nft-network:
driver: bridge
์คํ ๋ฐฉ๋ฒ:
# Docker ์ด๋ฏธ์ง ๋น๋
docker-compose build
# ์ปจํ
์ด๋ ์คํ
docker-compose up -d
# ๋ก๊ทธ ํ์ธ
docker-compose logs -f
# ์ปจํ
์ด๋ ์ค์ง
docker-compose down
โ๏ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐฑ์ ์คํฌ๋ฆฝํธ ์ค์
โ๏ธ ๋ก๊น ์์คํ ๊ตฌ์ฑ
โ๏ธ ๋ณด์ ์ค์ ๊ฒํ
โ๏ธ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง ๋๊ตฌ ์ค์น
โ๏ธ ์๋ฌ ์๋ฆผ ์์คํ ๊ตฌ์ถ
๐ ์ค์๊ฐ ๋ฐ์ดํฐ ๋๊ธฐํ
๋ธ๋ก์ฒด์ธ์ ์ค์๊ฐ ๊ฑฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋๊ธฐํํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์! ์ด๊ฒ ์ง์ง ์ค์ ์์ ์ค์ํ ๋ถ๋ถ์ด์ผ. โก
๐ ๋ธ๋ก์ฒด์ธ ์ด๋ฒคํธ ๋ฆฌ์ค๋
from web3 import Web3
from web3.middleware import geth_poa_middleware
import json
import time
from typing import Callable
class NFTEventListener:
def __init__(self, provider_url: str, contract_address: str,
contract_abi: list):
"""
NFT ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ด๊ธฐํ
Args:
provider_url: Web3 ํ๋ก๋ฐ์ด๋ URL
contract_address: NFT ์ปจํธ๋ํธ ์ฃผ์
contract_abi: ์ปจํธ๋ํธ ABI
"""
self.w3 = Web3(Web3.HTTPProvider(provider_url))
# PoA ์ฒด์ธ ์ง์ (Polygon ๋ฑ)
self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
self.contract = self.w3.eth.contract(
address=Web3.toChecksumAddress(contract_address),
abi=contract_abi
)
self.is_listening = False
def listen_transfer_events(self, callback: Callable,
from_block: int = 'latest'):
"""
Transfer ์ด๋ฒคํธ ๋ฆฌ์ค๋
Args:
callback: ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ฝ๋ฐฑ ํจ์
from_block: ์์ ๋ธ๋ก ๋ฒํธ
"""
print(f"๐ง Transfer ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์์... (๋ธ๋ก: {from_block})")
# ์ด๋ฒคํธ ํํฐ ์์ฑ
event_filter = self.contract.events.Transfer.create_filter(
fromBlock=from_block
)
self.is_listening = True
while self.is_listening:
try:
# ์ ์ด๋ฒคํธ ํ์ธ
for event in event_filter.get_new_entries():
self._process_transfer_event(event, callback)
# 1์ด ๋๊ธฐ
time.sleep(1)
except Exception as e:
print(f"โ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค๋ฅ: {e}")
time.sleep(5) # ์ค๋ฅ ๋ฐ์ ์ 5์ด ๋๊ธฐ
def _process_transfer_event(self, event, callback):
"""Transfer ์ด๋ฒคํธ ์ฒ๋ฆฌ"""
try:
# ์ด๋ฒคํธ ๋ฐ์ดํฐ ์ถ์ถ
event_data = {
'from_address': event['args']['from'],
'to_address': event['args']['to'],
'token_id': str(event['args']['tokenId']),
'transaction_hash': event['transactionHash'].hex(),
'block_number': event['blockNumber'],
'timestamp': self._get_block_timestamp(event['blockNumber'])
}
print(f"๐ฆ Transfer ์ด๋ฒคํธ ๊ฐ์ง:")
print(f" Token ID: {event_data['token_id']}")
print(f" From: {event_data['from_address'][:10]}...")
print(f" To: {event_data['to_address'][:10]}...")
# ์ฝ๋ฐฑ ํจ์ ์คํ
callback(event_data)
except Exception as e:
print(f"โ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์คํจ: {e}")
def _get_block_timestamp(self, block_number: int) -> int:
"""๋ธ๋ก ํ์์คํฌํ ์กฐํ"""
try:
block = self.w3.eth.get_block(block_number)
return block['timestamp']
except Exception as e:
print(f"โ ๋ธ๋ก ํ์์คํฌํ ์กฐํ ์คํจ: {e}")
return int(time.time())
def stop_listening(self):
"""๋ฆฌ์ค๋ ์ค์ง"""
self.is_listening = False
print("๐ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค์ง")
class SyncManager:
def __init__(self, db_manager, nft_service, sales_service):
"""
๋๊ธฐํ ๋งค๋์ ์ด๊ธฐํ
Args:
db_manager: DatabaseManager ์ธ์คํด์ค
nft_service: NFTService ์ธ์คํด์ค
sales_service: SalesService ์ธ์คํด์ค
"""
self.db = db_manager
self.nft_service = nft_service
self.sales_service = sales_service
def handle_transfer_event(self, event_data: dict):
"""
Transfer ์ด๋ฒคํธ ์ฒ๋ฆฌ ๋ฐ DB ์
๋ฐ์ดํธ
Args:
event_data: ์ด๋ฒคํธ ๋ฐ์ดํฐ
"""
try:
# NFT ์กฐํ
nft = self.nft_service.get_nft_by_token(
collection_id=1, # ์ค์ ๋ก๋ ์ปจํธ๋ํธ ์ฃผ์๋ก ์กฐํ
token_id=event_data['token_id']
)
if not nft:
print(f"โ ๏ธ NFT๋ฅผ ์ฐพ์ ์ ์์: Token ID {event_data['token_id']}")
return
# ๋ฏผํ
(0x0์์ ์ ์ก)์ด ์๋ ๊ฒฝ์ฐ ํ๋งค๋ก ๊ฐ์ฃผ
zero_address = '0x0000000000000000000000000000000000000000'
if event_data['from_address'].lower() != zero_address.lower():
# ํธ๋์ญ์
์์ธ ์ ๋ณด ์กฐํ
tx_details = self._get_transaction_details(
event_data['transaction_hash']
)
if tx_details:
# ํ๋งค ๊ธฐ๋ก
self.sales_service.record_sale(
nft_item_id=nft['id'],
seller_address=event_data['from_address'],
buyer_address=event_data['to_address'],
price=tx_details['value'],
transaction_hash=event_data['transaction_hash'],
block_number=event_data['block_number']
)
print(f"โ
ํ๋งค ๊ธฐ๋ก ์๋ฃ!")
else:
# ๋ฏผํ
์ธ ๊ฒฝ์ฐ ์์ ์๋ง ์
๋ฐ์ดํธ
self.nft_service.update_nft_owner(
nft_id=nft['id'],
new_owner=event_data['to_address']
)
print(f"โ
๋ฏผํ
๊ธฐ๋ก ์๋ฃ!")
except Exception as e:
print(f"โ Transfer ์ด๋ฒคํธ ์ฒ๋ฆฌ ์คํจ: {e}")
def _get_transaction_details(self, tx_hash: str) -> dict:
"""ํธ๋์ญ์
์์ธ ์ ๋ณด ์กฐํ"""
# ์ค์ ๊ตฌํ์์๋ Web3๋ฅผ ์ฌ์ฉํด ํธ๋์ญ์
์ ๋ณด ์กฐํ
# ์ฌ๊ธฐ์๋ ์์๋ก ๊ฐ๋จํ ์ฒ๋ฆฌ
return {
'value': 1.5, # ETH
'gas_used': 150000
}
์ด ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ ๋ธ๋ก์ฒด์ธ์ ์ค์๊ฐ ๊ฑฐ๋๋ฅผ ๊ฐ์งํด์ ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ธฐ๋กํด์ค. ์์ ์๋ํ! ๐ค
๐ฑ REST API ๊ตฌ์ถํ๊ธฐ
๋ฐ์ดํฐ๋ฒ ์ด์ค ์์คํ ์ ๋ง๋ค์์ผ๋, ์ด์ ์ธ๋ถ์์ ์ ๊ทผํ ์ ์๋ API๋ฅผ ๋ง๋ค์ด๋ณด์! Flask๋ฅผ ์ฌ์ฉํด์ ๊ฐ๋จํ๊ฒ ๊ตฌํํ ์ ์์ด. ๐
๐ Flask API ์๋ฒ
from flask import Flask, jsonify, request
from flask_cors import CORS
from database.db_manager import DatabaseManager
from services.nft_service import NFTService
from services.sales_service import SalesService
from utils.analytics import NFTAnalytics
import logging
app = Flask(__name__)
CORS(app) # CORS ํ์ฑํ
# ๋ก๊น
์ค์
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ์๋น์ค ์ด๊ธฐํ
db = DatabaseManager('nft_sales.db')
db.connect()
db.create_tables()
nft_service = NFTService(db)
sales_service = SalesService(db)
analytics = NFTAnalytics(db)
# ============= ์ปฌ๋ ์
API =============
@app.route('/api/collections', methods=['POST'])
def create_collection():
"""์ปฌ๋ ์
์์ฑ"""
try:
data = request.json
collection_id = nft_service.create_collection(
name=data['name'],
contract_address=data['contract_address'],
description=data.get('description'),
creator_address=data.get('creator_address'),
total_supply=data.get('total_supply')
)
return jsonify({
'success': True,
'collection_id': collection_id
}), 201
except Exception as e:
logger.error(f"์ปฌ๋ ์
์์ฑ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/collections/<contract_address>', methods=['GET'])
def get_collection(contract_address):
"""์ปฌ๋ ์
์กฐํ"""
try:
collection = nft_service.get_collection_by_address(contract_address)
if collection:
return jsonify({
'success': True,
'data': collection
})
else:
return jsonify({
'success': False,
'error': '์ปฌ๋ ์
์ ์ฐพ์ ์ ์์ต๋๋ค'
}), 404
except Exception as e:
logger.error(f"์ปฌ๋ ์
์กฐํ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/collections/<int:collection_id>/stats', methods=['GET'])
def get_collection_stats(collection_id):
"""์ปฌ๋ ์
ํต๊ณ"""
try:
stats = nft_service.get_collection_stats(collection_id)
return jsonify({
'success': True,
'data': stats
})
except Exception as e:
logger.error(f"ํต๊ณ ์กฐํ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
# ============= NFT API =============
@app.route('/api/nfts', methods=['POST'])
def create_nft():
"""NFT ์์ฑ"""
try:
data = request.json
nft_id = nft_service.add_nft_item(
collection_id=data['collection_id'],
token_id=data['token_id'],
name=data.get('name'),
description=data.get('description'),
metadata_uri=data.get('metadata_uri'),
image_uri=data.get('image_uri'),
current_owner=data.get('current_owner'),
rarity_score=data.get('rarity_score'),
attributes=data.get('attributes')
)
return jsonify({
'success': True,
'nft_id': nft_id
}), 201
except Exception as e:
logger.error(f"NFT ์์ฑ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/nfts/<int:collection_id>/<token_id>', methods=['GET'])
def get_nft(collection_id, token_id):
"""NFT ์กฐํ"""
try:
nft = nft_service.get_nft_by_token(collection_id, token_id)
if nft:
return jsonify({
'success': True,
'data': nft
})
else:
return jsonify({
'success': False,
'error': 'NFT๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค'
}), 404
except Exception as e:
logger.error(f"NFT ์กฐํ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/nfts/search', methods=['GET'])
def search_nfts():
"""NFT ๊ฒ์"""
try:
collection_id = request.args.get('collection_id', type=int)
owner = request.args.get('owner')
min_rarity = request.args.get('min_rarity', type=float)
max_rarity = request.args.get('max_rarity', type=float)
limit = request.args.get('limit', default=100, type=int)
nfts = nft_service.search_nfts(
collection_id=collection_id,
owner=owner,
min_rarity=min_rarity,
max_rarity=max_rarity,
limit=limit
)
return jsonify({
'success': True,
'data': nfts,
'count': len(nfts)
})
except Exception as e:
logger.error(f"NFT ๊ฒ์ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
# ============= ํ๋งค API =============
@app.route('/api/sales', methods=['POST'])
def record_sale():
"""ํ๋งค ๊ธฐ๋ก"""
try:
data = request.json
sale_id = sales_service.record_sale(
nft_item_id=data['nft_item_id'],
seller_address=data['seller_address'],
buyer_address=data['buyer_address'],
price=data['price'],
currency=data.get('currency', 'ETH'),
platform_fee=data.get('platform_fee', 0),
royalty_fee=data.get('royalty_fee', 0),
transaction_hash=data.get('transaction_hash'),
block_number=data.get('block_number'),
sale_type=data.get('sale_type', 'direct')
)
return jsonify({
'success': True,
'sale_id': sale_id
}), 201
except Exception as e:
logger.error(f"ํ๋งค ๊ธฐ๋ก ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/sales/nft/<int:nft_id>', methods=['GET'])
def get_nft_sales_history(nft_id):
"""NFT ํ๋งค ์ด๋ ฅ"""
try:
history = sales_service.get_nft_sales_history(nft_id)
return jsonify({
'success': True,
'data': history,
'count': len(history)
})
except Exception as e:
logger.error(f"ํ๋งค ์ด๋ ฅ ์กฐํ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/sales/user/<wallet_address>', methods=['GET'])
def get_user_sales(wallet_address):
"""์ฌ์ฉ์ ๊ฑฐ๋ ์ด๋ ฅ"""
try:
as_seller = request.args.get('as_seller', default='true').lower() == 'true'
sales = sales_service.get_user_sales(wallet_address, as_seller)
return jsonify({
'success': True,
'data': sales,
'count': len(sales)
})
except Exception as e:
logger.error(f"์ฌ์ฉ์ ๊ฑฐ๋ ์ด๋ ฅ ์กฐํ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/sales/top', methods=['GET'])
def get_top_sales():
"""์ต๊ณ ํ๋งค ๊ฑฐ๋"""
try:
limit = request.args.get('limit', default=10, type=int)
days = request.args.get('days', default=7, type=int)
top_sales = sales_service.get_top_sales(limit, days)
return jsonify({
'success': True,
'data': top_sales,
'count': len(top_sales)
})
except Exception as e:
logger.error(f"์ต๊ณ ํ๋งค ์กฐํ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
# ============= ๋ถ์ API =============
@app.route('/api/analytics/collection/<int:collection_id>/sales', methods=['GET'])
def get_sales_analytics(collection_id):
"""ํ๋งค ๋ถ์"""
try:
days = request.args.get('days', default=30, type=int)
stats = sales_service.get_collection_sales_stats(collection_id, days)
return jsonify({
'success': True,
'data': stats
})
except Exception as e:
logger.error(f"ํ๋งค ๋ถ์ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/analytics/collection/<int:collection_id>/holders', methods=['GET'])
def get_holder_analytics(collection_id):
"""์์ ์ ๋ถ์"""
try:
distribution = analytics.get_holder_distribution(collection_id)
return jsonify({
'success': True,
'data': distribution
})
except Exception as e:
logger.error(f"์์ ์ ๋ถ์ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
@app.route('/api/analytics/collection/<int:collection_id>/rarity', methods=['GET'])
def get_rarity_analytics(collection_id):
"""ํฌ๊ท๋ ๋ถ์"""
try:
analysis = analytics.analyze_rarity_price_correlation(collection_id)
return jsonify({
'success': True,
'data': analysis
})
except Exception as e:
logger.error(f"ํฌ๊ท๋ ๋ถ์ ์คํจ: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 400
# ============= ํฌ์ค ์ฒดํฌ =============
@app.route('/api/health', methods=['GET'])
def health_check():
"""ํฌ์ค ์ฒดํฌ"""
return jsonify({
'status': 'healthy',
'service': 'NFT Sales Manager API',
'version': '1.0.0'
})
# ============= ์๋ฌ ํธ๋ค๋ฌ =============
@app.errorhandler(404)
def not_found(error):
return jsonify({
'success': False,
'error': '์์ฒญํ ๋ฆฌ์์ค๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค'
}), 404
@app.errorhandler(500)
def internal_error(error):
logger.error(f"์๋ฒ ์ค๋ฅ: {error}")
return jsonify({
'success': False,
'error': '์๋ฒ ๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค'
}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
์ด์ ์์ ํ REST API ์๋ฒ๊ฐ ์์ฑ๋์ด! ํ๋ก ํธ์๋๋ ๋ค๋ฅธ ์๋น์ค์์ ์ฝ๊ฒ ์ฐ๋ํ ์ ์์ง. ๐
๐ก API ์ฌ์ฉ ์์
import requests
# API ๋ฒ ์ด์ค URL
BASE_URL = 'http://localhost:8000/api'
# 1. ์ปฌ๋ ์
์์ฑ
response = requests.post(f'{BASE_URL}/collections', json={
'name': 'CoolNFTs',
'contract_address': '0x1234567890abcdef',
'description': '๋ฉ์ง NFT ์ปฌ๋ ์
',
'total_supply': 10000
})
print(response.json())
# 2. NFT ๊ฒ์
response = requests.get(f'{BASE_URL}/nfts/search', params={
'collection_id': 1,
'min_rarity': 80,
'limit': 10
})
print(response.json())
# 3. ํ๋งค ํต๊ณ ์กฐํ
response = requests.get(f'{BASE_URL}/analytics/collection/1/sales', params={
'days': 30
})
print(response.json())
# 4. ์ต๊ณ ํ๋งค ์กฐํ
response = requests.get(f'{BASE_URL}/sales/top', params={
'limit': 5,
'days': 7
})
print(response.json())
๐ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ ๋ง ๊ธด ์ฌ์ ์ด์์ง? ๐ ์ฐ๋ฆฌ๋ ํ์ด์ฌ๊ณผ SQLite๋ฅผ ์ฌ์ฉํด์ ์์ ํ NFT ํ๋งค ๋ฐ์ดํฐ ๊ด๋ฆฌ ์์คํ ์ ๋ง๋ค์์ด. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ๋ถํฐ ์ค์๊ฐ ๋๊ธฐํ, REST API๊น์ง ๋ชจ๋ ๊ฑธ ๋ค๋ค์ง!
๐ ํต์ฌ ์์ฝ
์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ด ๊ฒ๋ค:
โ
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ: NFT ํ๋งค ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์ ์ฅํ๋ ํ
์ด๋ธ ๊ตฌ์กฐ
โ
SQLite ํ์ฉ: ๊ฐ๋ณ๊ณ ๊ฐ๋ ฅํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์คํ
๊ตฌ์ถ
โ
์๋น์ค ๋ ์ด์ด: ๋น์ฆ๋์ค ๋ก์ง์ ๊น๋ํ๊ฒ ๋ถ๋ฆฌํ๋ ๋ฐฉ๋ฒ
โ
๋ฐ์ดํฐ ๋ถ์: Pandas๋ฅผ ํ์ฉํ ํต๊ณ ๋ฐ ์ธ์ฌ์ดํธ ๋์ถ
โ
๋ณด์ ๋ฐ ์ต์ ํ: ํ๋ก๋์
๋ ๋ฒจ์ ์์คํ
๊ตฌ์ถ ๋
ธํ์ฐ
โ
์ค์๊ฐ ๋๊ธฐํ: ๋ธ๋ก์ฒด์ธ ์ด๋ฒคํธ๋ฅผ ์๋์ผ๋ก ๊ฐ์งํ๊ณ ๊ธฐ๋ก
โ
REST API: ์ธ๋ถ ์๋น์ค์ ์ฝ๊ฒ ์ฐ๋ํ ์ ์๋ ์ธํฐํ์ด์ค
๐ ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ๊ธฐ
์ด ์์คํ ์ ๊ธฐ๋ฐ์ผ๋ก ๋ ๋ฐ์ ์ํฌ ์ ์๋ ๋ฐฉํฅ๋ค:
1. ํ๋ก ํธ์๋ ๋์๋ณด๋ ๊ตฌ์ถ ๐
React๋ Vue.js๋ก ์๊ฐ์ ์ธ ๋์๋ณด๋๋ฅผ ๋ง๋ค์ด์ ๋ฐ์ดํฐ๋ฅผ ๋ ์ง๊ด์ ์ผ๋ก ํํํ ์ ์์ด.
2. ๋จธ์ ๋ฌ๋ ๊ฐ๊ฒฉ ์์ธก ๐ค
๊ณผ๊ฑฐ ํ๋งค ๋ฐ์ดํฐ๋ฅผ ํ์ตํด์ NFT ๊ฐ๊ฒฉ์ ์์ธกํ๋ ๋ชจ๋ธ์ ๋ง๋ค ์ ์์ด.
3. ์๋ฆผ ์์คํ
๐
ํน์ ์กฐ๊ฑด์ด ๋ง์กฑ๋๋ฉด ์ฌ์ฉ์์๊ฒ ์๋ฆผ์ ๋ณด๋ด๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ด.
4. ๋ค์ค ์ฒด์ธ ์ง์ ๐
์ด๋๋ฆฌ์๋ฟ๋ง ์๋๋ผ Polygon, Solana ๋ฑ ๋ค์ํ ๋ธ๋ก์ฒด์ธ์ ์ง์ํ๋๋ก ํ์ฅํ ์ ์์ด.
5. GraphQL API ๐ก
REST API ๋์ GraphQL์ ์ฌ์ฉํ๋ฉด ๋ ์ ์ฐํ ๋ฐ์ดํฐ ์ฟผ๋ฆฌ๊ฐ ๊ฐ๋ฅํด.
๐ ๋ง์ง๋ง ์กฐ์ธ
NFT์ ๋ธ๋ก์ฒด์ธ ๊ธฐ์ ์ ๊ณ์ ๋ฐ์ ํ๊ณ ์์ด. ์ค๋ ๋ฐฐ์ด ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก ๊ณ์ ํ์ตํ๊ณ ์คํํด๋ณด๊ธธ ๋ฐ๋ผ! ๐ช
ํนํ ์ด๋ฐ ๋ถ๋ถ๋ค์ ๋ ๊ณต๋ถํด๋ณด๋ฉด ์ข์:
โข Web3 ๊ฐ๋ฐ ์ฌํ (์ค๋งํธ ์ปจํธ๋ํธ ์ฐ๋)
โข ๋๊ท๋ชจ ๋ฐ์ดํฐ ์ฒ๋ฆฌ (PostgreSQL, Redis)
โข ํด๋ผ์ฐ๋ ๋ฐฐํฌ (AWS, GCP, Azure)
โข ๋ชจ๋ํฐ๋ง ๋ฐ ๋ก๊น
(Prometheus, Grafana)
โข ํ
์คํธ ์๋ํ (pytest, unittest)
๊ทธ๋ฆฌ๊ณ ๋ฌด์๋ณด๋ค ์ค์ํ ๊ฑด, ์ง์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด๋ ๊ฑฐ์ผ! ์ด๋ก ๋ง ์๋ ๊ฒ๋ณด๋ค ์ค์ ๋ก ๋ง๋ค์ด๋ณด๋ฉด์ ๋ถ๋ชํ๋ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๋ ๊ณผ์ ์์ ์ง์ง ์ค๋ ฅ์ด ๋์ด๋๊ฑฐ๋ . ๐
์, ์ด์ ๋น์ ๋ NFT ํ๋งค ๋ฐ์ดํฐ ๊ด๋ฆฌ ์์คํ ์ ๊ตฌ์ถํ ์ ์๋ ๋ฅ๋ ฅ์ ๊ฐ์ท์ด! ๐ ์ด ์ง์์ ๋ฐํ์ผ๋ก ๋ฉ์ง ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด๊ธธ ๋ฐ๋ผ. ํ์ดํ ! ๐ชโจ
๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋ ๊น์ด ์๋ ๋ด์ฉ์ด ํ์ํ๋ฉด ์ธ์ ๋ ์ง ๋ฌผ์ด๋ด. ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๋ฅผ ๋ง๋ค์ด๊ฐ์! ๐ค
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.