Parimutuel Typescript SDK QuickStart

What is the Hxro Parimutuel Protocol?

The Hxro Parimutuel Protocol is a tool that allows for peer-to-peer, floating strike options with a pool-based, parimutuel payoff. What this means is that users can deposit funds into the pool representing the outcome they think is most likely to occur (or they think will have favorable odds to choose the pool) and when the contest is settled, the total funds in all pools is distributed pro-rata to the winning pool. It can be used for financial transactions, gaming, sports wagering, and more. The protocol uses SAMM, a Smart Automated Market Maker, to allow liquidity pools to automatically participate in parimutuel events and provide low fees to participants. The protocol is designed to solve liquidity consistency issues in parimutuel markets and can be used in trading, prediction markets, and sports wagering. Parimutuel markets are pool-based and create the potential for asymmetric payoffs in cases where there is an imbalance in assets distributed to each outcome.

You can learn more about how the Parimutuel Protocol works here.

Index:

:

  1. Previous experience with TS

  2. Install node.js and npm

  3. Create a Typescript project. If you don’t know how, here is an example

  4. Install Solana Web3.js in your project by running npm i @solana/web3.js

  5. Install the Parimutuel SDK in your project by running npm i @hxronetwork/parimutuelsdk

(Access the full Hxro Network documentation here)

Starting out:

Importing dependencies:

import * as web3 from "@solana/web3.js";
import * as sdk from "@hxronetwork/parimutuelsdk";

Connecting to Hxro Network’s Parimutuel protocol:

  • You can connect to the parimutuel network with ParimutuelWeb3(config, connection)

  • To connect to Devnet you can pass in sdk.DEV_CONFIG for config and new web3.Connection(web3.clusterApiUrl(’devnet’), ‘confirmed’) for connection

const config = sdk.DEV_CONFIG
const rpc = web3.clusterApiUrl('devnet')
const connection = new web3.Connection( rpc, 'confirmed')

const parimutuelWeb3 = new sdk.ParimutuelWeb3(config, connection)
  • To connect to Mainnet you can pass in sdk.MAINNET_CONFIG for config and new web3.Connection(web3.clusterApiUrl(’mainnet-beta’), ‘confirmed’) for connection

const config = sdk.MAINNET_CONFIG
const rpc = web3.clusterApiUrl('mainnet-beta')
const connection = new web3.Connection(rpc, 'confirmed')

const parimutuelWeb3 = new sdk.ParimutuelWeb3(config, connection)

Tip: Instead of the web3.clusterApiUrl() you can pass in your own RPC URL (as a string) for a better connection to Solana since the public ones tend to be more highly saturated

const config = sdk.MAINNET_CONFIG
const rpc = "<https://your-rpc-url.com>"
const connection = new web3.Connection(rpc, 'confirmed')

const parimutuelWeb3 = new sdk.ParimutuelWeb3(config, connection)

You should now be connected to the Parimutuel Network.

Retrieving Data from the Network:

Now that you have connected to the network, let's look at how to retrieve data from it.

Understanding Markets and Contests:

Before we start, there are some key terms you should be familiar with:

  • Markets are the different parimutuel markets for a specific underlying asset pair. For example, on the Paris protocol, there are currently three main markets that can be accessed through MarketPairEnum:

    • MarketPairEnum.BTCUSD

    • MarketPairEnum.ETHUSD

    • MarketPairEnum.SOLUSD

    At the time of writing, liquidity is being solely allocated to the BTCUSD market pair.

  • Contests represent the individual trading events in a paris market. For example:

    • Contest #1:

      • Market: BTCUSD

      • Time: 5 minutes

      • Slot ID: 123

      • Long Pool: $100.00

      • Short Pool: $200.00

      • etc.

    • Contest #2:

      • Market: BTCUSD

      • Time: 5 minutes

      • Slot ID: 124

      • Long Pool: $150.00

      • Short Pool: $250.00

      • etc.

    And so on.

<aside> 📕 Paris is a shortened term for parimutuels.

</aside>

Retrieving Contests for a Market:

To retrieve all of the Contests for the BTCUSD market pair.

  1. To filter for the market pair that we want, use the getMarketPubkeys(*config, marketPair*) function, and pass in the config object for *config* and MarketPairEnum.BTCUSD for *marketPair*. This will return an array with all the markets for each expiry and their contests for each expiry in the BTCUSD pair.

const market = sdk.MarketPairEnum.BTCUSD
const markets = sdk.getMarketPubkeys(config, market);
  1. To filter for the expiry interval, use the .filter() method on the markets array, and pass in a callback function that checks if the duration property of each element is equal to the desired expiry time in seconds. For example, to filter for a 1-minute expiry interval:

const marketTerm = 60 // The expires are in seconds, so this would be the 1 min

const marketsByTime = markets.filter(
      (market) => market.duration === marketTerm
    );
  • Expires in Seconds reference sheet:

    • 1 minute: 60

    • 5 minutes: 300

    • 15 minutes: 900

    • 1 hour: 3600

    • 1 day: 86400

  1. To retrieve all of the paris contests for the BTCUSD market, use the getParimutuels(*markets, number*) function and pass in the marketsByTime array for *markets*, and the number of contests you want to retrieve for *number*. This function should be called from an asynchronous function:

const Paris = async () => {

	const parimutuels = await parimutuelWeb3.getParimutuels(marketsByTime, 5);

}

#1 Checkpoint ⛳

Here is a summary of the code we have covered so far:

import * as web3 from "@solana/web3.js";
import * as sdk from "@hxronetwork/parimutuelsdk";

const config = sdk.MAINNET_CONFIG;
const rpc = "<https://your-rpc-url.com>";
const connection = new web3.Connection(rpc, "confirmed");

const parimutuelWeb3 = new sdk.ParimutuelWeb3(config, connection);
const market = sdk.MarketPairEnum.BTCUSD;
const markets = sdk.getMarketPubkeys(config, market);
const marketTerm = 60; // The expires are in seconds, so this would be the 1 min
const marketsByTime = markets.filter(
  (market) => market.duration === marketTerm
);

const Paris = async () => {
    
  const parimutuels = await parimutuelWeb3.getParimutuels(marketsByTime, 5);

 
};

parimutuels is will give us an array of objects, with each object representing a Contest for the BTCUSD pair in the 1 min expiration interval. To better understand what a Contest object looks like, we can retrieve the first element in the array using parimutuels[0] and convert it to a string using JSON.stringify(). Then, we can print the string to the console by calling the **Paris()**function.

const Paris = async () => {

  const parimutuels = await parimutuelWeb3.getParimutuels(marketsByTime, 5);

  console.log(JSON.stringify(parimutuels[0]))
};

Paris()
  • After calling the Paris() function, you should have printed out something like this in your console:

    {
        "pubkey": "DUxxhftnEiWPgS6x9yQh9PEHGYdQXKsHtkis6hXCYnH9",
        "account": {
          "executable": false,
          "owner": "GUhB2ohrfqWspztgCrQpAmeVFBWmnWYhPcZuwY52WWRe",
          "lamports": 3375600,
          "data": {
            "type": "Buffer",
            "data": [ //Bunch of numbers we don't need right now
            ]
          }
        },
        "info": {
          "parimutuel": {
            "version": 4,
            "programId": "GUhB2ohrfqWspztgCrQpAmeVFBWmnWYhPcZuwY52WWRe",
            "bumpSeed": 255,
            "strike": "0187d769d510",
            "index": "0187d0708dd0",
            "slot": "63a7efc8",
            "marketOpen": "00",
            "marketClose": "018547ffbae0",
            "timeWindowStart": "018547ffbae0",
            "marketKey": "2YEMg8LfsDaj2w8XexhumJDvi1gwqAz2grrzhn1PTxtG",
            "honeypot": "C3HWV298Y29nSKiuexTdUxNopFLu752R7mZAeY4fcPtH",
            "numPositions": "03",
            "numPositionsSettled": "03",
            "numPositionsDestroyed": "00",
            "expired": 1,
            "rewardPerShare": "537c",
            "activeLongPositions": "0ff21320",
            "activeShortPositions": "0e056d0b",
            "networkFees": {
              "enum": 3,
              "version": 8,
              "protocolTokenRequiredForMinFee": "012c0096",
              "minProtocolFeeBps": 0,
              "maxProtocolFeeBps": 0,
              "minSettlementFeeBps": 0,
              "maxSettlementFeeBps": 0,
              "_padding1": 0,
              "_padding2": 0,
              "_padding4": 0,
              "_padding8": "00",
              "_padding8_0": "00",
              "_padding8_1": "00",
              "_padding8_2": "00",
              "_padding8_3": "00"
            },
            "marketFees": {
              "enum": 1,
              "version": 8,
              "protocolFeeBps": "00",
              "settlementFeeBps": "00",
              "_padding1": 0,
              "_padding2": 0,
              "_padding8": "00",
              "_padding16": "00",
              "_padding8_0": "00",
              "_padding8_1": "00",
              "_padding8_2": "00",
              "_padding8_3": "00"
            },
            "creator": "8ye1k4uMM5NDAAK6ntpe37BSTvNWRdsfbJTMz1GmSPF9"
          }
        }
      }

This may look like a lot, but we won't be using all of this data yet. First, we will focus on the info of each Contest

To start, we want to display the following information for each contest:

  • strike: the mark price in USDC at which the contest will exercise

  • slot: the unique identifier number of the contest

  • activeLongPositions: the amount of USDC in the "Long" side of the pool

  • activeShortPositions: the amount of USDC in the "Short" side of the pool

  • expired: a boolean value indicating whether the contest has expired or not

Note: values in USDC should be divided by 1,000,000, as this is the number of decimals the USDC SPL token has on Solana.

To display this information, we can use a forEach loop to iterate through the parimutuels array. We will assign the variable cont for each object in the array and access the relevant data within the info.parimutuel section of the object. Then, we will print the information to the console for each contest. Here's how we can do this:

const Paris = async () => {

  const parimutuels = await parimutuelWeb3.getParimutuels(marketsByTime, 5);

	console.log(`\\nMarket Pair: BTCUSD\\nMarket Expiry Interval: 1 min\\n`)

	const usdcDec = 1_000_000

  parimutuels.forEach((cont) => {
      const strike = cont.info.parimutuel.strike.toNumber() / usdcDec
			const slotId = cont.info.parimutuel.slot.toNumber()
			const longSide = cont.info.parimutuel.activeLongPositions.toNumber() / usdcDec
			const shortSide = cont.info.parimutuel.activeShortPositions.toNumber() / usdcDec
			const expired = cont.info.parimutuel.expired

			console.log(`\\nStrike: $ ${strike}\\nSlot: ${slotId}\\nLongs: $ ${longSide}\\nShorts: $ ${shortSide}\\nExprired?: ${expired? 'true' : 'false'}`)
  })
};

Paris()
  • You should be getting something like this in your console now:

    Market Pair: BTCUSD
    Market Expiry Interval: 1 min
    
    Strike: $ 1680400.999999
    Slot: 1671990780
    Longs: $ 249.841564
    Shorts: $ 252.0767
    Exprired?: true
    
    Strike: $ 1680495.75
    Slot: 1671990840
    Longs: $ 248.210444
    Shorts: $ 251.789555
    Exprired?: true
    
    Strike: $ 1680574.5
    Slot: 1671990900
    Longs: $ 272.52943
    Shorts: $ 231.91463
    Exprired?: true
    
    Strike: $ 1680553.749999
    Slot: 1671990900
    Longs: $ 241.585833
    Shorts: $ 261.873957
    Exprired?: false
    
    Strike: $ 0
    Slot: 1671990960
    Longs: $ 232.321538
    Shorts: $ 269.23845
    Exprired?: false
    
    Strike: $ 0
    Slot: 0
    Longs: $ 0
    Shorts: $ 0
    Exprired?: false

#2 Checkpoint ⛳

Here is a summary of the code we have covered so far:

import * as web3 from "@solana/web3.js";
import * as sdk from "@hxronetwork/parimutuelsdk";

const config = sdk.MAINNET_CONFIG;
const rpc = "<https://your-rpc-url.com>";
const connection = new web3.Connection(rpc, "confirmed");

const parimutuelWeb3 = new sdk.ParimutuelWeb3(config, connection);
const market = sdk.MarketPairEnum.BTCUSD;
const markets = sdk.getMarketPubkeys(config, market);
const marketTerm = 60; // The expires are in seconds, so this would be the 1 min
const marketsByTime = markets.filter(
  (market) => market.duration === marketTerm
);

const Paris = async () => {

  const parimutuels = await parimutuelWeb3.getParimutuels(marketsByTime, 5);

  console.log(`\\nMarket Pair: BTCUSD\\nMarket Expiry Interval: 1 min\\n`)

  const usdcDec = 1_000_000 

  parimutuels.forEach((cont) => {
      const strike = cont.info.parimutuel.strike.toNumber() / usdcDec
			const slotId = cont.info.parimutuel.slot.toNumber()
			const longSide = cont.info.parimutuel.activeLongPositions.toNumber() / usdcDec
			const shortSide = cont.info.parimutuel.activeShortPositions.toNumber() / usdcDec
			const expired = cont.info.parimutuel.expired

    console.log(`\\nStrike: ${strike}\\nSlot: ${slotId}\\nLongs: ${longSide}\\nShorts: ${shortSide}\\nExprired?: ${expired? 'true' : 'false'}`)
  })
};

Paris()

BONUS: Get odds for each contest

We can use the function calculateOdd: (side, *total*) from the SDK by passing in the side that we want to get the Odds for *side* and the sum of both longSide and shortSide for *total*

const totalVolume = longSide + shortSide

const longOdds = sdk.calculateNetOdds(longSide, totalVolume, 0.03)
const shortOdds = sdk.calculateNetOdds(shortSide, totalVolume, 0.03)

// Pass in 0.03 to take into account the 3% Hxro Network standard fee 
// (50% of it goes to stakers)

We've now successfully used the Paris Network to retrieve contest and market data.

Interacting with the Network:

Learn how to use:

  • PositionSideEnum

  • placePosition()

  • getUserPositions()

  • ParimutuelPositions[]

  • destroyPosition()

  • destroyPositionEntruy()

  • getParimutuelPositions()

  • getStore()

  • createStore()

Last updated