Files
PolymarketDocumentation/docs/developers/builders/relayer-client.md
T
2026-02-14 12:59:26 +01:00

28 KiB

Documentation Index

Fetch the complete documentation index at: https://docs.polymarket.com/llms.txt Use this file to discover all available pages before exploring further.

Relayer Client

Use Polymarket's Polygon relayer to execute gasless transactions for your users

Overview

The Relayer Client routes onchain transactions through Polymarket's infrastructure, providing gasless transactions for your users. Builder authentication is required to access the relayer.

Polymarket pays all gas fees Deploy Safe or Proxy wallets Split, merge, and redeem positions

Builder API Credentials

Each builder receives API credentials from their Builder Profile:

Credential Description
key Your builder API key identifier
secret Secret key for signing requests
passphrase Additional authentication passphrase
**Security Notice**: Your Builder API keys must be kept secure. Never expose them in client-side code.

Installation

```bash TypeScript theme={null} npm install @polymarket/builder-relayer-client ```
pip install py-builder-relayer-client

Relayer Endpoint

All relayer requests are sent to Polymarket's relayer service on Polygon:

https://relayer-v2.polymarket.com/

Signing Methods

Remote signing keeps your credentials secure on a server you control.
**How it works:**

1. Client sends request details to your signing server
2. Your server generates the HMAC signature
3. Client attaches headers and sends to relayer

### Server Implementation

Your signing server receives request details and returns the authentication headers:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { 
    buildHmacSignature, 
    BuilderApiKeyCreds 
  } from "@polymarket/builder-signing-sdk";

  const BUILDER_CREDENTIALS: BuilderApiKeyCreds = {
    key: process.env.POLY_BUILDER_API_KEY!,
    secret: process.env.POLY_BUILDER_SECRET!,
    passphrase: process.env.POLY_BUILDER_PASSPHRASE!,
  };

  // POST /sign - receives { method, path, body } from the client SDK
  export async function handleSignRequest(request) {
    const { method, path, body } = await request.json();
    
    const timestamp = Date.now().toString();
    
    const signature = buildHmacSignature(
      BUILDER_CREDENTIALS.secret,
      parseInt(timestamp),
      method,
      path,
      body
    );

    return {
      POLY_BUILDER_SIGNATURE: signature,
      POLY_BUILDER_TIMESTAMP: timestamp,
      POLY_BUILDER_API_KEY: BUILDER_CREDENTIALS.key,
      POLY_BUILDER_PASSPHRASE: BUILDER_CREDENTIALS.passphrase,
    };
  }
  ```

  ```python Python theme={null}
  import os
  import time
  from py_builder_signing_sdk.signing.hmac import build_hmac_signature
  from py_builder_signing_sdk import BuilderApiKeyCreds

  BUILDER_CREDENTIALS = BuilderApiKeyCreds(
      key=os.environ["POLY_BUILDER_API_KEY"],
      secret=os.environ["POLY_BUILDER_SECRET"],
      passphrase=os.environ["POLY_BUILDER_PASSPHRASE"],
  )

  # POST /sign - receives { method, path, body } from the client SDK
  def handle_sign_request(method: str, path: str, body: str):
      timestamp = str(int(time.time()))
      
      signature = build_hmac_signature(
          BUILDER_CREDENTIALS.secret,
          timestamp,
          method,
          path,
          body
      )

      return {
          "POLY_BUILDER_SIGNATURE": signature,
          "POLY_BUILDER_TIMESTAMP": timestamp,
          "POLY_BUILDER_API_KEY": BUILDER_CREDENTIALS.key,
          "POLY_BUILDER_PASSPHRASE": BUILDER_CREDENTIALS.passphrase,
      }
  ```
</CodeGroup>

<Warning>
  Never commit credentials to version control. Use environment variables or a secrets manager.
</Warning>

### Client Configuration

Point your client to your signing server:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createWalletClient, http, Hex } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { polygon } from "viem/chains";
  import { RelayClient } from "@polymarket/builder-relayer-client";
  import { BuilderConfig } from "@polymarket/builder-signing-sdk";

  // Create wallet
  const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
  const wallet = createWalletClient({
    account,
    chain: polygon,
    transport: http(process.env.RPC_URL)
  });

  // Configure remote signing
  const builderConfig = new BuilderConfig({
    remoteBuilderConfig: { 
      url: "https://your-server.com/sign" 
    }
  });

  const RELAYER_URL = "https://relayer-v2.polymarket.com/";
  const CHAIN_ID = 137;

  const client = new RelayClient(
    RELAYER_URL,
    CHAIN_ID,
    wallet,
    builderConfig
  );
  ```

  ```python Python theme={null}
  import os
  from py_builder_relayer_client.client import RelayClient
  from py_builder_signing_sdk import BuilderConfig, RemoteBuilderConfig

  private_key = os.getenv("PRIVATE_KEY")

  # Configure remote signing
  builder_config = BuilderConfig(
      remote_builder_config=RemoteBuilderConfig(
          url="https://your-server.com/sign"
      )
  )

  client = RelayClient(
      "https://relayer-v2.polymarket.com",
      137,
      private_key,
      builder_config
  )
  ```
</CodeGroup>
Sign locally when your backend handles all transactions.
**How it works:**

1. Your system creates transactions on behalf of users
2. Your system uses Builder API credentials locally to add headers
3. Complete signed request is sent directly to the relayer

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createWalletClient, http, Hex } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { polygon } from "viem/chains";
  import { RelayClient } from "@polymarket/builder-relayer-client";
  import { BuilderConfig } from "@polymarket/builder-signing-sdk";

  // Create wallet
  const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
  const wallet = createWalletClient({
    account,
    chain: polygon,
    transport: http(process.env.RPC_URL)
  });

  // Configure local signing
  const builderConfig = new BuilderConfig({
    localBuilderCreds: {
      key: process.env.POLY_BUILDER_API_KEY!,
      secret: process.env.POLY_BUILDER_SECRET!,
      passphrase: process.env.POLY_BUILDER_PASSPHRASE!
    }
  });

  const RELAYER_URL = "https://relayer-v2.polymarket.com/";
  const CHAIN_ID = 137;

  const client = new RelayClient(
    RELAYER_URL,
    CHAIN_ID,
    wallet,
    builderConfig
  );
  ```

  ```python Python theme={null}
  import os
  from py_builder_relayer_client.client import RelayClient
  from py_builder_signing_sdk import BuilderConfig, BuilderApiKeyCreds

  private_key = os.getenv("PRIVATE_KEY")

  # Configure local signing
  builder_config = BuilderConfig(
      local_builder_creds=BuilderApiKeyCreds(
          key=os.getenv("POLY_BUILDER_API_KEY"),
          secret=os.getenv("POLY_BUILDER_SECRET"),
          passphrase=os.getenv("POLY_BUILDER_PASSPHRASE"),
      )
  )

  client = RelayClient(
      "https://relayer-v2.polymarket.com",
      137,
      private_key,
      builder_config
  )
  ```
</CodeGroup>

<Warning>
  Never commit credentials to version control. Use environment variables or a secrets manager.
</Warning>

Authentication Headers

The SDK automatically generates and attaches these headers to each request:

Header Description
POLY_BUILDER_API_KEY Your builder API key
POLY_BUILDER_TIMESTAMP Unix timestamp of signature creation
POLY_BUILDER_PASSPHRASE Your builder passphrase
POLY_BUILDER_SIGNATURE HMAC signature of the request
With **local signing**, the SDK constructs and attaches these headers automatically. With **remote signing**, your server must return these headers (see Server Implementation above), and the SDK attaches them to the request.

Wallet Types

Choose your wallet type before using the relayer:

Gnosis Safe-based proxy wallets that require explicit deployment before use.
* **Best for:** Most builder integrations
* **Deployment:** Call `client.deploy()` before first transaction
* **Gas fees:** Paid by Polymarket

<CodeGroup>
  ```typescript TypeScript theme={null}
  const client = new RelayClient(
    "https://relayer-v2.polymarket.com", 
    137,
    eoaSigner, 
    builderConfig, 
    RelayerTxType.SAFE  // Default
  );

  // Deploy before first use
  const response = await client.deploy();
  const result = await response.wait();
  console.log("Safe Address:", result?.proxyAddress);
  ```

  ```python Python theme={null}
  from py_builder_relayer_client.client import RelayClient, RelayerTxType

  client = RelayClient(
      "https://relayer-v2.polymarket.com",
      137,
      private_key,
      builder_config,
      RelayerTxType.SAFE  # Default
  )

  # Deploy before first use
  response = client.deploy()
  result = response.wait()
  print(f"Safe Address: {result.proxy_address}")
  ```
</CodeGroup>
Custom Polymarket proxy wallets that auto-deploy on first transaction.
* **Used for:** Magic Link users from Polymarket.com
* **Deployment:** Automatic on first transaction
* **Gas fees:** Paid by Polymarket

<CodeGroup>
  ```typescript TypeScript theme={null}
  const client = new RelayClient(
    "https://relayer-v2.polymarket.com", 
    137,
    eoaSigner, 
    builderConfig, 
    RelayerTxType.PROXY
  );

  // No deploy() needed - auto-deploys on first tx
  await client.execute([transaction], "First transaction");
  ```

  ```python Python theme={null}
  from py_builder_relayer_client.client import RelayClient, RelayerTxType

  client = RelayClient(
      "https://relayer-v2.polymarket.com",
      137,
      private_key,
      builder_config,
      RelayerTxType.PROXY
  )

  # No deploy() needed - auto-deploys on first tx
  client.execute([transaction], "First transaction")
  ```
</CodeGroup>
| Feature | Safe Wallets | Proxy Wallets | | ----------------- | :-----------------: | :---------------------: | | Deployment | Explicit `deploy()` | Auto-deploy on first tx | | Gas Fees | Polymarket pays | Polymarket pays | | ERC20 Approvals | | | | CTF Operations | | | | Send Transactions | | |

Usage

Deploy a Wallet

For Safe wallets, deploy before executing transactions:

```typescript TypeScript theme={null} 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); }


```python Python theme={null}
response = client.deploy()
result = response.wait()

if result:
    print("Safe deployed successfully!")
    print(f"Transaction Hash: {result.transaction_hash}")
    print(f"Safe Address: {result.proxy_address}")

Execute Transactions

The execute method sends transactions through the relayer. Pass an array of transactions to batch multiple operations in a single call.

```typescript TypeScript theme={null} interface Transaction { to: string; // Target contract or wallet address data: string; // Encoded function call (use "0x" for simple transfers) value: string; // Amount of MATIC to send (usually "0") }

const response = await client.execute(transactions, "Description"); const result = await response.wait();

if (result) { console.log("Transaction confirmed:", result.transactionHash); }


```python Python theme={null}
# Transaction dict format:
# { "to": str, "data": str, "value": str }

response = client.execute(transactions, "Description")
result = response.wait()

if result:
    print(f"Transaction confirmed: {result.transaction_hash}")

Transaction Examples

Transfer tokens to any address (e.g., withdrawals):
<CodeGroup>
  ```typescript TypeScript theme={null}
  import { encodeFunctionData, parseUnits } from "viem";

  const transferTx = {
    to: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", // USDCe
    data: encodeFunctionData({
      abi: [{
        name: "transfer",
        type: "function",
        inputs: [
          { name: "to", type: "address" },
          { name: "amount", type: "uint256" }
        ],
        outputs: [{ type: "bool" }]
      }],
      functionName: "transfer",
      args: [
        "0xRecipientAddressHere",
        parseUnits("100", 6) // 100 USDCe (6 decimals)
      ]
    }),
    value: "0"
  };

  const response = await client.execute([transferTx], "Transfer USDCe");
  await response.wait();
  ```

  ```python Python theme={null}
  from web3 import Web3

  USDC_E = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"

  transfer_tx = {
      "to": USDC_E,
      "data": Web3().eth.contract(
          address=USDC_E,
          abi=[{"name": "transfer", "type": "function", "inputs": [{"name": "to", "type": "address"}, {"name": "amount", "type": "uint256"}], "outputs": [{"type": "bool"}]}]
      ).encode_abi(abi_element_identifier="transfer", args=["0xRecipientAddressHere", 100 * 10**6]),
      "value": "0"
  }

  response = client.execute([transfer_tx], "Transfer USDCe")
  response.wait()
  ```
</CodeGroup>
Set token allowances to enable trading:
<CodeGroup>
  ```typescript TypeScript theme={null}
  import { encodeFunctionData, maxUint256 } from "viem";

  const approveTx = {
    to: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", // USDCe
    data: encodeFunctionData({
      abi: [{
        name: "approve",
        type: "function",
        inputs: [
          { name: "spender", type: "address" },
          { name: "amount", type: "uint256" }
        ],
        outputs: [{ type: "bool" }]
      }],
      functionName: "approve",
      args: [
        "0x4d97dcd97ec945f40cf65f87097ace5ea0476045", // CTF
        maxUint256
      ]
    }),
    value: "0"
  };

  const response = await client.execute([approveTx], "Approve USDCe for CTF");
  await response.wait();
  ```

  ```python Python theme={null}
  from web3 import Web3

  USDC_E = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
  CTF = "0x4d97dcd97ec945f40cf65f87097ace5ea0476045"
  MAX_UINT256 = 2**256 - 1

  approve_tx = {
      "to": USDC_E,
      "data": Web3().eth.contract(
          address=USDC_E,
          abi=[{"name": "approve", "type": "function", "inputs": [{"name": "spender", "type": "address"}, {"name": "amount", "type": "uint256"}], "outputs": [{"type": "bool"}]}]
      ).encode_abi(abi_element_identifier="approve", args=[CTF, MAX_UINT256]),
      "value": "0"
  }

  response = client.execute([approve_tx], "Approve USDCe for CTF")
  response.wait()
  ```
</CodeGroup>
Redeem winning conditional tokens after market resolution:
<CodeGroup>
  ```typescript TypeScript theme={null}
  import { encodeFunctionData } from "viem";

  const redeemTx = {
    to: ctfAddress,
    data: encodeFunctionData({
      abi: [{
        name: "redeemPositions",
        type: "function",
        inputs: [
          { name: "collateralToken", type: "address" },
          { name: "parentCollectionId", type: "bytes32" },
          { name: "conditionId", type: "bytes32" },
          { name: "indexSets", type: "uint256[]" }
        ],
        outputs: []
      }],
      functionName: "redeemPositions",
      args: [collateralToken, parentCollectionId, conditionId, indexSets]
    }),
    value: "0"
  };

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

  ```python Python theme={null}
  from web3 import Web3

  CTF = "0x4d97dcd97ec945f40cf65f87097ace5ea0476045"

  redeem_tx = {
      "to": CTF,
      "data": Web3().eth.contract(
          address=CTF,
          abi=[{"name": "redeemPositions", "type": "function", "inputs": [{"name": "collateralToken", "type": "address"}, {"name": "parentCollectionId", "type": "bytes32"}, {"name": "conditionId", "type": "bytes32"}, {"name": "indexSets", "type": "uint256[]"}], "outputs": []}]
      ).encode_abi(abi_element_identifier="redeemPositions", args=[collateral_token, parent_collection_id, condition_id, index_sets]),
      "value": "0"
  }

  response = client.execute([redeem_tx], "Redeem positions")
  response.wait()
  ```
</CodeGroup>
Split collateral tokens into conditional outcome tokens:
<CodeGroup>
  ```typescript TypeScript theme={null}
  import { encodeFunctionData } from "viem";

  const splitTx = {
    to: ctfAddress,
    data: encodeFunctionData({
      abi: [{
        name: "splitPosition",
        type: "function",
        inputs: [
          { name: "collateralToken", type: "address" },
          { name: "parentCollectionId", type: "bytes32" },
          { name: "conditionId", type: "bytes32" },
          { name: "partition", type: "uint256[]" },
          { name: "amount", type: "uint256" }
        ],
        outputs: []
      }],
      functionName: "splitPosition",
      args: [collateralToken, parentCollectionId, conditionId, partition, amount]
    }),
    value: "0"
  };

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

  ```python Python theme={null}
  from web3 import Web3

  CTF = "0x4d97dcd97ec945f40cf65f87097ace5ea0476045"

  split_tx = {
      "to": CTF,
      "data": Web3().eth.contract(
          address=CTF,
          abi=[{"name": "splitPosition", "type": "function", "inputs": [{"name": "collateralToken", "type": "address"}, {"name": "parentCollectionId", "type": "bytes32"}, {"name": "conditionId", "type": "bytes32"}, {"name": "partition", "type": "uint256[]"}, {"name": "amount", "type": "uint256"}], "outputs": []}]
      ).encode_abi(abi_element_identifier="splitPosition", args=[collateral_token, parent_collection_id, condition_id, partition, amount]),
      "value": "0"
  }

  response = client.execute([split_tx], "Split positions")
  response.wait()
  ```
</CodeGroup>
Merge conditional tokens back into collateral:
<CodeGroup>
  ```typescript TypeScript theme={null}
  import { encodeFunctionData } from "viem";

  const mergeTx = {
    to: ctfAddress,
    data: encodeFunctionData({
      abi: [{
        name: "mergePositions",
        type: "function",
        inputs: [
          { name: "collateralToken", type: "address" },
          { name: "parentCollectionId", type: "bytes32" },
          { name: "conditionId", type: "bytes32" },
          { name: "partition", type: "uint256[]" },
          { name: "amount", type: "uint256" }
        ],
        outputs: []
      }],
      functionName: "mergePositions",
      args: [collateralToken, parentCollectionId, conditionId, partition, amount]
    }),
    value: "0"
  };

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

  ```python Python theme={null}
  from web3 import Web3

  CTF = "0x4d97dcd97ec945f40cf65f87097ace5ea0476045"

  merge_tx = {
      "to": CTF,
      "data": Web3().eth.contract(
          address=CTF,
          abi=[{"name": "mergePositions", "type": "function", "inputs": [{"name": "collateralToken", "type": "address"}, {"name": "parentCollectionId", "type": "bytes32"}, {"name": "conditionId", "type": "bytes32"}, {"name": "partition", "type": "uint256[]"}, {"name": "amount", "type": "uint256"}], "outputs": []}]
      ).encode_abi(abi_element_identifier="mergePositions", args=[collateral_token, parent_collection_id, condition_id, partition, amount]),
      "value": "0"
  }

  response = client.execute([merge_tx], "Merge positions")
  response.wait()
  ```
</CodeGroup>
Execute multiple transactions atomically in a single call:
<CodeGroup>
  ```typescript TypeScript theme={null}
  import { encodeFunctionData, parseUnits, maxUint256 } from "viem";

  const erc20Abi = [
    {
      name: "approve",
      type: "function",
      inputs: [
        { name: "spender", type: "address" },
        { name: "amount", type: "uint256" }
      ],
      outputs: [{ type: "bool" }]
    },
    {
      name: "transfer",
      type: "function",
      inputs: [
        { name: "to", type: "address" },
        { name: "amount", type: "uint256" }
      ],
      outputs: [{ type: "bool" }]
    }
  ] as const;

  // Approve CTF to spend USDCe
  const approveTx = {
    to: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
    data: encodeFunctionData({
      abi: erc20Abi,
      functionName: "approve",
      args: ["0x4d97dcd97ec945f40cf65f87097ace5ea0476045", maxUint256]
    }),
    value: "0"
  };

  // Transfer some USDCe to another wallet
  const transferTx = {
    to: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
    data: encodeFunctionData({
      abi: erc20Abi,
      functionName: "transfer",
      args: ["0xRecipientAddressHere", parseUnits("50", 6)]
    }),
    value: "0"
  };

  // Both transactions execute in one call
  const response = await client.execute(
    [approveTx, transferTx], 
    "Approve and transfer"
  );
  await response.wait();
  ```

  ```python Python theme={null}
  from web3 import Web3

  USDC_E = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
  CTF = "0x4d97dcd97ec945f40cf65f87097ace5ea0476045"
  MAX_UINT256 = 2**256 - 1

  erc20_abi = [
      {"name": "approve", "type": "function", "inputs": [{"name": "spender", "type": "address"}, {"name": "amount", "type": "uint256"}], "outputs": [{"type": "bool"}]},
      {"name": "transfer", "type": "function", "inputs": [{"name": "to", "type": "address"}, {"name": "amount", "type": "uint256"}], "outputs": [{"type": "bool"}]}
  ]

  contract = Web3().eth.contract(address=USDC_E, abi=erc20_abi)

  # Approve CTF to spend USDCe
  approve_tx = {
      "to": USDC_E,
      "data": contract.encode_abi(abi_element_identifier="approve", args=[CTF, MAX_UINT256]),
      "value": "0"
  }

  # Transfer some USDCe to another wallet
  transfer_tx = {
      "to": USDC_E,
      "data": contract.encode_abi(abi_element_identifier="transfer", args=["0xRecipientAddressHere", 50 * 10**6]),
      "value": "0"
  }

  # Both transactions execute in one call
  response = client.execute([approve_tx, transfer_tx], "Approve and transfer")
  response.wait()
  ```
</CodeGroup>

<Info>
  Batching reduces latency and ensures all transactions succeed or fail together.
</Info>

Reference

Contracts & Approvals

Contract Address USDCe Outcome Tokens
USDCe 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
CTF 0x4d97dcd97ec945f40cf65f87097ace5ea0476045
CTF Exchange 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E
Neg Risk CTF Exchange 0xC5d563A36AE78145C45a50134d48A1215220f80a
Neg Risk Adapter 0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296

Transaction States

State Description
STATE_NEW Transaction received by relayer
STATE_EXECUTED Transaction executed onchain
STATE_MINED Transaction included in a block
STATE_CONFIRMED Transaction confirmed (final )
STATE_FAILED Transaction failed (terminal )
STATE_INVALID Transaction rejected as invalid (terminal )

TypeScript Types

```typescript theme={null} // Transaction type used in all examples 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; }

</Accordion>

***

## Next Steps

<CardGroup cols={2}>
<Card title="Order Attribution" icon="tag" href="/developers/builders/order-attribution">
  Attribute orders to your builder account
</Card>

<Card title="Example Apps" icon="code" href="/developers/builders/examples">
  Complete integration examples
</Card>
</CardGroup>