Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # ── on-chain claim (CTF redeemPositions) ─────────────────────────────
- # Polygon mainnet contract addresses
- _CTF_ADDRESS = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
- _USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
- def claim_event(self, event_slug: str) -> None:
- """Claim (redeem) resolved positions for all markets in *event_slug*.
- Fetches conditionIds from Gamma, then sends a ``redeemPositions``
- transaction to the CTF contract for each one. No retry on failure —
- silently logs and moves on (429 rate-limits are common).
- """
- if not self.live or not self.auth.polygon_rpc_url:
- return
- condition_ids = fetch_event_condition_ids(event_slug)
- if not condition_ids:
- logger.info("claim: no condition ids for event=%s", event_slug)
- return
- for cid in condition_ids:
- try:
- tx_hash = self._send_redeem(cid)
- logger.info("claim: tx sent for condition=%s tx=%s", cid[:16], tx_hash)
- except Exception as e:
- err = str(e)
- if "429" in err:
- logger.warning("claim: 429 rate-limit for condition=%s, skipping", cid[:16])
- else:
- logger.warning("claim: failed for condition=%s: %s", cid[:16], err)
- def _send_redeem(self, condition_id: str) -> str:
- """Build, sign and broadcast a ``redeemPositions`` transaction.
- The Polymarket proxy wallet (POLY_FUNDER) is a Gnosis Safe with the
- EOA as the sole owner. We encode the inner ``redeemPositions`` call
- and wrap it in ``execTransaction`` on the Safe, signed by the EOA.
- """
- import httpx
- from eth_abi import encode as abi_encode
- from eth_account import Account
- from eth_account.messages import defunct_hash_message
- from eth_utils import keccak
- rpc = self.auth.polygon_rpc_url
- pk = self.auth.private_key
- if not pk.startswith("0x"):
- pk = "0x" + pk
- safe_address = self.auth.funder
- account = Account.from_key(pk)
- def _rpc_call(method: str, params_list: list) -> Any:
- resp = httpx.post(
- rpc,
- json={"jsonrpc": "2.0", "method": method, "params": params_list, "id": 1},
- timeout=15,
- )
- if resp.status_code == 429:
- raise RuntimeError("429 rate-limit from RPC")
- body = resp.json()
- if "error" in body:
- raise RuntimeError(body["error"])
- return body["result"]
- # 1) Encode inner call: redeemPositions(address,bytes32,bytes32,uint256[])
- redeem_sel = keccak(text="redeemPositions(address,bytes32,bytes32,uint256[])")[:4]
- cond_bytes = bytes.fromhex(condition_id.replace("0x", "").zfill(64))
- inner_data = redeem_sel + abi_encode(
- ["address", "bytes32", "bytes32", "uint256[]"],
- [self._USDC_ADDRESS, b"\x00" * 32, cond_bytes, [1, 2]],
- )
- # 2) Get Safe nonce
- safe_nonce_hex = _rpc_call("eth_call", [
- {"to": safe_address, "data": "0xaffed0e0"}, "latest",
- ])
- safe_nonce = int(safe_nonce_hex, 16)
- # 3) Get Safe domainSeparator
- domain_sep_hex = _rpc_call("eth_call", [
- {"to": safe_address, "data": "0xf698da25"}, "latest",
- ])
- domain_separator = bytes.fromhex(domain_sep_hex[2:])
- # 4) Compute Safe transaction hash (EIP-712)
- # encodeTransactionData(to, value, data, operation, safeTxGas,
- # baseGas, gasPrice, gasToken, refundReceiver, _nonce)
- SAFE_TX_TYPEHASH = keccak(
- text="SafeTx(address to,uint256 value,bytes data,uint8 operation,"
- "uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,"
- "address gasToken,address refundReceiver,uint256 nonce)"
- )
- ZERO_ADDR = "0x" + "0" * 40
- encoded_tx_data = abi_encode(
- ["bytes32", "address", "uint256", "bytes32", "uint8",
- "uint256", "uint256", "uint256", "address", "address", "uint256"],
- [
- SAFE_TX_TYPEHASH,
- self._CTF_ADDRESS, # to
- 0, # value
- keccak(inner_data), # keccak256(data)
- 0, # operation = Call
- 0, # safeTxGas
- 0, # baseGas
- 0, # gasPrice
- ZERO_ADDR, # gasToken
- ZERO_ADDR, # refundReceiver
- safe_nonce, # nonce
- ],
- )
- safe_tx_hash = keccak(
- b"\x19\x01" + domain_separator + keccak(encoded_tx_data)
- )
- # 5) Sign the hash with EOA
- signed_msg = account.signHash(safe_tx_hash)
- # Pack signature: r(32) + s(32) + v(1)
- signature = (
- signed_msg.r.to_bytes(32, "big")
- + signed_msg.s.to_bytes(32, "big")
- + signed_msg.v.to_bytes(1, "big")
- )
- # 6) Encode execTransaction call on the Safe
- exec_sel = keccak(
- text="execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)"
- )[:4]
- exec_params = abi_encode(
- ["address", "uint256", "bytes", "uint8",
- "uint256", "uint256", "uint256", "address", "address", "bytes"],
- [
- self._CTF_ADDRESS, # to
- 0, # value
- inner_data, # data
- 0, # operation = Call
- 0, # safeTxGas
- 0, # baseGas
- 0, # gasPrice
- ZERO_ADDR, # gasToken
- ZERO_ADDR, # refundReceiver
- signature, # signatures
- ],
- )
- outer_calldata = exec_sel + exec_params
- # 7) Send the outer transaction from EOA to the Safe
- eoa_nonce = int(_rpc_call("eth_getTransactionCount", [account.address, "latest"]), 16)
- gas_price = int(_rpc_call("eth_gasPrice", []), 16)
- tx = {
- "to": safe_address,
- "data": outer_calldata,
- "value": 0,
- "gas": 500_000,
- "gasPrice": gas_price,
- "nonce": eoa_nonce,
- "chainId": self.auth.chain_id,
- }
- signed_tx = account.sign_transaction(tx)
- raw_hex = "0x" + signed_tx.raw_transaction.hex()
- return str(_rpc_call("eth_sendRawTransaction", [raw_hex]))
Advertisement
Add Comment
Please, Sign In to add comment