From 7a4b65b22d830f0c3ca77f88880f8d8c351e9d2d Mon Sep 17 00:00:00 2001 From: "sam.see" Date: Wed, 6 May 2026 11:53:47 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(polymarket):=20v0.6.2=20=E2=80=94=20ne?= =?UTF-8?q?g=5Frisk=20redemption=20support=20for=20proxy/deposit=20wallets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/polymarket-plugin/CHANGELOG.md | 9 +- skills/polymarket-plugin/Cargo.toml | 2 +- .../polymarket-plugin/src/commands/redeem.rs | 78 +++++++++++---- skills/polymarket-plugin/src/onchainos.rs | 94 +++++++++++++++++++ 4 files changed, 161 insertions(+), 22 deletions(-) diff --git a/skills/polymarket-plugin/CHANGELOG.md b/skills/polymarket-plugin/CHANGELOG.md index caf433f33..a6fdb166b 100644 --- a/skills/polymarket-plugin/CHANGELOG.md +++ b/skills/polymarket-plugin/CHANGELOG.md @@ -1,6 +1,13 @@ # Polymarket Plugin Changelog -### v0.6.0 (2026-05-05) — Deposit wallet support (POLY_1271 / new user flow) +### v0.6.2 (2026-05-06) — NegRisk redemption support for proxy/deposit wallets + +- **feat**: Enabled `neg_risk` (multi-outcome) market redemptions for **POLY_PROXY** and **DEPOSIT_WALLET** modes. +- **fix**: Implemented `negrisk_redeem_via_proxy` using PROXY_FACTORY routing. +- **fix**: Implemented `negrisk_redeem_via_deposit_wallet` using gasless relayer WALLET batches. +- **fix**: Updated `redeem` command to automatically detect and route NegRisk tokens held in EOA, proxy, or deposit wallets. + +### v0.6.1 (2026-05-05) - **feat**: New `TradingMode::DepositWallet` — ERC-1967 proxy per user, deployed by `DEPOSIT_WALLET_FACTORY`. Fully gasless (relayer-paid). `maker = signer = deposit_wallet_address`, `signature_type = 3` (POLY_1271 / ERC-1271). - **feat**: `setup-deposit-wallet` command — 6-step onboarding: deploy via relayer WALLET-CREATE → sign 5-target approval batch (pUSD + CTF ERC-1155) → sync CLOB balance-allowance `signature_type=3` → save mode. diff --git a/skills/polymarket-plugin/Cargo.toml b/skills/polymarket-plugin/Cargo.toml index 8263124d7..48d957fca 100644 --- a/skills/polymarket-plugin/Cargo.toml +++ b/skills/polymarket-plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "polymarket-plugin" -version = "0.6.1" +version = "0.6.2" edition = "2021" [lib] diff --git a/skills/polymarket-plugin/src/commands/redeem.rs b/skills/polymarket-plugin/src/commands/redeem.rs index a602b2649..8169dd533 100644 --- a/skills/polymarket-plugin/src/commands/redeem.rs +++ b/skills/polymarket-plugin/src/commands/redeem.rs @@ -8,6 +8,7 @@ use crate::onchainos::{ ctf_redeem_positions, ctf_redeem_via_proxy, decimal_str_to_hex64, get_ctf_balance, get_ctf_balance_hex, ctf_get_collection_id_hex, ctf_get_position_id_hex, get_existing_proxy, get_pol_balance, get_wallet_address, negrisk_redeem_positions, + negrisk_redeem_via_deposit_wallet, negrisk_redeem_via_proxy, wait_for_tx_receipt_labeled, }; @@ -231,21 +232,17 @@ async fn redeem_one( }); if neg_risk { - // NegRisk markets: call NegRiskAdapter.redeemPositions(conditionId, [yes_bal, no_bal]). - // Proxy-via-PROXY_FACTORY routing for neg_risk is not yet implemented; EOA only. - if r.proxy && !r.eoa { - return Err(anyhow!( - "Neg_risk redeem from proxy wallet is not yet supported by this plugin. \ - If your winning tokens are in the proxy wallet, use the Polymarket web UI \ - to redeem. EOA redeem via NegRiskAdapter is fully supported." - )); - } - // Query on-chain ERC-1155 balances for each outcome token. // Propagate RPC errors (don't unwrap_or(0)) — silently treating an RPC failure as // "no balance" would tell users their winning tokens don't exist when really the // node is just unavailable. - let wallet = if r.proxy && proxy_addr.is_some() { proxy_addr.unwrap() } else { eoa_addr }; + let wallet = if r.proxy && proxy_addr.is_some() { + proxy_addr.unwrap() + } else if r.deposit_wallet && deposit_wallet_addr.is_some() { + deposit_wallet_addr.unwrap() + } else { + eoa_addr + }; let mut amounts: Vec = Vec::with_capacity(token_ids.len()); for tid in token_ids { let bal = get_ctf_balance(wallet, tid).await @@ -275,22 +272,63 @@ async fn redeem_one( let total_shares: u128 = amounts.iter().sum(); eprintln!( - "[polymarket] NegRisk redeem: {} total shares across {} outcomes — submitting NegRiskAdapter.redeemPositions...", + "[polymarket] NegRisk redeem: {} total shares across {} outcomes...", total_shares, amounts.len() ); - let tx = negrisk_redeem_positions(condition_id, &amounts, eoa_addr).await?; - eprintln!( - "[polymarket] NegRisk redeem tx {} — waiting up to {}s for on-chain confirmation...", - tx, REDEEM_WAIT_SECS - ); - wait_for_tx_receipt_labeled(&tx, REDEEM_WAIT_SECS, "NegRisk redeem").await?; - out["eoa_tx"] = serde_json::Value::String(tx); + + if r.eoa { + eprintln!( + "[polymarket] EOA holds winning tokens — submitting NegRiskAdapter.redeemPositions..." + ); + let tx = negrisk_redeem_positions(condition_id, &amounts, eoa_addr).await?; + eprintln!( + "[polymarket] NegRisk EOA redeem tx {} — waiting up to {}s for on-chain confirmation...", + tx, REDEEM_WAIT_SECS + ); + wait_for_tx_receipt_labeled(&tx, REDEEM_WAIT_SECS, "NegRisk EOA redeem").await?; + out["eoa_tx"] = serde_json::Value::String(tx); + } + + if r.proxy { + eprintln!( + "[polymarket] Proxy holds winning tokens — submitting NegRiskAdapter.redeemPositions via PROXY_FACTORY..." + ); + let tx = negrisk_redeem_via_proxy(condition_id, &amounts).await?; + eprintln!( + "[polymarket] NegRisk proxy redeem tx {} — waiting up to {}s for on-chain confirmation...", + tx, REDEEM_WAIT_SECS + ); + wait_for_tx_receipt_labeled(&tx, REDEEM_WAIT_SECS, "NegRisk proxy redeem").await?; + out["proxy_tx"] = serde_json::Value::String(tx); + } + + if r.deposit_wallet { + let dw = deposit_wallet_addr.unwrap(); + eprintln!( + "[polymarket] Deposit wallet holds winning tokens — submitting NegRiskAdapter.redeemPositions via relayer WALLET batch..." + ); + // Fetch builder credentials for relayer auth. + let clob_creds = crate::auth::ensure_credentials(client, eoa_addr).await + .map_err(|e| anyhow::anyhow!("Could not load CLOB credentials for deposit wallet neg_risk redeem: {}", e))?; + let builder = crate::api::get_builder_api_key(client, &clob_creds, eoa_addr).await + .map_err(|e| anyhow::anyhow!("Could not derive builder credentials for relayer: {}", e))?; + let tx = negrisk_redeem_via_deposit_wallet( + condition_id, &amounts, dw, eoa_addr, &builder, + ).await?; + eprintln!( + "[polymarket] NegRisk deposit wallet redeem tx {} — waiting up to {}s for confirmation...", + tx, REDEEM_WAIT_SECS + ); + wait_for_tx_receipt_labeled(&tx, REDEEM_WAIT_SECS, "NegRisk deposit wallet redeem").await?; + out["deposit_wallet_tx"] = serde_json::Value::String(tx); + } + out["amounts"] = serde_json::Value::Array( amounts.iter().map(|a| serde_json::Value::String(a.to_string())).collect() ); out["note"] = serde_json::Value::String( - "NegRiskAdapter.redeemPositions confirmed. USDC.e transferred to EOA.".into(), + "NegRiskAdapter.redeemPositions confirmed. Collateral (USDC.e) transferred to respective wallet(s).".into(), ); } else { // Standard binary market: call CTF.redeemPositions. diff --git a/skills/polymarket-plugin/src/onchainos.rs b/skills/polymarket-plugin/src/onchainos.rs index 653b10008..caef50d57 100644 --- a/skills/polymarket-plugin/src/onchainos.rs +++ b/skills/polymarket-plugin/src/onchainos.rs @@ -1168,6 +1168,100 @@ pub async fn negrisk_redeem_positions( extract_tx_hash(&result) } +/// ABI-encode and submit NegRiskAdapter.redeemPositions via the PROXY_FACTORY. +/// +/// Used when winning outcome tokens are held by the proxy wallet (POLY_PROXY mode). +pub async fn negrisk_redeem_via_proxy( + condition_id: &str, + amounts: &[u128], +) -> Result { + use crate::config::Contracts; + use sha3::{Digest, Keccak256}; + + let inner_calldata = build_negrisk_redeem_calldata(condition_id, amounts); + let inner_bytes = hex::decode(inner_calldata.trim_start_matches("0x")).expect("negrisk redeem calldata"); + let inner_len = inner_bytes.len(); + let pad_len = (32 - inner_len % 32) % 32; + let inner_padded = format!("{}{}", inner_calldata.trim_start_matches("0x"), "00".repeat(pad_len)); + + let outer_selector = Keccak256::digest(b"proxy((uint8,address,uint256,bytes)[])"); + let outer_selector_hex = hex::encode(&outer_selector[..4]); + let target_padded = pad_address(Contracts::NEG_RISK_ADAPTER); + let data_len_padded = format!("{:064x}", inner_len); + + let calldata = format!( + "0x{}\ + {}\ + {}\ + {}\ + {}\ + {}\ + {}\ + {}\ + {}\ + {}", + outer_selector_hex, + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000020", + "0000000000000000000000000000000000000000000000000000000000000001", + target_padded, + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000080", + data_len_padded, + inner_padded, + ); + + let result = wallet_contract_call(Contracts::PROXY_FACTORY, &calldata).await?; + extract_tx_hash(&result) +} + +/// ABI-encode and submit NegRiskAdapter.redeemPositions via the deposit wallet relayer WALLET batch. +/// +/// Used when winning outcome tokens are held by the deposit wallet (DEPOSIT_WALLET mode). +pub async fn negrisk_redeem_via_deposit_wallet( + condition_id: &str, + amounts: &[u128], + deposit_wallet: &str, + eoa_addr: &str, + builder: &crate::auth::BuilderCredentials, +) -> Result { + use crate::config::Contracts; + use crate::signing::{BatchParams, WalletCall, sign_batch_via_onchainos}; + use crate::api::{get_wallet_nonce, relayer_wallet_batch}; + + let calldata = build_negrisk_redeem_calldata(condition_id, amounts); + let calls = vec![WalletCall { + target: Contracts::NEG_RISK_ADAPTER.to_string(), + value: 0, + data: calldata, + }]; + + let client = reqwest::Client::new(); + let nonce = get_wallet_nonce(&client, eoa_addr).await + .map_err(|e| anyhow::anyhow!("Could not fetch wallet nonce for neg_risk redeem batch: {}", e))?; + let deadline = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + 300; + + let calls_json: Vec = calls.iter().map(|c| serde_json::json!({ + "target": c.target, + "value": c.value.to_string(), + "data": c.data, + })).collect(); + + let batch_params = BatchParams { + wallet: deposit_wallet.to_string(), + nonce, + deadline, + calls, + }; + let batch_sig = sign_batch_via_onchainos(&batch_params).await + .map_err(|e| anyhow::anyhow!("Batch signing for deposit wallet neg_risk redeem failed: {}", e))?; + + relayer_wallet_batch(&client, eoa_addr, deposit_wallet, nonce, deadline, calls_json, &batch_sig, builder).await +} /// Get native POL balance for an address (eth_getBalance). Returns human-readable f64 (POL). pub async fn get_pol_balance(addr: &str) -> Result { From afff2e9baa3a4bd952b674b8a15c4209d7ef0f99 Mon Sep 17 00:00:00 2001 From: "sam.see" Date: Thu, 7 May 2026 11:21:48 +0800 Subject: [PATCH 2/2] fix(polymarket): bump all version refs to 0.6.2 (Cargo.lock, plugin.yaml, plugin.json, SKILL.md) --- .../polymarket-plugin/.claude-plugin/plugin.json | 2 +- skills/polymarket-plugin/Cargo.lock | 2 +- skills/polymarket-plugin/SKILL.md | 14 +++++++------- skills/polymarket-plugin/plugin.yaml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/skills/polymarket-plugin/.claude-plugin/plugin.json b/skills/polymarket-plugin/.claude-plugin/plugin.json index 02b573859..d00b7cc75 100644 --- a/skills/polymarket-plugin/.claude-plugin/plugin.json +++ b/skills/polymarket-plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "polymarket-plugin", "description": "Trade prediction markets on Polymarket \u2014 buy and sell YES/NO outcome tokens on Polygon", - "version": "0.6.1", + "version": "0.6.2", "author": { "name": "skylavis-sky", "github": "skylavis-sky" diff --git a/skills/polymarket-plugin/Cargo.lock b/skills/polymarket-plugin/Cargo.lock index b3d935649..4fd3d9ab7 100644 --- a/skills/polymarket-plugin/Cargo.lock +++ b/skills/polymarket-plugin/Cargo.lock @@ -1105,7 +1105,7 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polymarket-plugin" -version = "0.6.1" +version = "0.6.2" dependencies = [ "anyhow", "base64", diff --git a/skills/polymarket-plugin/SKILL.md b/skills/polymarket-plugin/SKILL.md index e978c9cc1..4c851e16a 100644 --- a/skills/polymarket-plugin/SKILL.md +++ b/skills/polymarket-plugin/SKILL.md @@ -1,7 +1,7 @@ --- name: polymarket-plugin description: "Trade prediction markets on Polymarket - buy outcome tokens (YES/NO and categorical markets), check positions, list markets, manage orders, redeem winning tokens, and deposit funds on Polygon. Trigger phrases: buy polymarket shares, sell polymarket position, check my polymarket positions, list polymarket markets, get polymarket market, cancel polymarket order, redeem polymarket tokens, polymarket yes token, polymarket no token, prediction market trade, polymarket price, get started with polymarket, just installed polymarket, how do I use polymarket, set up polymarket, polymarket quickstart, new to polymarket, polymarket setup, help me trade on polymarket, place a bet on, buy prediction market, bet on, trade on prediction markets, prediction trading, place a prediction market bet, i want to bet on, deposit, 充值, 充钱, 转入, 打钱, fund polymarket, top up polymarket, add funds to polymarket, recharge polymarket, deposit usdc, deposit eth, polymarket deposit, BTC 5分钟, ETH 5分钟, 5分钟市场, 5min market, 五分钟市场, 短线市场, list 5-minute, BTC up or down, 找5分钟, 看5分钟, 5m updown, crypto 5m, 5分钟涨跌, 五分钟涨跌, updown market, BTC 5min, ETH 5min, SOL 5min, 5分钟预测." -version: "0.6.1" +version: "0.6.2" author: "skylavis-sky" tags: - prediction-market @@ -25,7 +25,7 @@ tags: # Check for skill updates (1-hour cache) UPDATE_CACHE="$HOME/.plugin-store/update-cache/polymarket-plugin" CACHE_MAX=3600 -LOCAL_VER="0.6.1" +LOCAL_VER="0.6.2" DO_CHECK=true if [ -f "$UPDATE_CACHE" ]; then @@ -137,12 +137,12 @@ mkdir -p ~/.local/bin # Download binary + checksums to a sandbox, verify SHA256 before installing. BIN_TMP=$(mktemp -d) -RELEASE_BASE="https://github.com/mig-pre/plugin-store/releases/download/plugins/polymarket-plugin@0.6.1" +RELEASE_BASE="https://github.com/mig-pre/plugin-store/releases/download/plugins/polymarket-plugin@0.6.2" curl -fsSL "${RELEASE_BASE}/polymarket-plugin-${TARGET}${EXT}" -o "$BIN_TMP/polymarket-plugin${EXT}" || { echo "ERROR: failed to download polymarket-plugin-${TARGET}${EXT}" >&2 rm -rf "$BIN_TMP"; exit 1; } curl -fsSL "${RELEASE_BASE}/checksums.txt" -o "$BIN_TMP/checksums.txt" || { - echo "ERROR: failed to download checksums.txt for polymarket-plugin@0.6.1" >&2 + echo "ERROR: failed to download checksums.txt for polymarket-plugin@0.6.2" >&2 rm -rf "$BIN_TMP"; exit 1; } EXPECTED=$(awk -v b="polymarket-plugin-${TARGET}${EXT}" '$2 == b {print $1; exit}' "$BIN_TMP/checksums.txt") @@ -166,7 +166,7 @@ ln -sf "$LAUNCHER" ~/.local/bin/polymarket-plugin # Register version mkdir -p "$HOME/.plugin-store/managed" -echo "0.6.1" > "$HOME/.plugin-store/managed/polymarket-plugin" +echo "0.6.2" > "$HOME/.plugin-store/managed/polymarket-plugin" ``` --- @@ -391,7 +391,7 @@ The first `buy` or `sell` automatically derives your Polymarket API credentials polymarket-plugin --version ``` -Expected: `polymarket-plugin 0.6.1`. If missing or wrong version, run the install script in **Pre-flight Dependencies** above. +Expected: `polymarket-plugin 0.6.2`. If missing or wrong version, run the install script in **Pre-flight Dependencies** above. ### Step 2 — Install `onchainos` CLI (required for buy/sell/cancel/redeem only) @@ -1565,4 +1565,4 @@ If a command exits with `ok: false` and no actionable suggestion, run the same c ## Changelog -See [CHANGELOG.md](CHANGELOG.md) for full version history. Current version: **0.6.1** (2026-05-05). +See [CHANGELOG.md](CHANGELOG.md) for full version history. Current version: **0.6.2** (2026-05-05). diff --git a/skills/polymarket-plugin/plugin.yaml b/skills/polymarket-plugin/plugin.yaml index 7e79f5f94..4d57d77a7 100644 --- a/skills/polymarket-plugin/plugin.yaml +++ b/skills/polymarket-plugin/plugin.yaml @@ -1,6 +1,6 @@ schema_version: 1 name: polymarket-plugin -version: "0.6.1" +version: "0.6.2" description: "Trade prediction markets on Polymarket — buy and sell YES/NO outcome tokens on Polygon" author: name: skylavis-sky