mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-09 22:28:04 -05:00
148 lines
4.3 KiB
Go
148 lines
4.3 KiB
Go
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
// Package pricefeed implements routines to retrieve on-chain price feeds from chainlink's
|
|
// decentralized oracle network.
|
|
package pricefeed
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/cockroachdb/apd/v3"
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
logging "github.com/ipfs/go-log"
|
|
|
|
"github.com/athanorlabs/atomic-swap/common"
|
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
|
)
|
|
|
|
const (
|
|
// mainnetEndpoint is a mainnet ethereum endpoint, from
|
|
// https://chainlist.org/chain/1, which stagenet users get pointed at for
|
|
// price feeds, as Sepolia doesn't have an XMR feed. Mainnet users will use
|
|
// the same ethereum endpoint that they use for other swap transactions.
|
|
mainnetEndpoint = "https://eth-rpc.gateway.pokt.network"
|
|
|
|
// https://data.chain.link/ethereum/mainnet/crypto-usd/eth-usd
|
|
chainlinkETHToUSDProxy = "0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419"
|
|
|
|
// https://data.chain.link/ethereum/mainnet/crypto-usd/xmr-usd
|
|
chainlinkXMRToUSDProxy = "0xfa66458cce7dd15d8650015c4fce4d278271618f"
|
|
)
|
|
|
|
var (
|
|
errUnsupportedNetwork = errors.New("unsupported network")
|
|
log = logging.Logger("pricefeed")
|
|
)
|
|
|
|
// PriceFeed contains the interesting data from a chainlink price feed query.
|
|
type PriceFeed struct {
|
|
Description string // "COIN / USD"
|
|
Price *apd.Decimal
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// GetETHUSDPrice returns the current ETH/USD price from the Chainlink oracle.
|
|
// It errors if the chain ID is not the Ethereum mainnet.
|
|
func GetETHUSDPrice(ctx context.Context, ec *ethclient.Client) (*PriceFeed, error) {
|
|
chainID, err := ec.ChainID(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch chainID.Uint64() {
|
|
case common.MainnetChainID:
|
|
// No extra work to do
|
|
case common.SepoliaChainID:
|
|
// Push stagenet/sepolia users to a mainnet endpoint
|
|
ec, err = ethclient.Dial(mainnetEndpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer ec.Close()
|
|
case common.GanacheChainID, common.HardhatChainID:
|
|
return &PriceFeed{
|
|
Description: "ETH / USD (fake)",
|
|
Price: apd.New(123412345678, -8), // 1234.12345678
|
|
UpdatedAt: time.Now(),
|
|
}, nil
|
|
default:
|
|
return nil, errUnsupportedNetwork
|
|
}
|
|
|
|
return getChainlinkPriceFeed(ctx, chainlinkETHToUSDProxy, ec)
|
|
}
|
|
|
|
// GetXMRUSDPrice returns the current XMR/USD price from the Chainlink oracle.
|
|
// It errors if the chain ID is not the Ethereum mainnet.
|
|
func GetXMRUSDPrice(ctx context.Context, ec *ethclient.Client) (*PriceFeed, error) {
|
|
chainID, err := ec.ChainID(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch chainID.Uint64() {
|
|
case common.MainnetChainID:
|
|
// No extra work to do
|
|
case common.SepoliaChainID:
|
|
// Push stagenet/sepolia users to a mainnet endpoint
|
|
ec, err = ethclient.Dial(mainnetEndpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer ec.Close()
|
|
case common.GanacheChainID, common.HardhatChainID:
|
|
return &PriceFeed{
|
|
Description: "XMR / USD (fake)",
|
|
Price: apd.New(12312345678, -8), // 123.12345678
|
|
UpdatedAt: time.Now(),
|
|
}, nil
|
|
default:
|
|
return nil, errUnsupportedNetwork
|
|
}
|
|
|
|
return getChainlinkPriceFeed(ctx, chainlinkXMRToUSDProxy, ec)
|
|
}
|
|
|
|
// getChainlinkPriceFeed retries the latest price feed data from the given contract address.
|
|
func getChainlinkPriceFeed(ctx context.Context, feedAddress string, ec *ethclient.Client) (*PriceFeed, error) {
|
|
chainlinkPriceFeedProxy, err := contracts.NewAggregatorV3Interface(ethcommon.HexToAddress(feedAddress), ec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
opts := &bind.CallOpts{
|
|
Context: ctx,
|
|
}
|
|
|
|
roundData, err := chainlinkPriceFeedProxy.LatestRoundData(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
decimals, err := chainlinkPriceFeedProxy.Decimals(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
price := apd.NewWithBigInt(new(apd.BigInt).SetMathBigInt(roundData.Answer), -int32(decimals))
|
|
_, _ = price.Reduce(price) // push even multiples of 10 to the exponent
|
|
updatedAt := time.Unix(roundData.UpdatedAt.Int64(), 0)
|
|
|
|
description, err := chainlinkPriceFeedProxy.Description(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Debugf("%s: $%s (%s)", description, price, updatedAt)
|
|
return &PriceFeed{
|
|
Description: description,
|
|
Price: price,
|
|
UpdatedAt: updatedAt,
|
|
}, nil
|
|
}
|