Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion skills/polymarket-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
9 changes: 8 additions & 1 deletion skills/polymarket-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion skills/polymarket-plugin/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion skills/polymarket-plugin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "polymarket-plugin"
version = "0.6.1"
version = "0.6.2"
edition = "2021"

[lib]
Expand Down
14 changes: 7 additions & 7 deletions skills/polymarket-plugin/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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"
```

---
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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).
2 changes: 1 addition & 1 deletion skills/polymarket-plugin/plugin.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
78 changes: 58 additions & 20 deletions skills/polymarket-plugin/src/commands/redeem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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<u128> = Vec::with_capacity(token_ids.len());
for tid in token_ids {
let bal = get_ctf_balance(wallet, tid).await
Expand Down Expand Up @@ -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.
Expand Down
94 changes: 94 additions & 0 deletions skills/polymarket-plugin/src/onchainos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
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<String> {
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<serde_json::Value> = 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<f64> {
Expand Down