@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/dist/index.mjs
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var PuulError = class extends Error {
|
|
3
|
+
constructor(apiError) {
|
|
4
|
+
const msg = Array.isArray(apiError.message) ? apiError.message.join(", ") : apiError.message;
|
|
5
|
+
super(msg);
|
|
6
|
+
this.name = "PuulError";
|
|
7
|
+
this.statusCode = apiError.statusCode;
|
|
8
|
+
this.code = apiError.code;
|
|
9
|
+
this.requestId = apiError.requestId;
|
|
10
|
+
this.details = apiError.details;
|
|
11
|
+
this.retryable = apiError.retryable ?? false;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/client.ts
|
|
16
|
+
var DEFAULT_BASE_URL = "https://api.joinpuul.com/api/v1";
|
|
17
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
18
|
+
var PuulPartner = class {
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.tokenCache = null;
|
|
21
|
+
this.config = {
|
|
22
|
+
clientId: config.clientId,
|
|
23
|
+
clientSecret: config.clientSecret,
|
|
24
|
+
baseUrl: (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, ""),
|
|
25
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
26
|
+
};
|
|
27
|
+
this.sessions = new SessionsAPI(this);
|
|
28
|
+
this.markets = new MarketsAPI(this);
|
|
29
|
+
this.predictions = new PredictionsAPI(this);
|
|
30
|
+
this.wallet = new WalletAPI(this);
|
|
31
|
+
}
|
|
32
|
+
// ==========================================================
|
|
33
|
+
// Internal HTTP layer
|
|
34
|
+
// ==========================================================
|
|
35
|
+
/** Get a valid access token, refreshing if needed */
|
|
36
|
+
async getAccessToken() {
|
|
37
|
+
if (this.tokenCache && Date.now() < this.tokenCache.expiresAt - 3e4) {
|
|
38
|
+
return this.tokenCache.accessToken;
|
|
39
|
+
}
|
|
40
|
+
const response = await this.rawRequest(
|
|
41
|
+
"POST",
|
|
42
|
+
"/partner/oauth/token",
|
|
43
|
+
{
|
|
44
|
+
client_id: this.config.clientId,
|
|
45
|
+
client_secret: this.config.clientSecret,
|
|
46
|
+
grant_type: "client_credentials"
|
|
47
|
+
},
|
|
48
|
+
false
|
|
49
|
+
// Don't use auth for the auth endpoint itself
|
|
50
|
+
);
|
|
51
|
+
this.tokenCache = {
|
|
52
|
+
accessToken: response.access_token,
|
|
53
|
+
expiresAt: Date.now() + response.expires_in * 1e3
|
|
54
|
+
};
|
|
55
|
+
return this.tokenCache.accessToken;
|
|
56
|
+
}
|
|
57
|
+
/** Authenticated request */
|
|
58
|
+
async request(method, path, body) {
|
|
59
|
+
return this.rawRequest(method, path, body, true);
|
|
60
|
+
}
|
|
61
|
+
/** Raw HTTP request */
|
|
62
|
+
async rawRequest(method, path, body, authenticate = true) {
|
|
63
|
+
const url = `${this.config.baseUrl}${path}`;
|
|
64
|
+
const headers = {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
"Accept": "application/json"
|
|
67
|
+
};
|
|
68
|
+
if (authenticate) {
|
|
69
|
+
const token = await this.getAccessToken();
|
|
70
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
71
|
+
}
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(url, {
|
|
76
|
+
method,
|
|
77
|
+
headers,
|
|
78
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
79
|
+
signal: controller.signal
|
|
80
|
+
});
|
|
81
|
+
const responseBody = await response.text();
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(responseBody);
|
|
85
|
+
} catch {
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new PuulError({
|
|
88
|
+
statusCode: response.status,
|
|
89
|
+
message: responseBody || response.statusText,
|
|
90
|
+
code: "UNKNOWN_ERROR"
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return responseBody;
|
|
94
|
+
}
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new PuulError(parsed);
|
|
97
|
+
}
|
|
98
|
+
return parsed;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error instanceof PuulError) throw error;
|
|
101
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
102
|
+
throw new PuulError({
|
|
103
|
+
statusCode: 0,
|
|
104
|
+
message: `Request timed out after ${this.config.timeoutMs}ms`,
|
|
105
|
+
code: "REQUEST_TIMEOUT",
|
|
106
|
+
retryable: true
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
throw new PuulError({
|
|
110
|
+
statusCode: 0,
|
|
111
|
+
message: error instanceof Error ? error.message : "Unknown network error",
|
|
112
|
+
code: "NETWORK_ERROR",
|
|
113
|
+
retryable: true
|
|
114
|
+
});
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var SessionsAPI = class {
|
|
121
|
+
constructor(client) {
|
|
122
|
+
this.client = client;
|
|
123
|
+
}
|
|
124
|
+
/** Create a link token for user linking */
|
|
125
|
+
async createLinkToken(params) {
|
|
126
|
+
return this.client.request("POST", "/partner/link-tokens", params);
|
|
127
|
+
}
|
|
128
|
+
/** Exchange a link token for a user session */
|
|
129
|
+
async create(linkToken) {
|
|
130
|
+
return this.client.request("POST", "/partner/sessions", { linkToken });
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
var MarketsAPI = class {
|
|
134
|
+
constructor(client) {
|
|
135
|
+
this.client = client;
|
|
136
|
+
}
|
|
137
|
+
/** List all live markets, optionally filtered by country */
|
|
138
|
+
async list(countries) {
|
|
139
|
+
const query = countries?.length ? `?countries=${countries.join(",")}` : "";
|
|
140
|
+
return this.client.request("GET", `/partner/markets${query}`);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var PredictionsAPI = class {
|
|
144
|
+
constructor(client) {
|
|
145
|
+
this.client = client;
|
|
146
|
+
}
|
|
147
|
+
/** Create a binding quote for slippage protection (expires in 10s) */
|
|
148
|
+
async createQuote(params) {
|
|
149
|
+
return this.client.request("POST", "/partner/predictions/quote", params);
|
|
150
|
+
}
|
|
151
|
+
/** Place a prediction on a market outcome */
|
|
152
|
+
async place(params) {
|
|
153
|
+
return this.client.request("POST", "/partner/predictions", params);
|
|
154
|
+
}
|
|
155
|
+
/** Get a specific prediction by ID */
|
|
156
|
+
async get(predictionId) {
|
|
157
|
+
return this.client.request("GET", `/partner/predictions/${predictionId}`);
|
|
158
|
+
}
|
|
159
|
+
/** List user predictions with optional filters */
|
|
160
|
+
async list(options) {
|
|
161
|
+
const params = new URLSearchParams();
|
|
162
|
+
if (options?.status) params.set("status", options.status);
|
|
163
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
164
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
165
|
+
return this.client.request("GET", `/partner/predictions${query}`);
|
|
166
|
+
}
|
|
167
|
+
/** Create a pending prediction (JIT funding flow) */
|
|
168
|
+
async createPending(params) {
|
|
169
|
+
return this.client.request("POST", "/partner/predictions/pending", params);
|
|
170
|
+
}
|
|
171
|
+
/** Get pending prediction status */
|
|
172
|
+
async getPending(pendingId) {
|
|
173
|
+
return this.client.request("GET", `/partner/predictions/pending/${pendingId}`);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var WalletAPI = class {
|
|
177
|
+
constructor(client) {
|
|
178
|
+
this.client = client;
|
|
179
|
+
}
|
|
180
|
+
/** Get partner deposit account details */
|
|
181
|
+
async getDepositAccount() {
|
|
182
|
+
return this.client.request("GET", "/partner/wallet/deposit-account");
|
|
183
|
+
}
|
|
184
|
+
/** Get user wallet balance */
|
|
185
|
+
async getBalance() {
|
|
186
|
+
return this.client.request("GET", "/partner/wallet/balance");
|
|
187
|
+
}
|
|
188
|
+
/** Get omnibus wallet balance */
|
|
189
|
+
async getOmnibusBalance(currency) {
|
|
190
|
+
const query = currency ? `?currency=${currency}` : "";
|
|
191
|
+
return this.client.request("GET", `/partner/wallet/omnibus-balance${query}`);
|
|
192
|
+
}
|
|
193
|
+
/** Deposit funds to a linked user wallet */
|
|
194
|
+
async deposit(params) {
|
|
195
|
+
return this.client.request("POST", "/partner/wallet/deposit", params);
|
|
196
|
+
}
|
|
197
|
+
/** Get withdrawal fee estimate */
|
|
198
|
+
async getWithdrawalFees(currency, amount) {
|
|
199
|
+
return this.client.request("GET", `/partner/wallet/withdrawal-fees?currency=${currency}&amount=${amount}`);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/webhooks.ts
|
|
204
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
205
|
+
function verifyWebhookSignature(payload, signature, secret) {
|
|
206
|
+
if (!payload || !signature || !secret) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
const expectedSignature = createHmac("sha256", secret).update(payload).digest("hex");
|
|
210
|
+
try {
|
|
211
|
+
const sigBuffer = Buffer.from(signature, "hex");
|
|
212
|
+
const expectedBuffer = Buffer.from(expectedSignature, "hex");
|
|
213
|
+
if (sigBuffer.length !== expectedBuffer.length) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
return timingSafeEqual(sigBuffer, expectedBuffer);
|
|
217
|
+
} catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function parseWebhookEvent(body) {
|
|
222
|
+
if (!body || typeof body !== "object") {
|
|
223
|
+
throw new Error("Invalid webhook body: expected an object");
|
|
224
|
+
}
|
|
225
|
+
const event = body;
|
|
226
|
+
if (typeof event.event !== "string") {
|
|
227
|
+
throw new Error('Invalid webhook body: missing "event" field');
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
event: event.event,
|
|
231
|
+
timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
232
|
+
data: event.data || {}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function isPredictionSettledEvent(event) {
|
|
236
|
+
return event.event === "prediction.settled";
|
|
237
|
+
}
|
|
238
|
+
function asPredictionSettledData(event) {
|
|
239
|
+
return event.data;
|
|
240
|
+
}
|
|
241
|
+
export {
|
|
242
|
+
PuulError,
|
|
243
|
+
PuulPartner,
|
|
244
|
+
asPredictionSettledData,
|
|
245
|
+
isPredictionSettledEvent,
|
|
246
|
+
parseWebhookEvent,
|
|
247
|
+
verifyWebhookSignature
|
|
248
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@puul/partner-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official TypeScript SDK for the Puul Partner API — integrate prediction markets into your platform",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
21
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^25.2.3",
|
|
27
|
+
"tsup": "^8.5.1",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"puul",
|
|
32
|
+
"prediction-markets",
|
|
33
|
+
"parimutuel",
|
|
34
|
+
"partner-api",
|
|
35
|
+
"sdk"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/Puul-Market/puul"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|