Add notional asset lookup (#239)
* Add notional job * fix coingecko integration * Add deployment for notional jobs --------- Co-authored-by: Agustin Pazos <agpazos85@gmail.com>
This commit is contained in:
parent
3c597917f5
commit
4f438ae188
|
@ -0,0 +1,13 @@
|
|||
ENVIRONMENT=production
|
||||
NAMESPACE=wormscan
|
||||
NAME=wormscan-notional-job
|
||||
IMAGE_NAME=
|
||||
RESOURCES_LIMITS_MEMORY=30Mi
|
||||
RESOURCES_LIMITS_CPU=20m
|
||||
RESOURCES_REQUESTS_MEMORY=15Mi
|
||||
RESOURCES_REQUESTS_CPU=10m
|
||||
P2P_NETWORK=mainnet
|
||||
COINGECKO_URL=https://api.coingecko.com/api/v3
|
||||
NOTIONAL_CHANNEL=WORMSCAN:NOTIONAL
|
||||
LOG_LEVEL=INFO
|
||||
CRONTAB_SCHEDULE=*/5 * * * *
|
|
@ -0,0 +1,13 @@
|
|||
ENVIRONMENT=staging
|
||||
NAMESPACE=wormscan
|
||||
NAME=wormscan-notional-job
|
||||
IMAGE_NAME=
|
||||
RESOURCES_LIMITS_MEMORY=30Mi
|
||||
RESOURCES_LIMITS_CPU=20m
|
||||
RESOURCES_REQUESTS_MEMORY=15Mi
|
||||
RESOURCES_REQUESTS_CPU=10m
|
||||
P2P_NETWORK=mainnet
|
||||
COINGECKO_URL=https://api.coingecko.com/api/v3
|
||||
NOTIONAL_CHANNEL=WORMSCAN:NOTIONAL
|
||||
LOG_LEVEL=INFO
|
||||
CRONTAB_SCHEDULE=*/5 * * * *
|
|
@ -0,0 +1,13 @@
|
|||
ENVIRONMENT=test
|
||||
NAMESPACE=wormscan-testnet
|
||||
NAME=wormscan-notional-job
|
||||
IMAGE_NAME=
|
||||
RESOURCES_LIMITS_MEMORY=30Mi
|
||||
RESOURCES_LIMITS_CPU=20m
|
||||
RESOURCES_REQUESTS_MEMORY=15Mi
|
||||
RESOURCES_REQUESTS_CPU=10m
|
||||
P2P_NETWORK=mainnet
|
||||
COINGECKO_URL=https://api.coingecko.com/api/v3
|
||||
NOTIONAL_CHANNEL=WORMSCAN:NOTIONAL
|
||||
LOG_LEVEL=INFO
|
||||
CRONTAB_SCHEDULE=*/5 * * * *
|
|
@ -0,0 +1,34 @@
|
|||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: notional
|
||||
namespace: {{ .NAMESPACE }}
|
||||
spec:
|
||||
schedule: "{{ .CRONTAB_SCHEDULE }}"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .NAME }}
|
||||
image: {{ .IMAGE_NAME }}
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ENV
|
||||
value: {{ .ENVIRONMENT }}
|
||||
- name: P2P_NETWORK
|
||||
value: {{ .P2P_NETWORK }}
|
||||
- name: LOG_LEVEL
|
||||
value: {{ .LOG_LEVEL }}
|
||||
- name: JOB_ID
|
||||
value: JOB_NOTIONAL_USD
|
||||
- name: COINGECKO_URL
|
||||
value: {{ .COINGECKO_URL }}
|
||||
- name: NOTIONAL_CHANNEL
|
||||
value: {{ .NOTIONAL_CHANNEL }}
|
||||
- name: CACHE_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: config
|
||||
key: redis-uri
|
||||
restartPolicy: OnFailure
|
|
@ -0,0 +1,21 @@
|
|||
# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
|
||||
FROM --platform=linux/amd64 docker.io/golang:1.19.2@sha256:0467d7d12d170ed8d998a2dae4a09aa13d0aa56e6d23c4ec2b1e4faacf86a813 AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY jobs jobs
|
||||
COPY common common
|
||||
|
||||
# Build the Go app
|
||||
RUN cd jobs && CGO_ENABLED=0 GOOS=linux go build -o "./jobs-app" cmd/main.go
|
||||
|
||||
############################
|
||||
# STEP 2 build a small image
|
||||
############################
|
||||
FROM alpine
|
||||
#Copy certificates
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
# Copy our static executable.
|
||||
COPY --from=build "/app/jobs/jobs-app" "/jobs-app"
|
||||
# Run the binary.
|
||||
ENTRYPOINT ["/jobs-app"]
|
|
@ -0,0 +1,17 @@
|
|||
SHELL := /bin/bash
|
||||
|
||||
|
||||
## help: print this help message
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo 'Usage:'
|
||||
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
|
||||
|
||||
build:
|
||||
go build -o jobs cmd/main.go
|
||||
|
||||
test:
|
||||
go test -v -cover ./...
|
||||
|
||||
|
||||
.PHONY: build doc test
|
|
@ -0,0 +1,2 @@
|
|||
# Jobs
|
||||
This component contains the jobs to be scheduler.
|
|
@ -0,0 +1,67 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/common/logger"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/jobs/config"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/jobs/internal/coingecko"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/notional"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type exitCode int
|
||||
|
||||
func main() {
|
||||
defer handleExit()
|
||||
context := context.Background()
|
||||
|
||||
// get the config
|
||||
cfg, errConf := config.New(context)
|
||||
if errConf != nil {
|
||||
log.Fatal("error creating config", errConf)
|
||||
}
|
||||
|
||||
logger := logger.New("wormhole-explorer-jobs", logger.WithLevel(cfg.LogLevel))
|
||||
logger.Info("started job execution", zap.String("job_id", cfg.JobID))
|
||||
|
||||
var err error
|
||||
switch cfg.JobID {
|
||||
case jobs.JobIDNotional:
|
||||
notionalJob := initNotionalJob(context, cfg, logger)
|
||||
err = notionalJob.Run()
|
||||
default:
|
||||
logger.Fatal("Invalid job id", zap.String("job_id", cfg.JobID))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error("failed job execution", zap.String("job_id", cfg.JobID), zap.Error(err))
|
||||
} else {
|
||||
logger.Info("finish job execution successfully", zap.String("job_id", cfg.JobID))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// initNotionalJob initializes notional job.
|
||||
func initNotionalJob(ctx context.Context, cfg *config.Configuration, logger *zap.Logger) *notional.NotionalJob {
|
||||
// init coingecko api client.
|
||||
api := coingecko.NewCoingeckoAPI(cfg.CoingeckoURL)
|
||||
// init redis client.
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: cfg.CacheURL})
|
||||
// create notional job.
|
||||
notionalJob := notional.NewNotionalJob(api, redisClient, cfg.P2pNetwork, cfg.NotionalChannel, logger)
|
||||
return notionalJob
|
||||
}
|
||||
|
||||
func handleExit() {
|
||||
if r := recover(); r != nil {
|
||||
if e, ok := r.(exitCode); ok {
|
||||
os.Exit(int(e))
|
||||
}
|
||||
panic(r) // not an Exit, bubble up
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Package config implement a simple configuration package.
|
||||
// It define a type [Configuration] that represent the aplication configuration
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/sethvargo/go-envconfig"
|
||||
)
|
||||
|
||||
// Configuration is the configuration for the job
|
||||
type Configuration struct {
|
||||
Env string `env:"ENV,default=development"`
|
||||
LogLevel string `env:"LOG_LEVEL,default=INFO"`
|
||||
JobID string `env:"JOB_ID,required"`
|
||||
CoingeckoURL string `env:"COINGECKO_URL,required"`
|
||||
CacheURL string `env:"CACHE_URL,required"`
|
||||
NotionalChannel string `env:"NOTIONAL_CHANNEL,required"`
|
||||
P2pNetwork string `env:"P2P_NETWORK,required"`
|
||||
}
|
||||
|
||||
// New creates a configuration with the values from .env file and environment variables.
|
||||
func New(ctx context.Context) (*Configuration, error) {
|
||||
_ = godotenv.Load(".env", "../.env")
|
||||
|
||||
var configuration Configuration
|
||||
if err := envconfig.Process(ctx, &configuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &configuration, nil
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
module github.com/wormhole-foundation/wormhole-explorer/jobs
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/sethvargo/go-envconfig v0.9.0
|
||||
github.com/wormhole-foundation/wormhole-explorer/common v0.0.0-20230417134228-3c597917f5c8
|
||||
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20230417145436-53703d8ffcf0
|
||||
go.uber.org/zap v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/ethereum/go-ethereum v1.10.21 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/holiman/uint256 v1.2.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.27.6 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
)
|
|
@ -0,0 +1,118 @@
|
|||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/ethereum/go-ethereum v1.10.21 h1:5lqsEx92ZaZzRyOqBEXux4/UR06m296RGzN3ol3teJY=
|
||||
github.com/ethereum/go-ethereum v1.10.21/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
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 v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o=
|
||||
github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
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.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
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.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE=
|
||||
github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/wormhole-foundation/wormhole-explorer/common v0.0.0-20230417134228-3c597917f5c8 h1:nJPjdHphY0JGPorg3GrGzIf8J4YR1eyTalxT7MzPIZg=
|
||||
github.com/wormhole-foundation/wormhole-explorer/common v0.0.0-20230417134228-3c597917f5c8/go.mod h1:wySbOH0GO2dRhkTktCCCBnZ4FgNIpy3fL4hEbNMz5KI=
|
||||
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20230417145436-53703d8ffcf0 h1:uEJOLDlkpDxpShkCbFobYPd3MHZpNpkpt0+iyQnb9x4=
|
||||
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20230417145436-53703d8ffcf0/go.mod h1:dE12DOucCq23gjGGGhtbyx41FBxuHxjpPvG+ArO+8t0=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
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.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
@ -0,0 +1,89 @@
|
|||
package coingecko
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||
)
|
||||
|
||||
// CoingeckoAPI is a client for the coingecko API
|
||||
type CoingeckoAPI struct {
|
||||
url string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewCoingeckoAPI creates a new coingecko client
|
||||
func NewCoingeckoAPI(url string) *CoingeckoAPI {
|
||||
return &CoingeckoAPI{
|
||||
url: url,
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
}
|
||||
|
||||
// NotionalUSD is the response from the coingecko API.
|
||||
type NotionalUSD struct {
|
||||
Price *float64 `json:"usd"`
|
||||
}
|
||||
|
||||
// GetNotionalUSD returns the notional USD value for the given ids
|
||||
// ids is a list of coingecko chain identifier.
|
||||
func (c *CoingeckoAPI) GetNotionalUSD(ids []string) (map[string]NotionalUSD, error) {
|
||||
var response map[string]NotionalUSD
|
||||
notionalUrl := fmt.Sprintf("%s/simple/price?ids=%s&vs_currencies=usd", c.url, strings.Join(ids, ","))
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, notionalUrl, nil)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
err = json.Unmarshal(body, &response)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// GetChainIDs returns the coingecko chain ids for the given p2p network.
|
||||
func GetChainIDs(p2pNetwork string) []string {
|
||||
if p2pNetwork == domain.P2pMainNet {
|
||||
return []string{
|
||||
"solana",
|
||||
"ethereum",
|
||||
"terra-luna",
|
||||
"binancecoin",
|
||||
"matic-network",
|
||||
"avalanche-2",
|
||||
"oasis-network",
|
||||
"algorand",
|
||||
"aurora",
|
||||
"fantom",
|
||||
"karura",
|
||||
"acala",
|
||||
"klay-token",
|
||||
"celo",
|
||||
"near",
|
||||
"moonbeam",
|
||||
"neon",
|
||||
"terra-luna-2",
|
||||
"injective-protocol",
|
||||
"aptos",
|
||||
"sui",
|
||||
"arbitrum",
|
||||
"optimism",
|
||||
"xpla",
|
||||
"bitcoin",
|
||||
"base-protocol"}
|
||||
}
|
||||
// TODO: define chains ids for testnet.
|
||||
return []string{}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Package jobs define an interface to execute jobs
|
||||
package jobs
|
||||
|
||||
// JobIDNotional is the job id for notional job.
|
||||
const (
|
||||
JobIDNotional = "JOB_NOTIONAL_USD"
|
||||
)
|
||||
|
||||
// Job is the interface for jobs.
|
||||
type Job interface {
|
||||
Run() error
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
// Package notional contains the logic to get the notional value of assets
|
||||
package notional
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/jobs/internal/coingecko"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// NotionalCacheKey is the cache key for notional value by chainID
|
||||
const NotionalCacheKey = "WORMSCAN:NOTIONAL:CHAIN_ID:%d"
|
||||
|
||||
// NotionalJob is the job to get the notional value of assets.
|
||||
type NotionalJob struct {
|
||||
coingeckoAPI *coingecko.CoingeckoAPI
|
||||
cacheClient *redis.Client
|
||||
cacheChannel string
|
||||
p2pNetwork string
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewNotionalJob creates a new notional job.
|
||||
func NewNotionalJob(api *coingecko.CoingeckoAPI, cacheClient *redis.Client, p2pNetwork, cacheChannel string, logger *zap.Logger) *NotionalJob {
|
||||
return &NotionalJob{
|
||||
coingeckoAPI: api,
|
||||
cacheClient: cacheClient,
|
||||
cacheChannel: cacheChannel,
|
||||
p2pNetwork: p2pNetwork,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the notional job.
|
||||
func (j *NotionalJob) Run() error {
|
||||
// get chains coingecko ids by p2p network.
|
||||
chainIDs := coingecko.GetChainIDs(j.p2pNetwork)
|
||||
if len(chainIDs) == 0 {
|
||||
return fmt.Errorf("no chain ids found for p2p network %s", j.p2pNetwork)
|
||||
}
|
||||
|
||||
// get notional value of assets.
|
||||
coingeckoNotionals, err := j.coingeckoAPI.GetNotionalUSD(chainIDs)
|
||||
if err != nil {
|
||||
j.logger.Error("failed to get notional value of assets",
|
||||
zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// convert notionals with coingecko assets ids to notionals with wormhole chainIDs.
|
||||
notionals := convertToWormholeChainIDs(coingeckoNotionals)
|
||||
|
||||
// save notional value of assets in cache.
|
||||
err = j.updateNotionalCache(notionals)
|
||||
if err != nil {
|
||||
j.logger.Error("failed to update notional value of assets in cache",
|
||||
zap.Error(err),
|
||||
zap.Any("notionals", notionals))
|
||||
return err
|
||||
}
|
||||
|
||||
// publish notional value of assets to redis pubsub.
|
||||
err = j.cacheClient.Publish(j.cacheChannel, "NOTIONA_UPDATED").Err()
|
||||
if err != nil {
|
||||
j.logger.Error("failed to publish notional update message to redis pubsub",
|
||||
zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateNotionalCache updates the notional value of assets in cache.
|
||||
func (j *NotionalJob) updateNotionalCache(notionals map[vaa.ChainID]NotionalCacheField) error {
|
||||
for chainID, notional := range notionals {
|
||||
key := fmt.Sprintf(NotionalCacheKey, chainID)
|
||||
err := j.cacheClient.Set(key, notional, 0).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotionalCacheField is the notional value of assets in cache.
|
||||
type NotionalCacheField struct {
|
||||
NotionalUsd float64 `json:"notional_usd"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
||||
func (n NotionalCacheField) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(n)
|
||||
}
|
||||
|
||||
// convertToWormholeChainIDs converts the coingecko chain ids to wormhole chain ids.
|
||||
func convertToWormholeChainIDs(m map[string]coingecko.NotionalUSD) map[vaa.ChainID]NotionalCacheField {
|
||||
w := make(map[vaa.ChainID]NotionalCacheField, len(m))
|
||||
now := time.Now()
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
case "solana":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDSolana] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "ethereum":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDEthereum] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "terra-luna":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDTerra] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "binancecoin":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDBSC] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "matic-network":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDPolygon] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "avalanche-2":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDAvalanche] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "oasis-network":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDOasis] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "algorand":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDAlgorand] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "aurora":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDAurora] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "fantom":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDFantom] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "karura":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDKarura] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "acala":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDAcala] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "klay-token":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDKlaytn] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "celo":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDCelo] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "near":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDNear] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "moonbeam":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDMoonbeam] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "neon":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDNeon] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "terra-luna-2":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDTerra2] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "injective-protocol":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDInjective] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "aptos":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDAptos] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "sui":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDSui] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "arbitrum":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDArbitrum] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "optimism":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDOptimism] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "xpla":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDXpla] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "bitcoin":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDBtc] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
case "base-protocol":
|
||||
if v.Price != nil {
|
||||
w[vaa.ChainIDBase] = NotionalCacheField{NotionalUsd: *v.Price, UpdatedAt: now}
|
||||
}
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
Loading…
Reference in New Issue