If you’re porting code: pair this with the V1→V2 migration guide. If you’re hitting an “allowance” error on what should be a funded wallet, the py-clob-client-v2 Errors page covers the symptoms.

Last verified: May 25, 2026, against docs.polymarket.com/concepts/pusd and the April 28, 2026 changelog entry.


The 30-second version

pUSD is a wrapper token. Native USDC sits in a Polymarket-controlled smart contract as backing. The contract mints 1 pUSD onto Polygon for every 1 USDC it receives, and burns 1 pUSD whenever it’s asked to release 1 USDC back. The ratio is enforced by the contract — no algorithmic peg, no fractional reserve, no oracle. From a trader’s perspective it behaves identically to any USD stablecoin you’ve used before, with one constraint: it’s designed to function inside Polymarket and isn’t currently listed on external exchanges.

Polymarket switched to it because the prior collateral, USDC.e, was a bridged representation of Circle’s USDC that originated on Ethereum and was wrapped by a third-party bridge for use on Polygon. That introduces bridge risk. By minting its own wrapper of native USDC on Polygon, Polymarket simplifies the trust model, controls the on/off ramps, and aligns the settlement layer with where its compliance posture is headed (CFTC discussions, US regulated re-entry).

For UI users that’s the whole story. For bot operators, there are exactly two things you need to do: (1) wrap any USDC.e in your account to pUSD via the CollateralOnramp contract, and (2) sync the CLOB’s view of your balance with update_balance_allowance(AssetType.COLLATERAL). Everything else is the same.


The naming story: pmUSD → “Polymarket USD” → pUSD

If you’ve been reading coverage of the upgrade, you’ve seen the asset called three different things, and that’s confused people enough that it’s worth pinning down.

April 6, 2026 — the announcement. Polymarket disclosed the full CLOB V2 upgrade in a blog post and shared it with crypto press, including CoinDesk’s coverage of the “full exchange upgrade”. Polymarket’s own materials referred to the new stablecoin as “Polymarket USD” (the full name). Different outlets riffed: some wrote it as just “Polymarket USD,” some abbreviated to “pmUSD” (a natural guess at the ticker), some went straight to “pUSD” before that was confirmed.

April 28, 2026 — the launch. When the contract shipped, the ERC-20 symbol on-chain was “pUSD”. The official docs at docs.polymarket.com/concepts/pusd standardized on that. Polymarket’s contract repository and the V2 SDK all reference pUSD exclusively. “pmUSD” was never the ticker — it was a transitional name in coverage between announcement and launch.

So: if you’re reading a March or early-April article that calls it “pmUSD,” it’s the same token. The canonical name is Polymarket USD, the canonical ticker is pUSD, and “pmUSD” was a pre-launch abbreviation that didn’t survive contact with the actual smart contract.

What about POLY? Don’t confuse pUSD with the (still-unlaunched) POLY governance token Polymarket has hinted at. pUSD is the collateral / settlement token — non-tradable, non-speculative, USD-pegged. POLY (if/when it launches) is expected to be the governance token. Different things.


Why Polymarket built pUSD instead of just using USDC

Three reasons, in order of how much they actually mattered.

1. Bridge risk. USDC.e is the bridged form of Circle’s USDC on Polygon — minted by Polygon’s PoS bridge from a USDC deposit on Ethereum, redeemable through the same bridge. Native USDC also exists on Polygon (Circle issues it directly), but Polymarket’s historical liquidity sat in USDC.e for legacy reasons. A bridged token inherits the bridge’s failure modes: a bridge exploit or freeze freezes the asset. By minting its own wrapper that holds native USDC as backing, Polymarket replaces a bridge dependency with a direct asset-claim relationship.

2. Control of the on/off ramps. USDC.e’s behavior — pausing, censorship, blacklisting — was governed by entities Polymarket didn’t control. The CollateralOnramp and CollateralOfframp are Polymarket contracts. The platform can pause its own wrapper in an incident, can enforce its own policy (e.g., the OnlyUnpaused() revert path on the onramp), and can iterate on the mechanism without coordinating with a third party.

3. Settlement infrastructure for an institutional posture. Polymarket is pushing for CFTC-supervised US re-entry, and the platform stack matters to regulators reviewing the architecture. A settlement asset Polymarket controls is easier to describe and audit than a bridged token that flows through third-party infrastructure. The framing in Polymarket’s own docs — “the protocol settles all trading activity in native USDC, providing a more capital efficient, scalable, and institutionally aligned settlement standard” — matches that posture.

Worth saying clearly: this is not a partnership with Circle, and pUSD is not Circle-issued. Circle issues USDC. Polymarket issues pUSD, with USDC as the backing asset, via a permissionless wrap mechanism on Polygon. The relationship is “Polymarket holds Circle’s USDC as reserves and mints its own wrapper against it” — clean asset-backed, no contractual entanglement.


How it works under the hood

Two contracts: CollateralOnramp (USDC.e → pUSD) and CollateralOfframp (pUSD → USDC.e). Both are permissionless. The Onramp accepts USDC.e because that’s the legacy asset Polymarket users already had; you don’t have to start with native USDC.

The onramp’s wrap function — quoted from the official docs:

function wrap(address _asset, address _to, uint256 _amount) external
  • _asset — the asset being wrapped. Must be USDC.e (0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174).
  • _to — the recipient of the minted pUSD. Doesn’t have to be msg.sender.
  • _amount — amount in USDC.e base units (6 decimals).

Two requirements that catch first-time wrappers:

  1. You must approve the CollateralOnramp first — not the pUSD token. Approval is on the input asset (USDC.e), granting the Onramp permission to pull the amount you’re wrapping.
  2. The Onramp reverts with OnlyUnpaused() if the admin has paused USDC.e wrapping — a circuit-breaker Polymarket can flip in an incident. As of writing this hasn’t been triggered, but it’s worth knowing your wrap call can revert with that custom error.

unwrap on the CollateralOfframp is the mirror: approve the Offramp to spend your pUSD, then call unwrap(USDC.e, recipient, amount).

Behind the scenes, the wrapped USDC.e (or, on the deposit-from-other-chains path, native USDC) sits in a Polymarket-controlled vault as backing. The total pUSD supply equals the backing — enforced by the contract code, audited by Cantina and Quantstamp per Polymarket’s announcement.


What changed for normal users

Nothing visible. If you logged into polymarket.com any time after the cutover, the front end prompted you once to approve the wrap, called the Onramp on your behalf, converted your USDC.e balance to pUSD 1:1, and you went back to trading. Your dashboard still says “$X.” Open orders had been cancelled during the cutover anyway (a separate V2 thing); deposits from external chains now auto-wrap to pUSD on arrival.

The frontend even renamed the displayed asset to “USDC” in the UI in most places to avoid confusing users — pUSD is the technical name; “USDC” is what most users see. That’s a deliberate UX choice. The actual token in your wallet is pUSD; it just walks and talks like USDC.


What changed for API-only bots

Two concrete things, both small but easy to miss.

1. Wrap your USDC.e to pUSD

If your bot wallet never visited polymarket.com, its balance is still in USDC.e. You have to wrap it yourself. Here it is in TypeScript (from the official docs):

import {
  createWalletClient, createPublicClient, http,
  parseAbi, parseUnits,
} from "viem";
import { polygon } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const account      = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({ account, chain: polygon, transport: http() });
const publicClient = createPublicClient({ chain: polygon, transport: http() });

const ONRAMP = "0x93070a847efEf7F70739046A929D47a521F5B8ee" as const;
const USDCE  = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174" as const;

const amount = parseUnits("100", 6);  // 100 USDC.e → 100 pUSD

// 1. Approve the Onramp to spend USDC.e
const approve = await walletClient.writeContract({
  address: USDCE,
  abi: parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]),
  functionName: "approve",
  args: [ONRAMP, amount],
});
await publicClient.waitForTransactionReceipt({ hash: approve });

// 2. Wrap
const wrap = await walletClient.writeContract({
  address: ONRAMP,
  abi: parseAbi(["function wrap(address _asset, address _to, uint256 _amount)"]),
  functionName: "wrap",
  args: [USDCE, account.address, amount],
});
await publicClient.waitForTransactionReceipt({ hash: wrap });

In Python with web3.py the shape is identical — approve USDC.e to ONRAMP, then call wrap(USDC.e, recipient, amount).

You only need to do this once for the existing balance. If your bot receives deposits from external chains via Polymarket’s bridge (bridge.polymarket.com), those auto-wrap to pUSD on arrival.

2. Re-sync the CLOB’s cached balance/allowance

After the on-chain wrap (and after any approval changes), tell the CLOB to refresh its cached view:

from py_clob_client_v2 import BalanceAllowanceParams, AssetType
client.update_balance_allowance(
    BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)
)

Then verify with the read:

bal = client.get_balance_allowance(
    BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)
)
print(bal)  # {'balance': '100000000', 'allowance': '999...', ...}  6-decimal pUSD

The BalanceAllowanceParams(asset_type=AssetType.COLLATERAL) shape is unchanged from V1 — the same call worked against USDC.e before the cutover. The only difference is that “COLLATERAL” now resolves to pUSD instead of USDC.e under the hood. If balance reads as zero after a successful wrap, you forgot the update_balance_allowance sync.

Allowance approvals on the V2 Exchange

You also need to approve the V2 Exchange contracts to move your pUSD when settling matched orders. The required approvals (for EOA users who manage allowances themselves) are:

  • pUSD → CTF Exchange V2 (0xE111180000d2663C0091e4f400237545B87B996B)
  • pUSD → Neg-Risk CTF Exchange V2 (0xe2222d279d744050d28e00520010520000310F59)
  • Conditional tokens (0x4D97DCd97eC945f40cF65F87097ACe5EA0476045) → same two exchanges via setApprovalForAll(true)

These are one-time per wallet. Polymarket’s frontend handles them automatically; API-only users need to submit them via web3.py / viem. After any approval transaction, call update_balance_allowance again so the CLOB’s cache is fresh.


Risks and edge cases worth knowing about

  • pUSD is not listed on external exchanges. It’s settlement infrastructure, not a tradable asset. Don’t expect a Uniswap pool or CEX listing — and treat any third-party “pUSD” pool with suspicion until verified against the canonical contract address.
  • The admin can pause wrapping with OnlyUnpaused(). The Polymarket team has stated they would use this only in incident response, but if your bot expects to wrap in an emergency (e.g., to top up a margin call), build a retry / fall-back path.
  • Unwrap path needs liquidity in the Offramp. The Offramp holds the USDC.e it received from earlier wraps; unwrapping requires that USDC.e to be available. In normal operation this is fine — total wrapped equals total backing — but in extreme imbalance it could create a queue.
  • The bridge does the wrap for you on deposits. If you fund the bot wallet by bridging USDC from another chain via bridge.polymarket.com, the bridge contract calls wrap() on receipt — you receive pUSD directly. If you fund via a direct USDC.e transfer on Polygon, you do the wrap.
  • Don’t confuse the asset addresses. USDC.e is 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174. pUSD is a different contract (see Polymarket Contracts for the canonical address). Approving the wrong contract is a no-op that wastes gas.

Changelog

DateChange
May 25, 2026Initial publication. Verified against docs.polymarket.com/concepts/pusd, the Apr 28 changelog, and the v2 migration guide.

Official Resources

Coverage (announcement → launch)

AgentBets Guides

Where This Fits in the Agent Betting Stack

pUSD spans Layer 2 (Wallet) and Layer 3 (Trading) in the Agent Betting Stack. At Layer 2 it’s a token your agent wallet holds; at Layer 3 it’s the asset every order is denominated in. If your wallet (Layer 2) was set up before the cutover and the bot is API-only, this collateral switch is the most common “why is my bot suddenly broken” cause that isn’t a code issue — fix it once with a wrap and you’re back to trading.


This guide is maintained by AgentBets.ai. Found an error or a contract address change? Let us know on Twitter.

Not financial advice. Built for builders.