den4ik2003

Untitled

Feb 18th, 2026
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.89 KB | None | 0 0
  1.     # ── on-chain claim (CTF redeemPositions) ─────────────────────────────
  2.  
  3.     # Polygon mainnet contract addresses
  4.     _CTF_ADDRESS = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
  5.     _USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
  6.  
  7.     def claim_event(self, event_slug: str) -> None:
  8.         """Claim (redeem) resolved positions for all markets in *event_slug*.
  9.  
  10.        Fetches conditionIds from Gamma, then sends a ``redeemPositions``
  11.        transaction to the CTF contract for each one.  No retry on failure —
  12.        silently logs and moves on (429 rate-limits are common).
  13.        """
  14.         if not self.live or not self.auth.polygon_rpc_url:
  15.             return
  16.  
  17.         condition_ids = fetch_event_condition_ids(event_slug)
  18.         if not condition_ids:
  19.             logger.info("claim: no condition ids for event=%s", event_slug)
  20.             return
  21.  
  22.         for cid in condition_ids:
  23.             try:
  24.                 tx_hash = self._send_redeem(cid)
  25.                 logger.info("claim: tx sent for condition=%s tx=%s", cid[:16], tx_hash)
  26.             except Exception as e:
  27.                 err = str(e)
  28.                 if "429" in err:
  29.                     logger.warning("claim: 429 rate-limit for condition=%s, skipping", cid[:16])
  30.                 else:
  31.                     logger.warning("claim: failed for condition=%s: %s", cid[:16], err)
  32.  
  33.     def _send_redeem(self, condition_id: str) -> str:
  34.         """Build, sign and broadcast a ``redeemPositions`` transaction.
  35.  
  36.        The Polymarket proxy wallet (POLY_FUNDER) is a Gnosis Safe with the
  37.        EOA as the sole owner.  We encode the inner ``redeemPositions`` call
  38.        and wrap it in ``execTransaction`` on the Safe, signed by the EOA.
  39.        """
  40.         import httpx
  41.         from eth_abi import encode as abi_encode
  42.         from eth_account import Account
  43.         from eth_account.messages import defunct_hash_message
  44.         from eth_utils import keccak
  45.  
  46.         rpc = self.auth.polygon_rpc_url
  47.         pk = self.auth.private_key
  48.         if not pk.startswith("0x"):
  49.             pk = "0x" + pk
  50.         safe_address = self.auth.funder
  51.  
  52.         account = Account.from_key(pk)
  53.  
  54.         def _rpc_call(method: str, params_list: list) -> Any:
  55.             resp = httpx.post(
  56.                 rpc,
  57.                 json={"jsonrpc": "2.0", "method": method, "params": params_list, "id": 1},
  58.                 timeout=15,
  59.             )
  60.             if resp.status_code == 429:
  61.                 raise RuntimeError("429 rate-limit from RPC")
  62.             body = resp.json()
  63.             if "error" in body:
  64.                 raise RuntimeError(body["error"])
  65.             return body["result"]
  66.  
  67.         # 1) Encode inner call: redeemPositions(address,bytes32,bytes32,uint256[])
  68.         redeem_sel = keccak(text="redeemPositions(address,bytes32,bytes32,uint256[])")[:4]
  69.         cond_bytes = bytes.fromhex(condition_id.replace("0x", "").zfill(64))
  70.         inner_data = redeem_sel + abi_encode(
  71.             ["address", "bytes32", "bytes32", "uint256[]"],
  72.             [self._USDC_ADDRESS, b"\x00" * 32, cond_bytes, [1, 2]],
  73.         )
  74.  
  75.         # 2) Get Safe nonce
  76.         safe_nonce_hex = _rpc_call("eth_call", [
  77.             {"to": safe_address, "data": "0xaffed0e0"}, "latest",
  78.         ])
  79.         safe_nonce = int(safe_nonce_hex, 16)
  80.  
  81.         # 3) Get Safe domainSeparator
  82.         domain_sep_hex = _rpc_call("eth_call", [
  83.             {"to": safe_address, "data": "0xf698da25"}, "latest",
  84.         ])
  85.         domain_separator = bytes.fromhex(domain_sep_hex[2:])
  86.  
  87.         # 4) Compute Safe transaction hash (EIP-712)
  88.         #    encodeTransactionData(to, value, data, operation, safeTxGas,
  89.         #                          baseGas, gasPrice, gasToken, refundReceiver, _nonce)
  90.         SAFE_TX_TYPEHASH = keccak(
  91.             text="SafeTx(address to,uint256 value,bytes data,uint8 operation,"
  92.                  "uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,"
  93.                  "address gasToken,address refundReceiver,uint256 nonce)"
  94.         )
  95.         ZERO_ADDR = "0x" + "0" * 40
  96.         encoded_tx_data = abi_encode(
  97.             ["bytes32", "address", "uint256", "bytes32", "uint8",
  98.              "uint256", "uint256", "uint256", "address", "address", "uint256"],
  99.             [
  100.                 SAFE_TX_TYPEHASH,
  101.                 self._CTF_ADDRESS,     # to
  102.                 0,                     # value
  103.                 keccak(inner_data),    # keccak256(data)
  104.                 0,                     # operation = Call
  105.                 0,                     # safeTxGas
  106.                 0,                     # baseGas
  107.                 0,                     # gasPrice
  108.                 ZERO_ADDR,             # gasToken
  109.                 ZERO_ADDR,             # refundReceiver
  110.                 safe_nonce,            # nonce
  111.             ],
  112.         )
  113.         safe_tx_hash = keccak(
  114.             b"\x19\x01" + domain_separator + keccak(encoded_tx_data)
  115.         )
  116.  
  117.         # 5) Sign the hash with EOA
  118.         signed_msg = account.signHash(safe_tx_hash)
  119.         # Pack signature: r(32) + s(32) + v(1)
  120.         signature = (
  121.             signed_msg.r.to_bytes(32, "big")
  122.             + signed_msg.s.to_bytes(32, "big")
  123.             + signed_msg.v.to_bytes(1, "big")
  124.         )
  125.  
  126.         # 6) Encode execTransaction call on the Safe
  127.         exec_sel = keccak(
  128.             text="execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)"
  129.         )[:4]
  130.         exec_params = abi_encode(
  131.             ["address", "uint256", "bytes", "uint8",
  132.              "uint256", "uint256", "uint256", "address", "address", "bytes"],
  133.             [
  134.                 self._CTF_ADDRESS,     # to
  135.                 0,                     # value
  136.                 inner_data,            # data
  137.                 0,                     # operation = Call
  138.                 0,                     # safeTxGas
  139.                 0,                     # baseGas
  140.                 0,                     # gasPrice
  141.                 ZERO_ADDR,             # gasToken
  142.                 ZERO_ADDR,             # refundReceiver
  143.                 signature,             # signatures
  144.             ],
  145.         )
  146.         outer_calldata = exec_sel + exec_params
  147.  
  148.         # 7) Send the outer transaction from EOA to the Safe
  149.         eoa_nonce = int(_rpc_call("eth_getTransactionCount", [account.address, "latest"]), 16)
  150.         gas_price = int(_rpc_call("eth_gasPrice", []), 16)
  151.  
  152.         tx = {
  153.             "to": safe_address,
  154.             "data": outer_calldata,
  155.             "value": 0,
  156.             "gas": 500_000,
  157.             "gasPrice": gas_price,
  158.             "nonce": eoa_nonce,
  159.             "chainId": self.auth.chain_id,
  160.         }
  161.         signed_tx = account.sign_transaction(tx)
  162.         raw_hex = "0x" + signed_tx.raw_transaction.hex()
  163.         return str(_rpc_call("eth_sendRawTransaction", [raw_hex]))
  164.  
Advertisement
Add Comment
Please, Sign In to add comment