├── src ├── _py_clob_client │ ├── __init__.py │ ├── headers │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── headers.cpython-312.pyc │ │ │ └── __init__.cpython-312.pyc │ │ └── headers.py │ ├── signing │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── hmac.cpython-312.pyc │ │ │ ├── eip712.cpython-312.pyc │ │ │ ├── model.cpython-312.pyc │ │ │ └── __init__.cpython-312.pyc │ │ ├── model.py │ │ ├── hmac.py │ │ └── eip712.py │ ├── http_helpers │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── helpers.cpython-312.pyc │ │ │ └── __init__.cpython-312.pyc │ │ └── helpers.py │ ├── order_builder │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-312.pyc │ │ │ ├── builder.cpython-312.pyc │ │ │ ├── helpers.cpython-312.pyc │ │ │ └── constants.cpython-312.pyc │ │ ├── helpers.py │ │ └── builder.py │ ├── __pycache__ │ │ ├── client.cpython-312.pyc │ │ ├── config.cpython-312.pyc │ │ ├── signer.cpython-312.pyc │ │ ├── __init__.cpython-312.pyc │ │ ├── clob_types.cpython-312.pyc │ │ ├── constants.cpython-312.pyc │ │ ├── endpoints.cpython-312.pyc │ │ ├── exceptions.cpython-312.pyc │ │ └── utilities.cpython-312.pyc │ ├── constants.py │ ├── signer.py │ ├── exceptions.py │ ├── endpoints.py │ ├── utilities.py │ ├── config.py │ ├── clob_types.py │ └── client.py ├── __init__.py ├── test │ ├── __init__.py │ ├── __pycache__ │ │ ├── draft2.cpython-312.pyc │ │ └── __init__.cpython-312.pyc │ ├── test_trade.py │ └── test_monitor.py ├── utils │ ├── __init__.py │ ├── __pycache__ │ │ ├── utils.cpython-312.pyc │ │ └── __init__.cpython-312.pyc │ ├── utils.py │ └── helpers.py ├── function │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-312.pyc │ │ ├── copy_trade.cpython-312.pyc │ │ ├── func_backtest.cpython-312.pyc │ │ ├── wallet_monitor.cpython-312.pyc │ │ └── wallet_backtest.cpython-312.pyc │ ├── func_copy_trade.py │ ├── func_monitor.py │ └── func_backtest.py ├── __pycache__ │ ├── config.cpython-312.pyc │ └── __init__.cpython-312.pyc ├── other │ ├── create_api_key.py │ └── set_approval.py ├── main_copy_trade.py ├── main_backtest.py ├── abi │ ├── FeeModule.json │ ├── NegRiskFeeModule.json │ ├── RelayHub.json │ ├── NegRiskAdapter.json │ ├── CtfExchange.json │ └── NegRiskCtfExchange.json └── main_search.py ├── assets └── outcome │ └── outcomes_here.txt ├── .gitignore ├── requirements.txt ├── LICENSE ├── .env.example └── README.md /src/_py_clob_client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/_py_clob_client/headers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/_py_clob_client/signing/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/_py_clob_client/http_helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/_py_clob_client/order_builder/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/outcome/outcomes_here.txt: -------------------------------------------------------------------------------- 1 | outcome data files in this dir 2 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Polymarket Trade Follower package. 3 | """ -------------------------------------------------------------------------------- /src/test/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test package for testing functionality. 3 | """ -------------------------------------------------------------------------------- /src/_py_clob_client/order_builder/constants.py: -------------------------------------------------------------------------------- 1 | BUY = "BUY" 2 | SELL = "SELL" 3 | -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility package containing helper functions. 3 | """ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/.gitignore -------------------------------------------------------------------------------- /src/function/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Monitoring package for tracking wallet activities. 3 | """ -------------------------------------------------------------------------------- /src/__pycache__/config.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/__pycache__/config.cpython-312.pyc -------------------------------------------------------------------------------- /src/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/test/__pycache__/draft2.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/test/__pycache__/draft2.cpython-312.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/utils.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/utils/__pycache__/utils.cpython-312.pyc -------------------------------------------------------------------------------- /src/test/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/test/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/utils/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/function/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/function/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/function/__pycache__/copy_trade.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/function/__pycache__/copy_trade.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/client.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/client.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/config.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/config.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/signer.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/signer.cpython-312.pyc -------------------------------------------------------------------------------- /src/function/__pycache__/func_backtest.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/function/__pycache__/func_backtest.cpython-312.pyc -------------------------------------------------------------------------------- /src/function/__pycache__/wallet_monitor.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/function/__pycache__/wallet_monitor.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/clob_types.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/clob_types.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/constants.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/constants.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/endpoints.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/endpoints.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/exceptions.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/exceptions.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/__pycache__/utilities.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/__pycache__/utilities.cpython-312.pyc -------------------------------------------------------------------------------- /src/function/__pycache__/wallet_backtest.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/function/__pycache__/wallet_backtest.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/signing/__pycache__/hmac.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/signing/__pycache__/hmac.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/headers/__pycache__/headers.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/headers/__pycache__/headers.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/signing/__pycache__/eip712.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/signing/__pycache__/eip712.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/signing/__pycache__/model.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/signing/__pycache__/model.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/headers/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/headers/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/signing/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/signing/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/http_helpers/__pycache__/helpers.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/http_helpers/__pycache__/helpers.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/http_helpers/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/http_helpers/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/order_builder/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/order_builder/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/order_builder/__pycache__/builder.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/order_builder/__pycache__/builder.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/order_builder/__pycache__/helpers.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/order_builder/__pycache__/helpers.cpython-312.pyc -------------------------------------------------------------------------------- /src/_py_clob_client/order_builder/__pycache__/constants.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcdx9999/polymarket-copy-trade/HEAD/src/_py_clob_client/order_builder/__pycache__/constants.cpython-312.pyc -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-dotenv>=0.19.0,<2.0.0 2 | web3>=5.31.3,<7.0.0 3 | websockets>=10.0.0,<12.0.0 4 | requests>=2.25.0,<3.0.0 5 | python-dateutil>=2.8.0,<3.0.0 6 | aiohttp>=3.8.0,<4.0.0 7 | asyncio>=3.4.3,<4.0.0 8 | -------------------------------------------------------------------------------- /src/_py_clob_client/signing/model.py: -------------------------------------------------------------------------------- 1 | from poly_eip712_structs import EIP712Struct, Address, String, Uint 2 | 3 | 4 | class ClobAuth(EIP712Struct): 5 | address = Address() 6 | timestamp = String() 7 | nonce = Uint() 8 | message = String() 9 | -------------------------------------------------------------------------------- /src/_py_clob_client/constants.py: -------------------------------------------------------------------------------- 1 | # Access levels 2 | L0 = 0 3 | L1 = 1 4 | L2 = 2 5 | 6 | 7 | CREDENTIAL_CREATION_WARNING = """🚨🚨🚨 8 | Your credentials CANNOT be recovered after they've been created. 9 | Be sure to store them safely! 10 | 🚨🚨🚨""" 11 | 12 | 13 | L1_AUTH_UNAVAILABLE = "A private key is needed to interact with this endpoint!" 14 | 15 | L2_AUTH_UNAVAILABLE = "API Credentials are needed to interact with this endpoint!" 16 | 17 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 18 | 19 | AMOY = 80002 20 | POLYGON = 137 21 | 22 | END_CURSOR = "LTE=" 23 | -------------------------------------------------------------------------------- /src/_py_clob_client/signer.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | 3 | 4 | class Signer: 5 | def __init__(self, private_key: str, chain_id: int): 6 | assert private_key is not None and chain_id is not None 7 | 8 | self.private_key = private_key 9 | self.account = Account.from_key(private_key) 10 | self.chain_id = chain_id 11 | 12 | def address(self): 13 | return self.account.address 14 | 15 | def get_chain_id(self): 16 | return self.chain_id 17 | 18 | def sign(self, message_hash): 19 | """ 20 | Signs a message hash 21 | """ 22 | return Account._sign_hash(message_hash, self.private_key).signature.hex() 23 | -------------------------------------------------------------------------------- /src/_py_clob_client/order_builder/helpers.py: -------------------------------------------------------------------------------- 1 | from math import floor, ceil 2 | from decimal import Decimal 3 | 4 | 5 | def round_down(x: float, sig_digits: int) -> float: 6 | return floor(x * (10**sig_digits)) / (10**sig_digits) 7 | 8 | 9 | def round_normal(x: float, sig_digits: int) -> float: 10 | return round(x * (10**sig_digits)) / (10**sig_digits) 11 | 12 | 13 | def round_up(x: float, sig_digits: int) -> float: 14 | return ceil(x * (10**sig_digits)) / (10**sig_digits) 15 | 16 | 17 | def to_token_decimals(x: float) -> int: 18 | f = (10**6) * x 19 | if decimal_places(f) > 0: 20 | f = round_normal(f, 0) 21 | return int(f) 22 | 23 | 24 | def decimal_places(x: float) -> int: 25 | return abs(Decimal(x.__str__()).as_tuple().exponent) 26 | -------------------------------------------------------------------------------- /src/_py_clob_client/signing/hmac.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import hashlib 3 | import base64 4 | 5 | 6 | def build_hmac_signature( 7 | secret: str, timestamp: str, method: str, requestPath: str, body=None 8 | ): 9 | """ 10 | Creates an HMAC signature by signing a payload with the secret 11 | """ 12 | base64_secret = base64.urlsafe_b64decode(secret) 13 | message = str(timestamp) + str(method) + str(requestPath) 14 | if body: 15 | # NOTE: Necessary to replace single quotes with double quotes 16 | # to generate the same hmac message as go and typescript 17 | message += str(body).replace("'", '"') 18 | 19 | h = hmac.new(base64_secret, bytes(message, "utf-8"), hashlib.sha256) 20 | 21 | # ensure base64 encoded 22 | return (base64.urlsafe_b64encode(h.digest())).decode("utf-8") 23 | -------------------------------------------------------------------------------- /src/utils/utils.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def get_target_position_size(address, token_id): 5 | """ 6 | returns size in shares 7 | """ 8 | url = f'https://data-api.polymarket.com/positions?user={address}&sizeThreshold=.1&limit=50&offset=0&sortBy=CURRENT&sortDirection=DESC' 9 | with requests.Session() as session: 10 | response = session.get(url) 11 | data = response.json() 12 | for position in data: 13 | if position['asset'] == token_id: 14 | return position['size'] 15 | return 0 16 | 17 | def get_position_all(address): 18 | """ 19 | returns size in shares 20 | """ 21 | url = f'https://data-api.polymarket.com/positions?user={address}&sizeThreshold=.1&limit=50&offset=0&sortBy=CURRENT&sortDirection=DESC' 22 | with requests.Session() as session: 23 | response = session.get(url) 24 | data = response.json() 25 | return data -------------------------------------------------------------------------------- /src/other/create_api_key.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os 3 | 4 | from py_clob_client.client import ClobClient 5 | from py_clob_client.constants import POLYGON 6 | 7 | 8 | def main(): 9 | host = "https://clob.polymarket.com" 10 | key = os.getenv("PK") 11 | chain_id = POLYGON 12 | funder = os.getenv("FUNDER") 13 | client = ClobClient(host, key=key, chain_id=chain_id, signature_type=2, funder=funder) 14 | 15 | try: 16 | api_creds = client.create_or_derive_api_creds() 17 | print("API Key:", api_creds.api_key) 18 | print("Secret:", api_creds.api_secret) 19 | print("Passphrase:", api_creds.api_passphrase) 20 | except Exception as e: 21 | print("Error creating API:", e) 22 | 23 | if __name__ == "__main__": 24 | # Set proxy first 25 | os.environ['HTTP_PROXY'] = 'http://localhost:15236' 26 | os.environ['HTTPS_PROXY'] = 'http://localhost:15236' 27 | main() -------------------------------------------------------------------------------- /src/_py_clob_client/signing/eip712.py: -------------------------------------------------------------------------------- 1 | from poly_eip712_structs import make_domain 2 | from eth_utils import keccak 3 | from py_order_utils.utils import prepend_zx 4 | 5 | from .model import ClobAuth 6 | from ..signer import Signer 7 | 8 | CLOB_DOMAIN_NAME = "ClobAuthDomain" 9 | CLOB_VERSION = "1" 10 | MSG_TO_SIGN = "This message attests that I control the given wallet" 11 | 12 | 13 | def get_clob_auth_domain(chain_id: int): 14 | return make_domain(name=CLOB_DOMAIN_NAME, version=CLOB_VERSION, chainId=chain_id) 15 | 16 | 17 | def sign_clob_auth_message(signer: Signer, timestamp: int, nonce: int) -> str: 18 | clob_auth_msg = ClobAuth( 19 | address=signer.address(), 20 | timestamp=str(timestamp), 21 | nonce=nonce, 22 | message=MSG_TO_SIGN, 23 | ) 24 | chain_id = signer.get_chain_id() 25 | auth_struct_hash = prepend_zx( 26 | keccak(clob_auth_msg.signable_bytes(get_clob_auth_domain(chain_id))).hex() 27 | ) 28 | return prepend_zx(signer.sign(auth_struct_hash)) 29 | -------------------------------------------------------------------------------- /src/_py_clob_client/exceptions.py: -------------------------------------------------------------------------------- 1 | from requests import Response 2 | 3 | 4 | class PolyException(Exception): 5 | def __init__(self, msg): 6 | self.msg = msg 7 | 8 | 9 | class PolyApiException(PolyException): 10 | def __init__(self, resp: Response = None, error_msg=None): 11 | assert resp is not None or error_msg is not None 12 | if resp is not None: 13 | self.status_code = resp.status_code 14 | self.error_msg = self._get_message(resp) 15 | if error_msg is not None: 16 | self.error_msg = error_msg 17 | self.status_code = None 18 | 19 | def _get_message(self, resp: Response): 20 | try: 21 | return resp.json() 22 | except Exception: 23 | return resp.text 24 | 25 | def __repr__(self): 26 | return "PolyApiException[status_code={}, error_message={}]".format( 27 | self.status_code, self.error_msg 28 | ) 29 | 30 | def __str__(self): 31 | return self.__repr__() 32 | -------------------------------------------------------------------------------- /src/utils/helpers.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import Union, Dict 3 | 4 | 5 | def format_decimal(value: Union[int, float, str, Decimal]) -> Decimal: 6 | """ 7 | Format a number as Decimal with proper precision 8 | """ 9 | return Decimal(str(value)) 10 | 11 | 12 | def calculate_slippage_price( 13 | base_price: Decimal, 14 | slippage: Decimal, 15 | is_buy: bool 16 | ) -> Decimal: 17 | """ 18 | Calculate price adjusted for slippage 19 | """ 20 | if is_buy: 21 | return base_price * (1 + slippage) 22 | return base_price * (1 - slippage) 23 | 24 | 25 | def validate_trade_data(trade_data: Dict) -> bool: 26 | """ 27 | Validate trade data contains all required fields 28 | """ 29 | required_fields = ['marketId', 'side', 'price', 'size'] 30 | return all(field in trade_data for field in required_fields) 31 | 32 | 33 | def format_log_message(message: str, data: Dict = None) -> str: 34 | """ 35 | Format log message with optional data 36 | """ 37 | if data: 38 | return f"{message} - {data}" 39 | return message -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 JKS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Wallet Configuration 2 | PRIVATE_KEY=your_private_key 3 | WALLET_ADDRESS=your_wallet_address 4 | 5 | # Network Configuration 6 | RPC_URL=your_rpc_url 7 | WS_URL=your_ws_url 8 | 9 | # Polygonscan Configuration 10 | POLYGONSCAN_API_KEY=your_polygonscan_api_key 11 | 12 | # CLOB Configuration 13 | PK=your_private_key 14 | PUBKEY=your_public_key 15 | FUNDER=your_funder_address 16 | CLOB_API_KEY=your_clob_api_key 17 | CLOB_SECRET=your_clob_secret 18 | CLOB_PASS_PHRASE=your_clob_pass_phrase 19 | 20 | # Polymarket Configuration 21 | MATCH_ORDERS_SIGNATURE=d2539b37 22 | 23 | # Trading Parameters 24 | MIN_ORDER_SIZE=10 25 | MAX_ORDER_SIZE=1000 26 | SLIPPAGE_TOLERANCE=0.01 27 | FOLLOW_DELAY=1 28 | 29 | # Test Configuration 30 | TEST_WALLET=your_test_wallet_address 31 | TEST_TOKEN_ID=your_test_token_id 32 | TEST_ORDER_AMOUNT=1000000 33 | TEST_MIN_ORDER=10 34 | TEST_MAX_ORDER=1000 35 | TEST_SLIPPAGE=0.01 36 | TEST_DELAY=1 37 | 38 | # Production Configuration 39 | TARGET_WALLET=wallet_address_to_follow 40 | PROD_MIN_ORDER=100 41 | PROD_MAX_ORDER=10000 42 | PROD_SLIPPAGE=0.005 43 | PROD_DELAY=2 44 | 45 | # Proxy Configuration 46 | HTTP_PROXY= 47 | HTTPS_PROXY= -------------------------------------------------------------------------------- /src/_py_clob_client/endpoints.py: -------------------------------------------------------------------------------- 1 | TIME = "/time" 2 | CREATE_API_KEY = "/auth/api-key" 3 | GET_API_KEYS = "/auth/api-keys" 4 | DELETE_API_KEY = "/auth/api-key" 5 | DERIVE_API_KEY = "/auth/derive-api-key" 6 | TRADES = "/data/trades" 7 | GET_ORDER_BOOK = "/book" 8 | GET_ORDER_BOOKS = "/books" 9 | GET_ORDER = "/data/order/" 10 | ORDERS = "/data/orders" 11 | POST_ORDER = "/order" 12 | CANCEL = "/order" 13 | CANCEL_ORDERS = "/orders" 14 | CANCEL_ALL = "/cancel-all" 15 | CANCEL_MARKET_ORDERS = "/cancel-market-orders" 16 | MID_POINT = "/midpoint" 17 | MID_POINTS = "/midpoints" 18 | PRICE = "/price" 19 | GET_PRICES = "/prices" 20 | GET_SPREAD = "/spread" 21 | GET_SPREADS = "/spreads" 22 | GET_LAST_TRADE_PRICE = "/last-trade-price" 23 | GET_LAST_TRADES_PRICES = "/last-trades-prices" 24 | GET_NOTIFICATIONS = "/notifications" 25 | DROP_NOTIFICATIONS = "/notifications" 26 | GET_BALANCE_ALLOWANCE = "/balance-allowance" 27 | UPDATE_BALANCE_ALLOWANCE = "/balance-allowance/update" 28 | IS_ORDER_SCORING = "/order-scoring" 29 | ARE_ORDERS_SCORING = "/orders-scoring" 30 | GET_TICK_SIZE = "/tick-size" 31 | GET_NEG_RISK = "/neg-risk" 32 | GET_SAMPLING_SIMPLIFIED_MARKETS = "/sampling-simplified-markets" 33 | GET_SAMPLING_MARKETS = "/sampling-markets" 34 | GET_SIMPLIFIED_MARKETS = "/simplified-markets" 35 | GET_MARKETS = "/markets" 36 | GET_MARKET = "/markets/" 37 | GET_MARKET_TRADES_EVENTS = "/live-activity/events/" 38 | -------------------------------------------------------------------------------- /src/_py_clob_client/utilities.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | from .clob_types import OrderBookSummary, OrderSummary, TickSize 4 | 5 | 6 | def parse_raw_orderbook_summary(raw_obs: any) -> OrderBookSummary: 7 | bids = [] 8 | for bid in raw_obs["bids"]: 9 | bids.append(OrderSummary(size=bid["size"], price=bid["price"])) 10 | 11 | asks = [] 12 | for ask in raw_obs["asks"]: 13 | asks.append(OrderSummary(size=ask["size"], price=ask["price"])) 14 | 15 | orderbookSummary = OrderBookSummary( 16 | market=raw_obs["market"], 17 | asset_id=raw_obs["asset_id"], 18 | timestamp=raw_obs["timestamp"], 19 | bids=bids, 20 | asks=asks, 21 | hash=raw_obs["hash"], 22 | ) 23 | 24 | return orderbookSummary 25 | 26 | 27 | def generate_orderbook_summary_hash(orderbook: OrderBookSummary) -> str: 28 | orderbook.hash = "" 29 | hash = hashlib.sha1(str(orderbook.json).encode("utf-8")).hexdigest() 30 | orderbook.hash = hash 31 | return hash 32 | 33 | 34 | def order_to_json(order, owner, orderType) -> dict: 35 | return {"order": order.dict(), "owner": owner, "orderType": orderType} 36 | 37 | 38 | def is_tick_size_smaller(a: TickSize, b: TickSize) -> bool: 39 | return float(a) < float(b) 40 | 41 | 42 | def price_valid(price: float, tick_size: TickSize) -> bool: 43 | return price >= float(tick_size) and price <= 1 - float(tick_size) 44 | -------------------------------------------------------------------------------- /src/test/test_trade.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import os 4 | import sys 5 | from dotenv import load_dotenv 6 | 7 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) 8 | 9 | from src.function.func_copy_trade import PolymarketTrader 10 | 11 | # Set proxy first 12 | # os.environ['HTTP_PROXY'] = os.getenv('HTTP_PROXY') 13 | # os.environ['HTTPS_PROXY'] = os.getenv('HTTPS_PROXY') 14 | 15 | # Setup logging 16 | logging.basicConfig( 17 | level=logging.INFO, 18 | format='%(asctime)s - %(levelname)s - %(message)s' 19 | ) 20 | logger = logging.getLogger(__name__) 21 | 22 | 23 | async def main(): 24 | # Initialize trader 25 | trader = PolymarketTrader() 26 | 27 | try: 28 | # Get market details from environment 29 | token_id = int(os.getenv('TEST_TOKEN_ID')) 30 | amount = int(os.getenv('TEST_ORDER_AMOUNT')) 31 | 32 | logger.info(f"Testing trade for market {token_id} with amount {amount}") 33 | 34 | # Create and submit orders 35 | await trader.place_order( 36 | token_id=token_id, 37 | side=0, # BUY:0, SELL:1 38 | amount=amount 39 | ) 40 | 41 | except Exception as e: 42 | logger.error(f"Error in main: {str(e)}") 43 | 44 | 45 | if __name__ == "__main__": 46 | try: 47 | asyncio.run(main()) 48 | except KeyboardInterrupt: 49 | logger.info("Program stopped by user") 50 | -------------------------------------------------------------------------------- /src/_py_clob_client/config.py: -------------------------------------------------------------------------------- 1 | from .clob_types import ContractConfig 2 | 3 | 4 | def get_contract_config(chainID: int, neg_risk: bool = False) -> ContractConfig: 5 | """ 6 | Get the contract configuration for the chain 7 | """ 8 | 9 | CONFIG = { 10 | 137: ContractConfig( 11 | exchange="0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E", 12 | collateral="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", 13 | conditional_tokens="0x4D97DCd97eC945f40cF65F87097ACe5EA0476045", 14 | ), 15 | 80002: ContractConfig( 16 | exchange="0xdFE02Eb6733538f8Ea35D585af8DE5958AD99E40", 17 | collateral="0x9c4e1703476e875070ee25b56a58b008cfb8fa78", 18 | conditional_tokens="0x69308FB512518e39F9b16112fA8d994F4e2Bf8bB", 19 | ), 20 | } 21 | 22 | NEG_RISK_CONFIG = { 23 | 137: ContractConfig( 24 | exchange="0xC5d563A36AE78145C45a50134d48A1215220f80a", 25 | collateral="0x2791bca1f2de4661ed88a30c99a7a9449aa84174", 26 | conditional_tokens="0x4D97DCd97eC945f40cF65F87097ACe5EA0476045", 27 | ), 28 | 80002: ContractConfig( 29 | exchange="0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296", 30 | collateral="0x9c4e1703476e875070ee25b56a58b008cfb8fa78", 31 | conditional_tokens="0x69308FB512518e39F9b16112fA8d994F4e2Bf8bB", 32 | ), 33 | } 34 | 35 | if neg_risk: 36 | config = NEG_RISK_CONFIG.get(chainID) 37 | else: 38 | config = CONFIG.get(chainID) 39 | if config is None: 40 | raise Exception("Invalid chainID: ${}".format(chainID)) 41 | 42 | return config 43 | -------------------------------------------------------------------------------- /src/_py_clob_client/headers/headers.py: -------------------------------------------------------------------------------- 1 | from ..clob_types import ApiCreds, RequestArgs 2 | from ..signing.hmac import build_hmac_signature 3 | from ..signer import Signer 4 | from ..signing.eip712 import sign_clob_auth_message 5 | from datetime import datetime 6 | 7 | POLY_ADDRESS = "POLY_ADDRESS" 8 | POLY_SIGNATURE = "POLY_SIGNATURE" 9 | POLY_TIMESTAMP = "POLY_TIMESTAMP" 10 | POLY_NONCE = "POLY_NONCE" 11 | POLY_API_KEY = "POLY_API_KEY" 12 | POLY_PASSPHRASE = "POLY_PASSPHRASE" 13 | 14 | 15 | def create_level_1_headers(signer: Signer, nonce: int = None): 16 | """ 17 | Creates Level 1 Poly headers for a request 18 | """ 19 | timestamp = int(datetime.now().timestamp()) 20 | 21 | n = 0 22 | if nonce is not None: 23 | n = nonce 24 | 25 | signature = sign_clob_auth_message(signer, timestamp, n) 26 | headers = { 27 | POLY_ADDRESS: signer.address(), 28 | POLY_SIGNATURE: signature, 29 | POLY_TIMESTAMP: str(timestamp), 30 | POLY_NONCE: str(n), 31 | } 32 | 33 | return headers 34 | 35 | 36 | def create_level_2_headers(signer: Signer, creds: ApiCreds, request_args: RequestArgs): 37 | """ 38 | Creates Level 2 Poly headers for a request 39 | """ 40 | timestamp = int(datetime.now().timestamp()) 41 | 42 | hmac_sig = build_hmac_signature( 43 | creds.api_secret, 44 | timestamp, 45 | request_args.method, 46 | request_args.request_path, 47 | request_args.body, 48 | ) 49 | 50 | return { 51 | POLY_ADDRESS: signer.address(), 52 | POLY_SIGNATURE: hmac_sig, 53 | POLY_TIMESTAMP: str(timestamp), 54 | POLY_API_KEY: creds.api_key, 55 | POLY_PASSPHRASE: creds.api_passphrase, 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 Polymarket one-stop Copy Trading Bot 2 | 3 | A Python-based trading bot that follows and replicates trades from specified wallets on Polymarket. It also includes backtesting tools to identify and validate profitable wallets before copy trading. 4 | 5 | ## ⚙️ Requirements 6 | 7 | - Python 3.8+ 8 | - Polygon Network wallet with USDC 9 | - Polygon RPC endpoint 10 | 11 | ## 🚀 Installation 12 | 13 | 1. Clone the repository: 14 | ```bash 15 | git clone https://github.com/yourusername/polymarket-copy-trade.git 16 | cd polymarket-copy-trade 17 | ``` 18 | 19 | 2. Install dependencies: 20 | ```bash 21 | pip install -r requirements.txt 22 | ``` 23 | 24 | 3. Set up environment variables: 25 | ```bash 26 | cp .env.example .env 27 | ``` 28 | Edit `.env` with your configuration. 29 | 30 | ## 📝 Usage 31 | 32 | ### Main Functions 33 | 34 | - Copy trading from target wallet: 35 | ```bash 36 | python src/main_copy_trade.py 37 | ``` 38 | 39 | - Backtest wallet trading history: 40 | ```bash 41 | python src/main_backtest.py 42 | ``` 43 | 44 | ### Test Functions 45 | 46 | The `src/test` directory contains individual test files for specific functionalities: 47 | 48 | - `test_trade.py`: Test trade 49 | - `test_monitor.py`: Test monitor 50 | 51 | ## 🔧 Modifications 52 | 53 | - Enhanced `_py_clob_client` with additional features: 54 | - Modified `create_market_order` functionality 55 | - Modified order amount calculations for both BUY/SELL sides 56 | - Improved transaction data decoding 57 | 58 | ## ⚠️ Disclaimer 59 | 60 | This bot is for educational purposes only. Use at your own risk. Trading cryptocurrency carries significant risks. 61 | 62 | ## 💬 Contact 63 | TG: @bitsong999 64 | 65 | ## 📝 TODO 66 | - effectively searching for smart wallet and build pools 67 | - periodically backtesting 68 | -------------------------------------------------------------------------------- /src/test/test_monitor.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import os 4 | import sys 5 | 6 | from decimal import Decimal 7 | from typing import Dict 8 | from dotenv import load_dotenv 9 | 10 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) 11 | 12 | from src.function.func_monitor import WalletMonitor 13 | 14 | # Set proxy first 15 | os.environ['HTTP_PROXY'] = os.getenv('HTTP_PROXY') 16 | os.environ['HTTPS_PROXY'] = os.getenv('HTTPS_PROXY') 17 | 18 | # Load environment variables 19 | # os.environ.clear() 20 | # load_dotenv() 21 | 22 | # Get test wallet from environment 23 | TEST_WALLET = os.getenv('TEST_WALLET') 24 | if not TEST_WALLET: 25 | raise ValueError("TEST_WALLET not set in .env file") 26 | 27 | logging.basicConfig( 28 | level=logging.INFO, 29 | format='%(asctime)s - %(levelname)s - %(message)s' 30 | ) 31 | logger = logging.getLogger(__name__) 32 | 33 | # Callback to process trade details 34 | async def handle_trade(trade_data: Dict): 35 | try: 36 | market_id = trade_data.get("tokenId", "Unknown TokenId") 37 | side = trade_data.get("side", "Unknown") 38 | maker = trade_data.get("maker", "Unknown") 39 | size = Decimal(str(trade_data.get("makerAmount", 0))) 40 | 41 | logger.info(f""" 42 | Trade Details: 43 | ------------- 44 | Token ID: {market_id} 45 | Side: {side} 46 | Maker: {maker} 47 | Size: {size} 48 | """) 49 | except Exception as e: 50 | logger.error(f"Error processing trade details: {str(e)}") 51 | 52 | async def main(): 53 | # Create monitor instance with our callback and test wallet 54 | monitor = WalletMonitor(handle_trade, mode='test') 55 | 56 | try: 57 | logger.info(f"Started monitoring test wallet: {TEST_WALLET}") 58 | await monitor.start() 59 | except KeyboardInterrupt: 60 | logger.info("Stopping monitor...") 61 | await monitor.stop() 62 | 63 | if __name__ == "__main__": 64 | try: 65 | asyncio.run(main()) 66 | except KeyboardInterrupt: 67 | logger.info("Program stopped by user") 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/main_copy_trade.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import sys 4 | import os 5 | from typing import Dict 6 | 7 | from function.func_monitor import WalletMonitor 8 | from function.func_copy_trade import PolymarketTrader 9 | 10 | # Configure logging 11 | logging.basicConfig( 12 | level=logging.INFO, 13 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 14 | handlers=[ 15 | logging.StreamHandler(sys.stdout), 16 | logging.FileHandler('polymarket_follower.log') 17 | ] 18 | ) 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | # Main application class for following trades on Polymarket 23 | class PolymarketFollower: 24 | def __init__(self): 25 | self.trader = PolymarketTrader() 26 | self.monitor = WalletMonitor(self.handle_trade) 27 | 28 | # Handle trades from monitored wallet 29 | async def handle_trade(self, trade_data: Dict): 30 | """ 31 | params: 32 | trade_data: Trade data from the monitored wallet 33 | """ 34 | logger.info(f"New trade detected: {trade_data}") 35 | await self.trader.execute_trade(trade_data) 36 | 37 | # Start the application 38 | async def start(self): 39 | try: 40 | # Initialize trader 41 | await self.trader.initialize() 42 | 43 | # Start monitoring 44 | logger.info("Starting wallet monitor...") 45 | await self.monitor.start() 46 | 47 | except KeyboardInterrupt: 48 | logger.info("Shutting down...") 49 | except Exception as e: 50 | logger.error(f"Application error: {str(e)}") 51 | finally: 52 | await self.cleanup() 53 | 54 | async def cleanup(self): 55 | """Clean up resources.""" 56 | await self.monitor.stop() 57 | await self.trader.close() 58 | 59 | 60 | async def main(): 61 | app = PolymarketFollower() 62 | await app.start() 63 | 64 | 65 | if __name__ == "__main__": 66 | os.environ['HTTP_PROXY'] = os.getenv('HTTP_PROXY') 67 | os.environ['HTTPS_PROXY'] = os.getenv('HTTPS_PROXY') 68 | try: 69 | asyncio.run(main()) 70 | except KeyboardInterrupt: 71 | logger.info("Application stopped by user") -------------------------------------------------------------------------------- /src/main_backtest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import pandas as pd 4 | from dotenv import load_dotenv 5 | from datetime import datetime 6 | from pprint import pprint 7 | 8 | from _py_clob_client.client import ClobClient 9 | from _py_clob_client.clob_types import ApiCreds 10 | from py_clob_client.constants import POLYGON 11 | from function.func_backtest import WalletBacktest 12 | 13 | # Configure logging 14 | logging.basicConfig( 15 | level=logging.INFO, 16 | format='%(asctime)s - %(levelname)s - %(message)s' 17 | ) 18 | logger = logging.getLogger(__name__) 19 | 20 | def get_polymarket_transactions(address: str, output_file: str): 21 | """ 22 | Get all USDC token transfers for an address and save to CSV 23 | 24 | Args: 25 | address: The address to get transactions for 26 | output_file: Output CSV file name 27 | """ 28 | # Get Polygonscan API key from environment 29 | api_key = os.getenv("POLYGONSCAN_API_KEY") 30 | if not api_key: 31 | raise ValueError("POLYGONSCAN_API_KEY not set in environment") 32 | 33 | # Initialize ClobClient 34 | creds = ApiCreds( 35 | api_key=os.getenv("CLOB_API_KEY"), 36 | api_secret=os.getenv("CLOB_SECRET"), 37 | api_passphrase=os.getenv("CLOB_PASS_PHRASE"), 38 | ) 39 | client = ClobClient( 40 | host="https://clob.polymarket.com", 41 | key=os.getenv('PK'), 42 | chain_id=POLYGON, 43 | creds=creds 44 | ) 45 | 46 | # Initialize WalletBacktest 47 | backtest = WalletBacktest(api_key, client) 48 | 49 | # Download all transactions 50 | # print(f"Downloading target transactions for {address}...") 51 | transactions = backtest.download_transactions(address) 52 | 53 | if not transactions: 54 | print("No target transactions found") 55 | return 56 | 57 | # Process transactions and save to CSV 58 | df = backtest.process_transactions(transactions, address) 59 | backtest.save_to_csv(df, output_file) 60 | 61 | 62 | if __name__ == "__main__": 63 | load_dotenv() 64 | os.environ['HTTP_PROXY'] = os.getenv('HTTP_PROXY') 65 | os.environ['HTTPS_PROXY'] = os.getenv('HTTPS_PROXY') 66 | counter = 0 67 | 68 | now = datetime.now() 69 | 70 | target_wallets_file = "" 71 | wallets_df = pd.read_csv(target_wallets_file) 72 | 73 | # Process each wallet address 74 | for wallet in wallets_df['wallet']: 75 | counter += 1 76 | print(f"\nProcessing wallet: {wallet} || ({counter}/{len(wallets_df)})") 77 | output_file = f"assets/outcome/backtest/{wallet}.csv" 78 | try: 79 | get_polymarket_transactions(wallet, output_file) 80 | except Exception as e: 81 | print(f"Error processing wallet {wallet}: {e}") 82 | continue 83 | 84 | logger.info(f"Total time: {datetime.now() - now}") 85 | 86 | -------------------------------------------------------------------------------- /src/_py_clob_client/clob_types.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from dataclasses import dataclass, asdict 3 | from json import dumps 4 | from typing import Literal, Optional 5 | 6 | from .constants import ZERO_ADDRESS 7 | 8 | 9 | @dataclass 10 | class ApiCreds: 11 | api_key: str 12 | api_secret: str 13 | api_passphrase: str 14 | 15 | 16 | @dataclass 17 | class RequestArgs: 18 | method: str 19 | request_path: str 20 | body: Any = None 21 | 22 | 23 | @dataclass 24 | class BookParams: 25 | token_id: str 26 | side: str = "" 27 | 28 | 29 | @dataclass 30 | class OrderArgs: 31 | token_id: str 32 | """ 33 | TokenID of the Conditional token asset being traded 34 | """ 35 | 36 | price: float 37 | """ 38 | Price used to create the order 39 | """ 40 | 41 | size: float 42 | """ 43 | Size in terms of the ConditionalToken 44 | """ 45 | 46 | side: str 47 | """ 48 | Side of the order 49 | """ 50 | 51 | fee_rate_bps: int = 0 52 | """ 53 | Fee rate, in basis points, charged to the order maker, charged on proceeds 54 | """ 55 | 56 | nonce: int = 0 57 | """ 58 | Nonce used for onchain cancellations 59 | """ 60 | 61 | expiration: int = 0 62 | """ 63 | Timestamp after which the order is expired. 64 | """ 65 | 66 | taker: str = ZERO_ADDRESS 67 | """ 68 | Address of the order taker. The zero address is used to indicate a public order 69 | """ 70 | 71 | 72 | @dataclass 73 | class MarketOrderArgs: 74 | token_id: str 75 | """ 76 | TokenID of the Conditional token asset being traded 77 | """ 78 | 79 | amount: float 80 | """ 81 | Amount in terms of Collateral 82 | """ 83 | 84 | side: str 85 | """ 86 | Side of the order 87 | """ 88 | 89 | price: float = 0 90 | """ 91 | Price used to create the order 92 | """ 93 | 94 | fee_rate_bps: int = 0 95 | """ 96 | Fee rate, in basis points, charged to the order maker, charged on proceeds 97 | """ 98 | 99 | nonce: int = 0 100 | """ 101 | Nonce used for onchain cancellations 102 | """ 103 | 104 | taker: str = ZERO_ADDRESS 105 | """ 106 | Address of the order taker. The zero address is used to indicate a public order 107 | """ 108 | 109 | 110 | @dataclass 111 | class TradeParams: 112 | id: str = None 113 | maker_address: str = None 114 | market: str = None 115 | asset_id: str = None 116 | before: int = None 117 | after: int = None 118 | 119 | 120 | @dataclass 121 | class OpenOrderParams: 122 | id: str = None 123 | market: str = None 124 | asset_id: str = None 125 | 126 | 127 | @dataclass 128 | class DropNotificationParams: 129 | ids: list[str] = None 130 | 131 | 132 | @dataclass 133 | class OrderSummary: 134 | price: str = None 135 | size: str = None 136 | 137 | @property 138 | def __dict__(self): 139 | return asdict(self) 140 | 141 | @property 142 | def json(self): 143 | return dumps(self.__dict__) 144 | 145 | 146 | @dataclass 147 | class OrderBookSummary: 148 | market: str = None 149 | asset_id: str = None 150 | timestamp: str = None 151 | bids: list[OrderSummary] = None 152 | asks: list[OrderSummary] = None 153 | hash: str = None 154 | 155 | @property 156 | def __dict__(self): 157 | return asdict(self) 158 | 159 | @property 160 | def json(self): 161 | return dumps(self.__dict__, separators=(",", ":")) 162 | 163 | 164 | class AssetType(enumerate): 165 | COLLATERAL = "COLLATERAL" 166 | CONDITIONAL = "CONDITIONAL" 167 | 168 | 169 | @dataclass 170 | class BalanceAllowanceParams: 171 | asset_type: AssetType = None 172 | token_id: str = None 173 | signature_type: int = -1 174 | 175 | 176 | class OrderType(enumerate): 177 | GTC = "GTC" 178 | FOK = "FOK" 179 | GTD = "GTD" 180 | 181 | 182 | @dataclass 183 | class OrderScoringParams: 184 | orderId: str 185 | 186 | 187 | @dataclass 188 | class OrdersScoringParams: 189 | orderIds: list[str] 190 | 191 | 192 | TickSize = Literal["0.1", "0.01", "0.001", "0.0001"] 193 | 194 | 195 | @dataclass 196 | class CreateOrderOptions: 197 | tick_size: TickSize 198 | neg_risk: bool 199 | 200 | 201 | @dataclass 202 | class PartialCreateOrderOptions: 203 | tick_size: Optional[TickSize] = None 204 | neg_risk: Optional[bool] = None 205 | 206 | 207 | @dataclass 208 | class RoundConfig: 209 | price: float 210 | size: float 211 | amount: float 212 | 213 | 214 | @dataclass 215 | class ContractConfig: 216 | """ 217 | Contract Configuration 218 | """ 219 | 220 | exchange: str 221 | """ 222 | The exchange contract responsible for matching orders 223 | """ 224 | 225 | collateral: str 226 | """ 227 | The ERC20 token used as collateral for the exchange's markets 228 | """ 229 | 230 | conditional_tokens: str 231 | """ 232 | The ERC1155 conditional tokens contract 233 | """ 234 | -------------------------------------------------------------------------------- /src/abi/FeeModule.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_exchange","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"NotAdmin","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeRefunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":true,"internalType":"address","name":"newAdminAddress","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":true,"internalType":"address","name":"removedAdmin","type":"address"}],"name":"RemovedAdmin","type":"event"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"admins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateral","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ctf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exchange","outputs":[{"internalType":"contract IExchange","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"takerOrder","type":"tuple"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"makerOrders","type":"tuple[]"},{"internalType":"uint256","name":"takerFillAmount","type":"uint256"},{"internalType":"uint256[]","name":"makerFillAmounts","type":"uint256[]"},{"internalType":"uint256","name":"makerFeeRate","type":"uint256"}],"name":"matchOrders","outputs":[],"stateMutability":"nonpayable","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":"nonpayable","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":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawFees","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /src/abi/NegRiskFeeModule.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_negRiskCtfExchange","type":"address"},{"internalType":"address","name":"_negRiskAdapter","type":"address"},{"internalType":"address","name":"_ctf","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"NotAdmin","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeRefunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":true,"internalType":"address","name":"newAdminAddress","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":true,"internalType":"address","name":"removedAdmin","type":"address"}],"name":"RemovedAdmin","type":"event"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"admins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateral","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ctf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exchange","outputs":[{"internalType":"contract IExchange","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"takerOrder","type":"tuple"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"makerOrders","type":"tuple[]"},{"internalType":"uint256","name":"takerFillAmount","type":"uint256"},{"internalType":"uint256[]","name":"makerFillAmounts","type":"uint256[]"},{"internalType":"uint256","name":"makerFeeRate","type":"uint256"}],"name":"matchOrders","outputs":[],"stateMutability":"nonpayable","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":"nonpayable","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":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawFees","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /src/_py_clob_client/http_helpers/helpers.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from py_clob_client.clob_types import ( 4 | DropNotificationParams, 5 | BalanceAllowanceParams, 6 | OrderScoringParams, 7 | OrdersScoringParams, 8 | TradeParams, 9 | OpenOrderParams, 10 | ) 11 | 12 | from ..exceptions import PolyApiException 13 | 14 | GET = "GET" 15 | POST = "POST" 16 | DELETE = "DELETE" 17 | PUT = "PUT" 18 | 19 | 20 | def overloadHeaders(method: str, headers: dict) -> dict: 21 | if headers is None: 22 | headers = dict() 23 | headers["User-Agent"] = "py_clob_client" 24 | 25 | headers["Accept"] = "*/*" 26 | headers["Connection"] = "keep-alive" 27 | headers["Content-Type"] = "application/json" 28 | 29 | if method == GET: 30 | headers["Accept-Encoding"] = "gzip" 31 | 32 | return headers 33 | 34 | 35 | def request(endpoint: str, method: str, headers=None, data=None): 36 | try: 37 | headers = overloadHeaders(method, headers) 38 | resp = requests.request( 39 | method=method, url=endpoint, headers=headers, json=data if data else None 40 | ) 41 | if resp.status_code != 200: 42 | raise PolyApiException(resp) 43 | 44 | try: 45 | return resp.json() 46 | except requests.JSONDecodeError: 47 | return resp.text 48 | 49 | except requests.RequestException: 50 | raise PolyApiException(error_msg="Request exception!") 51 | 52 | 53 | def post(endpoint, headers=None, data=None): 54 | return request(endpoint, POST, headers, data) 55 | 56 | 57 | def get(endpoint, headers=None, data=None): 58 | return request(endpoint, GET, headers, data) 59 | 60 | 61 | def delete(endpoint, headers=None, data=None): 62 | return request(endpoint, DELETE, headers, data) 63 | 64 | 65 | def build_query_params(url: str, param: str, val: str) -> str: 66 | url_with_params = url 67 | last = url_with_params[-1] 68 | # if last character in url string == "?", append the param directly: api.com?param=value 69 | if last == "?": 70 | url_with_params = "{}{}={}".format(url_with_params, param, val) 71 | else: 72 | # else add "&", then append the param 73 | url_with_params = "{}&{}={}".format(url_with_params, param, val) 74 | return url_with_params 75 | 76 | 77 | def add_query_trade_params( 78 | base_url: str, params: TradeParams = None, next_cursor="MA==" 79 | ) -> str: 80 | """ 81 | Adds query parameters to a url 82 | """ 83 | url = base_url 84 | if params: 85 | url = url + "?" 86 | if params.market: 87 | url = build_query_params(url, "market", params.market) 88 | if params.asset_id: 89 | url = build_query_params(url, "asset_id", params.asset_id) 90 | if params.after: 91 | url = build_query_params(url, "after", params.after) 92 | if params.before: 93 | url = build_query_params(url, "before", params.before) 94 | if params.maker_address: 95 | url = build_query_params(url, "maker_address", params.maker_address) 96 | if params.id: 97 | url = build_query_params(url, "id", params.id) 98 | if next_cursor: 99 | url = build_query_params(url, "next_cursor", next_cursor) 100 | return url 101 | 102 | 103 | def add_query_open_orders_params( 104 | base_url: str, params: OpenOrderParams = None, next_cursor="MA==" 105 | ) -> str: 106 | """ 107 | Adds query parameters to a url 108 | """ 109 | url = base_url 110 | if params: 111 | url = url + "?" 112 | if params.market: 113 | url = build_query_params(url, "market", params.market) 114 | if params.asset_id: 115 | url = build_query_params(url, "asset_id", params.asset_id) 116 | if params.id: 117 | url = build_query_params(url, "id", params.id) 118 | if next_cursor: 119 | url = build_query_params(url, "next_cursor", next_cursor) 120 | return url 121 | 122 | 123 | def drop_notifications_query_params( 124 | base_url: str, params: DropNotificationParams = None 125 | ) -> str: 126 | """ 127 | Adds query parameters to a url 128 | """ 129 | url = base_url 130 | if params: 131 | url = url + "?" 132 | if params.ids: 133 | url = build_query_params(url, "ids", ",".join(params.ids)) 134 | return url 135 | 136 | 137 | def add_balance_allowance_params_to_url( 138 | base_url: str, params: BalanceAllowanceParams = None 139 | ) -> str: 140 | """ 141 | Adds query parameters to a url 142 | """ 143 | url = base_url 144 | if params: 145 | url = url + "?" 146 | if params.asset_type: 147 | url = build_query_params(url, "asset_type", params.asset_type.__str__()) 148 | if params.token_id: 149 | url = build_query_params(url, "token_id", params.token_id) 150 | if params.signature_type is not None: 151 | url = build_query_params(url, "signature_type", params.signature_type) 152 | return url 153 | 154 | 155 | def add_order_scoring_params_to_url( 156 | base_url: str, params: OrderScoringParams = None 157 | ) -> str: 158 | """ 159 | Adds query parameters to a url 160 | """ 161 | url = base_url 162 | if params: 163 | url = url + "?" 164 | if params.orderId: 165 | url = build_query_params(url, "order_id", params.orderId) 166 | return url 167 | 168 | 169 | def add_orders_scoring_params_to_url( 170 | base_url: str, params: OrdersScoringParams = None 171 | ) -> str: 172 | """ 173 | Adds query parameters to a url 174 | """ 175 | url = base_url 176 | if params: 177 | url = url + "?" 178 | if params.orderIds: 179 | url = build_query_params(url, "order_ids", ",".join(params.orderIds)) 180 | return url 181 | -------------------------------------------------------------------------------- /src/other/set_approval.py: -------------------------------------------------------------------------------- 1 | import os 2 | from web3 import Web3 3 | from web3.constants import MAX_INT 4 | from web3.middleware import geth_poa_middleware 5 | from eth_account import Account 6 | 7 | rpc_url = os.getenv("RPC_URL") 8 | priv_key = os.getenv("PK") # Polygon account private key (needs some MATIC) 9 | pub_key = Account.from_key(priv_key).address # Polygon account public key corresponding to private key 10 | 11 | chain_id = 137 12 | 13 | erc20_approve = """[{"constant": false,"inputs": [{"name": "_spender","type": "address" },{ "name": "_value", "type": "uint256" }],"name": "approve","outputs": [{ "name": "", "type": "bool" }],"payable": false,"stateMutability": "nonpayable","type": "function"}]""" 14 | erc1155_set_approval = """[{"inputs": [{ "internalType": "address", "name": "operator", "type": "address" },{ "internalType": "bool", "name": "approved", "type": "bool" }],"name": "setApprovalForAll","outputs": [],"stateMutability": "nonpayable","type": "function"}]""" 15 | 16 | usdc_address = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174" 17 | ctf_address = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045" 18 | 19 | web3 = Web3(Web3.HTTPProvider(rpc_url)) 20 | web3.middleware_onion.inject(geth_poa_middleware, layer=0) 21 | 22 | nonce = web3.eth.get_transaction_count(pub_key) 23 | 24 | usdc = web3.eth.contract(address=usdc_address, abi=erc20_approve) 25 | ctf = web3.eth.contract(address=ctf_address, abi=erc1155_set_approval) 26 | 27 | # CTF Exchange 28 | raw_usdc_approve_txn = usdc.functions.approve("0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E", int(MAX_INT, 0) 29 | ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) 30 | signed_usdc_approve_tx = web3.eth.account.sign_transaction(raw_usdc_approve_txn, private_key=priv_key) 31 | send_usdc_approve_tx = web3.eth.send_raw_transaction(signed_usdc_approve_tx.raw_transaction) 32 | usdc_approve_tx_receipt = web3.eth.wait_for_transaction_receipt(send_usdc_approve_tx, 600) 33 | # print(usdc_approve_tx_receipt) 34 | if usdc_approve_tx_receipt.status == 1: 35 | print("USDC -> CTF Exchange approval successful") 36 | else: 37 | print("USDC -> CTF Exchange approval failed") 38 | 39 | nonce = web3.eth.get_transaction_count(pub_key) 40 | 41 | raw_ctf_approval_txn = ctf.functions.setApprovalForAll("0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E", True).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) 42 | signed_ctf_approval_tx = web3.eth.account.sign_transaction(raw_ctf_approval_txn, private_key=priv_key) 43 | send_ctf_approval_tx = web3.eth.send_raw_transaction(signed_ctf_approval_tx.raw_transaction) 44 | ctf_approval_tx_receipt = web3.eth.wait_for_transaction_receipt(send_ctf_approval_tx, 600) 45 | # print(ctf_approval_tx_receipt) 46 | if ctf_approval_tx_receipt.status == 1: 47 | print("CTF -> CTF Exchange approval successful") 48 | else: 49 | print("CTF -> CTF Exchange approval failed") 50 | 51 | nonce = web3.eth.get_transaction_count(pub_key) 52 | 53 | # Neg Risk CTF Exchange 54 | raw_usdc_approve_txn = usdc.functions.approve("0xC5d563A36AE78145C45a50134d48A1215220f80a", int(MAX_INT, 0) 55 | ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) 56 | signed_usdc_approve_tx = web3.eth.account.sign_transaction(raw_usdc_approve_txn, private_key=priv_key) 57 | send_usdc_approve_tx = web3.eth.send_raw_transaction(signed_usdc_approve_tx.raw_transaction) 58 | usdc_approve_tx_receipt = web3.eth.wait_for_transaction_receipt(send_usdc_approve_tx, 600) 59 | # print(usdc_approve_tx_receipt) 60 | if usdc_approve_tx_receipt.status == 1: 61 | print("USDC -> Neg Risk CTF Exchange approval successful") 62 | else: 63 | print("USDC -> Neg Risk CTF Exchange approval failed") 64 | 65 | nonce = web3.eth.get_transaction_count(pub_key) 66 | 67 | raw_ctf_approval_txn = ctf.functions.setApprovalForAll("0xC5d563A36AE78145C45a50134d48A1215220f80a", True).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) 68 | signed_ctf_approval_tx = web3.eth.account.sign_transaction(raw_ctf_approval_txn, private_key=priv_key) 69 | send_ctf_approval_tx = web3.eth.send_raw_transaction(signed_ctf_approval_tx.raw_transaction) 70 | ctf_approval_tx_receipt = web3.eth.wait_for_transaction_receipt(send_ctf_approval_tx, 600) 71 | # print(ctf_approval_tx_receipt) 72 | if ctf_approval_tx_receipt.status == 1: 73 | print("CTF -> Neg Risk CTF Exchange approval successful") 74 | else: 75 | print("CTF -> Neg Risk CTF Exchange approval failed") 76 | 77 | nonce = web3.eth.get_transaction_count(pub_key) 78 | 79 | # Neg Risk Adapter 80 | raw_usdc_approve_txn = usdc.functions.approve("0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296", int(MAX_INT, 0) 81 | ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) 82 | signed_usdc_approve_tx = web3.eth.account.sign_transaction(raw_usdc_approve_txn, private_key=priv_key) 83 | send_usdc_approve_tx = web3.eth.send_raw_transaction(signed_usdc_approve_tx.raw_transaction) 84 | usdc_approve_tx_receipt = web3.eth.wait_for_transaction_receipt(send_usdc_approve_tx, 600) 85 | # print(usdc_approve_tx_receipt) 86 | if usdc_approve_tx_receipt.status == 1: 87 | print("USDC -> Neg Risk Adapter approval successful") 88 | else: 89 | print("USDC -> Neg Risk Adapter approval failed") 90 | 91 | nonce = web3.eth.get_transaction_count(pub_key) 92 | 93 | raw_ctf_approval_txn = ctf.functions.setApprovalForAll("0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296", True).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) 94 | signed_ctf_approval_tx = web3.eth.account.sign_transaction(raw_ctf_approval_txn, private_key=priv_key) 95 | send_ctf_approval_tx = web3.eth.send_raw_transaction(signed_ctf_approval_tx.raw_transaction) 96 | ctf_approval_tx_receipt = web3.eth.wait_for_transaction_receipt(send_ctf_approval_tx, 600) 97 | # print(ctf_approval_tx_receipt) 98 | if ctf_approval_tx_receipt.status == 1: 99 | print("CTF -> Neg Risk Adapter approval successful") 100 | else: 101 | print("CTF -> Neg Risk Adapter approval failed") -------------------------------------------------------------------------------- /src/function/func_copy_trade.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import os 4 | 5 | from decimal import Decimal 6 | from typing import Dict 7 | from _py_clob_client.client import ClobClient 8 | from _py_clob_client.clob_types import ApiCreds, MarketOrderArgs, OrderArgs, OrderType, BalanceAllowanceParams, AssetType 9 | from _py_clob_client.order_builder.constants import SELL as SIDE_SELL 10 | from _py_clob_client.constants import POLYGON 11 | from dotenv import load_dotenv 12 | 13 | from utils.utils import get_target_position_size 14 | 15 | # load_dotenv() 16 | 17 | # Get configuration from environment 18 | PRIVATE_KEY = os.getenv('PK') 19 | creds = ApiCreds( 20 | api_key=os.getenv("CLOB_API_KEY"), 21 | api_secret=os.getenv("CLOB_SECRET"), 22 | api_passphrase=os.getenv("CLOB_PASS_PHRASE"), 23 | ) 24 | FUNDER_ADDRESS = os.getenv('FUNDER') 25 | WS_URL = os.getenv('WS_URL') 26 | 27 | # Test mode configuration 28 | TEST_MIN_ORDER = float(os.getenv('TEST_MIN_ORDER')) 29 | TEST_MAX_ORDER = float(os.getenv('TEST_MAX_ORDER')) 30 | TEST_DELAY = float(os.getenv('TEST_DELAY')) 31 | 32 | # Production mode configuration 33 | PROD_MIN_ORDER = float(os.getenv('PROD_MIN_ORDER')) 34 | PROD_MAX_ORDER = float(os.getenv('PROD_MAX_ORDER')) 35 | PROD_DELAY = float(os.getenv('PROD_DELAY')) 36 | 37 | if not all([PRIVATE_KEY, FUNDER_ADDRESS, WS_URL]): 38 | raise ValueError("Missing required environment variables") 39 | 40 | logger = logging.getLogger(__name__) 41 | 42 | 43 | class PolymarketTrader: 44 | def __init__(self, mode: str = 'prod'): 45 | """ 46 | Args: 47 | mode: 'test' for test mode, 'prod' for production mode 48 | """ 49 | self.mode = mode 50 | self.min_order = TEST_MIN_ORDER if mode == 'test' else PROD_MIN_ORDER 51 | self.max_order = TEST_MAX_ORDER if mode == 'test' else PROD_MAX_ORDER 52 | self.delay = TEST_DELAY if mode == 'test' else PROD_DELAY 53 | 54 | self.client = ClobClient( 55 | host="https://clob.polymarket.com", 56 | key=os.getenv('PK'), 57 | chain_id=POLYGON, 58 | creds=creds 59 | ) 60 | 61 | def check_cash_balance(self): 62 | """Fetch USDC balance using web3""" 63 | try: 64 | balance_info = self.client.get_balance_allowance( 65 | params=BalanceAllowanceParams( 66 | asset_type=AssetType.COLLATERAL 67 | ) 68 | ) 69 | return balance_info 70 | 71 | except Exception as e: 72 | logger.error(f"Failed to query balance: {e}") 73 | return None 74 | 75 | async def place_order(self, token_id: str, direction: str, amount: float) -> Dict: 76 | """ 77 | Place an order with specified parameters 78 | 79 | Args: 80 | token_id: Market identifier 81 | direction: Order direction (BUY/SELL) 82 | amount: Order amount (USDC amount for BUY, position proportion for SELL) 83 | """ 84 | try: 85 | if direction == "BUY": 86 | balance_info = self.check_cash_balance() 87 | if balance_info is None: 88 | logger.error("Unable to get balance info, exiting trade") 89 | return 90 | 91 | balance = float(balance_info['balance']) 92 | if balance < amount: 93 | logger.error(f"Insufficient balance: current balance: {balance}, required: {amount}") 94 | return 95 | else: 96 | position_size = get_target_position_size(os.getenv('PUBKEY'), token_id) 97 | if position_size < amount: 98 | logger.error(f"Insufficient position size: current position size: {position_size}, required: {amount}") 99 | return 100 | 101 | # create a market order 102 | order_args = MarketOrderArgs( 103 | token_id=token_id, 104 | amount=amount, 105 | side=direction 106 | ) 107 | signed_order = self.client.create_market_order(order_args) 108 | response = self.client.post_order(signed_order) 109 | 110 | logger.info(f"{direction} Order placed successfully: {response}") 111 | return response 112 | 113 | except Exception as e: 114 | logger.error(f"Failed to place order: {str(e)}") 115 | raise 116 | 117 | async def execute_trade(self, trade_data: Dict): 118 | """ 119 | Follow a trade with configured parameters 120 | """ 121 | try: 122 | # Add delay before following trade 123 | await asyncio.sleep(self.delay) 124 | 125 | # Extract trade details 126 | token_id = trade_data.get("tokenId") 127 | side = trade_data.get("side") 128 | makerAmount = Decimal(str(trade_data.get("makerAmount", 0))) 129 | 130 | # Validate trade parameters 131 | if not all([token_id, side, makerAmount]): 132 | logger.error("Invalid trade data received") 133 | return 134 | 135 | # Apply size limits 136 | makerAmount = min(max(makerAmount, Decimal(str(self.min_order))), Decimal(str(self.max_order))) 137 | 138 | # Place the market order 139 | order_response = await self.place_order( 140 | token_id=token_id, 141 | direction=side, 142 | amount=float(makerAmount) 143 | ) 144 | 145 | logger.info(f"Market order placed successfully: {order_response}") 146 | 147 | except Exception as e: 148 | logger.error(f"Error following trade: {str(e)}") 149 | 150 | async def close(self): 151 | """ 152 | Clean up resources 153 | """ 154 | if self.client: 155 | await self.client.close() 156 | 157 | -------------------------------------------------------------------------------- /src/abi/RelayHub.json: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"amount","type":"uint256"},{"name":"dest","type":"address"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"transactionFee","type":"uint256"},{"name":"url","type":"string"}],"name":"registerRelay","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"relay","type":"address"},{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"encodedFunction","type":"bytes"},{"name":"transactionFee","type":"uint256"},{"name":"gasPrice","type":"uint256"},{"name":"gasLimit","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"signature","type":"bytes"},{"name":"approvalData","type":"bytes"}],"name":"canRelay","outputs":[{"name":"status","type":"uint256"},{"name":"recipientContext","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"recipient","type":"address"},{"name":"encodedFunctionWithFrom","type":"bytes"},{"name":"transactionFee","type":"uint256"},{"name":"gasPrice","type":"uint256"},{"name":"gasLimit","type":"uint256"},{"name":"preChecksGas","type":"uint256"},{"name":"recipientContext","type":"bytes"}],"name":"recipientCallsAtomic","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"from","type":"address"}],"name":"getNonce","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"unsignedTx","type":"bytes"},{"name":"signature","type":"bytes"}],"name":"penalizeIllegalTransaction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"recipient","type":"address"},{"name":"encodedFunction","type":"bytes"},{"name":"transactionFee","type":"uint256"},{"name":"gasPrice","type":"uint256"},{"name":"gasLimit","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"signature","type":"bytes"},{"name":"approvalData","type":"bytes"}],"name":"relayCall","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"relayedCallStipend","type":"uint256"}],"name":"requiredGas","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"target","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"relay","type":"address"}],"name":"canUnstake","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"relay","type":"address"}],"name":"getRelay","outputs":[{"name":"totalStake","type":"uint256"},{"name":"unstakeDelay","type":"uint256"},{"name":"unstakeTime","type":"uint256"},{"name":"owner","type":"address"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"relayedCallStipend","type":"uint256"},{"name":"gasPrice","type":"uint256"},{"name":"transactionFee","type":"uint256"}],"name":"maxPossibleCharge","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"unsignedTx1","type":"bytes"},{"name":"signature1","type":"bytes"},{"name":"unsignedTx2","type":"bytes"},{"name":"signature2","type":"bytes"}],"name":"penalizeRepeatedNonce","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"target","type":"address"}],"name":"depositFor","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"relay","type":"address"},{"name":"unstakeDelay","type":"uint256"}],"name":"stake","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"relay","type":"address"}],"name":"removeRelayByOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"relay","type":"address"}],"name":"unstake","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relay","type":"address"},{"indexed":false,"name":"stake","type":"uint256"},{"indexed":false,"name":"unstakeDelay","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relay","type":"address"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"transactionFee","type":"uint256"},{"indexed":false,"name":"stake","type":"uint256"},{"indexed":false,"name":"unstakeDelay","type":"uint256"},{"indexed":false,"name":"url","type":"string"}],"name":"RelayAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relay","type":"address"},{"indexed":false,"name":"unstakeTime","type":"uint256"}],"name":"RelayRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relay","type":"address"},{"indexed":false,"name":"stake","type":"uint256"}],"name":"Unstaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"recipient","type":"address"},{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":true,"name":"dest","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relay","type":"address"},{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"selector","type":"bytes4"},{"indexed":false,"name":"reason","type":"uint256"}],"name":"CanRelayFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relay","type":"address"},{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"selector","type":"bytes4"},{"indexed":false,"name":"status","type":"uint8"},{"indexed":false,"name":"charge","type":"uint256"}],"name":"TransactionRelayed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"relay","type":"address"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Penalized","type":"event"}] -------------------------------------------------------------------------------- /src/_py_clob_client/order_builder/builder.py: -------------------------------------------------------------------------------- 1 | from py_order_utils.builders import OrderBuilder as UtilsOrderBuilder 2 | from py_order_utils.signer import Signer as UtilsSigner 3 | from py_order_utils.model import ( 4 | EOA, 5 | OrderData, 6 | SignedOrder, 7 | BUY as UtilsBuy, 8 | SELL as UtilsSell, 9 | ) 10 | 11 | from .helpers import ( 12 | to_token_decimals, 13 | round_down, 14 | round_normal, 15 | decimal_places, 16 | round_up, 17 | ) 18 | from .constants import BUY, SELL 19 | from ..config import get_contract_config 20 | from ..signer import Signer 21 | from ..clob_types import ( 22 | OrderArgs, 23 | CreateOrderOptions, 24 | TickSize, 25 | RoundConfig, 26 | MarketOrderArgs, 27 | OrderSummary, 28 | ) 29 | 30 | ROUNDING_CONFIG: dict[TickSize, RoundConfig] = { 31 | "0.1": RoundConfig(price=1, size=2, amount=3), 32 | "0.01": RoundConfig(price=2, size=2, amount=4), 33 | "0.001": RoundConfig(price=3, size=2, amount=5), 34 | "0.0001": RoundConfig(price=4, size=2, amount=6), 35 | } 36 | 37 | 38 | class OrderBuilder: 39 | def __init__(self, signer: Signer, sig_type=None, funder=None): 40 | self.signer = signer 41 | 42 | # Signature type used sign orders, defaults to EOA type 43 | self.sig_type = sig_type if sig_type is not None else EOA 44 | 45 | # Address which holds funds to be used. 46 | # Used for Polymarket proxy wallets and other smart contract wallets 47 | # Defaults to the address of the signer 48 | self.funder = funder if funder is not None else self.signer.address() 49 | 50 | def get_order_amounts( 51 | self, side: str, size: float, price: float, round_config: RoundConfig 52 | ): 53 | """ 54 | params: 55 | side: BUY or SELL 56 | size: position size 57 | price: price of the order 58 | round_config: rounding configuration 59 | """ 60 | raw_price = round_normal(price, round_config.price) 61 | 62 | if side == BUY: 63 | raw_taker_amt = round_down(size, round_config.size) 64 | 65 | raw_maker_amt = raw_taker_amt * raw_price 66 | if decimal_places(raw_maker_amt) > round_config.amount: 67 | raw_maker_amt = round_up(raw_maker_amt, round_config.amount + 4) 68 | if decimal_places(raw_maker_amt) > round_config.amount: 69 | raw_maker_amt = round_down(raw_maker_amt, round_config.amount) 70 | 71 | maker_amount = to_token_decimals(raw_maker_amt) 72 | taker_amount = to_token_decimals(raw_taker_amt) 73 | 74 | return UtilsBuy, maker_amount, taker_amount 75 | elif side == SELL: 76 | raw_maker_amt = round_down(size, round_config.size) 77 | 78 | raw_taker_amt = raw_maker_amt * raw_price 79 | if decimal_places(raw_taker_amt) > round_config.amount: 80 | raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4) 81 | if decimal_places(raw_taker_amt) > round_config.amount: 82 | raw_taker_amt = round_down(raw_taker_amt, round_config.amount) 83 | 84 | maker_amount = to_token_decimals(raw_maker_amt) 85 | taker_amount = to_token_decimals(raw_taker_amt) 86 | 87 | return UtilsSell, maker_amount, taker_amount 88 | else: 89 | raise ValueError(f"order_args.side must be '{BUY}' or '{SELL}'") 90 | 91 | def get_market_order_amounts( 92 | self, side: str, amount: float, price: float, round_config: RoundConfig 93 | ): 94 | """ 95 | params: 96 | side: BUY or SELL 97 | amount: USDC amount(BUY) or position size(SELL) 98 | price: price of the order 99 | round_config: rounding configuration 100 | """ 101 | raw_price = round_normal(price, round_config.price) 102 | 103 | if side == BUY: 104 | raw_maker_amt = round_down(amount, round_config.size) 105 | 106 | raw_taker_amt = raw_maker_amt / raw_price 107 | if decimal_places(raw_taker_amt) > round_config.amount: 108 | raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4) 109 | if decimal_places(raw_taker_amt) > round_config.amount: 110 | raw_taker_amt = round_down(raw_taker_amt, round_config.amount) 111 | 112 | maker_amount = to_token_decimals(raw_maker_amt) 113 | taker_amount = to_token_decimals(raw_taker_amt) 114 | 115 | return maker_amount, taker_amount 116 | 117 | elif side == SELL: 118 | raw_maker_amt = round_down(amount, round_config.size) 119 | 120 | raw_taker_amt = raw_maker_amt * raw_price 121 | if decimal_places(raw_taker_amt) > round_config.amount: 122 | raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4) 123 | if decimal_places(raw_taker_amt) > round_config.amount: 124 | raw_taker_amt = round_down(raw_taker_amt, round_config.amount) 125 | 126 | maker_amount = to_token_decimals(raw_maker_amt) 127 | taker_amount = to_token_decimals(raw_taker_amt) 128 | 129 | return maker_amount, taker_amount 130 | 131 | else: 132 | raise ValueError(f"order_args.side must be '{BUY}' or '{SELL}'") 133 | 134 | def create_order( 135 | self, order_args: OrderArgs, options: CreateOrderOptions 136 | ) -> SignedOrder: 137 | """ 138 | Creates and signs an order 139 | """ 140 | side, maker_amount, taker_amount = self.get_order_amounts( 141 | order_args.side, 142 | order_args.size, 143 | order_args.price, 144 | ROUNDING_CONFIG[options.tick_size], 145 | ) 146 | 147 | data = OrderData( 148 | maker=self.funder, 149 | taker=order_args.taker, 150 | tokenId=order_args.token_id, 151 | makerAmount=str(maker_amount), 152 | takerAmount=str(taker_amount), 153 | side=side, 154 | feeRateBps=str(order_args.fee_rate_bps), 155 | nonce=str(order_args.nonce), 156 | signer=self.signer.address(), 157 | expiration=str(order_args.expiration), 158 | signatureType=self.sig_type, 159 | ) 160 | 161 | contract_config = get_contract_config( 162 | self.signer.get_chain_id(), options.neg_risk 163 | ) 164 | 165 | order_builder = UtilsOrderBuilder( 166 | contract_config.exchange, 167 | self.signer.get_chain_id(), 168 | UtilsSigner(key=self.signer.private_key), 169 | ) 170 | 171 | return order_builder.build_signed_order(data) 172 | 173 | def create_market_order( 174 | self, order_args: MarketOrderArgs, options: CreateOrderOptions 175 | ) -> SignedOrder: 176 | """ 177 | Creates and signs a market order 178 | """ 179 | maker_amount, taker_amount = self.get_market_order_amounts( 180 | order_args.side, 181 | order_args.amount, 182 | order_args.price, 183 | ROUNDING_CONFIG[options.tick_size], 184 | ) 185 | print(f"maker_amount: {maker_amount}, taker_amount: {taker_amount}") 186 | exit() 187 | 188 | data = OrderData( 189 | maker=self.funder, 190 | taker=order_args.taker, 191 | tokenId=order_args.token_id, 192 | makerAmount=str(maker_amount), 193 | takerAmount=str(taker_amount), 194 | side=UtilsBuy if order_args.side == 'BUY' else UtilsSell, 195 | feeRateBps=str(order_args.fee_rate_bps), 196 | nonce=str(order_args.nonce), 197 | signer=self.signer.address(), 198 | expiration="0", 199 | signatureType=self.sig_type, 200 | ) 201 | 202 | contract_config = get_contract_config( 203 | self.signer.get_chain_id(), options.neg_risk 204 | ) 205 | 206 | order_builder = UtilsOrderBuilder( 207 | contract_config.exchange, 208 | self.signer.get_chain_id(), 209 | UtilsSigner(key=self.signer.private_key), 210 | ) 211 | 212 | return order_builder.build_signed_order(data) 213 | 214 | def calculate_market_price( 215 | self, positions: list[OrderSummary], amount_to_match: float 216 | ) -> float: 217 | sum = 0 218 | for p in positions: 219 | sum += float(p.size) * float(p.price) 220 | if sum >= amount_to_match: 221 | return float(p.price) 222 | raise Exception("no match") 223 | -------------------------------------------------------------------------------- /src/function/func_monitor.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import logging 4 | import os 5 | from typing import Callable, Dict, Optional 6 | from dotenv import load_dotenv 7 | 8 | import websockets 9 | from web3 import Web3 10 | from web3.middleware import geth_poa_middleware 11 | from eth_abi import decode, encode 12 | from eth_abi.codec import ABICodec 13 | from eth_abi.registry import registry 14 | 15 | load_dotenv() 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class WalletMonitor: 21 | def __init__(self, on_trade_callback: Callable, mode: str = 'prod'): 22 | """ 23 | params: 24 | on_trade_callback: Callback function to handle detected trades 25 | mode: 'test' for test wallet, 'prod' for target wallet (default: 'prod') 26 | """ 27 | # Select wallet based on mode 28 | wallet_env_var = 'TEST_WALLET' if mode == 'test' else 'TARGET_WALLET' 29 | wallet_address = os.getenv(wallet_env_var) 30 | 31 | self.target_wallet = Web3.to_checksum_address(wallet_address) 32 | logger.info(f"Monitoring in {mode} mode for wallet: {self.target_wallet}") 33 | 34 | # Use Polygon WebSocket URL 35 | self.ws_url = os.getenv('WS_URL') 36 | self.web3 = Web3(Web3.WebsocketProvider(self.ws_url)) 37 | # Add PoS middleware 38 | self.web3.middleware_onion.inject(geth_poa_middleware, layer=0) 39 | self.on_trade_callback = on_trade_callback 40 | self.websocket: Optional[websockets.WebSocketClientProtocol] = None 41 | self.running = False 42 | self.message_count = 0 43 | 44 | # Get matchOrders signature from env 45 | self.match_orders_signature = os.getenv('MATCH_ORDERS_SIGNATURE') 46 | if not self.match_orders_signature: 47 | raise ValueError("MATCH_ORDERS_SIGNATURE not set in .env file") 48 | if not self.match_orders_signature.startswith('0x'): 49 | self.match_orders_signature = '0x' + self.match_orders_signature 50 | 51 | # Load contract ABI 52 | try: 53 | abi_path = os.path.join(os.path.dirname(__file__), '..', 'asset/abi', 'NegRiskFeeModule.json') # same MATCH_ORDERS_SIGNATURE in NegRiskFeeModule.json & FeeModule.json 54 | logger.info(f"Loading ABI from: {abi_path}") 55 | 56 | with open(abi_path, 'r') as f: 57 | self.contract_abi = json.load(f) 58 | 59 | # Find matchOrders function ABI 60 | self.match_orders_abi = next( 61 | (item for item in self.contract_abi 62 | if item.get('type') == 'function' and item.get('name') == 'matchOrders'), 63 | None 64 | ) 65 | 66 | if self.match_orders_abi is None: 67 | raise ValueError("matchOrders function not found in ABI") 68 | 69 | except Exception as e: 70 | logger.error(f"Error loading ABI: {str(e)}") 71 | raise 72 | 73 | # Decode input data 74 | def decode_match_orders(self, input_data: str) -> Optional[Dict]: 75 | """Decode matchOrders function input data""" 76 | try: 77 | # Create contract function object 78 | contract = self.web3.eth.contract( 79 | abi=[self.match_orders_abi] 80 | ) 81 | 82 | # Decode parameters 83 | decoded = contract.decode_function_input(input_data) 84 | # logger.info(f"Decoded parameters: {decoded}") 85 | 86 | # decoded is a tuple of (function_name, parameters) 87 | _, params = decoded 88 | taker_order = params['takerOrder'] 89 | 90 | return { 91 | "maker": taker_order['maker'], 92 | "makerAmount": taker_order['makerAmount'], 93 | "tokenId": taker_order['tokenId'], 94 | "side": taker_order['side'] 95 | } 96 | 97 | except Exception as e: 98 | logger.debug(f"Error decoding matchOrders data: {str(e)}") 99 | return None 100 | 101 | # Process incoming WebSocket message 102 | async def process_message(self, message: str): 103 | """ 104 | params: 105 | message: Raw WebSocket message 106 | """ 107 | try: 108 | data = json.loads(message) 109 | if "params" in data and "result" in data["params"]: 110 | tx_data = data["params"]["result"] 111 | tx_hash = tx_data.get("hash", "unknown") 112 | input_data = tx_data.get("input", "") 113 | 114 | # Check if matchOrders call 115 | if input_data.startswith(self.match_orders_signature): 116 | logger.info(f"MatchOrders TX detected: {tx_hash}") 117 | decoded_data = self.decode_match_orders(input_data) 118 | # logger.info(f"Decoded data: {decoded_data}") 119 | 120 | if decoded_data and decoded_data["maker"].lower() == self.target_wallet.lower(): 121 | logger.info(f""" 122 | Target wallet matchOrders detected: 123 | TX Hash: {tx_hash} 124 | Maker: {decoded_data["maker"]} 125 | Maker Amount: {decoded_data["makerAmount"]} 126 | Token ID: {decoded_data["tokenId"]} 127 | Side: {"BUY" if decoded_data["side"] == 0 else "SELL"} 128 | """) 129 | await self.on_trade_callback(decoded_data) 130 | else: 131 | logger.debug(f"MatchOrders TX detected (not target): {tx_hash}") 132 | 133 | except json.JSONDecodeError: 134 | logger.error(f"Failed to decode WebSocket message: {message}") 135 | except Exception as e: 136 | logger.error(f"Error processing message: {str(e)}") 137 | 138 | # Get current block height from Polygon 139 | async def get_block_height(self): 140 | """Get current block height from Polygon network""" 141 | try: 142 | block_number = self.web3.eth.block_number 143 | return block_number 144 | except Exception as e: 145 | logger.error(f"Error getting block height: {str(e)}") 146 | return None 147 | 148 | # Monitor and log block height 149 | async def monitor_block_height(self): 150 | """Monitor and log block height every 5 seconds""" 151 | while self.running: 152 | try: 153 | block_height = await self.get_block_height() 154 | if block_height: 155 | logger.info(f"Current block height: {block_height} | Messages received: {self.message_count}") 156 | await asyncio.sleep(5) 157 | except Exception as e: 158 | logger.error(f"Error in block height monitor: {str(e)}") 159 | await asyncio.sleep(5) 160 | 161 | # Start monitoring 162 | async def start(self): 163 | self.running = True 164 | # Start block height monitoring in a separate task 165 | asyncio.create_task(self.monitor_block_height()) 166 | 167 | while self.running: 168 | try: 169 | logger.info(f"Connecting to Polygon WebSocket at {self.ws_url}") 170 | async with websockets.connect(self.ws_url) as websocket: 171 | self.websocket = websocket 172 | 173 | # Subscribe to all pending transactions 174 | subscribe_message = { 175 | "jsonrpc": "2.0", 176 | "id": 1, 177 | "method": "eth_subscribe", 178 | "params": ["alchemy_pendingTransactions"] 179 | } 180 | 181 | await websocket.send(json.dumps(subscribe_message)) 182 | subscription_response = await websocket.recv() 183 | logger.info(f"Subscription response: {subscription_response}") 184 | 185 | # Process incoming messages 186 | while self.running: 187 | message = await websocket.recv() 188 | self.message_count += 1 189 | await self.process_message(message) 190 | 191 | except websockets.exceptions.ConnectionClosed: 192 | logger.warning("WebSocket connection closed. Reconnecting...") 193 | await asyncio.sleep(5) 194 | except Exception as e: 195 | logger.error(f"Error in wallet monitor: {str(e)}") 196 | await asyncio.sleep(5) 197 | 198 | # Stop monitoring 199 | async def stop(self): 200 | self.running = False 201 | if self.websocket: 202 | await self.websocket.close() -------------------------------------------------------------------------------- /src/abi/NegRiskAdapter.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_ctf","type":"address"},{"internalType":"address","name":"_collateral","type":"address"},{"internalType":"address","name":"_vault","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DeterminedFlagAlreadySet","type":"error"},{"inputs":[],"name":"FeeBipsOutOfBounds","type":"error"},{"inputs":[],"name":"IndexOutOfBounds","type":"error"},{"inputs":[],"name":"InvalidIndexSet","type":"error"},{"inputs":[],"name":"LengthMismatch","type":"error"},{"inputs":[],"name":"MarketAlreadyDetermined","type":"error"},{"inputs":[],"name":"MarketAlreadyPrepared","type":"error"},{"inputs":[],"name":"MarketNotPrepared","type":"error"},{"inputs":[],"name":"NoConvertiblePositions","type":"error"},{"inputs":[],"name":"NotAdmin","type":"error"},{"inputs":[],"name":"NotApprovedForAll","type":"error"},{"inputs":[],"name":"OnlyOracle","type":"error"},{"inputs":[],"name":"UnexpectedCollateralToken","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"marketId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"oracle","type":"address"},{"indexed":false,"internalType":"uint256","name":"feeBips","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"MarketPrepared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":true,"internalType":"address","name":"newAdminAddress","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"marketId","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"questionId","type":"bytes32"},{"indexed":false,"internalType":"bool","name":"outcome","type":"bool"}],"name":"OutcomeReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"redeemer","type":"address"},{"indexed":true,"internalType":"bytes32","name":"conditionId","type":"bytes32"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"payout","type":"uint256"}],"name":"PayoutRedemption","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":true,"internalType":"bytes32","name":"conditionId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PositionSplit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":true,"internalType":"bytes32","name":"marketId","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"indexSet","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PositionsConverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":true,"internalType":"bytes32","name":"conditionId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PositionsMerge","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"marketId","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"questionId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"QuestionPrepared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":true,"internalType":"address","name":"removedAdmin","type":"address"}],"name":"RemovedAdmin","type":"event"},{"inputs":[],"name":"FEE_DENOMINATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NO_TOKEN_BURN_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"admins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_owners","type":"address[]"},{"internalType":"uint256[]","name":"_ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"col","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_marketId","type":"bytes32"},{"internalType":"uint256","name":"_indexSet","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"convertPositions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"ctf","outputs":[{"internalType":"contract IConditionalTokens","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_questionId","type":"bytes32"}],"name":"getConditionId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_marketId","type":"bytes32"}],"name":"getDetermined","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_marketId","type":"bytes32"}],"name":"getFeeBips","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_marketId","type":"bytes32"}],"name":"getMarketData","outputs":[{"internalType":"MarketData","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_marketId","type":"bytes32"}],"name":"getOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_questionId","type":"bytes32"},{"internalType":"bool","name":"_outcome","type":"bool"}],"name":"getPositionId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_marketId","type":"bytes32"}],"name":"getQuestionCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_marketId","type":"bytes32"}],"name":"getResult","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"isAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralToken","type":"address"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes32","name":"_conditionId","type":"bytes32"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mergePositions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_conditionId","type":"bytes32"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mergePositions","outputs":[],"stateMutability":"nonpayable","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":"nonpayable","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":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_feeBips","type":"uint256"},{"internalType":"bytes","name":"_metadata","type":"bytes"}],"name":"prepareMarket","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_marketId","type":"bytes32"},{"internalType":"bytes","name":"_metadata","type":"bytes"}],"name":"prepareQuestion","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_conditionId","type":"bytes32"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"}],"name":"redeemPositions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_questionId","type":"bytes32"},{"internalType":"bool","name":"_outcome","type":"bool"}],"name":"reportOutcome","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralToken","type":"address"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes32","name":"_conditionId","type":"bytes32"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"splitPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_conditionId","type":"bytes32"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"splitPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wcol","outputs":[{"internalType":"contract WrappedCollateral","name":"","type":"address"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /src/main_search.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | import pandas as pd 5 | import requests 6 | from typing import Dict, List, Generator 7 | from pprint import pprint 8 | from datetime import datetime, timedelta 9 | from dotenv import load_dotenv 10 | from web3 import Web3 11 | from function.func_backtest import WalletBacktest 12 | import time 13 | 14 | # Configure logging 15 | logging.basicConfig( 16 | level=logging.INFO, 17 | format='%(asctime)s - %(levelname)s - %(message)s' 18 | ) 19 | logger = logging.getLogger(__name__) 20 | 21 | class SmartWalletFinder: 22 | def __init__(self): 23 | """Initialize SmartWalletFinder with necessary configurations""" 24 | load_dotenv() 25 | 26 | # Initialize WalletBacktest 27 | api_key = os.getenv('POLYGONSCAN_API_KEY') 28 | if not api_key: 29 | raise ValueError("POLYGONSCAN_API_KEY not set in environment") 30 | 31 | self.backtest = WalletBacktest(api_key, None) # We don't need ClobClient for searching 32 | self.w3 = self.backtest.w3 # Use Web3 instance from WalletBacktest 33 | self.contract_abis = self.backtest.contract_abis # Use contract ABIs from WalletBacktest 34 | 35 | # Initialize data structures 36 | self.active_wallets: Dict[str, Dict] = {} # wallet -> trade data 37 | 38 | def get_transactions_in_window(self, contract_address: str, start_block: int, end_block: int) -> List[Dict]: 39 | """Get transactions for a contract within a specific block range""" 40 | base_url = "https://api.polygonscan.com/api" 41 | 42 | params = { 43 | 'module': 'account', 44 | 'action': 'txlist', 45 | 'address': contract_address, 46 | 'startblock': start_block, 47 | 'endblock': end_block, 48 | 'page': 1, 49 | 'offset': 10000, # Maximum records per request 50 | 'sort': 'desc', 51 | 'apikey': self.backtest.api_key 52 | } 53 | 54 | proxies = { 55 | 'http': os.getenv('HTTP_PROXY'), 56 | 'https': os.getenv('HTTPS_PROXY') 57 | } 58 | 59 | try: 60 | response = requests.get(base_url, params=params, proxies=proxies) 61 | data = response.json() 62 | 63 | if data['status'] == '1': 64 | transactions = data['result'] 65 | # if transactions: 66 | # logger.info(f"Block range: {transactions[-1]['blockNumber']} to {transactions[0]['blockNumber']}") 67 | # logger.info(f"Time range: {datetime.fromtimestamp(int(transactions[-1]['timeStamp']))} to {datetime.fromtimestamp(int(transactions[0]['timeStamp']))}") 68 | return transactions 69 | else: 70 | logger.error(f"Error getting transactions: {data['message']}") 71 | return [] 72 | 73 | except Exception as e: 74 | logger.error(f"Failed to get transactions: {e}") 75 | return [] 76 | 77 | def get_block_by_timestamp(self, timestamp: int) -> int: 78 | """Get the closest block number for a given timestamp""" 79 | base_url = "https://api.polygonscan.com/api" 80 | 81 | params = { 82 | 'module': 'block', 83 | 'action': 'getblocknobytime', 84 | 'timestamp': timestamp, 85 | 'closest': 'before', 86 | 'apikey': self.backtest.api_key 87 | } 88 | 89 | proxies = { 90 | 'http': os.getenv('HTTP_PROXY'), 91 | 'https': os.getenv('HTTPS_PROXY') 92 | } 93 | 94 | try: 95 | response = requests.get(base_url, params=params, proxies=proxies) 96 | data = response.json() 97 | 98 | if data['status'] == '1': 99 | return int(data['result']) 100 | else: 101 | logger.error(f"Error getting block number: {data['message']}") 102 | return 0 103 | 104 | except Exception as e: 105 | logger.error(f"Failed to get block number: {e}") 106 | return 0 107 | 108 | def get_transaction_batches(self, contract_address: str, hours: int) -> Generator[List[Dict], None, None]: 109 | """Generate batches of transactions using block ranges""" 110 | # Get current block 111 | current_block = self.w3.eth.block_number 112 | # logger.info(f"Current block: {current_block}") 113 | 114 | # Get block number from hours ago 115 | timestamp = int(datetime.now().timestamp()) - (hours * 3600) 116 | start_block = self.get_block_by_timestamp(timestamp) 117 | # logger.info(f"Start block: {start_block} (from {datetime.fromtimestamp(timestamp)})") 118 | 119 | if start_block == 0: 120 | logger.error("Failed to get start block") 121 | return 122 | 123 | # Calculate block range for each batch (approximately 1 hour worth of blocks) 124 | blocks_per_batch = 1800 # 3600/2 = 1800 blocks per hour(2 seconds per block) 125 | 126 | current_start = start_block 127 | while current_start < current_block: 128 | current_end = min(current_start + blocks_per_batch, current_block) 129 | 130 | transactions = self.get_transactions_in_window( 131 | contract_address, 132 | current_start, 133 | current_end 134 | ) 135 | 136 | if transactions: 137 | yield transactions 138 | 139 | # Add a small delay to avoid API rate limits 140 | time.sleep(0.5) 141 | 142 | # Move to next block range 143 | current_start = current_end + 1 144 | 145 | def update_wallet_stats(self, wallet: str, trade_data: Dict): 146 | """Update trading statistics for a wallet""" 147 | if wallet not in self.active_wallets: 148 | self.active_wallets[wallet] = { 149 | 'trade_count': 0, 150 | 'tokens_traded': set(), 151 | 'trades': [] 152 | } 153 | 154 | stats = self.active_wallets[wallet] 155 | stats['trade_count'] += 1 156 | stats['tokens_traded'].add(trade_data['tokenId']) 157 | stats['trades'].append({ 158 | 'timestamp': trade_data['timestamp'], 159 | 'tokenId': trade_data['tokenId'], 160 | 'amount': float(trade_data['makerAmount']), 161 | 'side': 'BUY' if trade_data['side'] == 0 else 'SELL', 162 | 'hash': trade_data['hash'] 163 | }) 164 | 165 | def analyze_wallets(self) -> pd.DataFrame: 166 | """Analyze collected wallet data and return potential smart wallets""" 167 | wallet_data = [] 168 | 169 | for wallet, stats in self.active_wallets.items(): 170 | wallet_data.append({ 171 | 'wallet': wallet, 172 | 'trade_count': stats['trade_count'], 173 | 'unique_tokens': len(stats['tokens_traded']), 174 | }) 175 | df = pd.DataFrame(wallet_data) 176 | 177 | # screen df 178 | df = df[df['trade_count'] >= 5] 179 | 180 | if not df.empty: 181 | # Define scoring weights 182 | score_weights = { 183 | 'unique_tokens': 0.2, 184 | 'trade_count': 0.8 185 | } 186 | 187 | # Standardize metrics (z-score normalization) 188 | metrics_to_standardize = ['trade_count', 'unique_tokens'] 189 | standardized_df = df.copy() 190 | 191 | for metric in metrics_to_standardize: 192 | mean = df[metric].mean() 193 | std = df[metric].std() 194 | if std != 0: # Avoid division by zero 195 | standardized_df[f'{metric}_std'] = (df[metric] - mean) / std 196 | else: 197 | standardized_df[f'{metric}_std'] = 0 # If std is 0, all values are the same 198 | 199 | # Calculate smart score using standardized metrics 200 | df['active_score'] = ( 201 | standardized_df['trade_count_std'] * score_weights['trade_count'] + 202 | standardized_df['unique_tokens_std'] * score_weights['unique_tokens'] 203 | ) 204 | 205 | # Sort by smart score 206 | df = df.sort_values('active_score', ascending=False) 207 | 208 | return df 209 | 210 | def save_results(self, df: pd.DataFrame, hours: int): 211 | """Save analysis results to CSV""" 212 | timestamp = datetime.now().strftime('%Y%m%d_%H%M') 213 | output_dir = 'assets/outcome' 214 | os.makedirs(output_dir, exist_ok=True) 215 | 216 | # Save detailed results 217 | output_file = f"{output_dir}/active_wallets_{timestamp}_{hours}h.csv" 218 | df.to_csv(output_file, index=False) 219 | logger.info(f"Saved to {output_file}") 220 | 221 | def decode_transaction_input(self, input_data: str) -> Dict: 222 | """Decode transaction input data""" 223 | decoded_data = [] 224 | if input_data.startswith('0x'): 225 | input_data = input_data[2:] 226 | 227 | # Get contract instance 228 | contract = self.w3.eth.contract(abi=self.contract_abis['FEE_MODULE']) 229 | 230 | # Get function signature (first 4 bytes / 8 characters of input) 231 | func_signature = '0x' + input_data[:8] 232 | 233 | # Check if this is the specific function signature we're looking for 234 | if func_signature.startswith(self.backtest.match_orders_signature): # matchOrders methodID 0xd2539b37 235 | try: 236 | # Decode input data 237 | decoded = contract.decode_function_input('0x' + input_data) 238 | decoded_data.append({ 239 | 'maker': decoded[1]['takerOrder'].get('maker', ''), 240 | 'signer': decoded[1]['takerOrder'].get('signer', ''), 241 | 'tokenId': decoded[1]['takerOrder'].get('tokenId', ''), 242 | 'makerAmount': decoded[1]['takerOrder'].get('makerAmount', ''), 243 | 'side': decoded[1]['takerOrder'].get('side', ''), 244 | 'signatureType': decoded[1]['takerOrder'].get('signatureType', ''), 245 | 'function_name': 'matchOrders' 246 | }) 247 | except Exception as e: 248 | logger.debug(f"Failed to decode input for tx: {e}") 249 | decoded_data.append({}) 250 | else: 251 | logger.debug(f"Skipping non-target function signature: {func_signature}") 252 | decoded_data.append({}) 253 | return decoded_data[0] if decoded_data else {} 254 | 255 | def find_smart_wallets(self, hours: int): 256 | """Find smart wallets from historical transactions""" 257 | logger.info(f"Searching for smart wallets in the last {hours} hours...") 258 | 259 | total_tx_count = 0 260 | 261 | # Get transactions for each Polymarket contract 262 | 263 | for name, address in {k: v for k, v in self.backtest.POLYMARKET_CONTRACTS.items() if k in ['FEE_MODULE', 'NEG_RISK_FEE_MODULE']}.items(): 264 | logger.info(f"Getting transactions for {name}...") 265 | 266 | # Process transactions in batches by time window 267 | for batch in self.get_transaction_batches(address, hours): 268 | batch_size = len(batch) 269 | total_tx_count += batch_size 270 | logger.info(f"Processing batch of {batch_size} transactions...") 271 | 272 | # Process each transaction in the batch 273 | for tx in batch: 274 | if tx.get('input', '').startswith(self.backtest.match_orders_signature): 275 | decoded = self.decode_transaction_input(tx.get('input', '')) 276 | if decoded and 'maker' in decoded: 277 | trade_data = { 278 | 'maker': decoded['maker'], 279 | 'makerAmount': decoded['makerAmount'], 280 | 'tokenId': decoded['tokenId'], 281 | 'side': decoded['side'], 282 | 'timestamp': datetime.fromtimestamp(int(tx['timeStamp'])), 283 | 'hash': tx['hash'] 284 | } 285 | if trade_data['maker']: 286 | self.update_wallet_stats(trade_data['maker'], trade_data) 287 | 288 | # Analyze and save results 289 | logger.info(f"Processed TOTAL {total_tx_count} transactions") 290 | logger.info(f"Found {len(self.active_wallets)} active wallets") 291 | results_df = self.analyze_wallets() 292 | self.save_results(results_df, hours) 293 | logger.info("---- DONE ----") 294 | 295 | return results_df 296 | 297 | def main(): 298 | # Set proxy from environment variables 299 | load_dotenv() 300 | os.environ['HTTP_PROXY'] = os.getenv('HTTP_PROXY') 301 | os.environ['HTTPS_PROXY'] = os.getenv('HTTPS_PROXY') 302 | 303 | finder = SmartWalletFinder() 304 | finder.find_smart_wallets(hours=5) 305 | 306 | if __name__ == "__main__": 307 | now = datetime.now() 308 | main() 309 | logger.info(f"Total time: {datetime.now() - now}") 310 | -------------------------------------------------------------------------------- /src/abi/CtfExchange.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_collateral","type":"address"},{"internalType":"address","name":"_ctf","type":"address"},{"internalType":"address","name":"_proxyFactory","type":"address"},{"internalType":"address","name":"_safeFactory","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyRegistered","type":"error"},{"inputs":[],"name":"FeeTooHigh","type":"error"},{"inputs":[],"name":"InvalidComplement","type":"error"},{"inputs":[],"name":"InvalidNonce","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidTokenId","type":"error"},{"inputs":[],"name":"MakingGtRemaining","type":"error"},{"inputs":[],"name":"MismatchedTokenIds","type":"error"},{"inputs":[],"name":"NotAdmin","type":"error"},{"inputs":[],"name":"NotCrossing","type":"error"},{"inputs":[],"name":"NotOperator","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[],"name":"NotTaker","type":"error"},{"inputs":[],"name":"OrderExpired","type":"error"},{"inputs":[],"name":"OrderFilledOrCancelled","type":"error"},{"inputs":[],"name":"Paused","type":"error"},{"inputs":[],"name":"TooLittleTokensReceived","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeCharged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newAdminAddress","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOperatorAddress","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"NewOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"name":"OrderCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"maker","type":"address"},{"indexed":true,"internalType":"address","name":"taker","type":"address"},{"indexed":false,"internalType":"uint256","name":"makerAssetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerAssetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"makerAmountFilled","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerAmountFilled","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"OrderFilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"takerOrderHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"takerOrderMaker","type":"address"},{"indexed":false,"internalType":"uint256","name":"makerAssetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerAssetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"makerAmountFilled","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerAmountFilled","type":"uint256"}],"name":"OrdersMatched","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldProxyFactory","type":"address"},{"indexed":true,"internalType":"address","name":"newProxyFactory","type":"address"}],"name":"ProxyFactoryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"removedAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"RemovedAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"removedOperator","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"RemovedOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldSafeFactory","type":"address"},{"indexed":true,"internalType":"address","name":"newSafeFactory","type":"address"}],"name":"SafeFactoryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"token0","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"token1","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"conditionId","type":"bytes32"}],"name":"TokenRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pauser","type":"address"}],"name":"TradingPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pauser","type":"address"}],"name":"TradingUnpaused","type":"event"},{"inputs":[{"internalType":"address","name":"admin_","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator_","type":"address"}],"name":"addOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"admins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"}],"name":"cancelOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"orders","type":"tuple[]"}],"name":"cancelOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"},{"internalType":"uint256","name":"fillAmount","type":"uint256"}],"name":"fillOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"orders","type":"tuple[]"},{"internalType":"uint256[]","name":"fillAmounts","type":"uint256[]"}],"name":"fillOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCollateral","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"}],"name":"getComplement","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"}],"name":"getConditionId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCtf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"name":"getOrderStatus","outputs":[{"components":[{"internalType":"bool","name":"isFilledOrCancelled","type":"bool"},{"internalType":"uint256","name":"remaining","type":"uint256"}],"internalType":"struct OrderStatus","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPolyProxyFactoryImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"name":"getPolyProxyWalletAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProxyFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"name":"getSafeAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSafeFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSafeFactoryImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"}],"name":"hashOrder","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incrementNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"isAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"isValidNonce","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"takerOrder","type":"tuple"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"makerOrders","type":"tuple[]"},{"internalType":"uint256","name":"takerFillAmount","type":"uint256"},{"internalType":"uint256[]","name":"makerFillAmounts","type":"uint256[]"}],"name":"matchOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","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":"nonpayable","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":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"orderStatus","outputs":[{"internalType":"bool","name":"isFilledOrCancelled","type":"bool"},{"internalType":"uint256","name":"remaining","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"parentCollectionId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseTrading","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxyFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"},{"internalType":"uint256","name":"complement","type":"uint256"},{"internalType":"bytes32","name":"conditionId","type":"bytes32"}],"name":"registerToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"registry","outputs":[{"internalType":"uint256","name":"complement","type":"uint256"},{"internalType":"bytes32","name":"conditionId","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"removeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceAdminRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOperatorRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"safeFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newProxyFactory","type":"address"}],"name":"setProxyFactory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newSafeFactory","type":"address"}],"name":"setSafeFactory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpauseTrading","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"},{"internalType":"uint256","name":"complement","type":"uint256"}],"name":"validateComplement","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"}],"name":"validateOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"}],"name":"validateOrderSignature","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"validateTokenId","outputs":[],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /src/abi/NegRiskCtfExchange.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_collateral","type":"address"},{"internalType":"address","name":"_ctf","type":"address"},{"internalType":"address","name":"_negRiskAdapter","type":"address"},{"internalType":"address","name":"_proxyFactory","type":"address"},{"internalType":"address","name":"_safeFactory","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyRegistered","type":"error"},{"inputs":[],"name":"FeeTooHigh","type":"error"},{"inputs":[],"name":"InvalidComplement","type":"error"},{"inputs":[],"name":"InvalidNonce","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidTokenId","type":"error"},{"inputs":[],"name":"MakingGtRemaining","type":"error"},{"inputs":[],"name":"MismatchedTokenIds","type":"error"},{"inputs":[],"name":"NotAdmin","type":"error"},{"inputs":[],"name":"NotCrossing","type":"error"},{"inputs":[],"name":"NotOperator","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[],"name":"NotTaker","type":"error"},{"inputs":[],"name":"OrderExpired","type":"error"},{"inputs":[],"name":"OrderFilledOrCancelled","type":"error"},{"inputs":[],"name":"Paused","type":"error"},{"inputs":[],"name":"TooLittleTokensReceived","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeCharged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newAdminAddress","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOperatorAddress","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"NewOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"name":"OrderCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"maker","type":"address"},{"indexed":true,"internalType":"address","name":"taker","type":"address"},{"indexed":false,"internalType":"uint256","name":"makerAssetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerAssetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"makerAmountFilled","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerAmountFilled","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"OrderFilled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"takerOrderHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"takerOrderMaker","type":"address"},{"indexed":false,"internalType":"uint256","name":"makerAssetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerAssetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"makerAmountFilled","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"takerAmountFilled","type":"uint256"}],"name":"OrdersMatched","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldProxyFactory","type":"address"},{"indexed":true,"internalType":"address","name":"newProxyFactory","type":"address"}],"name":"ProxyFactoryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"removedAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"RemovedAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"removedOperator","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"RemovedOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldSafeFactory","type":"address"},{"indexed":true,"internalType":"address","name":"newSafeFactory","type":"address"}],"name":"SafeFactoryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"token0","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"token1","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"conditionId","type":"bytes32"}],"name":"TokenRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pauser","type":"address"}],"name":"TradingPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pauser","type":"address"}],"name":"TradingUnpaused","type":"event"},{"inputs":[{"internalType":"address","name":"admin_","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator_","type":"address"}],"name":"addOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"admins","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"}],"name":"cancelOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"orders","type":"tuple[]"}],"name":"cancelOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"},{"internalType":"uint256","name":"fillAmount","type":"uint256"}],"name":"fillOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"orders","type":"tuple[]"},{"internalType":"uint256[]","name":"fillAmounts","type":"uint256[]"}],"name":"fillOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCollateral","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"}],"name":"getComplement","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"}],"name":"getConditionId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCtf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxFeeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"name":"getOrderStatus","outputs":[{"components":[{"internalType":"bool","name":"isFilledOrCancelled","type":"bool"},{"internalType":"uint256","name":"remaining","type":"uint256"}],"internalType":"struct OrderStatus","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPolyProxyFactoryImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"name":"getPolyProxyWalletAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProxyFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"name":"getSafeAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSafeFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSafeFactoryImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"}],"name":"hashOrder","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incrementNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"isAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"isValidNonce","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"takerOrder","type":"tuple"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order[]","name":"makerOrders","type":"tuple[]"},{"internalType":"uint256","name":"takerFillAmount","type":"uint256"},{"internalType":"uint256[]","name":"makerFillAmounts","type":"uint256[]"}],"name":"matchOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","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":"nonpayable","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":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"operators","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"orderStatus","outputs":[{"internalType":"bool","name":"isFilledOrCancelled","type":"bool"},{"internalType":"uint256","name":"remaining","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"parentCollectionId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pauseTrading","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxyFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"},{"internalType":"uint256","name":"complement","type":"uint256"},{"internalType":"bytes32","name":"conditionId","type":"bytes32"}],"name":"registerToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"registry","outputs":[{"internalType":"uint256","name":"complement","type":"uint256"},{"internalType":"bytes32","name":"conditionId","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"removeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceAdminRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOperatorRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"safeFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newProxyFactory","type":"address"}],"name":"setProxyFactory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newSafeFactory","type":"address"}],"name":"setSafeFactory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpauseTrading","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"},{"internalType":"uint256","name":"complement","type":"uint256"}],"name":"validateComplement","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"}],"name":"validateOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"components":[{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"makerAmount","type":"uint256"},{"internalType":"uint256","name":"takerAmount","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"feeRateBps","type":"uint256"},{"internalType":"enum Side","name":"side","type":"uint8"},{"internalType":"enum SignatureType","name":"signatureType","type":"uint8"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct Order","name":"order","type":"tuple"}],"name":"validateOrderSignature","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"validateTokenId","outputs":[],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /src/function/func_backtest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import requests 4 | import pandas as pd 5 | from pprint import pprint 6 | from web3 import Web3 7 | from web3.middleware import geth_poa_middleware 8 | from typing import Dict, List, Optional 9 | from concurrent.futures import ThreadPoolExecutor, as_completed 10 | from tqdm import tqdm 11 | from _py_clob_client.client import ClobClient 12 | from utils.utils import get_position_all 13 | from datetime import datetime, timedelta 14 | 15 | 16 | class WalletBacktest: 17 | # Polymarket contract addresses 18 | POLYMARKET_CONTRACTS = { 19 | "CTF_EXCHANGE": "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E".lower(), 20 | "NEG_RISK_CTF_EXCHANGE": "0xC5d563A36AE78145C45a50134d48A1215220f80a".lower(), 21 | "NEG_RISK_ADAPTER": "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296".lower(), 22 | "FEE_MODULE": "0x56C79347e95530c01A2FC76E732f9566dA16E113".lower(), 23 | "NEG_RISK_FEE_MODULE": "0x78769D50Be1763ed1CA0D5E878D93f05aabff29e".lower(), 24 | "RELAY_HUB": "0xD216153c06E857cD7f72665E0aF1d7D82172F494".lower(), 25 | "CONDITIONAL_TOKENS": "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045".lower() 26 | } 27 | 28 | # USDC transfer event signature 29 | TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".lower() 30 | USDC_SENDER = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045".lower() 31 | 32 | # Add after USDC_SENDER constant 33 | TRANSFER_SINGLE_TOPIC = "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62".lower() 34 | 35 | def __init__(self, api_key: str, clob_client: ClobClient, max_workers: int = 5): 36 | """ 37 | Initialize WalletBacktest 38 | 39 | Args: 40 | api_key: Polygonscan API key 41 | clob_client: Initialized ClobClient instance 42 | max_workers: Maximum number of workers for parallel processing 43 | """ 44 | self.api_key = api_key 45 | self.client = clob_client 46 | self.max_workers = max_workers 47 | 48 | self.w3 = Web3(Web3.HTTPProvider(os.getenv('RPC_URL'))) 49 | self.w3.middleware_onion.inject(geth_poa_middleware, layer=0) 50 | 51 | self.contract_abis = self._load_contract_abis() 52 | self.match_orders_signature = os.getenv('MATCH_ORDERS_SIGNATURE') 53 | if not self.match_orders_signature: 54 | raise ValueError("MATCH_ORDERS_SIGNATURE not set in .env file") 55 | if not self.match_orders_signature.startswith('0x'): 56 | self.match_orders_signature = '0x' + self.match_orders_signature 57 | 58 | def _load_contract_abis(self) -> dict: 59 | """Load all contract ABIs from assets folder""" 60 | contract_abis = {} 61 | contract_names = { 62 | "CTF_EXCHANGE": "CtfExchange", 63 | "NEG_RISK_CTF_EXCHANGE": "NegRiskCtfExchange", 64 | "NEG_RISK_ADAPTER": "NegRiskAdapter", 65 | "FEE_MODULE": "FeeModule", 66 | "NEG_RISK_FEE_MODULE": "NegRiskFeeModule" 67 | } 68 | 69 | try: 70 | for key, name in contract_names.items(): 71 | abi_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "abi", f"{name}.json") 72 | with open(abi_path) as f: 73 | contract_abis[key] = json.load(f) 74 | except Exception as e: 75 | print(f"Error loading contract ABI: {e}") 76 | raise 77 | 78 | return contract_abis 79 | 80 | def get_tx_by_hash(self, tx_hash: str) -> Optional[Dict]: 81 | """ 82 | Get transaction data directly by hash from Polygonscan 83 | """ 84 | base_url = "https://api.polygonscan.com/api" 85 | 86 | params = { 87 | 'module': 'proxy', 88 | 'action': 'eth_getTransactionByHash', 89 | 'txhash': tx_hash, 90 | 'apikey': self.api_key 91 | } 92 | 93 | proxies = { 94 | 'http': 'http://localhost:15236', 95 | 'https': 'http://localhost:15236' 96 | } 97 | 98 | try: 99 | response = requests.get(base_url, params=params, proxies=proxies) 100 | data = response.json() 101 | 102 | if data.get('result'): 103 | return data['result'] 104 | else: 105 | print(f"Error getting tx {tx_hash}: {data.get('message', 'Unknown error')}") 106 | return None 107 | 108 | except Exception as e: 109 | print(f"Failed to get transaction {tx_hash}: {e}") 110 | return None 111 | 112 | def get_tx_by_hash_web3(self, tx_hash: str) -> Optional[Dict]: 113 | """ 114 | Get transaction data by hash using Web3 115 | """ 116 | try: 117 | tx = self.w3.eth.get_transaction(tx_hash) 118 | if tx and hasattr(tx, 'input') and isinstance(tx['input'], bytes): 119 | tx_dict = dict(tx) 120 | tx_dict['input'] = '0x' + tx['input'].hex() 121 | return tx_dict 122 | except Exception as e: 123 | print(f"Error getting transaction: {e}") 124 | return None 125 | 126 | def decode_input_data_web3(self, contract_name: str, input_data: str) -> Optional[Dict]: 127 | """ 128 | Decode transaction input data using Web3 129 | """ 130 | try: 131 | # Check if input data matches the match_orders_signature 132 | if not input_data.startswith(self.match_orders_signature): 133 | return None 134 | 135 | # Create contract instance 136 | contract = self.w3.eth.contract(abi=self.contract_abis[contract_name]) 137 | 138 | # Decode input data 139 | decoded = contract.decode_function_input(input_data) 140 | 141 | # Extract function name and parameters 142 | func_name = decoded[0].fn_name 143 | params = decoded[1] 144 | return { 145 | 'function_name': func_name, 146 | 'parameters': params 147 | } 148 | except Exception as e: 149 | return None 150 | 151 | def _process_transfer(self, transfer: Dict, pbar: tqdm) -> Dict: 152 | """Process a single transfer by getting its full transaction data""" 153 | relay = False 154 | tx_hash = transfer['hash'] 155 | if transfer['from'] == self.POLYMARKET_CONTRACTS['CONDITIONAL_TOKENS']: 156 | relay = True 157 | if not relay: 158 | tx_data = self.get_tx_by_hash_web3(tx_hash) 159 | if tx_data: 160 | transfer['input'] = tx_data.get('input', '') 161 | transfer['interacted_with'] = tx_data.get('to', '') 162 | else: 163 | transfer['interacted_with'] = self.POLYMARKET_CONTRACTS['RELAY_HUB'] 164 | transfer['function_name'] = 'relayCall' 165 | pbar.update(1) 166 | return transfer 167 | 168 | def download_transactions(self, address: str, days: int = None) -> list: 169 | """ 170 | Download all ERC-20 token transfers and their corresponding transaction data 171 | 172 | Args: 173 | address: The address to get token transfers for 174 | days: Number of days to look back from current time (None means all history) 175 | """ 176 | base_url = "https://api.polygonscan.com/api" 177 | 178 | # Calculate start timestamp if days is provided 179 | if days is not None: 180 | current_time = datetime.now() 181 | start_date = current_time - timedelta(days=days) 182 | # Set to start of the day (midnight) 183 | start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0) 184 | start_timestamp = int(start_date.timestamp()) 185 | 186 | params = { 187 | 'module': 'account', 188 | 'action': 'tokentx', 189 | 'address': address, 190 | 'startblock': 0, 191 | 'endblock': 99999999, 192 | 'sort': 'desc', 193 | 'apikey': self.api_key 194 | } 195 | 196 | proxies = { 197 | 'http': os.getenv('HTTP_PROXY'), 198 | 'https': os.getenv('HTTP_PROXY') 199 | } 200 | 201 | try: 202 | # Get token transfers 203 | response = requests.get(base_url, params=params, proxies=proxies) 204 | data = response.json() 205 | 206 | if data['status'] == '1': # Success 207 | # Filter transfers that interact with Polymarket contracts 208 | polymarket_addresses = [addr.lower() for addr in self.POLYMARKET_CONTRACTS.values()] 209 | transfers = [ 210 | tx for tx in data['result'] 211 | if tx['from'].lower() in polymarket_addresses or tx['to'].lower() in polymarket_addresses 212 | ] 213 | 214 | # Filter by timestamp if days is provided 215 | if days is not None: 216 | transfers = [ 217 | tx for tx in transfers 218 | if int(tx['timeStamp']) >= start_timestamp 219 | ] 220 | 221 | # Use ThreadPoolExecutor for parallel processing 222 | with ThreadPoolExecutor(max_workers=self.max_workers) as executor: 223 | # Create a progress bar 224 | pbar = tqdm(total=len(transfers), desc="Processing transfers") 225 | 226 | # Submit all transfers to thread pool 227 | futures = [executor.submit(self._process_transfer, transfer, pbar) 228 | for transfer in transfers] 229 | 230 | # Get results as they complete 231 | transfers = [future.result() for future in as_completed(futures)] 232 | 233 | pbar.close() 234 | 235 | return transfers 236 | else: 237 | print(f"Error: {data['message']}") 238 | return [] 239 | 240 | except Exception as e: 241 | print(f"Failed to download transactions: {e}") 242 | return [] 243 | 244 | def get_current_positions(self, address: str) -> List[Dict]: 245 | """ 246 | Get current positions and their market prices 247 | """ 248 | positions = [] 249 | 250 | try: 251 | # Get all positions for the address 252 | position_data = get_position_all(address) 253 | 254 | # Calculate market price for each position 255 | for pos in position_data: 256 | token_id = pos['asset'] 257 | size = float(pos['size']) 258 | positions.append({ 259 | 'token_id': token_id, 260 | 'size': size, 261 | 'current_value': pos['currentValue'], 262 | }) 263 | except Exception as e: 264 | print(f"Error getting positions: {e}") 265 | 266 | return positions 267 | 268 | def calculate_pnl_stats(self, df: pd.DataFrame) -> pd.DataFrame: 269 | """ 270 | Calculate P&L and win rate for each token_id 271 | """ 272 | stats = [] 273 | total_realized_pnl = 0 274 | 275 | # Group by token_id 276 | for token_id, group in df.groupby('tokenId'): 277 | # Sort by timestamp 278 | group = group.sort_values('timeStamp') 279 | # Calculate running position and P&L 280 | total_cost = 0 281 | total_proceeds = 0 282 | 283 | for _, row in group.iterrows(): 284 | if int(row['side']) == 0: # BUY 285 | total_cost += row['value'] 286 | else: # SELL 287 | total_proceeds += row['value'] 288 | 289 | # Calculate metrics 290 | realized_pnl = total_proceeds - total_cost 291 | # Only add realized_pnl to total if this is a new token_id 292 | if not any(s['token_id'] == token_id for s in stats): 293 | total_realized_pnl += realized_pnl 294 | 295 | stats.append({ 296 | 'token_id': token_id, 297 | 'realized_pnl': realized_pnl, 298 | 'total_volume': total_cost + total_proceeds 299 | }) 300 | 301 | # Convert stats to DataFrame for easier processing 302 | stats_df = pd.DataFrame(stats) 303 | 304 | if not stats_df.empty: 305 | # Calculate win rate based on final P&L 306 | total_tokens = len(stats_df) 307 | winning_tokens = len(stats_df[stats_df['realized_pnl'] > 0]) 308 | win_rate = winning_tokens / total_tokens if total_tokens > 0 else 0 309 | 310 | # Add win_rate to all rows with the same token_id 311 | stats_df['win_rate'] = win_rate 312 | stats_df['total_realized_pnl'] = round(total_realized_pnl, 4) 313 | return stats_df 314 | 315 | def decode_transaction_data(self, df: pd.DataFrame) -> pd.DataFrame: 316 | """Decode transaction input data with parallel processing""" 317 | # Initialize columns with default values 318 | df['maker'] = '' 319 | df['signer'] = '' 320 | df['tokenId'] = '' 321 | df['makerAmount'] = '' 322 | df['side'] = 1 # Default side value is 1 323 | df['signatureType'] = '' 324 | 325 | with ThreadPoolExecutor(max_workers=self.max_workers) as executor: 326 | # Create futures for decoding each transaction 327 | futures = [] 328 | for idx, row in df.iterrows(): 329 | future = executor.submit(self._decode_single_transaction, row) 330 | futures.append((idx, future)) 331 | 332 | # Update DataFrame with decoded results 333 | for idx, future in tqdm(futures, desc="Decoding transactions"): 334 | decoded_data = future.result() 335 | for key, value in decoded_data.items(): 336 | df.at[idx, key] = value 337 | 338 | return df 339 | 340 | def _decode_single_transaction(self, row: pd.Series) -> Dict: 341 | """Decode a single transaction's input data""" 342 | decoded_data = {} 343 | try: 344 | if pd.notna(row['input']): 345 | input_data = row['input'] 346 | if input_data.startswith('0xd2539b37'): 347 | # 使用CTF_EXCHANGE合约的ABI来解码 348 | decoded = self.decode_input_data_web3('FEE_MODULE', input_data) 349 | if decoded: 350 | # 从parameters中获取参数 351 | params = decoded.get('parameters', {}) 352 | decoded_data.update({ 353 | 'maker': params['takerOrder'].get('maker', ''), 354 | 'signer': params['takerOrder'].get('signer', ''), 355 | 'tokenId': params['takerOrder'].get('tokenId', ''), 356 | 'makerAmount': params['takerOrder'].get('makerAmount', ''), 357 | 'side': params['takerOrder'].get('side', 1), 358 | 'signatureType': params['takerOrder'].get('signatureType', ''), 359 | 'function_name': decoded.get('function_name', '') 360 | }) 361 | except Exception as e: 362 | print(f"Error decoding transaction: {e}") 363 | 364 | return decoded_data 365 | 366 | def calculate_price(self, row: pd.Series, address: str) -> float: 367 | """ 368 | Calculate price based on side and transaction data 369 | 370 | Args: 371 | row: DataFrame row containing transaction data 372 | address: Target address for log filtering 373 | 374 | Returns: 375 | float: Calculated price 376 | """ 377 | # If side is empty, return 1 378 | if pd.isna(row['side']) or row['side'] == '': 379 | return 1.0 380 | 381 | # Convert side to int for comparison 382 | side = int(row['side']) 383 | 384 | # For SELL orders 385 | if side == 1: 386 | # If tokenId is empty for SELL orders, return 1 387 | if pd.isna(row['tokenId']) or row['tokenId'] == '': 388 | return 1.0 389 | 390 | # Normal SELL order price calculation 391 | if float(row['makerAmount']) == 0: 392 | return 1.0 393 | return float(row['value']) / (float(row['makerAmount']) / 1e6) 394 | 395 | # For BUY orders 396 | if side == 0: 397 | try: 398 | # Get transaction receipt 399 | receipt = self.w3.eth.get_transaction_receipt(row['hash']) 400 | 401 | # Find the relevant log 402 | for log in receipt['logs']: 403 | # Check if this is a TransferSingle event and to the target address 404 | if (("0x" + log['topics'][0].hex()).lower() == self.TRANSFER_SINGLE_TOPIC and 405 | "0x" + log['topics'][-1].hex()[-40:].lower() == address.lower()): 406 | # Get value from log data 407 | value = int(log['data'].hex()[-64:], 16) # Last 32 bytes contain the value 408 | if value == 0: 409 | return 1.0 410 | return float(row['makerAmount']) / value 411 | 412 | # If no matching log found, return 1 413 | return 1.0 414 | 415 | except Exception as e: 416 | print(f"Error calculating BUY price for tx {row['hash']}: {e}") 417 | return 1.0 418 | return 1.0 419 | 420 | def process_transactions(self, transactions: list, address: str) -> pd.DataFrame: 421 | """ 422 | Process transactions and calculate statistics 423 | 424 | Args: 425 | transactions: List of transactions to process 426 | address: Address to calculate statistics for 427 | """ 428 | df = pd.DataFrame(transactions) 429 | df['timeStamp'] = pd.to_datetime(df['timeStamp'].astype(int), unit='s') 430 | 431 | # Convert token value from wei to USDC (6 decimals) 432 | df['value'] = df['value'].astype(float) / 1e6 433 | 434 | df['gasPrice'] = df['gasPrice'].astype(float) / 1e9 435 | df['gasCost'] = (df['gasPrice'] * df['gasUsed'].astype(float)) / 1e9 436 | 437 | # Decode transaction data 438 | df = self.decode_transaction_data(df) 439 | 440 | # Calculate price for each transaction 441 | df['price'] = df.apply(lambda row: self.calculate_price(row, address), axis=1) 442 | 443 | # Get current positions 444 | positions = self.get_current_positions(address) 445 | 446 | # Add current position info to rows with matching token_id 447 | df['current_position'] = df.apply( 448 | lambda row: next( 449 | (pos['size'] for pos in positions if str(pos['token_id']) == str(row.get('tokenId'))), 450 | 0 451 | ), 452 | axis=1 453 | ) 454 | df['current_value'] = df.apply( 455 | lambda row: next( 456 | (pos['current_value'] for pos in positions if str(pos['token_id']) == str(row.get('tokenId'))), 457 | 0 458 | ), 459 | axis=1 460 | ) 461 | 462 | # delete empty function_name rows 463 | df = df.dropna(subset=['function_name']) 464 | 465 | # Calculate P&L statistics 466 | pnl_stats = self.calculate_pnl_stats(df) 467 | 468 | if not pnl_stats.empty: 469 | # Merge P&L stats back into main DataFrame 470 | df = df.merge( 471 | pnl_stats[['token_id', 'realized_pnl', 'win_rate', 'total_realized_pnl']], 472 | left_on='tokenId', 473 | right_on='token_id', 474 | how='left' 475 | ) 476 | 477 | df['totalTrades'] = len(df) 478 | # Calculate total value by summing current_value across all tokenIds 479 | total_current_value = df.drop_duplicates('tokenId')['current_value'].sum() 480 | df['total_current_value'] = total_current_value 481 | total_pnl = df['total_current_value'][0] + df['total_realized_pnl'][0] 482 | df['total_pnl'] = round(total_pnl, 4) 483 | 484 | return df 485 | 486 | 487 | def save_to_csv(self, df: pd.DataFrame, output_file: str): 488 | """ 489 | Save processed DataFrame to CSV with proper column names 490 | 491 | Args: 492 | df: DataFrame to save 493 | output_file: Output CSV file path 494 | """ 495 | # Create output directory if it doesn't exist 496 | output_dir = os.path.dirname(output_file) 497 | if output_dir and not os.path.exists(output_dir): 498 | os.makedirs(output_dir) 499 | 500 | # Select and rename columns 501 | columns = { 502 | 'timeStamp': 'time', 503 | 'current_position': 'currentPosition', 504 | 'current_value': 'currentValue', 505 | 'function_name': 'functionName', 506 | 'realized_pnl': 'realized P&L', 507 | 'win_rate': 'winRate', 508 | 'total_realized_pnl': 'totalRealizedP&L', 509 | 'total_pnl': 'totalP&L', 510 | 'total_current_value': 'totalCurrentValue' 511 | } 512 | 513 | # Fill missing columns with empty values 514 | for col in columns.keys(): 515 | if col not in df.columns: 516 | df[col] = '' 517 | 518 | df = df.rename(columns=columns) 519 | 520 | # Sort by timestamp 521 | df = df.sort_values('time', ascending=False) 522 | 523 | # Save to CSV 524 | df.to_csv(output_file, index=False) 525 | # print(f"\nSaved data to: {output_file}") 526 | 527 | # self._print_summary(df) 528 | 529 | def _print_summary(self, df: pd.DataFrame): 530 | """Print transaction and P&L summary""" 531 | print("\nSummary:") 532 | print(f"Total Transfers: {len(df)}") 533 | print(f"Date Range: {df['time'].min()} to {df['time'].max()}") 534 | 535 | incoming = df[df['side'] == 0]['value'].sum() 536 | outgoing = df[df['side'] == 1]['value'].sum() 537 | print(f"Total Incoming: {incoming:.2f} USDC, Outgoing: {outgoing:.2f} USDC") 538 | 539 | if 'totalRealizedP&L' in df.columns: 540 | total_pnl = df['totalRealizedP&L'].iloc[0] 541 | win_rate = df['winRate'].iloc[0] * 100 542 | print(f"Total Realized P&L: {float(total_pnl):.4f} USDC") 543 | print(f"Win Rate: {float(win_rate):.1f}%") 544 | print(f"Total P&L: {float(df['totalP&L'].iloc[0]):.4f} USDC") 545 | -------------------------------------------------------------------------------- /src/_py_clob_client/client.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Optional 3 | 4 | from .order_builder.builder import OrderBuilder 5 | from .headers.headers import create_level_1_headers, create_level_2_headers 6 | from .signer import Signer 7 | from .config import get_contract_config 8 | 9 | from .endpoints import ( 10 | CANCEL, 11 | CANCEL_ORDERS, 12 | CANCEL_MARKET_ORDERS, 13 | CANCEL_ALL, 14 | CREATE_API_KEY, 15 | DELETE_API_KEY, 16 | DERIVE_API_KEY, 17 | GET_API_KEYS, 18 | GET_LAST_TRADE_PRICE, 19 | GET_ORDER, 20 | GET_ORDER_BOOK, 21 | MID_POINT, 22 | ORDERS, 23 | POST_ORDER, 24 | PRICE, 25 | TIME, 26 | TRADES, 27 | GET_NOTIFICATIONS, 28 | DROP_NOTIFICATIONS, 29 | GET_BALANCE_ALLOWANCE, 30 | UPDATE_BALANCE_ALLOWANCE, 31 | IS_ORDER_SCORING, 32 | GET_TICK_SIZE, 33 | GET_NEG_RISK, 34 | ARE_ORDERS_SCORING, 35 | GET_SIMPLIFIED_MARKETS, 36 | GET_MARKETS, 37 | GET_MARKET, 38 | GET_SAMPLING_SIMPLIFIED_MARKETS, 39 | GET_SAMPLING_MARKETS, 40 | GET_MARKET_TRADES_EVENTS, 41 | GET_LAST_TRADES_PRICES, 42 | MID_POINTS, 43 | GET_ORDER_BOOKS, 44 | GET_PRICES, 45 | GET_SPREAD, 46 | GET_SPREADS, 47 | ) 48 | from .clob_types import ( 49 | ApiCreds, 50 | TradeParams, 51 | OpenOrderParams, 52 | OrderArgs, 53 | RequestArgs, 54 | DropNotificationParams, 55 | OrderBookSummary, 56 | BalanceAllowanceParams, 57 | OrderScoringParams, 58 | TickSize, 59 | CreateOrderOptions, 60 | OrdersScoringParams, 61 | OrderType, 62 | PartialCreateOrderOptions, 63 | BookParams, 64 | MarketOrderArgs, 65 | ) 66 | from .exceptions import PolyException 67 | from .http_helpers.helpers import ( 68 | add_query_trade_params, 69 | add_query_open_orders_params, 70 | delete, 71 | get, 72 | post, 73 | drop_notifications_query_params, 74 | add_balance_allowance_params_to_url, 75 | add_order_scoring_params_to_url, 76 | ) 77 | 78 | from .constants import L0, L1, L1_AUTH_UNAVAILABLE, L2, L2_AUTH_UNAVAILABLE, END_CURSOR 79 | from .utilities import ( 80 | parse_raw_orderbook_summary, 81 | generate_orderbook_summary_hash, 82 | order_to_json, 83 | is_tick_size_smaller, 84 | price_valid, 85 | ) 86 | 87 | 88 | class ClobClient: 89 | def __init__( 90 | self, 91 | host, 92 | chain_id: int = None, 93 | key: str = None, 94 | creds: ApiCreds = None, 95 | signature_type: int = None, 96 | funder: str = None, 97 | ): 98 | """ 99 | Initializes the clob client 100 | The client can be started in 3 modes: 101 | 1) Level 0: Requires only the clob host url 102 | Allows access to open CLOB endpoints 103 | 104 | 2) Level 1: Requires the host, chain_id and a private key. 105 | Allows access to L1 authenticated endpoints + all unauthenticated endpoints 106 | 107 | 3) Level 2: Requires the host, chain_id, a private key, and Credentials. 108 | Allows access to all endpoints 109 | """ 110 | self.host = host[0:-1] if host.endswith("/") else host 111 | self.chain_id = chain_id 112 | self.signer = Signer(key, chain_id) if key else None 113 | self.creds = creds 114 | self.mode = self._get_client_mode() 115 | 116 | if self.signer: 117 | self.builder = OrderBuilder( 118 | self.signer, sig_type=signature_type, funder=funder 119 | ) 120 | 121 | # local cache 122 | self.__tick_sizes = {} 123 | self.__neg_risk = {} 124 | 125 | self.logger = logging.getLogger(self.__class__.__name__) 126 | 127 | def get_address(self): 128 | """ 129 | Returns the public address of the signer 130 | """ 131 | return self.signer.address() if self.signer else None 132 | 133 | def get_collateral_address(self): 134 | """ 135 | Returns the collateral token address 136 | """ 137 | contract_config = get_contract_config(self.chain_id) 138 | if contract_config: 139 | return contract_config.collateral 140 | 141 | def get_conditional_address(self): 142 | """ 143 | Returns the conditional token address 144 | """ 145 | contract_config = get_contract_config(self.chain_id) 146 | if contract_config: 147 | return contract_config.conditional_tokens 148 | 149 | def get_exchange_address(self, neg_risk=False): 150 | """ 151 | Returns the exchange address 152 | """ 153 | contract_config = get_contract_config(self.chain_id, neg_risk) 154 | if contract_config: 155 | return contract_config.exchange 156 | 157 | def get_ok(self): 158 | """ 159 | Health check: Confirms that the server is up 160 | Does not need authentication 161 | """ 162 | return get("{}/".format(self.host)) 163 | 164 | def get_server_time(self): 165 | """ 166 | Returns the current timestamp on the server 167 | Does not need authentication 168 | """ 169 | return get("{}{}".format(self.host, TIME)) 170 | 171 | def create_api_key(self, nonce: int = None) -> ApiCreds: 172 | """ 173 | Creates a new CLOB API key for the given 174 | """ 175 | self.assert_level_1_auth() 176 | 177 | endpoint = "{}{}".format(self.host, CREATE_API_KEY) 178 | headers = create_level_1_headers(self.signer, nonce) 179 | 180 | creds_raw = post(endpoint, headers=headers) 181 | try: 182 | creds = ApiCreds( 183 | api_key=creds_raw["apiKey"], 184 | api_secret=creds_raw["secret"], 185 | api_passphrase=creds_raw["passphrase"], 186 | ) 187 | except: 188 | self.logger.error("Couldn't parse created CLOB creds") 189 | return None 190 | return creds 191 | 192 | def derive_api_key(self, nonce: int = None) -> ApiCreds: 193 | """ 194 | Derives an already existing CLOB API key for the given address and nonce 195 | """ 196 | self.assert_level_1_auth() 197 | 198 | endpoint = "{}{}".format(self.host, DERIVE_API_KEY) 199 | headers = create_level_1_headers(self.signer, nonce) 200 | 201 | creds_raw = get(endpoint, headers=headers) 202 | try: 203 | creds = ApiCreds( 204 | api_key=creds_raw["apiKey"], 205 | api_secret=creds_raw["secret"], 206 | api_passphrase=creds_raw["passphrase"], 207 | ) 208 | except: 209 | self.logger.error("Couldn't parse derived CLOB creds") 210 | return None 211 | return creds 212 | 213 | def create_or_derive_api_creds(self, nonce: int = None) -> ApiCreds: 214 | """ 215 | Creates API creds if not already created for nonce, otherwise derives them 216 | """ 217 | try: 218 | return self.create_api_key(nonce) 219 | except: 220 | return self.derive_api_key(nonce) 221 | 222 | def set_api_creds(self, creds: ApiCreds): 223 | """ 224 | Sets client api creds 225 | """ 226 | self.creds = creds 227 | self.mode = self._get_client_mode() 228 | 229 | def get_api_keys(self): 230 | """ 231 | Gets the available API keys for this address 232 | Level 2 Auth required 233 | """ 234 | self.assert_level_2_auth() 235 | 236 | request_args = RequestArgs(method="GET", request_path=GET_API_KEYS) 237 | headers = create_level_2_headers(self.signer, self.creds, request_args) 238 | return get("{}{}".format(self.host, GET_API_KEYS), headers=headers) 239 | 240 | def delete_api_key(self): 241 | """ 242 | Deletes an API key 243 | Level 2 Auth required 244 | """ 245 | self.assert_level_2_auth() 246 | 247 | request_args = RequestArgs(method="DELETE", request_path=DELETE_API_KEY) 248 | headers = create_level_2_headers(self.signer, self.creds, request_args) 249 | return delete("{}{}".format(self.host, DELETE_API_KEY), headers=headers) 250 | 251 | def get_midpoint(self, token_id): 252 | """ 253 | Get the mid market price for the given market 254 | """ 255 | return get("{}{}?token_id={}".format(self.host, MID_POINT, token_id)) 256 | 257 | def get_midpoints(self, params: list[BookParams]): 258 | """ 259 | Get the mid market prices for a set of token ids 260 | """ 261 | body = [{"token_id": param.token_id} for param in params] 262 | return post("{}{}".format(self.host, MID_POINTS), data=body) 263 | 264 | def get_price(self, token_id, side): 265 | """ 266 | Get the market price for the given market 267 | """ 268 | return get("{}{}?token_id={}&side={}".format(self.host, PRICE, token_id, side)) 269 | 270 | def get_prices(self, params: list[BookParams]): 271 | """ 272 | Get the market prices for a set 273 | """ 274 | body = [{"token_id": param.token_id, "side": param.side} for param in params] 275 | return post("{}{}".format(self.host, GET_PRICES), data=body) 276 | 277 | def get_spread(self, token_id): 278 | """ 279 | Get the spread for the given market 280 | """ 281 | return get("{}{}?token_id={}".format(self.host, GET_SPREAD, token_id)) 282 | 283 | def get_spreads(self, params: list[BookParams]): 284 | """ 285 | Get the spreads for a set of token ids 286 | """ 287 | body = [{"token_id": param.token_id} for param in params] 288 | return post("{}{}".format(self.host, GET_SPREADS), data=body) 289 | 290 | def get_tick_size(self, token_id: str) -> TickSize: 291 | if token_id in self.__tick_sizes: 292 | return self.__tick_sizes[token_id] 293 | 294 | result = get("{}{}?token_id={}".format(self.host, GET_TICK_SIZE, token_id)) 295 | self.__tick_sizes[token_id] = str(result["minimum_tick_size"]) 296 | 297 | return self.__tick_sizes[token_id] 298 | 299 | def get_neg_risk(self, token_id: str) -> bool: 300 | if token_id in self.__neg_risk: 301 | return self.__neg_risk[token_id] 302 | 303 | result = get("{}{}?token_id={}".format(self.host, GET_NEG_RISK, token_id)) 304 | self.__neg_risk[token_id] = result["neg_risk"] 305 | 306 | return result["neg_risk"] 307 | 308 | def __resolve_tick_size( 309 | self, token_id: str, tick_size: TickSize = None 310 | ) -> TickSize: 311 | min_tick_size = self.get_tick_size(token_id) 312 | if tick_size is not None: 313 | if is_tick_size_smaller(tick_size, min_tick_size): 314 | raise Exception( 315 | "invalid tick size (" 316 | + str(tick_size) 317 | + "), minimum for the market is " 318 | + str(min_tick_size), 319 | ) 320 | else: 321 | tick_size = min_tick_size 322 | return tick_size 323 | 324 | def create_order( 325 | self, order_args: OrderArgs, options: Optional[PartialCreateOrderOptions] = None 326 | ): 327 | """ 328 | Creates and signs an order 329 | Level 1 Auth required 330 | """ 331 | self.assert_level_1_auth() 332 | 333 | # add resolve_order_options, or similar 334 | tick_size = self.__resolve_tick_size( 335 | order_args.token_id, 336 | options.tick_size if options else None, 337 | ) 338 | 339 | if not price_valid(order_args.price, tick_size): 340 | raise Exception( 341 | "price (" 342 | + str(order_args.price) 343 | + "), min: " 344 | + str(tick_size) 345 | + " - max: " 346 | + str(1 - float(tick_size)) 347 | ) 348 | 349 | neg_risk = ( 350 | options.neg_risk 351 | if options and options.neg_risk 352 | else self.get_neg_risk(order_args.token_id) 353 | ) 354 | 355 | return self.builder.create_order( 356 | order_args, 357 | CreateOrderOptions( 358 | tick_size=tick_size, 359 | neg_risk=neg_risk, 360 | ), 361 | ) 362 | 363 | def create_market_order( 364 | self, 365 | order_args: MarketOrderArgs, 366 | options: Optional[PartialCreateOrderOptions] = None, 367 | ): 368 | """ 369 | Creates and signs an order 370 | Level 1 Auth required 371 | """ 372 | self.assert_level_1_auth() 373 | 374 | # add resolve_order_options, or similar 375 | tick_size = self.__resolve_tick_size( 376 | order_args.token_id, 377 | options.tick_size if options else None, 378 | ) 379 | 380 | if order_args.price is None or order_args.price <= 0: 381 | order_args.price = self.calculate_market_price( 382 | order_args.token_id, order_args.side, order_args.amount 383 | ) 384 | 385 | if not price_valid(order_args.price, tick_size): 386 | raise Exception( 387 | "price (" 388 | + str(order_args.price) 389 | + "), min: " 390 | + str(tick_size) 391 | + " - max: " 392 | + str(1 - float(tick_size)) 393 | ) 394 | 395 | neg_risk = ( 396 | options.neg_risk 397 | if options and options.neg_risk 398 | else self.get_neg_risk(order_args.token_id) 399 | ) 400 | 401 | return self.builder.create_market_order( 402 | order_args, 403 | CreateOrderOptions( 404 | tick_size=tick_size, 405 | neg_risk=neg_risk, 406 | ), 407 | ) 408 | 409 | def post_order(self, order, orderType: OrderType = OrderType.GTC): 410 | """ 411 | Posts the order 412 | """ 413 | self.assert_level_2_auth() 414 | body = order_to_json(order, self.creds.api_key, orderType) 415 | headers = create_level_2_headers( 416 | self.signer, 417 | self.creds, 418 | RequestArgs(method="POST", request_path=POST_ORDER, body=body), 419 | ) 420 | return post("{}{}".format(self.host, POST_ORDER), headers=headers, data=body) 421 | 422 | def create_and_post_order( 423 | self, order_args: OrderArgs, options: PartialCreateOrderOptions = None 424 | ): 425 | """ 426 | Utility function to create and publish an order 427 | """ 428 | ord = self.create_order(order_args, options) 429 | return self.post_order(ord) 430 | 431 | def cancel(self, order_id): 432 | """ 433 | Cancels an order 434 | Level 2 Auth required 435 | """ 436 | self.assert_level_2_auth() 437 | body = {"orderID": order_id} 438 | 439 | request_args = RequestArgs(method="DELETE", request_path=CANCEL, body=body) 440 | headers = create_level_2_headers(self.signer, self.creds, request_args) 441 | return delete("{}{}".format(self.host, CANCEL), headers=headers, data=body) 442 | 443 | def cancel_orders(self, order_ids): 444 | """ 445 | Cancels orders 446 | Level 2 Auth required 447 | """ 448 | self.assert_level_2_auth() 449 | body = order_ids 450 | 451 | request_args = RequestArgs( 452 | method="DELETE", request_path=CANCEL_ORDERS, body=body 453 | ) 454 | headers = create_level_2_headers(self.signer, self.creds, request_args) 455 | return delete( 456 | "{}{}".format(self.host, CANCEL_ORDERS), headers=headers, data=body 457 | ) 458 | 459 | def cancel_all(self): 460 | """ 461 | Cancels all available orders for the user 462 | Level 2 Auth required 463 | """ 464 | self.assert_level_2_auth() 465 | request_args = RequestArgs(method="DELETE", request_path=CANCEL_ALL) 466 | headers = create_level_2_headers(self.signer, self.creds, request_args) 467 | return delete("{}{}".format(self.host, CANCEL_ALL), headers=headers) 468 | 469 | def cancel_market_orders(self, market: str = "", asset_id: str = ""): 470 | """ 471 | Cancels orders 472 | Level 2 Auth required 473 | """ 474 | self.assert_level_2_auth() 475 | body = {"market": market, "asset_id": asset_id} 476 | 477 | request_args = RequestArgs( 478 | method="DELETE", request_path=CANCEL_MARKET_ORDERS, body=body 479 | ) 480 | headers = create_level_2_headers(self.signer, self.creds, request_args) 481 | return delete( 482 | "{}{}".format(self.host, CANCEL_MARKET_ORDERS), headers=headers, data=body 483 | ) 484 | 485 | def get_orders(self, params: OpenOrderParams = None, next_cursor="MA=="): 486 | """ 487 | Gets orders for the API key 488 | Requires Level 2 authentication 489 | """ 490 | self.assert_level_2_auth() 491 | request_args = RequestArgs(method="GET", request_path=ORDERS) 492 | headers = create_level_2_headers(self.signer, self.creds, request_args) 493 | 494 | results = [] 495 | next_cursor = next_cursor if next_cursor is not None else "MA==" 496 | while next_cursor != END_CURSOR: 497 | url = add_query_open_orders_params( 498 | "{}{}".format(self.host, ORDERS), params, next_cursor 499 | ) 500 | response = get(url, headers=headers) 501 | next_cursor = response["next_cursor"] 502 | results += response["data"] 503 | 504 | return results 505 | 506 | def get_order_book(self, token_id) -> OrderBookSummary: 507 | """ 508 | Fetches the orderbook for the token_id 509 | """ 510 | raw_obs = get("{}{}?token_id={}".format(self.host, GET_ORDER_BOOK, token_id)) 511 | return parse_raw_orderbook_summary(raw_obs) 512 | 513 | def get_order_books(self, params: list[BookParams]) -> list[OrderBookSummary]: 514 | """ 515 | Fetches the orderbook for a set of token ids 516 | """ 517 | body = [{"token_id": param.token_id} for param in params] 518 | raw_obs = post("{}{}".format(self.host, GET_ORDER_BOOKS), data=body) 519 | return [parse_raw_orderbook_summary(r) for r in raw_obs] 520 | 521 | def get_order_book_hash(self, orderbook: OrderBookSummary) -> str: 522 | """ 523 | Calculates the hash for the given orderbook 524 | """ 525 | return generate_orderbook_summary_hash(orderbook) 526 | 527 | def get_order(self, order_id): 528 | """ 529 | Fetches the order corresponding to the order_id 530 | Requires Level 2 authentication 531 | """ 532 | self.assert_level_2_auth() 533 | endpoint = "{}{}".format(GET_ORDER, order_id) 534 | request_args = RequestArgs(method="GET", request_path=endpoint) 535 | headers = create_level_2_headers(self.signer, self.creds, request_args) 536 | return get("{}{}".format(self.host, endpoint), headers=headers) 537 | 538 | def get_trades(self, params: TradeParams = None, next_cursor="MA=="): 539 | """ 540 | Fetches the trade history for a user 541 | Requires Level 2 authentication 542 | """ 543 | self.assert_level_2_auth() 544 | request_args = RequestArgs(method="GET", request_path=TRADES) 545 | headers = create_level_2_headers(self.signer, self.creds, request_args) 546 | 547 | results = [] 548 | next_cursor = next_cursor if next_cursor is not None else "MA==" 549 | while next_cursor != END_CURSOR: 550 | url = add_query_trade_params( 551 | "{}{}".format(self.host, TRADES), params, next_cursor 552 | ) 553 | response = get(url, headers=headers) 554 | next_cursor = response["next_cursor"] 555 | results += response["data"] 556 | 557 | return results 558 | 559 | def get_last_trade_price(self, token_id): 560 | """ 561 | Fetches the last trade price token_id 562 | """ 563 | return get("{}{}?token_id={}".format(self.host, GET_LAST_TRADE_PRICE, token_id)) 564 | 565 | def get_last_trades_prices(self, params: list[BookParams]): 566 | """ 567 | Fetches the last trades prices for a set of token ids 568 | """ 569 | body = [{"token_id": param.token_id} for param in params] 570 | return post("{}{}".format(self.host, GET_LAST_TRADES_PRICES), data=body) 571 | 572 | def assert_level_1_auth(self): 573 | """ 574 | Level 1 Poly Auth 575 | """ 576 | if self.mode < L1: 577 | raise PolyException(L1_AUTH_UNAVAILABLE) 578 | 579 | def assert_level_2_auth(self): 580 | """ 581 | Level 2 Poly Auth 582 | """ 583 | if self.mode < L2: 584 | raise PolyException(L2_AUTH_UNAVAILABLE) 585 | 586 | def _get_client_mode(self): 587 | if self.signer is not None and self.creds is not None: 588 | return L2 589 | if self.signer is not None: 590 | return L1 591 | return L0 592 | 593 | def get_notifications(self): 594 | """ 595 | Fetches the notifications for a user 596 | Requires Level 2 authentication 597 | """ 598 | self.assert_level_2_auth() 599 | request_args = RequestArgs(method="GET", request_path=GET_NOTIFICATIONS) 600 | headers = create_level_2_headers(self.signer, self.creds, request_args) 601 | url = "{}{}?signature_type={}".format( 602 | self.host, GET_NOTIFICATIONS, self.builder.sig_type 603 | ) 604 | return get(url, headers=headers) 605 | 606 | def drop_notifications(self, params: DropNotificationParams = None): 607 | """ 608 | Drops the notifications for a user 609 | Requires Level 2 authentication 610 | """ 611 | self.assert_level_2_auth() 612 | request_args = RequestArgs(method="DELETE", request_path=DROP_NOTIFICATIONS) 613 | headers = create_level_2_headers(self.signer, self.creds, request_args) 614 | url = drop_notifications_query_params( 615 | "{}{}".format(self.host, DROP_NOTIFICATIONS), params 616 | ) 617 | return delete(url, headers=headers) 618 | 619 | def get_balance_allowance(self, params: BalanceAllowanceParams = None): 620 | """ 621 | Fetches the balance & allowance for a user 622 | Requires Level 2 authentication 623 | """ 624 | self.assert_level_2_auth() 625 | request_args = RequestArgs(method="GET", request_path=GET_BALANCE_ALLOWANCE) 626 | headers = create_level_2_headers(self.signer, self.creds, request_args) 627 | if params.signature_type == -1: 628 | params.signature_type = self.builder.sig_type 629 | url = add_balance_allowance_params_to_url( 630 | "{}{}".format(self.host, GET_BALANCE_ALLOWANCE), params 631 | ) 632 | return get(url, headers=headers) 633 | 634 | def update_balance_allowance(self, params: BalanceAllowanceParams = None): 635 | """ 636 | Updates the balance & allowance for a user 637 | Requires Level 2 authentication 638 | """ 639 | self.assert_level_2_auth() 640 | request_args = RequestArgs(method="GET", request_path=UPDATE_BALANCE_ALLOWANCE) 641 | headers = create_level_2_headers(self.signer, self.creds, request_args) 642 | if params.signature_type == -1: 643 | params.signature_type = self.builder.sig_type 644 | url = add_balance_allowance_params_to_url( 645 | "{}{}".format(self.host, UPDATE_BALANCE_ALLOWANCE), params 646 | ) 647 | return get(url, headers=headers) 648 | 649 | def is_order_scoring(self, params: OrderScoringParams): 650 | """ 651 | Check if the order is currently scoring 652 | Requires Level 2 authentication 653 | """ 654 | self.assert_level_2_auth() 655 | request_args = RequestArgs(method="GET", request_path=IS_ORDER_SCORING) 656 | headers = create_level_2_headers(self.signer, self.creds, request_args) 657 | url = add_order_scoring_params_to_url( 658 | "{}{}".format(self.host, IS_ORDER_SCORING), params 659 | ) 660 | return get(url, headers=headers) 661 | 662 | def are_orders_scoring(self, params: OrdersScoringParams): 663 | """ 664 | Check if the orders are currently scoring 665 | Requires Level 2 authentication 666 | """ 667 | self.assert_level_2_auth() 668 | body = params.orderIds 669 | request_args = RequestArgs( 670 | method="POST", request_path=ARE_ORDERS_SCORING, body=body 671 | ) 672 | headers = create_level_2_headers(self.signer, self.creds, request_args) 673 | return post( 674 | "{}{}".format(self.host, ARE_ORDERS_SCORING), headers=headers, data=body 675 | ) 676 | 677 | def get_sampling_markets(self, next_cursor="MA=="): 678 | """ 679 | Get the current sampling markets 680 | """ 681 | return get( 682 | "{}{}?next_cursor={}".format(self.host, GET_SAMPLING_MARKETS, next_cursor) 683 | ) 684 | 685 | def get_sampling_simplified_markets(self, next_cursor="MA=="): 686 | """ 687 | Get the current sampling simplified markets 688 | """ 689 | return get( 690 | "{}{}?next_cursor={}".format( 691 | self.host, GET_SAMPLING_SIMPLIFIED_MARKETS, next_cursor 692 | ) 693 | ) 694 | 695 | def get_markets(self, next_cursor="MA=="): 696 | """ 697 | Get the current markets 698 | """ 699 | return get("{}{}?next_cursor={}".format(self.host, GET_MARKETS, next_cursor)) 700 | 701 | def get_simplified_markets(self, next_cursor="MA=="): 702 | """ 703 | Get the current simplified markets 704 | """ 705 | return get( 706 | "{}{}?next_cursor={}".format(self.host, GET_SIMPLIFIED_MARKETS, next_cursor) 707 | ) 708 | 709 | def get_market(self, condition_id): 710 | """ 711 | Get a market by condition_id 712 | """ 713 | return get("{}{}{}".format(self.host, GET_MARKET, condition_id)) 714 | 715 | def get_market_trades_events(self, condition_id): 716 | """ 717 | Get the market's trades events by condition id 718 | """ 719 | return get("{}{}{}".format(self.host, GET_MARKET_TRADES_EVENTS, condition_id)) 720 | 721 | def calculate_market_price(self, token_id: str, side: str, amount: float) -> float: 722 | """ 723 | Calculates the matching price considering an amount and the current orderbook 724 | """ 725 | book = self.get_order_book(token_id) 726 | if book is None: 727 | raise Exception("no orderbook") 728 | if side == "BUY": 729 | if book.asks is None: 730 | raise Exception("no match") 731 | return self.builder.calculate_market_price(book.asks, amount) 732 | else: 733 | if book.bids is None: 734 | raise Exception("no match") 735 | return self.builder.calculate_market_price(book.bids, amount) 736 | --------------------------------------------------------------------------------