New endpoint: top chain pairs by number of transfers (#307)

### Summary

Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/275

This pull request implements the endpoint `GET /api/v1/top-chain-pairs-by-num-transfers`, which returns the chain pairs that have the most transfers.Internally, the endpoint uses data summarized daily to speed up query execution times.

This endpoint has a mandatory query parameter named timerange, which must be set to `7d`, `15d` or `30d`.
This commit is contained in:
agodnic 2023-05-12 13:05:18 -03:00 committed by GitHub
parent 25a675f99a
commit c25ebcb6fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 532 additions and 44 deletions

View File

@ -8,12 +8,14 @@ option task = {
start = date.sub(from: now(), d: 24h)
stop = now()
from(bucket: "wormscan-24hours-mainnet-staging")
from(bucket: "wormscan")
|> range(start: start, stop: stop)
|> filter(fn: (r) => r["_measurement"] == "vaa_volume")
|> filter(fn: (r) => r["_field"] == "volume")
|> drop(columns: ["app_id", "destination_address", "destination_chain"])
|> group(columns: ["emitter_chain", "token_address", "token_chain"])
|> sum(column: "_value")
|> set(key: "_measurement", value: "vaa_volume_24h")
|> set(key: "_measurement", value: "asset_volumes_24h")
|> set(key: "_field", value: "volume")
|> map(fn: (r) => ({r with _time: start}))
|> to(bucket: "wormscan-30days-mainnet-staging")
|> to(bucket: "wormscan-30days")

View File

@ -0,0 +1,21 @@
import "date"
option task = {
name: "chain pair transfers with 24-hour granularity",
every: 24h,
}
start = date.sub(from: now(), d: 24h)
stop = now()
from(bucket: "wormscan")
|> range(start: start, stop: stop)
|> filter(fn: (r) => r["_measurement"] == "vaa_volume")
|> filter(fn: (r) => r["_field"] == "volume")
|> drop(columns: ["app_id", "destination_address", "token_address", "token_chain", "_field"])
|> group(columns: ["emitter_chain", "destination_chain"])
|> count(column: "_value")
|> set(key: "_measurement", value: "chain_pair_transfers_24h")
|> set(key: "_field", value: "num_transfers")
|> map(fn: (r) => ({r with _time: start}))
|> to(bucket: "wormscan-30days")

View File

@ -943,6 +943,64 @@ const docTemplate = `{
}
}
},
"/api/v1/top-assets-by-volume": {
"get": {
"description": "Returns a list of the (emitter_chain, asset) pairs with the most volume.",
"tags": [
"Wormscan"
],
"operationId": "get-top-assets-by-volume",
"parameters": [
{
"type": "string",
"description": "Time span, supported values: 7d, 15d, 30d.",
"name": "timeSpan",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/transactions.TopAssetsResponse"
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/top-chain-pairs-by-num-transfers": {
"get": {
"description": "Returns a list of the (emitter_chain, destination_chain) pairs with the highest number of transfers.",
"tags": [
"Wormscan"
],
"operationId": "get-top-chain-pairs-by-num-transfers",
"parameters": [
{
"type": "string",
"description": "Time span, supported values: 7d, 15d, 30d.",
"name": "timeSpan",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/transactions.TopChainPairsResponse"
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/vaas/": {
"get": {
"description": "Returns all VAAs. Output is paginated and can also be be sorted.",
@ -2301,6 +2359,20 @@ const docTemplate = `{
}
}
},
"transactions.AssetWithVolume": {
"type": "object",
"properties": {
"emitterChain": {
"$ref": "#/definitions/vaa.ChainID"
},
"symbol": {
"type": "string"
},
"volume": {
"type": "string"
}
}
},
"transactions.ChainActivity": {
"type": "object",
"properties": {
@ -2312,6 +2384,20 @@ const docTemplate = `{
}
}
},
"transactions.ChainPair": {
"type": "object",
"properties": {
"destinationChain": {
"$ref": "#/definitions/vaa.ChainID"
},
"emitterChain": {
"$ref": "#/definitions/vaa.ChainID"
},
"numberOfTransfers": {
"type": "string"
}
}
},
"transactions.Destination": {
"type": "object",
"properties": {
@ -2333,12 +2419,38 @@ const docTemplate = `{
"description": "Number of VAAs emitted in the last 24 hours (does not include Pyth messages).",
"type": "string"
},
"24h_volume": {
"description": "Volume transferred through the token bridge in the last 24 hours, in USD.",
"type": "string"
},
"total_tx_count": {
"description": "Number of VAAs emitted since the creation of the network (does not include Pyth messages)",
"type": "string"
}
}
},
"transactions.TopAssetsResponse": {
"type": "object",
"properties": {
"assets": {
"type": "array",
"items": {
"$ref": "#/definitions/transactions.AssetWithVolume"
}
}
}
},
"transactions.TopChainPairsResponse": {
"type": "object",
"properties": {
"chainPairs": {
"type": "array",
"items": {
"$ref": "#/definitions/transactions.ChainPair"
}
}
}
},
"transactions.TransactionCountResult": {
"type": "object",
"properties": {

View File

@ -936,6 +936,64 @@
}
}
},
"/api/v1/top-assets-by-volume": {
"get": {
"description": "Returns a list of the (emitter_chain, asset) pairs with the most volume.",
"tags": [
"Wormscan"
],
"operationId": "get-top-assets-by-volume",
"parameters": [
{
"type": "string",
"description": "Time span, supported values: 7d, 15d, 30d.",
"name": "timeSpan",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/transactions.TopAssetsResponse"
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/top-chain-pairs-by-num-transfers": {
"get": {
"description": "Returns a list of the (emitter_chain, destination_chain) pairs with the highest number of transfers.",
"tags": [
"Wormscan"
],
"operationId": "get-top-chain-pairs-by-num-transfers",
"parameters": [
{
"type": "string",
"description": "Time span, supported values: 7d, 15d, 30d.",
"name": "timeSpan",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/transactions.TopChainPairsResponse"
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/vaas/": {
"get": {
"description": "Returns all VAAs. Output is paginated and can also be be sorted.",
@ -2294,6 +2352,20 @@
}
}
},
"transactions.AssetWithVolume": {
"type": "object",
"properties": {
"emitterChain": {
"$ref": "#/definitions/vaa.ChainID"
},
"symbol": {
"type": "string"
},
"volume": {
"type": "string"
}
}
},
"transactions.ChainActivity": {
"type": "object",
"properties": {
@ -2305,6 +2377,20 @@
}
}
},
"transactions.ChainPair": {
"type": "object",
"properties": {
"destinationChain": {
"$ref": "#/definitions/vaa.ChainID"
},
"emitterChain": {
"$ref": "#/definitions/vaa.ChainID"
},
"numberOfTransfers": {
"type": "string"
}
}
},
"transactions.Destination": {
"type": "object",
"properties": {
@ -2326,12 +2412,38 @@
"description": "Number of VAAs emitted in the last 24 hours (does not include Pyth messages).",
"type": "string"
},
"24h_volume": {
"description": "Volume transferred through the token bridge in the last 24 hours, in USD.",
"type": "string"
},
"total_tx_count": {
"description": "Number of VAAs emitted since the creation of the network (does not include Pyth messages)",
"type": "string"
}
}
},
"transactions.TopAssetsResponse": {
"type": "object",
"properties": {
"assets": {
"type": "array",
"items": {
"$ref": "#/definitions/transactions.AssetWithVolume"
}
}
}
},
"transactions.TopChainPairsResponse": {
"type": "object",
"properties": {
"chainPairs": {
"type": "array",
"items": {
"$ref": "#/definitions/transactions.ChainPair"
}
}
}
},
"transactions.TransactionCountResult": {
"type": "object",
"properties": {

View File

@ -466,6 +466,15 @@ definitions:
next:
type: string
type: object
transactions.AssetWithVolume:
properties:
emitterChain:
$ref: '#/definitions/vaa.ChainID'
symbol:
type: string
volume:
type: string
type: object
transactions.ChainActivity:
properties:
txs:
@ -473,6 +482,15 @@ definitions:
$ref: '#/definitions/transactions.Tx'
type: array
type: object
transactions.ChainPair:
properties:
destinationChain:
$ref: '#/definitions/vaa.ChainID'
emitterChain:
$ref: '#/definitions/vaa.ChainID'
numberOfTransfers:
type: string
type: object
transactions.Destination:
properties:
chain:
@ -488,11 +506,29 @@ definitions:
description: Number of VAAs emitted in the last 24 hours (does not include
Pyth messages).
type: string
24h_volume:
description: Volume transferred through the token bridge in the last 24 hours,
in USD.
type: string
total_tx_count:
description: Number of VAAs emitted since the creation of the network (does
not include Pyth messages)
type: string
type: object
transactions.TopAssetsResponse:
properties:
assets:
items:
$ref: '#/definitions/transactions.AssetWithVolume'
type: array
type: object
transactions.TopChainPairsResponse:
properties:
chainPairs:
items:
$ref: '#/definitions/transactions.ChainPair'
type: array
type: object
transactions.TransactionCountResult:
properties:
count:
@ -1242,6 +1278,46 @@ paths:
description: Internal Server Error
tags:
- Wormscan
/api/v1/top-assets-by-volume:
get:
description: Returns a list of the (emitter_chain, asset) pairs with the most
volume.
operationId: get-top-assets-by-volume
parameters:
- description: 'Time span, supported values: 7d, 15d, 30d.'
in: query
name: timeSpan
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/transactions.TopAssetsResponse'
"500":
description: Internal Server Error
tags:
- Wormscan
/api/v1/top-chain-pairs-by-num-transfers:
get:
description: Returns a list of the (emitter_chain, destination_chain) pairs
with the highest number of transfers.
operationId: get-top-chain-pairs-by-num-transfers
parameters:
- description: 'Time span, supported values: 7d, 15d, 30d.'
in: query
name: timeSpan
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/transactions.TopChainPairsResponse'
"500":
description: Internal Server Error
tags:
- Wormscan
/api/v1/vaas/:
get:
description: Returns all VAAs. Output is paginated and can also be be sorted.

View File

@ -19,7 +19,7 @@ type Scorecards struct {
Volume24h string
}
// AssetDTO is used for the return value of the function `GetTopAssetsByVolume`.
// AssetDTO is used for the return value of the function `GetTopAssets`.
type AssetDTO struct {
EmitterChain sdk.ChainID
TokenChain sdk.ChainID
@ -27,27 +27,34 @@ type AssetDTO struct {
Volume string
}
// TopAssetsTimerange is used as an input parameter for the function `GetTopAssetsByVolume`.
type TopAssetsTimerange string
// ChainPairDTO is used for the return value of the function `GetTopChainPairs`.
type ChainPairDTO struct {
EmitterChain sdk.ChainID
DestinationChain sdk.ChainID
NumberOfTransfers string
}
// TopStatisticsTimeSpan is used as an input parameter for the functions `GetTopAssets` and `GetTopChainPairs`.
type TopStatisticsTimeSpan string
const (
TopAssetsTimerange7Days TopAssetsTimerange = "7d"
TopAssetsTimerange15Days TopAssetsTimerange = "15d"
TopAssetsTimerange30Days TopAssetsTimerange = "30d"
TimeSpan7Days TopStatisticsTimeSpan = "7d"
TimeSpan15Days TopStatisticsTimeSpan = "15d"
TimeSpan30Days TopStatisticsTimeSpan = "30d"
)
// NewTopAssetsTimerange parses a string and returns a `TopAssetsTimerange`.
func NewTopAssetsTimerange(s string) (*TopAssetsTimerange, error) {
// ParseTopStatisticsTimeSpan parses a string and returns a `TopAssetsTimeSpan`.
func ParseTopStatisticsTimeSpan(s string) (*TopStatisticsTimeSpan, error) {
if s == string(TopAssetsTimerange7Days) ||
s == string(TopAssetsTimerange15Days) ||
s == string(TopAssetsTimerange30Days) {
if s == string(TimeSpan7Days) ||
s == string(TimeSpan15Days) ||
s == string(TimeSpan30Days) {
tmp := TopAssetsTimerange(s)
tmp := TopStatisticsTimeSpan(s)
return &tmp, nil
}
return nil, fmt.Errorf("invalid timerange: %s", s)
return nil, fmt.Errorf("invalid time span: %s", s)
}
type GlobalTransactionDoc struct {

View File

@ -61,13 +61,13 @@ from(bucket: "%s")
|> sum(column: "_value")
`
const queryTemplateTopAssetsByVolume = `
const queryTemplateTopAssets = `
import "date"
// Get historic volumes from the summarized metric.
summarized = from(bucket: "%s")
|> range(start: -%s)
|> filter(fn: (r) => r["_measurement"] == "vaa_volume_24h")
|> filter(fn: (r) => r["_measurement"] == "asset_volumes_24h")
|> group(columns: ["emitter_chain", "token_address", "token_chain"])
// Get the current day's volume from the unsummarized metric.
@ -76,6 +76,7 @@ startOfDay = date.truncate(t: now(), unit: 1d)
raw = from(bucket: "%s")
|> range(start: startOfDay)
|> filter(fn: (r) => r["_measurement"] == "vaa_volume")
|> filter(fn: (r) => r["_field"] == "volume")
|> group(columns: ["emitter_chain", "token_address", "token_chain"])
// Merge all results, compute the sum, return the top 7 volumes.
@ -86,6 +87,36 @@ union(tables: [summarized, raw])
|> top(columns: ["_value"], n: 7)
`
const queryTemplateTopChainPairs = `
import "date"
// Get historic number of transfers from the summarized metric.
summarized = from(bucket: "%s")
|> range(start: -%s)
|> filter(fn: (r) => r["_measurement"] == "chain_pair_transfers_24h")
|> group(columns: ["emitter_chain", "destination_chain"])
|> sum()
// Get the current day's number of transfers from the unsummarized metric.
// This assumes that the summarization task runs exactly once per day at 00:00hs
startOfDay = date.truncate(t: now(), unit: 1d)
raw = from(bucket: "%s")
|> range(start: startOfDay)
|> filter(fn: (r) => r["_measurement"] == "vaa_volume")
|> filter(fn: (r) => r["_field"] == "volume")
|> group(columns: ["emitter_chain", "destination_chain"])
|> drop(columns: ["app_id", "destination_address", "token_address", "token_chain", "_field"])
|> group(columns: ["emitter_chain", "destination_chain"])
|> count()
// Merge all results, compute the sum, return the top 7 volumes.
union(tables: [summarized, raw])
|> group(columns: ["emitter_chain", "destination_chain"])
|> sum()
|> group()
|> top(columns: ["_value"], n: 7)
`
type Repository struct {
influxCli influxdb2.Client
queryAPI api.QueryAPI
@ -121,10 +152,10 @@ func NewRepository(
return &r
}
func (r *Repository) GetTopAssetsByVolume(ctx context.Context, timerange *TopAssetsTimerange) ([]AssetDTO, error) {
func (r *Repository) GetTopAssets(ctx context.Context, timeSpan *TopStatisticsTimeSpan) ([]AssetDTO, error) {
// Submit the query to InfluxDB
query := fmt.Sprintf(queryTemplateTopAssetsByVolume, r.bucket30DaysRetention, *timerange, r.bucket24HoursRetention)
query := fmt.Sprintf(queryTemplateTopAssets, r.bucket30DaysRetention, *timeSpan, r.bucketInfiniteRetention)
result, err := r.queryAPI.Query(ctx, query)
if err != nil {
return nil, err
@ -178,6 +209,61 @@ func (r *Repository) GetTopAssetsByVolume(ctx context.Context, timerange *TopAss
return assets, nil
}
func (r *Repository) GetTopChainPairs(ctx context.Context, timeSpan *TopStatisticsTimeSpan) ([]ChainPairDTO, error) {
// Submit the query to InfluxDB
query := fmt.Sprintf(queryTemplateTopChainPairs, r.bucket30DaysRetention, *timeSpan, r.bucketInfiniteRetention)
result, err := r.queryAPI.Query(ctx, query)
if err != nil {
return nil, err
}
if result.Err() != nil {
return nil, result.Err()
}
// Scan query results
type Row struct {
EmitterChain string `mapstructure:"emitter_chain"`
DestinationChain string `mapstructure:"destination_chain"`
NumberOfTransfers int64 `mapstructure:"_value"`
}
var rows []Row
for result.Next() {
var row Row
if err := mapstructure.Decode(result.Record().Values(), &row); err != nil {
return nil, err
}
rows = append(rows, row)
}
// Convert the rows into the response model
var assets []ChainPairDTO
for i := range rows {
// parse emitter chain
emitterChain, err := strconv.ParseUint(rows[i].EmitterChain, 10, 16)
if err != nil {
return nil, fmt.Errorf("failed to convert emitter chain field to uint16")
}
// parse destination chain
destinationChain, err := strconv.ParseUint(rows[i].DestinationChain, 10, 16)
if err != nil {
return nil, fmt.Errorf("failed to convert destination chain field to uint16")
}
// append the new item to the response
asset := ChainPairDTO{
EmitterChain: sdk.ChainID(emitterChain),
DestinationChain: sdk.ChainID(destinationChain),
NumberOfTransfers: fmt.Sprintf("%d", rows[i].NumberOfTransfers),
}
assets = append(assets, asset)
}
return assets, nil
}
// convertToDecimal converts an integer amount to a decimal string, with 8 decimals of precision.
func convertToDecimal(amount int64) string {

View File

@ -28,8 +28,12 @@ func (s *Service) GetScorecards(ctx context.Context) (*Scorecards, error) {
return s.repo.GetScorecards(ctx)
}
func (s *Service) GetTopAssetsByVolume(ctx context.Context, timerange *TopAssetsTimerange) ([]AssetDTO, error) {
return s.repo.GetTopAssetsByVolume(ctx, timerange)
func (s *Service) GetTopAssets(ctx context.Context, timeSpan *TopStatisticsTimeSpan) ([]AssetDTO, error) {
return s.repo.GetTopAssets(ctx, timeSpan)
}
func (s *Service) GetTopChainPairs(ctx context.Context, timeSpan *TopStatisticsTimeSpan) ([]ChainPairDTO, error) {
return s.repo.GetTopChainPairs(ctx, timeSpan)
}
// GetChainActivity get chain activity.

View File

@ -299,16 +299,20 @@ func ExtractIsNotional(ctx *fiber.Ctx) (bool, error) {
return false, response.NewInvalidQueryParamError(ctx, "INVALID <by> QUERY PARAMETER", nil)
}
// ExtractTopAssetsTimerange parses the `timerange` parameter from the `GET /api/v1/top-assets-by-volume` endpoint.
func ExtractTopAssetsTimerange(ctx *fiber.Ctx) (*transactions.TopAssetsTimerange, error) {
// ExtractTopStatisticsTimeSpan parses the `timespan` parameter used on top statistics endpoints.
//
// The endpoints that accept this parameter are:
// * `GET /api/v1/top-assets-by-volume`
// * `GET /api/v1/top-chain-pairs-by-num-transfers`
func ExtractTopStatisticsTimeSpan(ctx *fiber.Ctx) (*transactions.TopStatisticsTimeSpan, error) {
s := ctx.Query("timerange")
timerange, err := transactions.NewTopAssetsTimerange(s)
s := ctx.Query("timeSpan")
timeSpan, err := transactions.ParseTopStatisticsTimeSpan(s)
if err != nil {
return nil, response.NewInvalidQueryParamError(ctx, "INVALID <timerange> QUERY PARAMETER", nil)
return nil, response.NewInvalidQueryParamError(ctx, "INVALID <timeSpan> QUERY PARAMETER", nil)
}
return timerange, nil
return timeSpan, nil
}
func ExtractTimeRange(ctx *fiber.Ctx) (*time.Time, *time.Time, error) {

View File

@ -67,7 +67,8 @@ func RegisterRoutes(
api.Get("/last-txs", transactionCtrl.GetLastTransactions)
api.Get("/scorecards", transactionCtrl.GetScorecards)
api.Get("/x-chain-activity", transactionCtrl.GetChainActivity)
api.Get("/top-assets-by-volume", transactionCtrl.GetTopAssetsByVolume)
api.Get("/top-assets-by-volume", transactionCtrl.GetTopAssets)
api.Get("/top-chain-pairs-by-num-transfers", transactionCtrl.GetTopChainPairs)
// vaas resource
vaas := api.Group("/vaas")

View File

@ -85,30 +85,70 @@ func (c *Controller) GetScorecards(ctx *fiber.Ctx) error {
return ctx.JSON(response)
}
// GetTopAssetsByVolume godoc
// @Description Returns the list of (emitter_chain, asset) pairs with the most volume.
// GetTopChainPairs godoc
// @Description Returns a list of the (emitter_chain, destination_chain) pairs with the highest number of transfers.
// @Tags Wormscan
// @ID get-top-assets-by-volume
// @Success 200 {object} TopAssetsByVolumeResponse
// @ID get-top-chain-pairs-by-num-transfers
// @Param timeSpan query string true "Time span, supported values: 7d, 15d, 30d."
// @Success 200 {object} TopChainPairsResponse
// @Failure 500
// @Router /api/v1/top-assets-by-volume [get]
func (c *Controller) GetTopAssetsByVolume(ctx *fiber.Ctx) error {
// @Router /api/v1/top-chain-pairs-by-num-transfers [get]
func (c *Controller) GetTopChainPairs(ctx *fiber.Ctx) error {
// Extract query parameters
timerange, err := middleware.ExtractTopAssetsTimerange(ctx)
timeSpan, err := middleware.ExtractTopStatisticsTimeSpan(ctx)
if err != nil {
return err
}
// Query chain pairs from the database
chainPairDTOs, err := c.srv.GetTopChainPairs(ctx.Context(), timeSpan)
if err != nil {
c.logger.Error("failed to get top chain pairs by number of transfers", zap.Error(err))
return err
}
// Convert DTOs to the response model
response := TopChainPairsResponse{
ChainPairs: make([]ChainPair, 0, len(chainPairDTOs)),
}
for i := range chainPairDTOs {
chainPair := ChainPair{
EmitterChain: chainPairDTOs[i].EmitterChain,
DestinationChain: chainPairDTOs[i].DestinationChain,
NumberOfTransfers: chainPairDTOs[i].NumberOfTransfers,
}
response.ChainPairs = append(response.ChainPairs, chainPair)
}
return ctx.JSON(response)
}
// GetTopAssets godoc
// @Description Returns a list of the (emitter_chain, asset) pairs with the most volume.
// @Tags Wormscan
// @ID get-top-assets-by-volume
// @Param timeSpan query string true "Time span, supported values: 7d, 15d, 30d."
// @Success 200 {object} TopAssetsResponse
// @Failure 500
// @Router /api/v1/top-assets-by-volume [get]
func (c *Controller) GetTopAssets(ctx *fiber.Ctx) error {
// Extract query parameters
timeSpan, err := middleware.ExtractTopStatisticsTimeSpan(ctx)
if err != nil {
return err
}
// Query assets from the database
assetDTOs, err := c.srv.GetTopAssetsByVolume(ctx.Context(), timerange)
assetDTOs, err := c.srv.GetTopAssets(ctx.Context(), timeSpan)
if err != nil {
c.logger.Error("failed to get top assets by volume", zap.Error(err))
return err
}
// Convert DTOs to the response model
response := TopAssetsByVolumeResponse{
response := TopAssetsResponse{
Assets: make([]AssetWithVolume, 0, len(assetDTOs)),
}
for i := range assetDTOs {
@ -133,7 +173,6 @@ func (c *Controller) GetTopAssetsByVolume(ctx *fiber.Ctx) error {
}
return ctx.JSON(response)
}
// GetChainActivity godoc

View File

@ -44,13 +44,24 @@ type ScorecardsResponse struct {
Volume24h string `json:"24h_volume"`
}
// TopAssetsByVolumeResponse is the "200 OK" response model for `GET /api/v1/top-assets-by-volume`.
type TopAssetsByVolumeResponse struct {
// TopAssetsResponse is the "200 OK" response model for `GET /api/v1/top-assets-by-volume`.
type TopAssetsResponse struct {
Assets []AssetWithVolume `json:"assets"`
}
type AssetWithVolume struct {
EmitterChain sdk.ChainID `json:"emitterChain`
EmitterChain sdk.ChainID `json:"emitterChain"`
Symbol string `json:"symbol"`
Volume string `json:"volume"`
}
// TopChainPairsResponse is the "200 OK" response model for `GET /api/v1/top-chain-pairs-by-num-transfers`.
type TopChainPairsResponse struct {
ChainPairs []ChainPair `json:"chainPairs"`
}
type ChainPair struct {
EmitterChain sdk.ChainID `json:"emitterChain"`
DestinationChain sdk.ChainID `json:"destinationChain"`
NumberOfTransfers string `json:"numberOfTransfers"`
}

View File

@ -6,15 +6,18 @@ 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
github.com/wormhole-foundation/wormhole-explorer/common v0.0.0-20230512135429-25a675f99a4b
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20230426150516-e695fad0bed8
go.uber.org/zap v1.24.0
)
require (
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/ethereum/go-ethereum v1.10.21 // indirect
github.com/go-redis/redis/v8 v8.11.5 // 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

View File

@ -2,6 +2,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
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/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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=
@ -9,6 +11,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK
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/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/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=
@ -16,6 +20,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
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=
@ -56,8 +62,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
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-explorer/common v0.0.0-20230512135429-25a675f99a4b h1:Z5ulKWN8mtUW4zX8nMl+oJeGAj0Wk2wRFLj7aDnGVoI=
github.com/wormhole-foundation/wormhole-explorer/common v0.0.0-20230512135429-25a675f99a4b/go.mod h1:9N5u61eWAJ5CTFu4UF6gJ8FP2FiLbU39XCRMdYKxY/I=
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/wormhole-foundation/wormhole/sdk v0.0.0-20230426150516-e695fad0bed8 h1:rrOyHd+H9a6Op1iUyZNCaI5v9D1syq8jDAYyX/2Q4L8=
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20230426150516-e695fad0bed8/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=