// Session fields (from src/lib/auth.ts:357-371)
session: {
modelName: 'sessions',
cookieCache: {
enabled: true,
maxAge: 5 * 60, // 5 minutes
},
fields: {
userId: 'user_id',
token: 'token',
expiresAt: 'expires_at',
ipAddress: 'ip_address',
userAgent: 'user_agent',
createdAt: 'created_at',
updatedAt: 'updated_at',
},
}
```typescript
### Getting Current User
Authenticated endpoints check for a valid session:
<CodeGroup>
```typescript Example: Notifications Endpoint
// src/app/api/notifications/route.ts
import { UserRepository } from '@/lib/db/queries/user'
export async function GET() {
const user = await UserRepository.getCurrentUser()
if (!user) {
return NextResponse.json(
{ error: 'Unauthenticated.' },
{ status: 401 }
)
}
// Proceed with authenticated request
const { data: notifications } = await NotificationRepository.getByUserId(user.id)
return NextResponse.json(notifications)
}
```typescript
```typescript Example: Events Endpoint
// src/app/api/events/route.ts
export async function GET(request: Request) {
// User is optional - returns public data if not authenticated
const user = await UserRepository.getCurrentUser()
const userId = user?.id
const { data: events } = await EventRepository.listEvents({
userId, // Used for bookmarks and personalization
tag,
search,
status,
})
return NextResponse.json(events)
}
```typescript
</CodeGroup>
## HMAC Signature Authentication
The CLOB API requires HMAC-SHA256 signatures for authenticated requests. This is used for trading operations, order management, and position queries.
### HMAC Signature Process
<Steps>
<Step title="Get Trading Credentials">
Users must first obtain API credentials from [auth.kuest.com](https://auth.kuest.com):
- `KUEST_API_KEY` - Public API key identifier
- `KUEST_API_SECRET` - Secret key (base64 encoded)
- `KUEST_PASSPHRASE` - Additional passphrase
</Step>
<Step title="Build Signature String">
Concatenate timestamp, HTTP method, path, and body (if present)
</Step>
<Step title="Sign with HMAC-SHA256">
Use the base64-decoded secret to sign the message
</Step>
<Step title="Encode as URL-safe Base64">
Replace `+` with `-` and `/` with `_`
</Step>
</Steps>
### Implementation
The HMAC signing logic is in `src/lib/hmac.ts`:
<CodeGroup>
```typescript HMAC Signing Function
// src/lib/hmac.ts
import crypto from 'node:crypto'
import { Buffer } from 'node:buffer'
export function buildClobHmacSignature(
secret: string,
timestamp: number,
method: string,
requestPath: string,
body?: string
): string {
// For GET requests, strip query parameters from path
const normalizedPath = method === 'GET'
? requestPath.split('?')[0]
: requestPath
// Build message: timestamp + method + path + body
let message = timestamp + method + normalizedPath
if (body !== undefined) {
message += body
}
// Decode base64 secret
const base64Secret = Buffer.from(secret, 'base64')
// Create HMAC-SHA256 signature
const hmac = crypto.createHmac('sha256', base64Secret)
const sig = hmac.update(message).digest('base64')
// URL-safe base64 encoding
return sig.replace(/\+/g, '-').replace(/\//g, '_')
}
```typescript
```typescript Usage Example
// src/app/api/open-orders/route.ts
import { buildClobHmacSignature } from '@/lib/hmac'
const CLOB_URL = process.env.CLOB_URL
async function fetchClobOpenOrders({
auth,
userAddress,
makerAddress,
market,
}: {
auth: { key: string, secret: string, passphrase: string }
userAddress: string
makerAddress?: string
market?: string
}) {
// Build query parameters
const params = new URLSearchParams()
if (makerAddress) params.set('maker', makerAddress)
if (market) params.set('market', market)
const path = `/data/orders?${params.toString()}`
// Generate timestamp (Unix seconds)
const timestamp = Math.floor(Date.now() / 1000)
// Build HMAC signature
const signature = buildClobHmacSignature(
auth.secret,
timestamp,
'GET',
path
)
// Make authenticated request
const response = await fetch(`${CLOB_URL}${path}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'KUEST_ADDRESS': userAddress,
'KUEST_API_KEY': auth.key,
'KUEST_PASSPHRASE': auth.passphrase,
'KUEST_TIMESTAMP': timestamp.toString(),
'KUEST_SIGNATURE': signature,
},
})
return await response.json()
}
```typescript
</CodeGroup>
### Required Headers
All CLOB API requests must include these headers:
| Header | Description | Example |
|--------|-------------|----------|
| `KUEST_ADDRESS` | User's EVM wallet address | `0x1234...5678` |
| `KUEST_API_KEY` | Public API key | `abc123...` |
| `KUEST_PASSPHRASE` | API passphrase | `my-passphrase` |
| `KUEST_TIMESTAMP` | Current Unix timestamp (seconds) | `1709481600` |
| `KUEST_SIGNATURE` | HMAC-SHA256 signature | `abcd1234_efgh5678...` |
### Signature Examples
<CodeGroup>
```typescript GET Request
// GET /data/orders?maker=0x1234
const timestamp = 1709481600
const method = 'GET'
const path = '/data/orders' // Query params stripped for GET
const body = undefined
const message = `${timestamp}${method}${path}`
// "1709481600GET/data/orders"
const signature = buildClobHmacSignature(secret, timestamp, method, path)
```typescript
```typescript POST Request
// POST /orders with JSON body
const timestamp = 1709481600
const method = 'POST'
const path = '/orders'
const body = JSON.stringify({
market: '0xabc...',
price: 0.75,
size: 100
})
const message = `${timestamp}${method}${path}${body}`
// "1709481600POST/orders{\"market\":\"0xabc...\",...}"
const signature = buildClobHmacSignature(secret, timestamp, method, path, body)
```typescript
</CodeGroup>
## Wallet Authentication (SIWE)
Users authenticate using Sign-In with Ethereum (SIWE), implemented via Better Auth and Reown AppKit.
### Configuration
Wallet authentication is configured in `src/lib/auth.ts`:
```typescript
// src/lib/auth.ts:256-290
import { siwe } from 'better-auth/plugins'
import { getChainIdFromMessage } from '@reown/appkit-siwe'
import { createPublicClient, http } from 'viem'
export const auth = betterAuth({
// ... other config
plugins: [
siwe({
schema: {
walletAddress: {
modelName: 'wallets',
fields: {
userId: 'user_id',
address: 'address',
chainId: 'chain_id',
isPrimary: 'is_primary',
createdAt: 'created_at',
},
},
},
domain: SIWE_DOMAIN, // e.g., 'kuest.com'
emailDomainName: SIWE_EMAIL_DOMAIN, // e.g., 'kuest.com'
anonymous: true,
getNonce: async () => generateRandomString(32),
verifyMessage: async ({ message, signature, address }) => {
const chainId = getChainIdFromMessage(message)
// Use Reown WalletConnect RPC
const publicClient = createPublicClient({
transport: http(
`https://rpc.walletconnect.org/v1/?chainId=${chainId}&projectId=${projectId}`
),
})
return await publicClient.verifyMessage({
message,
address: address as `0x${string}`,
signature: signature as `0x${string}`,
})
},
}),
],
})
```typescript
### SIWE Flow
<Steps>
<Step title="User connects wallet">
User clicks "Connect Wallet" and selects their Web3 wallet (MetaMask, WalletConnect, etc.)
</Step>
<Step title="Request nonce">
Frontend requests a random nonce from the server
</Step>
<Step title="Sign message">
User signs a SIWE message containing:
- Domain (e.g., `kuest.com`)
- User's wallet address
- Chain ID (e.g., Polygon)
- Nonce (prevents replay attacks)
- Timestamp
</Step>
<Step title="Verify signature">
Server verifies the signature using a public RPC node
</Step>
<Step title="Create session">
If valid, create a user account (if new) and start a session
</Step>
</Steps>
### User Model
Authenticated users have the following structure:
```typescript
// src/lib/auth.ts:311-352
user: {
modelName: 'users',
fields: {
name: 'address', // Primary wallet address
email: 'email',
emailVerified: 'email_verified',
image: 'image',
createdAt: 'created_at',
updatedAt: 'updated_at',
},
additionalFields: {
address: { type: 'string' },
username: { type: 'string' },
settings: { type: 'json' },
proxy_wallet_address: { type: 'string' },
proxy_wallet_signature: { type: 'string' },
proxy_wallet_status: { type: 'string' },
affiliate_code: { type: 'string' },
},
}
```typescript
## Trading Auth Storage
API credentials are stored encrypted in the user's settings. The system uses AES-256-GCM encryption.
### Storing Credentials
```typescript
// src/lib/trading-auth/server.ts
import { encryptSecret } from '@/lib/encryption'
export async function saveUserTradingAuthCredentials(
userId: string,
payload: {
relayer?: { key: string, secret: string, passphrase: string }
clob?: { key: string, secret: string, passphrase: string }
}
) {
// Encrypt each credential
const tradingAuth = {
relayer: payload.relayer ? {
key: encryptSecret(payload.relayer.key),
secret: encryptSecret(payload.relayer.secret),
passphrase: encryptSecret(payload.relayer.passphrase),
updatedAt: new Date().toISOString(),
} : undefined,
clob: payload.clob ? {
key: encryptSecret(payload.clob.key),
secret: encryptSecret(payload.clob.secret),
passphrase: encryptSecret(payload.clob.passphrase),
updatedAt: new Date().toISOString(),
} : undefined,
}
// Store in user.settings.tradingAuth
await db.update(users)
.set({ settings: { tradingAuth } })
.where(eq(users.id, userId))
}
```typescript
### Retrieving Credentials
```typescript
export async function getUserTradingAuthSecrets(
userId: string
): Promise<TradingAuthSecrets | null> {
const [row] = await db
.select({ settings: users.settings })
.from(users)
.where(eq(users.id, userId))
.limit(1)
const tradingAuth = row?.settings?.tradingAuth
if (!tradingAuth) return null
// Decrypt credentials
return {
relayer: decodeEntry(tradingAuth.relayer),
clob: decodeEntry(tradingAuth.clob),
}
}
function decodeEntry(entry?: TradingAuthSecretEntry) {
if (!entry) return undefined
return {
key: decryptSecret(entry.key),
secret: decryptSecret(entry.secret),
passphrase: decryptSecret(entry.passphrase),
}
}
```typescript
## Two-Factor Authentication
Users can enable TOTP-based 2FA for additional security.
### 2FA Configuration
```typescript
// src/lib/auth.ts:292-309
import { twoFactor } from 'better-auth/plugins'
export const auth = betterAuth({
plugins: [
twoFactor({
skipVerificationOnEnable: false,
schema: {
user: {
fields: {
twoFactorEnabled: 'two_factor_enabled',
},
},
twoFactor: {
modelName: 'two_factors',
fields: {
secret: 'secret',
backupCodes: 'backup_codes',
userId: 'user_id',
},
},
},
}),
],
})
```typescript
When 2FA is enabled, users must verify their TOTP code after wallet signature verification.
## Security Best Practices
<Warning>
Never expose your API secret or passphrase in client-side code. Always make authenticated requests from your backend.
</Warning>
### Credential Security
1. **Rotate credentials regularly** - Generate new API keys periodically
2. **Use environment variables** - Never hardcode credentials
3. **Encrypt at rest** - User credentials are encrypted using `BETTER_AUTH_SECRET`
4. **HTTPS only** - Always use secure connections for API requests
### Timestamp Validation
HMAC signatures include timestamps to prevent replay attacks:
```typescript
const timestamp = Math.floor(Date.now() / 1000)
// Timestamp must be within acceptable window (typically ±30 seconds)
if (Math.abs(currentTime - requestTime) > 30) {
throw new Error('Request timestamp too old or in future')
}
```typescript
### L2 Auth Context
The system uses "L2 Auth Context" cookies to validate trading sessions across devices:
```typescript
// When credentials are saved, create a unique context ID
const l2AuthContextId = createL2AuthContextId() // Random 32-byte hex
const contextHash = hashL2AuthContextId(l2AuthContextId)
// Store hash in user.settings.tradingAuth.l2Contexts
// Set cookie with the actual ID (not the hash)
// Max 5 contexts per user
```typescript
This ensures credentials are only accessible from devices where they were originally saved.
## Next Steps
<CardGroup cols={2}>
<Card title="List Markets" icon="list" href="/api/markets/list-markets">
Query available prediction markets
</Card>
<Card title="Place Orders" icon="cart-shopping" href="/api/trading/place-order">
Submit trades using HMAC authentication
</Card>
<Card title="Get Positions" icon="wallet" href="/api/positions/current-positions">
Check your current holdings
</Card>
<Card title="Integration Guide" icon="code" href="/integration/overview">
Build a trading bot with the API
</Card>
</CardGroup>