@puul/partner-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -0
- package/dist/index.d.mts +339 -0
- package/dist/index.d.ts +339 -0
- package/dist/index.js +280 -0
- package/dist/index.mjs +248 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# @puul/partner-sdk
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for the **Puul Partner API** — integrate prediction markets into your platform with type safety and zero configuration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Auto-authentication** — OAuth2 token management handled for you
|
|
8
|
+
- 📦 **Zero runtime dependencies** — uses native `fetch` (Node 18+)
|
|
9
|
+
- 🎯 **Fully typed** — complete TypeScript definitions for all API shapes
|
|
10
|
+
- 🪝 **Webhook helpers** — signature verification & typed event parsing
|
|
11
|
+
- ⚡ **Dual output** — ESM + CommonJS builds
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @puul/partner-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { PuulPartner } from '@puul/partner-sdk';
|
|
23
|
+
|
|
24
|
+
const puul = new PuulPartner({
|
|
25
|
+
clientId: 'pk_live_abc123',
|
|
26
|
+
clientSecret: 'sk_live_secret789',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// List live markets
|
|
30
|
+
const markets = await puul.markets.list();
|
|
31
|
+
console.log(markets[0].question);
|
|
32
|
+
// → "Will Bitcoin reach $100k by March?"
|
|
33
|
+
|
|
34
|
+
// Place a prediction
|
|
35
|
+
const prediction = await puul.predictions.place({
|
|
36
|
+
marketId: markets[0].id,
|
|
37
|
+
outcomeId: markets[0].outcomes[0].id,
|
|
38
|
+
stakeAmount: 100000, // ₦1,000 (minor units)
|
|
39
|
+
stakeCurrency: 'NGN',
|
|
40
|
+
idempotencyKey: `pred_${Date.now()}`,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API Reference
|
|
45
|
+
|
|
46
|
+
### Authentication
|
|
47
|
+
|
|
48
|
+
The SDK automatically manages OAuth2 tokens. You never need to call the auth endpoint directly.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const puul = new PuulPartner({
|
|
52
|
+
clientId: 'pk_live_abc123',
|
|
53
|
+
clientSecret: 'sk_live_secret789',
|
|
54
|
+
baseUrl: 'https://api.joinpuul.com/api/v1', // optional
|
|
55
|
+
timeoutMs: 30000, // optional
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### User Linking
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// 1. Create a link token (server-side)
|
|
63
|
+
const linkToken = await puul.sessions.createLinkToken({
|
|
64
|
+
externalUserId: 'your-user-id-123',
|
|
65
|
+
email: 'user@example.com', // optional
|
|
66
|
+
countryCode: 'NG', // optional
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 2. Exchange link token for a session
|
|
70
|
+
const session = await puul.sessions.create(linkToken.linkToken);
|
|
71
|
+
// session.access_token is scoped to the linked user
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Markets
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// All live markets
|
|
78
|
+
const markets = await puul.markets.list();
|
|
79
|
+
|
|
80
|
+
// Filter by country
|
|
81
|
+
const ngMarkets = await puul.markets.list(['NG', 'GH']);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Predictions
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Optional: Lock in odds with a quote (10s expiry)
|
|
88
|
+
const quote = await puul.predictions.createQuote({
|
|
89
|
+
marketId: 'market-uuid',
|
|
90
|
+
outcomeId: 'outcome-uuid',
|
|
91
|
+
stakeAmount: 50000,
|
|
92
|
+
stakeCurrency: 'NGN',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Place prediction (with or without quote)
|
|
96
|
+
const prediction = await puul.predictions.place({
|
|
97
|
+
marketId: 'market-uuid',
|
|
98
|
+
outcomeId: 'outcome-uuid',
|
|
99
|
+
stakeAmount: 50000,
|
|
100
|
+
stakeCurrency: 'NGN',
|
|
101
|
+
idempotencyKey: 'unique-key',
|
|
102
|
+
quoteId: quote.quoteId, // optional
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Get prediction details
|
|
106
|
+
const details = await puul.predictions.get(prediction.id);
|
|
107
|
+
|
|
108
|
+
// List user predictions
|
|
109
|
+
const history = await puul.predictions.list({ status: 'OPEN', limit: 20 });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Pending Predictions (JIT Funding)
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Create a pending prediction — returns bank payment details
|
|
116
|
+
const pending = await puul.predictions.createPending({
|
|
117
|
+
marketId: 'market-uuid',
|
|
118
|
+
outcomeId: 'outcome-uuid',
|
|
119
|
+
stakeAmount: 500000,
|
|
120
|
+
stakeCurrency: 'NGN',
|
|
121
|
+
userExternalId: 'your-user-id',
|
|
122
|
+
idempotencyKey: 'unique-key',
|
|
123
|
+
});
|
|
124
|
+
// pending.paymentReference — use this in the bank transfer
|
|
125
|
+
|
|
126
|
+
// Check status
|
|
127
|
+
const status = await puul.predictions.getPending(pending.id);
|
|
128
|
+
// status.status: 'pending' → 'funded' → 'placed'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Wallet
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
const balance = await puul.wallet.getBalance();
|
|
135
|
+
const omnibus = await puul.wallet.getOmnibusBalance('USDC');
|
|
136
|
+
|
|
137
|
+
await puul.wallet.deposit({
|
|
138
|
+
amount: 5000, // $50.00 in minor units
|
|
139
|
+
currency: 'USDC',
|
|
140
|
+
idempotency_key: 'dep_unique_123',
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Webhook Verification
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { verifyWebhookSignature, parseWebhookEvent } from '@puul/partner-sdk';
|
|
148
|
+
|
|
149
|
+
app.post('/webhooks/puul', express.raw({ type: '*/*' }), (req, res) => {
|
|
150
|
+
const isValid = verifyWebhookSignature(
|
|
151
|
+
req.body.toString(),
|
|
152
|
+
req.headers['x-puul-signature'] as string,
|
|
153
|
+
process.env.PUUL_WEBHOOK_SECRET!,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!isValid) {
|
|
157
|
+
return res.status(401).send('Invalid signature');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const event = parseWebhookEvent(JSON.parse(req.body.toString()));
|
|
161
|
+
|
|
162
|
+
switch (event.event) {
|
|
163
|
+
case 'prediction.settled':
|
|
164
|
+
console.log('Prediction settled:', event.data);
|
|
165
|
+
break;
|
|
166
|
+
case 'deposit.confirmed':
|
|
167
|
+
console.log('Deposit confirmed:', event.data);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
res.status(200).send('OK');
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Error Handling
|
|
176
|
+
|
|
177
|
+
All API errors throw a `PuulError` with structured details:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { PuulError } from '@puul/partner-sdk';
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await puul.predictions.place(params);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error instanceof PuulError) {
|
|
186
|
+
console.error(error.code); // 'INSUFFICIENT_BALANCE'
|
|
187
|
+
console.error(error.message); // 'Insufficient wallet balance'
|
|
188
|
+
console.error(error.statusCode); // 400
|
|
189
|
+
console.error(error.requestId); // 'req_abc123'
|
|
190
|
+
console.error(error.retryable); // false
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Supported Currencies
|
|
196
|
+
|
|
197
|
+
| Code | Currency |
|
|
198
|
+
|------|----------|
|
|
199
|
+
| `NGN` | Nigerian Naira |
|
|
200
|
+
| `USDC` | USD Coin |
|
|
201
|
+
| `USDT` | Tether |
|
|
202
|
+
| `KES` | Kenyan Shilling |
|
|
203
|
+
| `GHS` | Ghanaian Cedi |
|
|
204
|
+
| `ZAR` | South African Rand |
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
interface PuulPartnerConfig {
|
|
2
|
+
/** Your partner client ID */
|
|
3
|
+
clientId: string;
|
|
4
|
+
/** Your partner client secret */
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
/** Base URL for the Puul API. Defaults to https://api.joinpuul.com/api/v1 */
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
/** Request timeout in ms. Defaults to 30000 */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
interface AccessTokenResponse {
|
|
12
|
+
access_token: string;
|
|
13
|
+
token_type: string;
|
|
14
|
+
expires_in: number;
|
|
15
|
+
}
|
|
16
|
+
interface CreateLinkTokenParams {
|
|
17
|
+
externalUserId: string;
|
|
18
|
+
email?: string;
|
|
19
|
+
phone?: string;
|
|
20
|
+
countryCode?: string;
|
|
21
|
+
}
|
|
22
|
+
interface LinkTokenResponse {
|
|
23
|
+
linkToken: string;
|
|
24
|
+
expiresAt: string;
|
|
25
|
+
}
|
|
26
|
+
interface SessionResponse {
|
|
27
|
+
access_token: string;
|
|
28
|
+
token_type: string;
|
|
29
|
+
expires_in: number;
|
|
30
|
+
user_id: string;
|
|
31
|
+
}
|
|
32
|
+
interface MarketOutcome {
|
|
33
|
+
id: string;
|
|
34
|
+
slug: string;
|
|
35
|
+
label: string;
|
|
36
|
+
pool: {
|
|
37
|
+
pool_amount: string;
|
|
38
|
+
total_bets: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
interface Market {
|
|
42
|
+
id: string;
|
|
43
|
+
question: string;
|
|
44
|
+
description: string;
|
|
45
|
+
category: string;
|
|
46
|
+
status: string;
|
|
47
|
+
currency: string;
|
|
48
|
+
close_time: string;
|
|
49
|
+
freeze_at: string | null;
|
|
50
|
+
target_countries: string[];
|
|
51
|
+
outcomes: MarketOutcome[];
|
|
52
|
+
}
|
|
53
|
+
type PredictionStatus = 'OPEN' | 'WON' | 'LOST' | 'VOIDED';
|
|
54
|
+
type Currency = 'NGN' | 'USDC' | 'USDT' | 'KES' | 'GHS' | 'ZAR';
|
|
55
|
+
interface PlacePredictionParams {
|
|
56
|
+
marketId: string;
|
|
57
|
+
outcomeId: string;
|
|
58
|
+
stakeAmount: number;
|
|
59
|
+
stakeCurrency: Currency;
|
|
60
|
+
idempotencyKey: string;
|
|
61
|
+
/** Optional quote ID for slippage protection */
|
|
62
|
+
quoteId?: string;
|
|
63
|
+
/** Optional FX quote ID */
|
|
64
|
+
fxQuoteId?: string;
|
|
65
|
+
/** Max slippage in basis points (100 = 1%) */
|
|
66
|
+
maxSlippageBps?: number;
|
|
67
|
+
}
|
|
68
|
+
interface CreateQuoteParams {
|
|
69
|
+
marketId: string;
|
|
70
|
+
outcomeId: string;
|
|
71
|
+
stakeAmount: number;
|
|
72
|
+
stakeCurrency: Currency;
|
|
73
|
+
}
|
|
74
|
+
interface QuoteResponse {
|
|
75
|
+
quoteId: string;
|
|
76
|
+
estimatedReturn: number;
|
|
77
|
+
multiplier: string;
|
|
78
|
+
expiresAt: string;
|
|
79
|
+
}
|
|
80
|
+
interface Prediction {
|
|
81
|
+
id: string;
|
|
82
|
+
market_id: string;
|
|
83
|
+
market_question: string | null;
|
|
84
|
+
market_close_time: string | null;
|
|
85
|
+
market_freeze_at: string | null;
|
|
86
|
+
outcome_id: string;
|
|
87
|
+
outcome_slug: string | null;
|
|
88
|
+
outcome_label: string | null;
|
|
89
|
+
user_external_id: string | null;
|
|
90
|
+
stake_amount: string;
|
|
91
|
+
stake_currency: string;
|
|
92
|
+
status: PredictionStatus;
|
|
93
|
+
estimated_return: string;
|
|
94
|
+
live_estimated_return: string | number;
|
|
95
|
+
estimated_multiplier: string;
|
|
96
|
+
final_return: string | null;
|
|
97
|
+
final_multiplier: string | null;
|
|
98
|
+
placed_at: string;
|
|
99
|
+
idempotency_key: string;
|
|
100
|
+
}
|
|
101
|
+
interface PredictionListResponse {
|
|
102
|
+
predictions: Prediction[];
|
|
103
|
+
total: number;
|
|
104
|
+
limit: number;
|
|
105
|
+
}
|
|
106
|
+
interface CreatePendingPredictionParams {
|
|
107
|
+
marketId: string;
|
|
108
|
+
outcomeId?: string;
|
|
109
|
+
/** @deprecated Use outcomeId for multi-outcome markets */
|
|
110
|
+
side?: 'YES' | 'NO';
|
|
111
|
+
stakeAmount: number;
|
|
112
|
+
stakeCurrency: Currency;
|
|
113
|
+
userExternalId: string;
|
|
114
|
+
idempotencyKey: string;
|
|
115
|
+
maxSlippageBps?: number;
|
|
116
|
+
/** TTL in seconds. Default: 300 */
|
|
117
|
+
ttlSeconds?: number;
|
|
118
|
+
}
|
|
119
|
+
interface PendingPrediction {
|
|
120
|
+
id: string;
|
|
121
|
+
status: string;
|
|
122
|
+
userExternalId: string;
|
|
123
|
+
marketId: string;
|
|
124
|
+
outcomeId: string | null;
|
|
125
|
+
side: string | null;
|
|
126
|
+
stakeAmount: number;
|
|
127
|
+
stakeCurrency: string;
|
|
128
|
+
paymentReference: string;
|
|
129
|
+
expiresAt: string;
|
|
130
|
+
predictionId: string | null;
|
|
131
|
+
createdAt: string;
|
|
132
|
+
fundedAt: string | null;
|
|
133
|
+
placedAt: string | null;
|
|
134
|
+
errorMessage: string | null;
|
|
135
|
+
}
|
|
136
|
+
interface WalletBalance {
|
|
137
|
+
balance: number;
|
|
138
|
+
currency: string;
|
|
139
|
+
}
|
|
140
|
+
interface DepositAccountInfo {
|
|
141
|
+
accountNumber: string;
|
|
142
|
+
accountName: string;
|
|
143
|
+
bankName: string;
|
|
144
|
+
currency: string;
|
|
145
|
+
}
|
|
146
|
+
interface DepositParams {
|
|
147
|
+
amount: number;
|
|
148
|
+
currency?: string;
|
|
149
|
+
idempotency_key: string;
|
|
150
|
+
reference?: string;
|
|
151
|
+
}
|
|
152
|
+
type WebhookEventType = 'prediction.settled' | 'deposit.confirmed' | 'withdrawal.completed';
|
|
153
|
+
interface WebhookEvent<T = {
|
|
154
|
+
[key: string]: unknown;
|
|
155
|
+
}> {
|
|
156
|
+
event: WebhookEventType;
|
|
157
|
+
timestamp: string;
|
|
158
|
+
data: T;
|
|
159
|
+
}
|
|
160
|
+
interface PredictionSettledData {
|
|
161
|
+
prediction_id: string;
|
|
162
|
+
market_id: string;
|
|
163
|
+
outcome_id: string;
|
|
164
|
+
outcome_slug: string;
|
|
165
|
+
user_external_id: string;
|
|
166
|
+
status: 'WON' | 'LOST' | 'VOIDED';
|
|
167
|
+
stake_amount: string;
|
|
168
|
+
stake_currency: string;
|
|
169
|
+
final_return: string;
|
|
170
|
+
settled_at: string;
|
|
171
|
+
}
|
|
172
|
+
interface PuulApiError {
|
|
173
|
+
statusCode: number;
|
|
174
|
+
message: string | string[];
|
|
175
|
+
code: string;
|
|
176
|
+
requestId?: string;
|
|
177
|
+
details?: unknown;
|
|
178
|
+
retryable?: boolean;
|
|
179
|
+
}
|
|
180
|
+
declare class PuulError extends Error {
|
|
181
|
+
readonly statusCode: number;
|
|
182
|
+
readonly code: string;
|
|
183
|
+
readonly requestId?: string;
|
|
184
|
+
readonly details?: unknown;
|
|
185
|
+
readonly retryable: boolean;
|
|
186
|
+
constructor(apiError: PuulApiError);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Official Puul Partner SDK client.
|
|
191
|
+
*
|
|
192
|
+
* Zero runtime dependencies — uses native `fetch` (Node 18+).
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* import { PuulPartner } from '@puul/partner-sdk';
|
|
197
|
+
*
|
|
198
|
+
* const puul = new PuulPartner({
|
|
199
|
+
* clientId: 'pk_live_abc123',
|
|
200
|
+
* clientSecret: 'sk_live_secret789',
|
|
201
|
+
* });
|
|
202
|
+
*
|
|
203
|
+
* // List markets
|
|
204
|
+
* const markets = await puul.markets.list();
|
|
205
|
+
*
|
|
206
|
+
* // Place a prediction
|
|
207
|
+
* const prediction = await puul.predictions.place({
|
|
208
|
+
* marketId: markets[0].id,
|
|
209
|
+
* outcomeId: markets[0].outcomes[0].id,
|
|
210
|
+
* stakeAmount: 100000,
|
|
211
|
+
* stakeCurrency: 'NGN',
|
|
212
|
+
* idempotencyKey: 'unique-key-123',
|
|
213
|
+
* });
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
declare class PuulPartner {
|
|
217
|
+
private readonly config;
|
|
218
|
+
private tokenCache;
|
|
219
|
+
readonly sessions: SessionsAPI;
|
|
220
|
+
readonly markets: MarketsAPI;
|
|
221
|
+
readonly predictions: PredictionsAPI;
|
|
222
|
+
readonly wallet: WalletAPI;
|
|
223
|
+
constructor(config: PuulPartnerConfig);
|
|
224
|
+
/** Get a valid access token, refreshing if needed */
|
|
225
|
+
getAccessToken(): Promise<string>;
|
|
226
|
+
/** Authenticated request */
|
|
227
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
228
|
+
/** Raw HTTP request */
|
|
229
|
+
private rawRequest;
|
|
230
|
+
}
|
|
231
|
+
declare class SessionsAPI {
|
|
232
|
+
private readonly client;
|
|
233
|
+
constructor(client: PuulPartner);
|
|
234
|
+
/** Create a link token for user linking */
|
|
235
|
+
createLinkToken(params: CreateLinkTokenParams): Promise<LinkTokenResponse>;
|
|
236
|
+
/** Exchange a link token for a user session */
|
|
237
|
+
create(linkToken: string): Promise<SessionResponse>;
|
|
238
|
+
}
|
|
239
|
+
declare class MarketsAPI {
|
|
240
|
+
private readonly client;
|
|
241
|
+
constructor(client: PuulPartner);
|
|
242
|
+
/** List all live markets, optionally filtered by country */
|
|
243
|
+
list(countries?: string[]): Promise<Market[]>;
|
|
244
|
+
}
|
|
245
|
+
declare class PredictionsAPI {
|
|
246
|
+
private readonly client;
|
|
247
|
+
constructor(client: PuulPartner);
|
|
248
|
+
/** Create a binding quote for slippage protection (expires in 10s) */
|
|
249
|
+
createQuote(params: CreateQuoteParams): Promise<QuoteResponse>;
|
|
250
|
+
/** Place a prediction on a market outcome */
|
|
251
|
+
place(params: PlacePredictionParams): Promise<Prediction>;
|
|
252
|
+
/** Get a specific prediction by ID */
|
|
253
|
+
get(predictionId: string): Promise<Prediction>;
|
|
254
|
+
/** List user predictions with optional filters */
|
|
255
|
+
list(options?: {
|
|
256
|
+
status?: PredictionStatus;
|
|
257
|
+
limit?: number;
|
|
258
|
+
}): Promise<PredictionListResponse>;
|
|
259
|
+
/** Create a pending prediction (JIT funding flow) */
|
|
260
|
+
createPending(params: CreatePendingPredictionParams): Promise<PendingPrediction>;
|
|
261
|
+
/** Get pending prediction status */
|
|
262
|
+
getPending(pendingId: string): Promise<PendingPrediction>;
|
|
263
|
+
}
|
|
264
|
+
declare class WalletAPI {
|
|
265
|
+
private readonly client;
|
|
266
|
+
constructor(client: PuulPartner);
|
|
267
|
+
/** Get partner deposit account details */
|
|
268
|
+
getDepositAccount(): Promise<DepositAccountInfo>;
|
|
269
|
+
/** Get user wallet balance */
|
|
270
|
+
getBalance(): Promise<WalletBalance>;
|
|
271
|
+
/** Get omnibus wallet balance */
|
|
272
|
+
getOmnibusBalance(currency?: string): Promise<WalletBalance>;
|
|
273
|
+
/** Deposit funds to a linked user wallet */
|
|
274
|
+
deposit(params: DepositParams): Promise<unknown>;
|
|
275
|
+
/** Get withdrawal fee estimate */
|
|
276
|
+
getWithdrawalFees(currency: string, amount: number): Promise<unknown>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Verify a webhook signature from Puul.
|
|
281
|
+
*
|
|
282
|
+
* @param payload - The raw request body as a string
|
|
283
|
+
* @param signature - The value of the X-Puul-Signature header
|
|
284
|
+
* @param secret - Your webhook secret
|
|
285
|
+
* @returns true if the signature is valid
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```typescript
|
|
289
|
+
* import { verifyWebhookSignature } from '@puul/partner-sdk';
|
|
290
|
+
*
|
|
291
|
+
* app.post('/webhooks/puul', (req, res) => {
|
|
292
|
+
* const isValid = verifyWebhookSignature(
|
|
293
|
+
* req.body, // raw string body
|
|
294
|
+
* req.headers['x-puul-signature'],
|
|
295
|
+
* process.env.PUUL_WEBHOOK_SECRET,
|
|
296
|
+
* );
|
|
297
|
+
*
|
|
298
|
+
* if (!isValid) {
|
|
299
|
+
* return res.status(401).send('Invalid signature');
|
|
300
|
+
* }
|
|
301
|
+
*
|
|
302
|
+
* // Process the event...
|
|
303
|
+
* res.status(200).send('OK');
|
|
304
|
+
* });
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
declare function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean;
|
|
308
|
+
/**
|
|
309
|
+
* Parse and type a webhook event body.
|
|
310
|
+
*
|
|
311
|
+
* @param body - The parsed JSON body of the webhook request
|
|
312
|
+
* @returns A typed WebhookEvent object
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* import { parseWebhookEvent } from '@puul/partner-sdk';
|
|
317
|
+
*
|
|
318
|
+
* const event = parseWebhookEvent(req.body);
|
|
319
|
+
*
|
|
320
|
+
* if (event.event === 'prediction.settled') {
|
|
321
|
+
* const { prediction_id, status, final_return } = event.data;
|
|
322
|
+
* // Handle settlement...
|
|
323
|
+
* }
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
declare function parseWebhookEvent(body: unknown): WebhookEvent;
|
|
327
|
+
/**
|
|
328
|
+
* Type guard for prediction.settled events.
|
|
329
|
+
* Returns true if the event type is 'prediction.settled'.
|
|
330
|
+
* Use this to narrow the type before accessing `.data` as PredictionSettledData.
|
|
331
|
+
*/
|
|
332
|
+
declare function isPredictionSettledEvent(event: WebhookEvent): boolean;
|
|
333
|
+
/**
|
|
334
|
+
* Convenience cast for prediction.settled event data.
|
|
335
|
+
* Call after verifying with `isPredictionSettledEvent`.
|
|
336
|
+
*/
|
|
337
|
+
declare function asPredictionSettledData(event: WebhookEvent): PredictionSettledData;
|
|
338
|
+
|
|
339
|
+
export { type AccessTokenResponse, type CreateLinkTokenParams, type CreatePendingPredictionParams, type CreateQuoteParams, type Currency, type DepositAccountInfo, type DepositParams, type LinkTokenResponse, type Market, type MarketOutcome, type PendingPrediction, type PlacePredictionParams, type Prediction, type PredictionListResponse, type PredictionSettledData, type PredictionStatus, type PuulApiError, PuulError, PuulPartner, type PuulPartnerConfig, type QuoteResponse, type SessionResponse, type WalletBalance, type WebhookEvent, type WebhookEventType, asPredictionSettledData, isPredictionSettledEvent, parseWebhookEvent, verifyWebhookSignature };
|