여러 토큰들이나 풀을 모니터링 할 때 일일이 코드를 만들어주는 것은 시간도 많이 들고 비효율적이다. 따라서 토큰같은 경우 ERC20 Class를 만들어서 토큰 주소만 입력해주면 봇을 만들 수 있도록 짜놓는 것이 좋다.
class Erc20Token:
"""
Represents an ERC-20 token. Must be initialized with an address.
Brownie will load the Contract object from the supplied ABI if given,
then attempt to load the verified ABI from the block explorer.
If both methods fail, it will attempt to use a supplied ERC-20 ABI
"""
def __init__(
self,
address: str,
user: network.account.LocalAccount,
abi: list = None,
oracle_address: str = None,
) -> None:
self.address = address
self._user = user
if abi:
try:
self._contract = Contract.from_abi(
name="", address=self.address, abi=abi
)
except:
raise
else:
try:
self._contract = Contract.from_explorer(self.address)
except:
self._contract = Contract.from_abi(
name="", address=self.address, abi=ERC20
)
self.name = self._contract.name.call()
self.symbol = self._contract.symbol.call()
self.decimals = self._contract.decimals.call()
self.balance = self._contract.balanceOf.call(self._user)
self.normalized_balance = self.balance / (10 ** self.decimals)
if oracle_address:
self._price_oracle = ChainlinkPriceContract(address=oracle_address)
self.price = self._price_oracle.price
print(f"• {self.symbol} ({self.name})")
def _str__(self):
return self.symbol
def get_approval(self, external_address: str):
return self._contract.allowance.call(self._user.address, external_address)
def set_approval(self, external_address: str, value: int):
if value == "unlimited":
value = 2 ** 256 - 1
try:
self._contract.approve(
external_address,
value,
{"from": self._user.address},
)
except Exception as e:
print(f"Exception in token_approve: {e}")
raise
def update_balance(self):
self.balance = self._contract.balanceOf.call(self._user)
self.normalized_balance = self.balance / (10 ** self.decimals)
def update_price(self):
self.price = self._price_oracle.update_price()
__init__
은 constructor와 같은 개념이다. 첫 파라미터로 self를 넣어주고 나머지 주소와 abi 등을 받아서 만들어진다. 이 때 -> None
은 리턴 형태를 말한다. 나머지 메소드 부분에서도 self
를 인자로 받는 것을 알 수 있는데 이는 파이썬 메소드의 특징으로 클래스로부터 만들어진 객체를 의미한다.
파이썬의 클래스와 메소드 부분의 자세한 설명은 점프 투 파이썬에 잘 나와있다.
weth = Erc20Token(
address='0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
user=testAccount,
abi=ERC20
)
위와 같이 파라미터에 적절하게 값을 매칭해서 넣어주면 된다.
Factory는 DEX에서 pair pool을 만들어주는 역할을 한다. 따라서 우리가 어떤 토큰으로 pool을 만들거나 유동성을 공급할 때 Factory 컨트랙트를 사용한다. 우리는 이걸 역이용해서 아비트라지를 시도 할 토큰 쌍을 검색하거나 불러올 때 사용할 것이다.
각 DEX별로 Factory의 컨트랙트는 다르다. Trader Joe의 경우, 0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10
이다. 현재 Trader Joe는 Liquidity Book으로 프로젝트를 업그레이드 하고 있기 때문에 나중에 컨트랙트가 바뀔 수 있으니 주의하자.
$ brownie console --network avax-main
>>> tj_factory = Contract.from_explorer('0x9Ad6C38BE94206cA50bb0d90783181662f0Cfa10')
>>> tj_factory.allPairsLength()
17287
현재 pair의 개수는 17287개다.
>>> tj_factory.getPair('0x3Ee97d514BBef95a2f110e6B9b73824719030f7a','0xCE1bFFBD5374Dac86a2893119683F4911a2F7814')
'0x033C3Fc1fC13F803A233D262e24d1ec3fd4EFB48'
getPair
로 sSPELL-SPELL 풀의 컨트랙트 주소를 알 수 있다. 인자로 넘겨주는 토큰 컨트랙트의 주수는 앞 뒤가 바뀌어도 상관없다.
>>> tj_factory.getPair('0x3Ee97d514BBef95a2f110e6B9b73824719030f7a','0x1db749847c4abb991d8b6032102383e6bfd9b1c7')
'0x0000000000000000000000000000000000000000'
그런데 가끔 뜬금없는 토큰 주소를 넣으면 pair가 없을 수 있다. 따라서 이런 경우를 예외처리 해줘야 한다.
for id in range(tj_factory.allPairsLength()):
_ = Contract.from_explorer(tj_factory.allPairs(id), silent=True)
print(f"POOL {id}: {_.token0()} - {_.token1()}\n")
요런 식으로 풀의 모든 데이터를 가져올 수도 있다.