pnp-adapter
v0.1.2
Published
Official SDK for PNP-Adapter - Decentralized AI-Powered Prediction Marketplace on Solana
Downloads
251
Maintainers
Readme
PNP-ADAPTER
TypeScript SDK for interacting with the PNP Protocol on Solana - a decentralized prediction marketplace that uses AI to settle markets.
Installation
npm install pnp-adapterOr with yarn/pnpm:
npm install pnp-adapter
yarn add pnp-adapterQuick Start
import { PnpClient } from "pnp-adapter";
import { PublicKey } from "@solana/web3.js";
const client = new PnpClient({
rpcUrl: "https://api.mainnet-beta.solana.com",
});
const { transaction, blockhash, lastValidBlockHeight } = await client.buyV3Tokens(
userWalletPublicKey,
{
marketAddress: "...",
amount: 10_000_000,
side: { yes: {} },
}
);Features
- Transaction Builder Pattern: SDK returns unsigned transactions for you to sign
- Multi-version Support: V1, V2, and V3 market support
- TypeScript First: Full type safety with TypeScript
- No Wallet Dependency: Uses noop wallet internally, you handle signing
- Slippage Calculations: Built-in price impact calculations
Integration Examples
Frontend (React + Wallet Adapter)
import { useWallet, useConnection } from "@solana/wallet-adapter-react";
import { PnpClient } from "pnp-adapter";
function BuyTokensButton({ marketAddress, amount, side }) {
const { publicKey, signTransaction } = useWallet();
const { connection } = useConnection();
const handleBuy = async () => {
if (!publicKey || !signTransaction) return;
const client = new PnpClient({
rpcUrl: connection.rpcEndpoint,
});
const { transaction, blockhash, lastValidBlockHeight } = await client.buyV3Tokens(
publicKey,
{
marketAddress,
amount,
side,
minimumTokensOut: 0,
}
);
const signed = await signTransaction(transaction);
const signature = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction({
signature,
blockhash,
lastValidBlockHeight,
});
console.log("Transaction confirmed:", signature);
};
return <button onClick={handleBuy}>Buy Tokens</button>;
}Frontend (Vue.js + Solana Wallet)
import { PnpClient } from "pnp-adapter";
import { useWallet } from "solana-wallets-vue";
const { publicKey, signTransaction } = useWallet();
const client = new PnpClient({
rpcUrl: "https://api.mainnet-beta.solana.com",
});
async function buyTokens(marketAddress: string, amount: number) {
if (!publicKey.value || !signTransaction.value) return;
const { transaction, blockhash, lastValidBlockHeight } = await client.buyV3Tokens(
publicKey.value,
{
marketAddress,
amount,
side: { yes: {} },
}
);
const signed = await signTransaction.value(transaction);
const connection = client.getConnection();
const signature = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction({
signature,
blockhash,
lastValidBlockHeight,
});
return signature;
}Server-side (Node.js)
import { PnpClient, toBaseUnits } from "pnp-adapter";
import { Keypair, Connection } from "@solana/web3.js";
import bs58 from "bs58";
const privateKey = process.env.WALLET_PRIVATE_KEY!;
const keypair = Keypair.fromSecretKey(bs58.decode(privateKey));
const client = new PnpClient({
rpcUrl: process.env.RPC_URL!,
commitment: "confirmed",
});
async function executeTrade(marketAddress: string, usdcAmount: number) {
const { transaction, blockhash, lastValidBlockHeight } = await client.buyV3Tokens(
keypair.publicKey,
{
marketAddress,
amount: toBaseUnits(usdcAmount),
side: { yes: {} },
}
);
transaction.sign(keypair);
const connection = client.getConnection();
const signature = await connection.sendRawTransaction(transaction.serialize(), {
skipPreflight: false,
preflightCommitment: "confirmed",
});
const confirmation = await connection.confirmTransaction({
signature,
blockhash,
lastValidBlockHeight,
});
if (confirmation.value.err) {
throw new Error(`Transaction failed: ${confirmation.value.err}`);
}
return signature;
}
executeTrade("market-address-here", 10)
.then(console.log)
.catch(console.error);AI Agent / Bot Integration
import { PnpClient, toBaseUnits, fromBaseUnits } from "pnp-adapter";
import { Keypair } from "@solana/web3.js";
class PredictionMarketAgent {
private client: PnpClient;
private wallet: Keypair;
constructor(rpcUrl: string, privateKey: Uint8Array) {
this.client = new PnpClient({ rpcUrl });
this.wallet = Keypair.fromSecretKey(privateKey);
}
async getMarketPrices(marketAddress: string) {
const data = await this.client.getMarketPricesAndData(marketAddress);
if (!data) throw new Error("Market not found");
return data.prices;
}
async placeBet(marketAddress: string, side: "yes" | "no", amount: number) {
const { transaction } = await this.client.buyV3Tokens(this.wallet.publicKey, {
marketAddress,
amount: toBaseUnits(amount),
side: side === "yes" ? { yes: {} } : { no: {} },
});
transaction.sign(this.wallet);
const connection = this.client.getConnection();
return connection.sendRawTransaction(transaction.serialize());
}
async analyzeAndTrade(marketAddress: string) {
const prices = await this.getMarketPrices(marketAddress);
if (prices.yesPrice < 0.3) {
console.log("YES token undervalued, buying...");
return this.placeBet(marketAddress, "yes", 10);
} else if (prices.noPrice < 0.3) {
console.log("NO token undervalued, buying...");
return this.placeBet(marketAddress, "no", 10);
}
console.log("No opportunity found");
return null;
}
}Express.js API Server
import express from "express";
import { PnpClient, toBaseUnits } from "pnp-adapter";
import { Keypair, PublicKey } from "@solana/web3.js";
const app = express();
app.use(express.json());
const client = new PnpClient({
rpcUrl: process.env.RPC_URL!,
});
app.get("/api/markets/:address", async (req, res) => {
try {
const data = await client.getMarketPricesAndData(req.params.address);
if (!data) {
return res.status(404).json({ error: "Market not found" });
}
res.json(data);
} catch (error) {
res.status(500).json({ error: "Failed to fetch market data" });
}
});
app.get("/api/markets/:address/price/:side", async (req, res) => {
try {
const side = req.params.side as "yes" | "no";
const price = await client.getPrice(req.params.address, side);
res.json({ price });
} catch (error) {
res.status(500).json({ error: "Failed to fetch price" });
}
});
app.post("/api/build-transaction", async (req, res) => {
try {
const { userPublicKey, marketAddress, amount, side } = req.body;
const { transaction, blockhash, lastValidBlockHeight } = await client.buyV3Tokens(
new PublicKey(userPublicKey),
{
marketAddress,
amount: toBaseUnits(amount),
side: side === "yes" ? { yes: {} } : { no: {} },
}
);
res.json({
transaction: transaction.serialize({ requireAllSignatures: false }).toString("base64"),
blockhash,
lastValidBlockHeight,
});
} catch (error) {
res.status(500).json({ error: "Failed to build transaction" });
}
});
app.listen(3000, () => console.log("Server running on port 3000"));API Reference
PnpClient
Constructor
const client = new PnpClient({
rpcUrl: string,
programId?: PublicKey,
commitment?: "processed" | "confirmed" | "finalized",
});Market Creation
await client.createMarket(creator, { question, initialLiquidity, endTime });
await client.createMarketV2(payer, creator, { question, initialLiquidity, endTime });
await client.createMarketV3(payer, creator, { question, initialAmount, side, creatorSideCap, endTime, maxPotRatio });Trading
await client.mintDecisionTokens(buyer, { marketAddress, amount, isYes, creatorAddress, minimumOut? });
await client.burnDecisionTokens(buyer, { marketAddress, amount, isYes, creatorAddress });
await client.buyV3Tokens(buyer, { marketAddress, amount, side, minimumTokensOut? });Redemption
await client.redeemPosition(user, { marketAddress, marketCreatorAddress, yesTokenAddress, noTokenAddress });
await client.redeemPositionV2(user, params);
await client.redeemV3Position(redeemer, params);Creator Functions
await client.claimCreatorFee(creator);
await client.creatorRefund(creator, { marketAddress, yesTokenAddress, noTokenAddress });
await client.creatorRefundV2(creator, marketAddress);
await client.creatorRefundV3(creator, marketAddress);Read Functions
await client.getMarketVersion(marketAddress);
await client.getMarketData(marketAddress);
await client.getMarketPricesAndData(marketAddress);
await client.getPrice(marketAddress, "yes" | "no");
await client.getMarketTokenAddresses(marketAddress);
await client.getGlobalConfig();Utility Functions
import {
shortenAddress,
toBaseUnits,
fromBaseUnits,
calculateMintingSlippage,
calculateBurnSlippage,
getTokenBalance,
getUserTokenBalance,
getMultipleTokenBalances,
getCachedData,
setCachedData,
clearCache,
} from "pnp-adapter";
shortenAddress("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 4);
const baseUnits = toBaseUnits(10);
const humanUnits = fromBaseUnits(10_000_000);
const { tokensToMint, priceImpact } = calculateMintingSlippage(
collateralAmount,
marketReserves,
supplyToMint,
supplyOther
);
const { collateralOut, priceImpact } = calculateBurnSlippage(
tokensToBurn,
marketReserves,
supplyToBurn,
supplyOther
);PDA Derivation
import {
getGlobalConfigPDA,
getMarketPDA,
getMarketV3PDA,
getYesTokenMintPDA,
getNoTokenMintPDA,
getCreatorFeeTreasuryPDA,
} from "pnp-adapter";Types
import type {
TokenSide,
TransactionResult,
MarketData,
MarketPricesAndData,
MarketTokenAddresses,
MarketVersion,
CreateMarketParams,
CreateMarketV2Params,
CreateMarketV3Params,
BuyTokensParams,
MintTokensParams,
BurnTokensParams,
RedeemParams,
} from "pnp-adapter";
const yesSide: TokenSide = { yes: {} };
const noSide: TokenSide = { no: {} };Constants
import {
PROGRAM_ID,
COLLATERAL_TOKEN_MINT,
METADATA_PROGRAM_ID,
DEVNET_RPC_URL,
MAINNET_RPC_URL,
DECIMALS,
} from "pnp-adapter";Transaction Flow
The SDK follows a transaction builder pattern:
- Build Transaction: SDK constructs the transaction with all required accounts
- Return to Caller: Transaction is returned unsigned with blockhash info
- Sign Transaction: You sign with your wallet (frontend) or keypair (server)
- Send Transaction: You send the signed transaction to the network
const { transaction, blockhash, lastValidBlockHeight } = await client.buyV3Tokens(...);
transaction.sign(keypair);
const signature = await connection.sendRawTransaction(transaction.serialize());
await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight });Error Handling
try {
const { transaction } = await client.buyV3Tokens(buyer, params);
} catch (error) {
if (error.message.includes("Account not found")) {
console.error("Market does not exist");
} else if (error.message.includes("insufficient funds")) {
console.error("Insufficient balance");
} else {
console.error("Transaction failed:", error);
}
}Development
npm install
npm run build
npm test
npm run lint
npm run formatScripts
| Command | Description |
|---------|-------------|
| npm run build | Build ESM, CJS, and type declarations |
| npm run build:esm | Build ESM only |
| npm run build:cjs | Build CJS only |
| npm run build:types | Build type declarations only |
| npm test | Run tests |
| npm run lint | Lint code |
| npm run format | Format code |
| npm run clean | Clean build artifacts |
License
MIT
