Add Safe contract ABI and implement token merging functionality
- Introduced `safeAbi.py` containing the ABI for the Safe contract, defining events and functions for managing ownership and transactions. - Created `merger.py` to handle merging conditional tokens back to USDC, including logic for determining merge amounts, signing transactions, and executing them through the Safe contract. - Integrated environment variable loading for sensitive data and RPC connection setup. - Implemented asynchronous polling for mergeable positions from the Polymarket API, with error handling and transaction receipt verification.
This commit is contained in:
+721
@@ -0,0 +1,721 @@
|
||||
ctf_abi = [
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"owner",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"id",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"balanceOf",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"interfaceId",
|
||||
"type":"bytes4"
|
||||
}
|
||||
],
|
||||
"name":"supportsInterface",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"payoutNumerators",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":False,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"from",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"to",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"ids",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"name":"values",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"name":"data",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"name":"safeBatchTransferFrom",
|
||||
"outputs":[
|
||||
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"owners",
|
||||
"type":"address[]"
|
||||
},
|
||||
{
|
||||
"name":"ids",
|
||||
"type":"uint256[]"
|
||||
}
|
||||
],
|
||||
"name":"balanceOfBatch",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"uint256[]"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":False,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"operator",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"approved",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"name":"setApprovalForAll",
|
||||
"outputs":[
|
||||
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"bytes32"
|
||||
}
|
||||
],
|
||||
"name":"payoutDenominator",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"owner",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"operator",
|
||||
"type":"address"
|
||||
}
|
||||
],
|
||||
"name":"isApprovedForAll",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":False,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"from",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"to",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"id",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"name":"value",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"name":"data",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"name":"safeTransferFrom",
|
||||
"outputs":[
|
||||
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"oracle",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"questionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"outcomeSlotCount",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"ConditionPreparation",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"oracle",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"questionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"outcomeSlotCount",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"payoutNumerators",
|
||||
"type":"uint256[]"
|
||||
}
|
||||
],
|
||||
"name":"ConditionResolution",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"stakeholder",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"collateralToken",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"parentCollectionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"partition",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"amount",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"PositionSplit",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"stakeholder",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"collateralToken",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"parentCollectionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"partition",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"amount",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"PositionsMerge",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"redeemer",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"collateralToken",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"parentCollectionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"indexSets",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"payout",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"PayoutRedemption",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"operator",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"from",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"to",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"id",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"value",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"TransferSingle",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"operator",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"from",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"to",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"ids",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"values",
|
||||
"type":"uint256[]"
|
||||
}
|
||||
],
|
||||
"name":"TransferBatch",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"owner",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"operator",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"approved",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"name":"ApprovalForAll",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"anonymous":False,
|
||||
"inputs":[
|
||||
{
|
||||
"indexed":False,
|
||||
"name":"value",
|
||||
"type":"string"
|
||||
},
|
||||
{
|
||||
"indexed":True,
|
||||
"name":"id",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"URI",
|
||||
"type":"event"
|
||||
},
|
||||
{
|
||||
"constant":False,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"oracle",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"questionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"outcomeSlotCount",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"prepareCondition",
|
||||
"outputs":[
|
||||
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":False,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"questionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"payouts",
|
||||
"type":"uint256[]"
|
||||
}
|
||||
],
|
||||
"name":"reportPayouts",
|
||||
"outputs":[
|
||||
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":False,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"collateralToken",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"parentCollectionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"partition",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"name":"amount",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"splitPosition",
|
||||
"outputs":[
|
||||
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":False,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"collateralToken",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"parentCollectionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"partition",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"name":"amount",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"mergePositions",
|
||||
"outputs":[
|
||||
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":False,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"collateralToken",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"parentCollectionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"indexSets",
|
||||
"type":"uint256[]"
|
||||
}
|
||||
],
|
||||
"name":"redeemPositions",
|
||||
"outputs":[
|
||||
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
}
|
||||
],
|
||||
"name":"getOutcomeSlotCount",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"oracle",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"questionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"outcomeSlotCount",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"getConditionId",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"bytes32"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"pure",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"parentCollectionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"conditionId",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"name":"indexSet",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"getCollectionId",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"bytes32"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":True,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"collateralToken",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"name":"collectionId",
|
||||
"type":"bytes32"
|
||||
}
|
||||
],
|
||||
"name":"getPositionId",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"payable":False,
|
||||
"stateMutability":"pure",
|
||||
"type":"function"
|
||||
}
|
||||
]
|
||||
+1138
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,215 @@
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
import asyncio
|
||||
from web3 import Web3
|
||||
from eth_account import Account
|
||||
from dotenv import load_dotenv
|
||||
from web3.middleware import ExtraDataToPOAMiddleware
|
||||
from abi.ctfAbi import ctf_abi
|
||||
from abi.safeAbi import safe_abi
|
||||
|
||||
# Constants
|
||||
CONDITIONAL_TOKENS_FRAMEWORK_ADDRESS = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
|
||||
NEG_RISK_ADAPTER_ADDRESS = "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"
|
||||
USDC_ADDRESS = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
|
||||
USDCE_DIGITS = 6
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def merge_tokens(condition_id, amount=None, neg_risk=False):
|
||||
"""
|
||||
Merge conditional tokens back to USDC.
|
||||
|
||||
Args:
|
||||
condition_id: The condition ID (bytes32 hex string)
|
||||
amount: Amount to merge in USDC (e.g., "1.5"). If None, merges all available tokens
|
||||
neg_risk: Whether to use NEG_RISK_ADAPTER (default: False)
|
||||
|
||||
Returns:
|
||||
bool: True if merge successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Connect to provider
|
||||
rpc_url = os.getenv("RPC_URL")
|
||||
w3 = Web3(Web3.HTTPProvider(rpc_url))
|
||||
|
||||
# Inject POA middleware for Polygon
|
||||
from web3.middleware import ExtraDataToPOAMiddleware
|
||||
|
||||
w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
|
||||
|
||||
# Load wallet and safe
|
||||
private_key = os.getenv("PRIVATE_KEY")
|
||||
account = Account.from_key(private_key)
|
||||
safe_address = Web3.to_checksum_address(os.getenv("POLYMARKET_PROXY_ADDRESS"))
|
||||
|
||||
safe = w3.eth.contract(address=safe_address, abi=safe_abi)
|
||||
|
||||
# Determine merge amount
|
||||
if amount is None:
|
||||
# Get minimum balance of both positions
|
||||
ctf_contract = w3.eth.contract(
|
||||
address=Web3.to_checksum_address(CONDITIONAL_TOKENS_FRAMEWORK_ADDRESS),
|
||||
abi=ctf_abi,
|
||||
)
|
||||
|
||||
parent_collection_id = bytes(32)
|
||||
collection_id_0 = ctf_contract.functions.getCollectionId(
|
||||
parent_collection_id, bytes.fromhex(condition_id[2:]), 1
|
||||
).call()
|
||||
collection_id_1 = ctf_contract.functions.getCollectionId(
|
||||
parent_collection_id, bytes.fromhex(condition_id[2:]), 2
|
||||
).call()
|
||||
|
||||
position_id_0 = ctf_contract.functions.getPositionId(
|
||||
Web3.to_checksum_address(USDC_ADDRESS), collection_id_0
|
||||
).call()
|
||||
position_id_1 = ctf_contract.functions.getPositionId(
|
||||
Web3.to_checksum_address(USDC_ADDRESS), collection_id_1
|
||||
).call()
|
||||
|
||||
balance_0 = ctf_contract.functions.balanceOf(
|
||||
safe_address, position_id_0
|
||||
).call()
|
||||
balance_1 = ctf_contract.functions.balanceOf(
|
||||
safe_address, position_id_1
|
||||
).call()
|
||||
|
||||
amount_wei = min(balance_0, balance_1)
|
||||
|
||||
if amount_wei == 0:
|
||||
print("Merge failed: No tokens to merge")
|
||||
return False
|
||||
else:
|
||||
amount_wei = int(float(amount) * (10**USDCE_DIGITS))
|
||||
|
||||
# Encode merge transaction
|
||||
ctf_contract = w3.eth.contract(abi=ctf_abi)
|
||||
parent_collection_id = (
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
)
|
||||
partition = [1, 2]
|
||||
|
||||
data = ctf_contract.functions.mergePositions(
|
||||
Web3.to_checksum_address(USDC_ADDRESS),
|
||||
bytes.fromhex(parent_collection_id[2:]),
|
||||
bytes.fromhex(condition_id[2:]),
|
||||
partition,
|
||||
amount_wei,
|
||||
)._encode_transaction_data()
|
||||
|
||||
# Sign and execute Safe transaction
|
||||
nonce = safe.functions.nonce().call()
|
||||
to = (
|
||||
NEG_RISK_ADAPTER_ADDRESS
|
||||
if neg_risk
|
||||
else CONDITIONAL_TOKENS_FRAMEWORK_ADDRESS
|
||||
)
|
||||
|
||||
tx_hash = safe.functions.getTransactionHash(
|
||||
Web3.to_checksum_address(to),
|
||||
0,
|
||||
bytes.fromhex(data[2:]),
|
||||
0, # operation: Call
|
||||
0,
|
||||
0,
|
||||
0, # safeTxGas, baseGas, gasPrice
|
||||
"0x0000000000000000000000000000000000000000", # gasToken
|
||||
"0x0000000000000000000000000000000000000000", # refundReceiver
|
||||
nonce,
|
||||
).call()
|
||||
|
||||
# Sign the hash
|
||||
hash_bytes = Web3.to_bytes(
|
||||
hexstr=tx_hash.hex() if hasattr(tx_hash, "hex") else tx_hash
|
||||
)
|
||||
signature_obj = account.unsafe_sign_hash(hash_bytes)
|
||||
|
||||
r = signature_obj.r.to_bytes(32, byteorder="big")
|
||||
s = signature_obj.s.to_bytes(32, byteorder="big")
|
||||
v = signature_obj.v.to_bytes(1, byteorder="big")
|
||||
signature = r + s + v
|
||||
|
||||
# Build and send transaction
|
||||
tx = safe.functions.execTransaction(
|
||||
Web3.to_checksum_address(to),
|
||||
0,
|
||||
bytes.fromhex(data[2:]),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
signature,
|
||||
).build_transaction(
|
||||
{
|
||||
"from": account.address,
|
||||
"nonce": w3.eth.get_transaction_count(account.address),
|
||||
"gas": 500000,
|
||||
"gasPrice": w3.eth.gas_price,
|
||||
}
|
||||
)
|
||||
|
||||
signed_tx = account.sign_transaction(tx)
|
||||
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
|
||||
|
||||
# Wait for receipt
|
||||
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
|
||||
|
||||
# Check if transaction succeeded
|
||||
if receipt["status"] == 1:
|
||||
print(f"Merge successful! Amount: {amount_wei / 10**USDCE_DIGITS} USDC")
|
||||
return True
|
||||
else:
|
||||
print("Merge failed: Transaction reverted")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"Merge failed: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
private_key = os.getenv("PRIVATE_KEY")
|
||||
account = Account.from_key(private_key)
|
||||
safe_address = Web3.to_checksum_address(os.getenv("POLYMARKET_PROXY_ADDRESS"))
|
||||
w3 = Web3(Web3.HTTPProvider(os.getenv("RPC_URL")))
|
||||
w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
|
||||
matic_balance = w3.eth.get_balance(account.address)
|
||||
proxy_balance = w3.eth.get_balance(safe_address)
|
||||
|
||||
print(f"--- Wallet Check ---")
|
||||
print(f"Signer Address (from Private Key): {account.address}")
|
||||
print(f"Signer POL Balance: {w3.from_wei(matic_balance, 'ether')} POL")
|
||||
print(f"Proxy Address (Safe): {safe_address}")
|
||||
print(f"Proxy POL Balance: {w3.from_wei(proxy_balance, 'ether')} POL")
|
||||
print(f"--------------------")
|
||||
|
||||
if matic_balance == 0:
|
||||
raise Exception(f"STOP: Your Signer address {account.address} has NO gas!")
|
||||
|
||||
|
||||
async def do_it():
|
||||
|
||||
url = f"https://data-api.polymarket.com/positions?sizeThreshold=1&limit=100&sortBy=TOKENS&sortDirection=DESC&user={os.getenv('POLYMARKET_PROXY_ADDRESS')}&mergeable=true"
|
||||
|
||||
response = requests.get(url).json()
|
||||
if not response:
|
||||
print("No mergeable positions found.")
|
||||
return
|
||||
condition_ids = set([token["conditionId"] for token in response])
|
||||
condition_ids
|
||||
for condition_id in condition_ids:
|
||||
print("Merging tokens for condition ID:", condition_id)
|
||||
merge_tokens(condition_id)
|
||||
|
||||
|
||||
async def main():
|
||||
while True:
|
||||
asyncio.create_task(do_it())
|
||||
await asyncio.sleep(10)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user