Skip to content

Polymarket/turnkey-safe-builder-example

Repository files navigation

Polymarket Safe Proxy - Turnkey Embedded Wallets Integration Demo

A Next.js application demonstrating how to integrate Polymarket's CLOB Client and Builder Relayer Client for gasless trading with builder order attribution, using Turnkey for web2-style authentication and non-custodial wallet provisioning.

This demo shows developers how to:

  • Authenticate users via Turnkey email login for web2-style onboarding
  • Provision an EOA wallet automatically via Turnkey's embedded wallets
  • Deploy a Gnosis Safe Proxy Wallet using the builder-relayer-client
  • Obtain User API Credentials from the CLOB client
  • Set token approvals for CTF Contract, CTF Exchange, Neg Risk Exchange, and Neg Risk Adapter
  • Place orders via CLOB client with builder attribution using remote signing

Table of Contents

  1. Prerequisites
  2. Quick Start
  3. Core Integration Patterns
  4. Key Implementation Details
  5. Project Structure
  6. Environment Variables
  7. Key Dependencies

Prerequisites

Before running this demo, you need:

  1. Builder API Credentials from Polymarket

    • Visit polymarket.com/settings?tab=builder to obtain your Builder credentials
    • You'll need: API_KEY, SECRET, and PASSPHRASE
  2. Polygon RPC URL

    • Any Polygon mainnet RPC (Alchemy, Infura, or public RPC)
  3. Turnkey Organization

    • Sign up at turnkey.com and create an organization
    • Get your Organization ID and Auth Proxy Config ID from the Turnkey Dashboard

Quick Start

Installation

npm install

Environment Setup

Populate .env.local:

# Polygon RPC endpoint
NEXT_PUBLIC_POLYGON_RPC_URL=your_RPC_URL

# Turnkey credentials (from turnkey.com dashboard)
NEXT_PUBLIC_TURNKEY_ORGANIZATION_ID=your_organization_id
NEXT_PUBLIC_TURNKEY_AUTH_PROXY_CONFIG_ID=your_auth_proxy_config_id

# Builder credentials (from polymarket.com/settings?tab=builder)
POLYMARKET_BUILDER_API_KEY=your_builder_api_key
POLYMARKET_BUILDER_SECRET=your_builder_secret
POLYMARKET_BUILDER_PASSPHRASE=your_builder_passphrase

Run Development Server

npm run dev

Open http://localhost:3000


Core Integration Patterns

Flow Overview

This application demonstrates two distinct user flows:

New User Flow

  1. User authenticates via Turnkey (email)
  2. Turnkey provisions a non-custodial EOA embedded wallet
  3. Initialize RelayClient with builder config
  4. Derive Safe address (deterministic from Turnkey EOA)
  5. Deploy Safe using RelayClient
  6. Obtain User API Credentials via temporary ClobClient
  7. Set token approvals (USDC.e + outcome tokens) in batch transaction
  8. Initialize authenticated ClobClient with credentials + builder config
  9. Ready to trade with builder attribution

Returning User Flow

  1. User authenticates via Turnkey (retrieves existing wallet)
  2. Initialize RelayClient with builder config
  3. Load or derive existing User API Credentials
  4. Verify Safe is deployed (skip deployment)
  5. Verify token approvals (skip if already approved)
  6. Initialize authenticated ClobClient with credentials + builder config
  7. Ready to trade with builder attribution

Key Implementation Details

1. Turnkey Authentication

Files: providers/WalletProvider.tsx, providers/WalletContext.tsx

Users authenticate via Turnkey's React Wallet Kit, which handles email login and automatically provisions a non-custodial EOA embedded wallet. No browser extension required.

import { TurnkeyProvider, useTurnkey, AuthState } from "@turnkey/react-wallet-kit";
import { createWalletClient, http } from "viem";
import { createAccount } from "@turnkey/viem";
import { polygon } from "viem/chains";

// Wrap your app with TurnkeyProvider
<TurnkeyProvider
  config={{
    organizationId: process.env.NEXT_PUBLIC_TURNKEY_ORGANIZATION_ID!,
    authProxyConfigId: process.env.NEXT_PUBLIC_TURNKEY_AUTH_PROXY_CONFIG_ID!,
  }}
>
  {children}
</TurnkeyProvider>

// Usage in components:
const { handleLogin, logout, authState, wallets, httpClient, session } = useTurnkey();
const authenticated = authState === AuthState.Authenticated;

handleLogin(); // Opens Turnkey auth modal

// Find embedded wallet and Ethereum account
const wallet = wallets.find((w) => w.source === "embedded");
const account = wallet?.accounts?.find((acc) => acc.address?.startsWith("0x"));
const eoaAddress = account?.address as `0x${string}`;

// Create Turnkey-powered viem account using @turnkey/viem
const turnkeyAccount = await createAccount({
  client: httpClient,
  organizationId: session.organizationId,
  signWith: eoaAddress,
  ethereumAddress: eoaAddress,
});

// Create viem wallet client
const walletClient = createWalletClient({
  account: turnkeyAccount,
  chain: polygon,
  transport: http(POLYGON_RPC_URL),
});

2. TurnkeyEthersAdapter

File: utils/TurnkeyEthersAdapter.ts

Since Polymarket's ClobClient requires an ethers v5 Signer for EIP-712 typed data signing, but Turnkey's official @turnkey/ethers package only supports ethers v6, we need a custom adapter.

The TurnkeyEthersSigner class bridges the viem WalletClient (powered by Turnkey) to an ethers v5-compatible Signer interface.

import { Signer, providers } from "ethers";
import { toHex } from "viem";
import type { WalletClient } from "viem";

export class TurnkeyEthersSigner extends Signer {
  private walletClient: WalletClient;
  private _address: `0x${string}`;
  public provider: providers.Provider | undefined;

  constructor(walletClient: WalletClient, provider?: providers.Provider) {
    super();
    this.walletClient = walletClient;

    if (!walletClient.account) {
      throw new Error("WalletClient must have an account");
    }

    this._address = walletClient.account.address;
    this.provider = provider;
  }

  async getAddress(): Promise<string> {
    return this._address;
  }

  async signMessage(message: string | Uint8Array): Promise<string> {
    const formattedMessage =
      typeof message === "string" ? message : toHex(message);

    return await this.walletClient.signMessage({
      account: this.walletClient.account!,
      message: formattedMessage,
    });
  }

  async _signTypedData(domain: any, types: any, value: any): Promise<string> {
    const { EIP712Domain, ...typesWithoutDomain } = types;
    return await this.walletClient.signTypedData({
      account: this.walletClient.account!,
      domain,
      types: typesWithoutDomain,
      primaryType: Object.keys(typesWithoutDomain)[0],
      message: value,
    });
  }

  async signTransaction(
    transaction: providers.TransactionRequest
  ): Promise<string> {
    const viemTx = {
      to: transaction.to as `0x${string}` | undefined,
      value: transaction.value
        ? BigInt(transaction.value.toString())
        : undefined,
      data: transaction.data as `0x${string}` | undefined,
      gas: transaction.gasLimit
        ? BigInt(transaction.gasLimit.toString())
        : undefined,
      // ... other fields
    };
    return await this.walletClient.signTransaction(viemTx as any);
  }

  connect(provider: providers.Provider): TurnkeyEthersSigner {
    return new TurnkeyEthersSigner(this.walletClient, provider);
  }
}

Why this adapter is needed:

Client Signer Type Required Used For
RelayClient viem WalletClient Safe deployment, token approvals
ClobClient ethers v5 Signer with _signTypedData Order placement, API credentials

Key method: _signTypedData

The ClobClient uses EIP-712 typed data signing for creating API credentials and signing orders. The adapter wraps viem's signTypedData method:

  • Removes EIP712Domain from types (viem handles this internally)
  • Passes domain, types, and message value to viem
  • Returns the signature in the format ethers expects

Usage in WalletProvider:

// Create ethers provider
const ethersProvider = new providers.JsonRpcProvider(POLYGON_RPC_URL);
await ethersProvider.ready;

// Create adapter that wraps viem walletClient
const ethersSigner = new TurnkeyEthersSigner(walletClient, ethersProvider);

3. Builder Config with Remote Signing

File: app/api/polymarket/sign/route.ts

Builder credentials are stored server-side and accessed via a remote signing endpoint. This keeps your builder credentials secure while enabling order attribution or relay authentication.

// Server-side API route
import {
  BuilderApiKeyCreds,
  buildHmacSignature,
} from "@polymarket/builder-signing-sdk";

const BUILDER_CREDENTIALS: BuilderApiKeyCreds = {
  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,
  });
}

Why remote signing?

  • Builder credentials never exposed to client
  • Secure HMAC signature generation
  • Required for builder order attribution (with ClobClient) or authentication (RelayClient)

4. RelayClient Initialization

File: hooks/useRelayClient.ts

The RelayClient is initialized with the user's viem WalletClient and builder config. It's used for Safe deployment, token approvals, and CTF operations.

import { RelayClient } from "@polymarket/builder-relayer-client";
import { BuilderConfig } from "@polymarket/builder-signing-sdk";

const builderConfig = new BuilderConfig({
  remoteBuilderConfig: {
    url: "/api/polymarket/sign", // Your remote signing endpoint
  },
});

// walletClient comes from WalletProvider context (viem WalletClient powered by Turnkey)
const relayClient = new RelayClient(
  "https://relayer-v2.polymarket.com/",
  137, // Polygon chain ID
  walletClient,
  builderConfig
);

Key Points:

  • Uses viem WalletClient directly (RelayClient internally creates a ViemSigner)
  • Requires builder config for authentication
  • Used for Safe deployment and approvals
  • Persisted throughout trading session

5. Safe Deployment

File: hooks/useSafeDeployment.ts

The Safe address is deterministically derived from the user's Turnkey EOA, then deployed if it doesn't exist.

import { deriveSafe } from "@polymarket/builder-relayer-client/dist/builder/derive";
import { getContractConfig } from "@polymarket/builder-relayer-client/dist/config";

// Step 1: Derive Safe address (deterministic)
const config = getContractConfig(137); // Polygon
const safeAddress = deriveSafe(eoaAddress, config.SafeContracts.SafeFactory);

// Step 2: Check if Safe is deployed
const deployed = await relayClient.getDeployed(safeAddress);

// Step 3: Deploy Safe if needed (Turnkey handles signature)
if (!deployed) {
  const response = await relayClient.deploy();
  const result = await response.wait();
  console.log("Safe deployed at:", result.proxyAddress);
}

Important:

  • Safe address is deterministic - same EOA always gets same Safe address
  • Safe is the "funder" address that holds USDC.e and outcome tokens
  • One-time deployment per EOA on user's first login
  • Turnkey handles the signature request

6. User API Credentials

File: hooks/useUserApiCredentials.ts

User API Credentials are obtained by creating a temporary ClobClient with the TurnkeyEthersSigner and calling deriveApiKey() or createApiKey().

import { ClobClient } from "@polymarket/clob-client";

// Create temporary CLOB client with TurnkeyEthersSigner
const tempClient = new ClobClient(
  "https://clob.polymarket.com",
  137, // Polygon chain ID
  ethersSigner // TurnkeyEthersSigner adapter
);

// Try to derive existing credentials (for returning users)
const derivedCreds = await tempClient.deriveApiKey().catch(() => null);

if (derivedCreds?.key && derivedCreds?.secret && derivedCreds?.passphrase) {
  creds = derivedCreds; // Turnkey handles signature
} else {
  // If derive fails, create new credentials
  creds = await tempClient.createApiKey(); // Turnkey handles signature
}

// creds = { key: string, secret: string, passphrase: string }

Flow:

  1. First-time users: createApiKey() creates new credentials
  2. Returning users: deriveApiKey() retrieves existing credentials
  3. Both methods require user signature (EIP-712) - handled by TurnkeyEthersSigner._signTypedData()
  4. Credentials are stored in localStorage for future sessions

Important:

Credentials alone are not enough to place new orders. However, they can be used to view orders and cancel limit orders. Storing credentials in localStorage is not recommended for production due to XSS vulnerability risks. This demo prioritizes simplicity over security — in production, use secure httpOnly cookies or server-side session management instead.

Why temporary client?

  • Credentials are needed to create the authenticated client
  • Temporary client is destroyed after obtaining credentials

7. Token Approvals

Files: hooks/useTokenApprovals.ts, utils/approvals.ts

Before trading, the Safe must approve multiple contracts to spend USDC.e and manage outcome tokens. This involves setting approvals for both ERC-20 (USDC.e) and ERC-1155 (outcome tokens).

Required Approvals

USDC.e (ERC-20) Approvals:

  • CTF Contract: 0x4d97dcd97ec945f40cf65f87097ace5ea0476045
  • CTF Exchange: 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E
  • Neg Risk CTF Exchange: 0xC5d563A36AE78145C45a50134d48A1215220f80a
  • Neg Risk Adapter: 0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296

Outcome Token (ERC-1155) Approvals:

  • CTF Exchange: 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E
  • Neg Risk CTF Exchange: 0xC5d563A36AE78145C45a50134d48A1215220f80a
  • Neg Risk Adapter: 0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296

Implementation

import { createAllApprovalTxs, checkAllApprovals } from "@/utils/approvals";

// Step 1: Check existing approvals
const approvalStatus = await checkAllApprovals(safeAddress);

if (approvalStatus.allApproved) {
  console.log("All approvals already set");
  // Skip approval step
} else {
  // Step 2: Create approval transactions
  const approvalTxs = createAllApprovalTxs();
  // Returns array of SafeTransaction objects

  // Step 3: Execute all approvals in a single batch
  const response = await relayClient.execute(
    approvalTxs,
    "Set all token approvals for trading"
  );

  await response.wait();
  console.log("All approvals set successfully");
}

Approval Transaction Structure

Each approval transaction is a SafeTransaction:

// ERC-20 approval (USDC.e)
{
  to: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC.e address
  operation: OperationType.Call,
  data: erc20Interface.encodeFunctionData('approve', [
    spenderAddress,
    MAX_UINT256 // Unlimited approval
  ]),
  value: '0'
}

// ERC-1155 approval (outcome tokens)
{
  to: '0x4d97dcd97ec945f40cf65f87097ace5ea0476045', // CTF Contract address
  operation: OperationType.Call,
  data: erc1155Interface.encodeFunctionData('setApprovalForAll', [
    operatorAddress,
    true // Enable operator
  ]),
  value: '0'
}

Why Multiple Approvals?

Polymarket's trading system uses different contracts for different market types:

  • CTF Contract: Manages outcome tokens (ERC-1155)
  • CTF Exchange: Standard binary markets
  • Neg Risk CTF Exchange: Negative risk markets (mutually exclusive outcomes)
  • Neg Risk Adapter: Converts between neg risk and standard markets

Setting all approvals upfront ensures:

  • Users can trade in any market type
  • One-time setup (approvals persist across sessions)
  • Gasless execution via RelayClient
  • Single user signature for all approvals

Checking Approvals

Before setting approvals, the app checks onchain state:

// Check USDC.e approval
const allowance = await publicClient.readContract({
  address: USDC_E_ADDRESS,
  abi: ERC20_ABI,
  functionName: "allowance",
  args: [safeAddress, spenderAddress],
});

const isApproved = allowance >= threshold; // 1000000000000 (1M USDC.e)

// Check outcome token approval
const isApprovedForAll = await publicClient.readContract({
  address: CTF_CONTRACT_ADDRESS,
  abi: ERC1155_ABI,
  functionName: "isApprovedForAll",
  args: [safeAddress, operatorAddress],
});

Key Points:

  • Uses batch execution via relayClient.execute() for gas efficiency
  • Sets unlimited approvals (MaxUint256) for ERC-20 tokens
  • Sets operator approvals for ERC-1155 outcome tokens
  • One-time setup per Safe (persists across sessions)
  • User signs once to approve all transactions (Turnkey handles signature)
  • Gasless for the user

8. Authenticated ClobClient

File: hooks/useClobClient.ts

After obtaining User API Credentials, create the authenticated ClobClient with the TurnkeyEthersSigner and builder config.

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, // Polygon chain ID
  ethersSigner, // TurnkeyEthersSigner adapter
  userApiCredentials, // { key, secret, passphrase }
  2, // signatureType = 2 for EOA associated to a Gnosis Safe proxy wallet
  safeAddress, // funder address from step 5
  undefined, // mandatory placeholder
  false,
  builderConfig // Builder order attribution
);

Parameters Explained:

  • ethersSigner: TurnkeyEthersSigner wrapping the Turnkey viem wallet
  • userApiCredentials: Obtained from Step 6
  • signatureType = 2: Indicates EOA associated to a Gnosis Safe proxy wallet
  • safeAddress: The Safe proxy wallet address that holds funds
  • builderConfig: Enables order attribution

This is the persistent client used for all trading operations.


9. Placing Orders

File: hooks/useClobOrder.ts

With the authenticated ClobClient, you can place orders with builder attribution.

// Create order
const order = {
  tokenID: "0x...", // Outcome token address
  price: 0.65, // Price in decimal (65 cents)
  size: 10, // Number of shares
  side: "BUY", // or 'SELL'
  feeRateBps: 0,
  expiration: 0, // 0 = Good-til-Cancel
  taker: "0x0000000000000000000000000000000000000000",
};

// Submit order (Turnkey handles signature via TurnkeyEthersSigner)
const response = await clobClient.createAndPostOrder(
  order,
  { negRisk: false }, // Market-specific flag
  OrderType.GTC
);

console.log("Order ID:", response.orderID);

Key Points:

  • Orders are signed by the user's Turnkey EOA (via TurnkeyEthersSigner._signTypedData)
  • Executed from the Safe address (funder)
  • Builder attribution is automatic via builderConfig
  • Gasless execution (no gas fees for users)

Cancel Order:

await clobClient.cancelOrder({ orderID: "order_id_here" });

Project Structure

Core Implementation Files

polymarket-turnkey-safe/
├── app/
│   ├── api/
│   │   └── polymarket/
│   │       └── sign/
│   │           └── route.ts              # Remote signing endpoint
│   └── page.tsx                          # Main application UI
│
├── components/
│   └── Header/
│       └── index.tsx                     # Login/logout with useTurnkey()
│
├── hooks/
│   ├── useTradingSession.ts              # Session orchestration (main flow)
│   ├── useRelayClient.ts                 # RelayClient initialization
│   ├── useSafeDeployment.ts              # Safe deployment logic
│   ├── useUserApiCredentials.ts          # User API credential derivation
│   ├── useTokenApprovals.ts              # Token approval management
│   ├── useClobClient.ts                  # Authenticated CLOB client
│   └── useClobOrder.ts                   # Order placement/cancellation
│
├── providers/
│   ├── WalletProvider.tsx                # Turnkey authentication + wallet clients
│   ├── WalletContext.tsx                 # Wallet context and useWallet hook
│   └── TradingProvider.tsx               # Trading session + client context
│
├── utils/
│   ├── TurnkeyEthersAdapter.ts           # ethers v5 Signer wrapping viem WalletClient
│   ├── session.ts                        # Session persistence (localStorage)
│   └── approvals.ts                      # Token approval utilities
│
└── constants/
    ├── polymarket.ts                     # API URLs and constants
    └── tokens.ts                         # Token addresses

Key Hook: useTradingSession.ts

This is the orchestrator that manages the entire trading session lifecycle:

// Coordinates:
// 1. Initialize RelayClient with builder config
// 2. Derive Safe address
// 3. Check if Safe is deployed → deploy if needed
// 4. Get User API Credentials → derive or create
// 5. Check token approvals → approve if needed (batch)
// 6. Save session to localStorage
// 7. Initialize authenticated ClobClient

const {
  tradingSession,
  currentStep,
  initializeTradingSession,
  endTradingSession,
  relayClient,
  isTradingSessionComplete,
} = useTradingSession();

Read this hook first to understand the complete flow.


Environment Variables

Create .env.local:

# Required: Polygon RPC
NEXT_PUBLIC_POLYGON_RPC_URL=https://polygon-rpc.com

# Required: Turnkey credentials (from turnkey.com dashboard)
NEXT_PUBLIC_TURNKEY_ORGANIZATION_ID=your_organization_id
NEXT_PUBLIC_TURNKEY_AUTH_PROXY_CONFIG_ID=your_auth_proxy_config_id

# Required: Builder credentials (from polymarket.com/settings?tab=builder)
POLYMARKET_BUILDER_API_KEY=your_builder_api_key
POLYMARKET_BUILDER_SECRET=your_builder_secret
POLYMARKET_BUILDER_PASSPHRASE=your_builder_passphrase

Key Dependencies

Package Version Purpose
@turnkey/react-wallet-kit ^0.1.3 Authentication / Embedded Wallet UI
@turnkey/viem ^1.1.13 Create Turnkey-powered viem accounts
@polymarket/clob-client ^4.22.8 Order placement, User API credentials
@polymarket/builder-relayer-client ^0.0.6 Safe deployment, token approvals, CTF operations
@polymarket/builder-signing-sdk ^0.0.8 Builder credential HMAC signatures
viem ^2.39.2 Ethereum interactions, RPC calls
ethers ^5.8.0 Wallet signing, EIP-712 messages (via adapter)
@tanstack/react-query ^5.90.10 Server state management
next 16.0.3 React framework, API routes

Architecture Diagram

User (email login)
         ↓
    [Turnkey Auth]
    useTurnkey() → handleLogin()
         ↓
    Turnkey EOA (embedded wallet)
         ↓
┌────────────────────────────────────────────────────┐
│  Wallet Initialization (WalletProvider)            │
├────────────────────────────────────────────────────┤
│  1. @turnkey/viem createAccount() → Turnkey acc    │
│  2. viem createWalletClient() → WalletClient       │
│  3. TurnkeyEthersSigner → ethers v5 compatible     │
└────────────────────────────────────────────────────┘
         ↓
┌────────────────────────────────────────────────────┐
│  Trading Session Initialization                    │
├────────────────────────────────────────────────────┤
│  1. Initialize RelayClient (viem WalletClient)     │
│  2. Derive Safe address from EOA                   │
│  3. Check if Safe deployed → deploy if needed      │
│  4. Get User API Credentials (TurnkeyEthersSigner) │
│  5. Set token approvals (batch execution):         │
│     - USDC.e → 4 spenders (ERC-20)                 │
│     - Outcome tokens → 3 operators (ERC-1155)      │
│  6. Save session to localStorage                   │
└────────────────────────────────────────────────────┘
         ↓
┌────────────────────────────────────────────────────┐
│  Authenticated ClobClient                          │
├────────────────────────────────────────────────────┤
│  - User API Credentials                            │
│  - Builder Config (remote signing)                 │
│  - Safe address (funder)                           │
│  - TurnkeyEthersSigner (EIP-712 signing)           │
└────────────────────────────────────────────────────┘
         ↓
    Place Orders
    (Standard + Neg Risk markets)
    (with builder attribution)

Troubleshooting

"Turnkey authentication failed"

  • Verify NEXT_PUBLIC_TURNKEY_ORGANIZATION_ID is set correctly in .env.local
  • Verify NEXT_PUBLIC_TURNKEY_AUTH_PROXY_CONFIG_ID is set correctly
  • Check Turnkey dashboard for any organization configuration issues

"Failed to initialize relay client"

  • Check builder credentials in .env.local
  • Verify /api/polymarket/sign endpoint is accessible
  • Check browser console for errors
  • Ensure walletClient is initialized before RelayClient

"Safe deployment failed"

  • Check Polygon RPC URL is valid
  • User must approve signature via Turnkey
  • Verify builder credentials are configured correctly
  • Check browser console for relay service errors

"Token approval failed"

  • Safe must be deployed first
  • User must approve transaction signature via Turnkey
  • Verify builder relay service is operational

"signer._signTypedData is not a function"

  • Ensure you're using TurnkeyEthersSigner (not the viem WalletClient) with ClobClient
  • The adapter implements _signTypedData for EIP-712 signing

Orders not appearing

  • Verify trading session is complete
  • Check Safe has USDC.e balance
  • Wait 2-3 seconds for CLOB sync

Resources

Turnkey Documentation

Polymarket Documentation

GitHub Repositories

Other Resources


Support

Questions or issues? Reach out on Telegram: @notyrjo


License

MIT


Built for builders exploring the Polymarket ecosystem with Turnkey authentication

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages