In order to ensure easy integration with external partners Overtime V2 API is created. API returns all main data available on Overtime V2. Using Overtime V2 API endpoints someone can get data about:
More details about each API endpoint with request/response examples also can be found under Postman documentation.
Contract integration
Once all data are fetched from V2 API, the next step is integration with Overtime V2 contracts. Integration for both, a single or a parlay should be done with the Sports AMM V2 contract. Integration for live markets should be done with theLive Trading Processor contract.
The next sections describe integration with Overtime V2 API and Overtime V2 contracts together with JS code examples.
Buy a ticket
Users placing trades with THALES will get 1% extra payouts for each game they have on their ticket.
Let's say someone wants to buy a 2-game ticket with a buy-in amount of 20 THALES:
Integration with Overtime V2 API and Sports AMM V2 contract should include the following steps:
Select ticket markets and positions and get a quote for a ticket from Overtime V2 API.
NOTE: This step is not mandatory. The trading method on contract requires a total quote as a parameter, but that can be calculated by simply multiplying market odds. However, the API method returns some additional data, like ticket liquidity or validation errors, if any.
Get a Sports AMM V2 contract address for a specific network from Thales V2 contracts
Get a Live Trading Processor contract address for a specific network from Thales V2 contracts
Get a Live Trading Processor contract ABI from the Overtime V2 contract repository
Create a Live Trading Processor contract instance
Call requestLiveTrade method on Live Trading Processor contract with input parameters fetched from Overtime V2 API in steps #1 and #2
Wait for the request to finish - fulfilled successfully or failed with some error (e.g. odds changed)
The JS code snippet below implements these steps:
import axios from"axios";import bytes32 from"bytes32";import dotenv from"dotenv";import { ethers } from"ethers";import w3utils from"web3-utils";import liveTradingProcessorContractAbi from "./liveTradingProcessorContractAbi.js"; // Live Trading Processor contract ABI
dotenv.config();constAPI_URL="https://overtimemarketsv2.xyz"; // base API URLconstNETWORK_ID=10; // Optimism network IDconstNETWORK="optimism"; // Optimism networkconst LIVE_TRADING_PROCCESSOR_CONTRACT_ADDRESS = "0x3b834149F21B9A6C2DDC9F6ce97F2FD1097F8EAB"; // Live Trading Processor contract address on Optimism
constBUY_IN=10; // 20 USDCconstPOSITION=2; // drawconstCOLLATERAL_DECIMALS=6; // USDC decimals: 6const COLLATERAL_ADDRESS = "0x0000000000000000000000000000000000000000"; // USDC contract address (can be ZERO address since USDC is default collateral)
constSLIPPAGE=0.02; // slippage 2%const REFERRAL_ADDRESS = "0x0000000000000000000000000000000000000000"; // referral address, set to ZERO address for testing
// create instance of Infura provider for Optimism networkconstprovider=newethers.providers.InfuraProvider( { chainId:Number(NETWORK_ID), name:NETWORK },process.env.INFURA,);// create wallet instance for provided private key and providerconstwallet=newethers.Wallet(process.env.PRIVATE_KEY, provider);// create instance of Live Trading Processor contractconstliveTradingProcessor=newethers.Contract(LIVE_TRADING_PROCCESSOR_CONTRACT_ADDRESS, liveTradingProcessorContractAbi, wallet,);constdelay= (time) => {returnnewPromise(function (resolve) {setTimeout(resolve, time); });};constconvertFromBytes32= (value) => {constresult=bytes32({ input: value });returnresult.replace(/\0/g,"");};constbuyLivePosition=async () => {try {// get live markets from Overtime V2 APIconstmarketsResponse=awaitaxios.get(`${API_URL}/overtime-v2/networks/${NETWORK_ID}/live-markets`);constmarkets=marketsResponse.data.markets;// get a Tokyo Verdy 1969 - Consadole Sapporo marketconstmarket= markets[2];console.log(`Game: ${market.homeTeam} - ${market.awayTeam}`);// convert market odds got from API to BigNumberconstparsedQuote=ethers.utils.parseEther(market.odds[POSITION].normalizedImplied.toString());// convert buy-in amount to BigNumberconstparsedBuyInAmount=ethers.utils.parseUnits(BUY_IN.toString(),COLLATERAL_DECIMALS);// convert slippage tolerance to BigNumberconstparsedSlippage=ethers.utils.parseEther(SLIPPAGE.toString());// get max allowed execution delay from Live Trading Processor contractconstmaxAllowedExecutionDelay=Number(awaitliveTradingProcessor.maxAllowedExecutionDelay());// call trade method on Sports AMM V2 contractconsttx=awaitliveTradingProcessor.requestLiveTrade( { _gameId:convertFromBytes32(market.gameId),// use converted from bytes32 gameId field from API for gameId _sportId:market.subLeagueId,// use subLeagueId field from API for sportId _typeId:market.typeId, _position:POSITION, _line:market.line *100,// multiple lines by 100 because the contract can not accept decimals _buyInAmount: parsedBuyInAmount, _expectedQuote: parsedQuote, _additionalSlippage: parsedSlippage, _referrer:REFERRAL_ADDRESS, _collateral:COLLATERAL_ADDRESS, }, { type:2, maxPriorityFeePerGas:w3utils.toWei("0.00000000000000001"), }, );// wait for the resultconsttxResult=awaittx.wait();if (txResult) {console.log("Live trade requested. Fulfilling live trade...");constrequestId=txResult.events.find((event) =>event.event ==="LiveTradeRequested").args[2];let requestInProgress =true;conststartTime=Date.now();console.log(`Fulfill start time: ${newDate(startTime)}`);while (requestInProgress) {constisFulfilled=awaitliveTradingProcessor.requestIdToFulfillAllowed(requestId);console.log(`Is fulfilled: ${isFulfilled}`);if (isFulfilled) {console.log(`Fulfill end time: ${newDate(Date.now())}`);console.log(`Fulfill duration: ${(Date.now() - startTime) /1000} seconds`);console.log(`Successfully bought live position from Sports AMM V2`); requestInProgress =false; } else {// Add buffer of 10 seconds to wait for request to start executionif (Date.now() - startTime >= (Number(maxAllowedExecutionDelay) +10) *1000) {console.log("Odds changed while fulfilling the order. Try increasing the slippage."); requestInProgress =false; } else {awaitdelay(1000); } } } } } catch (e) {console.log("Failed to buy live position from Sports AMM V2", e); }};buyLivePosition();