Skip to content

Commit

Permalink
Merge pull request coingecko#2118 from coingecko/curve
Browse files Browse the repository at this point in the history
Add support for Curve finance
  • Loading branch information
tmlee authored Jun 8, 2020
2 parents 2250d04 + bf64493 commit 0d25482
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ Or install it yourself as:
| Cryptopia | Y | Y | Y | | Y | | cryptopia | |
| Crytrex | Y | Y [x] | | | Y | Y | crytrex | |
| Currency | Y | | | | Y | Y | currency | |
| Curve | Y | | | | Y | | curve | |
| Cybex | Y | | Y | | Y | | cybex | |
| Dach.Exchange | Y | | | | Y | Y | dach_exchange | |
| Dakuce | Y | Y | Y | | Y | | dakuce | |
Expand Down
17 changes: 17 additions & 0 deletions lib/cryptoexchange/exchanges/curve/authentication.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Cryptoexchange::Exchanges
module Curve
class Authentication < Cryptoexchange::Services::Authentication
def api_key
HashHelper.dig(Cryptoexchange::Credentials.get(@exchange), 'api_key')
end

def headers
# Do nothing, no headers override needed for API key only
end

def required_credentials
%i(api_key)
end
end
end
end
16 changes: 16 additions & 0 deletions lib/cryptoexchange/exchanges/curve/market.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Cryptoexchange::Exchanges
module Curve
class Market < Cryptoexchange::Models::Market
NAME = 'curve'

def self.api_key
authentication = Cryptoexchange::Exchanges::Curve::Authentication.new(
:market,
Cryptoexchange::Exchanges::Curve::Market::NAME
)
authentication.validate_credentials!
authentication.api_key
end
end
end
end
62 changes: 62 additions & 0 deletions lib/cryptoexchange/exchanges/curve/services/market.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module Cryptoexchange::Exchanges
module Curve
module Services
class Market < Cryptoexchange::Services::Market
class << self
def supports_individual_ticker_query?
true
end
end

HOURS_24 = 24*60*60

def fetch(market_pair)
tokens_response = TheGraphClient::Client.query(TheGraphClient::TokensQuery)
tokens = tokens_response.data.tokens

# Get the token information
# Decimals to calculate price, Id to pass into graphql
base_token = tokens.select { |token| token.symbol == market_pair.base_raw }.first
base_token_id = base_token.id
base_token_decimals = base_token.decimals.to_i
target_token = tokens.select { |token| token.symbol == market_pair.target_raw }.first
target_token_id = target_token.id
target_token_decimals = target_token.decimals.to_i

# Get swaps from both direction
# Example, DAI-ETH and ETH-DAI
swaps_response = TheGraphClient::Client.query(TheGraphClient::SwapsQuery, variables: { fromToken: base_token_id, toToken: target_token_id, range_timestamp: Time.now.to_i - HOURS_24 })
swaps_response_inverse = TheGraphClient::Client.query(TheGraphClient::SwapsQuery, variables: { fromToken: target_token_id, toToken: base_token_id, range_timestamp: Time.now.to_i - HOURS_24 })
latest_swap = swaps_response.data.swaps.first
latest_swap_inverse = swaps_response_inverse.data.swaps.first

# Put the latest swap from both side together so we can compare and get the latest of the two
latest_swaps = []
latest_swaps << latest_swap if latest_swap
latest_swaps << latest_swap_inverse if latest_swap_inverse
last_swap = latest_swaps.max_by { |k| k.timestamp}

if last_swap
last_price = 1.0 / last_swap.underlying_price.to_f
volume = swaps_response.data.swaps.map(&:from_token_amount).map { |s| s.to_f / 10**base_token_decimals }.sum
volume_inverse = swaps_response_inverse.data.swaps.map(&:to_token_amount).map { |s| s.to_f / 10**base_token_decimals }.sum

adapt(last_price, market_pair, volume + volume_inverse)
end
end

def adapt(last_price, market_pair, volume)
ticker = Cryptoexchange::Models::Ticker.new
ticker.base = market_pair.base
ticker.target = market_pair.target
ticker.market = Curve::Market::NAME
ticker.last = last_price.to_f
ticker.volume = volume.to_f
ticker.timestamp = nil
ticker.payload = { last_price: last_price, volume: volume }
ticker
end
end
end
end
end
29 changes: 29 additions & 0 deletions lib/cryptoexchange/exchanges/curve/services/pairs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module Cryptoexchange::Exchanges
module Curve
module Services
class Pairs < Cryptoexchange::Services::Pairs
def pairs_url
"https://api.blocklytics.org/pools/v1/pairs?platform=Curve&key=#{Cryptoexchange::Exchanges::Curve::Market.api_key}"
end

def fetch
output = fetch_via_api(pairs_url)
adapt(output)
end

def adapt(output)
market_pairs = []
output.each do |pair|
base, target = pair["pair"].split("-")
market_pairs << Cryptoexchange::Models::MarketPair.new(
base: base,
target: target,
market: Curve::Market::NAME,
)
end
market_pairs
end
end
end
end
end
36 changes: 36 additions & 0 deletions lib/cryptoexchange/exchanges/curve/the_graph_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require "graphql/client"
require "graphql/client/http"

module Cryptoexchange::Exchanges
module Curve
class TheGraphClient < Cryptoexchange::Models::Market
HTTP = GraphQL::Client::HTTP.new("https://api.thegraph.com/subgraphs/name/blocklytics/curve") do
end
Schema = GraphQL::Client.load_schema(HTTP)
Client = GraphQL::Client.new(schema: Schema, execute: HTTP)

TokensQuery = Client.parse <<-'GRAPHQL'
query
{
tokens {
id
symbol
decimals
}
}
GRAPHQL

SwapsQuery = Client.parse <<-'GRAPHQL'
query($fromToken: String!, $toToken: String!, $range_timestamp: BigInt!)
{
swaps(orderBy: timestamp, orderDirection: desc, where: { isUnderlyingSwap: false, fromToken: $fromToken, toToken: $toToken, timestamp_gte: $range_timestamp } ){
fromTokenAmount,
toTokenAmount,
underlyingPrice,
timestamp
}
}
GRAPHQL
end
end
end

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0d25482

Please sign in to comment.