Skip to content

Commit 92c5f37

Browse files
authored
fix: claim winnings in client (#1)
1 parent f963edb commit 92c5f37

File tree

3 files changed

+145
-16
lines changed

3 files changed

+145
-16
lines changed

.gitignore

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,10 @@ cython_debug/
173173
# PyPI configuration file
174174
.pypirc
175175

176-
# Cursor
177-
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
176+
# Cursor
177+
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
178178
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
179179
# refer to https://docs.cursor.com/context/ignore-files
180180
.cursorignore
181-
.cursorindexingignore
181+
.cursorindexingignore
182+
CLAUDE.md

examples/claim_winnings.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
Example script for claiming winnings from a resolved market.
3+
4+
This example shows how to:
5+
- Create an authenticated client
6+
- Claim winnings from a resolved market using gasless permits
7+
8+
Requirements:
9+
- Set TURBINE_PRIVATE_KEY environment variable (your wallet private key)
10+
- Set TURBINE_API_KEY_ID and TURBINE_API_PRIVATE_KEY (API credentials)
11+
- Have winning position tokens in the resolved market
12+
13+
Usage:
14+
python examples/claim_winnings.py <market_contract_address>
15+
16+
Example:
17+
python examples/claim_winnings.py 0xB8c45e915F8a78ff8FD691bBDED2125bc9Fa4d96
18+
"""
19+
20+
import os
21+
import sys
22+
23+
try:
24+
from dotenv import load_dotenv
25+
load_dotenv()
26+
except ImportError:
27+
pass # dotenv is optional, use exported env vars
28+
29+
from turbine_client import TurbineClient
30+
31+
32+
def main():
33+
if len(sys.argv) < 2:
34+
print("Usage: python examples/claim_winnings.py <market_contract_address> [chain_id]")
35+
print("\nSupported chains:")
36+
print(" 137 - Polygon mainnet (default)")
37+
print(" 43114 - Avalanche mainnet")
38+
print(" 84532 - Base Sepolia")
39+
print("\nExample:")
40+
print(" python examples/claim_winnings.py 0xB8c45e915F8a78ff8FD691bBDED2125bc9Fa4d96")
41+
print(" python examples/claim_winnings.py 0x... 43114 # Avalanche")
42+
sys.exit(1)
43+
44+
market_contract_address = sys.argv[1]
45+
chain_id = int(sys.argv[2]) if len(sys.argv) > 2 else 137
46+
47+
# Get credentials from environment
48+
private_key = os.getenv("TURBINE_PRIVATE_KEY")
49+
api_key_id = os.getenv("TURBINE_API_KEY_ID")
50+
api_private_key = os.getenv("TURBINE_API_PRIVATE_KEY")
51+
52+
if not private_key:
53+
print("Error: TURBINE_PRIVATE_KEY environment variable not set")
54+
sys.exit(1)
55+
56+
if not api_key_id or not api_private_key:
57+
print("Error: TURBINE_API_KEY_ID and TURBINE_API_PRIVATE_KEY must be set")
58+
print("\nYou can register for API credentials using:")
59+
print(" from turbine_client import TurbineClient")
60+
print(" creds = TurbineClient.request_api_credentials(")
61+
print(' host="https://api.turbinefi.com",')
62+
print(' private_key="0x...",')
63+
print(" )")
64+
sys.exit(1)
65+
66+
# Create authenticated client
67+
client = TurbineClient(
68+
host="https://api.turbinefi.com",
69+
chain_id=chain_id,
70+
private_key=private_key,
71+
api_key_id=api_key_id,
72+
api_private_key=api_private_key,
73+
)
74+
75+
print(f"Claiming winnings from market: {market_contract_address}")
76+
print(f"Chain ID: {chain_id}")
77+
print(f"Wallet: {client.address}")
78+
print()
79+
80+
try:
81+
result = client.claim_winnings(market_contract_address)
82+
print("\nSuccess!")
83+
print(f"Transaction hash: {result.get('tx_hash', result)}")
84+
except ValueError as e:
85+
print(f"\nError: {e}")
86+
sys.exit(1)
87+
except Exception as e:
88+
print(f"\nUnexpected error: {e}")
89+
sys.exit(1)
90+
finally:
91+
client.close()
92+
93+
94+
if __name__ == "__main__":
95+
main()

turbine_client/client.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -983,21 +983,49 @@ def claim_winnings(
983983
raise ValueError(f"No RPC URL for chain {self._chain_id}")
984984

985985
w3 = Web3(Web3.HTTPProvider(rpc_url))
986+
print(f"Connected to RPC: {rpc_url}")
986987

987-
# Query getStaticMarketData() from market contract
988-
# Returns: (ctf, collateralToken, conditionId, yesTokenId, noTokenId)
989-
static_data_selector = "0x3e47d6f3" # getStaticMarketData()
990-
static_data = w3.eth.call({
988+
# Ensure address is checksummed
989+
market_contract_address = Web3.to_checksum_address(market_contract_address)
990+
print(f"Market address: {market_contract_address}")
991+
992+
# Query individual getters from market contract
993+
# ctf() -> address
994+
print("Querying ctf()...")
995+
ctf_data = w3.eth.call({
996+
"to": market_contract_address,
997+
"data": "0x22a9339f", # ctf()
998+
})
999+
ctf_address = Web3.to_checksum_address("0x" + ctf_data[12:32].hex())
1000+
print(f"CTF address: {ctf_address}")
1001+
1002+
# collateralToken() -> address
1003+
collateral_data = w3.eth.call({
1004+
"to": market_contract_address,
1005+
"data": "0xb2016bd4", # collateralToken()
1006+
})
1007+
collateral_token = Web3.to_checksum_address("0x" + collateral_data[12:32].hex())
1008+
1009+
# conditionId() -> bytes32
1010+
condition_data = w3.eth.call({
1011+
"to": market_contract_address,
1012+
"data": "0x2ddc7de7", # conditionId()
1013+
})
1014+
condition_id = "0x" + condition_data.hex()
1015+
1016+
# yesTokenId() -> uint256
1017+
yes_data = w3.eth.call({
9911018
"to": market_contract_address,
992-
"data": static_data_selector,
1019+
"data": "0x76cd28a2", # yesTokenId()
9931020
})
1021+
yes_token_id = int(yes_data.hex(), 16)
9941022

995-
# Decode the response (5 values: address, address, bytes32, uint256, uint256)
996-
ctf_address = "0x" + static_data[12:32].hex()
997-
collateral_token = "0x" + static_data[44:64].hex()
998-
condition_id = "0x" + static_data[64:96].hex()
999-
yes_token_id = int(static_data[96:128].hex(), 16)
1000-
no_token_id = int(static_data[128:160].hex(), 16)
1023+
# noTokenId() -> uint256
1024+
no_data = w3.eth.call({
1025+
"to": market_contract_address,
1026+
"data": "0x8c2557a8", # noTokenId()
1027+
})
1028+
no_token_id = int(no_data.hex(), 16)
10011029

10021030
print(f"Market data:")
10031031
print(f" CTF: {ctf_address}")
@@ -1006,14 +1034,19 @@ def claim_winnings(
10061034

10071035
# Query getResolutionStatus() from market contract
10081036
# Returns: (expired, resolved, assertionId, winningOutcome, canPropose, canSettle)
1009-
resolution_selector = "0x7a9262a2" # getResolutionStatus()
1037+
resolution_selector = "0x13b63fce" # getResolutionStatus()
10101038
resolution_data = w3.eth.call({
10111039
"to": market_contract_address,
10121040
"data": resolution_selector,
10131041
})
10141042

10151043
# Decode: bool, bool, bytes32, uint8, bool, bool
1016-
resolved = bool(int(resolution_data[31:32].hex(), 16))
1044+
# Each value is padded to 32 bytes:
1045+
# expired: bytes 0-32 (value at byte 31)
1046+
# resolved: bytes 32-64 (value at byte 63)
1047+
# assertionId: bytes 64-96
1048+
# winningOutcome: bytes 96-128 (uint8 padded)
1049+
resolved = bool(resolution_data[63])
10171050
winning_outcome = int(resolution_data[96:128].hex(), 16)
10181051

10191052
if not resolved:

0 commit comments

Comments
 (0)