Skip to content
Open
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
2 changes: 1 addition & 1 deletion pkg/rates/calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ type Mock struct {
// TonApiToken the token for TonApi to increase HTTP limits is obtained from https://tonconsole.com/tonapi
TonApiToken string
// URL to the CSV file from the analytics service https://tonconsole.com/analytics (data is sourced from the TonApi analytics database)
StonFiV1ResultUrl, StonFiV2ResultUrl, StonFiV2StableSwapResultUrl, SwapCoffeeResultUrl, BidaskResultUrl string
StonFiV1ResultUrl, StonFiV2ResultUrl, StonFiV2StableSwapResultUrl, SwapCoffeeResultUrl, BidaskResultUrl, MooncxResultUrl string
// URL to the CSV file from the analytics service https://tonconsole.com/analytics (data is sourced from the TonApi analytics database)
DedustResultUrl string
}
Expand Down
142 changes: 142 additions & 0 deletions pkg/rates/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
bidask string = "Bidask"
swapCoffee string = "SwapCoffee"
coinbase string = "Coinbase"
mooncx string = "Mooncx"
)

type Invariant int
Expand Down Expand Up @@ -483,6 +484,11 @@ func (m *Mock) getJettonPricesFromDex(pools map[ton.AccountID]float64) map[ton.A
URL: m.BidaskResultUrl,
PoolResponseConverter: m.convertBidaskPoolResponse,
},
{
Name: mooncx,
URL: m.MooncxResultUrl,
PoolResponseConverter: convertMooncxPoolResponse,
},
}
var actualAssets []Pool
var actualLpAssets []LpAsset
Expand Down Expand Up @@ -1139,6 +1145,142 @@ func (m *Mock) convertBidaskPoolResponse(respBody []byte) ([]Pool, []LpAsset, er
return actualAssets, nil, nil
}

func convertMooncxPoolResponse(respBody []byte) ([]Pool, []LpAsset, error) {
reader := csv.NewReader(bytes.NewReader(respBody))
records, err := reader.ReadAll()
if err != nil {
return nil, nil, err
}
parseDecimals := func(meta string) (int, error) {
if meta == "NULL" {
return defaultDecimals, nil
}
converted := make(map[string]any)
if err = json.Unmarshal([]byte(meta), &converted); err != nil {
return 0, err
}
value, ok := converted["decimals"]
if !ok || value == "NaN" {
value = fmt.Sprintf("%d", defaultDecimals)
}
decimals, err := strconv.Atoi(value.(string))
if err != nil {
return 0, err
}
return decimals, nil
}
parseAssets := func(record []string) (Pool, error) {
var firstAsset, secondAsset Asset
switch {
// If the column asset_1_native contains true,
// then we consider this token as a pool to TON
case record[0] == "true":
firstAsset = Asset{Account: references.PTonV1}
secondAccountID, err := ton.ParseAccountID(record[3])
if err != nil {
return Pool{}, err
}
secondAsset = Asset{Account: secondAccountID}
// If the column asset_2_native contains true,
// then we consider this token as a pool to TON
case record[2] == "true":
firstAccountID, err := ton.ParseAccountID(record[1])
if err != nil {
return Pool{}, err
}
firstAsset = Asset{Account: firstAccountID}
secondAsset = Asset{Account: references.PTonV1}
default:
// By default, we assume that the two assets are not paired with TON.
// This could be a pair like a jetton to USDT or to other jettons
firstAccountID, err := ton.ParseAccountID(record[1])
if err != nil {
return Pool{}, err
}
firstAsset = Asset{Account: firstAccountID}
secondAccountID, err := ton.ParseAccountID(record[3])
if err != nil {
return Pool{}, err
}
secondAsset = Asset{Account: secondAccountID}
}
firstAsset.Decimals, err = parseDecimals(record[4])
if err != nil {
return Pool{}, err
}
secondAsset.Decimals, err = parseDecimals(record[5])
if err != nil {
return Pool{}, err
}
firstAsset.Reserve, err = strconv.ParseFloat(record[6], 64)
if err != nil {
return Pool{}, err
}
secondAsset.Reserve, err = strconv.ParseFloat(record[7], 64)
if err != nil {
return Pool{}, err
}
firstAsset.HoldersCount, err = strconv.Atoi(record[8])
if err != nil {
return Pool{}, err
}
secondAsset.HoldersCount, err = strconv.Atoi(record[9])
if err != nil {
return Pool{}, err
}
pool := Pool{
Assets: []Asset{firstAsset, secondAsset},
Invariant: XYInv,
}
return pool, nil
}
parseLpAsset := func(record []string, firstAsset, secondAsset Asset) (LpAsset, error) {
lpAsset, err := ton.ParseAccountID(record[10])
if err != nil {
return LpAsset{}, err
}
decimals, err := strconv.ParseInt(record[11], 10, 64)
if err != nil {
return LpAsset{}, err
}
if record[12] == "0" {
return LpAsset{}, fmt.Errorf("unknown total supply")
}
totalSupply, ok := new(big.Int).SetString(record[12], 10)
if !ok {
return LpAsset{}, fmt.Errorf("failed to parse total supply")
}
return LpAsset{
Account: lpAsset,
Decimals: int(decimals),
TotalSupply: totalSupply,
Assets: []Asset{firstAsset, secondAsset},
}, nil
}
actualLpAssets := make(map[ton.AccountID]LpAsset)
for idx, record := range records {
if idx == 0 || len(record) < 13 { // Skip headers
continue
}
assets, err := parseAssets(record)
if err != nil {
slog.Error("[convertMooncxPoolResponse] failed to parse assets", slog.Any("error", err))
continue
}
firstAsset, secondAsset := assets.Assets[0], assets.Assets[1]
if firstAsset.Reserve == 0 || secondAsset.Reserve == 0 {
continue
}
lpAsset, err := parseLpAsset(record, firstAsset, secondAsset)
if err != nil {
continue
}
actualLpAssets[lpAsset.Account] = lpAsset
}

return nil, maps.Values(actualLpAssets), nil
}

func calculateLpAssetPrice(asset LpAsset, pools map[ton.AccountID]float64) float64 {
firstAsset := asset.Assets[0]
secondAsset := asset.Assets[1]
Expand Down
Loading