a3ba8b7fca
- Update RPC endpoint from llamarpc to polygon-rpc.com for better reliability - Improve .env file loading to check both local and parent directory - Update README example to use proper condition_id format (0x...) instead of numeric - Clarify that condition_id should be hex format in documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
140 lines
5.3 KiB
JavaScript
140 lines
5.3 KiB
JavaScript
/**
|
|
* 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 { existsSync } = require('fs');
|
|
const { signAndExecuteSafeTransaction } = require('./safe-helpers');
|
|
const { safeAbi } = require('./safeAbi');
|
|
|
|
// Load environment variables
|
|
const localEnvPath = resolve(__dirname, '.env');
|
|
const parentEnvPath = resolve(__dirname, '../.env');
|
|
const envPath = existsSync(localEnvPath) ? localEnvPath : parentEnvPath;
|
|
require('dotenv').config({ path: envPath })
|
|
|
|
// Connect to Polygon network
|
|
const provider = new ethers.providers.JsonRpcProvider("https://polygon-rpc.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);
|
|
}); |