@predictdotfun/sdk
v1.2.4
Published
A TypeScript SDK to help developers interface with the Predict's protocol.
Readme
A TypeScript SDK to help developers interface with the Predict's protocol.
Sections:
- How to install the SDK
- How to set approvals
- How to use a Predict account
- How to create a LIMIT order (recommended)
- How to create a MARKET order
- How to redeem positions
- How to merge positions
- How to check USDT balance
- How to interface with contracts
- How to cancel orders
- License
How to install the SDK
This package has ethers v6 as a peer dependency.
yarn add @predictdotfun/sdk ethersnpm install @predictdotfun/sdk ethersSee the OrderBuilder class for more in-depth details on each function.
Predict Account
Predict supports interacting with the protocol using either a traditional Externally Owned Account (EOA) or a Smart Wallet ("Predict Account"). If you use the web app, a Smart Wallet is automatically created for you. To interact with your Smart Wallet programmatically using the SDK, follow the example shown in How to use a Predict account.
How to set approvals
Before trading, you need to set approvals for ERC-1155 (ConditionalTokens) and ERC-20 (USDT). This can be achieved by sending a transaction to the respective contracts (see the Contracts section) and approving both the CTF_EXCHANGE and the NEG_RISK_CTF_EXCHANGE or via the SDK utils.
Contracts: The current deployed contracts can be found either in the Constants.ts file or in the Deployed Contracts documentation.
The following example demonstrates how to set the necessary approvals using the SDK utils.
import { Wallet, MaxInt256 } from "ethers";
import { OrderBuilder, ChainId, Side } from "@predictdotfun/sdk";
// Create a wallet to send the approvals transactions (must be the orders' `maker`)
const signer = new Wallet(process.env.WALLET_PRIVATE_KEY);
// The main function which initiates the OrderBuilder (only once per signer) and then provides it as dependency to other util functions
async function main() {
// Create a new instance of the OrderBuilder class. Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer);
// Call an helper function to set the approvals and provide the OrderBuilder instance.
await setApprovals(orderBuilder);
}
async function setApprovals(orderBuilder: OrderBuilder) {
// Set all the approval needed within the protocol
const result = await orderBuilder.setApprovals();
// Check if the approvals were set successfully
if (!result.success) {
throw new Error("Failed to set approvals.");
}
}How to use a Predict account
Here's an example of how to use a Predict account to create/cancel orders and set approvals.
- Initiate the Privy Wallet: The wallet is needed to sign orders. Can be found in the account settings.
- Ensure the Privy Wallet has funds: You will need to add some ETH to be able to set approvals and cancel orders, if needed.
- Initialize
OrderBuilder: Instantiate theOrderBuilderclass by callingOrderBuilder.make.- NOTE: Include the
predictAccountaddress, which is also known as the deposit address.
- NOTE: Include the
- Set Approvals: Ensure the necessary approvals are set (refer to Set Approvals).
- Determine Order Amounts: Use
getLimitOrderAmountsto calculate order amounts. - Build Order: Use
buildOrderto generate aLIMITstrategy order.- NOTE: Fetch the
feeRateBpsvia theGET /marketsendpoint on the REST API - NOTE: Set the
signerandmakerto thepredictAccountaddress, NOT the signer/privy wallet address.
- NOTE: Fetch the
- Generate Typed Data: Call
buildTypedDatato generate typed data for the order. - Sign Order: Obtain a
SignedOrderobject by callingsignTypedDataOrder. - Compute Order Hash: Compute the order hash using
buildTypedDataHash.
import { Wallet } from "ethers";
import { OrderBuilder, ChainId, Side } from "@predictdotfun/sdk";
// Create a wallet to send the approvals transactions (must be the orders' `maker`)
const signer = new Wallet(process.env.PRIVY_WALLET_PRIVATE_KEY);
// The main function which initiates the OrderBuilder (only once per signer) and then provides it as dependency to other util functions
async function main() {
// Create a new instance of the OrderBuilder class with the predictAccount option
// Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer, {
predictAccount: "PREDICT_ACCOUNT_ADDRESS", // Your deposit address from account settings
});
// Call an helper function to create the order and provide the OrderBuilder instance
await createOrder(orderBuilder);
}
async function createOrder(orderBuilder: OrderBuilder) {
// Step 1. Set approvals and define the order params as usual
// Step 2. Create the order (maker and signer are automatically set to the predictAccount)
const order = orderBuilder.buildOrder("LIMIT", {
side: Side.BUY, // Equivalent to 0
tokenId: "OUTCOME_ON_CHAIN_ID", // This can be fetched via the API or on-chain
makerAmount, // 0.4 USDT * 10 shares (in wei)
takerAmount, // 10 shares (in wei)
nonce: 0n,
feeRateBps: 0, // Should be fetched via the `GET /markets` endpoint
});
// Step 3. Sign and submit the order as usual
}This will allow you to perform operations via your Predict Account (via the smart wallet).
How to create a LIMIT order (recommended)
Here's an example of how to use the OrderBuilder to create and sign a LIMIT strategy buy order:
- Create Wallet: The wallet is needed to sign orders.
- Initialize
OrderBuilder: Instantiate theOrderBuilderclass by callingOrderBuilder.make. - Set Approvals: Ensure the necessary approvals are set (refer to Set Approvals).
- Determine Order Amounts: Use
getLimitOrderAmountsto calculate order amounts. - Build Order: Use
buildOrderto generate aLIMITstrategy order.- NOTE: Fetch the
feeRateBpsvia theGET /marketsendpoint on the REST API
- NOTE: Fetch the
- Generate Typed Data: Call
buildTypedDatato generate typed data for the order. - Sign Order: Obtain a
SignedOrderobject by callingsignTypedDataOrder. - Compute Order Hash: Compute the order hash using
buildTypedDataHash.
import { Wallet } from "ethers";
import { OrderBuilder, ChainId, Side } from "@predictdotfun/sdk";
// Create a wallet for signing orders
const signer = new Wallet(process.env.WALLET_PRIVATE_KEY);
// The main function which initiates the OrderBuilder (only once per signer) and then provides it as dependency to other util functions
async function main() {
// Create a new instance of the OrderBuilder class. Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer);
// Call an helper function to create the order and provide the OrderBuilder instance
await createOrder(orderBuilder);
}
async function createOrder(orderBuilder: OrderBuilder) {
/**
* NOTE: You can also call `setApprovals` once per wallet.
*/
// Set all the approval needed within the protocol (if needed)
const result = await orderBuilder.setApprovals();
// Check if the approvals were set successfully
if (!result.success) {
throw new Error("Failed to set approvals.");
}
// Simple helper function to calculate the amounts for a `LIMIT` order
const { lastPrice, pricePerShare, makerAmount, takerAmount } = orderBuilder.getLimitOrderAmounts({
side: Side.BUY,
pricePerShareWei: 400000000000000000n, // 0.4 USDT (in wei)
quantityWei: 10000000000000000000n, // 10 shares (in wei)
});
// Build a limit order (if you are using a Predict account replace `signer.address` with your deposit address)
const order = orderBuilder.buildOrder("LIMIT", {
maker: signer.address,
signer: signer.address,
side: Side.BUY, // Equivalent to 0
tokenId: "OUTCOME_ON_CHAIN_ID", // This can be fetched via the API or on-chain
makerAmount, // 0.4 USDT * 10 shares (in wei)
takerAmount, // 10 shares (in wei)
nonce: 0n,
feeRateBps: 0, // Should be fetched via the `GET /markets` endpoint
});
// Build typed data for the order (isNegRisk and isYieldBearing can be fetched via the API)
const typedData = orderBuilder.buildTypedData(order, { isNegRisk: true, isYieldBearing: true });
// Sign the order by providing the typedData of the order
const signedOrder = await orderBuilder.signTypedDataOrder(typedData);
// Compute the order's hash
const hash = orderBuilder.buildTypedDataHash(typedData);
// Example structure required to create an order via Predict's API
const createOrderBody = {
data: {
order: { ...signedOrder, hash },
pricePerShare,
strategy: "LIMIT",
},
};
}How to create a MARKET order
Similarly to the above, here's the flow to create a MARKET sell order:
- Create Wallet: The wallet is needed to sign orders.
- Initialize
OrderBuilder: Instantiate theOrderBuilderclass by callingOrderBuilder.make. - Set Approvals: Ensure the necessary approvals are set (refer to Set Approvals).
- Fetch Orderbook: Query the Predict API for the latest orderbook for the market.
- Determine Order Amounts: Use
getMarketOrderAmountsto calculate order amounts. - Build Order: Call
buildOrderto generate aMARKETstrategy order.- NOTE: Fetch the
feeRateBpsvia theGET /marketsendpoint on the REST API
- NOTE: Fetch the
- Generate Typed Data: Use
buildTypedDatato create typed data for the order. - Sign Order: Obtain a
SignedOrderobject by callingsignTypedDataOrder. - Compute Order Hash: Compute the order hash using
buildTypedDataHash.
import { Wallet } from "ethers";
import { OrderBuilder, ChainId, Side } from "@predictdotfun/sdk";
// Create a wallet for signing orders
const signer = new Wallet(process.env.WALLET_PRIVATE_KEY);
// The main function which initiates the OrderBuilder (only once per signer) and then provides it as dependency to other util functions
async function main() {
// Create a new instance of the OrderBuilder class. Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer);
// Call an helper function to create the order and provide the OrderBuilder instance
await createOrder(orderBuilder);
}
async function createOrder(orderBuilder: OrderBuilder) {
// Fetch the orderbook for the specific market via `GET orderbook/{marketId}`
const book = {};
/**
* NOTE: You can also call `setApprovals` once per wallet.
*/
// Set all the approval needed within the protocol (if needed)
const result = await orderBuilder.setApprovals();
// Check if the approvals were set successfully
if (!result.success) {
throw new Error("Failed to set approvals.");
}
// Helper function to calculate the amounts for a `MARKET` order
const { lastPrice, pricePerShare, makerAmount, takerAmount } = orderBuilder.getMarketOrderAmounts(
{
side: Side.SELL,
quantityWei: 10000000000000000000n, // 10 shares (in wei) e.g. parseEther("10")
},
book, // It's recommended to re-fetch the orderbook regularly to avoid issues
);
// Build a limit order (if you are using a Predict account replace `signer.address` with your deposit address)
const order = orderBuilder.buildOrder("MARKET", {
maker: signer.address,
signer: signer.address,
side: Side.SELL, // Equivalent to 1
tokenId: "OUTCOME_ON_CHAIN_ID", // This can be fetched via the API or on-chain
makerAmount, // 10 shares (in wei)
takerAmount, // 0.4 USDT * 10 shares (in wei)
nonce: 0n,
feeRateBps: 0, // Should be fetched via the `GET /markets` endpoint
});
// Build typed data for the order (isNegRisk and isYieldBearing can be fetched via the API)
const typedData = orderBuilder.buildTypedData(order, { isNegRisk: false, isYieldBearing: false });
// Sign the order by providing the typedData of the order
const signedOrder = await orderBuilder.signTypedDataOrder(typedData);
// Compute the order's hash
const hash = orderBuilder.buildTypedDataHash(typedData);
// Example structure required to create an order via Predict's API
const createOrderBody = {
data: {
order: { ...signedOrder, hash },
pricePerShare,
strategy: "MARKET",
slippageBps: "200", // Only used for `MARKET` orders, in this example it's 2%
},
};
}How to redeem positions
The OrderBuilder class provides the redeemPositions method to redeem your positions on the Predict protocol. The method supports all market types through the isNegRisk and isYieldBearing options.
- Create a Wallet: Initialize a wallet that will be used to sign the redemption transaction.
- Initialize
OrderBuilder: Instantiate theOrderBuilderclass by calling the staticmakemethod. - Redeem Positions: Call the
redeemPositionsmethod with the appropriate options.
The conditionId and indexSet can be fetched from the GET /positions endpoint.
import { Wallet } from "ethers";
import { OrderBuilder, ChainId } from "@predictdotfun/sdk";
// Initialize the wallet with your private key
const signer = new Wallet(process.env.WALLET_PRIVATE_KEY);
// The main function initiates the OrderBuilder (only once per signer)
// Then provides it as dependency to other functions
async function main() {
// Create a new instance of the OrderBuilder class. Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer);
// Redeem positions for a standard market
await redeemStandardPositions(orderBuilder);
// Redeem positions for a NegRisk market
await redeemNegRiskPositions(orderBuilder);
}
async function redeemStandardPositions(orderBuilder: OrderBuilder) {
const conditionId = "CONDITION_ID_FROM_API"; // A hash string
const indexSet = "INDEX_SET_FROM_API"; // 1 or 2 based on the position you want to redeem
const result = await orderBuilder.redeemPositions({
conditionId,
indexSet,
isNegRisk: false,
isYieldBearing: true, // Set based on market type
});
if (result.success) {
console.log("Positions redeemed successfully:", result.receipt);
} else {
console.error("Failed to redeem positions:", result.cause);
}
}
async function redeemNegRiskPositions(orderBuilder: OrderBuilder) {
const conditionId = "CONDITION_ID_FROM_API"; // A hash string
const indexSet = "INDEX_SET_FROM_API"; // 1 or 2 based on the position you want to redeem
const amount = "POSITION_AMOUNT_FROM_API"; // The amount to redeem, usually the max
const result = await orderBuilder.redeemPositions({
conditionId,
indexSet,
amount,
isNegRisk: true,
isYieldBearing: true, // Set based on market type
});
if (result.success) {
console.log("Positions redeemed successfully:", result.receipt);
} else {
console.error("Failed to redeem positions:", result.cause);
}
}How to merge positions
The OrderBuilder class provides the mergePositions method to combine both outcome tokens back into collateral (USDT). This is useful when you hold equal amounts of both YES and NO positions.
- Create a Wallet: Initialize a wallet that will be used to sign the merge transaction.
- Initialize
OrderBuilder: Instantiate theOrderBuilderclass by calling the staticmakemethod. - Merge Positions: Call the
mergePositionsmethod with the appropriate options.
The conditionId can be fetched from the GET /positions endpoint.
import { Wallet } from "ethers";
import { OrderBuilder, ChainId } from "@predictdotfun/sdk";
// Initialize the wallet with your private key
const signer = new Wallet(process.env.WALLET_PRIVATE_KEY);
async function main() {
// Create a new instance of the OrderBuilder class. Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer);
await mergePositions(orderBuilder);
}
async function mergePositions(orderBuilder: OrderBuilder) {
const conditionId = "CONDITION_ID_FROM_API";
const amount = 10000000000000000000n; // 10 tokens (in wei)
const result = await orderBuilder.mergePositions({
conditionId,
amount,
isNegRisk: false,
isYieldBearing: true, // Set based on market type
});
if (result.success) {
console.log("Positions merged successfully:", result.receipt);
} else {
console.error("Failed to merge positions:", result.cause);
}
}How to check USDT balance
The method balanceOf allows to easily check the current USDT balance of the connected signer.
import { Wallet } from "ethers";
import { OrderBuilder, ChainId } from "@predictdotfun/sdk";
// Initialize the wallet with your private key
const signer = new Wallet(process.env.WALLET_PRIVATE_KEY);
// The main function initiates the OrderBuilder (only once per signer)
// Then provides it as dependency to other functions
async function main() {
// Create a new instance of the OrderBuilder class. Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer);
await checkBalance(orderBuilder);
}
async function checkBalance(orderBuilder: OrderBuilder) {
// Fetch the current account/wallet balance in wei
const balanceWei = await orderBuilder.balanceOf();
// Example check
if (balanceWei >= orderAmountWei) {
console.log("Enough balance to create the order");
} else {
console.error("Not enough balance to create the order");
}
}How to interface with contracts
To facilitate interactions with Predict's contracts we expose the necessary instances of each contract, including ABIs and types.
import { OrderBuilder, ChainId } from "@predictdotfun/sdk";
import { Wallet } from "ethers";
// Create a wallet to interact with on-chain contracts
const signer = new Wallet(process.env.WALLET_PRIVATE_KEY);
// The main function initiates the OrderBuilder (only once per signer)
// Then provides it as dependency to other functions
async function main() {
// Create a new instance of the OrderBuilder class. Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer);
await callContracts(orderBuilder);
}
async function callContracts(orderBuilder: OrderBuilder) {
// If the signer is not provided within `OrderBuilder.make` the contracts won't be initiated
if (!orderBuilder.contracts) {
throw new Error("The signer was not provided during the OrderBuilder init.");
}
// You can now call contract functions (the actual contract instance is within `contract`)
// The `codec` property contains the ethers Interface, useful to encode and decode data
const balance = await orderBuilder.contracts["USDT"].contract.balanceOf(signer.address);
}Some other useful utils, ABIs and types exposed by the SDK.
import {
// Supported Chains
ChainId,
// Addresses
AddressesByChainId,
// Contract Interfaces
CTFExchange,
ConditionalTokens,
YieldBearingConditionalTokens,
NegRiskAdapter,
NegRiskCtfExchange,
ERC20,
// ABIs
CTFExchangeAbi,
NegRiskCtfExchangeAbi,
NegRiskAdapterAbi,
ConditionalTokensAbi,
YieldBearingConditionalTokensAbi,
ERC20Abi,
// Order builder
OrderBuilder,
} from "@predictdotfun/sdk";How to cancel orders
Here's an example on how to cancel orders via the SDK
- Fetch Orders: Retrieve your open orders using
GET /orders. - Cancel Orders off chain: Call
POST /orders/cancelwith orderIds and cancel orders from the orderbook - Group by
isNegRiskandisYieldBearing: Separate orders based on theisNegRiskandisYieldBearingproperties. - Cancel Orders: Call the specific cancel function based on the order(s) type (
isNegRiskandisYieldBearing). - Check Transaction Success: Check to confirm the transaction was successful.
import { Wallet } from "ethers";
import { OrderBuilder, ChainId, Side } from "@predictdotfun/sdk";
// Create a new JsonRpcProvider instance
const provider = new JsonRpcProvider(process.env.RPC_PROVIDER_URL);
// Create a wallet to send the cancel transactions on-chain
const signer = new Wallet(process.env.WALLET_PRIVATE_KEY).connect(provider);
// The main function which initiates the OrderBuilder (only once per signer) and then provides it as dependency to other util functions
async function main() {
// Create a new instance of the OrderBuilder class. Note: This should only be done once per signer
const orderBuilder = await OrderBuilder.make(ChainId.BnbMainnet, signer);
// Call a helper function to cancel orders and provide the OrderBuilder instance
await cancelOrdersHelper(orderBuilder);
}
async function cancelOrdersHelper(orderBuilder: OrderBuilder) {
// Fetch your open orders from the `GET /orders` endpoint
const apiResponse = [
// There are more fields, but for cancellations we only care about `order`, `isNegRisk` and `isYieldBearing`
{ order: {}, isNegRisk: true, isYieldBearing: true },
{ order: {}, isNegRisk: true, isYieldBearing: false },
{ order: {}, isNegRisk: false, isYieldBearing: true },
{ order: {}, isNegRisk: false, isYieldBearing: false },
];
// Determine which orders you want to cancel
const ordersToCancel = [
{ order: {}, isNegRisk: true, isYieldBearing: true },
{ order: {}, isNegRisk: true, isYieldBearing: false },
{ order: {}, isNegRisk: false, isYieldBearing: true },
{ order: {}, isNegRisk: false, isYieldBearing: false },
];
const regularOrders: Order[] = [];
const negRiskOrders: Order[] = [];
const regularYieldBearingOrders: Order[] = [];
const negRiskYieldBearingOrders: Order[] = [];
// Group the orders by `isNegRisk` and `isYieldBearing`
for (const { order, isNegRisk, isYieldBearing } of ordersToCancel) {
if (isYieldBearing) {
if (isNegRisk) {
negRiskYieldBearingOrders.push(order);
} else {
regularYieldBearingOrders.push(order);
}
} else {
if (isNegRisk) {
negRiskOrders.push(order);
} else {
regularOrders.push(order);
}
}
}
// Call the respective cancel functions
const regResult = await orderBuilder.cancelOrders(regularOrders, { isNegRisk: false, isYieldBearing: false });
const negRiskResult = await orderBuilder.cancelOrders(negRiskOrders, { isNegRisk: true, isYieldBearing: false });
const regYieldBearingResult = await orderBuilder.cancelOrders(regularYieldBearingOrders, {
isNegRisk: false,
isYieldBearing: true,
});
const negRiskYieldBearingResult = await orderBuilder.cancelOrders(negRiskYieldBearingOrders, {
isNegRisk: true,
isYieldBearing: true,
});
// Check for the transactions success
const success =
regResult.success && negRiskResult.success && regYieldBearingResult.success && negRiskYieldBearingResult.success;
}License
By contributing to this project, you agree that your contributions will be licensed under the project's MIT License.
