Repo is ready

This commit is contained in:
warproxxx
2025-03-31 12:16:51 -04:00
commit 8e500d9437
35 changed files with 6044 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
# Poly-Merger
A utility for merging Polymarket positions efficiently. This tool helps in consolidating opposite positions in the same market, allowing you to:
1. Reduce gas costs
2. Free up capital
3. Simplify position management
## How It Works
The merger tool interacts with Polymarket's smart contracts to combine opposite positions in binary markets. When you hold both YES and NO shares in the same market, this tool will merge them to recover your USDC.
## Usage
The merger is invoked through the main Poly-Maker bot when position merging conditions are met, but you can also use it independently:
```
node merge.js [amount_to_merge] [condition_id] [is_neg_risk_market]
```
Example:
```
node merge.js 1000000 1234567 true
```
This would merge 1 USDC worth of opposing positions in market 1234567, which is a negative risk market.
## Prerequisites
- Node.js
- ethers.js v5.x
- A .env file with your Polygon network private key
## Notes
This implementation is based on open-source Polymarket code but has been optimized for automated market making operations.
+136
View File
@@ -0,0 +1,136 @@
/**
* Poly-Merger: Position Merging Utility for Polymarket
*
* This script handles merging of YES and NO positions in Polymarket prediction markets
* to recover collateral. It works with both regular and negative risk markets.
*
* The merger supports Gnosis Safe wallets through the safe-helpers.js utility.
*
* Usage:
* node merge.js [amountToMerge] [conditionId] [isNegRiskMarket]
*
* Example:
* node merge.js 1000000 12345 true
*/
const { ethers } = require('ethers');
const { resolve } = require('path');
const { signAndExecuteSafeTransaction } = require('./safe-helpers');
const { safeAbi } = require('./safeAbi');
// Load environment variables
require('dotenv').config()
// Connect to Polygon network
const provider = new ethers.providers.JsonRpcProvider("https://polygon.llamarpc.com");
const privateKey = process.env.PK;
const wallet = new ethers.Wallet(privateKey, provider);
// Polymarket contract addresses
const addresses = {
// Adapter contract for negative risk markets
neg_risk_adapter: '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296',
// USDC token contract on Polygon
collateral: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
// Main conditional tokens contract for prediction markets
conditional_tokens: '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045'
};
// Minimal ABIs for the contracts we interact with
const negRiskAdapterAbi = [
"function mergePositions(bytes32 conditionId, uint256 amount)"
];
const conditionalTokensAbi = [
"function mergePositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)"
];
/**
* Merges YES and NO positions in a Polymarket prediction market to recover USDC collateral.
*
* This function handles both regular and negative risk markets via different contract calls.
* It uses the Gnosis Safe wallet infrastructure for secure transaction execution.
*
* @param {string|number} amountToMerge - Raw amount of tokens to merge (typically expressed in raw units, e.g., 1000000 = 1 USDC)
* @param {string|number} conditionId - The market's condition ID
* @param {boolean} isNegRiskMarket - Whether this is a negative risk market (uses different contract)
* @returns {string} The transaction hash of the merge operation
*/
async function mergePositions(amountToMerge, conditionId, isNegRiskMarket) {
// Log parameters for debugging
console.log(amountToMerge, conditionId, isNegRiskMarket);
// Prepare transaction parameters
const nonce = await provider.getTransactionCount(wallet.address);
const gasPrice = await provider.getGasPrice();
const gasLimit = 10000000; // Set high gas limit to ensure transaction completes
let tx;
// Different contract calls for different market types
if (isNegRiskMarket) {
// For negative risk markets, use the adapter contract
const negRiskAdapter = new ethers.Contract(addresses.neg_risk_adapter, negRiskAdapterAbi, wallet);
tx = await negRiskAdapter.populateTransaction.mergePositions(conditionId, amountToMerge);
} else {
// For regular markets, use the conditional tokens contract directly
const conditionalTokens = new ethers.Contract(addresses.conditional_tokens, conditionalTokensAbi, wallet);
tx = await conditionalTokens.populateTransaction.mergePositions(
addresses.collateral, // USDC contract
ethers.constants.HashZero, // Parent collection ID (0 for top-level markets)
conditionId, // Market ID
[1, 2], // Partition (indexes of outcomes to merge)
amountToMerge // Amount to merge
);
}
// Prepare full transaction object
const transaction = {
...tx,
chainId: 137, // Polygon chain ID
gasPrice: gasPrice,
gasLimit: gasLimit,
nonce: nonce
};
// Get the Safe address from environment variables
const safeAddress = process.env.BROWSER_ADDRESS;
const safe = new ethers.Contract(safeAddress, safeAbi, wallet);
// Execute the transaction through the Safe
console.log("Signing Transaction")
const txResponse = await signAndExecuteSafeTransaction(
wallet,
safe,
transaction.to,
transaction.data,
{
gasPrice: transaction.gasPrice,
gasLimit: transaction.gasLimit
}
);
console.log("Sent transaction. Waiting for response")
const txReceipt = await txResponse.wait();
console.log("merge positions " + txReceipt.transactionHash);
return txReceipt.transactionHash;
}
// Parse command line arguments
const args = process.argv.slice(2);
// Amount of tokens to merge (in raw units, e.g., 1000000 = 1 USDC)
const amountToMerge = args[0];
// The market's condition ID
const conditionId = args[1];
// Whether this is a negative risk market (true/false)
const isNegRiskMarket = args[2] === 'true';
// Execute the merge operation and handle any errors
mergePositions(amountToMerge, conditionId, isNegRiskMarket)
.catch(error => {
console.error("Error merging positions:", error);
process.exit(1);
});
+1339
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
{
"name": "poly-merger",
"version": "1.0.0",
"description": "Position merging utility for Polymarket",
"main": "merge.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"dotenv": "^16.4.5",
"ethers": "^5.6.3"
}
}
+128
View File
@@ -0,0 +1,128 @@
const { BigNumber, ethers } = require('ethers');
function joinHexData(hexData) {
return `0x${hexData
.map(hex => {
const stripped = hex.replace(/^0x/, "");
return stripped.length % 2 === 0 ? stripped : "0" + stripped;
})
.join("")}`;
}
function abiEncodePacked(...params) {
return joinHexData(
params.map(({ type, value }) => {
const encoded = ethers.utils.defaultAbiCoder.encode([type], [value]);
if (type === "bytes" || type === "string") {
const bytesLength = parseInt(encoded.slice(66, 130), 16);
return encoded.slice(130, 130 + 2 * bytesLength);
}
let typeMatch = type.match(/^(?:u?int\d*|bytes\d+|address)\[\]$/);
if (typeMatch) {
return encoded.slice(130);
}
if (type.startsWith("bytes")) {
const bytesLength = parseInt(type.slice(5));
return encoded.slice(2, 2 + 2 * bytesLength);
}
typeMatch = type.match(/^u?int(\d*)$/);
if (typeMatch) {
if (typeMatch[1] !== "") {
const bytesLength = parseInt(typeMatch[1]) / 8;
return encoded.slice(-2 * bytesLength);
}
return encoded.slice(-64);
}
if (type === "address") {
return encoded.slice(-40);
}
throw new Error(`unsupported type ${type}`);
})
);
}
async function signTransactionHash(signer, message) {
const messageArray = ethers.utils.arrayify(message);
let sig = await signer.signMessage(messageArray);
let sigV = parseInt(sig.slice(-2), 16);
switch (sigV) {
case 0:
case 1:
sigV += 31;
break;
case 27:
case 28:
sigV += 4;
break;
default:
throw new Error("Invalid signature");
}
sig = sig.slice(0, -2) + sigV.toString(16);
return {
r: BigNumber.from("0x" + sig.slice(2, 66)).toString(),
s: BigNumber.from("0x" + sig.slice(66, 130)).toString(),
v: BigNumber.from("0x" + sig.slice(130, 132)).toString(),
};
}
async function signAndExecuteSafeTransaction(signer, safe, to, data, overrides = {}) {
const nonce = await safe.nonce();
console.log("Nonce for safe: ", nonce);
const value = "0";
const safeTxGas = "0";
const baseGas = "0";
const gasPrice = "0";
const gasToken = ethers.constants.AddressZero;
const refundReceiver = ethers.constants.AddressZero;
const operation = 0;
const txHash = await safe.getTransactionHash(
to,
value,
data,
operation,
safeTxGas,
baseGas,
gasPrice,
gasToken,
refundReceiver,
nonce
);
console.log("Transaction hash: ", txHash);
const rsvSignature = await signTransactionHash(signer, txHash);
const packedSig = abiEncodePacked(
{ type: "uint256", value: rsvSignature.r },
{ type: "uint256", value: rsvSignature.s },
{ type: "uint8", value: rsvSignature.v }
);
console.log("Executing transaction");
return safe.execTransaction(
to,
value,
data,
operation,
safeTxGas,
baseGas,
gasPrice,
gasToken,
refundReceiver,
packedSig,
overrides
);
}
module.exports = {
signAndExecuteSafeTransaction,
};
File diff suppressed because it is too large Load Diff