Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions cmd/constants/marketmaps/markets.go
Original file line number Diff line number Diff line change
Expand Up @@ -9718,6 +9718,40 @@ var (
PolymarketMarketMapJSON = `
{
"markets":{
"ARSENAL_WIN_PREMIER_LEAGUE/USD":{
"ticker":{
"currency_pair":{
"Base":"ARSENAL_WIN_PREMIER_LEAGUE",
"Quote":"USD"
},
"decimals":4,
"min_provider_count":1,
"enabled":true
},
"provider_configs":[
{
"name":"polymarket_api",
"off_chain_ticker":"71634047218945647363395171891674404459556716906851450020664198669471161667814"
}
]
},
"BTC_ABOVE_100K/USD":{
"ticker":{
"currency_pair":{
"Base":"BTC_ABOVE_100K",
"Quote":"USD"
},
"decimals":4,
"min_provider_count":1,
"enabled":true
},
"provider_configs":[
{
"name":"polymarket_api",
"off_chain_ticker":"70589272990567584699583734524973289598058001943159544537175680785792668210572"
}
]
},
"WILL_BERNIE_SANDERS_WIN_THE_2024_US_PRESIDENTIAL_ELECTION?YES/USD":{
"ticker":{
"currency_pair":{
Expand All @@ -9731,7 +9765,7 @@ var (
"provider_configs":[
{
"name":"polymarket_api",
"off_chain_ticker":"0x08f5fe8d0d29c08a96f0bc3dfb52f50e0caf470d94d133d95d38fa6c847e0925/95128817762909535143571435260705470642391662537976312011260538371392879420759"
"off_chain_ticker":"95128817762909535143571435260705470642391662537976312011260538371392879420759"
}
]
},
Expand All @@ -9748,7 +9782,7 @@ var (
"provider_configs":[
{
"name":"polymarket_api",
"off_chain_ticker":"0x1ab07117f9f698f28490f57754d6fe5309374230c95867a7eba572892a11d710/50107902083284751016545440401692219408556171231461347396738260657226842527986"
"off_chain_ticker":"50107902083284751016545440401692219408556171231461347396738260657226842527986"
}
]
}
Expand Down
86 changes: 9 additions & 77 deletions providers/apis/polymarket/api_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"math/big"
"net/http"
"strings"
"time"

"github.com/dydxprotocol/slinky/oracle/config"
Expand All @@ -18,7 +17,7 @@ const (
Name = "polymarket_api"

// URL is the default base URL of the Polymarket CLOB API. It uses the `markets` endpoint with a given market ID.
URL = "https://clob.polymarket.com/markets/%s"
URL = "https://clob.polymarket.com/midpoint?token_id=%s"

// priceAdjustmentMin is the value the price gets set to in the event of price == 0.
priceAdjustmentMin = 0.0001
Expand Down Expand Up @@ -62,56 +61,11 @@ func (h APIHandler) CreateURL(ids []types.ProviderTicker) (string, error) {
if len(ids) != 1 {
return "", fmt.Errorf("expected 1 ticker, got %d", len(ids))
}
marketID, _, err := getMarketAndTokenFromTicker(ids[0])
if err != nil {
return "", err
}
return fmt.Sprintf(h.api.Endpoints[0].URL, marketID), nil
}

type TokenData struct {
TokenID string `json:"token_id"`
Outcome string `json:"outcome"`
Price float64 `json:"price"`
return fmt.Sprintf(h.api.Endpoints[0].URL, ids[0]), nil
}

type MarketsResponse struct {
EnableOrderBook bool `json:"enable_order_book"`
Active bool `json:"active"`
Closed bool `json:"closed"`
Archived bool `json:"archived"`
AcceptingOrders bool `json:"accepting_orders"`
AcceptingOrderTimestamp time.Time `json:"accepting_order_timestamp"`
MinimumOrderSize int `json:"minimum_order_size"`
MinimumTickSize float64 `json:"minimum_tick_size"`
ConditionID string `json:"condition_id"`
QuestionID string `json:"question_id"`
Question string `json:"question"`
Description string `json:"description"`
MarketSlug string `json:"market_slug"`
EndDateIso time.Time `json:"end_date_iso"`
GameStartTime any `json:"game_start_time"`
SecondsDelay int `json:"seconds_delay"`
Fpmm string `json:"fpmm"`
MakerBaseFee int `json:"maker_base_fee"`
TakerBaseFee int `json:"taker_base_fee"`
NotificationsEnabled bool `json:"notifications_enabled"`
NegRisk bool `json:"neg_risk"`
NegRiskMarketID string `json:"neg_risk_market_id"`
NegRiskRequestID string `json:"neg_risk_request_id"`
Icon string `json:"icon"`
Image string `json:"image"`
Rewards struct {
Rates []struct {
AssetAddress string `json:"asset_address"`
RewardsDailyRate int `json:"rewards_daily_rate"`
} `json:"rates"`
MinSize int `json:"min_size"`
MaxSpread float64 `json:"max_spread"`
} `json:"rewards"`
Is5050Outcome bool `json:"is_50_50_outcome"`
Tokens []TokenData `json:"tokens"`
Tags []string `json:"tags"`
type PriceResponse struct {
Mid *float64 `json:"mid,string"`
}

// ParseResponse parses the HTTP response from the markets endpoint of the Polymarket API endpoint and returns
Expand All @@ -120,30 +74,16 @@ func (h APIHandler) ParseResponse(ids []types.ProviderTicker, response *http.Res
if len(ids) != 1 {
return priceResponseError(ids, fmt.Errorf("expected 1 ticker, got %d", len(ids)), providertypes.ErrorInvalidResponse)
}

var result MarketsResponse
var result PriceResponse
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
return priceResponseError(ids, fmt.Errorf("failed to decode market response: %w", err), providertypes.ErrorFailedToDecode)
return priceResponseError(ids, fmt.Errorf("failed to decode price response"), providertypes.ErrorFailedToDecode)
}

_, tokenID, err := getMarketAndTokenFromTicker(ids[0])
if err != nil {
return priceResponseError(ids, err, providertypes.ErrorAPIGeneral)
if result.Mid == nil {
return priceResponseError(ids, fmt.Errorf("unable to get price from response"), providertypes.ErrorFailedToDecode)
}

var tokenData *TokenData
for _, token := range result.Tokens {
if token.TokenID == tokenID {
tokenData = &token
break
}
}

if tokenData == nil {
return priceResponseError(ids, fmt.Errorf("token ID %s not found in response", tokenID), providertypes.ErrorInvalidResponse)
}

price := new(big.Float).SetFloat64(tokenData.Price)
price := new(big.Float).SetFloat64(*result.Mid)

// switch price to priceAdjustmentMin if its 0.00.
if big.NewFloat(0.00).Cmp(price) == 0 {
Expand All @@ -163,11 +103,3 @@ func priceResponseError(ids []types.ProviderTicker, err error, code providertype
providertypes.NewErrorWithCode(err, code),
)
}

func getMarketAndTokenFromTicker(t types.ProviderTicker) (marketID string, tokenID string, err error) {
split := strings.Split(t.GetOffChainTicker(), "/")
if len(split) != 2 {
return "", "", fmt.Errorf("expected ticker format market_id/token_id, got: %s", t.GetOffChainTicker())
}
return split[0], split[1], nil
}
59 changes: 22 additions & 37 deletions providers/apis/polymarket/api_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
"github.com/dydxprotocol/slinky/oracle/types"
)

var candidateWinsElectionToken = types.DefaultProviderTicker{
OffChainTicker: "0xc6485bb7ea46d7bb89beb9c91e7572ecfc72a6273789496f78bc5e989e4d1638/95128817762909535143571435260705470642391662537976312011260538371392879420759",
var btcAbove100k = types.DefaultProviderTicker{
OffChainTicker: "109316475563207680750454013262168290636995541053876975584833586297692429518773",
}

func TestNewAPIHandler(t *testing.T) {
Expand Down Expand Up @@ -92,17 +92,17 @@ func TestCreateURL(t *testing.T) {
{
name: "too many",
pts: []types.ProviderTicker{
candidateWinsElectionToken,
candidateWinsElectionToken,
btcAbove100k,
btcAbove100k,
},
expErr: "expected 1 ticker, got 2",
},
{
name: "happy case",
pts: []types.ProviderTicker{
candidateWinsElectionToken,
btcAbove100k,
},
expectedURL: fmt.Sprintf(URL, "0xc6485bb7ea46d7bb89beb9c91e7572ecfc72a6273789496f78bc5e989e4d1638"),
expectedURL: fmt.Sprintf(URL, "109316475563207680750454013262168290636995541053876975584833586297692429518773"),
},
}
h, err := NewAPIHandler(DefaultAPIConfig)
Expand Down Expand Up @@ -130,48 +130,33 @@ func TestParseResponse(t *testing.T) {
expectedPrice *big.Float
}{
"happy path": {
data: `{"tokens": [{
"token_id": "95128817762909535143571435260705470642391662537976312011260538371392879420759",
"outcome": "Yes",
"price": 1}]}]}`,
ticker: []types.ProviderTicker{candidateWinsElectionToken},
data: `{"mid": "1"}`,
ticker: []types.ProviderTicker{btcAbove100k},
expectedPrice: big.NewFloat(1.00),
},
"zero resolution": {
data: `{"tokens": [{
"token_id": "95128817762909535143571435260705470642391662537976312011260538371392879420759",
"outcome": "Yes",
"price": 0}]}]}`,
ticker: []types.ProviderTicker{candidateWinsElectionToken},
data: `{"mid": "0"}`,
ticker: []types.ProviderTicker{btcAbove100k},
expectedPrice: big.NewFloat(priceAdjustmentMin),
},
"other values work": {
data: `{"tokens": [{
"token_id": "95128817762909535143571435260705470642391662537976312011260538371392879420759",
"outcome": "Yes",
"price": 0.325}]}]}`,
ticker: []types.ProviderTicker{candidateWinsElectionToken},
data: `{"mid": "0.325"}`,
ticker: []types.ProviderTicker{btcAbove100k},
expectedPrice: big.NewFloat(0.325),
},
"token not in response": {
data: `{"tokens": [{
"token_id": "35128817762909535143571435260705470642391662537976312011260538371392879420759",
"outcome": "Yes",
"price": 0.325}]}]}`,
ticker: []types.ProviderTicker{candidateWinsElectionToken},
expectedErr: "token ID 95128817762909535143571435260705470642391662537976312011260538371392879420759 not found in response",
},
"bad response data": {
data: `{"tokens": [{
"token_id":z,
"outcome": "Yes",
"price": 0.325}]}]}`,
ticker: []types.ProviderTicker{candidateWinsElectionToken},
expectedErr: "failed to decode market response",
data: `[{"mid": "0.325"}]}]`,
ticker: []types.ProviderTicker{btcAbove100k},
expectedErr: "failed to decode price response",
},
"missing price data": {
data: `{"not_price": "0.332"}`,
ticker: []types.ProviderTicker{btcAbove100k},
expectedErr: "unable to get price from response",
},
"too many tickers": {
data: `{"tokens": []}`,
ticker: []types.ProviderTicker{candidateWinsElectionToken, candidateWinsElectionToken},
data: `{"mid": "0.325"}`,
ticker: []types.ProviderTicker{btcAbove100k, btcAbove100k},
expectedErr: "expected 1 ticker, got 2",
},
}
Expand Down