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

๐ ํ์ด์ฌ์ผ๋ก ์ง๊ฐ ๊ฐ ํ ํฐ ์ค์ ์๋ํ ๊ตฌํํ๊ธฐ
๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ์๋ฅผ ์ํ ์ค์ ๊ฐ์ด๋ ๐
๐ฏ ์ ํ ํฐ ์ค์ ์๋ํ๊ฐ ํ์ํ ๊น?
์ผ, ์์งํ ๋งํด์ ์ํธํํ ๊ฑฐ๋ํ๋ค ๋ณด๋ฉด ์ง์ง ๊ท์ฐฎ์ ์๊ฐ๋ค ๋ง์ง ์์? ๐คํนํ DeFi(ํ์ค์ํ ๊ธ์ต) ์ํ๊ณ์์ ํ๋ํ๋ค ๋ณด๋ฉด, ์ฌ๋ฌ ์ง๊ฐ ์ฌ์ด์์ ํ ํฐ์ ์ฎ๊ธฐ๊ณ , ์ค์ํ๊ณ , ๋ ๋ค์ ์ฎ๊ธฐ๊ณ ... ์ด๋ฐ ๋ฐ๋ณต ์์ ์ด ์ ๋ง ๋ง์. ์๋์ผ๋ก ํ๋ํ๋ ์ฒ๋ฆฌํ๋ค ๋ณด๋ฉด ์๊ฐ๋ ์ค๋ ๊ฑธ๋ฆฌ๊ณ , ์ค์ํ ๊ฐ๋ฅ์ฑ๋ ๋์์ง์ง.
๋ ํฐ ๋ฌธ์ ๋ ํ์ด๋ฐ์ด์ผ. ์ํธํํ ์์ฅ์ 24์๊ฐ ์ฌ์ง ์๊ณ ์์ง์ด๊ฑฐ๋ ? ์๋ฒฝ 3์์ ์ข์ ์ค์ ๊ธฐํ๊ฐ ์๋๋ฐ, ์๊ณ ์์ผ๋ฉด ๊ทธ๋ฅ ๋ ๋ ค๋ฒ๋ฆฌ๋ ๊ฑฐ์ง. ๐ด
๊ทธ๋์ ์ค๋์ ํ์ด์ฌ์ผ๋ก ์ด ๋ชจ๋ ๊ฑธ ์๋ํํ๋ ๋ฐฉ๋ฒ์ ์๋ ค์ค๊ฒ. ํ๋ก๊ทธ๋๋ฐ ์ค๋ ฅ์ ํค์ฐ๊ณ ์ถ๋ค๋ฉด ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๊ด๋ จ ๊ฐ์๋ฅผ ์ฐพ์๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ!
๐ง ํ์ํ ๊ธฐ์ ์คํ๊ณผ ์ค๋น๋ฌผ
๋ณธ๊ฒฉ์ ์ผ๋ก ์์ํ๊ธฐ ์ ์, ๋ญ ์ค๋นํด์ผ ํ๋์ง ์์๋ณด์. ๊ฑฑ์ ๋ง, ์๊ฐ๋ณด๋ค ๋ณต์กํ์ง ์์! ๐
๐ฆ ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข Web3.py: ์ด๋๋ฆฌ์ ๋ธ๋ก์ฒด์ธ๊ณผ ์ํธ์์ฉํ๊ธฐ ์ํ ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข eth-account: ์ง๊ฐ ๊ด๋ฆฌ์ ํธ๋์ญ์ ์๋ช ์ ์ํ ๋๊ตฌ
โข python-dotenv: ๋ฏผ๊ฐํ ์ ๋ณด(๊ฐ์ธํค ๋ฑ)๋ฅผ ์์ ํ๊ฒ ๊ด๋ฆฌ
โข requests: API ํธ์ถ์ ์ํ HTTP ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข asyncio: ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ก ํจ์จ์ฑ ๊ทน๋ํ
๐ ํ์ํ ๋ฐฐ๊ฒฝ ์ง์โข Web3.py: ์ด๋๋ฆฌ์ ๋ธ๋ก์ฒด์ธ๊ณผ ์ํธ์์ฉํ๊ธฐ ์ํ ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข eth-account: ์ง๊ฐ ๊ด๋ฆฌ์ ํธ๋์ญ์ ์๋ช ์ ์ํ ๋๊ตฌ
โข python-dotenv: ๋ฏผ๊ฐํ ์ ๋ณด(๊ฐ์ธํค ๋ฑ)๋ฅผ ์์ ํ๊ฒ ๊ด๋ฆฌ
โข requests: API ํธ์ถ์ ์ํ HTTP ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข asyncio: ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ก ํจ์จ์ฑ ๊ทน๋ํ
์์งํ ๋งํ๋ฉด, ๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ์ด ์ฒ์์ด๋ผ๋ฉด ์ข ์ด๋ ค์ธ ์ ์์ด. ํ์ง๋ง ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐ๋ผ์ค๋ฉด ์ถฉ๋ถํ ํ ์ ์์ด! ๋ค์ ๊ฐ๋ ๋ค์ ์๊ณ ์์ผ๋ฉด ํจ์ฌ ์์ํด:
1. ์ค๋งํธ ์ปจํธ๋ํธ ๊ธฐ์ด
์ค๋งํธ ์ปจํธ๋ํธ๋ ๋ธ๋ก์ฒด์ธ ์์์ ์๋์ผ๋ก ์คํ๋๋ ํ๋ก๊ทธ๋จ์ด์ผ. ํ ํฐ ์ค์๋ ๊ฒฐ๊ตญ ์ค๋งํธ ์ปจํธ๋ํธ์ ํจ์๋ฅผ ํธ์ถํ๋ ๊ฑฐ๊ฑฐ๋ .
2. ERC-20 ํ ํฐ ํ์ค
์ด๋๋ฆฌ์์์ ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ํ ํฐ ํ์ค์ด์ผ. transfer, approve, allowance ๊ฐ์ ํจ์๋ค์ ์ดํดํด์ผ ํด.
3. ๊ฐ์ค(Gas) ๊ฐ๋
๋ธ๋ก์ฒด์ธ์์ ํธ๋์ญ์ ์ ์คํํ๋ ค๋ฉด ์์๋ฃ๋ฅผ ๋ด์ผ ํ๋๋ฐ, ์ด๊ฒ ๋ฐ๋ก ๊ฐ์ค์ผ. ๊ฐ์ค ๊ฐ๊ฒฉ ์ค์ ์ ์๋ชปํ๋ฉด ํธ๋์ญ์ ์ด ์คํจํ๊ฑฐ๋ ๋๋ฌด ๋น์ธ์ง ์ ์์ด.
4. DEX(ํ์ค์ํ ๊ฑฐ๋์) ์๋ ์๋ฆฌ
Uniswap, PancakeSwap ๊ฐ์ DEX๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง ์์์ผ ํด. AMM(Automated Market Maker) ๋ฐฉ์์ ์ดํดํ๋ฉด ๊ธ์์ฒจํ! ๐ฏ
๐ก ์ด๋ณด์ ํ
์ฒ์๋ถํฐ ๋ฉ์ธ๋ท์์ ์ค์ตํ์ง ๋ง! ํ ์คํธ๋ท(Goerli, Sepolia ๋ฑ)์์ ์ถฉ๋ถํ ์ฐ์ตํ ๋ค์์ ์ค์ ์ ํฌ์ ํ๋ ๊ฒ ์์ ํด. ์ค์๋ก ์ง์ง ๋ ๋ ๋ฆฌ๋ฉด... ๐ฑ
์ฒ์๋ถํฐ ๋ฉ์ธ๋ท์์ ์ค์ตํ์ง ๋ง! ํ ์คํธ๋ท(Goerli, Sepolia ๋ฑ)์์ ์ถฉ๋ถํ ์ฐ์ตํ ๋ค์์ ์ค์ ์ ํฌ์ ํ๋ ๊ฒ ์์ ํด. ์ค์๋ก ์ง์ง ๋ ๋ ๋ฆฌ๋ฉด... ๐ฑ
๐ ๏ธ ๊ฐ๋ฐ ํ๊ฒฝ ์ธํ ํ๊ธฐ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋ฉ ํ๊ฒฝ์ ๋ง๋ค์ด๋ณด์! ๋จ๊ณ๋ณ๋ก ์ฒ์ฒํ ๋ฐ๋ผ์ ๋ด.
Step 1: ๊ฐ์ํ๊ฒฝ ์์ฑ
ํ๋ก์ ํธ๋ง๋ค ๋ ๋ฆฝ์ ์ธ ํ๊ฒฝ์ ๋ง๋๋ ๊ฒ ์ข์. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฒ์ ์ถฉ๋์ ๋ฐฉ์งํ ์ ์๊ฑฐ๋ .
ํ๋ก์ ํธ๋ง๋ค ๋ ๋ฆฝ์ ์ธ ํ๊ฒฝ์ ๋ง๋๋ ๊ฒ ์ข์. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฒ์ ์ถฉ๋์ ๋ฐฉ์งํ ์ ์๊ฑฐ๋ .
python -m venv swap_automation
source swap_automation/bin/activate # Windows: swap_automation\Scripts\activate
Step 2: ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
โข web3: ๋ธ๋ก์ฒด์ธ๊ณผ ๋ํํ๋ ํต์ญ์ฌ ๐ฃ๏ธ
โข eth-account: ์ง๊ฐ ๊ด๋ฆฌ์ ๐
โข python-dotenv: ๋น๋ฐ ์ ๋ณด ๋ณด๊ดํจ ๐
โข aiohttp: ๋น ๋ฅธ ๋น๋๊ธฐ HTTP ์์ฒญ โก
pip install web3 eth-account python-dotenv requests aiohttp
๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ญ ํ๋ ๊ฑด์ง ๊ฐ๋จํ ์ค๋ช
ํ๋ฉด:โข web3: ๋ธ๋ก์ฒด์ธ๊ณผ ๋ํํ๋ ํต์ญ์ฌ ๐ฃ๏ธ
โข eth-account: ์ง๊ฐ ๊ด๋ฆฌ์ ๐
โข python-dotenv: ๋น๋ฐ ์ ๋ณด ๋ณด๊ดํจ ๐
โข aiohttp: ๋น ๋ฅธ ๋น๋๊ธฐ HTTP ์์ฒญ โก
Step 3: ํ๋ก์ ํธ ๊ตฌ์กฐ ๋ง๋ค๊ธฐ
๊น๋ํ ํ๋ก์ ํธ ๊ตฌ์กฐ๋ ์ ์ง๋ณด์๋ฅผ ํจ์ฌ ์ฝ๊ฒ ๋ง๋ค์ด์ค.
๊น๋ํ ํ๋ก์ ํธ ๊ตฌ์กฐ๋ ์ ์ง๋ณด์๋ฅผ ํจ์ฌ ์ฝ๊ฒ ๋ง๋ค์ด์ค.
swap_automation/
โโโ .env # ํ๊ฒฝ ๋ณ์ (๊ฐ์ธํค, API ํค ๋ฑ)
โโโ config.py # ์ค์ ํ์ผ
โโโ wallet_manager.py # ์ง๊ฐ ๊ด๋ฆฌ ๋ชจ๋
โโโ swap_engine.py # ์ค์ ๋ก์ง ํต์ฌ
โโโ price_monitor.py # ๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง
โโโ main.py # ๋ฉ์ธ ์คํ ํ์ผ
โโโ utils.py # ์ ํธ๋ฆฌํฐ ํจ์๋ค
Step 4: .env ํ์ผ ์ค์
์ ๋๋ก ๊ฐ์ธํค๋ฅผ ์ฝ๋์ ์ง์ ๋ฃ์ผ๋ฉด ์ ๋ผ! GitHub์ ์ฌ๋ ธ๋ค๊ฐ ํดํน๋นํ๋ ์ฌ๋ก๊ฐ ์ง์ง ๋ง๊ฑฐ๋ . ๐จ
์ ๋๋ก ๊ฐ์ธํค๋ฅผ ์ฝ๋์ ์ง์ ๋ฃ์ผ๋ฉด ์ ๋ผ! GitHub์ ์ฌ๋ ธ๋ค๊ฐ ํดํน๋นํ๋ ์ฌ๋ก๊ฐ ์ง์ง ๋ง๊ฑฐ๋ . ๐จ
# .env ํ์ผ
PRIVATE_KEY_A=your_wallet_a_private_key_here
PRIVATE_KEY_B=your_wallet_b_private_key_here
INFURA_API_KEY=your_infura_api_key
ETHERSCAN_API_KEY=your_etherscan_api_key
RPC_URL=https://mainnet.infura.io/v3/YOUR_PROJECT_ID
โ ๏ธ ๋ณด์ ๊ฒฝ๊ณ
.env ํ์ผ์ ์ ๋ Git์ ์ปค๋ฐํ์ง ๋ง! .gitignore์ ๊ผญ ์ถ๊ฐํด์ผ ํด. ๊ทธ๋ฆฌ๊ณ ๊ฐ์ธํค๋ ์ ๋ ๋๊ตฌ์๊ฒ๋ ๊ณต์ ํ์ง ๋ง ๊ฒ!
.env ํ์ผ์ ์ ๋ Git์ ์ปค๋ฐํ์ง ๋ง! .gitignore์ ๊ผญ ์ถ๊ฐํด์ผ ํด. ๊ทธ๋ฆฌ๊ณ ๊ฐ์ธํค๋ ์ ๋ ๋๊ตฌ์๊ฒ๋ ๊ณต์ ํ์ง ๋ง ๊ฒ!
๐ผ ์ง๊ฐ ๊ด๋ฆฌ ๋ชจ๋ ๊ตฌํํ๊ธฐ
์ด์ ์ค์ ์ฝ๋๋ฅผ ์์ฑํด๋ณด์! ๋จผ์ ์ง๊ฐ์ ๊ด๋ฆฌํ๋ ๋ชจ๋๋ถํฐ ๋ง๋ค ๊ฑฐ์ผ.wallet_manager.py ์์ฑ
from web3 import Web3
from eth_account import Account
from dotenv import load_dotenv
import os
load_dotenv()
class WalletManager:
def __init__(self, rpc_url):
"""
์ง๊ฐ ๋งค๋์ ์ด๊ธฐํ
rpc_url: ๋ธ๋ก์ฒด์ธ ๋
ธ๋ ์ฐ๊ฒฐ ์ฃผ์
"""
self.w3 = Web3(Web3.HTTPProvider(rpc_url))
# ์ฐ๊ฒฐ ํ์ธ
if not self.w3.is_connected():
raise Exception("๋ธ๋ก์ฒด์ธ ๋
ธ๋ ์ฐ๊ฒฐ ์คํจ! ๐ข")
print(f"โ
๋ธ๋ก์ฒด์ธ ์ฐ๊ฒฐ ์ฑ๊ณต! ํ์ฌ ๋ธ๋ก: {self.w3.eth.block_number}")
# ์ง๊ฐ ๋ก๋
self.wallet_a = self._load_wallet(os.getenv('PRIVATE_KEY_A'))
self.wallet_b = self._load_wallet(os.getenv('PRIVATE_KEY_B'))
def _load_wallet(self, private_key):
"""๊ฐ์ธํค๋ก๋ถํฐ ์ง๊ฐ ๊ฐ์ฒด ์์ฑ"""
if not private_key:
raise ValueError("๊ฐ์ธํค๊ฐ ์ค์ ๋์ง ์์์ด์!")
account = Account.from_key(private_key)
return {
'address': account.address,
'private_key': private_key,
'account': account
}
def get_balance(self, wallet_type='A', token_address=None):
"""
์ง๊ฐ ์์ก ์กฐํ
wallet_type: 'A' ๋๋ 'B'
token_address: None์ด๋ฉด ETH, ์ฃผ์ ์
๋ ฅ์ ํด๋น ํ ํฐ
"""
wallet = self.wallet_a if wallet_type == 'A' else self.wallet_b
if token_address is None:
# ETH ์์ก ์กฐํ
balance_wei = self.w3.eth.get_balance(wallet['address'])
balance_eth = self.w3.from_wei(balance_wei, 'ether')
return float(balance_eth)
else:
# ERC-20 ํ ํฐ ์์ก ์กฐํ
token_contract = self._get_token_contract(token_address)
balance = token_contract.functions.balanceOf(wallet['address']).call()
decimals = token_contract.functions.decimals().call()
return balance / (10 ** decimals)
def _get_token_contract(self, token_address):
"""ERC-20 ํ ํฐ ์ปจํธ๋ํธ ๊ฐ์ฒด ๋ฐํ"""
# ํ์ค ERC-20 ABI (๊ฐ์ํ ๋ฒ์ )
erc20_abi = [
{
"constant": True,
"inputs": [{"name": "_owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "balance", "type": "uint256"}],
"type": "function"
},
{
"constant": True,
"inputs": [],
"name": "decimals",
"outputs": [{"name": "", "type": "uint8"}],
"type": "function"
},
{
"constant": False,
"inputs": [
{"name": "_spender", "type": "address"},
{"name": "_value", "type": "uint256"}
],
"name": "approve",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
},
{
"constant": True,
"inputs": [
{"name": "_owner", "type": "address"},
{"name": "_spender", "type": "address"}
],
"name": "allowance",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
]
return self.w3.eth.contract(
address=Web3.to_checksum_address(token_address),
abi=erc20_abi
)
์ด ์ฝ๋๊ฐ ํ๋ ์ผ์ ์ฝ๊ฒ ์ค๋ช ํ๋ฉด:
1. ๋ธ๋ก์ฒด์ธ ์ฐ๊ฒฐ ๐
Web3 ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ด๋๋ฆฌ์ ๋คํธ์ํฌ์ ์ฐ๊ฒฐํด. Infura ๊ฐ์ ๋ ธ๋ ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ฉด ์ง์ ๋ ธ๋๋ฅผ ์ด์ํ์ง ์์๋ ๋ผ.
2. ์ง๊ฐ ๋ก๋ ๐
๊ฐ์ธํค๋ฅผ ์ฌ์ฉํด์ ์ง๊ฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด. ์ด ๊ฐ์ฒด๋ก ํธ๋์ญ์ ์ ์๋ช ํ ์ ์์ด.
3. ์์ก ์กฐํ ๐ฐ
ETH๋ ํ ํฐ์ด๋ ํ์ฌ ์์ก์ ํ์ธํ ์ ์์ด. ํ ํฐ์ ๊ฒฝ์ฐ decimals๋ฅผ ๊ณ ๋ คํด์ ์ค์ ๊ฐ์ผ๋ก ๋ณํํด์ค์ผ ํด.
4. ํ ํฐ ์ปจํธ๋ํธ ์ธํฐํ์ด์ค ๐
ERC-20 ํ์ค ABI๋ฅผ ์ฌ์ฉํด์ ํ ํฐ ์ปจํธ๋ํธ์ ์ํธ์์ฉํ ์ ์๊ฒ ๋ง๋ค์ด.
๐ก Pro Tip
์ค์ ํ๋ก์ ํธ์์๋ ์ ์ฒด ERC-20 ABI๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ข์. ์ ์ฝ๋๋ ์ค๋ช ์ ์ํด ํ์ํ ํจ์๋ง ํฌํจ์ํจ ๊ฑฐ์ผ. Etherscan์์ ์ปจํธ๋ํธ ABI๋ฅผ ๋ณต์ฌํด์ ์ฌ์ฉํ๋ฉด ๋ผ!
์ค์ ํ๋ก์ ํธ์์๋ ์ ์ฒด ERC-20 ABI๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ข์. ์ ์ฝ๋๋ ์ค๋ช ์ ์ํด ํ์ํ ํจ์๋ง ํฌํจ์ํจ ๊ฑฐ์ผ. Etherscan์์ ์ปจํธ๋ํธ ABI๋ฅผ ๋ณต์ฌํด์ ์ฌ์ฉํ๋ฉด ๋ผ!
๐ ์ค์ ์์ง ํต์ฌ ๋ก์ง ๊ตฌํ
์, ์ด์ ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ด์ผ! ์ค์ ๋ก ํ ํฐ์ ์ค์ํ๋ ๋ก์ง์ ๋ง๋ค์ด๋ณด์. ์ฌ๊ธฐ์๋ Uniswap V2๋ฅผ ์์๋ก ๋ค๊ฒ. ๐ฆswap_engine.py ์์ฑ
from web3 import Web3
from eth_account import Account
import time
import json
class SwapEngine:
def __init__(self, wallet_manager, router_address, router_abi):
"""
์ค์ ์์ง ์ด๊ธฐํ
wallet_manager: WalletManager ์ธ์คํด์ค
router_address: Uniswap Router ์ปจํธ๋ํธ ์ฃผ์
router_abi: Router ABI
"""
self.wm = wallet_manager
self.w3 = wallet_manager.w3
# Uniswap Router ์ปจํธ๋ํธ
self.router = self.w3.eth.contract(
address=Web3.to_checksum_address(router_address),
abi=router_abi
)
# WETH ์ฃผ์ (Wrapped ETH)
self.weth_address = self.router.functions.WETH().call()
print(f"๐ฆ Uniswap Router ์ฐ๊ฒฐ ์๋ฃ!")
print(f"WETH ์ฃผ์: {self.weth_address}")
def approve_token(self, token_address, wallet_type='A', amount=None):
"""
ํ ํฐ ์ฌ์ฉ ์น์ธ (approve)
DEX๊ฐ ๋ด ํ ํฐ์ ์ฌ์ฉํ ์ ์๋๋ก ํ๊ฐํ๋ ๊ณผ์
"""
wallet = self.wm.wallet_a if wallet_type == 'A' else self.wm.wallet_b
token_contract = self.wm._get_token_contract(token_address)
# amount๊ฐ None์ด๋ฉด ๋ฌด์ ํ ์น์ธ
if amount is None:
amount = 2**256 - 1 # ์ต๋๊ฐ
else:
decimals = token_contract.functions.decimals().call()
amount = int(amount * (10 ** decimals))
# ํ์ฌ ์น์ธ๋ ํ์ธ
current_allowance = token_contract.functions.allowance(
wallet['address'],
self.router.address
).call()
if current_allowance >= amount:
print(f"โ
์ด๋ฏธ ์ถฉ๋ถํ ์น์ธ๋์ด ์์ด์: {current_allowance}")
return True
# approve ํธ๋์ญ์
์์ฑ
approve_txn = token_contract.functions.approve(
self.router.address,
amount
).build_transaction({
'from': wallet['address'],
'gas': 100000,
'gasPrice': self.w3.eth.gas_price,
'nonce': self.w3.eth.get_transaction_count(wallet['address'])
})
# ํธ๋์ญ์
์๋ช
๋ฐ ์ ์ก
signed_txn = self.w3.eth.account.sign_transaction(
approve_txn,
wallet['private_key']
)
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
print(f"๐ Approve ํธ๋์ญ์
์ ์ก: {tx_hash.hex()}")
# ํธ๋์ญ์
์๋ฃ ๋๊ธฐ
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
if receipt['status'] == 1:
print(f"โ
Approve ์ฑ๊ณต!")
return True
else:
print(f"โ Approve ์คํจ!")
return False
def get_amounts_out(self, amount_in, path):
"""
์ค์ ์ ๋ฐ์ ์ ์๋ ํ ํฐ ์ ๊ณ์ฐ
amount_in: ์
๋ ฅ ํ ํฐ ์
path: ์ค์ ๊ฒฝ๋ก [token_in, token_out]
"""
# ํ ํฐ decimals ๊ฐ์ ธ์ค๊ธฐ
token_in_contract = self.wm._get_token_contract(path[0])
decimals = token_in_contract.functions.decimals().call()
amount_in_wei = int(amount_in * (10 ** decimals))
# Uniswap์์ ์์ ์ถ๋ ฅ๋ ์กฐํ
amounts = self.router.functions.getAmountsOut(
amount_in_wei,
path
).call()
# ์ถ๋ ฅ ํ ํฐ decimals๋ก ๋ณํ
token_out_contract = self.wm._get_token_contract(path[-1])
out_decimals = token_out_contract.functions.decimals().call()
amount_out = amounts[-1] / (10 ** out_decimals)
return amount_out
def execute_swap(self, amount_in, amount_out_min, path, wallet_type='A', deadline_minutes=20):
"""
์ค์ ์ค์ ์คํ
amount_in: ์
๋ ฅ ํ ํฐ ์
amount_out_min: ์ต์ ์ถ๋ ฅ ํ ํฐ ์ (์ฌ๋ฆฌํผ์ง ๋ณดํธ)
path: ์ค์ ๊ฒฝ๋ก
wallet_type: ์ฌ์ฉํ ์ง๊ฐ
deadline_minutes: ํธ๋์ญ์
์ ํจ ์๊ฐ
"""
wallet = self.wm.wallet_a if wallet_type == 'A' else self.wm.wallet_b
# ํ ํฐ decimals ์ฒ๋ฆฌ
token_in_contract = self.wm._get_token_contract(path[0])
token_out_contract = self.wm._get_token_contract(path[-1])
in_decimals = token_in_contract.functions.decimals().call()
out_decimals = token_out_contract.functions.decimals().call()
amount_in_wei = int(amount_in * (10 ** in_decimals))
amount_out_min_wei = int(amount_out_min * (10 ** out_decimals))
# Deadline ์ค์ (ํ์ฌ ์๊ฐ + ์ง์ ๋ถ)
deadline = int(time.time()) + (deadline_minutes * 60)
print(f"\n๐ ์ค์ ์ค๋น ์ค...")
print(f"์
๋ ฅ: {amount_in} {path[0][:6]}...")
print(f"์ต์ ์ถ๋ ฅ: {amount_out_min} {path[-1][:6]}...")
# ์ค์ ํธ๋์ญ์
์์ฑ
swap_txn = self.router.functions.swapExactTokensForTokens(
amount_in_wei,
amount_out_min_wei,
path,
wallet['address'],
deadline
).build_transaction({
'from': wallet['address'],
'gas': 250000, # ๊ฐ์ค ๋ฆฌ๋ฐ
'gasPrice': self.w3.eth.gas_price,
'nonce': self.w3.eth.get_transaction_count(wallet['address'])
})
# ๊ฐ์ค ์์ธก (์ ํ์ฌํญ)
try:
estimated_gas = self.w3.eth.estimate_gas(swap_txn)
swap_txn['gas'] = int(estimated_gas * 1.2) # 20% ์ฌ์
print(f"โฝ ์์ ๊ฐ์ค: {estimated_gas}")
except Exception as e:
print(f"โ ๏ธ ๊ฐ์ค ์์ธก ์คํจ, ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ: {e}")
# ํธ๋์ญ์
์๋ช
signed_txn = self.w3.eth.account.sign_transaction(
swap_txn,
wallet['private_key']
)
# ํธ๋์ญ์
์ ์ก
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
print(f"\n๐ค ์ค์ ํธ๋์ญ์
์ ์ก!")
print(f"TX Hash: {tx_hash.hex()}")
# ํธ๋์ญ์
์๋ฃ ๋๊ธฐ
print("โณ ๋ธ๋ก ํ์ธ ๋๊ธฐ ์ค...")
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=300)
if receipt['status'] == 1:
print(f"\nโ
์ค์ ์ฑ๊ณต! ๐")
print(f"๊ฐ์ค ์ฌ์ฉ๋: {receipt['gasUsed']}")
print(f"๋ธ๋ก ๋ฒํธ: {receipt['blockNumber']}")
return {
'success': True,
'tx_hash': tx_hash.hex(),
'gas_used': receipt['gasUsed']
}
else:
print(f"\nโ ์ค์ ์คํจ! ๐ข")
return {
'success': False,
'tx_hash': tx_hash.hex()
}
def swap_with_slippage_protection(self, amount_in, path, wallet_type='A',
slippage_percent=0.5):
"""
์ฌ๋ฆฌํผ์ง ๋ณดํธ ๊ธฐ๋ฅ์ด ์๋ ์ค์
slippage_percent: ํ์ฉ ์ฌ๋ฆฌํผ์ง (%)
"""
# ์์ ์ถ๋ ฅ๋ ๊ณ์ฐ
expected_out = self.get_amounts_out(amount_in, path)
# ์ฌ๋ฆฌํผ์ง ์ ์ฉํ ์ต์ ์ถ๋ ฅ๋
amount_out_min = expected_out * (1 - slippage_percent / 100)
print(f"\n๐ฑ ์ฌ๋ฆฌํผ์ง ๋ณดํธ ์ค์")
print(f"์์ ์ถ๋ ฅ: {expected_out:.6f}")
print(f"์ต์ ์ถ๋ ฅ: {amount_out_min:.6f} (์ฌ๋ฆฌํผ์ง {slippage_percent}%)")
# ์ค์ ์คํ
return self.execute_swap(amount_in, amount_out_min, path, wallet_type)
์, ์ฝ๋๊ฐ ์ข ๊ธธ์ง? ํ์ง๋ง ํ๋ํ๋ ๋ฏ์ด๋ณด๋ฉด ๊ทธ๋ ๊ฒ ์ด๋ ต์ง ์์! ํต์ฌ ๊ฐ๋ ๋ค์ ์ ๋ฆฌํด๋ณผ๊ฒ. ๐
๐ ํต์ฌ ๊ฐ๋
์ค๋ช
1. Approve (์น์ธ)
DEX๊ฐ ๋ด ํ ํฐ์ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ์น์ธ์ ๋ฐ์์ผ ํด. ์ด๊ฒ ์์ผ๋ฉด ์ค์์ด ๋ถ๊ฐ๋ฅํด. ํ ๋ฒ ์น์ธํ๋ฉด ์ฌ๋ฌ ๋ฒ ์ค์ํ ์ ์์ด.
2. getAmountsOut (์์ ์ถ๋ ฅ๋)
์ค์ํ๊ธฐ ์ ์ ์ผ๋ง๋ ๋ฐ์ ์ ์๋์ง ๋ฏธ๋ฆฌ ๊ณ์ฐํด. ์ด๊ฑธ๋ก ๊ฑฐ๋๊ฐ ์ด๋์ธ์ง ํ๋จํ ์ ์์ง.
3. Slippage (์ฌ๋ฆฌํผ์ง)
์์ ๊ฐ๊ฒฉ๊ณผ ์ค์ ์ฒด๊ฒฐ ๊ฐ๊ฒฉ์ ์ฐจ์ด์ผ. ๋ณ๋์ฑ์ด ํฐ ์์ฅ์์๋ ์ฌ๋ฆฌํผ์ง๊ฐ ํด ์ ์์ด์, ์ต์ ์ถ๋ ฅ๋์ ์ค์ ํด์ ๋ณดํธํด์ผ ํด.
4. Deadline (๋ง๊ฐ ์๊ฐ)
ํธ๋์ญ์ ์ด ๋๋ฌด ์ค๋ ๋๊ธฐํ๋ฉด ๊ฐ๊ฒฉ์ด ๋ง์ด ๋ณํ ์ ์์ด. ๊ทธ๋์ ์ ํจ ์๊ฐ์ ์ค์ ํ๋ ๊ฑฐ์ผ.
5. Gas Estimation (๊ฐ์ค ์์ธก)
ํธ๋์ญ์ ์คํ์ ํ์ํ ๊ฐ์ค๋ฅผ ๋ฏธ๋ฆฌ ๊ณ์ฐํด. ๋ถ์กฑํ๋ฉด ์คํจํ๊ณ , ๋๋ฌด ๋ง์ผ๋ฉด ๋ญ๋น๋๊น ์ ์ ํ ๊ฐ์ ์ฐพ๋ ๊ฒ ์ค์ํด.
1. Approve (์น์ธ)
DEX๊ฐ ๋ด ํ ํฐ์ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ์น์ธ์ ๋ฐ์์ผ ํด. ์ด๊ฒ ์์ผ๋ฉด ์ค์์ด ๋ถ๊ฐ๋ฅํด. ํ ๋ฒ ์น์ธํ๋ฉด ์ฌ๋ฌ ๋ฒ ์ค์ํ ์ ์์ด.
2. getAmountsOut (์์ ์ถ๋ ฅ๋)
์ค์ํ๊ธฐ ์ ์ ์ผ๋ง๋ ๋ฐ์ ์ ์๋์ง ๋ฏธ๋ฆฌ ๊ณ์ฐํด. ์ด๊ฑธ๋ก ๊ฑฐ๋๊ฐ ์ด๋์ธ์ง ํ๋จํ ์ ์์ง.
3. Slippage (์ฌ๋ฆฌํผ์ง)
์์ ๊ฐ๊ฒฉ๊ณผ ์ค์ ์ฒด๊ฒฐ ๊ฐ๊ฒฉ์ ์ฐจ์ด์ผ. ๋ณ๋์ฑ์ด ํฐ ์์ฅ์์๋ ์ฌ๋ฆฌํผ์ง๊ฐ ํด ์ ์์ด์, ์ต์ ์ถ๋ ฅ๋์ ์ค์ ํด์ ๋ณดํธํด์ผ ํด.
4. Deadline (๋ง๊ฐ ์๊ฐ)
ํธ๋์ญ์ ์ด ๋๋ฌด ์ค๋ ๋๊ธฐํ๋ฉด ๊ฐ๊ฒฉ์ด ๋ง์ด ๋ณํ ์ ์์ด. ๊ทธ๋์ ์ ํจ ์๊ฐ์ ์ค์ ํ๋ ๊ฑฐ์ผ.
5. Gas Estimation (๊ฐ์ค ์์ธก)
ํธ๋์ญ์ ์คํ์ ํ์ํ ๊ฐ์ค๋ฅผ ๋ฏธ๋ฆฌ ๊ณ์ฐํด. ๋ถ์กฑํ๋ฉด ์คํจํ๊ณ , ๋๋ฌด ๋ง์ผ๋ฉด ๋ญ๋น๋๊น ์ ์ ํ ๊ฐ์ ์ฐพ๋ ๊ฒ ์ค์ํด.
๐ ๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง ์์คํ ๊ตฌ์ถ
์ค์์ ์๋ํํ๋ ค๋ฉด ๊ฐ๊ฒฉ์ ์ค์๊ฐ์ผ๋ก ๋ชจ๋ํฐ๋งํด์ผ ํด. ์ข์ ๊ธฐํ๋ฅผ ๋์น์ง ์์ผ๋ ค๋ฉด ๋ง์ด์ผ! ๐price_monitor.py ์์ฑ
import asyncio
import aiohttp
from datetime import datetime
import json
class PriceMonitor:
def __init__(self, swap_engine):
"""
๊ฐ๊ฒฉ ๋ชจ๋ํฐ ์ด๊ธฐํ
swap_engine: SwapEngine ์ธ์คํด์ค
"""
self.swap_engine = swap_engine
self.price_history = {}
self.alert_conditions = []
def add_alert_condition(self, token_pair, condition_type, threshold, callback):
"""
๊ฐ๊ฒฉ ์๋ฆผ ์กฐ๊ฑด ์ถ๊ฐ
token_pair: (token_a, token_b) ํํ
condition_type: 'above' ๋๋ 'below'
threshold: ์๊ณ๊ฐ
callback: ์กฐ๊ฑด ์ถฉ์กฑ์ ์คํํ ํจ์
"""
self.alert_conditions.append({
'pair': token_pair,
'type': condition_type,
'threshold': threshold,
'callback': callback,
'triggered': False
})
print(f"๐ ์๋ฆผ ์กฐ๊ฑด ์ถ๊ฐ: {token_pair} {condition_type} {threshold}")
async def get_current_price(self, token_in, token_out, amount=1.0):
"""
ํ์ฌ ๊ฐ๊ฒฉ ์กฐํ (๋น๋๊ธฐ)
"""
try:
price = self.swap_engine.get_amounts_out(
amount,
[token_in, token_out]
)
return price
except Exception as e:
print(f"โ ๊ฐ๊ฒฉ ์กฐํ ์คํจ: {e}")
return None
async def monitor_price(self, token_in, token_out, interval=10):
"""
๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง (๋น๋๊ธฐ)
interval: ์ฒดํฌ ๊ฐ๊ฒฉ (์ด)
"""
pair_key = f"{token_in[:6]}.../{token_out[:6]}..."
print(f"\n๐๏ธ ๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง ์์: {pair_key}")
print(f"์ฒดํฌ ๊ฐ๊ฒฉ: {interval}์ด")
while True:
try:
price = await self.get_current_price(token_in, token_out)
if price is not None:
timestamp = datetime.now().strftime("%H:%M:%S")
# ๊ฐ๊ฒฉ ํ์คํ ๋ฆฌ ์ ์ฅ
if pair_key not in self.price_history:
self.price_history[pair_key] = []
self.price_history[pair_key].append({
'timestamp': timestamp,
'price': price
})
# ์ต๊ทผ 100๊ฐ๋ง ์ ์ง
if len(self.price_history[pair_key]) > 100:
self.price_history[pair_key].pop(0)
print(f"[{timestamp}] {pair_key}: {price:.6f}")
# ์๋ฆผ ์กฐ๊ฑด ์ฒดํฌ
await self._check_alert_conditions(
(token_in, token_out),
price
)
await asyncio.sleep(interval)
except Exception as e:
print(f"โ ๋ชจ๋ํฐ๋ง ์ค๋ฅ: {e}")
await asyncio.sleep(interval)
async def _check_alert_conditions(self, token_pair, current_price):
"""์๋ฆผ ์กฐ๊ฑด ํ์ธ ๋ฐ ์คํ"""
for condition in self.alert_conditions:
if condition['pair'] != token_pair:
continue
if condition['triggered']:
continue
should_trigger = False
if condition['type'] == 'above' and current_price > condition['threshold']:
should_trigger = True
elif condition['type'] == 'below' and current_price < condition['threshold']:
should_trigger = True
if should_trigger:
print(f"\n๐จ ์๋ฆผ ์กฐ๊ฑด ์ถฉ์กฑ!")
print(f"ํ์ด: {token_pair}")
print(f"ํ์ฌ ๊ฐ๊ฒฉ: {current_price}")
print(f"์กฐ๊ฑด: {condition['type']} {condition['threshold']}")
condition['triggered'] = True
# ์ฝ๋ฐฑ ํจ์ ์คํ
if asyncio.iscoroutinefunction(condition['callback']):
await condition['callback'](token_pair, current_price)
else:
condition['callback'](token_pair, current_price)
def get_price_statistics(self, pair_key):
"""๊ฐ๊ฒฉ ํต๊ณ ๊ณ์ฐ"""
if pair_key not in self.price_history:
return None
prices = [item['price'] for item in self.price_history[pair_key]]
if not prices:
return None
return {
'current': prices[-1],
'min': min(prices),
'max': max(prices),
'avg': sum(prices) / len(prices),
'change': ((prices[-1] - prices[0]) / prices[0] * 100) if len(prices) > 1 else 0
}
async def monitor_multiple_pairs(self, pairs, interval=10):
"""
์ฌ๋ฌ ํ ํฐ ํ์ด ๋์ ๋ชจ๋ํฐ๋ง
pairs: [(token_in, token_out), ...] ๋ฆฌ์คํธ
"""
tasks = []
for token_in, token_out in pairs:
task = asyncio.create_task(
self.monitor_price(token_in, token_out, interval)
)
tasks.append(task)
await asyncio.gather(*tasks)
์ด ๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง ์์คํ ์ ์ฅ์ ์ ๋ญ๊น? ๐ค
1. ๋น๋๊ธฐ ์ฒ๋ฆฌ โก
asyncio๋ฅผ ์ฌ์ฉํด์ ์ฌ๋ฌ ํ ํฐ ํ์ด๋ฅผ ๋์์ ๋ชจ๋ํฐ๋งํ ์ ์์ด. ํ๋์ฉ ์์ฐจ์ ์ผ๋ก ์ฒดํฌํ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ํจ์จ์ ์ด์ง!
2. ์๋ฆผ ์์คํ ๐
ํน์ ๊ฐ๊ฒฉ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋๋ฉด ์๋์ผ๋ก ์๋ฆผ์ ๋ณด๋ด๊ฑฐ๋ ์ค์์ ์คํํ ์ ์์ด. 24์๊ฐ ๋ชจ๋ํฐ๋ง์ด ๊ฐ๋ฅํด์ง๋ ๊ฑฐ์ผ.
3. ๊ฐ๊ฒฉ ํ์คํ ๋ฆฌ ๐
๊ณผ๊ฑฐ ๊ฐ๊ฒฉ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํด์ ํต๊ณ๋ฅผ ๋ผ ์ ์์ด. ์ด๊ฑธ๋ก ๋ ๋๋ํ ๊ฑฐ๋ ์ ๋ต์ ์ธ์ธ ์ ์์ง.
4. ํ์ฅ ๊ฐ๋ฅ์ฑ ๐
์ฝ๋ฐฑ ํจ์๋ฅผ ํตํด ๋ค์ํ ์๋ํ ๋ก์ง์ ์ถ๊ฐํ ์ ์์ด. ํ ๋ ๊ทธ๋จ ์๋ฆผ, ์ด๋ฉ์ผ ๋ฐ์ก, ์๋ ์ค์ ๋ฑ ๋ญ๋ ์ง ๊ฐ๋ฅํด!
๐ฎ ๋ฉ์ธ ์คํ ๋ก์ง ์์ฑ
์, ์ด์ ๋ชจ๋ ๋ชจ๋์ ํตํฉํด์ ์ค์ ๋ก ๋์ํ๋ ํ๋ก๊ทธ๋จ์ ๋ง๋ค์ด๋ณด์! ๐ฏmain.py ์์ฑ
import asyncio
from wallet_manager import WalletManager
from swap_engine import SwapEngine
from price_monitor import PriceMonitor
import json
import os
from dotenv import load_dotenv
# ํ๊ฒฝ ๋ณ์ ๋ก๋
load_dotenv()
# Uniswap V2 Router ์ ๋ณด
UNISWAP_ROUTER_ADDRESS = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
# ๊ฐ์ํ๋ Uniswap Router ABI (ํ์ํ ํจ์๋ง)
UNISWAP_ROUTER_ABI = json.loads('''[
{
"inputs": [],
"name": "WETH",
"outputs": [{"internalType": "address", "name": "", "type": "address"}],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{"internalType": "uint256", "name": "amountIn", "type": "uint256"},
{"internalType": "address[]", "name": "path", "type": "address[]"}
],
"name": "getAmountsOut",
"outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "uint256", "name": "amountIn", "type": "uint256"},
{"internalType": "uint256", "name": "amountOutMin", "type": "uint256"},
{"internalType": "address[]", "name": "path", "type": "address[]"},
{"internalType": "address", "name": "to", "type": "address"},
{"internalType": "uint256", "name": "deadline", "type": "uint256"}
],
"name": "swapExactTokensForTokens",
"outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}],
"stateMutability": "nonpayable",
"type": "function"
}
]''')
# ํ ํฐ ์ฃผ์ (์์ - ์ค์ ์ฃผ์๋ก ๋ณ๊ฒฝ ํ์)
USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F"
async def auto_swap_on_price_condition(token_pair, current_price):
"""
๊ฐ๊ฒฉ ์กฐ๊ฑด ์ถฉ์กฑ์ ์๋ ์ค์ ์คํ
"""
print(f"\n๐ค ์๋ ์ค์ ์์!")
token_in, token_out = token_pair
amount_to_swap = 100 # ์ค์ํ ์ (์ค์ ๋ก๋ ๋์ ์ผ๋ก ๊ณ์ฐ)
# ์ค์ ์คํ
result = swap_engine.swap_with_slippage_protection(
amount_in=amount_to_swap,
path=[token_in, token_out],
wallet_type='A',
slippage_percent=1.0
)
if result['success']:
print(f"โ
์๋ ์ค์ ์๋ฃ! TX: {result['tx_hash']}")
else:
print(f"โ ์๋ ์ค์ ์คํจ!")
async def main():
"""๋ฉ์ธ ์คํ ํจ์"""
print("=" * 60)
print("๐ ํ ํฐ ์ค์ ์๋ํ ์์คํ
์์!")
print("=" * 60)
# 1. ์ง๊ฐ ๋งค๋์ ์ด๊ธฐํ
print("\n๐ฑ ์ง๊ฐ ๋งค๋์ ์ด๊ธฐํ ์ค...")
wallet_manager = WalletManager(os.getenv('RPC_URL'))
# ์ง๊ฐ ์์ก ํ์ธ
print(f"\n๐ฐ Wallet A ์์ก:")
eth_balance = wallet_manager.get_balance('A')
print(f" ETH: {eth_balance:.4f}")
# 2. ์ค์ ์์ง ์ด๊ธฐํ
print("\n๐ง ์ค์ ์์ง ์ด๊ธฐํ ์ค...")
global swap_engine
swap_engine = SwapEngine(
wallet_manager,
UNISWAP_ROUTER_ADDRESS,
UNISWAP_ROUTER_ABI
)
# 3. ๊ฐ๊ฒฉ ๋ชจ๋ํฐ ์ด๊ธฐํ
print("\n๐ ๊ฐ๊ฒฉ ๋ชจ๋ํฐ ์ด๊ธฐํ ์ค...")
price_monitor = PriceMonitor(swap_engine)
# 4. ์๋ฆผ ์กฐ๊ฑด ์ค์
print("\nโ๏ธ ์๋ฆผ ์กฐ๊ฑด ์ค์ ์ค...")
# ์์: USDC/DAI ๊ฐ๊ฒฉ์ด 1.01 ์ด์์ด๋ฉด ์๋ ์ค์
price_monitor.add_alert_condition(
token_pair=(USDC_ADDRESS, DAI_ADDRESS),
condition_type='above',
threshold=1.01,
callback=auto_swap_on_price_condition
)
# 5. ๋ชจ๋ํฐ๋ง ์์
print("\n๐๏ธ ๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง ์์!")
print("Ctrl+C๋ฅผ ๋๋ฌ ์ข
๋ฃํ์ธ์.\n")
try:
# ์ฌ๋ฌ ํ์ด ๋์ ๋ชจ๋ํฐ๋ง
await price_monitor.monitor_multiple_pairs(
pairs=[
(USDC_ADDRESS, DAI_ADDRESS),
# ํ์ํ ๋งํผ ํ์ด ์ถ๊ฐ ๊ฐ๋ฅ
],
interval=30 # 30์ด๋ง๋ค ์ฒดํฌ
)
except KeyboardInterrupt:
print("\n\nโน๏ธ ํ๋ก๊ทธ๋จ ์ข
๋ฃ!")
# ํต๊ณ ์ถ๋ ฅ
print("\n๐ ๊ฐ๊ฒฉ ํต๊ณ:")
for pair_key in price_monitor.price_history:
stats = price_monitor.get_price_statistics(pair_key)
if stats:
print(f"\n{pair_key}:")
print(f" ํ์ฌ: {stats['current']:.6f}")
print(f" ์ต์ : {stats['min']:.6f}")
print(f" ์ต๊ณ : {stats['max']:.6f}")
print(f" ํ๊ท : {stats['avg']:.6f}")
print(f" ๋ณ๋: {stats['change']:.2f}%")
if __name__ == "__main__":
asyncio.run(main())
์ด์ ํ๋ก๊ทธ๋จ์ ์คํํ๋ฉด ์ด๋ฐ ํ๋ฆ์ผ๋ก ๋์ํด:
1๋จ๊ณ: ์ด๊ธฐํ ๐ง
์ง๊ฐ ์ฐ๊ฒฐ โ ์ค์ ์์ง ์ค๋น โ ๊ฐ๊ฒฉ ๋ชจ๋ํฐ ์ค์
2๋จ๊ณ: ๋ชจ๋ํฐ๋ง ๐
์ค์ ํ ํ ํฐ ํ์ด์ ๊ฐ๊ฒฉ์ ์ฃผ๊ธฐ์ ์ผ๋ก ์ฒดํฌ
3๋จ๊ณ: ์กฐ๊ฑด ํ์ธ ๐ฏ
์๋ฆผ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋๋์ง ๊ณ์ ํ์ธ
4๋จ๊ณ: ์๋ ์คํ ๐ค
์กฐ๊ฑด์ด ์ถฉ์กฑ๋๋ฉด ์๋์ผ๋ก ์ค์ ์คํ!
5๋จ๊ณ: ํต๊ณ ์์ง ๐
๋ชจ๋ ๊ฐ๊ฒฉ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํด์ ๋์ค์ ๋ถ์ ๊ฐ๋ฅ
โก ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
์๋ํ ์์คํ ์ ๋ง๋ค์๋ค๊ณ ๋์ด ์๋์ผ. ์ค์ ๋ก ์ด์ํ๋ ค๋ฉด ์ฑ๋ฅ ์ต์ ํ๊ฐ ์ ๋ง ์ค์ํด! ๐ช1. ๊ฐ์ค ๋น์ฉ ์ต์ ํ
๋ธ๋ก์ฒด์ธ์์ ๊ฐ์ฅ ํฐ ๋น์ฉ์ ๋ฐ๋ก ๊ฐ์ค์ผ. ๋๋ํ๊ฒ ๊ด๋ฆฌํ์ง ์์ผ๋ฉด ์์ต๋ณด๋ค ๋น์ฉ์ด ๋ ํด ์๋ ์์ด. ๐ฑ
๊ฐ์ค ๊ฐ๊ฒฉ ๋์ ์กฐ์ ์ฝ๋
def get_optimal_gas_price(self):
"""
ํ์ฌ ๋คํธ์ํฌ ์ํฉ์ ๋ง๋ ์ต์ ๊ฐ์ค ๊ฐ๊ฒฉ ๊ณ์ฐ
"""
# ํ์ฌ ๊ฐ์ค ๊ฐ๊ฒฉ ์กฐํ
current_gas = self.w3.eth.gas_price
# ์ต๊ทผ ๋ธ๋ก๋ค์ ๊ฐ์ค ๊ฐ๊ฒฉ ๋ถ์
latest_block = self.w3.eth.block_number
gas_prices = []
for i in range(5): # ์ต๊ทผ 5๊ฐ ๋ธ๋ก
block = self.w3.eth.get_block(latest_block - i)
if 'baseFeePerGas' in block:
gas_prices.append(block['baseFeePerGas'])
if gas_prices:
avg_gas = sum(gas_prices) // len(gas_prices)
# ํ๊ท ๋ณด๋ค 10% ๋๊ฒ ์ค์ (๋น ๋ฅธ ์ฒ๋ฆฌ๋ฅผ ์ํด)
optimal_gas = int(avg_gas * 1.1)
else:
optimal_gas = current_gas
# ์ต๋ ๊ฐ์ค ๊ฐ๊ฒฉ ์ ํ (๋๋ฌด ๋น์ธ๋ฉด ์คํ ์ ํจ)
max_gas_price = self.w3.to_wei(100, 'gwei')
return min(optimal_gas, max_gas_price)
2. RPC ์์ฒญ ์ต์ ํ
๋ธ๋ก์ฒด์ธ ๋ ธ๋์ ๋๋ฌด ๋ง์ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋๊ฐ ๋๋ ค์ง๊ณ , ๋ฌด๋ฃ ํ๋์ด๋ฉด ์ ํ์ ๊ฑธ๋ฆด ์๋ ์์ด.
๐ฏ RPC ์ต์ ํ ํ
โข ๋ฐฐ์น ์์ฒญ ์ฌ์ฉ: ์ฌ๋ฌ ๊ฐ์ ์์ฒญ์ ํ ๋ฒ์ ๋ณด๋ด๊ธฐ
โข ์บ์ฑ ํ์ฉ: ์์ฃผ ์กฐํํ๋ ๋ฐ์ดํฐ๋ ๋ก์ปฌ์ ์ ์ฅ
โข ์น์์ผ ์ฐ๊ฒฐ: HTTP๋ณด๋ค ๋น ๋ฅธ ์ค์๊ฐ ํต์
โข ์ฌ๋ฌ RPC ์๋ํฌ์ธํธ: ํ๋๊ฐ ๋๋ฆฌ๋ฉด ๋ค๋ฅธ ๊ฑธ๋ก ์ ํ
โข ๋ฐฐ์น ์์ฒญ ์ฌ์ฉ: ์ฌ๋ฌ ๊ฐ์ ์์ฒญ์ ํ ๋ฒ์ ๋ณด๋ด๊ธฐ
โข ์บ์ฑ ํ์ฉ: ์์ฃผ ์กฐํํ๋ ๋ฐ์ดํฐ๋ ๋ก์ปฌ์ ์ ์ฅ
โข ์น์์ผ ์ฐ๊ฒฐ: HTTP๋ณด๋ค ๋น ๋ฅธ ์ค์๊ฐ ํต์
โข ์ฌ๋ฌ RPC ์๋ํฌ์ธํธ: ํ๋๊ฐ ๋๋ฆฌ๋ฉด ๋ค๋ฅธ ๊ฑธ๋ก ์ ํ
class OptimizedRPCManager:
def __init__(self, rpc_urls):
"""์ฌ๋ฌ RPC ์๋ํฌ์ธํธ ๊ด๋ฆฌ"""
self.rpc_urls = rpc_urls
self.current_index = 0
self.w3_instances = [Web3(Web3.HTTPProvider(url)) for url in rpc_urls]
self.cache = {}
self.cache_ttl = 10 # ์บ์ ์ ํจ ์๊ฐ (์ด)
def get_w3(self):
"""ํ์ฌ ํ์ฑ Web3 ์ธ์คํด์ค ๋ฐํ"""
return self.w3_instances[self.current_index]
def switch_rpc(self):
"""๋ค์ RPC๋ก ์ ํ"""
self.current_index = (self.current_index + 1) % len(self.rpc_urls)
print(f"๐ RPC ์ ํ: {self.rpc_urls[self.current_index]}")
def get_with_cache(self, key, fetch_function, ttl=None):
"""์บ์ฑ์ ํ์ฉํ ๋ฐ์ดํฐ ์กฐํ"""
import time
if ttl is None:
ttl = self.cache_ttl
now = time.time()
# ์บ์์ ์๊ณ ์ ํจํ๋ฉด ๋ฐํ
if key in self.cache:
cached_data, cached_time = self.cache[key]
if now - cached_time < ttl:
return cached_data
# ์บ์๊ฐ ์๊ฑฐ๋ ๋ง๋ฃ๋๋ฉด ์๋ก ์กฐํ
data = fetch_function()
self.cache[key] = (data, now)
return data
3. ๋ฉํฐ์ค๋ ๋ฉ๊ณผ ๋น๋๊ธฐ ์ฒ๋ฆฌ
์ฌ๋ฌ ์์ ์ ๋์์ ์ฒ๋ฆฌํ๋ฉด ์ ์ฒด ์์คํ ์ฑ๋ฅ์ด ํฌ๊ฒ ํฅ์๋ผ. ํนํ ๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง ๊ฐ์ I/O ์์ ์ ํจ๊ณผ์ ์ด์ผ! ๐
import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor
class AsyncSwapSystem:
def __init__(self):
self.executor = ThreadPoolExecutor(max_workers=5)
async def fetch_multiple_prices(self, token_pairs):
"""์ฌ๋ฌ ํ ํฐ ํ์ด์ ๊ฐ๊ฒฉ์ ๋์์ ์กฐํ"""
async with aiohttp.ClientSession() as session:
tasks = []
for token_in, token_out in token_pairs:
task = self.fetch_price_async(session, token_in, token_out)
tasks.append(task)
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
async def fetch_price_async(self, session, token_in, token_out):
"""๋น๋๊ธฐ ๊ฐ๊ฒฉ ์กฐํ"""
try:
# API ํธ์ถ ๋๋ ๋ธ๋ก์ฒด์ธ ์กฐํ
# ์ค์ ๊ตฌํ์ ์ฌ์ฉํ๋ DEX์ ๋ฐ๋ผ ๋ค๋ฆ
price = await self.get_price_from_api(session, token_in, token_out)
return {
'pair': (token_in, token_out),
'price': price,
'success': True
}
except Exception as e:
return {
'pair': (token_in, token_out),
'error': str(e),
'success': False
}
async def parallel_swap_execution(self, swap_tasks):
"""์ฌ๋ฌ ์ค์์ ๋ณ๋ ฌ๋ก ์คํ"""
results = await asyncio.gather(*swap_tasks, return_exceptions=True)
successful = [r for r in results if r.get('success')]
failed = [r for r in results if not r.get('success')]
print(f"โ
์ฑ๊ณต: {len(successful)}๊ฐ")
print(f"โ ์คํจ: {len(failed)}๊ฐ")
return results
๐ก๏ธ ๋ณด์๊ณผ ๋ฆฌ์คํฌ ๊ด๋ฆฌ
์๋ํ ์์คํ ์ ํธ๋ฆฌํ์ง๋ง, ๋ณด์์ ๋ ์ ๊ฒฝ ์จ์ผ ํด. ํ ๋ฒ ์ค์ํ๋ฉด ๋ชจ๋ ์์ฐ์ ์์ ์๋ ์๊ฑฐ๋ ! ๐ฐโ ๏ธ ํ์ ๋ณด์ ์ฒดํฌ๋ฆฌ์คํธ
- โ ๊ฐ์ธํค๋ ์ ๋ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ์๊ธฐ
- โ .env ํ์ผ์ Git์ ์ปค๋ฐํ์ง ์๊ธฐ
- โ ์๋ฒ์์ ์คํํ ๋๋ ๋ฐฉํ๋ฒฝ ์ค์
- โ ์ ๊ธฐ์ ์ผ๋ก ๋ก๊ทธ ํ์ธํ๊ธฐ
- โ ๋น์ ์์ ์ธ ํธ๋์ญ์ ๊ฐ์ง ์์คํ ๊ตฌ์ถ
- โ ์ต๋ ๊ฑฐ๋ ๊ธ์ก ์ ํ ์ค์
- โ ํ ์คํธ๋ท์์ ์ถฉ๋ถํ ๊ฒ์ฆ ํ ๋ฉ์ธ๋ท ์ฌ์ฉ
class RiskManager:
def __init__(self, max_daily_loss=1000, max_single_trade=100):
"""
๋ฆฌ์คํฌ ๋งค๋์ ์ด๊ธฐํ
max_daily_loss: ์ผ์ผ ์ต๋ ์์ค ํ๋ (USD)
max_single_trade: ๋จ์ผ ๊ฑฐ๋ ์ต๋ ๊ธ์ก (USD)
"""
self.max_daily_loss = max_daily_loss
self.max_single_trade = max_single_trade
self.daily_pnl = 0 # ์ผ์ผ ์์ต
self.trade_history = []
self.emergency_stop = False
def check_trade_allowed(self, trade_amount_usd):
"""๊ฑฐ๋ ์คํ ๊ฐ๋ฅ ์ฌ๋ถ ํ์ธ"""
# ๊ธด๊ธ ์ ์ง ์ํ ํ์ธ
if self.emergency_stop:
print("๐จ ๊ธด๊ธ ์ ์ง ์ํ! ๊ฑฐ๋ ๋ถ๊ฐ")
return False
# ๋จ์ผ ๊ฑฐ๋ ๊ธ์ก ํ์ธ
if trade_amount_usd > self.max_single_trade:
print(f"โ ๏ธ ๊ฑฐ๋ ๊ธ์ก ์ด๊ณผ: {trade_amount_usd} > {self.max_single_trade}")
return False
# ์ผ์ผ ์์ค ํ๋ ํ์ธ
if self.daily_pnl < -self.max_daily_loss:
print(f"๐ ์ผ์ผ ์์ค ํ๋ ๋๋ฌ: {self.daily_pnl}")
self.emergency_stop = True
return False
return True
def record_trade(self, trade_result):
"""๊ฑฐ๋ ๊ฒฐ๊ณผ ๊ธฐ๋ก"""
self.trade_history.append(trade_result)
if 'pnl' in trade_result:
self.daily_pnl += trade_result['pnl']
# ์ด์ ํจํด ๊ฐ์ง
self._detect_anomalies()
def _detect_anomalies(self):
"""๋น์ ์์ ์ธ ๊ฑฐ๋ ํจํด ๊ฐ์ง"""
if len(self.trade_history) < 5:
return
# ์ต๊ทผ 5๊ฐ ๊ฑฐ๋ ๋ถ์
recent_trades = self.trade_history[-5:]
failed_count = sum(1 for t in recent_trades if not t.get('success'))
# ์ฐ์ ์คํจ๊ฐ 3๋ฒ ์ด์์ด๋ฉด ๊ฒฝ๊ณ
if failed_count >= 3:
print("๐จ ๊ฒฝ๊ณ : ์ฐ์ ๊ฑฐ๋ ์คํจ ๊ฐ์ง!")
print("์์คํ
์ ๊ฒ์ด ํ์ํฉ๋๋ค.")
# ์ฌ๊ธฐ์ ํ
๋ ๊ทธ๋จ ์๋ฆผ ๋ฑ์ ๋ณด๋ผ ์ ์์
def reset_daily_stats(self):
"""์ผ์ผ ํต๊ณ ์ด๊ธฐํ (๋งค์ผ ์์ ์ ์คํ)"""
print(f"\n๐ ์ผ์ผ ๋ฆฌํฌํธ")
print(f"์ด ๊ฑฐ๋: {len(self.trade_history)}๊ฑด")
print(f"์์ต: ${self.daily_pnl:.2f}")
self.daily_pnl = 0
self.trade_history = []
self.emergency_stop = False
์ฌ๋ฆฌํผ์ง ๋ณดํธ ๊ฐํ
์์ฅ ๋ณ๋์ฑ์ด ํด ๋๋ ์ฌ๋ฆฌํผ์ง๊ฐ ์์๋ณด๋ค ํจ์ฌ ํด ์ ์์ด. ๋์ ์ผ๋ก ์ฌ๋ฆฌํผ์ง๋ฅผ ์กฐ์ ํ๋ ๊ฒ ์ข์.
def calculate_dynamic_slippage(self, token_pair, base_slippage=0.5):
"""
์์ฅ ์ํฉ์ ๋ฐ๋ผ ๋์ ์ผ๋ก ์ฌ๋ฆฌํผ์ง ๊ณ์ฐ
"""
# ์ต๊ทผ ๊ฐ๊ฒฉ ๋ณ๋์ฑ ๊ณ์ฐ
price_history = self.get_recent_prices(token_pair, minutes=10)
if len(price_history) < 2:
return base_slippage
# ํ์คํธ์ฐจ๋ก ๋ณ๋์ฑ ์ธก์
import statistics
volatility = statistics.stdev(price_history)
avg_price = statistics.mean(price_history)
# ๋ณ๋์ฑ ๋น์จ
volatility_ratio = (volatility / avg_price) * 100
# ๋ณ๋์ฑ์ด ๋์ผ๋ฉด ์ฌ๋ฆฌํผ์ง ์ฆ๊ฐ
if volatility_ratio > 2.0:
adjusted_slippage = base_slippage * 2
print(f"โ ๏ธ ๋์ ๋ณ๋์ฑ ๊ฐ์ง: {volatility_ratio:.2f}%")
print(f"์ฌ๋ฆฌํผ์ง ์กฐ์ : {base_slippage}% โ {adjusted_slippage}%")
elif volatility_ratio > 1.0:
adjusted_slippage = base_slippage * 1.5
else:
adjusted_slippage = base_slippage
# ์ต๋ ์ฌ๋ฆฌํผ์ง ์ ํ
return min(adjusted_slippage, 5.0)
๐ฑ ์ค์ ๋ฐฐํฌ์ ์ด์
์ฝ๋๋ฅผ ๋ค ์์ฑํ์ผ๋ฉด ์ด์ ์ค์ ๋ก ๋ฐฐํฌํด์ ์ด์ํด์ผ์ง! ์ด๋ป๊ฒ ํ๋ฉด ์์ ์ ์ผ๋ก 24์๊ฐ ๋๋ฆด ์ ์์๊น? ๐ค1. ํด๋ผ์ฐ๋ ์๋ฒ ๋ฐฐํฌ
๊ฐ์ธ ์ปดํจํฐ๋ฅผ 24์๊ฐ ์ผ๋๋ ๊ฑด ๋นํจ์จ์ ์ด์ผ. ํด๋ผ์ฐ๋ ์๋ฒ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ํจ์ฌ ๋์!
| ์๋น์ค | ์ฅ์ | ๋จ์ | ์ถ์ฒ๋ |
|---|---|---|---|
| AWS EC2 | ์์ ์ , ๋ค์ํ ์ต์ | ์ด๋ณด์์๊ฒ ๋ณต์กํจ | โญโญโญโญ |
| DigitalOcean | ๊ฐ๋จํ UI, ์ ๋ ด | ๊ธฐ๋ฅ์ด ์ ํ์ | โญโญโญโญโญ |
| Google Cloud | ๋ฌด๋ฃ ํฌ๋ ๋ง ์ ๊ณต | ๊ฐ๊ฒฉ ์ฒด๊ณ ๋ณต์ก | โญโญโญ |
| Heroku | ๋ฐฐํฌ ๋งค์ฐ ์ฌ์ | ๋น์, ์ฑ๋ฅ ์ ํ | โญโญโญ |
2. Docker ์ปจํ ์ด๋ํ
Docker๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ค ํ๊ฒฝ์์๋ ๋์ผํ๊ฒ ์คํํ ์ ์์ด. ๋ฐฐํฌ๊ฐ ํจ์ฌ ์ฌ์์ง์ง! ๐ณ
Dockerfile ์์ฑ
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
# ์์กด์ฑ ์ค์น
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋ ๋ณต์ฌ
COPY . .
# ํ๊ฒฝ ๋ณ์ ์ค์
ENV PYTHONUNBUFFERED=1
# ์คํ
CMD ["python", "main.py"]
docker-compose.yml ์์ฑ
# docker-compose.yml
version: '3.8'
services:
swap-bot:
build: .
container_name: token-swap-automation
restart: unless-stopped
env_file:
- .env
volumes:
- ./logs:/app/logs
networks:
- swap-network
# ๋ชจ๋ํฐ๋ง ๋๊ตฌ (์ ํ์ฌํญ)
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- swap-network
networks:
swap-network:
driver: bridge
3. ๋ก๊น ๊ณผ ๋ชจ๋ํฐ๋ง
์์คํ ์ด ์ ๋๋ก ๋์ํ๋์ง ํ์ธํ๋ ค๋ฉด ๋ก๊ทธ๊ฐ ํ์์ผ! ๐
import logging
from logging.handlers import RotatingFileHandler
import json
from datetime import datetime
class SwapLogger:
def __init__(self, log_file='swap_bot.log'):
"""๋ก๊ฑฐ ์ด๊ธฐํ"""
self.logger = logging.getLogger('SwapBot')
self.logger.setLevel(logging.INFO)
# ํ์ผ ํธ๋ค๋ฌ (์ต๋ 10MB, 5๊ฐ ๋ฐฑ์
)
file_handler = RotatingFileHandler(
log_file,
maxBytes=10*1024*1024,
backupCount=5
)
# ์ฝ์ ํธ๋ค๋ฌ
console_handler = logging.StreamHandler()
# ํฌ๋งท ์ค์
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
self.logger.addHandler(console_handler)
def log_swap(self, swap_data):
"""์ค์ ์คํ ๋ก๊ทธ"""
log_entry = {
'timestamp': datetime.now().isoformat(),
'type': 'SWAP',
'data': swap_data
}
self.logger.info(json.dumps(log_entry))
def log_price(self, token_pair, price):
"""๊ฐ๊ฒฉ ๋ก๊ทธ"""
log_entry = {
'timestamp': datetime.now().isoformat(),
'type': 'PRICE',
'pair': token_pair,
'price': price
}
self.logger.info(json.dumps(log_entry))
def log_error(self, error_msg, exception=None):
"""์๋ฌ ๋ก๊ทธ"""
log_entry = {
'timestamp': datetime.now().isoformat(),
'type': 'ERROR',
'message': error_msg,
'exception': str(exception) if exception else None
}
self.logger.error(json.dumps(log_entry))
4. ํ ๋ ๊ทธ๋จ ์๋ฆผ ์ฐ๋
์ค์ํ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ์ฆ์ ์๋ฆผ์ ๋ฐ์ ์ ์์ด์ผ ํด! ํ ๋ ๊ทธ๋จ ๋ด์ ๋ง๋ค์ด๋ณด์. ๐ฒ
import requests
import asyncio
class TelegramNotifier:
def __init__(self, bot_token, chat_id):
"""
ํ
๋ ๊ทธ๋จ ์๋ฆผ ์ด๊ธฐํ
bot_token: ๋ด ํ ํฐ (@BotFather์์ ๋ฐ๊ธ)
chat_id: ์ฑํ
๋ฐฉ ID
"""
self.bot_token = bot_token
self.chat_id = chat_id
self.api_url = f"https://api.telegram.org/bot{bot_token}"
async def send_message(self, message, parse_mode='HTML'):
"""๋ฉ์์ง ์ ์ก"""
url = f"{self.api_url}/sendMessage"
data = {
'chat_id': self.chat_id,
'text': message,
'parse_mode': parse_mode
}
try:
response = requests.post(url, data=data)
return response.json()
except Exception as e:
print(f"ํ
๋ ๊ทธ๋จ ์ ์ก ์คํจ: {e}")
return None
async def notify_swap_success(self, swap_result):
"""์ค์ ์ฑ๊ณต ์๋ฆผ"""
message = f"""
๐ <b>์ค์ ์ฑ๊ณต!</b>
๐ฑ ๊ฑฐ๋ ํด์: <code>{swap_result['tx_hash'][:10]}...</code>
โฝ ๊ฐ์ค ์ฌ์ฉ: {swap_result['gas_used']}
๐ฐ ๊ธ์ก: {swap_result.get('amount', 'N/A')}
โ
๊ฑฐ๋๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์์ต๋๋ค!
"""
await self.send_message(message)
async def notify_error(self, error_msg):
"""์๋ฌ ์๋ฆผ"""
message = f"""
๐จ <b>์๋ฌ ๋ฐ์!</b>
โ {error_msg}
์์คํ
์ ํ์ธํด์ฃผ์ธ์.
"""
await self.send_message(message)
async def notify_daily_report(self, stats):
"""์ผ์ผ ๋ฆฌํฌํธ"""
message = f"""
๐ <b>์ผ์ผ ๊ฑฐ๋ ๋ฆฌํฌํธ</b>
๐ ์ด ๊ฑฐ๋: {stats['total_trades']}๊ฑด
โ
์ฑ๊ณต: {stats['successful']}๊ฑด
โ ์คํจ: {stats['failed']}๊ฑด
๐ต ์ด ์์ต: ${stats['pnl']:.2f}
๊ณ์ ์ด์ฌํ ์ผํ๊ณ ์์ด์! ๐ช
"""
await self.send_message(message)
๐ก ์ด์ ํ
์ค์ ์ด์ํ ๋๋ ์ฌ๋ฌ ๊ฐ์ ๋ด์ ๋์์ ๋๋ฆฌ๋ ๊ฒ๋ ์ข์ ์ ๋ต์ด์ผ. ํ๋๋ ๋ณด์์ ์ผ๋ก, ํ๋๋ ๊ณต๊ฒฉ์ ์ผ๋ก ์ค์ ํด์ ๋ฆฌ์คํฌ๋ฅผ ๋ถ์ฐ์ํฌ ์ ์๊ฑฐ๋ !
์ค์ ์ด์ํ ๋๋ ์ฌ๋ฌ ๊ฐ์ ๋ด์ ๋์์ ๋๋ฆฌ๋ ๊ฒ๋ ์ข์ ์ ๋ต์ด์ผ. ํ๋๋ ๋ณด์์ ์ผ๋ก, ํ๋๋ ๊ณต๊ฒฉ์ ์ผ๋ก ์ค์ ํด์ ๋ฆฌ์คํฌ๋ฅผ ๋ถ์ฐ์ํฌ ์ ์๊ฑฐ๋ !
๐ ๊ณ ๊ธ ์ ๋ต๊ณผ ์ต์ ํ
๊ธฐ๋ณธ์ ์ธ ์๋ํ๋ฅผ ๋์ด์, ๋ ๋๋ํ ์ ๋ต์ ๊ตฌํํด๋ณด์! ์ด ๋ถ๋ถ์ ์ข ์ด๋ ค์ธ ์ ์์ง๋ง, ์์ต๋ฅ ์ ํฌ๊ฒ ๋์ผ ์ ์์ด. ๐1. ์ฐจ์ต๊ฑฐ๋ (Arbitrage) ์ ๋ต
๊ฐ์ ํ ํฐ์ด ์๋ก ๋ค๋ฅธ DEX์์ ๋ค๋ฅธ ๊ฐ๊ฒฉ์ ๊ฑฐ๋๋ ๋๊ฐ ์์ด. ์ด ์ฐจ์ด๋ฅผ ์ด์ฉํด์ ์์ต์ ๋ผ ์ ์์ง!
class ArbitrageEngine:
def __init__(self, dex_list):
"""
์ฐจ์ต๊ฑฐ๋ ์์ง
dex_list: [(dex_name, router_address, router_abi), ...]
"""
self.dexes = {}
for name, address, abi in dex_list:
self.dexes[name] = {
'router': self.w3.eth.contract(address=address, abi=abi)
}
async def find_arbitrage_opportunities(self, token_a, token_b, amount=1.0):
"""
์ฐจ์ต๊ฑฐ๋ ๊ธฐํ ํ์
"""
opportunities = []
# ๋ชจ๋ DEX ํ์ด์์ ๊ฐ๊ฒฉ ์กฐํ
prices = {}
for dex_name, dex_info in self.dexes.items():
try:
price = await self.get_price_from_dex(
dex_name,
token_a,
token_b,
amount
)
prices[dex_name] = price
except Exception as e:
print(f"โ {dex_name} ๊ฐ๊ฒฉ ์กฐํ ์คํจ: {e}")
# ๊ฐ๊ฒฉ ์ฐจ์ด ๋ถ์
if len(prices) < 2:
return opportunities
dex_names = list(prices.keys())
for i in range(len(dex_names)):
for j in range(i + 1, len(dex_names)):
dex1, dex2 = dex_names[i], dex_names[j]
price1, price2 = prices[dex1], prices[dex2]
# ๊ฐ๊ฒฉ ์ฐจ์ด ๊ณ์ฐ
if price1 > price2:
profit_percent = ((price1 - price2) / price2) * 100
buy_dex, sell_dex = dex2, dex1
buy_price, sell_price = price2, price1
else:
profit_percent = ((price2 - price1) / price1) * 100
buy_dex, sell_dex = dex1, dex2
buy_price, sell_price = price1, price2
# ์์ต์ฑ ์๋ ๊ธฐํ๋ง ์ ์ฅ (๊ฐ์ค๋น ๊ณ ๋ ค)
if profit_percent > 1.0: # 1% ์ด์ ์ฐจ์ด
opportunities.append({
'buy_dex': buy_dex,
'sell_dex': sell_dex,
'buy_price': buy_price,
'sell_price': sell_price,
'profit_percent': profit_percent,
'estimated_profit': (sell_price - buy_price) * amount
})
# ์์ต๋ฅ ๋์ ์์ผ๋ก ์ ๋ ฌ
opportunities.sort(key=lambda x: x['profit_percent'], reverse=True)
return opportunities
async def execute_arbitrage(self, opportunity, amount):
"""
์ฐจ์ต๊ฑฐ๋ ์คํ
"""
print(f"\n๐ ์ฐจ์ต๊ฑฐ๋ ์คํ!")
print(f"๋งค์: {opportunity['buy_dex']} @ {opportunity['buy_price']}")
print(f"๋งค๋: {opportunity['sell_dex']} @ {opportunity['sell_price']}")
print(f"์์ ์์ต: {opportunity['profit_percent']:.2f}%")
# 1๋จ๊ณ: ์ ๋ ดํ DEX์์ ๋งค์
buy_result = await self.swap_on_dex(
opportunity['buy_dex'],
token_a,
token_b,
amount
)
if not buy_result['success']:
print("โ ๋งค์ ์คํจ!")
return {'success': False}
# 2๋จ๊ณ: ๋น์ผ DEX์์ ๋งค๋
sell_result = await self.swap_on_dex(
opportunity['sell_dex'],
token_b,
token_a,
buy_result['amount_out']
)
if not sell_result['success']:
print("โ ๏ธ ๋งค๋ ์คํจ! ํ ํฐ์ด ๋ฌถ์์ต๋๋ค.")
return {'success': False, 'stuck_tokens': buy_result['amount_out']}
# ์ค์ ์์ต ๊ณ์ฐ
actual_profit = sell_result['amount_out'] - amount
print(f"โ
์ฐจ์ต๊ฑฐ๋ ์๋ฃ! ์ค์ ์์ต: {actual_profit:.6f}")
return {
'success': True,
'profit': actual_profit,
'buy_tx': buy_result['tx_hash'],
'sell_tx': sell_result['tx_hash']
}
2. ํ๋์๋ก (Flash Loan) ํ์ฉ
ํ๋์๋ก ์ ๋ด๋ณด ์์ด ๊ฑฐ์ก์ ๋น๋ ค์ ํ ํธ๋์ญ์ ์์์ ๊ฐ๋ ๊ธฐ๋ฒ์ด์ผ. ์ฐจ์ต๊ฑฐ๋์ ํ์ฉํ๋ฉด ์๋ณธ ์์ด๋ ํฐ ์์ต์ ๋ผ ์ ์์ด! ๐ฐ
โก ํ๋์๋ก ์๋ ์๋ฆฌ
1. ํ๋กํ ์ฝ์์ ๊ฑฐ์ก ๋์ถ (์: 100 ETH)
2. ๋์ถ๊ธ์ผ๋ก ์ฐจ์ต๊ฑฐ๋ ์คํ
3. ์์ต๊ธ ํฌํจํด์ ์ฆ์ ์ํ
4. ๋จ์ ์์ต์ ๋ด๊ฐ ๊ฐ์ ธ๊ฐ
๋ชจ๋ ๊ณผ์ ์ด ํ๋์ ํธ๋์ญ์ ์์์ ์ผ์ด๋๊ธฐ ๋๋ฌธ์, ์ํํ์ง ๋ชปํ๋ฉด ์ ์ฒด ํธ๋์ญ์ ์ด ์ทจ์๋ผ. ๊ทธ๋์ ๋ฆฌ์คํฌ๊ฐ ์์ด!
1. ํ๋กํ ์ฝ์์ ๊ฑฐ์ก ๋์ถ (์: 100 ETH)
2. ๋์ถ๊ธ์ผ๋ก ์ฐจ์ต๊ฑฐ๋ ์คํ
3. ์์ต๊ธ ํฌํจํด์ ์ฆ์ ์ํ
4. ๋จ์ ์์ต์ ๋ด๊ฐ ๊ฐ์ ธ๊ฐ
๋ชจ๋ ๊ณผ์ ์ด ํ๋์ ํธ๋์ญ์ ์์์ ์ผ์ด๋๊ธฐ ๋๋ฌธ์, ์ํํ์ง ๋ชปํ๋ฉด ์ ์ฒด ํธ๋์ญ์ ์ด ์ทจ์๋ผ. ๊ทธ๋์ ๋ฆฌ์คํฌ๊ฐ ์์ด!
class FlashLoanArbitrage:
def __init__(self, aave_pool_address, aave_pool_abi):
"""
ํ๋์๋ก ์ฐจ์ต๊ฑฐ๋ ์์ง
Aave ํ๋กํ ์ฝ ์ฌ์ฉ
"""
self.aave_pool = self.w3.eth.contract(
address=aave_pool_address,
abi=aave_pool_abi
)
def create_flashloan_contract(self):
"""
ํ๋์๋ก ์คํ ์ค๋งํธ ์ปจํธ๋ํธ ๋ฐฐํฌ
(Solidity ์ฝ๋๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํด์ผ ํจ)
"""
# ์ค์ ๋ก๋ Solidity๋ก ์์ฑํ ์ปจํธ๋ํธ๋ฅผ ๋ฐฐํฌํด์ผ ํด
# ์ฌ๊ธฐ์๋ ๊ฐ๋
๋ง ์ค๋ช
flashloan_contract_code = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
contract FlashLoanArbitrage is FlashLoanSimpleReceiverBase {
address payable owner;
constructor(address _addressProvider)
FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider))
{
owner = payable(msg.sender);
}
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external override returns (bool) {
// 1. ์ฐจ์ต๊ฑฐ๋ ๋ก์ง ์คํ
// 2. ์์ต ํ์ธ
// 3. ๋์ถ๊ธ + ์์๋ฃ ์ํ
uint256 amountOwed = amount + premium;
IERC20(asset).approve(address(POOL), amountOwed);
return true;
}
function requestFlashLoan(address _token, uint256 _amount) public {
address receiverAddress = address(this);
address asset = _token;
uint256 amount = _amount;
bytes memory params = "";
uint16 referralCode = 0;
POOL.flashLoanSimple(
receiverAddress,
asset,
amount,
params,
referralCode
);
}
}
"""
print("๐ ํ๋์๋ก ์ปจํธ๋ํธ ๋ฐฐํฌ ํ์")
print("Remix๋ Hardhat์ ์ฌ์ฉํด์ ๋ฐฐํฌํ์ธ์!")
3. MEV(Maximal Extractable Value) ์ ๋ต
MEV๋ ๋ธ๋ก ์์ฑ ๊ณผ์ ์์ ํธ๋์ญ์ ์์๋ฅผ ์กฐ์ํด์ ์ด์ต์ ์ป๋ ๊ณ ๊ธ ๊ธฐ๋ฒ์ด์ผ. ํ๋ก ํธ๋ฌ๋, ๋ฐฑ๋ฌ๋, ์๋์์น ๊ณต๊ฒฉ ๋ฑ์ด ์์ด.
โ ๏ธ ์ค๋ฆฌ์ ๊ณ ๋ ค์ฌํญ
MEV ์ ๋ต ์ค ์ผ๋ถ๋ ๋ค๋ฅธ ์ฌ์ฉ์์๊ฒ ํผํด๋ฅผ ์ค ์ ์์ด. ํนํ ์๋์์น ๊ณต๊ฒฉ์ ๋ ผ๋์ ์ฌ์ง๊ฐ ์์ผ๋, ์ฌ์ฉํ ๋๋ ์ค๋ฆฌ์ ์ธก๋ฉด์ ๊ผญ ๊ณ ๋ คํด์ผ ํด!
MEV ์ ๋ต ์ค ์ผ๋ถ๋ ๋ค๋ฅธ ์ฌ์ฉ์์๊ฒ ํผํด๋ฅผ ์ค ์ ์์ด. ํนํ ์๋์์น ๊ณต๊ฒฉ์ ๋ ผ๋์ ์ฌ์ง๊ฐ ์์ผ๋, ์ฌ์ฉํ ๋๋ ์ค๋ฆฌ์ ์ธก๋ฉด์ ๊ผญ ๊ณ ๋ คํด์ผ ํด!
class MEVStrategy:
def __init__(self, flashbots_relay_url):
"""
MEV ์ ๋ต ์์ง
Flashbots๋ฅผ ํตํด ํ๋ผ์ด๋น ํธ๋์ญ์
์ ์ก
"""
self.flashbots_url = flashbots_relay_url
async def detect_profitable_tx(self, pending_tx):
"""
์์ต์ฑ ์๋ ํธ๋์ญ์
๊ฐ์ง
"""
# ๋๊ธฐ ์ค์ธ ํธ๋์ญ์
๋ถ์
if self.is_large_swap(pending_tx):
# ํฐ ์ค์ ํธ๋์ญ์
๋ฐ๊ฒฌ
opportunity = await self.calculate_sandwich_opportunity(pending_tx)
if opportunity['profitable']:
return opportunity
return None
def is_large_swap(self, tx):
"""ํฐ ์ค์ ํธ๋์ญ์
์ธ์ง ํ์ธ"""
# ํธ๋์ญ์
๋ฐ์ดํฐ ํ์ฑ
# Uniswap swapExactTokensForTokens ํจ์ ํธ์ถ์ธ์ง ํ์ธ
# ๊ธ์ก์ด ์ถฉ๋ถํ ํฐ์ง ํ์ธ
pass
async def send_bundle_to_flashbots(self, transactions):
"""
Flashbots๋ฅผ ํตํด ํธ๋์ญ์
๋ฒ๋ค ์ ์ก
"""
bundle = {
'txs': transactions,
'blockNumber': self.w3.eth.block_number + 1,
'minTimestamp': 0,
'maxTimestamp': 0
}
# Flashbots API ํธ์ถ
# ์ค์ ๊ตฌํ์ Flashbots ๋ฌธ์ ์ฐธ๊ณ
print("๐ฆ Flashbots ๋ฒ๋ค ์ ์ก")
๐ ๋๋ฒ๊น ๊ณผ ๋ฌธ์ ํด๊ฒฐ
๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ๋น์ฐํ ๋ฌธ์ ๊ฐ ์๊ฒจ. ์์ฃผ ๋ฐ์ํ๋ ์ด์๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํด๋ดค์ด! ๐ ๏ธ
๐ ์์ฃผ ๋ฐ์ํ๋ ์๋ฌ๋ค
1. "Insufficient funds for gas"
โ ์ง๊ฐ์ ETH๊ฐ ๋ถ์กฑํด. ๊ฐ์ค๋น๋ฅผ ๋ผ ์ ์์ ๋งํผ ETH๋ฅผ ์ถฉ์ ํด์ผ ํด.
2. "Transaction underpriced"
โ ๊ฐ์ค ๊ฐ๊ฒฉ์ด ๋๋ฌด ๋ฎ์. ํ์ฌ ๋คํธ์ํฌ ์ํฉ์ ๋ง๊ฒ ๊ฐ์ค ๊ฐ๊ฒฉ์ ๋์ฌ์ผ ํด.
3. "Execution reverted"
โ ์ค๋งํธ ์ปจํธ๋ํธ ์คํ ์ค ์ค๋ฅ ๋ฐ์. ์ฌ๋ฆฌํผ์ง ์ค์ ์ด ๋๋ฌด ๋ฎ๊ฑฐ๋, approve๊ฐ ์ ๋์ ์ ์์ด.
4. "Nonce too low"
โ ์ด๋ฏธ ์ฌ์ฉ๋ nonce๋ฅผ ์ฌ์ฌ์ฉํ๋ ค๊ณ ํจ. ์ต์ nonce๋ฅผ ๋ค์ ์กฐํํด์ผ ํด.
5. "Replacement transaction underpriced"
โ ๊ฐ์ nonce์ ํธ๋์ญ์ ์ ๊ต์ฒดํ๋ ค๋ฉด ๊ฐ์ค ๊ฐ๊ฒฉ์ ์ต์ 10% ์ด์ ๋์ฌ์ผ ํด.
๋๋ฒ๊น
๋๊ตฌ ํ์ฉ1. "Insufficient funds for gas"
โ ์ง๊ฐ์ ETH๊ฐ ๋ถ์กฑํด. ๊ฐ์ค๋น๋ฅผ ๋ผ ์ ์์ ๋งํผ ETH๋ฅผ ์ถฉ์ ํด์ผ ํด.
2. "Transaction underpriced"
โ ๊ฐ์ค ๊ฐ๊ฒฉ์ด ๋๋ฌด ๋ฎ์. ํ์ฌ ๋คํธ์ํฌ ์ํฉ์ ๋ง๊ฒ ๊ฐ์ค ๊ฐ๊ฒฉ์ ๋์ฌ์ผ ํด.
3. "Execution reverted"
โ ์ค๋งํธ ์ปจํธ๋ํธ ์คํ ์ค ์ค๋ฅ ๋ฐ์. ์ฌ๋ฆฌํผ์ง ์ค์ ์ด ๋๋ฌด ๋ฎ๊ฑฐ๋, approve๊ฐ ์ ๋์ ์ ์์ด.
4. "Nonce too low"
โ ์ด๋ฏธ ์ฌ์ฉ๋ nonce๋ฅผ ์ฌ์ฌ์ฉํ๋ ค๊ณ ํจ. ์ต์ nonce๋ฅผ ๋ค์ ์กฐํํด์ผ ํด.
5. "Replacement transaction underpriced"
โ ๊ฐ์ nonce์ ํธ๋์ญ์ ์ ๊ต์ฒดํ๋ ค๋ฉด ๊ฐ์ค ๊ฐ๊ฒฉ์ ์ต์ 10% ์ด์ ๋์ฌ์ผ ํด.
class DebugHelper:
def __init__(self, w3):
self.w3 = w3
def analyze_failed_transaction(self, tx_hash):
"""
์คํจํ ํธ๋์ญ์
๋ถ์
"""
print(f"\n๐ ํธ๋์ญ์
๋ถ์: {tx_hash}")
# ํธ๋์ญ์
์ ๋ณด ์กฐํ
tx = self.w3.eth.get_transaction(tx_hash)
receipt = self.w3.eth.get_transaction_receipt(tx_hash)
print(f"\n๐ ๊ธฐ๋ณธ ์ ๋ณด:")
print(f" From: {tx['from']}")
print(f" To: {tx['to']}")
print(f" Value: {self.w3.from_wei(tx['value'], 'ether')} ETH")
print(f" Gas Price: {self.w3.from_wei(tx['gasPrice'], 'gwei')} Gwei")
print(f" Gas Limit: {tx['gas']}")
print(f"\n๐ ์คํ ๊ฒฐ๊ณผ:")
print(f" Status: {'์ฑ๊ณต' if receipt['status'] == 1 else '์คํจ'}")
print(f" Gas Used: {receipt['gasUsed']}")
print(f" Block: {receipt['blockNumber']}")
# ์คํจ ์์ธ ์ถ์
if receipt['status'] == 0:
print(f"\nโ ์คํจ ์์ธ ๋ถ์:")
# ๊ฐ์ค ๋ถ์กฑ
if receipt['gasUsed'] == tx['gas']:
print(" โข ๊ฐ์ค ๋ถ์กฑ (Out of Gas)")
print(" โ ๊ฐ์ค ๋ฆฌ๋ฐ์ ๋์ฌ๋ณด์ธ์")
# ํธ๋์ญ์
์๋ฎฌ๋ ์ด์
์ผ๋ก ์์ธ ์๋ฌ ํ์ธ
try:
self.w3.eth.call(tx, receipt['blockNumber'] - 1)
except Exception as e:
print(f" โข ์๋ฌ ๋ฉ์์ง: {str(e)}")
def simulate_transaction(self, tx_params):
"""
ํธ๋์ญ์
์๋ฎฌ๋ ์ด์
(์ค์ ์ ์ก ์ ํ
์คํธ)
"""
print("\n๐งช ํธ๋์ญ์
์๋ฎฌ๋ ์ด์
...")
try:
# eth_call๋ก ์๋ฎฌ๋ ์ด์
result = self.w3.eth.call(tx_params)
print("โ
์๋ฎฌ๋ ์ด์
์ฑ๊ณต!")
print(f"์์ ๊ฒฐ๊ณผ: {result.hex()}")
# ๊ฐ์ค ์์ธก
estimated_gas = self.w3.eth.estimate_gas(tx_params)
print(f"์์ ๊ฐ์ค: {estimated_gas}")
return True
except Exception as e:
print(f"โ ์๋ฎฌ๋ ์ด์
์คํจ: {str(e)}")
return False
def check_token_approval(self, token_address, owner, spender):
"""ํ ํฐ ์น์ธ ์ํ ํ์ธ"""
token = self.w3.eth.contract(
address=token_address,
abi=ERC20_ABI
)
allowance = token.functions.allowance(owner, spender).call()
decimals = token.functions.decimals().call()
print(f"\n๐ ์น์ธ ์ํ:")
print(f" Owner: {owner}")
print(f" Spender: {spender}")
print(f" Allowance: {allowance / (10 ** decimals)}")
return allowance
๐ ์ถ๊ฐ ํ์ต ๋ฆฌ์์ค
์ฌ๊ธฐ๊น์ง ๋ฐ๋ผ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐ ํ์ง๋ง ๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ์ ๊ณ์ ๋ฐฐ์์ผ ํ๋ ๋ถ์ผ์ผ. ๋ ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ๋ฆฌ์์ค๋ค์ ์ถ์ฒํด:
๐ ํ์ต ์๋ฃ
๊ณต์ ๋ฌธ์
โข Web3.py ๊ณต์ ๋ฌธ์: https://web3py.readthedocs.io/
โข Uniswap V2 ๋ฌธ์: https://docs.uniswap.org/protocol/V2/
โข Ethereum ๊ฐ๋ฐ์ ๋ฌธ์: https://ethereum.org/developers
์ปค๋ฎค๋ํฐ
โข Ethereum Stack Exchange: ๊ธฐ์ ์ง๋ฌธ
โข Reddit r/ethdev: ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ
โข Discord ์๋ฒ๋ค: ์ค์๊ฐ ์ํต
์ค์ต ํ๋ซํผ
โข Remix IDE: ๋ธ๋ผ์ฐ์ ์์ Solidity ๊ฐ๋ฐ
โข Hardhat: ํ๋กํ์ ๋ ๊ฐ๋ฐ ํ๊ฒฝ
โข Tenderly: ํธ๋์ญ์ ๋๋ฒ๊น ๋๊ตฌ
๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ ์ค๋ ฅ์ ๋ ํค์ฐ๊ณ ์ถ๋ค๋ฉด, ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ๋ค์ ๋ฉํ ๋ง์ ๋ฐ์๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ์ค์ ๊ฒฝํ์ด ์๋ ๊ฐ๋ฐ์๋ค์๊ฒ ์ง์ ๋ฐฐ์ธ ์ ์๊ฑฐ๋ ! ๐ฏ๊ณต์ ๋ฌธ์
โข Web3.py ๊ณต์ ๋ฌธ์: https://web3py.readthedocs.io/
โข Uniswap V2 ๋ฌธ์: https://docs.uniswap.org/protocol/V2/
โข Ethereum ๊ฐ๋ฐ์ ๋ฌธ์: https://ethereum.org/developers
์ปค๋ฎค๋ํฐ
โข Ethereum Stack Exchange: ๊ธฐ์ ์ง๋ฌธ
โข Reddit r/ethdev: ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ
โข Discord ์๋ฒ๋ค: ์ค์๊ฐ ์ํต
์ค์ต ํ๋ซํผ
โข Remix IDE: ๋ธ๋ผ์ฐ์ ์์ Solidity ๊ฐ๋ฐ
โข Hardhat: ํ๋กํ์ ๋ ๊ฐ๋ฐ ํ๊ฒฝ
โข Tenderly: ํธ๋์ญ์ ๋๋ฒ๊น ๋๊ตฌ
์ค์ ํ๋ก์ ํธ ์์ด๋์ด
๋ฐฐ์ด ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์ด๋ฐ ํ๋ก์ ํธ๋ค์ ๋ง๋ค์ด๋ณผ ์ ์์ด:
1. ๋ฉํฐ์ฒด์ธ ์ค์ ๋ด ๐
์ด๋๋ฆฌ์๋ฟ๋ง ์๋๋ผ BSC, Polygon, Arbitrum ๋ฑ ์ฌ๋ฌ ์ฒด์ธ์์ ๋์์ ์๋ํ๋ ๋ด
2. ์ ๋์ฑ ๋ง์ด๋ ์๋ํ ๐ง
์ต์ ์ ์ ๋์ฑ ํ์ ์ฐพ์์ ์๋์ผ๋ก ์์นํ๊ณ ์ํํ๋ ์์คํ
3. NFT ์ค๋์ดํ ๋ด ๐จ
์๋ก ๋ฏผํ ๋๋ NFT๋ฅผ ๋น ๋ฅด๊ฒ ๊ตฌ๋งคํ๋ ๋ด
4. ๊ฐ์ค ๊ฐ๊ฒฉ ์ต์ ํ ์๋น์ค โฝ
๊ฐ์ค ๊ฐ๊ฒฉ์ด ์ ๋ ดํ ๋๋ฅผ ์์ธกํด์ ํธ๋์ญ์ ์ ์คํํ๋ ์๋น์ค
5. ํฌํธํด๋ฆฌ์ค ๋ฆฌ๋ฐธ๋ฐ์ฑ ๋ด ๐
์ค์ ํ ๋น์จ์ ๋ง์ถฐ ์๋์ผ๋ก ์์ฐ์ ์ฌ์กฐ์ ํ๋ ๋ด
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์, ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด! ๐์ฐ๋ฆฌ๋ ํ์ด์ฌ์ผ๋ก ํ ํฐ ์ค์ ์๋ํ ์์คํ ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ฒ์๋ถํฐ ๋๊น์ง ์ดํด๋ดค์ด. ์ง๊ฐ ๊ด๋ฆฌ๋ถํฐ ์์ํด์ ์ค์ ์์ง, ๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง, ๋ฆฌ์คํฌ ๊ด๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ๊ณ ๊ธ ์ ๋ต๊น์ง ๋ค๋ค์ง.
๐ก ํต์ฌ ํฌ์ธํธ ์ ๋ฆฌ
- โ ๋ณด์์ด ์ต์ฐ์ : ๊ฐ์ธํค ๊ด๋ฆฌ๋ฅผ ์ฒ ์ ํ ํ๊ณ , ํ ์คํธ๋ท์์ ์ถฉ๋ถํ ๊ฒ์ฆ
- โ ๋ฆฌ์คํฌ ๊ด๋ฆฌ: ์ต๋ ์์ค ํ๋๋ฅผ ์ค์ ํ๊ณ , ์ด์ ํจํด์ ๊ฐ์งํ๋ ์์คํ ๊ตฌ์ถ
- โ ์ฑ๋ฅ ์ต์ ํ: ๊ฐ์ค๋น๋ฅผ ์ ์ฝํ๊ณ , ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ก ํจ์จ์ฑ ํฅ์
- โ ์ง์์ ์ธ ๋ชจ๋ํฐ๋ง: ๋ก๊ทธ๋ฅผ ๊ผผ๊ผผํ ํ์ธํ๊ณ , ์๋ฆผ ์์คํ ํ์ฉ
- โ ๊ณ์ ํ์ตํ๊ธฐ: ๋ธ๋ก์ฒด์ธ ๊ธฐ์ ์ ๋น ๋ฅด๊ฒ ๋ฐ์ ํ๋ ์ต์ ํธ๋ ๋๋ฅผ ๋ฐ๋ผ๊ฐ์ผ ํด
๋ธ๋ก์ฒด์ธ ๊ฐ๋ฐ์ ์ด๋ ต์ง๋ง, ์ ๋ง ์ฌ๋ฏธ์๊ณ ๋ณด๋์ฐฌ ๋ถ์ผ์ผ. ์๋ํ ์์คํ ์ ๋ง๋ค๋ฉด์ ํ๋ก๊ทธ๋๋ฐ ์ค๋ ฅ๋ ๋๊ณ , ๋ธ๋ก์ฒด์ธ์ ๋ํ ์ดํด๋ ๊น์ด์ง๊ฑฐ๋ . ๐ช
์ฒ์์๋ ์์ ๊ธ์ก์ผ๋ก ์์ํด์, ์์คํ ์ด ์์ ์ ์ผ๋ก ๋์ํ๋ ๊ฑธ ํ์ธํ ๋ค์์ ์ ์ฐจ ๊ท๋ชจ๋ฅผ ํค์๊ฐ๋ ๊ฒ ์ข์. ๊ทธ๋ฆฌ๊ณ ์ ๋ ํฌ์ํ ์ ์๋ ๋์ผ๋ก ์คํํ์ง ๋ง! ๐
ํน์ ๋งํ๋ ๋ถ๋ถ์ด ์๊ฑฐ๋, ๋ ๊น์ด ์๋ ๋ด์ฉ์ ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํ๊ฑฐ๋ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ํจ๊ป ์ฑ์ฅํ๋ ๊ฒ ์ค์ํ๋๊น!
์, ์ด์ ๋น์ ๋ ๋ธ๋ก์ฒด์ธ ์๋ํ ์ ๋ฌธ๊ฐ๊ฐ ๋ ์ค๋น๊ฐ ๋์ด! ๐
ํ์ด์ ๋น์ด, ๊ทธ๋ฆฌ๊ณ ํดํผ ์ฝ๋ฉ! ๐
๐ ๋น์ ์ ๋ธ๋ก์ฒด์ธ ์ฌ์ ์ ์์ํฉ๋๋ค!
Keep Building, Keep Learning! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
๋๊ธ์ ๋ถ๋ฌ์ค๋ ์ค์
๋๋ค.
๋๊ธ์ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค. ์ฐ๊ฒฐ ์ํ๋ฅผ ํ์ธํ๊ณ ๋ค์ ์๋ํด ์ฃผ์ธ์.
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.