diff --git a/.github/workflows/commitlint.yaml b/.github/workflows/commitlint.yaml index c376171..3b8de5e 100644 --- a/.github/workflows/commitlint.yaml +++ b/.github/workflows/commitlint.yaml @@ -9,14 +9,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: "3.10" - name: Install Dependencies run: pip install .[dev] diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index a1b9019..081d138 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -10,18 +10,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e .[release] - + - name: Build run: python setup.py sdist bdist_wheel diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 80aae47..9e28d57 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,12 +7,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: "3.10" - name: Install Dependencies run: pip install .[lint] @@ -30,12 +30,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: "3.10" - name: Install Dependencies run: pip install .[lint,test] # Might need test deps @@ -49,13 +49,13 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] # eventually add `windows-latest` - python-version: [3.7, 3.8, 3.9] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -63,25 +63,4 @@ jobs: run: pip install .[test] - name: Run Tests - run: pytest -m "not fuzzing" -n 0 -s --cov - -# NOTE: uncomment this block after you've marked tests with @pytest.mark.fuzzing -# fuzzing: -# runs-on: ubuntu-latest -# -# strategy: -# fail-fast: true -# -# steps: -# - uses: actions/checkout@v2 -# -# - name: Setup Python -# uses: actions/setup-python@v2 -# with: -# python-version: 3.8 -# -# - name: Install Dependencies -# run: pip install .[test] -# -# - name: Run Tests -# run: pytest -m "fuzzing" --no-cov -s + run: ape test -s diff --git a/.github/workflows/title.yaml b/.github/workflows/title.yaml index d283678..6e8999f 100644 --- a/.github/workflows/title.yaml +++ b/.github/workflows/title.yaml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: "3.10" - name: Install Dependencies run: pip install commitizen diff --git a/ape-config.yaml b/ape-config.yaml new file mode 100644 index 0000000..fc64ff1 --- /dev/null +++ b/ape-config.yaml @@ -0,0 +1,39 @@ +# NOTE: We don't need this other than for copying over the manifests to build with the +# python package, and for testing + +plugins: + # For compiling contracts + - name: solidity + # For fork testing + - name: foundry + +dependencies: + - name: uniswap-v3 + github: Uniswap/v3-core + ref: v1.0.0 + + - name: uniswap-v2 + github: Uniswap/v2-core + ref: v1.0.1 + + - name: permit2 + github: Uniswap/permit2 + ref: main + config_override: + solidity: + via_ir: True # NOTE: Trouble compiling without this + + - name: universal-router + github: Uniswap/universal-router + ref: v1.6.0 + config_override: + dependencies: + - name: openzeppelin + github: OpenZeppelin/openzeppelin-contracts + ref: v4.7.0 + solidity: + import_remapping: + - "permit2=permit2" + - "@uniswap/v3-core=uniswap-v3" + - "@uniswap/v2-core=uniswap-v2" + via_ir: True # NOTE: Trouble compiling without this diff --git a/pyproject.toml b/pyproject.toml index 870096a..eccd50e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,13 +19,11 @@ include = '\.pyi?$' [tool.pytest.ini_options] addopts = """ - -n auto - -p no:ape_test --cov-branch --cov-report term --cov-report html --cov-report xml - --cov= + --cov=uniswap_sdk """ python_files = "test_*.py" testpaths = "tests" @@ -37,3 +35,6 @@ force_grid_wrap = 0 include_trailing_comma = true multi_line_output = 3 use_parentheses = true + +[tool.ruff] +line-length = 100 diff --git a/scripts/update_manifests.py b/scripts/update_manifests.py new file mode 100644 index 0000000..0e1b2b5 --- /dev/null +++ b/scripts/update_manifests.py @@ -0,0 +1,68 @@ +from pathlib import Path + +from ape import project +from ethpm_types import ContractType + +PACKAGE_FOLDER = Path(__file__).parent.parent / "uniswap_sdk" + + +def clean_contract_type(contract_type): + clean_model_dict = contract_type.model_dump( + exclude={ + "ast", + "deployment_bytecode", + "runtime_bytecode", + "source_id", + "pcmap", + "dev_messages", + "sourcemap", + "userdoc", + "devdoc", + "method_identifiers", + } + ) + return ContractType.model_validate(clean_model_dict) + + +def clean_manifest(manifest, *contract_types_to_keep): + manifest.contract_types = { + k: clean_contract_type(v) + for k, v in manifest.contract_types.items() + if k in contract_types_to_keep + } + return manifest.model_dump_json( + exclude={ + "meta", + "dependencies", + "sources", + "compilers", + } + ) + + +def main(): + manifest_json = clean_manifest( + project.dependencies["uniswap-v2"]["v1.0.1"].extract_manifest(), + "UniswapV2Factory", + "UniswapV2Pair", + ) + (PACKAGE_FOLDER / "v2-manifest.json").write_text(manifest_json) + + manifest_json = clean_manifest( + project.dependencies["uniswap-v3"]["v1.0.0"].extract_manifest(), + "UniswapV3Factory", + "UniswapV3Pool", + ) + (PACKAGE_FOLDER / "v3-manifest.json").write_text(manifest_json) + + manifest_json = clean_manifest( + project.dependencies["permit2"]["main"].extract_manifest(), + "Permit2", + ) + (PACKAGE_FOLDER / "permit2-manifest.json").write_text(manifest_json) + + manifest_json = clean_manifest( + project.dependencies["universal-router"]["v1.6.0"].extract_manifest(), + "UniversalRouter", + ) + (PACKAGE_FOLDER / "unirouter-manifest.json").write_text(manifest_json) diff --git a/setup.py b/setup.py index d013eba..72bdffb 100644 --- a/setup.py +++ b/setup.py @@ -4,16 +4,16 @@ extras_require = { "test": [ # `test` GitHub Action jobs uses this - "pytest>=6.0,<7.0", # Core testing package + "pytest", # Core testing package "pytest-xdist", # multi-process runner "pytest-cov", # Coverage analyzer plugin - "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer + "hypothesis", # Strategy-based fuzzer ], "lint": [ - "black>=21.10b0,<22.0", # auto-formatter and linter - "mypy>=0.910,<1.0", # Static type analyzer - "flake8>=3.8.3,<4.0", # Style linter - "isort>=5.9.3,<6.0", # Import sorting linter + "black", # auto-formatter and linter + "mypy", # Static type analyzer + "flake8", # Style linter + "isort", # Import sorting linter ], "release": [ # `release` GitHub Action job uses this "setuptools", # Installation tool @@ -21,7 +21,7 @@ "twine", # Package upload tool ], "dev": [ - "commitizen>=2.19,<2.20", # Manage commits and publishing releases + "commitizen", # Manage commits and publishing releases "pre-commit", # Ensure that linters are run prior to commiting "pytest-watch", # `ptw` test watcher/runner "IPython", # Console for interacting @@ -53,17 +53,17 @@ url="https://github.com/ApeWorX/uniswap-sdk", include_package_data=True, install_requires=[ - "importlib-metadata ; python_version<'3.8'", - "eth-ape>=0.2.3,<0.3.0", + "eth-ape>=0.8,<1", + "ethpm-types>=0.6.11", # higher peer dep of `eth-ape`, solves typing issue ], # NOTE: Add 3rd party libraries here - python_requires=">=3.7.2,<4", + python_requires=">=3.8,<4", extras_require=extras_require, py_modules=["uniswap_sdk"], license="Apache-2.0", zip_safe=False, keywords="ethereum", packages=find_packages(exclude=["tests", "tests.*"]), - package_data={"uniswap_sdk": ["py.typed"]}, + package_data={"uniswap_sdk": ["py.typed", "*.json"]}, classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", @@ -72,9 +72,10 @@ "Operating System :: MacOS", "Operating System :: POSIX", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ], ) diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/functional/test_universal_router.py b/tests/functional/test_universal_router.py new file mode 100644 index 0000000..09f0c8b --- /dev/null +++ b/tests/functional/test_universal_router.py @@ -0,0 +1,210 @@ +import itertools + +import pytest +from hexbytes import HexBytes + +from uniswap_sdk import universal_router as ur + +# Some convienent constants +DEV = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" +YFI = "0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e" +FEE = 10000 +AMOUNT = VALUE = 10**18 +AMOUNT_MIN = 1234 +BIPS = 100 +TOKEN_ID = 1234 +DEADLINE = 2**42 +NONCE = 1 +PAYER_IS_USER = False +DATA = b"hentai is art" +ENCODED_PATH = HexBytes( + # path is: address || ( uint24 || address)+ + f"{WETH.lower().removeprefix('0x')}{FEE:06x}{YFI.lower().removeprefix('0x')}" +) + +TEST_CASES = { + # Format of test cases: + # : dict( + # command_bytes: HexBytes=..., + # command_args: tuple[HexBytes]=..., + # plan_steps: tuple[Callable[[Plan], Plan]]=..., + # ) + # NOTE: *must all follow the same format to load properly* + ur.WRAP_ETH.__name__: dict( + command_bytes=HexBytes(ur.WRAP_ETH.type), + command_args=( + HexBytes( + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + "0000000000000000000000000000000000000000000000000de0b6b3a7640000" + ), + ), + plan_steps=(lambda plan: plan.wrap_eth(DEV, AMOUNT),), + ), + ur.UNWRAP_WETH.__name__: dict( + command_bytes=HexBytes(ur.UNWRAP_WETH.type), + command_args=( + HexBytes( + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + "0000000000000000000000000000000000000000000000000de0b6b3a7640000" + ), + ), + plan_steps=(lambda plan: plan.unwrap_weth(DEV, AMOUNT),), + ), + ur.APPROVE_ERC20.__name__: dict( + command_bytes=HexBytes(ur.APPROVE_ERC20.type), + command_args=( + HexBytes( + "0000000000000000000000000bc529c00c6401aef6d220be8c6ea1667f6ad93e" + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ), + ), + plan_steps=(lambda plan: plan.approve_erc20(YFI, DEV),), + ), + ur.BALANCE_CHECK_ERC20.__name__: dict( + command_bytes=HexBytes(ur.BALANCE_CHECK_ERC20.type), + command_args=( + HexBytes( + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + "0000000000000000000000000bc529c00c6401aef6d220be8c6ea1667f6ad93e" + "0000000000000000000000000000000000000000000000000de0b6b3a7640000" + ), + ), + plan_steps=(lambda plan: plan.balance_check_erc20(DEV, YFI, AMOUNT),), + ), + ur.TRANSFER.__name__: dict( + command_bytes=HexBytes(ur.TRANSFER.type), + command_args=( + HexBytes( + "0000000000000000000000000bc529c00c6401aef6d220be8c6ea1667f6ad93e" + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + "0000000000000000000000000000000000000000000000000de0b6b3a7640000" + ), + ), + plan_steps=(lambda plan: plan.transfer(YFI, DEV, AMOUNT),), + ), + ur.SWEEP.__name__: dict( + command_bytes=HexBytes(ur.SWEEP.type), + command_args=( + HexBytes( + "0000000000000000000000000bc529c00c6401aef6d220be8c6ea1667f6ad93e" + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + "0000000000000000000000000000000000000000000000000de0b6b3a7640000" + ), + ), + plan_steps=(lambda plan: plan.sweep(YFI, DEV, AMOUNT),), + ), + ur.PAY_PORTION.__name__: dict( + command_bytes=HexBytes(ur.PAY_PORTION.type), + command_args=( + HexBytes( + "0000000000000000000000000bc529c00c6401aef6d220be8c6ea1667f6ad93e" + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + "0000000000000000000000000000000000000000000000000000000000000064" + ), + ), + plan_steps=(lambda plan: plan.pay_portion(YFI, DEV, BIPS),), + ), + ur.V2_SWAP_EXACT_IN.__name__: dict( + command_bytes=HexBytes(ur.V2_SWAP_EXACT_IN.type), + command_args=( + HexBytes( + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + "0000000000000000000000000000000000000000000000000de0b6b3a7640000" + "00000000000000000000000000000000000000000000000000000000000004d2" + "00000000000000000000000000000000000000000000000000000000000000a0" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000002" + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + "0000000000000000000000000bc529c00c6401aef6d220be8c6ea1667f6ad93e" + ), + ), + plan_steps=( + lambda plan: plan.v2_swap_exact_in(DEV, AMOUNT, AMOUNT_MIN, [WETH, YFI], PAYER_IS_USER), + ), + ), + ur.V2_SWAP_EXACT_OUT.__name__: dict( + command_bytes=HexBytes(ur.V2_SWAP_EXACT_OUT.type), + command_args=( + HexBytes( + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + "0000000000000000000000000000000000000000000000000de0b6b3a7640000" + "00000000000000000000000000000000000000000000000000000000000004d2" + "00000000000000000000000000000000000000000000000000000000000000a0" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000002" + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + "0000000000000000000000000bc529c00c6401aef6d220be8c6ea1667f6ad93e" + ), + ), + plan_steps=( + lambda plan: plan.v2_swap_exact_out( + DEV, AMOUNT, AMOUNT_MIN, [WETH, YFI], PAYER_IS_USER + ), + ), + ), +} + + +# Multi-step plan test cases: +def case_combinations(tests): + return { + f"{caseA}:{caseB}": dict( + command_bytes=HexBytes( + tests[caseA].get("command_bytes", HexBytes(b"")) # type: ignore[operator] + + tests[caseB].get("command_bytes", HexBytes(b"")) + ), + command_args=( + *tests[caseA].get("command_args", tuple()), + *tests[caseB].get("command_args", tuple()), + ), + plan_steps=( + *tests[caseA].get("plan_steps", tuple()), + *tests[caseB].get("plan_steps", tuple()), + ), + ) + for caseA, caseB in itertools.combinations(tests, 2) + } + + +def case_products(testsA, testsB): + return { + f"{caseA}:{caseB}": dict( + command_bytes=HexBytes( + testsA[caseA].get("command_bytes", HexBytes(b"")) # type: ignore[operator] + + testsB[caseB].get("command_bytes", HexBytes(b"")) + ), + command_args=( + *testsA[caseA].get("command_args", tuple()), + *testsB[caseB].get("command_args", tuple()), + ), + plan_steps=( + *testsA[caseA].get("plan_steps", tuple()), + *testsB[caseB].get("plan_steps", tuple()), + ), + ) + for caseA, caseB in itertools.product(testsA, testsB) + } + + +TWO_STEP_TESTS_CASES = case_combinations(TEST_CASES) +THREE_STEP_TEST_CASES = case_products(TEST_CASES, TWO_STEP_TESTS_CASES) +TEST_CASES.update(TWO_STEP_TESTS_CASES) +TEST_CASES.update(THREE_STEP_TEST_CASES) + + +@pytest.mark.parametrize("command_name", TEST_CASES) +def test_encode_decode_plan(command_name): + plan = ur.Plan() + for add_step in TEST_CASES[command_name].get("plan_steps"): + plan = add_step(plan) + + encoded_command_bytes, encoded_command_args = ( + TEST_CASES[command_name].get("command_bytes"), + TEST_CASES[command_name].get("command_args"), + ) + decoded_plan = ur.Plan.decode(encoded_command_bytes, encoded_command_args) + + assert plan == decoded_plan + assert plan.encoded_commands == decoded_plan.encoded_commands == encoded_command_bytes + assert plan.encode_args() == decoded_plan.encode_args() == list(encoded_command_args) diff --git a/uniswap_sdk/__init__.py b/uniswap_sdk/__init__.py index 57e12c5..6cde4a1 100644 --- a/uniswap_sdk/__init__.py +++ b/uniswap_sdk/__init__.py @@ -1 +1,6 @@ -# Add module top-level imports here +from .universal_router import Plan, UniversalRouter + +__all__ = [ + Plan.__name__, + UniversalRouter.__name__, +] diff --git a/uniswap_sdk/packages.py b/uniswap_sdk/packages.py new file mode 100644 index 0000000..2944322 --- /dev/null +++ b/uniswap_sdk/packages.py @@ -0,0 +1,88 @@ +from importlib import resources +from typing import cast + +from ape.contracts import ContractContainer, ContractInstance +from ape.managers.project import ProjectManager +from ape.types import AddressType +from evmchains import get_chain_meta + +root = resources.files(__package__) + +with resources.as_file(root.joinpath("v2-manifest.json")) as manifest_json_file: + V2 = ProjectManager.from_manifest(manifest_json_file) + +with resources.as_file(root.joinpath("v3-manifest.json")) as manifest_json_file: + V3 = ProjectManager.from_manifest(manifest_json_file) + +with resources.as_file(root.joinpath("unirouter-manifest.json")) as manifest_json_file: + UNI_ROUTER = ProjectManager.from_manifest(manifest_json_file) + +with resources.as_file(root.joinpath("permit2-manifest.json")) as manifest_json_file: + PERMIT2 = ProjectManager.from_manifest(manifest_json_file) + + +def chain_id(ecosystem: str, network: str) -> int: + return get_chain_meta(ecosystem, network).chainId + + +def addr(raw_addr: str) -> AddressType: + return cast(AddressType, raw_addr) + + +def get_contract_instance(ct: ContractContainer, chain_id: int) -> ContractInstance: + assert ct.contract_type.name # for mypy + if not (addresses := ADDRESSES_BY_CHAIN_ID.get(ct.contract_type.name)): + raise ValueError(f"Contract Type `{ct.__class__.__class__}` is not supported.") + + if not (address := addresses.get(chain_id, addresses.get(0))): + raise ValueError(f"No known address for `{ct.__class__.__name__}` on chain ID: {chain_id}") + + if not (contract := ct.at(address)).is_contract: + raise ValueError(f"{contract.address} is not a contract on chain ID: {chain_id}") + + return contract + + +# NOTE: chain_id `0` is wildcard match +ADDRESSES_BY_CHAIN_ID: dict[str, dict[int, AddressType]] = { + V2.UniswapV2Factory.contract_type.name: { + chain_id("ethereum", "mainnet"): addr("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), + chain_id("ethereum", "sepolia"): addr("0xB7f907f7A9eBC822a80BD25E224be42Ce0A698A0"), + chain_id("arbitrum", "mainnet"): addr("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9"), + chain_id("optimism", "mainnet"): addr("0x0c3c1c532F1e39EdF36BE9Fe0bE1410313E074Bf"), + chain_id("avalanche", "mainnet"): addr("0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C"), + chain_id("polygon", "mainnet"): addr("0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C"), + chain_id("bsc", "mainnet"): addr("0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6"), + chain_id("base", "mainnet"): addr("0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6"), + chain_id("blast", "mainnet"): addr("0x5C346464d33F90bABaf70dB6388507CC889C1070"), + }, + # NOTE: UniswapV2Pair addresses should be queried from factory + V3.UniswapV3Factory.contract_type.name: { + 0: addr("0x1F98431c8aD98523631AE4a59f267346ea31F984"), + chain_id("ethereum", "sepolia"): addr("0x0227628f3F023bb0B980b67D528571c95c6DaC1c"), + chain_id("arbitrum", "sepolia"): addr("0x248AB79Bbb9bC29bB72f7Cd42F17e054Fc40188e"), + chain_id("optimism", "sepolia"): addr("0x8CE191193D15ea94e11d327b4c7ad8bbE520f6aF"), + chain_id("avalanche", "mainnet"): addr("0x740b1c1de25031C31FF4fC9A62f554A55cdC1baD"), + chain_id("bsc", "mainnet"): addr("0xdB1d10011AD0Ff90774D0C6Bb92e5C5c8b4461F7"), + chain_id("base", "mainnet"): addr("0x33128a8fC17869897dcE68Ed026d694621f6FDfD"), + chain_id("base", "sepolia"): addr("0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24"), + chain_id("blast", "mainnet"): addr("0x792edAdE80af5fC680d96a2eD80A44247D2Cf6Fd"), + }, + # NOTE: UniswapV3Pool addresses should be queried from factory + # https://github.com/Uniswap/universal-router/tree/main/deploy-addresses + UNI_ROUTER.UniversalRouter.contract_type.name: { + 0: addr("0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD"), + chain_id("arbitrum", "mainnet"): addr("0x5E325eDA8064b456f4781070C0738d849c824258"), + chain_id("arbitrum", "sepolia"): addr("0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2"), + chain_id("optimism", "mainnet"): addr("0xCb1355ff08Ab38bBCE60111F1bb2B784bE25D7e8"), + chain_id("optimism", "sepolia"): addr("0xD5bBa708b39537d33F2812E5Ea032622456F1A95"), + chain_id("polygon", "mainnet"): addr("0xec7BE89e9d109e7e3Fec59c222CF297125FEFda2"), + chain_id("bsc", "mainnet"): addr("0x4Dae2f939ACf50408e13d58534Ff8c2776d45265"), + chain_id("base", "mainnet"): addr("0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD"), + chain_id("base", "sepolia"): addr("0x050E797f3625EC8785265e1d9BDd4799b97528A1"), + chain_id("blast", "mainnet"): addr("0x643770E279d5D0733F21d6DC03A8efbABf3255B4"), + }, + PERMIT2.Permit2.contract_type.name: { + 0: addr("0x000000000022D473030F116dDEE9F6B43aC78BA3"), + }, +} diff --git a/uniswap_sdk/permit2-manifest.json b/uniswap_sdk/permit2-manifest.json new file mode 100644 index 0000000..2749a4a --- /dev/null +++ b/uniswap_sdk/permit2-manifest.json @@ -0,0 +1 @@ +{"contractTypes":{"Permit2":{"abi":[{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"AllowanceExpired","type":"error"},{"inputs":[],"name":"ExcessiveInvalidation","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxAmount","type":"uint256"}],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidContractSignature","type":"error"},{"inputs":[],"name":"InvalidNonce","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidSignatureLength","type":"error"},{"inputs":[],"name":"InvalidSigner","type":"error"},{"inputs":[],"name":"LengthMismatch","type":"error"},{"inputs":[{"internalType":"uint256","name":"signatureDeadline","type":"uint256"}],"name":"SignatureExpired","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint160","name":"amount","type":"uint160"},{"indexed":false,"internalType":"uint48","name":"expiration","type":"uint48"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"spender","type":"address"}],"name":"Lockdown","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint48","name":"newNonce","type":"uint48"},{"indexed":false,"internalType":"uint48","name":"oldNonce","type":"uint48"}],"name":"NonceInvalidation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint160","name":"amount","type":"uint160"},{"indexed":false,"internalType":"uint48","name":"expiration","type":"uint48"},{"indexed":false,"internalType":"uint48","name":"nonce","type":"uint48"}],"name":"Permit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"word","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"mask","type":"uint256"}],"name":"UnorderedNonceInvalidation","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint160","name":"amount","type":"uint160"},{"internalType":"uint48","name":"expiration","type":"uint48"},{"internalType":"uint48","name":"nonce","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint160","name":"amount","type":"uint160"},{"internalType":"uint48","name":"expiration","type":"uint48"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint48","name":"newNonce","type":"uint48"}],"name":"invalidateNonces","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"wordPos","type":"uint256"},{"internalType":"uint256","name":"mask","type":"uint256"}],"name":"invalidateUnorderedNonces","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"internalType":"struct IAllowanceTransfer.TokenSpenderPair[]","name":"approvals","type":"tuple[]"}],"name":"lockdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"nonceBitmap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint160","name":"amount","type":"uint160"},{"internalType":"uint48","name":"expiration","type":"uint48"},{"internalType":"uint48","name":"nonce","type":"uint48"}],"internalType":"struct IAllowanceTransfer.PermitDetails[]","name":"details","type":"tuple[]"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"sigDeadline","type":"uint256"}],"internalType":"struct IAllowanceTransfer.PermitBatch","name":"permitBatch","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint160","name":"amount","type":"uint160"},{"internalType":"uint48","name":"expiration","type":"uint48"},{"internalType":"uint48","name":"nonce","type":"uint48"}],"internalType":"struct IAllowanceTransfer.PermitDetails","name":"details","type":"tuple"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"sigDeadline","type":"uint256"}],"internalType":"struct IAllowanceTransfer.PermitSingle","name":"permitSingle","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct ISignatureTransfer.TokenPermissions","name":"permitted","type":"tuple"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct ISignatureTransfer.PermitTransferFrom","name":"permit","type":"tuple"},{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"requestedAmount","type":"uint256"}],"internalType":"struct ISignatureTransfer.SignatureTransferDetails","name":"transferDetails","type":"tuple"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permitTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct ISignatureTransfer.TokenPermissions[]","name":"permitted","type":"tuple[]"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct ISignatureTransfer.PermitBatchTransferFrom","name":"permit","type":"tuple"},{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"requestedAmount","type":"uint256"}],"internalType":"struct ISignatureTransfer.SignatureTransferDetails[]","name":"transferDetails","type":"tuple[]"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permitTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct ISignatureTransfer.TokenPermissions","name":"permitted","type":"tuple"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct ISignatureTransfer.PermitTransferFrom","name":"permit","type":"tuple"},{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"requestedAmount","type":"uint256"}],"internalType":"struct ISignatureTransfer.SignatureTransferDetails","name":"transferDetails","type":"tuple"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bytes32","name":"witness","type":"bytes32"},{"internalType":"string","name":"witnessTypeString","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permitWitnessTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct ISignatureTransfer.TokenPermissions[]","name":"permitted","type":"tuple[]"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct ISignatureTransfer.PermitBatchTransferFrom","name":"permit","type":"tuple"},{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"requestedAmount","type":"uint256"}],"internalType":"struct ISignatureTransfer.SignatureTransferDetails[]","name":"transferDetails","type":"tuple[]"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bytes32","name":"witness","type":"bytes32"},{"internalType":"string","name":"witnessTypeString","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permitWitnessTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint160","name":"amount","type":"uint160"},{"internalType":"address","name":"token","type":"address"}],"internalType":"struct IAllowanceTransfer.AllowanceTransferDetails[]","name":"transferDetails","type":"tuple[]"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint160","name":"amount","type":"uint160"},{"internalType":"address","name":"token","type":"address"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"Permit2","methodIdentifiers":{"DOMAIN_SEPARATOR()":"0x3644e515","allowance(address,address,address)":"0x927da105","approve(address,address,uint160,uint48)":"0x87517c45","invalidateNonces(address,address,uint48)":"0x65d9723c","invalidateUnorderedNonces(uint256,uint256)":"0x3ff9dcb1","lockdown((address,address)[])":"0xcc53287f","nonceBitmap(address,uint256)":"0x4fe02b44","permit(address,((address,uint160,uint48,uint48),address,uint256),bytes)":"0x2b67b570","permit(address,((address,uint160,uint48,uint48)[],address,uint256),bytes)":"0x2a2d80d1","permitTransferFrom(((address,uint256),uint256,uint256),(address,uint256),address,bytes)":"0x30f28b7a","permitTransferFrom(((address,uint256)[],uint256,uint256),(address,uint256)[],address,bytes)":"0xedd9444b","permitWitnessTransferFrom(((address,uint256),uint256,uint256),(address,uint256),address,bytes32,string,bytes)":"0x137c29fe","permitWitnessTransferFrom(((address,uint256)[],uint256,uint256),(address,uint256)[],address,bytes32,string,bytes)":"0xfe8ec1a7","transferFrom((address,address,uint160,address)[])":"0x0d58b1db","transferFrom(address,address,uint160,address)":"0x36c78516"}}},"deployments":{},"manifest":"ethpm/3","name":"permit2","version":"main"} \ No newline at end of file diff --git a/uniswap_sdk/permit2.py b/uniswap_sdk/permit2.py new file mode 100644 index 0000000..bdfd316 --- /dev/null +++ b/uniswap_sdk/permit2.py @@ -0,0 +1,22 @@ +from typing import List + +from eip712 import EIP712Message, EIP712Type + + +class PermitDetails(EIP712Type): + token: "address" # type: ignore[name-defined] # noqa + amount: "uint160" # type: ignore[name-defined] # noqa + expiration: "uint48" # type: ignore[name-defined] # noqa + nonce: "uint48" # type: ignore[name-defined] # noqa + + +class PermitSingle(EIP712Message): + details: PermitDetails + spender: "address" # type: ignore[name-defined] # noqa + sigDeadline: "uint256" # type: ignore[name-defined] # noqa + + +class PermitBatch(EIP712Message): + details: List[PermitDetails] + spender: "address" # type: ignore[name-defined] # noqa + sigDeadline: "uint256" # type: ignore[name-defined] # noqa diff --git a/uniswap_sdk/unirouter-manifest.json b/uniswap_sdk/unirouter-manifest.json new file mode 100644 index 0000000..dabce1d --- /dev/null +++ b/uniswap_sdk/unirouter-manifest.json @@ -0,0 +1 @@ +{"contractTypes":{"UniversalRouter":{"abi":[{"inputs":[{"components":[{"internalType":"address","name":"permit2","type":"address"},{"internalType":"address","name":"weth9","type":"address"},{"internalType":"address","name":"seaportV1_5","type":"address"},{"internalType":"address","name":"seaportV1_4","type":"address"},{"internalType":"address","name":"openseaConduit","type":"address"},{"internalType":"address","name":"nftxZap","type":"address"},{"internalType":"address","name":"x2y2","type":"address"},{"internalType":"address","name":"foundation","type":"address"},{"internalType":"address","name":"sudoswap","type":"address"},{"internalType":"address","name":"elementMarket","type":"address"},{"internalType":"address","name":"nft20Zap","type":"address"},{"internalType":"address","name":"cryptopunks","type":"address"},{"internalType":"address","name":"looksRareV2","type":"address"},{"internalType":"address","name":"routerRewardsDistributor","type":"address"},{"internalType":"address","name":"looksRareRewardsDistributor","type":"address"},{"internalType":"address","name":"looksRareToken","type":"address"},{"internalType":"address","name":"v2Factory","type":"address"},{"internalType":"address","name":"v3Factory","type":"address"},{"internalType":"bytes32","name":"pairInitCodeHash","type":"bytes32"},{"internalType":"bytes32","name":"poolInitCodeHash","type":"bytes32"}],"internalType":"struct RouterParameters","name":"params","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BalanceTooLow","type":"error"},{"inputs":[],"name":"BuyPunkFailed","type":"error"},{"inputs":[],"name":"ContractLocked","type":"error"},{"inputs":[],"name":"ETHNotAccepted","type":"error"},{"inputs":[{"internalType":"uint256","name":"commandIndex","type":"uint256"},{"internalType":"bytes","name":"message","type":"bytes"}],"name":"ExecutionFailed","type":"error"},{"inputs":[],"name":"FromAddressIsNotOwner","type":"error"},{"inputs":[],"name":"InsufficientETH","type":"error"},{"inputs":[],"name":"InsufficientToken","type":"error"},{"inputs":[],"name":"InvalidBips","type":"error"},{"inputs":[{"internalType":"uint256","name":"commandType","type":"uint256"}],"name":"InvalidCommandType","type":"error"},{"inputs":[],"name":"InvalidOwnerERC1155","type":"error"},{"inputs":[],"name":"InvalidOwnerERC721","type":"error"},{"inputs":[],"name":"InvalidPath","type":"error"},{"inputs":[],"name":"InvalidReserves","type":"error"},{"inputs":[],"name":"InvalidSpender","type":"error"},{"inputs":[],"name":"LengthMismatch","type":"error"},{"inputs":[],"name":"SliceOutOfBounds","type":"error"},{"inputs":[],"name":"TransactionDeadlinePassed","type":"error"},{"inputs":[],"name":"UnableToClaim","type":"error"},{"inputs":[],"name":"UnsafeCast","type":"error"},{"inputs":[],"name":"V2InvalidPath","type":"error"},{"inputs":[],"name":"V2TooLittleReceived","type":"error"},{"inputs":[],"name":"V2TooMuchRequested","type":"error"},{"inputs":[],"name":"V3InvalidAmountOut","type":"error"},{"inputs":[],"name":"V3InvalidCaller","type":"error"},{"inputs":[],"name":"V3InvalidSwap","type":"error"},{"inputs":[],"name":"V3TooLittleReceived","type":"error"},{"inputs":[],"name":"V3TooMuchRequested","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardsSent","type":"event"},{"inputs":[{"internalType":"bytes","name":"looksRareClaim","type":"bytes"}],"name":"collectRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"commands","type":"bytes"},{"internalType":"bytes[]","name":"inputs","type":"bytes[]"}],"name":"execute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"commands","type":"bytes"},{"internalType":"bytes[]","name":"inputs","type":"bytes[]"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"execute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"contractName":"UniversalRouter","methodIdentifiers":{"collectRewards(bytes)":"0x709a1cc2","execute(bytes,bytes[])":"0x24856bc3","execute(bytes,bytes[],uint256)":"0x3593564c","onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)":"0xbc197c81","onERC1155Received(address,address,uint256,uint256,bytes)":"0xf23a6e61","onERC721Received(address,address,uint256,bytes)":"0x150b7a02","supportsInterface(bytes4)":"0x01ffc9a7","uniswapV3SwapCallback(int256,int256,bytes)":"0xfa461e33"}}},"deployments":{},"manifest":"ethpm/3","name":"universal-router","version":"v1.6.0"} \ No newline at end of file diff --git a/uniswap_sdk/universal_router.py b/uniswap_sdk/universal_router.py new file mode 100644 index 0000000..1beef93 --- /dev/null +++ b/uniswap_sdk/universal_router.py @@ -0,0 +1,655 @@ +from itertools import cycle +from typing import Any, Callable, ClassVar, Iterable, Optional, Type, Union + +from ape.api import ReceiptAPI, TransactionAPI +from ape.contracts import ContractInstance +from ape.exceptions import DecodingError +from ape.managers import ManagerAccessMixin +from ape.utils import StructParser, cached_property +from ape_ethereum.ecosystem import parse_type +from eth_abi import decode as abi_decode +from eth_abi import encode as abi_encode +from eth_abi.exceptions import InsufficientDataBytes +from eth_abi.packed import encode_packed +from ethpm_types.abi import ABIType, MethodABI +from hexbytes import HexBytes +from pydantic import BaseModel, field_validator + +from .packages import UNI_ROUTER, get_contract_instance + + +# NOTE: Special constants +class Constants: + # internal constants + _ALLOW_REVERT_FLAG = 0x80 + _COMMAND_TYPE_MASK = 0x3F + + # Used for identifying cases when this contract's balance of a token is to be used as an input + CONTRACT_BALANCE = int(2**255) + # Used for identifying cases when a v2 pair has already received input tokens + ALREADY_PAID = 0 + # Used as a flag for identifying the transfer of ETH instead of a token + ETH = "0x0000000000000000000000000000000000000000" + # Used as a flag for identifying that msg.sender should be used + MSG_SENDER = "0x0000000000000000000000000000000000000001" + # Used as a flag for identifying address(this) should be used + ADDRESS_THIS = "0x0000000000000000000000000000000000000002" + + +class Command(BaseModel, ManagerAccessMixin): + # NOTE: Define in class defs + type: ClassVar[int] + definition: ClassVar[list[ABIType]] + is_revertible: ClassVar[bool] = False + + # NOTE: For parsing live data + args: list[Any] + allow_revert: bool = False + + @field_validator("args") + @classmethod + def validate_args(cls, args: list) -> list: + if len(args) != len(cls.definition): + raise ValueError( + f"Number of args ({len(args)}) does not match definition ({len(cls.definition)})." + ) + + return args + + def __repr__(self) -> str: + args_str = ", ".join(f"{def_.name}={arg}" for def_, arg in zip(self.definition, self.args)) + return f"{self.__class__.__name__}({args_str})" + + def __getattr__(self, attr: str) -> Any: + for idx, abi_type in enumerate(self.definition): + if abi_type.name == attr: + return self.args[idx] + + raise AttributeError + + @property + def command_byte(self) -> int: + return (Constants._ALLOW_REVERT_FLAG if self.allow_revert else 0x0) | self.type + + def encode_args(self) -> HexBytes: + parser = StructParser( + MethodABI(name=self.__class__.__name__, inputs=self.__class__.definition) + ) + arguments = parser.encode_input(self.args) + arg_types = [i.canonical_type for i in self.definition] + python_types = tuple( + self.provider.network.ecosystem._python_type_for_abi_type(i) for i in self.definition + ) + converted_args = self.conversion_manager.convert(arguments, python_types) + encoded_calldata = abi_encode(arg_types, converted_args) + return HexBytes(encoded_calldata) + + @classmethod + def _decode_args(cls, calldata: HexBytes) -> list[Any]: + raw_input_types = [i.canonical_type for i in cls.definition] + input_types = [parse_type(i.model_dump(mode="json")) for i in cls.definition] + + try: + raw_input_values = abi_decode(raw_input_types, calldata, strict=False) + except InsufficientDataBytes as err: + raise DecodingError(str(err)) from err + + return [ + cls.provider.network.ecosystem.decode_primitive_value(v, t) + for v, t in zip(raw_input_values, input_types) + ] + + @classmethod + def decode(cls, command_byte: int, calldata: HexBytes): + command_type = command_byte & Constants._COMMAND_TYPE_MASK + + if command_type not in ALL_COMMANDS_BY_TYPE: + raise NotImplementedError(f"Unsupported command type: '{command_type}'") + + command_cls = ALL_COMMANDS_BY_TYPE[command_type] + allow_revert = bool(command_byte & Constants._ALLOW_REVERT_FLAG) + + if allow_revert and not command_cls.is_revertible: + raise ValueError("Command is not reversible but reversibility is set.") + + return command_cls(args=command_cls._decode_args(calldata), allow_revert=allow_revert) + + +def encode_path(path: list) -> bytes: + if len(path) % 2 != 1: + ValueError("Path must be an odd-length sequence of token, fee rate, token, ...") + + types = [type for _, type in zip(path, cycle(["address", "uint24"]))] + return encode_packed(types, path) + + +def decode_path(path: bytes) -> list: + decoded_path: list[Union[str, int]] = [] + decoded_type = cycle(["address", "uint24"]) + while len(path) > 0: + t = next(decoded_type) + idx = 20 if t == "address" else 3 + data, path = path[:idx], path[idx:] + decoded_path.extend(abi_decode([t], b"\x00" * (12 if t == "address" else 29) + data)) + + return decoded_path + + +class _V3_EncodePathInput(Command): + @field_validator("args", mode="before") + @classmethod + def encode_path_input(cls, args: list) -> list: + if isinstance(args[3], list): + args[3] = encode_path(args[3]) + + return args + + def __repr__(self) -> str: + names = list(def_.name for def_ in self.definition) + args = self.args.copy() + names[3] = "path" + args[3] = self.path + args_str = ", ".join(f"{name}={arg}" for name, arg in zip(names, args)) + return f"{self.__class__.__name__}({args_str})" + + @property + def path(self) -> list: + return decode_path(self.encodedPath) + + +class V3_SWAP_EXACT_IN(_V3_EncodePathInput, Command): + type = 0x00 + + definition = [ + ABIType(name="recipient", type="address"), + ABIType(name="amountIn", type="uint256"), + ABIType(name="amountOutMin", type="uint256"), + ABIType(name="encodedPath", type="bytes"), + ABIType(name="payerIsUser", type="bool"), + ] + + +class V3_SWAP_EXACT_OUT(_V3_EncodePathInput, Command): + type = 0x01 + + definition = [ + ABIType(name="recipient", type="address"), + ABIType(name="amountOut", type="uint256"), + ABIType(name="amountInMax", type="uint256"), + ABIType(name="encodedPath", type="bytes"), + ABIType(name="payerIsUser", type="bool"), + ] + + +class PERMIT2_TRANSFER_FROM(Command): + type = 0x02 + + definition = [ + ABIType(name="token", type="address"), + ABIType(name="recipient", type="address"), + ABIType(name="amount", type="uint160"), + ] + + +class PERMIT2_PERMIT_BATCH(Command): + type = 0x03 + + definition = [ + ABIType( + name="details", + type="tuple[]", + components=[ + ABIType(name="token", type="address"), + ABIType(name="amount", type="uint160"), + ABIType(name="expiration", type="uint48"), + ABIType(name="nonce", type="uint48"), + ], + ), + ABIType(name="spender", type="address"), + ABIType(name="deadline", type="uint256"), + ] + + +class SWEEP(Command): + type = 0x04 + + definition = [ + ABIType(name="token", type="address"), + ABIType(name="recipient", type="address"), + ABIType(name="amountMin", type="uint256"), + ] + + +class TRANSFER(Command): + type = 0x05 + + definition = [ + ABIType(name="token", type="address"), + ABIType(name="recipient", type="address"), + ABIType(name="amount", type="uint256"), + ] + + +class PAY_PORTION(Command): + type = 0x06 + + definition = [ + ABIType(name="token", type="address"), + ABIType(name="recipient", type="address"), + ABIType(name="bips", type="uint256"), + ] + + +class V2_SWAP_EXACT_IN(Command): + type = 0x08 + + definition = [ + ABIType(name="recipient", type="address"), + ABIType(name="amountIn", type="uint256"), + ABIType(name="amountOutMin", type="uint256"), + ABIType(name="path", type="address[]"), + ABIType(name="payerIsUser", type="bool"), + ] + + +class V2_SWAP_EXACT_OUT(Command): + type = 0x09 + + definition = [ + ABIType(name="recipient", type="address"), + ABIType(name="amountOut", type="uint256"), + ABIType(name="amountInMax", type="uint256"), + ABIType(name="path", type="address[]"), + ABIType(name="payerIsUser", type="bool"), + ] + + +class PERMIT2_PERMIT(Command): + type = 0x0A + + definition = [ + ABIType( + name="details", + type="tuple", + components=[ + ABIType(name="token", type="address"), + ABIType(name="amount", type="uint160"), + ABIType(name="expiration", type="uint48"), + ABIType(name="nonce", type="uint48"), + ], + ), + ABIType(name="spender", type="address"), + ABIType(name="deadline", type="uint256"), + ] + + +class WRAP_ETH(Command): + """Wrap `amountMin` (or more) ether and send to `recipient`""" + + type = 0x0B + + definition = [ + ABIType(name="recipient", type="address"), + ABIType(name="amountMin", type="uint256"), + ] + + +class UNWRAP_WETH(Command): + type = 0x0C + + definition = [ + ABIType(name="recipient", type="address"), + ABIType(name="amountMin", type="uint256"), + ] + + +class PERMIT2_TRANSFER_FROM_BATCH(Command): + type = 0x0D + + definition = [ + ABIType( + name="batch", + type="tuple[]", + components=[ + ABIType(name="sender", type="address"), + ABIType(name="recipient", type="address"), + ABIType(name="amount", type="uint160"), + ABIType(name="token", type="address"), + ], + ) + ] + + +class BALANCE_CHECK_ERC20(Command): + type = 0x0E + + definition = [ + ABIType(name="owner", type="address"), + ABIType(name="token", type="address"), + ABIType(name="minBalance", type="uint256"), + ] + + +class SEAPORT_V1_5(Command): + type = 0x10 + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ] + + +class LOOKS_RARE_V2(Command): + type = 0x11 + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ] + + +class NFTX(Command): + type = 0x12 + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ] + + +class CRYPTOPUNKS(Command): + type = 0x13 + + definition = [ + ABIType(name="punk_id", type="uint256"), + ABIType(name="recipient", type="address"), + ABIType(name="value", type="uint256"), + ] + + +class OWNER_CHECK_721(Command): + type = 0x15 + + definition = [ + ABIType(name="owner", type="address"), + ABIType(name="token", type="address"), + ABIType(name="token_id", type="uint256"), + ] + + +class OWNER_CHECK_1155(Command): + type = 0x16 + + definition = [ + ABIType(name="owner", type="address"), + ABIType(name="token", type="address"), + ABIType(name="token_id", type="uint256"), + ABIType(name="min_balance", type="uint256"), + ] + + +class SWEEP_ERC721(Command): + type = 0x17 + + definition = [ + ABIType(name="token", type="address"), + ABIType(name="recipient", type="address"), + ABIType(name="token_id", type="uint256"), + ] + + +class X2Y2_721(Command): + type = 0x18 + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ABIType(name="recipient", type="address"), + ABIType(name="token", type="address"), + ABIType(name="token_id", type="uint256"), + ] + + +class SUDOSWAP(Command): + type = 0x19 + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ] + + +class NFT20(Command): + type = 0x1A + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ] + + +class X2Y2_1155(Command): + type = 0x1B + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ABIType(name="recipient", type="address"), + ABIType(name="token", type="address"), + ABIType(name="token_id", type="uint256"), + ABIType(name="amount", type="uint256"), + ] + + +class FOUNDATION(Command): + type = 0x1C + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ABIType(name="recipient", type="address"), + ABIType(name="token", type="address"), + ABIType(name="token_id", type="uint256"), + ] + + +class SWEEP_ERC1155(Command): + type = 0x1D + + definition = [ + ABIType(name="token", type="address"), + ABIType(name="recipient", type="address"), + ABIType(name="token_id", type="uint256"), + ABIType(name="amount", type="uint256"), + ] + + +class ELEMENT_MARKET(Command): + type = 0x1E + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ] + + +class SEAPORT_V1_4(Command): + type = 0x20 + + definition = [ + ABIType(name="value", type="uint256"), + ABIType(name="data", type="bytes"), + ] + + +class EXECUTE_SUB_PLAN(Command): + type = 0x21 + + definition = [ + ABIType(name="commands", type="bytes"), + ABIType(name="inputs", type="bytes[]"), + ] + + @field_validator("args", mode="before") + @classmethod + def encode_sub_plan(cls, sub_plan: Union[list, list[Command], "Plan"]) -> list: + if ( + isinstance(sub_plan, list) + and len(sub_plan) > 0 + and all(isinstance(e, Plan) for e in sub_plan) + ): + sub_plan = Plan(commands=sub_plan) + + # NOTE: Intentionally execute this if above is true + if isinstance(sub_plan, Plan): + return [sub_plan.encoded_commands, sub_plan.encode_args()] + + return sub_plan # should be raw sub plan otherwise (validator check) + + @property + def decoded_sub_plan(self) -> "Plan": + return Plan.decode(self.args[0], self.args[1]) + + def add_step(self, command: Command): + self.args[0] += command.command_byte + self.args[1].append(command.encode_args()) + + def rm_step(self): + if len(self.args[0]) == 0: + raise ValueError("No more items to pop.") + + self.args[0] = self.args[0][:-1] + self.args[1].poplast() + + +class APPROVE_ERC20(Command): + type = 0x22 + + definition = [ + ABIType(name="token", type="address"), + ABIType(name="spender", type="address"), + ] + + +# NOTE: Must come after all the subclassing action above +ALL_COMMANDS_BY_TYPE: dict[int, Type[Command]] = { + cls.type: cls for cls in Command.__subclasses__() if not cls.__name__.startswith("_") +} +ALL_COMMANDS_BY_NAME: dict[str, Type[Command]] = { + cls.__name__: cls for cls in Command.__subclasses__() if not cls.__name__.startswith("_") +} + + +class Plan(BaseModel): + """ + A plan to execute with the UniversalRouter. + All of the available plan steps are available as attributes on this class. + You can also directly add a plan step through `plan.add(...)` + + Usage exmaple:: + >>> plan = Plan().wrap_eth(...)... # Use the builder pattern to create a plan + >>> plan.add(V3_SWAP_EXACT_IN(args=...)) # Directly add a command to the plan + """ + + commands: list[Command] = [] + + @classmethod + def decode(cls, encoded_commands: HexBytes, encoded_args: Iterable[HexBytes]) -> "Plan": + return cls( + commands=[ + Command.decode(command_byte, encoded_args) + for command_byte, encoded_args in zip(encoded_commands, encoded_args) + ] + ) + + def add(self, command: Command) -> "Plan": + self.commands.append(command) + return self + + @property + def encoded_commands(self) -> HexBytes: + return HexBytes(bytearray([cmd.command_byte for cmd in self.commands])) + + def encode_args(self) -> list[HexBytes]: + return [cmd.encode_args() for cmd in self.commands] + + def __getattr__(self, command_name: str) -> Callable[..., "Plan"]: + if command_name.upper() not in ALL_COMMANDS_BY_NAME: + raise AttributeError(f"Unsupported command type: '{command_name.upper()}'.") + + command_cls = ALL_COMMANDS_BY_NAME[command_name.upper()] + + def add_command(*args, **kwargs): + # TODO: Conversion and kwarg stripping + return self.add(command_cls(args=args, **kwargs)) + + # TODO: Make `add_command` repr nicely and show args + add_command.__doc__ = command_cls.__doc__ + return add_command + + # TODO: Add nice __repr__/__str__ + + def __dir__(self) -> list[str]: + return list(n.lower() for n in ALL_COMMANDS_BY_NAME) + + +class UniversalRouter(ManagerAccessMixin): + """ + Class for working with the Uniswap UniversalRouter: + https://docs.uniswap.org/contracts/universal-router/overview + + It is useful to use the :class:`Plan` object to help you create your plans for the router. + + Usage example:: + >>> ur = UniversalRouter() + >>> plan = Plan().wrap_eth("1 ether").unwrap_eth("1 ether") + >>> ur.execute(plan, sender=me, value="1 ether") + """ + + constants = Constants + + @cached_property + def contract(self) -> ContractInstance: + return get_contract_instance(UNI_ROUTER.UniversalRouter, self.provider.chain_id) + + def decode_plan_from_calldata(self, calldata: HexBytes) -> Plan: + _, decoded_calldata = self.contract.execute.decode_input(calldata) + return Plan.decode(decoded_calldata["commands"], decoded_calldata["inputs"]) + + def decode_plan_from_transaction(self, txn: Union[str, TransactionAPI, ReceiptAPI]) -> Plan: + """ + Decode any plan from a transaction object, receipt object, + or transaction hash (that has been mined) + """ + if isinstance(txn, str): + txn = self.provider.get_receipt(txn) + + assert isinstance(txn, (TransactionAPI, ReceiptAPI)) # for mypy + if txn.receiver != self.contract.address: + raise ValueError("Cannot decode plan from transaction to different contract.") + + return self.decode_plan_from_calldata(HexBytes(txn.data)) + + def plan_as_transaction( + self, plan: Plan, deadline: Optional[int] = None, **txn_args + ) -> TransactionAPI: + """ + Encode the plan as a transaction for further processing + """ + args: list[Any] = [plan.encoded_commands, plan.encode_args()] + + if deadline is not None: + args.append(deadline) + + return self.contract.execute.as_transaction(*args, **txn_args) + + def execute(self, plan: Plan, deadline: Optional[int] = None, **txn_args) -> ReceiptAPI: + """ + Submit the plan as a transaction and broadcast it + """ + args: list[Any] = [plan.encoded_commands, plan.encode_args()] + + if deadline is not None: + args.append(deadline) + + return self.contract.execute(*args, **txn_args) diff --git a/uniswap_sdk/v2-manifest.json b/uniswap_sdk/v2-manifest.json new file mode 100644 index 0000000..c1fee47 --- /dev/null +++ b/uniswap_sdk/v2-manifest.json @@ -0,0 +1 @@ +{"contractTypes":{"UniswapV2Factory":{"abi":[{"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":false,"internalType":"address","name":"pair","type":"address"},{"indexed":false,"internalType":"uint256","name":"","type":"uint256"}],"name":"PairCreated","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allPairs","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allPairsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"createPair","outputs":[{"internalType":"address","name":"pair","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeToSetter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_feeTo","type":"address"}],"name":"setFeeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"name":"setFeeToSetter","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"UniswapV2Factory","methodIdentifiers":{"allPairs(uint256)":"0x1e3dd18b","allPairsLength()":"0x574f2ba3","createPair(address,address)":"0xc9c65396","feeTo()":"0x017e7e58","feeToSetter()":"0x094b7415","getPair(address,address)":"0xe6a43905","setFeeTo(address)":"0xf46901ed","setFeeToSetter(address)":"0xa2e74af6"}},"UniswapV2Pair":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0Out","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1Out","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"reserve0","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"reserve1","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token0","type":"address"},{"internalType":"address","name":"_token1","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"kLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"price0CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price1CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount0Out","type":"uint256"},{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sync","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"contractName":"UniswapV2Pair","methodIdentifiers":{"DOMAIN_SEPARATOR()":"0x3644e515","MINIMUM_LIQUIDITY()":"0xba9a7a56","PERMIT_TYPEHASH()":"0x30adf81f","allowance(address,address)":"0xdd62ed3e","approve(address,uint256)":"0x095ea7b3","balanceOf(address)":"0x70a08231","burn(address)":"0x89afcb44","decimals()":"0x313ce567","factory()":"0xc45a0155","getReserves()":"0x0902f1ac","initialize(address,address)":"0x485cc955","kLast()":"0x7464fc3d","mint(address)":"0x6a627842","name()":"0x06fdde03","nonces(address)":"0x7ecebe00","permit(address,address,uint256,uint256,uint8,bytes32,bytes32)":"0xd505accf","price0CumulativeLast()":"0x5909c0d5","price1CumulativeLast()":"0x5a3d5493","skim(address)":"0xbc25cf77","swap(uint256,uint256,address,bytes)":"0x022c0d9f","symbol()":"0x95d89b41","sync()":"0xfff6cae9","token0()":"0x0dfe1681","token1()":"0xd21220a7","totalSupply()":"0x18160ddd","transfer(address,uint256)":"0xa9059cbb","transferFrom(address,address,uint256)":"0x23b872dd"}}},"deployments":{},"manifest":"ethpm/3","name":"uniswap-v2","version":"v1.0.1"} \ No newline at end of file diff --git a/uniswap_sdk/v3-manifest.json b/uniswap_sdk/v3-manifest.json new file mode 100644 index 0000000..0169cfe --- /dev/null +++ b/uniswap_sdk/v3-manifest.json @@ -0,0 +1 @@ +{"contractTypes":{"UniswapV3Factory":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint24","name":"fee","type":"uint24"},{"indexed":true,"internalType":"int24","name":"tickSpacing","type":"int24"}],"name":"FeeAmountEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":true,"internalType":"uint24","name":"fee","type":"uint24"},{"indexed":false,"internalType":"int24","name":"tickSpacing","type":"int24"},{"indexed":false,"internalType":"address","name":"pool","type":"address"}],"name":"PoolCreated","type":"event"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"}],"name":"createPool","outputs":[{"internalType":"address","name":"pool","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"}],"name":"enableFeeAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"","type":"uint24"}],"name":"feeAmountTickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint24","name":"","type":"uint24"}],"name":"getPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"parameters","outputs":[{"internalType":"address","name":"factory","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"}],"contractName":"UniswapV3Factory","methodIdentifiers":{"createPool(address,address,uint24)":"0xa1671295","enableFeeAmount(uint24,int24)":"0x8a7c195f","feeAmountTickSpacing(uint24)":"0x22afcccb","getPool(address,address,uint24)":"0x1698ee82","owner()":"0x8da5cb5b","parameters()":"0x89035730","setOwner(address)":"0x13af4035"}},"UniswapV3Pool":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"Collect","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"CollectProtocol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid1","type":"uint256"}],"name":"Flash","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"observationCardinalityNextOld","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"observationCardinalityNextNew","type":"uint16"}],"name":"IncreaseObservationCardinalityNext","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"internalType":"int24","name":"tick","type":"int24"}],"name":"Initialize","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"feeProtocol0Old","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol1Old","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol0New","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol1New","type":"uint8"}],"name":"SetFeeProtocol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"int256","name":"amount0","type":"int256"},{"indexed":false,"internalType":"int256","name":"amount1","type":"int256"},{"indexed":false,"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"int24","name":"tick","type":"int24"}],"name":"Swap","type":"event"},{"inputs":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount0Requested","type":"uint128"},{"internalType":"uint128","name":"amount1Requested","type":"uint128"}],"name":"collect","outputs":[{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint128","name":"amount0Requested","type":"uint128"},{"internalType":"uint128","name":"amount1Requested","type":"uint128"}],"name":"collectProtocol","outputs":[{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeGrowthGlobal0X128","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeGrowthGlobal1X128","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"flash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"}],"name":"increaseObservationCardinalityNext","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"liquidity","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLiquidityPerTick","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mint","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"observations","outputs":[{"internalType":"uint32","name":"blockTimestamp","type":"uint32"},{"internalType":"int56","name":"tickCumulative","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityCumulativeX128","type":"uint160"},{"internalType":"bool","name":"initialized","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32[]","name":"secondsAgos","type":"uint32[]"}],"name":"observe","outputs":[{"internalType":"int56[]","name":"tickCumulatives","type":"int56[]"},{"internalType":"uint160[]","name":"secondsPerLiquidityCumulativeX128s","type":"uint160[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"positions","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"feeGrowthInside0LastX128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1LastX128","type":"uint256"},{"internalType":"uint128","name":"tokensOwed0","type":"uint128"},{"internalType":"uint128","name":"tokensOwed1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFees","outputs":[{"internalType":"uint128","name":"token0","type":"uint128"},{"internalType":"uint128","name":"token1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"feeProtocol0","type":"uint8"},{"internalType":"uint8","name":"feeProtocol1","type":"uint8"}],"name":"setFeeProtocol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"slot0","outputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"internalType":"int24","name":"tick","type":"int24"},{"internalType":"uint16","name":"observationIndex","type":"uint16"},{"internalType":"uint16","name":"observationCardinality","type":"uint16"},{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"},{"internalType":"uint8","name":"feeProtocol","type":"uint8"},{"internalType":"bool","name":"unlocked","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"}],"name":"snapshotCumulativesInside","outputs":[{"internalType":"int56","name":"tickCumulativeInside","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityInsideX128","type":"uint160"},{"internalType":"uint32","name":"secondsInside","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"zeroForOne","type":"bool"},{"internalType":"int256","name":"amountSpecified","type":"int256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[{"internalType":"int256","name":"amount0","type":"int256"},{"internalType":"int256","name":"amount1","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int16","name":"","type":"int16"}],"name":"tickBitmap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int24","name":"","type":"int24"}],"name":"ticks","outputs":[{"internalType":"uint128","name":"liquidityGross","type":"uint128"},{"internalType":"int128","name":"liquidityNet","type":"int128"},{"internalType":"uint256","name":"feeGrowthOutside0X128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthOutside1X128","type":"uint256"},{"internalType":"int56","name":"tickCumulativeOutside","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityOutsideX128","type":"uint160"},{"internalType":"uint32","name":"secondsOutside","type":"uint32"},{"internalType":"bool","name":"initialized","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"contractName":"UniswapV3Pool","methodIdentifiers":{"burn(int24,int24,uint128)":"0xa34123a7","collect(address,int24,int24,uint128,uint128)":"0x4f1eb3d8","collectProtocol(address,uint128,uint128)":"0x85b66729","factory()":"0xc45a0155","fee()":"0xddca3f43","feeGrowthGlobal0X128()":"0xf3058399","feeGrowthGlobal1X128()":"0x46141319","flash(address,uint256,uint256,bytes)":"0x490e6cbc","increaseObservationCardinalityNext(uint16)":"0x32148f67","initialize(uint160)":"0xf637731d","liquidity()":"0x1a686502","maxLiquidityPerTick()":"0x70cf754a","mint(address,int24,int24,uint128,bytes)":"0x3c8a7d8d","observations(uint256)":"0x252c09d7","observe(uint32[])":"0x883bdbfd","positions(bytes32)":"0x514ea4bf","protocolFees()":"0x1ad8b03b","setFeeProtocol(uint8,uint8)":"0x8206a4d1","slot0()":"0x3850c7bd","snapshotCumulativesInside(int24,int24)":"0xa38807f2","swap(address,bool,int256,uint160,bytes)":"0x128acb08","tickBitmap(int16)":"0x5339c296","tickSpacing()":"0xd0c93a7c","ticks(int24)":"0xf30dba93","token0()":"0x0dfe1681","token1()":"0xd21220a7"}}},"deployments":{},"manifest":"ethpm/3","name":"uniswap-v3","version":"v1.0.0"} \ No newline at end of file