Skip to content

fix[polymarket-monitor]: resolve markets via CLOB condition lookup#4945

Open
md0x wants to merge 6 commits intomasterfrom
codex/polymarket-market-id-rest-fallback
Open

fix[polymarket-monitor]: resolve markets via CLOB condition lookup#4945
md0x wants to merge 6 commits intomasterfrom
codex/polymarket-market-id-rest-fallback

Conversation

@md0x
Copy link
Copy Markdown
Contributor

@md0x md0x commented Apr 8, 2026

Summary

This changes monitor-polymarket market resolution to stop relying on Gamma GraphQL identifier lookups.

New flow:

  • derive the standard Polymarket condition_id from proposal.requester + questionID
  • if that misses, treat keccak256(ancillaryData) as a possible neg-risk request id and resolve the canonical question id on-chain through NegRiskOperator.questionIds(...)
  • derive the neg-risk adapter condition_id and fetch the market from CLOB by condition_id
  • enrich the result with the public Gamma REST market payload by slug

Problem

On April 8, 2026, the notifier failed to verify a valid neg-risk Polymarket proposal because it used keccak256(ancillaryData) as a direct Gamma GraphQL lookup key.

That is brittle for neg-risk markets because the ancillary hash is the neg-risk request id, not always the canonical market questionID. As a result, the bot could fail on the metadata lookup path even when the market existed and was otherwise resolvable.

Concrete failing example:

  • market: Queretaro (1272207)
  • canonical questionID: 0x303df379b982e189dc6aea356cad409cd18b8bc634a95e318b5e7ef025ab4400
  • neg-risk request id: 0x6e0a8c466f66bc6f7d3f113d3dcf019035adf909fd6efcca0368c3bec245914b

Why this fix

This makes market discovery follow the on-chain/CLOB identity path instead of depending on:

  • ancillary text containing market_id
  • Gamma GraphQL accepting a specific identifier form

The notifier still returns the same market metadata shape after the lookup succeeds, because it uses the public Gamma REST payload only after CLOB has identified the market.

Validation

  • yarn workspace @uma/monitor-v2 build
  • yarn workspace @uma/monitor-v2 test test/PolymarketMonitor.ts --grep 'resolves markets through CLOB condition lookup and Gamma slug lookup'
  • historical replay on a Polygon fork:
    • fork URL: https://polygon-mainnet.g.alchemy.com/v2/DnSj5ge0vStbqvOYzEhSV
    • fork block: 85274397
    • frozen time: 2026-04-08T17:20:31Z
    • targeted proposal requester: 0x2F5e3684cb1F318ec51b00Edba38d79Ac2c0aA9d
    • targeted proposal hash: 0xad2a68d579ad978d0dea48a359f3058442cd0eda5a04900109cc40160d5672e0
    • GET https://clob.polymarket.com/markets/0x7a73edc351f3a81f146f24cb3fe8d69448b22b9225e9c4799272a8ceb1fe5801 -> miss on the standard requester condition_id
    • GET https://clob.polymarket.com/markets/0xf77c27919433746aa6ff6759e36e8455458359f41a1f0dc75e6993736120091e -> hit on the neg-risk-resolved condition_id
    • GET https://gamma-api.polymarket.com/markets/slug/mex-que-jua-2026-02-22-que -> returns the expected market metadata
    • no POST https://gamma-api.polymarket.com/query
    • resolved canonical questionID: 0x303df379b982e189dc6aea356cad409cd18b8bc634a95e318b5e7ef025ab4400

@md0x md0x changed the title [codex] monitor-polymarket: use Gamma REST lookup by market_id monitor-polymarket: use Gamma REST lookup by market_id Apr 8, 2026
@md0x md0x changed the title monitor-polymarket: use Gamma REST lookup by market_id [codex] monitor-polymarket: resolve markets via CLOB condition lookup Apr 8, 2026
Signed-off-by: Pablo Maldonado <pablo@umaproject.org>
@md0x md0x changed the title [codex] monitor-polymarket: resolve markets via CLOB condition lookup fix[polymarket-monitor]: resolve markets via CLOB condition lookup Apr 8, 2026
clobTokenIds: JSON.parse(market.clobTokenIds),
};
});
return [processMarket(await fetchGammaMarket(clobMarket))];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the function from returning every Gamma match for question_id, neg_risk_request_id, or game_id to returning exactly one market from the first CLOB hit. For sports proposals, can that drop sibling markets that share the same game_id and skip discrepancy checks that the previous path covered?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I think this sports path probably is not exercised in practice anymore because I don’t think this identifier/requester combination is still used, but you’re right that the change regressed the old behavior for sports. I’ve fixed it so we still do the canonical CLOB/neg-risk discovery, then expand sports proposals back out to all sibling markets from the Gamma event via public REST.

params.apiEndpoint = "https://clob.polymarket.com";
params.graphqlEndpoint = "https://gamma-api.polymarket.com/query";

const questionId = "0x6e0a8c466f66bc6f7d3f113d3dcf019035adf909fd6efcca0368c3bec245914b";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only covers the standard requester-derived condition id. The PR description says the real failure was the neg-risk fallback via questionIds() and nrAdapter(). Can we add a case where the standard CLOB lookup misses, the operator resolves the canonical question id, and the fallback condition id succeeds?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

};
};

const gammaApiBaseUrl = params.graphqlEndpoint.replace(/\/query\/?$/, "");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lookup path is now entirely public REST. Should initMonitoringParams() still hard-require POLYMARKET_API_KEY? Otherwise the monitor can still fail to start without a secret this change no longer uses.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, fixed

md0x added 2 commits April 9, 2026 09:33
Signed-off-by: Pablo Maldonado <pablo@umaproject.org>
Signed-off-by: Pablo Maldonado <pablo@umaproject.org>
@md0x md0x requested a review from Reinis-FRP April 9, 2026 11:20
@md0x md0x marked this pull request as ready for review April 9, 2026 11:20
@md0x md0x requested review from chrismaree and mrice32 as code owners April 9, 2026 11:20
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d566d1d61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

].filter((url): url is string => Boolean(url));

for (const url of candidateUrls) {
const { data } = await params.httpClient.get<PolymarketMarketResponse | PolymarketMarketResponse[]>(url);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Continue Gamma fallback URLs after a 404

The fetchGammaMarket loop builds slug, market_id, and id URLs as fallbacks, but an HTTP 404 from the first candidate throws immediately and prevents trying the remaining candidates. In practice, if market_slug is stale/missing while market_id is still valid, this path now reports the market as not found even though a later URL would succeed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 3c5e56ad. We now continue through the Gamma fallback URLs on 404, so a stale/missing slug still falls back to market_id/id. I also added a regression test that exercises the slug -> market_id path.

};

const findClobMarket = async (): Promise<ClobMarketResponse | null> => {
const conditionIds = [...getStandardConditionIds(questionID), ...(await getNegRiskConditionIds())];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make neg-risk operator lookup a true fallback

This line eagerly awaits getNegRiskConditionIds() before any CLOB lookup, so every proposal now performs on-chain neg-risk resolution calls even when the standard requester-based condition ID works. That is a regression from the intended “only if standard miss” flow and can materially slow monitoring or increase RPC pressure/rate-limit risk under load.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 3c5e56ad. Neg-risk resolution is now a true fallback again: we first try the standard requester-derived condition ids, and only call into the neg-risk operator path if those miss. I also tightened the happy-path test to assert we do not touch the provider when the standard lookup succeeds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants