Skip to main content

Overview

The Polymarket Relayer Client allows builders to route onchain transactions through Polymarket’s Polygon relayer infrastructure. This provides several key benefits:
  • Gasless Transactions: Polymarket pays for all gas fees on your behalf
  • Wallet Deployment: Deploy Safe Wallets explicitly, or use auto-deploying Proxy Wallets for Magic Link users
  • Token Approvals: Set allowances for trading tokens
  • CTF Operations: Execute Conditional Token Framework (CTF) operations including:
    • Splitting positions
    • Merging positions
    • Redeeming positions
    • Converting positions

Wallet Types

The Relayer Client supports two types of wallets:

Safe Wallets (RelayerTxType.SAFE)

  • Gnosis Safe-based proxy wallets
  • Requires explicit deployment using client.deploy() before first use
  • Default wallet type when not specified

Proxy Wallets (RelayerTxType.PROXY)

  • Custom Polymarket proxy wallets
  • Auto-deploys on first transaction (no deploy() call needed)
  • Used for Magic Link users who authenticate via email/Google on Polymarket.com

Comparison

FeatureSafe WalletsProxy Wallets
DeploymentExplicit deploy() requiredAuto-deployed on first transaction
Gas FeesPaid by PolymarketPaid by Polymarket
Transaction Execution
ERC20 Approvals
CTF Operations
All code examples in this guide use the generic Transaction type, which works for both Safe and Proxy wallets. Simply change the RelayerTxType when initializing your client.

Installation

npm install @polymarket/builder-relayer-client
# or
pnpm install @polymarket/builder-relayer-client

Quick Start

Relayer URL

The Polymarket relayer is publicly available at:
https://relayer-v2.polymarket.com/

Basic Setup

The relayer client is available for both TypeScript and Python:
import { ethers } from "ethers";
import { RelayClient, RelayerTxType } from "@polymarket/builder-relayer-client";
import { BuilderApiKeyCreds, BuilderConfig } from "@polymarket/builder-signing-sdk";

const relayerUrl = process.env.POLYMARKET_RELAYER_URL;
const chainId = 137; // Polygon mainnet

// Create wallet
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

// Configure builder credentials
const builderCreds: BuilderApiKeyCreds = {
    key: process.env.BUILDER_API_KEY!,
    secret: process.env.BUILDER_SECRET!,
    passphrase: process.env.BUILDER_PASS_PHRASE!
};

const builderConfig = new BuilderConfig({
    localBuilderCreds: builderCreds
});

// Initialize client
const client = new RelayClient(relayerUrl, chainId, wallet, builderConfig);
// or explicitly:
const safeClient = new RelayClient(relayerUrl, chainId, wallet, builderConfig, RelayerTxType.SAFE);

// For Proxy Wallets (Magic Link users)
const proxyClient = new RelayClient(relayerUrl, chainId, wallet, builderConfig, RelayerTxType.PROXY);

Remote Signing

For enhanced security, you can use remote signing to keep your builder credentials on a secure server:
import { BuilderConfig } from "@polymarket/builder-signing-sdk";

const builderConfig = new BuilderConfig({
    remoteBuilderConfig: {url: "http://localhost:3000/sign"}
});

const client = new RelayClient(relayerUrl, chainId, wallet, builderConfig);

Core Features

Deploying Safe Wallets

Deploy a Safe wallet for your user with a single call. Polymarket pays the gas fees:
const response = await client.deploy();
const result = await response.wait();

if (result) {
    console.log("Safe deployed successfully!");
    console.log("Transaction Hash:", result.transactionHash);
    console.log("Safe Address:", result.proxyAddress);
} else {
    console.log("Safe deployment failed");
}
Note: The deploy() method only works with Safe wallets. Proxy wallets deploy automatically.

Setting Token Approvals

Set token allowances to enable trading. This example approves USDC spending for the Conditional Token Framework:
import { ethers } from "ethers";
import { Interface } from "ethers/lib/utils";
import { Transaction } from "@polymarket/builder-relayer-client";

// Define ERC20 approval interface
const erc20Interface = new Interface([{
    "constant": false,
    "inputs": [
        {"name": "_spender", "type": "address"},
        {"name": "_value", "type": "uint256"}
    ],
    "name": "approve",
    "outputs": [{"name": "", "type": "bool"}],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
}]);

// Create approval transaction
function createApprovalTransaction(
    tokenAddress: string,
    spenderAddress: string
): Transaction {
    return {
        to: tokenAddress,
        data: erc20Interface.encodeFunctionData("approve", [
            spenderAddress,
            ethers.constants.MaxUint256
        ]),
        value: "0"
    };
}

// Execute the approval
const usdcAddress = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";
const ctfAddress = "0x4d97dcd97ec945f40cf65f87097ace5ea0476045";

const approvalTx = createApprovalTransaction(usdcAddress, ctfAddress);
const response = await client.execute(
    [approvalTx],
    "Approve USDC for CTF"
);

const result = await response.wait();
console.log("Approval completed:", result?.transactionHash);

Monitoring Transaction Status

The relayer client provides built-in transaction monitoring:
// Automatic waiting (recommended)
const response = await client.execute(transactions);
const result = await response.wait();

if (result) {
    console.log("Transaction confirmed:", result.transactionHash);
    console.log("Transaction state:", result.state);
} else {
    console.log("Transaction failed or timed out");
}

Transaction States

Transactions move through the following states:
  • STATE_NEW: Transaction received by relayer
  • STATE_EXECUTED: Transaction executed onchain
  • STATE_MINED: Transaction included in a block
  • STATE_CONFIRMED: Transaction confirmed (final state)
  • STATE_FAILED: Transaction failed (terminal state)
  • STATE_INVALID: Transaction rejected as invalid (terminal state)

CTF Operations

The relayer client enables you to execute Conditional Token Framework operations for your users:

Split Positions

Split collateral tokens into conditional tokens representing different outcomes:
import { Interface } from "ethers/lib/utils";
import { Transaction } from "@polymarket/builder-relayer-client";

const ctfInterface = new Interface([
    "function splitPosition(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] partition, uint amount)"
]);

const splitTx: Transaction = {
    to: ctfAddress,
    data: ctfInterface.encodeFunctionData("splitPosition", [
        collateralToken,
        parentCollectionId,
        conditionId,
        partition,
        amount
    ]),
    value: "0"
};

const response = await client.execute([splitTx], "Split position");
const result = await response.wait();

Merge Positions

Merge conditional tokens back into collateral:
const ctfInterface = new Interface([
    "function mergePositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] partition, uint amount)"
]);

const mergeTx: Transaction = {
    to: ctfAddress,
    data: ctfInterface.encodeFunctionData("mergePositions", [
        collateralToken,
        parentCollectionId,
        conditionId,
        partition,
        amount
    ]),
    value: "0"
};

const response = await client.execute([mergeTx], "Merge position");
const result = await response.wait();

Redeem Positions

Redeem winning conditional tokens for collateral after market resolution:
const ctfInterface = new Interface([
    "function redeemPositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] indexSets)"
]);

const redeemTx: Transaction = {
    to: ctfAddress,
    data: ctfInterface.encodeFunctionData("redeemPositions", [
        collateralToken,
        parentCollectionId,
        conditionId,
        indexSets
    ]),
    value: "0"
};

const response = await client.execute([redeemTx], "Redeem position");
const result = await response.wait();

Transaction Metadata

Use descriptive metadata to track transaction purposes:
Metadata is limited to 500 characters or less
await client.execute(
    transactions,
    "User deposit: 100 USDC for market ABC123"
);

TypeScript Types

The relayer client is fully typed. Key types include:
// Transaction type used in all examples (works for both Safe and Proxy wallets)
interface Transaction {
    to: string;
    data: string;
    value: string;
}

// Wallet type selector
enum RelayerTxType {
    SAFE = "SAFE",
    PROXY = "PROXY"
}

// Transaction states
enum RelayerTransactionState {
    STATE_NEW = "STATE_NEW",
    STATE_EXECUTED = "STATE_EXECUTED",
    STATE_MINED = "STATE_MINED",
    STATE_CONFIRMED = "STATE_CONFIRMED",
    STATE_FAILED = "STATE_FAILED",
    STATE_INVALID = "STATE_INVALID"
}

// Response from relayer
interface RelayerTransaction {
    transactionID: string;
    transactionHash: string;
    from: string;
    to: string;
    proxyAddress: string;
    data: string;
    state: string;
    type: string;
    metadata: string;
    createdAt: Date;
    updatedAt: Date;
}

Contract Addresses

Polygon Mainnet

  • USDC: 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
  • CTF (Conditional Token Framework): 0x4d97dcd97ec945f40cf65f87097ace5ea0476045
  • CTF Exchange: 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E
  • Neg Risk CTF Exchange: 0xC5d563A36AE78145C45a50134d48A1215220f80a
These addresses are commonly used for token approvals and CTF operations.

Resources

Full Integration Examples

Complete Next.js applications demonstrating builder integration with various wallet providers:

TypeScript

Python

Additional Resources

Support

If you encounter issues or have questions about using the relayer client, please contact [email protected].