Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.polynode.dev/llms.txt

Use this file to discover all available pages before exploring further.

PolyUSD (pUSD) is the collateral token for Polymarket’s V2 exchange system. It is backed 1:1 by USDC and replaces USDC.e as the exchange collateral. To trade on V2, you need PolyUSD. Token info: Name: Polymarket USD | Symbol: pUSD | Decimals: 6 | View on Polygonscan

How PolyUSD Works

According to Polymarket’s announcement, PolyUSD is backed 1:1 by USDC. Power users and API traders can wrap either USDC or USDC.e into PolyUSD. For most frontend users, the transition is seamless — the Polymarket UI handles wrapping automatically with a one-time approval. There are two wrapping paths:
PathContractInput TokenStatus
Collateral Onramp0x93070a847efef7f70739046a929d47a521f5b8eeUSDC.e (bridged)Active — working today
PermissionedRamp0xebc2459ec962869ca4c0bd1e06368272732bcb08Native USDC (Circle)Not active yet — deployed but zero transactions
Both paths produce the same PolyUSD token. The V2 exchange doesn’t care how you obtained your PolyUSD.
The USDC.e → Onramp path is what’s working right now. We’ve tested it on mainnet and verified the full flow. The native USDC path via the PermissionedRamp exists on-chain but has not processed any transactions yet. When Polymarket activates it, wrapping native USDC will also be supported.

Quick Reference

Address
PolyUSD Token0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb
Collateral Onramp (USDC.e → PolyUSD)0x93070a847efef7f70739046a929d47a521f5b8ee
Collateral Offramp (PolyUSD → USDC.e)0x2957922eb93258b93368531d39facca3b4dc5854
PermissionedRamp (native USDC, not yet active)0xebc2459ec962869ca4c0bd1e06368272732bcb08
USDC.e (bridged USDC)0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
Backing Vault0xC417fD8E9661c0d2120B64a04Bb3278C17E99DB1

Wrapping: USDC.e → PolyUSD (Active Path)

To get PolyUSD today, wrap your USDC.e through the Collateral Onramp contract.

Steps

  1. Approve the Onramp to spend your USDC.e
  2. Call wrap(underlyingToken, recipient, amount) on the Onramp
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("YOUR_RPC_URL");
const wallet = new ethers.Wallet("YOUR_PRIVATE_KEY", provider);

const USDC_E = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";
const ONRAMP = "0x93070a847efef7f70739046a929d47a521f5b8ee";

// 1. Approve Onramp for USDC.e
const usdc = new ethers.Contract(USDC_E, [
  "function approve(address,uint256) returns (bool)",
  "function balanceOf(address) view returns (uint256)"
], wallet);

await (await usdc.approve(ONRAMP, ethers.MaxUint256)).wait();

// 2. Wrap USDC.e → PolyUSD
const amount = await usdc.balanceOf(wallet.address);
const wrapData = new ethers.Interface(["function wrap(address,address,uint256)"])
  .encodeFunctionData("wrap", [USDC_E, wallet.address, amount]);

const tx = await wallet.sendTransaction({ to: ONRAMP, data: wrapData, gasLimit: 300000 });
await tx.wait();

console.log("Wrapped", (Number(amount) / 1e6).toFixed(2), "USDC.e → PolyUSD");

Using the polynode SDK

The SDK handles wrapping automatically:
// Wrap 1 USDC.e → PolyUSD
let tx_hash = trader.wrap_to_polyusd(1_000_000).await?;

// Check balance
let balance = trader.get_polyusd_balance().await?;

Wrap Function Signature

function wrap(address underlyingToken, address recipient, uint256 amount)
  • underlyingToken — the USDC.e contract address (0x2791Bca1...)
  • recipient — who receives the PolyUSD (usually yourself)
  • amount — raw amount in 6-decimal units (1 USDC = 1,000,000)

Unwrapping: PolyUSD → USDC.e

Unwrapping is designed to be permissionless — any EOA can call CollateralOfframp.unwrap(asset, to, amount) and burn their pUSD in exchange for USDC.e. The function signature mirrors wrap:
function unwrap(address underlyingToken, address recipient, uint256 amount)
Unwrap is live today. The Collateral Offramp holds WRAPPER_ROLE on the pUSD token and processes user unwraps directly. Verified 2026-04-21 via tx 0x154a906c5790e272cb209146e6716ff255d92791519f642f5c43a4997a47e030 (0.1 pUSD → 0.1 USDC.e from a Safe wallet via the Polymarket relayer).Alternative paths that also still work:
  • Polymarket’s withdraw UI at bridge.polymarket.com/withdraw — routes through a Uniswap v3 pool or off-chain witness flow.
  • Settlement through the V2 exchangeCTFCollateralAdapter unwraps internally during matchOrders.
  • Redeeming resolved positions — ConditionalTokens pays out USDC.e directly.

Steps

  1. Approve the Offramp to spend your pUSD
  2. Call unwrap(USDC.e, recipient, amount) on the Offramp
Both steps are EOA-callable. For Safe wallets, route both through the Polymarket relayer (gasless) — the polynode SDK does this automatically, see below. The SDK’s wrapToPolyUsd() / unwrapFromPolyUsd() automatically detect your wallet type. If you onboarded with ensureReady(privateKey) (Safe wallet, the default), both calls route through the Polymarket relayer and execute as gasless Safe transactions signed by your EOA. Amounts are raw 6-decimal integers (1_000_000 = $1).
// polynode-sdk >= 0.9.1
import { PolyNodeTrader } from 'polynode-sdk';

const trader = new PolyNodeTrader({
  polynodeKey: 'pn_live_...',
  exchangeVersion: 'v2',
  builderCredentials: { key: '...', secret: '...', passphrase: '...' },
});
await trader.ensureReady('0xYOUR_EOA_PRIVATE_KEY');

// Wrap 10 USDC.e → pUSD (gasless via Safe+relayer)
const wrapTx = await trader.wrapToPolyUsd(10_000_000n);

// Unwrap 5 pUSD → USDC.e (gasless via Safe+relayer)
const unwrapTx = await trader.unwrapFromPolyUsd(5_000_000n);
Which wallet type am I using? ensureReady(privateKey) defaults to POLY_GNOSIS_SAFE — your EOA controls a Safe at the derived funder address, and SDK methods route through the Polymarket relayer (gasless). If you pass { type: SignatureType.EOA } explicitly, the SDK signs directly from the EOA and you pay MATIC for gas. For Safe mode, builderCredentials is required because the SDK uses your Polymarket builder HMAC to authenticate relayer requests — mint one at polymarket.com/settings?tab=builder.
Placing an order right after a wrap? Call await trader.refreshBalanceAllowance() in between. The V2 CLOB caches its view of your balance + allowances per API key; for a few seconds after wrap, that view is stale. If you submit an order before the cache refreshes, the CLOB can return a cryptic "error parsing fee rate bps" instead of the real “balance not yet visible” message. A single refreshBalanceAllowance() call fixes it. ensureReady already does this for you on first setup — wrapToPolyUsd does not, because not every wrap is followed by an immediate order.
ContractAddressRole on pUSD (today)
Collateral Onramp (wrap)0x93070a847efef7f70739046a929d47a521f5b8eeWRAPPER_ROLE ✓
Collateral Offramp (unwrap)0x2957922eb93258b93368531d39facca3b4dc5854WRAPPER_ROLE ✓
PermissionedRamp (witness-signed)0xebc2459ec962869ca4c0bd1e06368272732bcb08WRAPPER_ROLE ✓
CtfCollateralAdapter (internal)0xAdA100Db00Ca00073811820692005400218FcE1fWRAPPER_ROLE ✓
NegRiskCtfCollateralAdapter (internal)0xadA2005600Dec949baf300f4C6120000bDB6eAabWRAPPER_ROLE ✓

How It Works Under the Hood

PolyUSD is backed 1:1 by collateral held in a vault contract (0xC417fD8E...). When you wrap:
  1. Your USDC.e transfers to the vault
  2. PolyUSD is minted to your wallet
When you unwrap:
  1. Your PolyUSD is burned
  2. USDC.e is released from the vault to your wallet
During V2 trade settlements, the exchange automatically handles PolyUSD ↔ USDC.e conversion through the CTFCollateralAdapter. The ConditionalTokens contract underneath still uses USDC.e for position splitting — PolyUSD is the user-facing layer that sits on top.
The vault currently holds USDC.e as the backing collateral. Polymarket has stated that PolyUSD is backed 1:1 by USDC. A PermissionedRamp contract exists for native USDC wrapping but is not yet active. When it activates, the vault may hold a mix of USDC.e and native USDC, but PolyUSD remains 1:1 redeemable regardless of the backing composition.

Tracking PolyUSD Events

polynode detects PolyUSD wrapping and unwrapping through the settlement stream. These appear as deposit events:
  • Wrap (deposit): direction: "deposit", from is the Onramp contract
  • Unwrap (withdrawal): direction: "withdrawal", to is the user wallet
Subscribe to deposits on the WebSocket to receive these events. Internal settlement wraps/unwraps (between exchange and adapter contracts) are filtered out automatically.

Key Facts

  • Decimals: 6 (same as USDC)
  • Backing: 1:1 by USDC (currently held as USDC.e in the vault)
  • Wrapping: USDC.e via Onramp (active) or native USDC via PermissionedRamp (not yet active)
  • Proxy: ERC-1967 upgradeable proxy
  • Chain: Polygon mainnet (chain ID 137)
  • Minimum wrap: No minimum (tested with amounts as low as $0.007)