fix: serialize concurrent buys per market + always verify on-chain payout before redeem

executeBuy (race condition):
- Multiple WebSocket events for the same market can arrive concurrently.
  All calls saw no existing position and all proceeded to buy → 3x fills.
- Added _buyQueue (Map<conditionId, Promise>) that chains each buy for
  the same market after the previous one. Second call now sees the filled
  position and respects maxPositionSize.

redeemer (gas estimation error):
- Gamma API can return resolved=true before payoutDenominator is written
  on-chain. Calling redeemPositions when payoutDenominator==0 causes the
  contract to revert → UNPREDICTABLE_GAS_LIMIT from ethers.js.
- Now always verify on-chain payout after the API check. If on-chain
  payout not set yet, skip and retry next interval.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
direkturcrypto
2026-02-25 04:22:09 +07:00
co-authored by Claude Sonnet 4.6
parent 526fc7a057
commit 365dc4e164
2 changed files with 59 additions and 24 deletions
+14 -9
View File
@@ -140,17 +140,22 @@ export async function checkAndRedeemPositions() {
for (const position of positions) {
try {
// 1. Check via Gamma API
// 1. Quick check via Gamma API (low cost)
const resolution = await checkMarketResolution(position.conditionId);
const isResolved = resolution?.resolved;
const apiResolved = resolution?.resolved;
if (!isResolved) {
// 2. Fallback: on-chain check
const onChain = await checkOnChainPayout(position.conditionId);
if (!onChain.resolved) continue;
logger.info(`Market resolved on-chain: ${position.market}`);
} else {
logger.info(`Market resolved: ${position.market}`);
if (!apiResolved) continue; // Not resolved yet — check again next interval
logger.info(`Market resolved via API: ${position.market}`);
// 2. ALWAYS verify on-chain payout before calling redeemPositions.
// Gamma API can report "resolved" before payoutDenominator is written
// on-chain. Calling redeemPositions with payoutDenominator == 0 causes
// the contract to revert → gas estimation failure.
const onChain = await checkOnChainPayout(position.conditionId);
if (!onChain.resolved) {
logger.info(`On-chain payout not set yet for ${position.market} — will retry next interval`);
continue;
}
// 3. Simulate or execute real redeem