From 7508654d0060cf198473073a27127b93ffa7cb27 Mon Sep 17 00:00:00 2001 From: Nawaz Haider Date: Wed, 14 Jan 2026 13:12:37 +0600 Subject: [PATCH] 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. --- abi/__init__.py | 0 abi/ctfAbi.py | 721 ++++++++++++++++++++++++++++++ abi/safeAbi.py | 1138 +++++++++++++++++++++++++++++++++++++++++++++++ merger.py | 215 +++++++++ 4 files changed, 2074 insertions(+) create mode 100644 abi/__init__.py create mode 100644 abi/ctfAbi.py create mode 100644 abi/safeAbi.py create mode 100644 merger.py diff --git a/abi/__init__.py b/abi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/abi/ctfAbi.py b/abi/ctfAbi.py new file mode 100644 index 0000000..e75c8a4 --- /dev/null +++ b/abi/ctfAbi.py @@ -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" + } + ] \ No newline at end of file diff --git a/abi/safeAbi.py b/abi/safeAbi.py new file mode 100644 index 0000000..b80d13b --- /dev/null +++ b/abi/safeAbi.py @@ -0,0 +1,1138 @@ +safe_abi = [ + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "AddedOwner", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "bytes32", + "name": "approvedHash", + "type": "bytes32" + }, + { + "indexed": True, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ApproveHash", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "handler", + "type": "address" + } + ], + "name": "ChangedFallbackHandler", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "guard", + "type": "address" + } + ], + "name": "ChangedGuard", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + } + ], + "name": "ChangedThreshold", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "module", + "type": "address" + } + ], + "name": "DisabledModule", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "module", + "type": "address" + } + ], + "name": "EnabledModule", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "payment", + "type": "uint256" + } + ], + "name": "ExecutionFailure", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "module", + "type": "address" + } + ], + "name": "ExecutionFromModuleFailure", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "module", + "type": "address" + } + ], + "name": "ExecutionFromModuleSuccess", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "payment", + "type": "uint256" + } + ], + "name": "ExecutionSuccess", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "RemovedOwner", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "module", + "type": "address" + }, + { + "indexed": False, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": False, + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "indexed": False, + "internalType": "enum Enum.Operation", + "name": "operation", + "type": "uint8" + } + ], + "name": "SafeModuleTransaction", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": False, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": False, + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "indexed": False, + "internalType": "enum Enum.Operation", + "name": "operation", + "type": "uint8" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "safeTxGas", + "type": "uint256" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "baseGas", + "type": "uint256" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "gasPrice", + "type": "uint256" + }, + { + "indexed": False, + "internalType": "address", + "name": "gasToken", + "type": "address" + }, + { + "indexed": False, + "internalType": "address payable", + "name": "refundReceiver", + "type": "address" + }, + { + "indexed": False, + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "indexed": False, + "internalType": "bytes", + "name": "additionalInfo", + "type": "bytes" + } + ], + "name": "SafeMultiSigTransaction", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeReceived", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "indexed": False, + "internalType": "address[]", + "name": "owners", + "type": "address[]" + }, + { + "indexed": False, + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "indexed": False, + "internalType": "address", + "name": "initializer", + "type": "address" + }, + { + "indexed": False, + "internalType": "address", + "name": "fallbackHandler", + "type": "address" + } + ], + "name": "SafeSetup", + "type": "event" + }, + { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "SignMsg", + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "fallback" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "addOwnerWithThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hashToApprove", + "type": "bytes32" + } + ], + "name": "approveHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "approvedHashes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "changeThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "dataHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "requiredSignatures", + "type": "uint256" + } + ], + "name": "checkNSignatures", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "dataHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "checkSignatures", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "prevModule", + "type": "address" + }, + { + "internalType": "address", + "name": "module", + "type": "address" + } + ], + "name": "disableModule", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "domainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "module", + "type": "address" + } + ], + "name": "enableModule", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "enum Enum.Operation", + "name": "operation", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "safeTxGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasPrice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "gasToken", + "type": "address" + }, + { + "internalType": "address", + "name": "refundReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + } + ], + "name": "encodeTransactionData", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "enum Enum.Operation", + "name": "operation", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "safeTxGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasPrice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "gasToken", + "type": "address" + }, + { + "internalType": "address payable", + "name": "refundReceiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "execTransaction", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "enum Enum.Operation", + "name": "operation", + "type": "uint8" + } + ], + "name": "execTransactionFromModule", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "enum Enum.Operation", + "name": "operation", + "type": "uint8" + } + ], + "name": "execTransactionFromModuleReturnData", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "start", + "type": "address" + }, + { + "internalType": "uint256", + "name": "pageSize", + "type": "uint256" + } + ], + "name": "getModulesPaginated", + "outputs": [ + { + "internalType": "address[]", + "name": "array", + "type": "address[]" + }, + { + "internalType": "address", + "name": "next", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOwners", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "getStorageAt", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "enum Enum.Operation", + "name": "operation", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "safeTxGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasPrice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "gasToken", + "type": "address" + }, + { + "internalType": "address", + "name": "refundReceiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + } + ], + "name": "getTransactionHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "module", + "type": "address" + } + ], + "name": "isModuleEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "isOwner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "prevOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "removeOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "enum Enum.Operation", + "name": "operation", + "type": "uint8" + } + ], + "name": "requiredTxGas", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "handler", + "type": "address" + } + ], + "name": "setFallbackHandler", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "guard", + "type": "address" + } + ], + "name": "setGuard", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_owners", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "address", + "name": "fallbackHandler", + "type": "address" + }, + { + "internalType": "address", + "name": "paymentToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "address payable", + "name": "paymentReceiver", + "type": "address" + } + ], + "name": "setup", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "signedMessages", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "targetContract", + "type": "address" + }, + { + "internalType": "bytes", + "name": "calldataPayload", + "type": "bytes" + } + ], + "name": "simulateAndRevert", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "prevOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "swapOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] \ No newline at end of file diff --git a/merger.py b/merger.py new file mode 100644 index 0000000..3a484ef --- /dev/null +++ b/merger.py @@ -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())