[API/ANALYTICS] Scorecards endpoint (#232)
### Summary This pull request adds the `GET /api/v1/scorecards` endpoint, which is required by the wormscan frontend. Most of the fields that this endpoint should return are being omitted because the data is not currently available on the backend. Those fields will be added iteratively as the data becomes available. The current format of the response is: ```json { "total_tx_count": "1300200", "24h_tx_count": "4200" } ``` Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/221 ## Deployment details In order to populate the `"total_tx_count"` metric, a task is needed in influxdb: ``` $ cat total-vaa-count.flux option task = { name: "Total number of emitted VAAs", every: 1m } from(bucket: "wormhole-explorer") |> range(start: 2018-01-01T00:00:00Z) |> filter(fn: (r) => r._measurement == "vaa_count") |> group(columns: ["_measurement"]) |> set(key: "_measurement", value: "total_vaa_count") |> count() |> map(fn: (r) => ({r with _time: now()})) |> map(fn: (r) => ({r with _field: "total_vaa_count"})) |> to(bucket: "wormhole-explorer", org: "xlabs") ```
This commit is contained in:
parent
18efc01460
commit
c8aba636e4
|
@ -923,6 +923,26 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/scorecards": {
|
||||
"get": {
|
||||
"description": "Returns a list of KPIs for Wormhole.",
|
||||
"tags": [
|
||||
"Wormscan"
|
||||
],
|
||||
"operationId": "get-scorecards",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/transactions.ScorecardsResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/vaas/": {
|
||||
"get": {
|
||||
"description": "Returns all VAAs. Output is paginated and can also be be sorted.",
|
||||
|
@ -2306,6 +2326,9 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"transactions.ScorecardsResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"transactions.TransactionCountResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -916,6 +916,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/scorecards": {
|
||||
"get": {
|
||||
"description": "Returns a list of KPIs for Wormhole.",
|
||||
"tags": [
|
||||
"Wormscan"
|
||||
],
|
||||
"operationId": "get-scorecards",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/transactions.ScorecardsResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/vaas/": {
|
||||
"get": {
|
||||
"description": "Returns all VAAs. Output is paginated and can also be be sorted.",
|
||||
|
@ -2299,6 +2319,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"transactions.ScorecardsResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"transactions.TransactionCountResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -482,6 +482,8 @@ definitions:
|
|||
volume:
|
||||
type: number
|
||||
type: object
|
||||
transactions.ScorecardsResponse:
|
||||
type: object
|
||||
transactions.TransactionCountResult:
|
||||
properties:
|
||||
count:
|
||||
|
@ -1216,6 +1218,19 @@ paths:
|
|||
description: Internal Server Error
|
||||
tags:
|
||||
- Wormscan
|
||||
/api/v1/scorecards:
|
||||
get:
|
||||
description: Returns a list of KPIs for Wormhole.
|
||||
operationId: get-scorecards
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/transactions.ScorecardsResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
tags:
|
||||
- Wormscan
|
||||
/api/v1/vaas/:
|
||||
get:
|
||||
description: Returns all VAAs. Output is paginated and can also be be sorted.
|
||||
|
|
|
@ -7,6 +7,14 @@ import (
|
|||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
)
|
||||
|
||||
type Scorecards struct {
|
||||
// Number of VAAs emitted since the creation of the network (does not include Pyth messages)
|
||||
TotalTxCount string
|
||||
|
||||
// Number of VAAs emitted in the last 24 hours (does not include Pyth messages).
|
||||
TxCount24h string
|
||||
}
|
||||
|
||||
type GlobalTransactionDoc struct {
|
||||
ID string `bson:"_id" json:"id"`
|
||||
OriginTx *OriginTx `bson:"originTx" json:"originTx"`
|
||||
|
|
|
@ -43,6 +43,21 @@ from(bucket: "%s")
|
|||
|> map(fn:(r) => ( {_time: r._time, count: r._value}))
|
||||
`
|
||||
|
||||
const queryTemplateTotalTxCount = `
|
||||
from(bucket: "%s")
|
||||
|> range(start: 2018-01-01T00:00:00Z)
|
||||
|> filter(fn: (r) => r._field == "total_vaa_count")
|
||||
|> last()
|
||||
`
|
||||
|
||||
const queryTemplateTxCount24h = `
|
||||
from(bucket: "%s")
|
||||
|> range(start: -24h)
|
||||
|> filter(fn: (r) => r._measurement == "vaa_count")
|
||||
|> group(columns: ["_measurement"])
|
||||
|> count()
|
||||
`
|
||||
|
||||
type Repository struct {
|
||||
influxCli influxdb2.Client
|
||||
queryAPI api.QueryAPI
|
||||
|
@ -100,6 +115,83 @@ func (r *Repository) buildFindVolumeQuery(q *ChainActivityQuery) string {
|
|||
return fmt.Sprintf(queryTemplate, r.bucket, start, stop, operation)
|
||||
}
|
||||
|
||||
func (r *Repository) GetScorecards(ctx context.Context) (*Scorecards, error) {
|
||||
|
||||
totalTxCount, err := r.getTotalTxCount(ctx)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to query total transaction count", zap.Error(err))
|
||||
}
|
||||
|
||||
txCount24h, err := r.getTxCount24h(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query 24h transactions: %w", err)
|
||||
}
|
||||
|
||||
// build the result and return
|
||||
scorecards := Scorecards{
|
||||
TotalTxCount: totalTxCount,
|
||||
TxCount24h: txCount24h,
|
||||
}
|
||||
|
||||
return &scorecards, nil
|
||||
}
|
||||
|
||||
func (r *Repository) getTotalTxCount(ctx context.Context) (string, error) {
|
||||
|
||||
// query 24h transactions
|
||||
query := fmt.Sprintf(queryTemplateTotalTxCount, r.bucket)
|
||||
result, err := r.queryAPI.Query(ctx, query)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to query total transaction count", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
if result.Err() != nil {
|
||||
r.logger.Error("total transaction count query result has errors", zap.Error(err))
|
||||
return "", result.Err()
|
||||
}
|
||||
if !result.Next() {
|
||||
return "", errors.New("expected at least one record in total transaction count query result")
|
||||
}
|
||||
|
||||
// deserialize the row returned
|
||||
row := struct {
|
||||
Value uint64 `mapstructure:"_value"`
|
||||
}{}
|
||||
if err := mapstructure.Decode(result.Record().Values(), &row); err != nil {
|
||||
return "", fmt.Errorf("failed to decode total transaction count query response: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprint(row.Value), nil
|
||||
}
|
||||
|
||||
func (r *Repository) getTxCount24h(ctx context.Context) (string, error) {
|
||||
|
||||
// query 24h transactions
|
||||
query := fmt.Sprintf(queryTemplateTxCount24h, r.bucket)
|
||||
result, err := r.queryAPI.Query(ctx, query)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to query 24h transactions", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
if result.Err() != nil {
|
||||
r.logger.Error("24h transactions query result has errors", zap.Error(err))
|
||||
return "", result.Err()
|
||||
}
|
||||
if !result.Next() {
|
||||
return "", errors.New("expected at least one record in 24h transactions query result")
|
||||
}
|
||||
|
||||
// deserialize the row returned
|
||||
row := struct {
|
||||
Value uint64 `mapstructure:"_value"`
|
||||
}{}
|
||||
if err := mapstructure.Decode(result.Record().Values(), &row); err != nil {
|
||||
return "", fmt.Errorf("failed to decode 24h transaction count query response: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprint(row.Value), nil
|
||||
}
|
||||
|
||||
// GetTransactionCount get the last transactions.
|
||||
func (r *Repository) GetTransactionCount(ctx context.Context, q *TransactionCountQuery) ([]TransactionCountResult, error) {
|
||||
query := r.buildLastTrxQuery(q)
|
||||
|
|
|
@ -24,6 +24,10 @@ func (s *Service) GetTransactionCount(ctx context.Context, q *TransactionCountQu
|
|||
return s.repo.GetTransactionCount(ctx, q)
|
||||
}
|
||||
|
||||
func (s *Service) GetScorecards(ctx context.Context) (*Scorecards, error) {
|
||||
return s.repo.GetScorecards(ctx)
|
||||
}
|
||||
|
||||
// GetChainActivity get chain activity.
|
||||
func (s *Service) GetChainActivity(ctx context.Context, q *ChainActivityQuery) ([]ChainActivityResult, error) {
|
||||
return s.repo.FindChainActivity(ctx, q)
|
||||
|
|
|
@ -63,9 +63,10 @@ func RegisterRoutes(
|
|||
api.Get("/address/:id", addressCtrl.FindById)
|
||||
|
||||
// analytics
|
||||
api.Get("/last-txs", transactionCtrl.GetLastTransactions)
|
||||
api.Get("/x-chain-activity", transactionCtrl.GetChainActivity)
|
||||
api.Get("/global-tx/:chain/:emitter/:sequence", transactionCtrl.FindGlobalTransactionByID)
|
||||
api.Get("/last-txs", transactionCtrl.GetLastTransactions)
|
||||
api.Get("/scorecards", transactionCtrl.GetScorecards)
|
||||
api.Get("/x-chain-activity", transactionCtrl.GetChainActivity)
|
||||
|
||||
// vaas resource
|
||||
vaas := api.Group("/vaas")
|
||||
|
|
|
@ -58,6 +58,30 @@ func (c *Controller) GetLastTransactions(ctx *fiber.Ctx) error {
|
|||
return ctx.JSON(lastTrx)
|
||||
}
|
||||
|
||||
// GetScorecards godoc
|
||||
// @Description Returns a list of KPIs for Wormhole.
|
||||
// @Tags Wormscan
|
||||
// @ID get-scorecards
|
||||
// @Success 200 {object} ScorecardsResponse
|
||||
// @Failure 500
|
||||
// @Router /api/v1/scorecards [get]
|
||||
func (c *Controller) GetScorecards(ctx *fiber.Ctx) error {
|
||||
|
||||
// Query indicators from the database
|
||||
scorecards, err := c.srv.GetScorecards(ctx.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert indicators to the response model
|
||||
response := ScorecardsResponse{
|
||||
TxCount24h: scorecards.TxCount24h,
|
||||
TotalTxCount: scorecards.TotalTxCount,
|
||||
}
|
||||
|
||||
return ctx.JSON(response)
|
||||
}
|
||||
|
||||
// GetChainActivity godoc
|
||||
// @Description Returns a list of tx by source chain and destination chain.
|
||||
// @Tags Wormscan
|
||||
|
|
|
@ -19,3 +19,23 @@ type Destination struct {
|
|||
type ChainActivity struct {
|
||||
Txs []Tx `json:"txs"`
|
||||
}
|
||||
|
||||
// ScorecardsResponse is the response model for the endpoint `GET /api/v1/scorecards`.
|
||||
type ScorecardsResponse struct {
|
||||
//TODO: we don't have the data for these fields yet, uncomment as the data becomes available.
|
||||
|
||||
//TVL string `json:"tvl"`
|
||||
|
||||
//TotalVolume string `json:"total_volume"`
|
||||
|
||||
// Number of VAAs emitted since the creation of the network (does not include Pyth messages)
|
||||
TotalTxCount string `json:"total_tx_count,omitempty"`
|
||||
|
||||
//Volume24h string `json:"24h_volume"`
|
||||
|
||||
// Number of VAAs emitted in the last 24 hours (does not include Pyth messages).
|
||||
TxCount24h string `json:"24h_tx_count"`
|
||||
|
||||
// Number of VAAs emitted in the last 24 hours (includes Pyth messages).
|
||||
//Messages24h string `json:"24h_messages"`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue