Polycule
Decentralized copy-trading bot via Telegram.
Learn how to build Polymarket trading apps using Privy embedded wallets and Safe (Gnosis) smart accounts. Gasless trading, email login, builder attribution explained.
Building a prediction market trading app used to mean asking users to install MetaMask, write down seed phrases, and fund wallets with ETH for gas. Terrible UX. Users bounce.
Polymarket's Privy + Safe integration changes the game. Users sign up with their email, get an invisible embedded wallet, and trade gaslessly. No extensions. No seed phrases. Just prediction markets.
This tutorial walks through Polymarket's official example repo so you can build the same experience into your own apps.
Three pieces working together:
Privy → Web2-Style Auth
Users log in with email. Behind the scenes, Privy creates an EOA (externally owned account) that the user controls through their login session. No browser extension needed.
Safe → Smart Account
A Gnosis Safe proxy wallet is deployed for each user, controlled by their Privy EOA. This is where USDC.e and outcome tokens live. The Safe address is deterministic—same EOA always gets the same Safe.
Builder Relayer → Gasless
Polymarket's builder relayer sponsors gas for Safe deployments, token approvals, and order execution. Users never need ETH or MATIC. Your builder credentials get attribution for orders routed through your app.
User signs in with email
↓
[Privy Auth]
↓
EOA embedded wallet (controlled via login session)
↓
┌────────────────────────────────────────────────┐
│ First-Time Setup (one-time per user) │
├────────────────────────────────────────────────┤
│ 1. Derive Safe address from EOA │
│ 2. Deploy Safe via RelayClient (gasless) │
│ 3. Get User API Credentials from CLOB │
│ 4. Batch approve tokens (7 approvals) │
└────────────────────────────────────────────────┘
↓
Authenticated ClobClient
↓
Place orders (gasless, builder attribution)# Clone the example git clone https://github.com/Polymarket/privy-safe-builder-example cd privy-safe-builder-example # Install dependencies npm install # Create .env.local cat > .env.local << 'EOF' NEXT_PUBLIC_POLYGON_RPC_URL=https://polygon-rpc.com NEXT_PUBLIC_PRIVY_APP_ID=your_privy_app_id POLYMARKET_BUILDER_API_KEY=your_builder_api_key POLYMARKET_BUILDER_SECRET=your_builder_secret POLYMARKET_BUILDER_PASSPHRASE=your_builder_passphrase EOF # Run it npm run dev
Open localhost:3000 and you should see the demo app. Log in with email, and you're trading on Polymarket.
Your builder secret never touches the client. Instead, there's an API route that generates HMAC signatures server-side:
// app/api/polymarket/sign/route.ts
import { buildHmacSignature } from "@polymarket/builder-signing-sdk";
const BUILDER_CREDENTIALS = {
key: process.env.POLYMARKET_BUILDER_API_KEY!,
secret: process.env.POLYMARKET_BUILDER_SECRET!,
passphrase: process.env.POLYMARKET_BUILDER_PASSPHRASE!,
};
export async function POST(request: NextRequest) {
const { method, path, body } = await request.json();
const sigTimestamp = Date.now().toString();
const signature = buildHmacSignature(
BUILDER_CREDENTIALS.secret,
parseInt(sigTimestamp),
method,
path,
body
);
return NextResponse.json({
POLY_BUILDER_SIGNATURE: signature,
POLY_BUILDER_TIMESTAMP: sigTimestamp,
POLY_BUILDER_API_KEY: BUILDER_CREDENTIALS.key,
POLY_BUILDER_PASSPHRASE: BUILDER_CREDENTIALS.passphrase,
});
}The CLOB client and RelayClient both use this endpoint to sign requests. Your secret stays server-side where it belongs.
Each user's Safe address is deterministic—derived from their Privy EOA:
import { deriveSafe } from "@polymarket/builder-relayer-client/dist/builder/derive";
import { getContractConfig } from "@polymarket/builder-relayer-client/dist/config";
// Same EOA always gets same Safe address
const config = getContractConfig(137); // Polygon
const safeAddress = deriveSafe(eoaAddress, config.SafeContracts.SafeFactory);
// Check if already deployed
const deployed = await relayClient.getDeployed(safeAddress);
// Deploy if needed (gasless)
if (!deployed) {
const response = await relayClient.deploy();
const result = await response.wait();
console.log("Safe deployed at:", result.proxyAddress);
}The Safe is where all the action happens. USDC.e goes in, outcome tokens come out. The Privy EOA is just the controller.
Before trading, the Safe needs to approve multiple contracts. Polymarket has both regular markets and "negative risk" markets, each with their own contracts:
USDC.e (ERC-20) approvals:
Outcome tokens (ERC-1155) approvals:
// Batch all 7 approvals in one transaction const approvalTxs = createAllApprovalTxs(); const response = await relayClient.execute( approvalTxs, "Set all token approvals for trading" ); await response.wait(); // Done. User signed once, all approvals set.
Users need their own API credentials to place orders. Create a temporary CLOB client to derive or create them:
import { ClobClient } from "@polymarket/clob-client";
// Temporary client (no creds yet)
const tempClient = new ClobClient(
"https://clob.polymarket.com",
137,
ethersSigner // From Privy
);
// Try to derive existing creds (returning user)
let creds = await tempClient.deriveApiKey().catch(() => null);
// If that fails, create new ones (new user)
if (!creds?.key) {
creds = await tempClient.createApiKey();
}
// Store for session (NOT localStorage in production!)
// creds = { key, secret, passphrase }Heads up: The demo stores creds in localStorage for simplicity. In production, use httpOnly cookies or server-side sessions. XSS vulnerabilities could expose these credentials.
With everything set up, create the authenticated CLOB client and start trading:
import { ClobClient } from "@polymarket/clob-client";
import { BuilderConfig } from "@polymarket/builder-signing-sdk";
const builderConfig = new BuilderConfig({
remoteBuilderConfig: { url: "/api/polymarket/sign" },
});
const clobClient = new ClobClient(
"https://clob.polymarket.com",
137,
signer,
userApiCredentials, // { key, secret, passphrase }
2, // signatureType = EOA + Safe proxy
safeAddress, // The Safe that holds funds
undefined,
false,
builderConfig // For builder attribution
);
// Create and submit an order
const order = {
tokenID: "0x...", // Outcome token
price: 0.65, // 65 cents
size: 10, // 10 shares
side: "BUY",
feeRateBps: 0,
expiration: 0, // Good-til-cancel
taker: "0x0000000000000000000000000000000000000000",
};
const response = await clobClient.createAndPostOrder(
order,
{ negRisk: false },
OrderType.GTC
);
console.log("Order placed:", response.orderID);All of this is coordinated by the useTradingSession hook. It handles the entire lifecycle:
const {
tradingSession,
currentStep, // "deploying_safe" | "setting_approvals" | etc.
initializeTradingSession,
endTradingSession,
relayClient,
isTradingSessionComplete,
} = useTradingSession();
// When user logs in
useEffect(() => {
if (isLoggedIn && !tradingSession) {
initializeTradingSession();
}
}, [isLoggedIn]);polymarket-privy-safe/
├── app/
│ ├── api/polymarket/sign/route.ts # Remote signing endpoint
│ └── page.tsx # Main UI
├── hooks/
│ ├── useTradingSession.ts # Orchestrates everything
│ ├── useRelayClient.ts # Safe deployment & approvals
│ ├── useSafeDeployment.ts # Safe derivation & deploy
│ ├── useUserApiCredentials.ts # CLOB credential management
│ ├── useTokenApprovals.ts # Token approval logic
│ ├── useClobClient.ts # Authenticated CLOB client
│ └── useClobOrder.ts # Order placement
├── providers/
│ ├── WalletProvider.tsx # Privy setup
│ └── TradingProvider.tsx # Trading context
└── utils/
├── session.ts # Session persistence
└── approvals.ts # Approval transaction buildersStart by reading useTradingSession.ts—it shows how everything connects.
Safe Deployment Can Take 30+ Seconds
Show a loading state. Users are patient if you tell them what's happening.
USDC.e Balance Check
Users need USDC.e in their Safe (not their EOA) to trade. Build a clear funding flow.
Signature Type = 2
When initializing ClobClient, signature type 2 means "EOA associated with a Safe proxy." This is critical for order signing.
Session Persistence
The demo uses localStorage. For production, implement proper session management—returning users shouldn't have to wait for Safe deployment again.
Builder Credentials Exposure
The demo's /api/polymarket/sign returns API key and passphrase to the client. For production, implement a proxy pattern where all CLOB requests go through your server.
"Privy authentication failed"
Check your NEXT_PUBLIC_PRIVY_APP_ID. Make sure your domain is allowlisted in the Privy dashboard.
"Safe deployment failed"
Verify builder credentials. Check that the remote signing endpoint is working (curl -X POST localhost:3000/api/polymarket/sign).
Orders not appearing
Wait a few seconds for CLOB sync. Make sure the Safe has USDC.e. Check that approvals were set correctly.
The Privy + Safe stack removes all the crypto friction. Users don't need to know what a Safe is or that they have an EOA. They sign in with email and trade.
Clone the repo, get your builder credentials, and start building. The example code covers the hard parts—wallet provisioning, Safe deployment, batch approvals, gasless execution. You just need to add your product logic.
Decentralized copy-trading bot via Telegram.
AI Agent interface and MCP server for prediction markets.
No-code playground for building & sharing AI agents
Sophisticated automated trading bot for position mirroring.
Real-time Polymarket Data Access
The most user-friendly Polymarket copy trading app. Simple, powerful, and built for everyone — from first-time bettors to seasoned traders
Data on all Polymarket traders, markets, positions, and trades
Track and analyze whale activity in prediction markets.