@puul/typescript-sdk
v1.6.0
Published
Official TypeScript SDK for the Puul Partner API — integrate prediction markets into your platform
Maintainers
Readme
@puul/typescript-sdk
Official TypeScript SDK for the Puul Partner API — integrate prediction markets into your platform with type safety and zero configuration.
Features
- 🔐 Auto-authentication — OAuth2 token management handled for you
- 📦 Zero runtime dependencies — uses native
fetch(Node 18+) - 🎯 Fully typed — complete TypeScript definitions for all API shapes
- 🪝 Webhook helpers — signature verification & typed event parsing
- ⚡ Dual output — ESM + CommonJS builds
Installation
npm install @puul/typescript-sdkQuick Start
import { PuulPartner } from '@puul/typescript-sdk';
const puul = new PuulPartner({
clientId: 'pk_live_abc123',
clientSecret: 'sk_live_secret789',
});
// List live markets
const markets = await puul.markets.list();
console.log(markets[0].question);
// → "Will Bitcoin reach $100k by March?"
// Place a prediction
const prediction = await puul.predictions.place({
marketId: markets[0].id,
outcomeId: markets[0].outcomes[0].id,
stakeAmount: 100000, // ₦1,000 (minor units)
stakeCurrency: 'NGN',
idempotencyKey: `pred_${Date.now()}`,
});API Reference
Authentication
The SDK automatically manages OAuth2 tokens. You never need to call the auth endpoint directly.
const puul = new PuulPartner({
clientId: 'pk_live_abc123',
clientSecret: 'sk_live_secret789',
baseUrl: 'https://api.joinpuul.com/api/v1', // optional
timeoutMs: 30000, // optional
});User Linking
// 1. Create a link token (server-side)
const linkToken = await puul.sessions.createLinkToken({
externalUserId: 'your-user-id-123',
email: '[email protected]', // optional
countryCode: 'NG', // optional
});
// 2. Exchange link token for a session
const session = await puul.sessions.create(linkToken.linkToken);
// session.access_token is scoped to the linked userMarkets
// All live markets
const markets = await puul.markets.list();
// Filter by country
const ngMarkets = await puul.markets.list(['NG', 'GH']);Predictions
// Optional: Lock in odds with a quote (10s expiry)
const quote = await puul.predictions.createQuote({
marketId: 'market-uuid',
outcomeId: 'outcome-uuid',
stakeAmount: 50000,
stakeCurrency: 'NGN',
});
// Place prediction (with or without quote)
const prediction = await puul.predictions.place({
marketId: 'market-uuid',
outcomeId: 'outcome-uuid',
stakeAmount: 50000,
stakeCurrency: 'NGN',
idempotencyKey: 'unique-key',
quoteId: quote.quoteId, // optional
});
// Get prediction details
const details = await puul.predictions.get(prediction.id);
// List user predictions
const history = await puul.predictions.list({ status: 'OPEN', limit: 20 });Pending Predictions (JIT Funding)
// Create a pending prediction — returns bank payment details
const pending = await puul.predictions.createPending({
marketId: 'market-uuid',
outcomeId: 'outcome-uuid',
stakeAmount: 500000,
stakeCurrency: 'NGN',
userExternalId: 'your-user-id',
idempotencyKey: 'unique-key',
});
// pending.paymentReference — use this in the bank transfer
// Check status
const status = await puul.predictions.getPending(pending.id);
// status.status: 'pending' → 'funded' → 'placed'Wallet
const balance = await puul.wallet.getBalance();
const omnibus = await puul.wallet.getOmnibusBalance('USDC');
await puul.wallet.deposit({
amount: 5000, // $50.00 in minor units
currency: 'USDC',
idempotency_key: 'dep_unique_123',
});
// Withdraw from omnibus to configured bank/crypto destination
const withdrawal = await puul.wallet.withdraw({
amount: 500000, // $5,000 in minor units
currency: 'USDC',
method: 'bank', // or 'crypto'
});
// withdrawal.withdrawalId, withdrawal.statusAuto-Settlement: If your
payout_methodis configured (via the admin dashboard), revenue share is automatically paid out after each market settlement. You don't need to callwithdraw()manually — it happens at settlement time.
Webhook Verification
import { verifyWebhookSignature, parseWebhookEvent } from '@puul/typescript-sdk';
app.post('/webhooks/puul', express.raw({ type: '*/*' }), (req, res) => {
const isValid = verifyWebhookSignature(
req.body.toString(),
req.headers['x-puul-signature'] as string,
process.env.PUUL_WEBHOOK_SECRET!,
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const event = parseWebhookEvent(JSON.parse(req.body.toString()));
switch (event.event) {
case 'prediction.settled':
console.log('Prediction settled:', event.data);
break;
case 'prediction.voided':
console.log('Prediction voided:', event.data);
// Handle refund — user gets full stake back
break;
case 'deposit.confirmed':
console.log('Deposit confirmed:', event.data);
break;
case 'withdrawal.completed':
console.log('Withdrawal completed:', event.data);
break;
}
res.status(200).send('OK');
});Error Handling
All API errors throw a PuulError with structured details:
import { PuulError } from '@puul/typescript-sdk';
try {
await puul.predictions.place(params);
} catch (error) {
if (error instanceof PuulError) {
console.error(error.code); // 'INSUFFICIENT_BALANCE'
console.error(error.message); // 'Insufficient wallet balance'
console.error(error.statusCode); // 400
console.error(error.requestId); // 'req_abc123'
console.error(error.retryable); // false
}
}Supported Currencies
| Code | Currency |
|------|----------|
| NGN | Nigerian Naira |
| USDC | USD Coin |
| USDT | Tether |
| KES | Kenyan Shilling |
| GHS | Ghanaian Cedi |
| ZAR | South African Rand |
License
MIT
