Skip to content

Commit

Permalink
migrate to new API WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
callmephilip committed Feb 10, 2025
1 parent 0f6fbf9 commit 8fb3211
Show file tree
Hide file tree
Showing 17 changed files with 833 additions and 1,166 deletions.
12 changes: 2 additions & 10 deletions src/abi.ipynb

Large diffs are not rendered by default.

395 changes: 395 additions & 0 deletions src/chains.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,395 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# ⛓ Chain "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| default_exp chains"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"from nbdev.showdoc import *"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"import asyncio, web3, os\n",
"from functools import wraps, reduce\n",
"from typing import List, TypeVar, Callable, Optional, Tuple\n",
"from fastcore.utils import patch\n",
"from web3 import AsyncWeb3, AsyncHTTPProvider, Account\n",
"from web3.eth.async_eth import AsyncContract\n",
"from sugar.config import ChainSettings, make_op_chain_settings, make_base_chain_settings\n",
"from sugar.helpers import normalize_address, MAX_UINT256, float_to_uint256, apply_slippage, get_future_timestamp\n",
"from sugar.abi import sugar, price_oracle, router\n",
"from sugar.token import Token\n",
"from sugar.pool import LiquidityPool\n",
"from sugar.price import Price\n",
"from sugar.deposit import Deposit\n",
"from sugar.helpers import ADDRESS_ZERO, chunk"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Chain implementation "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"T = TypeVar('T')\n",
"\n",
"def require_context(f: Callable[..., T]) -> Callable[..., T]:\n",
" @wraps(f)\n",
" async def wrapper(self: 'Chain', *args, **kwargs) -> T:\n",
" if not self._in_context: raise RuntimeError(\"Chain methods can only be accessed within 'async with' block\")\n",
" return await f(self, *args, **kwargs)\n",
" return wrapper\n",
"\n",
"class Chain:\n",
" account: Optional[Account]\n",
" web3: AsyncWeb3\n",
" sugar: AsyncContract\n",
" router: AsyncContract\n",
"\n",
" def __init__(self, settings: ChainSettings):\n",
" self.settings, self._in_context = settings, False\n",
"\n",
" @property\n",
" def account(self) -> Account: return self.web3.eth.account.from_key(os.getenv(\"SUGAR_PK\"))\n",
"\n",
" async def __aenter__(self):\n",
" \"\"\"Async context manager entry\"\"\"\n",
" self._in_context = True\n",
" self.web3 = AsyncWeb3(AsyncHTTPProvider(self.settings.rpc_uri))\n",
" self.sugar = self.web3.eth.contract(address=self.settings.sugar_contract_addr, abi=sugar)\n",
" self.prices = self.web3.eth.contract(address=self.settings.price_oracle_contract_addr, abi=price_oracle)\n",
" self.router = self.web3.eth.contract(address=self.settings.router_contract_addr, abi=router)\n",
" return self\n",
"\n",
" async def __aexit__(self, exc_type, exc_val, exc_tb):\n",
" \"\"\"Async context manager exit\"\"\"\n",
" self._in_context = False\n",
" await self.web3.provider.disconnect()\n",
" return None"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Get tokens"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"@patch\n",
"@require_context\n",
"async def get_all_tokens(self: Chain, listed_only: bool = True) -> List[Token]:\n",
" tokens = list(map(lambda t: Token.from_tuple(t), await self.sugar.functions.tokens(self.settings.pagination_limit, 0, ADDRESS_ZERO, []).call()))\n",
" return list(filter(lambda t: t.listed, tokens)) if listed_only else tokens\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Get pools"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"@patch\n",
"@require_context\n",
"async def get_pools(self: Chain) -> List[LiquidityPool]:\n",
" pools, offset, limit = [], 0, self.settings.pool_page_size\n",
" tokens = await self.get_all_tokens()\n",
" prices = await self.get_prices(tokens)\n",
" tokens = {t.token_address: t for t in tokens}\n",
" prices = {price.token.token_address: price for price in prices}\n",
"\n",
" while True:\n",
" pools_batch = await self.sugar.functions.all(limit, offset).call()\n",
" pools += pools_batch\n",
" if len(pools_batch) < limit: break\n",
" else: offset += limit\n",
"\n",
" return list(filter(lambda p: p is not None, map(lambda p: LiquidityPool.from_tuple(p, tokens), pools)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Get prices"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"# @cache_in_seconds(ORACLE_PRICES_CACHE_MINUTES * 60)\n",
"@patch\n",
"async def _get_prices(self: Chain, tokens: Tuple[Token]):\n",
" prices = await self.prices.functions.getManyRatesWithCustomConnectors(\n",
" list(map(lambda t: t.token_address, tokens)),\n",
" self.settings.stable_token_addr,\n",
" False, # use wrappers\n",
" self.settings.connector_tokens_addrs,\n",
" 10 # threshold_filer\n",
" ).call()\n",
" # 6 decimals for USDC\n",
" return [Price(token=tokens[cnt], price=price / 10**6) for cnt, price in enumerate(prices)]\n",
"\n",
"@patch\n",
"@require_context\n",
"async def get_prices(self: Chain, tokens: List[Token]) -> List[Price]:\n",
" \"\"\"Get prices for tokens in target stable token\"\"\"\n",
" # filter out stable token from tokens list so getManyRatesWithCustomConnectors so does not freak out\n",
" tokens_without_stable = list(filter(lambda t: t.token_address != self.settings.stable_token_addr, tokens))\n",
" stable = next(filter(lambda t: t.token_address == self.settings.stable_token_addr, tokens), None)\n",
"\n",
" batches = await asyncio.gather(\n",
" *map(\n",
" # XX: lists are not cacheable, convert them to tuples so lru cache is happy\n",
" lambda ts: self._get_prices(tuple(ts)),\n",
" list(chunk(tokens_without_stable, self.settings.price_batch_size)),\n",
" )\n",
" )\n",
" return ([Price(token=stable, price=1)] if stable else []) + reduce(lambda l1, l2: l1 + l2, batches, [])\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Sign and send transaction"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"@patch\n",
"@require_context\n",
"async def sign_and_send_tx(self: Chain, tx, wait: bool = True):\n",
" spender = self.account.address\n",
" tx = await tx.build_transaction({ 'from': spender, 'nonce': await self.web3.eth.get_transaction_count(spender) })\n",
" signed_tx = self.account.sign_transaction(tx)\n",
" tx_hash = await self.web3.eth.send_raw_transaction(signed_tx.raw_transaction)\n",
" return await self.web3.eth.wait_for_transaction_receipt(tx_hash) if wait else tx_hash"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Set and check token allowance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"@patch\n",
"@require_context\n",
"async def set_token_allowance(self: Chain, token: Token, addr: str, amount: int):\n",
" ERC20_ABI = [{\n",
" \"name\": \"approve\",\n",
" \"type\": \"function\",\n",
" \"constant\": False,\n",
" \"inputs\": [{\"name\": \"spender\", \"type\": \"address\"}, {\"name\": \"amount\", \"type\": \"uint256\"}],\n",
" \"outputs\": [{\"name\": \"\", \"type\": \"bool\"}]\n",
" }]\n",
" token_contract = self.web3.eth.contract(address=token.token_address, abi=ERC20_ABI)\n",
" return await self.sign_and_send_tx(token_contract.functions.approve(addr, amount))\n",
"\n",
"@patch\n",
"@require_context\n",
"async def check_token_allowance(self: Chain, token: Token, addr: str) -> int:\n",
" ERC20_ABI = [{\n",
" \"name\": \"allowance\",\n",
" \"type\": \"function\",\n",
" \"constant\": True,\n",
" \"inputs\": [{\"name\": \"owner\", \"type\": \"address\"}, {\"name\": \"spender\", \"type\": \"address\"}],\n",
" \"outputs\": [{\"name\": \"\", \"type\": \"uint256\"}]\n",
" }]\n",
" token_contract = self.web3.eth.contract(address=token.token_address, abi=ERC20_ABI)\n",
" return await token_contract.functions.allowance(self.account.address, addr).call()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Deposit"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"@patch\n",
"@require_context\n",
"async def deposit(self: Chain, deposit: Deposit, delay_in_minutes: float = 30, slippage: float = 0.01):\n",
" amount_token0, pool, router_contract_addr = deposit.amount_token0, deposit.pool, self.settings.router_contract_addr\n",
" print(f\"gonna deposit {amount_token0} {pool.token0.symbol} into {pool.symbol} from {self.account.address}\")\n",
" [token0_amount, token1_amount, _] = await self.router.functions.quoteAddLiquidity(\n",
" pool.token0.token_address,\n",
" pool.token1.token_address,\n",
" pool.is_stable,\n",
" pool.factory,\n",
" float_to_uint256(amount_token0, pool.token0.decimals),\n",
" MAX_UINT256\n",
" ).call()\n",
" print(f\"Quote: {pool.token0.symbol} {token0_amount / 10 ** pool.token0.decimals} -> {pool.token1.symbol} {token1_amount / 10 ** pool.token1.decimals}\")\n",
"\n",
" # set up allowance for both tokens\n",
" print(f\"setting up allowance for {pool.token0.symbol}\")\n",
" await self.set_token_allowance(pool.token0, router_contract_addr, token0_amount)\n",
"\n",
" print(f\"setting up allowance for {pool.token1.symbol}\")\n",
" await self.set_token_allowance(pool.token1, router_contract_addr, token1_amount)\n",
"\n",
" # check allowances\n",
" token0_allowance = await self.check_token_allowance(pool.token0, router_contract_addr)\n",
" token1_allowance = await self.check_token_allowance(pool.token1, router_contract_addr)\n",
"\n",
" print(f\"allowances: {token0_allowance}, {token1_allowance}\")\n",
"\n",
" # adding liquidity\n",
"\n",
" params = [\n",
" pool.token0.token_address,\n",
" pool.token1.token_address,\n",
" pool.is_stable,\n",
" token0_amount,\n",
" token1_amount,\n",
" apply_slippage(token0_amount, slippage),\n",
" apply_slippage(token1_amount, slippage),\n",
" self.account.address,\n",
" get_future_timestamp(delay_in_minutes)\n",
" ]\n",
"\n",
" print(f\"adding liquidity with params: {params}\")\n",
"\n",
" return await self.sign_and_send_tx(self.router.functions.addLiquidity(*params))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## OP Chain"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"class OPChain(Chain):\n",
" def __init__(self, **kwargs): super().__init__(make_op_chain_settings())\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Base Chain"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"\n",
"class BaseChain(Chain):\n",
" def __init__(self, **kwargs): super().__init__(make_base_chain_settings())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"\n",
"import nbdev; nbdev.nbdev_export()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "python3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Loading

0 comments on commit 8fb3211

Please sign in to comment.