409 lines
13 KiB
Markdown
409 lines
13 KiB
Markdown
|
|
> ## 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.
|
|||
|
|
|
|||
|
|
# Authentication
|
|||
|
|
|
|||
|
|
> Understanding authentication using Polymarket's CLOB
|
|||
|
|
|
|||
|
|
The CLOB uses two levels of authentication: **L1 (Private Key)** and **L2 (API Key)**.
|
|||
|
|
Either can be accomplished using the CLOB client or REST API. Authentication is not
|
|||
|
|
required to access client public methods and public endpoints.
|
|||
|
|
|
|||
|
|
## Authentication Levels
|
|||
|
|
|
|||
|
|
<CardGroup cols={2}>
|
|||
|
|
<Card title="L1 Authentication" icon="key" href="#l1-authentication">
|
|||
|
|
Use the private key of the user’s account to sign messages
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="L2 Authentication" icon="lock" href="#l2-authentication">
|
|||
|
|
Use API credentials (key, secret, passphrase) to authenticate requests to the CLOB
|
|||
|
|
</Card>
|
|||
|
|
</CardGroup>
|
|||
|
|
|
|||
|
|
***
|
|||
|
|
|
|||
|
|
## L1 Authentication
|
|||
|
|
|
|||
|
|
### What is L1?
|
|||
|
|
|
|||
|
|
L1 authentication uses the wallet's private key to sign an EIP-712 message used in the
|
|||
|
|
request header. It proves ownership and control over the private key. The private key
|
|||
|
|
stays in control of the user and all trading activity remains non-custodial.
|
|||
|
|
|
|||
|
|
### What This Enables
|
|||
|
|
|
|||
|
|
Access to L1 methods that create or derive L2 authentication headers.
|
|||
|
|
|
|||
|
|
* Create user API credentials
|
|||
|
|
* Derive existing user API credentials
|
|||
|
|
* Sign/create user's orders locally
|
|||
|
|
|
|||
|
|
### CLOB Client
|
|||
|
|
|
|||
|
|
<Tabs>
|
|||
|
|
<Tab title="TypeScript">
|
|||
|
|
```typescript theme={null}
|
|||
|
|
import { ClobClient } from "@polymarket/clob-client";
|
|||
|
|
import { Wallet } from "ethers"; // v5.8.0
|
|||
|
|
|
|||
|
|
const HOST = "https://clob.polymarket.com";
|
|||
|
|
const CHAIN_ID = 137; // Polygon mainnet
|
|||
|
|
const signer = new Wallet(process.env.PRIVATE_KEY);
|
|||
|
|
|
|||
|
|
const client = new ClobClient(
|
|||
|
|
HOST,
|
|||
|
|
CHAIN_ID,
|
|||
|
|
signer // Signer enables L1 methods
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Gets API key, or else creates
|
|||
|
|
const apiCreds = await client.createOrDeriveApiKey();
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
apiCreds = {
|
|||
|
|
"apiKey": "550e8400-e29b-41d4-a716-446655440000",
|
|||
|
|
"secret": "base64EncodedSecretString",
|
|||
|
|
"passphrase": "randomPassphraseString"
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
```
|
|||
|
|
</Tab>
|
|||
|
|
|
|||
|
|
<Tab title="Python">
|
|||
|
|
```python theme={null}
|
|||
|
|
from py_clob_client.client import ClobClient
|
|||
|
|
import os
|
|||
|
|
|
|||
|
|
host = "https://clob.polymarket.com"
|
|||
|
|
chain_id = 137 # Polygon mainnet
|
|||
|
|
private_key = os.getenv("PRIVATE_KEY")
|
|||
|
|
|
|||
|
|
client = ClobClient(
|
|||
|
|
host=host,
|
|||
|
|
chain_id=chaind_id,
|
|||
|
|
key=private_key # Signer enables L1 methods
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Gets API key, or else creates
|
|||
|
|
api_creds = await client.create_or_derive_api_key()
|
|||
|
|
|
|||
|
|
# api_creds = {
|
|||
|
|
# "apiKey": "550e8400-e29b-41d4-a716-446655440000",
|
|||
|
|
# "secret": "base64EncodedSecretString",
|
|||
|
|
# "passphrase": "randomPassphraseString"
|
|||
|
|
# }
|
|||
|
|
```
|
|||
|
|
</Tab>
|
|||
|
|
</Tabs>
|
|||
|
|
|
|||
|
|
<Warning>
|
|||
|
|
**Never commit private keys to version control.** Always use environment
|
|||
|
|
variables or secure key management systems.
|
|||
|
|
</Warning>
|
|||
|
|
|
|||
|
|
***
|
|||
|
|
|
|||
|
|
### REST API
|
|||
|
|
|
|||
|
|
While we highly recommend using our provided clients to handle signing
|
|||
|
|
and authentication, the following is for developers who choose NOT to
|
|||
|
|
use our [Python](https://github.com/Polymarket/py-clob-client) or
|
|||
|
|
[TypeScript](https://github.com/Polymarket/clob-client) clients.
|
|||
|
|
|
|||
|
|
When making direct REST API calls with L1 authentication, include these headers:
|
|||
|
|
|
|||
|
|
| Header | Required? | Description |
|
|||
|
|
| ---------------- | --------- | ---------------------- |
|
|||
|
|
| `POLY_ADDRESS` | yes | Polygon signer address |
|
|||
|
|
| `POLY_SIGNATURE` | yes | CLOB EIP 712 signature |
|
|||
|
|
| `POLY_TIMESTAMP` | yes | Current UNIX timestamp |
|
|||
|
|
| `POLY_NONCE` | yes | Nonce. Default 0 |
|
|||
|
|
|
|||
|
|
The `POLY_SIGNATURE` is generated by signing the following EIP-712 struct.
|
|||
|
|
|
|||
|
|
<Accordion title="EIP-712 Signing Example">
|
|||
|
|
<CodeGroup>
|
|||
|
|
```typescript Typescript theme={null}
|
|||
|
|
const domain = {
|
|||
|
|
name: "ClobAuthDomain",
|
|||
|
|
version: "1",
|
|||
|
|
chainId: chainId, // Polygon Chain ID 137
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const types = {
|
|||
|
|
ClobAuth: [
|
|||
|
|
{ name: "address", type: "address" },
|
|||
|
|
{ name: "timestamp", type: "string" },
|
|||
|
|
{ name: "nonce", type: "uint256" },
|
|||
|
|
{ name: "message", type: "string" },
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const value = {
|
|||
|
|
address: signingAddress, // The Signing address
|
|||
|
|
timestamp: ts, // The CLOB API server timestamp
|
|||
|
|
nonce: nonce, // The nonce used
|
|||
|
|
message: "This message attests that I control the given wallet",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const sig = await signer._signTypedData(domain, types, value);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```python Python theme={null}
|
|||
|
|
domain = {
|
|||
|
|
"name": "ClobAuthDomain",
|
|||
|
|
"version": "1",
|
|||
|
|
"chainId": chainId, # Polygon Chain ID 137
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
types = {
|
|||
|
|
"ClobAuth": [
|
|||
|
|
{"name": "address", "type": "address"},
|
|||
|
|
{"name": "timestamp", "type": "string"},
|
|||
|
|
{"name": "nonce", "type": "uint256"},
|
|||
|
|
{"name": "message", "type": "string"},
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
value = {
|
|||
|
|
"address": signingAddress, # The signing address
|
|||
|
|
"timestamp": ts, # The CLOB API server timestamp
|
|||
|
|
"nonce": nonce, # The nonce used
|
|||
|
|
"message": "This message attests that I control the given wallet",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sig = await signer._signTypedData(domain, types, value)
|
|||
|
|
```
|
|||
|
|
</CodeGroup>
|
|||
|
|
</Accordion>
|
|||
|
|
|
|||
|
|
Reference implementations:
|
|||
|
|
|
|||
|
|
* [TypeScript](https://github.com/Polymarket/clob-client/blob/main/src/signing/eip712.ts)
|
|||
|
|
* [Python](https://github.com/Polymarket/py-clob-client/blob/main/py_clob_client/signing/eip712.py)
|
|||
|
|
|
|||
|
|
***
|
|||
|
|
|
|||
|
|
**Create API Credentials**
|
|||
|
|
|
|||
|
|
Create new API credentials for user.
|
|||
|
|
|
|||
|
|
```bash theme={null}
|
|||
|
|
POST {clob-endpoint}/auth/api-key
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Derive API Credentials**
|
|||
|
|
|
|||
|
|
Derive API credentials for user.
|
|||
|
|
|
|||
|
|
```bash theme={null}
|
|||
|
|
GET {clob-endpoint}/auth/derive-api-key
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Response**
|
|||
|
|
|
|||
|
|
```json theme={null}
|
|||
|
|
{
|
|||
|
|
"apiKey": "550e8400-e29b-41d4-a716-446655440000",
|
|||
|
|
"secret": "base64EncodedSecretString",
|
|||
|
|
"passphrase": "randomPassphraseString"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**You'll need all three values for L2 authentication.**
|
|||
|
|
|
|||
|
|
***
|
|||
|
|
|
|||
|
|
## L2 Authentication
|
|||
|
|
|
|||
|
|
### What is L2?
|
|||
|
|
|
|||
|
|
The next level of authentication is called L2, and it consists of the
|
|||
|
|
user's API credentials (apiKey, secret, passphrase) generated from L1
|
|||
|
|
authentication. These are used solely to authenticate requests made to
|
|||
|
|
the CLOB API. Requests are signed using HMAC-SHA256.
|
|||
|
|
|
|||
|
|
### What This Enables
|
|||
|
|
|
|||
|
|
Access to L2 methods such as posting signed/created orders, viewing open
|
|||
|
|
orders, cancelling open orders, getting trades
|
|||
|
|
|
|||
|
|
* Cancel or get user's open orders
|
|||
|
|
* Check user's balances and allowances
|
|||
|
|
* Post user's signed orders
|
|||
|
|
|
|||
|
|
### CLOB Client
|
|||
|
|
|
|||
|
|
<Tabs>
|
|||
|
|
<Tab title="TypeScript">
|
|||
|
|
```typescript theme={null}
|
|||
|
|
import { ClobClient } from "@polymarket/clob-client";
|
|||
|
|
import { Wallet } from "ethers"; // v5.8.0
|
|||
|
|
|
|||
|
|
const HOST = "https://clob.polymarket.com";
|
|||
|
|
const CHAIN_ID = 137; // Polygon mainnet
|
|||
|
|
const signer = new Wallet(process.env.PRIVATE_KEY);
|
|||
|
|
|
|||
|
|
const client = new ClobClient(
|
|||
|
|
HOST,
|
|||
|
|
CHAIN_ID,
|
|||
|
|
signer,
|
|||
|
|
apiCreds, // Generated from L1 auth, API credentials enable L2 methods
|
|||
|
|
1, // signatureType explained below
|
|||
|
|
FUNDER // funder explained below
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Now you can trade!*
|
|||
|
|
const order = await client.createAndPostOrder(
|
|||
|
|
{ tokenID: "123456", price: 0.65, size: 100, side: "BUY" },
|
|||
|
|
{ tickSize: "0.01", negRisk: false }
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
</Tab>
|
|||
|
|
|
|||
|
|
<Tab title="Python">
|
|||
|
|
```python theme={null}
|
|||
|
|
from py_clob_client.client import ClobClient
|
|||
|
|
import os
|
|||
|
|
|
|||
|
|
host = "https://clob.polymarket.com"
|
|||
|
|
chain_id = 137 # Polygon mainnet
|
|||
|
|
private_key = os.getenv("PRIVATE_KEY")
|
|||
|
|
|
|||
|
|
client = ClobClient(
|
|||
|
|
host="https://clob.polymarket.com",
|
|||
|
|
chain_id=137,
|
|||
|
|
key=os.getenv("PRIVATE_KEY"),
|
|||
|
|
creds=api_creds, # Generated from L1 auth, API credentials enable L2 methods
|
|||
|
|
signature_type=1, # signatureType explained below
|
|||
|
|
funder=os.getenv("FUNDER_ADDRESS") # funder explained below
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Now you can trade!*
|
|||
|
|
order = await client.create_and_post_order(
|
|||
|
|
{"token_id": "123456", "price": 0.65, "size": 100, "side": "BUY"},
|
|||
|
|
{"tick_size": "0.01", "neg_risk": False}
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
</Tab>
|
|||
|
|
</Tabs>
|
|||
|
|
|
|||
|
|
<Info>
|
|||
|
|
Even with L2 authentication headers, methods that create user orders still require the user to sign the order payload.
|
|||
|
|
</Info>
|
|||
|
|
|
|||
|
|
***
|
|||
|
|
|
|||
|
|
### REST API
|
|||
|
|
|
|||
|
|
While we highly recommend using our provided clients to handle signing
|
|||
|
|
and authentication, the following is for developers who choose NOT to
|
|||
|
|
use our [Python](https://github.com/Polymarket/py-clob-client) or
|
|||
|
|
[TypeScript](https://github.com/Polymarket/clob-client) clients.
|
|||
|
|
|
|||
|
|
When making direct REST API calls with L2 authentication, include these headers:
|
|||
|
|
|
|||
|
|
| Header | Required? | Description |
|
|||
|
|
| ----------------- | --------- | ----------------------------- |
|
|||
|
|
| `POLY_ADDRESS` | yes | Polygon signer address |
|
|||
|
|
| `POLY_SIGNATURE` | yes | HMAC signature for request |
|
|||
|
|
| `POLY_TIMESTAMP` | yes | Current UNIX timestamp |
|
|||
|
|
| `POLY_API_KEY` | yes | User's API `apiKey` value |
|
|||
|
|
| `POLY_PASSPHRASE` | yes | User's API `passphrase` value |
|
|||
|
|
|
|||
|
|
The `POLY_SIGNATURE` for L2 is an HMAC-SHA256 signature created using the user's API credentials `secret` value.
|
|||
|
|
Reference implementations can be found in the [Typescript](https://github.com/Polymarket/clob-client/blob/main/src/signing/hmac.ts)
|
|||
|
|
and [Python](https://github.com/Polymarket/py-clob-client/blob/main/py_clob_client/signing/hmac.py) clients.
|
|||
|
|
|
|||
|
|
***
|
|||
|
|
|
|||
|
|
## Signature Types and Funder
|
|||
|
|
|
|||
|
|
When initializing the L2 client, you must specify your wallet **signatureType** and the **funder** address which holds the funds:
|
|||
|
|
|
|||
|
|
| Signature Type | Value | Description |
|
|||
|
|
| -------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|||
|
|
| EOA | 0 | Standard Ethereum wallet (MetaMask). Funder is the EOA address and will need POL to pay gas on transactions. |
|
|||
|
|
| POLY\_PROXY | 1 | A custom proxy wallet only used with users who logged in via Magic Link email/Google. Using this requires the user to have exported their PK from Polymarket.com and imported into your app. |
|
|||
|
|
| GNOSIS\_SAFE | 2 | Gnosis Safe multisig proxy wallet (most common). Use this for any new or returning user who does not fit the other 2 types. |
|
|||
|
|
|
|||
|
|
<Tip>
|
|||
|
|
The wallet addresses displayed to the user on Polymarket.com is the proxy wallet and should be used as the funder.
|
|||
|
|
These can be deterministically derived or you can deploy them on behalf of the user.
|
|||
|
|
These proxy wallets are automatically deployed for the user on their first login to Polymarket.com.
|
|||
|
|
</Tip>
|
|||
|
|
|
|||
|
|
***
|
|||
|
|
|
|||
|
|
## Troubleshooting
|
|||
|
|
|
|||
|
|
<AccordionGroup>
|
|||
|
|
<Accordion title="Error: INVALID_SIGNATURE">
|
|||
|
|
Your wallet's private key is incorrect or improperly formatted.
|
|||
|
|
|
|||
|
|
**Solution:**
|
|||
|
|
|
|||
|
|
* Verify your private key is a valid hex string (starts with "0x")
|
|||
|
|
* Ensure you're using the correct key for the intended address
|
|||
|
|
* Check that the key has proper permissions
|
|||
|
|
</Accordion>
|
|||
|
|
|
|||
|
|
<Accordion title="Error: NONCE_ALREADY_USED">
|
|||
|
|
The nonce you provided has already been used to create an API key.
|
|||
|
|
|
|||
|
|
**Solution:**
|
|||
|
|
|
|||
|
|
* Use `deriveApiKey()` with the same nonce to retrieve existing credentials
|
|||
|
|
* Or use a different nonce with `createApiKey()`
|
|||
|
|
</Accordion>
|
|||
|
|
|
|||
|
|
<Accordion title="Error: Invalid Funder Address">
|
|||
|
|
Your funder address is incorrect or doesn't match your wallet.
|
|||
|
|
|
|||
|
|
**Solution:** Check your Polymarket profile address at [polymarket.com/settings](https://polymarket.com/settings).
|
|||
|
|
|
|||
|
|
If it does not exist or user has never logged into Polymarket.com, deploy it first before creating L2 authentication.
|
|||
|
|
</Accordion>
|
|||
|
|
|
|||
|
|
<Accordion title="Lost API credentials but have nonce">
|
|||
|
|
```typescript theme={null}
|
|||
|
|
// Use deriveApiKey with the original nonce
|
|||
|
|
const recovered = await client.deriveApiKey(originalNonce);
|
|||
|
|
```
|
|||
|
|
</Accordion>
|
|||
|
|
|
|||
|
|
<Accordion title="Lost both credentials and nonce">
|
|||
|
|
Unfortunately, there's no way to recover lost API credentials without the nonce. You'll need to create new credentials:
|
|||
|
|
|
|||
|
|
```typescript theme={null}
|
|||
|
|
// Create fresh credentials with a new nonce
|
|||
|
|
const newCreds = await client.createApiKey();
|
|||
|
|
// Save the nonce this time!
|
|||
|
|
```
|
|||
|
|
</Accordion>
|
|||
|
|
</AccordionGroup>
|
|||
|
|
|
|||
|
|
***
|
|||
|
|
|
|||
|
|
## See Client Methods
|
|||
|
|
|
|||
|
|
<CardGroup cols={2}>
|
|||
|
|
<Card title="Public Methods" icon="globe" href="/developers/CLOB/clients/methods-public">
|
|||
|
|
Access market data, orderbooks, and prices.
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="L1 Methods" icon="key" href="/developers/CLOB/clients/methods-l1">
|
|||
|
|
Private key authentication to create or derive API keys (L2 headers).
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="L2 Methods" icon="lock" href="/developers/CLOB/clients/methods-l2">
|
|||
|
|
Manage and close orders. Creating orders requires signer.
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="Builder Program Methods" icon="hammer" href="/developers/CLOB/clients/methods-builder">
|
|||
|
|
Builder-specific operations for those in the Builders Program.
|
|||
|
|
</Card>
|
|||
|
|
</CardGroup>
|