Skip to content

Commit 43d7731

Browse files
committed
Fix signing to allow reading balance and placing orders
1 parent 705ee1b commit 43d7731

File tree

10 files changed

+268
-42
lines changed

10 files changed

+268
-42
lines changed

core/API_REFERENCE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,9 @@ Requires your **Polygon Private Key**. See [Setup Guide](https://github.com/qoer
316316
import pmxt from 'pmxtjs';
317317

318318
const polymarket = new pmxt.Polymarket({
319-
privateKey: process.env.POLYMARKET_PRIVATE_KEY
319+
privateKey: process.env.POLYMARKET_PRIVATE_KEY,
320+
funderAddress: process.env.POLYMARKET_PROXY_ADDRESS, // Optional: Proxy address
321+
signatureType: 'gnosis-safe' // 'eoa' | 'poly-proxy' | 'gnosis-safe'
320322
});
321323
```
322324

core/docs/SETUP_POLYMARKET.md

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,59 @@
11
# Polymarket Setup Guide
22

3-
To trade on Polymarket via the API, you need your **Polygon Private Key**.
3+
To trade on Polymarket via the API, you need your **Polygon Private Key**. If you are using a Polymarket Smart Wallet (Proxy), you will also need your **Proxy Address**.
44

5-
## How to export your Private Key (MetaMask)
5+
## 1. Exporting your Private Key (EOA)
66

7-
1. **Open the Account Menu**
7+
This is the private key of your "Signer" wallet (the one you use to log in to Polymarket).
8+
9+
1. **Open the Account Menu** in MetaMask.
810
Click the top-left icon (or account selector) to view your accounts.
911
![Select Account](images/0.png)
1012

11-
2. **Open Account Options**
12-
Click the three dots ("...") next to the account you want to use.
13+
2. **Open Account Options** (...) next to the account you want to use.
1314
![Account Options](images/1.png)
1415

15-
3. **Access Account Details**
16-
Select "Account Details".
16+
3. **Select Account Details**.
1717
![Account Details](images/2.png)
1818

19-
4. **Reveal Private Key**
20-
Click "Show Private Key" and unlock your wallet.
19+
4. **Reveal Private Key** and unlock your wallet.
2120
![Reveal Key](images/3.png)
2221

23-
5. **Copy the Key**
24-
Copy the private key string.
22+
5. **Copy the Key**.
2523
![Copy Key](images/4.png)
2624

27-
## Configuration
25+
## 2. Finding your Proxy Address (Optional but Recommended)
26+
27+
Most modern Polymarket accounts use a "Smart Wallet" or Proxy to hold funds. While `pmxt` attempts to auto-discover this address, it is more reliable to provide it manually.
28+
29+
1. Go to [Polymarket.com](https://polymarket.com).
30+
2. Hover over your profile in the top right.
31+
3. Your **Proxy Address** is the address shown. It is the address starting with `0x`.
32+
33+
## 3. Configuration
2834

29-
Add the key to your `.env` file in the project root:
35+
Add these to your `.env` file or pass them directly to the constructor. `pmxt` supports both numeric IDs and human-readable names:
3036

3137
```bash
3238
POLYMARKET_PRIVATE_KEY=0x...
39+
POLYMARKET_PROXY_ADDRESS=0x...
40+
41+
# Choose your account type:
42+
# - 'gnosis_safe' (Modern accounts, recommended)
43+
# - 'polyproxy' (Older accounts)
44+
# - 'eoa' (Standard wallet, no proxy)
45+
POLYMARKET_SIGNATURE_TYPE='gnosis_safe'
3346
```
3447

35-
*Note: Ensure the key starts with `0x`. If it does not, prefix 0x manually.*
48+
## 4. Initialization (Python)
49+
50+
```python
51+
import os
52+
import pmxt
53+
54+
exchange = pmxt.Polymarket(
55+
private_key=os.getenv('POLYMARKET_PRIVATE_KEY'),
56+
proxy_address=os.getenv('POLYMARKET_PROXY_ADDRESS'),
57+
signature_type='gnosis_safe'
58+
)
59+
```

core/src/BaseExchange.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface ExchangeCredentials {
2525
privateKey?: string; // Required for Polymarket L1 auth
2626

2727
// Polymarket-specific L2 fields
28-
signatureType?: number; // 0 = EOA, 1 = Poly Proxy, 2 = Gnosis Safe
28+
signatureType?: number | string; // 0 = EOA, 1 = Poly Proxy, 2 = Gnosis Safe (Can also use 'eoa', 'polyproxy', 'gnosis_safe')
2929
funderAddress?: string; // The address funding the trades (defaults to signer address)
3030
}
3131

core/src/exchanges/limitless/auth.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ export class LimitlessAuth {
8181
return creds;
8282
}
8383

84+
/**
85+
* Maps human-readable signature type names to their numeric values.
86+
*/
87+
private mapSignatureType(type: number | string | undefined | null): number {
88+
if (type === undefined || type === null) return 0;
89+
if (typeof type === 'number') return type;
90+
91+
const normalized = type.toLowerCase().replace(/[^a-z0-9]/g, '');
92+
switch (normalized) {
93+
case 'eoa':
94+
return 0;
95+
case 'polyproxy':
96+
case 'polymarketproxy':
97+
return 1;
98+
case 'gnosissafe':
99+
case 'safe':
100+
return 2;
101+
default:
102+
const parsed = parseInt(normalized);
103+
return isNaN(parsed) ? 0 : parsed;
104+
}
105+
}
106+
84107
/**
85108
* Get an authenticated CLOB client for L2 operations (trading).
86109
* This client can place orders, cancel orders, query positions, etc.
@@ -95,7 +118,7 @@ export class LimitlessAuth {
95118
const apiCreds = await this.getApiCredentials();
96119

97120
// Determine signature type (default to EOA = 0)
98-
const signatureType = this.credentials.signatureType ?? 0;
121+
const signatureType = this.mapSignatureType(this.credentials.signatureType);
99122

100123
// Determine funder address (defaults to signer's address)
101124
const funderAddress = this.credentials.funderAddress ?? this.signer!.address;
@@ -106,7 +129,7 @@ export class LimitlessAuth {
106129
BASE_CHAIN_ID as any,
107130
this.signer,
108131
apiCreds,
109-
signatureType,
132+
signatureType as any,
110133
funderAddress
111134
);
112135

core/src/exchanges/polymarket/auth.ts

Lines changed: 124 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ClobClient } from '@polymarket/clob-client';
22
import type { ApiKeyCreds } from '@polymarket/clob-client';
33
import { Wallet } from 'ethers';
4+
import axios from 'axios';
45
import { ExchangeCredentials } from '../../BaseExchange';
56

67
const POLYMARKET_HOST = 'https://clob.polymarket.com';
@@ -15,6 +16,8 @@ export class PolymarketAuth {
1516
private signer?: Wallet;
1617
private clobClient?: ClobClient;
1718
private apiCreds?: ApiKeyCreds;
19+
private discoveredProxyAddress?: string;
20+
private discoveredSignatureType?: number;
1821

1922
constructor(credentials: ExchangeCredentials) {
2023
this.credentials = credentials;
@@ -63,24 +66,100 @@ export class PolymarketAuth {
6366
try {
6467
// console.log('Trying to derive existing API key...');
6568
creds = await l1Client.deriveApiKey();
69+
if (!creds || !creds.key || !creds.secret || !creds.passphrase) {
70+
// If derived creds are missing, throw to trigger catch -> create
71+
throw new Error("Derived credentials are incomplete/empty");
72+
}
6673
} catch (deriveError: any) {
67-
// console.log('Derivation failed, trying to create new API key...');
74+
console.log('[PolymarketAuth] Derivation failed:', deriveError.message || deriveError);
75+
console.log('[PolymarketAuth] Attempting to create new API key...');
6876
try {
6977
creds = await l1Client.createApiKey();
78+
console.log('[PolymarketAuth] createApiKey returned:', JSON.stringify(creds, null, 2));
7079
} catch (createError: any) {
71-
console.error('Failed to both derive and create API key:', createError?.message || createError);
72-
throw new Error('Authentication failed: Could not create or derive API key.');
80+
const apiError = createError?.response?.data?.error || createError?.message || createError;
81+
console.error('[PolymarketAuth] Failed to both derive and create API key. Create error:', apiError);
82+
throw new Error(`Authentication failed: Could not create or derive API key. Latest error: ${apiError}`);
7383
}
7484
}
7585

76-
if (!creds) {
77-
throw new Error('Authentication failed: Credentials are empty.');
86+
if (!creds || !creds.key || !creds.secret || !creds.passphrase) {
87+
console.error('[PolymarketAuth] Incomplete credentials:', { hasKey: !!creds?.key, hasSecret: !!creds?.secret, hasPassphrase: !!creds?.passphrase });
88+
throw new Error('Authentication failed: Derived credentials are incomplete.');
7889
}
7990

91+
console.log(`[PolymarketAuth] Successfully obtained API credentials for key ${creds.key.substring(0, 8)}...`);
8092
this.apiCreds = creds;
8193
return creds;
8294
}
8395

96+
/**
97+
* Discover the proxy address and signature type for the signer.
98+
*/
99+
async discoverProxy(): Promise<{ proxyAddress: string; signatureType: number }> {
100+
if (this.discoveredProxyAddress) {
101+
return {
102+
proxyAddress: this.discoveredProxyAddress,
103+
signatureType: this.discoveredSignatureType ?? 0
104+
};
105+
}
106+
107+
const address = this.signer!.address;
108+
try {
109+
// Polymarket Data API / Profiles endpoint
110+
// Path-based: https://data-api.polymarket.com/profiles/0x...
111+
const response = await axios.get(`https://data-api.polymarket.com/profiles/${address}`);
112+
const profile = response.data;
113+
console.log(`[PolymarketAuth] Profile for ${address}:`, JSON.stringify(profile));
114+
115+
if (profile && profile.proxyAddress) {
116+
this.discoveredProxyAddress = profile.proxyAddress;
117+
// Determine signature type.
118+
// Polymarket usually uses 1 for their own proxy and 2 for Gnosis Safe (which is what their new profiles use).
119+
// If it's a proxy address but we don't know the type, 1 is a safe default for Polymarket.
120+
this.discoveredSignatureType = profile.isGnosisSafe ? 2 : 1;
121+
122+
console.log(`[PolymarketAuth] Auto-discovered proxy for ${address}: ${this.discoveredProxyAddress} (Type: ${this.discoveredSignatureType})`);
123+
return {
124+
proxyAddress: this.discoveredProxyAddress as string,
125+
signatureType: this.discoveredSignatureType as number
126+
};
127+
}
128+
} catch (error) {
129+
console.warn(`[PolymarketAuth] Could not auto-discover proxy for ${address}:`, error instanceof Error ? error.message : error);
130+
}
131+
132+
// Fallback to EOA if discovery fails
133+
return {
134+
proxyAddress: address,
135+
signatureType: 0
136+
};
137+
}
138+
139+
/**
140+
* Maps human-readable signature type names to their numeric values.
141+
*/
142+
private mapSignatureType(type: number | string | undefined | null): number {
143+
if (type === undefined || type === null) return 0;
144+
if (typeof type === 'number') return type;
145+
146+
const normalized = type.toLowerCase().replace(/[^a-z0-9]/g, '');
147+
switch (normalized) {
148+
case 'eoa':
149+
return 0;
150+
case 'polyproxy':
151+
case 'polymarketproxy':
152+
return 1;
153+
case 'gnosissafe':
154+
case 'safe':
155+
return 2;
156+
default:
157+
// If it's a numeric string, parse it
158+
const parsed = parseInt(normalized);
159+
return isNaN(parsed) ? 0 : parsed;
160+
}
161+
}
162+
84163
/**
85164
* Get an authenticated CLOB client for L2 operations (trading).
86165
* This client can place orders, cancel orders, query positions, etc.
@@ -91,28 +170,61 @@ export class PolymarketAuth {
91170
return this.clobClient;
92171
}
93172

173+
// 1. Determine proxy and signature type early
174+
let proxyAddress = this.credentials.funderAddress || undefined;
175+
let signatureType = this.mapSignatureType(this.credentials.signatureType);
176+
177+
if (!proxyAddress) {
178+
const discovered = await this.discoverProxy();
179+
proxyAddress = discovered.proxyAddress;
180+
if (this.credentials.signatureType === undefined || this.credentials.signatureType === null) {
181+
signatureType = discovered.signatureType;
182+
}
183+
}
184+
94185
// Get API credentials (L1 auth)
186+
// Pass signature type if we know it (some accounts need it for derivation?)
95187
const apiCreds = await this.getApiCredentials();
96188

97-
// Determine signature type (default to EOA = 0)
98-
const signatureType = this.credentials.signatureType ?? 0;
99-
100-
// Determine funder address (defaults to signer's address)
101-
const funderAddress = this.credentials.funderAddress ?? this.signer!.address;
189+
// 3. Defaults
190+
const signerAddress = this.signer!.address;
191+
const finalProxyAddress: string = (proxyAddress || signerAddress) as string;
192+
const finalSignatureType: number = signatureType;
102193

103194
// Create L2-authenticated client
195+
console.log(`[PolymarketAuth] Initializing ClobClient | Signer: ${signerAddress} | Funder: ${finalProxyAddress} | SigType: ${finalSignatureType}`);
196+
104197
this.clobClient = new ClobClient(
105198
POLYMARKET_HOST,
106199
POLYGON_CHAIN_ID,
107200
this.signer,
108201
apiCreds,
109-
signatureType,
110-
funderAddress
202+
finalSignatureType,
203+
finalProxyAddress
111204
);
112205

113206
return this.clobClient;
114207
}
115208

209+
/**
210+
* Get the funder address (Proxy) if available.
211+
* Note: This is an async-safe getter if discovery is needed.
212+
*/
213+
async getEffectiveFunderAddress(): Promise<string> {
214+
if (this.credentials.funderAddress) {
215+
return this.credentials.funderAddress;
216+
}
217+
const discovered = await this.discoverProxy();
218+
return discovered.proxyAddress;
219+
}
220+
221+
/**
222+
* Synchronous getter for credentials funder address.
223+
*/
224+
getFunderAddress(): string {
225+
return this.credentials.funderAddress || this.signer!.address;
226+
}
227+
116228
/**
117229
* Get the signer's address.
118230
*/

0 commit comments

Comments
 (0)