aboutsummaryrefslogtreecommitdiffstats
path: root/arbiter
diff options
context:
space:
mode:
Diffstat (limited to 'arbiter')
-rw-r--r--arbiter/.golangci.yml18
-rw-r--r--arbiter/Dockerfile18
-rw-r--r--arbiter/arbiter.go505
-rw-r--r--arbiter/go.mod17
-rw-r--r--arbiter/go.sum41
-rwxr-xr-xarbiter/tests.sh4
6 files changed, 0 insertions, 603 deletions
diff --git a/arbiter/.golangci.yml b/arbiter/.golangci.yml
deleted file mode 100644
index 878e031..0000000
--- a/arbiter/.golangci.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-run:
- concurrency: 16
- timeout: 5m
- modules-download-mode: readonly
- allow-parallel-runners: true
- allow-serial-runners: true
- go: '1.22'
-linters-settings:
- depguard:
- rules:
- srcs:
- listMode: "Strict"
- allow:
- - $gostd
- - github.com/go-redis/redis/v8
- - github.com/gorilla/mux
- - github.com/rs/zerolog
- - github.com/rs/zerolog/log
diff --git a/arbiter/Dockerfile b/arbiter/Dockerfile
deleted file mode 100644
index a512798..0000000
--- a/arbiter/Dockerfile
+++ /dev/null
@@ -1,18 +0,0 @@
-FROM golang:1.22-alpine3.19 as builder
-RUN apk update && apk upgrade
-RUN apk add go git
-COPY go.* /arbiter/
-RUN cd /arbiter && go mod download
-COPY *.go /arbiter/
-RUN cd /arbiter && go build
-
-FROM alpine:3.19 as certbuilder
-RUN apk add openssl
-WORKDIR /certs
-RUN openssl req -nodes -new -x509 -subj="/C=US/ST=Denial/L=springfield/O=Dis/CN=localhost" -keyout server.key -out server.cert
-
-# FROM gcr.io/distroless/static-debian10
-FROM alpine:3.19
-COPY --from=certbuilder /certs /certs
-COPY --from=builder /arbiter/arbiter /arbiter/
-ENTRYPOINT ["/arbiter/arbiter"]
diff --git a/arbiter/arbiter.go b/arbiter/arbiter.go
deleted file mode 100644
index 6145a45..0000000
--- a/arbiter/arbiter.go
+++ /dev/null
@@ -1,505 +0,0 @@
-package main
-
-import (
- "context"
- "crypto/tls"
- "encoding/json"
- "errors"
- "flag"
- "io"
- "net/http"
- "net/url"
- "os"
- "os/signal"
- "strconv"
- "sync"
- "time"
-
- "github.com/go-redis/redis/v8"
- "github.com/gorilla/mux"
- "github.com/rs/zerolog"
- "github.com/rs/zerolog/log"
-)
-
-var (
- errBadLogic = errors.New("we should not be here")
- errUnexpectedParam = errors.New("got unexpected parameter")
- errUnknownDeployment = errors.New("unknown deployment kind")
-)
-
-const (
- serverDeploymentType = "SERVER_DEPLOYMENT_TYPE"
- coingeckoAPIURLv3 = "https://api.coingecko.com/api/v3"
- coincapAPIURLv2 = "https://api.coincap.io/v2"
- getTimeout = 5
- httpClientTimeout = 5
- serverTLSReadTimeout = 15
- serverTLSWriteTimeout = 15
- defaultGracefulShutdown = 15
-)
-
-// https://docs.coincap.io/
-type CoinCapAssetGetResponseData struct {
- ID string `json:"id"`
- Rank string `json:"rank"`
- Symbol string `json:"symbol"`
- Name string `json:"name"`
- Supply string `json:"supply"`
- MaxSupply string `json:"maxSupply"`
- MarketCapUsd string `json:"marketCapUsd"`
- VolumeUsd24Hr string `json:"volumeUsd24Hr"`
- PriceUsd string `json:"priceUsd"`
- ChangePercent24Hr string `json:"changePercent24Hr"`
- Vwap24Hr string `json:"vwap24Hr"`
-}
-
-type priceResponseData struct {
- Name string `json:"name"`
- Price float64 `json:"price"`
- Unit string `json:"unit"`
- Err string `json:"err"`
- IsSuccessful bool `json:"isSuccessful"`
-}
-
-type CoinCapAssetGetResponse struct {
- Data CoinCapAssetGetResponseData `json:"data"`
- TimeStamp int64 `json:"timestamp"`
-}
-
-type HTTPHandlerFunc func(http.ResponseWriter, *http.Request)
-
-type HTTPHandler struct {
- name string
- function HTTPHandlerFunc
-}
-
-type priceChanStruct struct {
- name string
- price float64
-}
-
-type errorChanStruct struct {
- hasError bool
- err error
-}
-
-func GetProxiedClient() *http.Client {
- transport := &http.Transport{
- DisableKeepAlives: true,
- Proxy: http.ProxyFromEnvironment,
- }
- client := &http.Client{
- Transport: transport,
- Timeout: httpClientTimeout * time.Second,
- CheckRedirect: nil,
- Jar: nil,
- }
-
- return client
-}
-
-// OWASP: https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html
-func addSecureHeaders(writer *http.ResponseWriter) {
- (*writer).Header().Set("Cache-Control", "no-store")
- (*writer).Header().Set("Content-Security-Policy", "default-src https;")
- (*writer).Header().Set("Strict-Transport-Security", "max-age=63072000;")
- (*writer).Header().Set("X-Content-Type-Options", "nosniff")
- (*writer).Header().Set("X-Frame-Options", "DENY")
- (*writer).Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
-}
-
-func getPriceFromCoinGecko(
- ctx context.Context,
- name, unit string,
- wg *sync.WaitGroup,
- priceChan chan<- priceChanStruct,
- errChan chan<- errorChanStruct,
-) {
- defer wg.Done()
-
- priceFloat := 0.
-
- params := "/simple/price?ids=" + url.QueryEscape(name) + "&" +
- "vs_currencies=" + url.QueryEscape(unit)
- path := coingeckoAPIURLv3 + params
-
- client := GetProxiedClient()
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err)
-
- return
- }
-
- resp, err := client.Do(req)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err).Send()
-
- return
- }
- defer resp.Body.Close()
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err).Send()
- }
-
- jsonBody := make(map[string]interface{})
-
- err = json.Unmarshal(body, &jsonBody)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err).Send()
- }
-
- price, isOk := jsonBody[name].(map[string]interface{})
- if !isOk {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err)
-
- return
- }
-
- log.Info().Msg(string(body))
-
- priceFloat, isOk = price[unit].(float64)
- if !isOk {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err)
-
- return
- }
-
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: false, err: nil}
-}
-
-func getPriceFromCoinCap(
- ctx context.Context,
- name string,
- wg *sync.WaitGroup,
- priceChan chan<- priceChanStruct,
- errChan chan<- errorChanStruct,
-) {
- defer wg.Done()
-
- priceFloat := 0.
-
- params := "/assets/" + url.QueryEscape(name)
- path := coincapAPIURLv2 + params
-
- client := GetProxiedClient()
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err)
-
- return
- }
-
- resp, err := client.Do(req)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err).Send()
-
- return
- }
- defer resp.Body.Close()
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err)
- }
-
- var coinCapAssetGetResponse CoinCapAssetGetResponse
-
- err = json.Unmarshal(body, &coinCapAssetGetResponse)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err).Send()
- }
-
- priceFloat, err = strconv.ParseFloat(coinCapAssetGetResponse.Data.PriceUsd, 64)
- if err != nil {
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: true, err: err}
-
- log.Error().Err(err).Send()
- }
-
- log.Info().Msg(string(body))
-
- priceChan <- priceChanStruct{name: name, price: priceFloat}
- errChan <- errorChanStruct{hasError: false, err: nil}
-}
-
-func arbHandler(w http.ResponseWriter, r *http.Request) {
- w.Header().Add("Content-Type", "application/json")
-
- if r.Method != http.MethodGet {
- http.Error(w, "Method is not supported.", http.StatusNotFound)
- }
-
- addSecureHeaders(&w)
-
- var name string
-
- var unit string
-
- params := r.URL.Query()
- for key, value := range params {
- switch key {
- case "name":
- name = value[0]
- case "unit":
- unit = value[0]
- default:
- log.Error().Err(errUnexpectedParam)
- }
- }
-
- priceChan := make(chan priceChanStruct, 1)
- errChan := make(chan errorChanStruct, 1)
-
- var waitGroup sync.WaitGroup
-
- ctx, cancel := context.WithTimeout(context.Background(), getTimeout*time.Second)
- defer cancel()
-
- waitGroup.Add(1)
-
- //nolint:contextcheck
- getPriceFromCoinGecko(ctx, name, unit, &waitGroup, priceChan, errChan)
- waitGroup.Wait()
-
- select {
- case err := <-errChan:
- if err.hasError {
- log.Error().Err(err.err)
- }
- default:
- log.Error().Err(errBadLogic).Send()
- }
-
- var price priceChanStruct
- select {
- case priceCh := <-priceChan:
- price = priceCh
- default:
- log.Error().Err(errBadLogic)
- }
-
- responseData := priceResponseData{
- Name: price.name,
- Price: price.price,
- Unit: "USD",
- Err: "",
- IsSuccessful: true,
- }
-
- jsonResp, err := json.Marshal(responseData)
- if err != nil {
- cancel()
- //nolint:gocritic
- log.Fatal().Err(err)
- }
-
- _, err = w.Write(jsonResp)
- if err != nil {
- cancel()
- log.Fatal().Err(err)
- }
-}
-
-func coincapHandler(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodGet {
- http.Error(w, "Method is not supported.", http.StatusNotFound)
- }
-
- w.Header().Add("Content-Type", "application/json")
-
- addSecureHeaders(&w)
-
- var name string
-
- params := r.URL.Query()
- for key, value := range params {
- switch key {
- case "name":
- name = value[0]
- default:
- log.Error().Err(errUnexpectedParam).Send()
- }
- }
-
- priceChan := make(chan priceChanStruct, 1)
- errChan := make(chan errorChanStruct, 1)
-
- var waitGroup sync.WaitGroup
-
- waitGroup.Add(1)
-
- ctx, cancel := context.WithTimeout(context.Background(), getTimeout*time.Second)
- defer cancel()
-
- getPriceFromCoinCap(ctx, name, &waitGroup, priceChan, errChan)
- waitGroup.Wait()
-
- select {
- case err := <-errChan:
- if err.hasError {
- log.Error().Err(err.err)
- }
- default:
- log.Error().Err(errBadLogic)
- }
-
- var price priceChanStruct
- select {
- case priceCh := <-priceChan:
- price = priceCh
- default:
- log.Error().Err(errBadLogic)
- }
-
- responseData := priceResponseData{
- Name: price.name,
- Price: price.price,
- Unit: "USD",
- Err: "",
- IsSuccessful: true,
- }
-
- jsonResp, err := json.Marshal(responseData)
- if err != nil {
- cancel()
-
- log.Fatal().Err(err).Send()
- }
-
- _, err = w.Write(jsonResp)
- if err != nil {
- cancel()
- log.Fatal().Err(err)
- }
-}
-
-func setupLogging() {
- zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
-}
-
-func startServer(gracefulWait time.Duration,
- handlers []HTTPHandler,
- serverDeploymentType string, port string,
-) {
- route := mux.NewRouter()
- cfg := &tls.Config{
- MinVersion: tls.VersionTLS13,
- }
-
- srv := &http.Server{
- Addr: "0.0.0.0:" + port,
- WriteTimeout: time.Second * serverTLSWriteTimeout,
- ReadTimeout: time.Second * serverTLSReadTimeout,
- Handler: route,
- TLSConfig: cfg,
- }
-
- for i := range len(handlers) {
- route.HandleFunc(handlers[i].name, handlers[i].function)
- }
-
- go func() {
- var certPath, keyPath string
-
- switch os.Getenv(serverDeploymentType) {
- case "deployment":
- certPath = "/etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem"
- keyPath = "/etc/letsencrypt/live/api.terminaldweller.com/privkey.pem"
- case "test":
- certPath = "/certs/server.cert"
- keyPath = "/certs/server.key"
- default:
- log.Error().Err(errUnknownDeployment).Send()
- }
-
- if err := srv.ListenAndServeTLS(certPath, keyPath); err != nil {
- log.Error().Err(err)
- }
- }()
-
- c := make(chan os.Signal, 1)
-
- signal.Notify(c, os.Interrupt)
- <-c
-
- ctx, cancel := context.WithTimeout(context.Background(), gracefulWait)
- defer cancel()
-
- if err := srv.Shutdown(ctx); err != nil {
- log.Error().Err(err)
- }
-
- log.Info().Msg("gracefully shut down the server")
-}
-
-func main() {
- var gracefulWait time.Duration
-
- var rdb *redis.Client
-
- flag.DurationVar(
- &gracefulWait,
- "gracefulwait",
- time.Second*defaultGracefulShutdown,
- "the duration to wait during the graceful shutdown",
- )
-
- flagPort := flag.String("port", "8009", "determines the port the server will listen on")
- redisDB := flag.Int64("redisdb", 1, "determines the db number")
- redisAddress := flag.String("redisaddress", "redis:6379", "determines the address of the redis instance")
- redisPassword := flag.String("redispassword", "", "determines the password of the redis db")
- flag.Parse()
-
- rdb = redis.NewClient(&redis.Options{
- Addr: *redisAddress,
- Password: *redisPassword,
- DB: int(*redisDB),
- })
- defer rdb.Close()
-
- setupLogging()
-
- handlerFuncs := []HTTPHandler{
- {name: "/crypto/v1/arb/gecko", function: arbHandler},
- {name: "/crypto/v1/arb/coincap", function: coincapHandler},
- }
-
- startServer(gracefulWait, handlerFuncs, serverDeploymentType, *flagPort)
-}
diff --git a/arbiter/go.mod b/arbiter/go.mod
deleted file mode 100644
index a7a87c6..0000000
--- a/arbiter/go.mod
+++ /dev/null
@@ -1,17 +0,0 @@
-module arbiter
-
-go 1.22
-
-require (
- github.com/go-redis/redis/v8 v8.11.5
- github.com/gorilla/mux v1.8.1
- github.com/rs/zerolog v1.31.0
-)
-
-require (
- github.com/cespare/xxhash/v2 v2.2.0 // indirect
- github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
- github.com/mattn/go-colorable v0.1.13 // indirect
- github.com/mattn/go-isatty v0.0.20 // indirect
- golang.org/x/sys v0.15.0 // indirect
-)
diff --git a/arbiter/go.sum b/arbiter/go.sum
deleted file mode 100644
index 85a6437..0000000
--- a/arbiter/go.sum
+++ /dev/null
@@ -1,41 +0,0 @@
-github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
-github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
-github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
-github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
-github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
-github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
-github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
-github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/arbiter/tests.sh b/arbiter/tests.sh
deleted file mode 100755
index be923f3..0000000
--- a/arbiter/tests.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-
-curl -k -X GET "https://localhost:8009/crypto/v1/arb/gecko?name=ethereum&unit=usd"
-curl -k -X GET "https://localhost:8009/crypto/v1/arb/coincap?name=ethereum"