Add top-100-corridors endpoint (#1065)
Improve handling token in volume metrics
This commit is contained in:
parent
54c5d34a6e
commit
e63a7a12e4
|
@ -12,7 +12,22 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnknownToken = errors.New("token is unknown")
|
type UnknownTokenErr struct {
|
||||||
|
detail string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnknownTokenErr) Error() string {
|
||||||
|
return fmt.Sprintf("unknown token. %s", e.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsUnknownTokenErr(err error) bool {
|
||||||
|
switch err.(type) {
|
||||||
|
case *UnknownTokenErr:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type TransferredToken struct {
|
type TransferredToken struct {
|
||||||
AppId string
|
AppId string
|
||||||
|
@ -62,20 +77,6 @@ func (r *TokenResolver) GetTransferredTokenByVaa(ctx context.Context, vaa *sdk.V
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the VAA payload
|
|
||||||
payload, err := sdk.DecodeTransferPayloadHdr(vaa.Payload)
|
|
||||||
if err == nil && payload.OriginChain != sdk.ChainIDUnset {
|
|
||||||
return &TransferredToken{
|
|
||||||
AppId: domain.AppIdPortalTokenBridge,
|
|
||||||
FromChain: vaa.EmitterChain,
|
|
||||||
ToChain: payload.TargetChain,
|
|
||||||
TokenAddress: payload.OriginAddress,
|
|
||||||
TokenChain: payload.OriginChain,
|
|
||||||
Amount: payload.Amount,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the VAA with standarized properties
|
// Parse the VAA with standarized properties
|
||||||
result, err := r.client.ParseVaaWithStandarizedProperties(vaa)
|
result, err := r.client.ParseVaaWithStandarizedProperties(vaa)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -97,7 +98,7 @@ func (r *TokenResolver) GetTransferredTokenByVaa(ctx context.Context, vaa *sdk.V
|
||||||
r.logger.Debug("Creating transferred token",
|
r.logger.Debug("Creating transferred token",
|
||||||
zap.String("vaaId", vaa.MessageID()),
|
zap.String("vaaId", vaa.MessageID()),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
return nil, ErrUnknownToken
|
return nil, &UnknownTokenErr{detail: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
return token, err
|
return token, err
|
||||||
|
@ -105,16 +106,16 @@ func (r *TokenResolver) GetTransferredTokenByVaa(ctx context.Context, vaa *sdk.V
|
||||||
|
|
||||||
func createToken(s parser.StandardizedProperties, emitterChain sdk.ChainID) (*TransferredToken, error) {
|
func createToken(s parser.StandardizedProperties, emitterChain sdk.ChainID) (*TransferredToken, error) {
|
||||||
|
|
||||||
if s.TokenChain.String() == sdk.ChainIDUnset.String() {
|
if !domain.ChainIdIsValid(s.TokenChain) {
|
||||||
return nil, errors.New("tokenChain is unset")
|
return nil, fmt.Errorf("tokenChain is invalid: %d", s.TokenChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ToChain.String() == sdk.ChainIDUnset.String() {
|
if !domain.ChainIdIsValid(s.ToChain) {
|
||||||
return nil, errors.New("toChain is unset")
|
return nil, fmt.Errorf("toChain is invalid: %d", s.ToChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
if emitterChain.String() == sdk.ChainIDUnset.String() {
|
if !domain.ChainIdIsValid(emitterChain) {
|
||||||
return nil, errors.New("emitterChain is unset")
|
return nil, fmt.Errorf("emitterChain is invalid: %d", emitterChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.TokenAddress == "" {
|
if s.TokenAddress == "" {
|
||||||
|
|
|
@ -96,7 +96,7 @@ func (m *Metric) Push(ctx context.Context, params *Params) error {
|
||||||
|
|
||||||
transferredToken, err := m.getTransferredTokenByVaa(ctx, params.Vaa)
|
transferredToken, err := m.getTransferredTokenByVaa(ctx, params.Vaa)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != token.ErrUnknownToken {
|
if !token.IsUnknownTokenErr(err) {
|
||||||
m.logger.Error("Failed to obtain transferred token for this VAA",
|
m.logger.Error("Failed to obtain transferred token for this VAA",
|
||||||
zap.String("trackId", params.TrackID),
|
zap.String("trackId", params.TrackID),
|
||||||
zap.String("vaaId", params.Vaa.MessageID()),
|
zap.String("vaaId", params.Vaa.MessageID()),
|
||||||
|
@ -129,8 +129,8 @@ func (m *Metric) Push(ctx context.Context, params *Params) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
m.logger.Warn("Cannot obtain transferred token for this VAA",
|
m.logger.Warn("Cannot obtain transferred token for this VAA",
|
||||||
|
zap.Error(err),
|
||||||
zap.String("trackId", params.TrackID),
|
zap.String("trackId", params.TrackID),
|
||||||
zap.String("vaaId", params.Vaa.MessageID()),
|
zap.String("vaaId", params.Vaa.MessageID()),
|
||||||
)
|
)
|
||||||
|
@ -145,6 +145,7 @@ func (m *Metric) Push(ctx context.Context, params *Params) error {
|
||||||
if params.Vaa.EmitterChain != sdk.ChainIDPythNet {
|
if params.Vaa.EmitterChain != sdk.ChainIDPythNet {
|
||||||
m.logger.Info("Transaction processed successfully",
|
m.logger.Info("Transaction processed successfully",
|
||||||
zap.String("trackId", params.TrackID),
|
zap.String("trackId", params.TrackID),
|
||||||
|
zap.Bool("isVaaSigned", isVaaSigned),
|
||||||
zap.String("vaaId", params.Vaa.MessageID()))
|
zap.String("vaaId", params.Vaa.MessageID()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import "date"
|
||||||
|
|
||||||
|
option task = {
|
||||||
|
name: "top 100 corridors with 3-hour granularity",
|
||||||
|
every: 3h,
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceBucket = "wormscan"
|
||||||
|
destinationBucket = "wormscan-24hours"
|
||||||
|
execution = date.truncate(t: now(), unit: 1h)
|
||||||
|
start7d = date.truncate(t: -7d, unit: 1h)
|
||||||
|
start2d = date.truncate(t: -2d, unit: 1h)
|
||||||
|
|
||||||
|
from(bucket: sourceBucket)
|
||||||
|
|> range(start: start7d)
|
||||||
|
|> filter(fn: (r) => r._measurement == "vaa_volume_v2" and r._field == "volume")
|
||||||
|
|> group(columns: ["emitter_chain", "destination_chain", "token_chain", "token_address"])
|
||||||
|
|> count(column: "_value")
|
||||||
|
|> group()
|
||||||
|
|> sort(desc:true)
|
||||||
|
|> limit(n:100)
|
||||||
|
|> map(fn: (r) => ({r with _time: execution}))
|
||||||
|
|> set(key: "_measurement", value: "top_100_corridors_7_days_3h_v2")
|
||||||
|
|> set(key: "_field", value: "count")
|
||||||
|
|> to(bucket: destinationBucket)
|
||||||
|
|
||||||
|
from(bucket: sourceBucket)
|
||||||
|
|> range(start: start2d)
|
||||||
|
|> filter(fn: (r) => r._measurement == "vaa_volume_v2" and r._field == "volume")
|
||||||
|
|> group(columns: ["emitter_chain", "destination_chain", "token_chain", "token_address"])
|
||||||
|
|> count(column: "_value")
|
||||||
|
|> group()
|
||||||
|
|> sort(desc:true)
|
||||||
|
|> limit(n:100)
|
||||||
|
|> map(fn: (r) => ({r with _time: execution}))
|
||||||
|
|> set(key: "_measurement", value: "top_100_corridors_2_days_3h_v2")
|
||||||
|
|> set(key: "_field", value: "count")
|
||||||
|
|> to(bucket: destinationBucket)
|
||||||
|
|
184
api/docs/docs.go
184
api/docs/docs.go
|
@ -991,6 +991,37 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/top-100-corridors": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns a list of the top 100 tokens, sorted in descending order by the number of transactions.",
|
||||||
|
"tags": [
|
||||||
|
"wormholescan"
|
||||||
|
],
|
||||||
|
"operationId": "/api/v1/top-100-corridors",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Time span, supported values: 2d and 7d (default is 2d).",
|
||||||
|
"name": "timeSpan",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/stats.TopCorridorsResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/top-assets-by-volume": {
|
"/api/v1/top-assets-by-volume": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns a list of emitter_chain and asset pairs with ordered by volume.\nThe volume is calculated using the notional price of the symbol at the day the VAA was emitted.",
|
"description": "Returns a list of emitter_chain and asset pairs with ordered by volume.\nThe volume is calculated using the notional price of the symbol at the day the VAA was emitted.",
|
||||||
|
@ -1049,6 +1080,37 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/top-symbols-by-volume": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns a list of symbols by origin chain and tokens.\nThe volume is calculated using the notional price of the symbol at the day the VAA was emitted.",
|
||||||
|
"tags": [
|
||||||
|
"wormholescan"
|
||||||
|
],
|
||||||
|
"operationId": "top-symbols-by-volume",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Time span, supported values: 7d, 15d and 30d (default is 7d).",
|
||||||
|
"name": "timeSpan",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/stats.TopSymbolByVolumeResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/transactions/": {
|
"/api/v1/transactions/": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns transactions. Output is paginated.",
|
"description": "Returns transactions. Output is paginated.",
|
||||||
|
@ -2410,11 +2472,20 @@ const docTemplate = `{
|
||||||
"relays.DeliveryReponse": {
|
"relays.DeliveryReponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"budget": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"execution": {
|
"execution": {
|
||||||
"$ref": "#/definitions/relays.ResultExecutionResponse"
|
"$ref": "#/definitions/relays.ResultExecutionResponse"
|
||||||
},
|
},
|
||||||
|
"maxRefund": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"relayGasUsed": {
|
"relayGasUsed": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"targetChainDecimals": {
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2485,6 +2556,9 @@ const docTemplate = `{
|
||||||
"instructions": {
|
"instructions": {
|
||||||
"$ref": "#/definitions/relays.InstructionsResponse"
|
"$ref": "#/definitions/relays.InstructionsResponse"
|
||||||
},
|
},
|
||||||
|
"maxAttempts": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"toTxHash": {
|
"toTxHash": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -2717,6 +2791,88 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"stats.TokenResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"emitter_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"token_address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"txs": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats.TopCorridor": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"emitter_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"target_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"token_address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"txs": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats.TopCorridorsResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"corridors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/stats.TopCorridor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats.TopSymbolByVolumeResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"symbols": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/stats.TopSymbolResult"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats.TopSymbolResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"symbol": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tokens": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/stats.TokenResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"txs": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"transactions.AssetWithVolume": {
|
"transactions.AssetWithVolume": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -3025,6 +3181,7 @@ const docTemplate = `{
|
||||||
17,
|
17,
|
||||||
18,
|
18,
|
||||||
19,
|
19,
|
||||||
|
20,
|
||||||
21,
|
21,
|
||||||
22,
|
22,
|
||||||
23,
|
23,
|
||||||
|
@ -3034,8 +3191,19 @@ const docTemplate = `{
|
||||||
29,
|
29,
|
||||||
30,
|
30,
|
||||||
32,
|
32,
|
||||||
|
34,
|
||||||
|
35,
|
||||||
3104,
|
3104,
|
||||||
10002
|
4000,
|
||||||
|
4001,
|
||||||
|
4002,
|
||||||
|
4003,
|
||||||
|
4004,
|
||||||
|
10002,
|
||||||
|
10003,
|
||||||
|
10004,
|
||||||
|
10005,
|
||||||
|
10006
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"ChainIDUnset",
|
"ChainIDUnset",
|
||||||
|
@ -3058,6 +3226,7 @@ const docTemplate = `{
|
||||||
"ChainIDNeon",
|
"ChainIDNeon",
|
||||||
"ChainIDTerra2",
|
"ChainIDTerra2",
|
||||||
"ChainIDInjective",
|
"ChainIDInjective",
|
||||||
|
"ChainIDOsmosis",
|
||||||
"ChainIDSui",
|
"ChainIDSui",
|
||||||
"ChainIDAptos",
|
"ChainIDAptos",
|
||||||
"ChainIDArbitrum",
|
"ChainIDArbitrum",
|
||||||
|
@ -3067,8 +3236,19 @@ const docTemplate = `{
|
||||||
"ChainIDBtc",
|
"ChainIDBtc",
|
||||||
"ChainIDBase",
|
"ChainIDBase",
|
||||||
"ChainIDSei",
|
"ChainIDSei",
|
||||||
|
"ChainIDScroll",
|
||||||
|
"ChainIDMantle",
|
||||||
"ChainIDWormchain",
|
"ChainIDWormchain",
|
||||||
"ChainIDSepolia"
|
"ChainIDCosmoshub",
|
||||||
|
"ChainIDEvmos",
|
||||||
|
"ChainIDKujira",
|
||||||
|
"ChainIDNeutron",
|
||||||
|
"ChainIDCelestia",
|
||||||
|
"ChainIDSepolia",
|
||||||
|
"ChainIDArbitrumSepolia",
|
||||||
|
"ChainIDBaseSepolia",
|
||||||
|
"ChainIDOptimismSepolia",
|
||||||
|
"ChainIDHolesky"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"vaa.VaaDoc": {
|
"vaa.VaaDoc": {
|
||||||
|
|
|
@ -984,6 +984,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/top-100-corridors": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns a list of the top 100 tokens, sorted in descending order by the number of transactions.",
|
||||||
|
"tags": [
|
||||||
|
"wormholescan"
|
||||||
|
],
|
||||||
|
"operationId": "/api/v1/top-100-corridors",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Time span, supported values: 2d and 7d (default is 2d).",
|
||||||
|
"name": "timeSpan",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/stats.TopCorridorsResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/top-assets-by-volume": {
|
"/api/v1/top-assets-by-volume": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns a list of emitter_chain and asset pairs with ordered by volume.\nThe volume is calculated using the notional price of the symbol at the day the VAA was emitted.",
|
"description": "Returns a list of emitter_chain and asset pairs with ordered by volume.\nThe volume is calculated using the notional price of the symbol at the day the VAA was emitted.",
|
||||||
|
@ -1042,6 +1073,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/top-symbols-by-volume": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns a list of symbols by origin chain and tokens.\nThe volume is calculated using the notional price of the symbol at the day the VAA was emitted.",
|
||||||
|
"tags": [
|
||||||
|
"wormholescan"
|
||||||
|
],
|
||||||
|
"operationId": "top-symbols-by-volume",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Time span, supported values: 7d, 15d and 30d (default is 7d).",
|
||||||
|
"name": "timeSpan",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/stats.TopSymbolByVolumeResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/transactions/": {
|
"/api/v1/transactions/": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns transactions. Output is paginated.",
|
"description": "Returns transactions. Output is paginated.",
|
||||||
|
@ -2403,11 +2465,20 @@
|
||||||
"relays.DeliveryReponse": {
|
"relays.DeliveryReponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"budget": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"execution": {
|
"execution": {
|
||||||
"$ref": "#/definitions/relays.ResultExecutionResponse"
|
"$ref": "#/definitions/relays.ResultExecutionResponse"
|
||||||
},
|
},
|
||||||
|
"maxRefund": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"relayGasUsed": {
|
"relayGasUsed": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"targetChainDecimals": {
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2478,6 +2549,9 @@
|
||||||
"instructions": {
|
"instructions": {
|
||||||
"$ref": "#/definitions/relays.InstructionsResponse"
|
"$ref": "#/definitions/relays.InstructionsResponse"
|
||||||
},
|
},
|
||||||
|
"maxAttempts": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"toTxHash": {
|
"toTxHash": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -2710,6 +2784,88 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"stats.TokenResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"emitter_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"token_address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"txs": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats.TopCorridor": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"emitter_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"target_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"token_address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token_chain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"txs": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats.TopCorridorsResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"corridors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/stats.TopCorridor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats.TopSymbolByVolumeResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"symbols": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/stats.TopSymbolResult"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats.TopSymbolResult": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"symbol": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tokens": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/stats.TokenResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"txs": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"transactions.AssetWithVolume": {
|
"transactions.AssetWithVolume": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -3018,6 +3174,7 @@
|
||||||
17,
|
17,
|
||||||
18,
|
18,
|
||||||
19,
|
19,
|
||||||
|
20,
|
||||||
21,
|
21,
|
||||||
22,
|
22,
|
||||||
23,
|
23,
|
||||||
|
@ -3027,8 +3184,19 @@
|
||||||
29,
|
29,
|
||||||
30,
|
30,
|
||||||
32,
|
32,
|
||||||
|
34,
|
||||||
|
35,
|
||||||
3104,
|
3104,
|
||||||
10002
|
4000,
|
||||||
|
4001,
|
||||||
|
4002,
|
||||||
|
4003,
|
||||||
|
4004,
|
||||||
|
10002,
|
||||||
|
10003,
|
||||||
|
10004,
|
||||||
|
10005,
|
||||||
|
10006
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"ChainIDUnset",
|
"ChainIDUnset",
|
||||||
|
@ -3051,6 +3219,7 @@
|
||||||
"ChainIDNeon",
|
"ChainIDNeon",
|
||||||
"ChainIDTerra2",
|
"ChainIDTerra2",
|
||||||
"ChainIDInjective",
|
"ChainIDInjective",
|
||||||
|
"ChainIDOsmosis",
|
||||||
"ChainIDSui",
|
"ChainIDSui",
|
||||||
"ChainIDAptos",
|
"ChainIDAptos",
|
||||||
"ChainIDArbitrum",
|
"ChainIDArbitrum",
|
||||||
|
@ -3060,8 +3229,19 @@
|
||||||
"ChainIDBtc",
|
"ChainIDBtc",
|
||||||
"ChainIDBase",
|
"ChainIDBase",
|
||||||
"ChainIDSei",
|
"ChainIDSei",
|
||||||
|
"ChainIDScroll",
|
||||||
|
"ChainIDMantle",
|
||||||
"ChainIDWormchain",
|
"ChainIDWormchain",
|
||||||
"ChainIDSepolia"
|
"ChainIDCosmoshub",
|
||||||
|
"ChainIDEvmos",
|
||||||
|
"ChainIDKujira",
|
||||||
|
"ChainIDNeutron",
|
||||||
|
"ChainIDCelestia",
|
||||||
|
"ChainIDSepolia",
|
||||||
|
"ChainIDArbitrumSepolia",
|
||||||
|
"ChainIDBaseSepolia",
|
||||||
|
"ChainIDOptimismSepolia",
|
||||||
|
"ChainIDHolesky"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"vaa.VaaDoc": {
|
"vaa.VaaDoc": {
|
||||||
|
|
|
@ -398,10 +398,16 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
relays.DeliveryReponse:
|
relays.DeliveryReponse:
|
||||||
properties:
|
properties:
|
||||||
|
budget:
|
||||||
|
type: string
|
||||||
execution:
|
execution:
|
||||||
$ref: '#/definitions/relays.ResultExecutionResponse'
|
$ref: '#/definitions/relays.ResultExecutionResponse'
|
||||||
|
maxRefund:
|
||||||
|
type: string
|
||||||
relayGasUsed:
|
relayGasUsed:
|
||||||
type: integer
|
type: integer
|
||||||
|
targetChainDecimals:
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
relays.InstructionsResponse:
|
relays.InstructionsResponse:
|
||||||
properties:
|
properties:
|
||||||
|
@ -447,6 +453,8 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
instructions:
|
instructions:
|
||||||
$ref: '#/definitions/relays.InstructionsResponse'
|
$ref: '#/definitions/relays.InstructionsResponse'
|
||||||
|
maxAttempts:
|
||||||
|
type: integer
|
||||||
toTxHash:
|
toTxHash:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
@ -596,6 +604,59 @@ definitions:
|
||||||
next:
|
next:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
stats.TokenResult:
|
||||||
|
properties:
|
||||||
|
emitter_chain:
|
||||||
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
|
token_address:
|
||||||
|
type: string
|
||||||
|
token_chain:
|
||||||
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
|
txs:
|
||||||
|
type: number
|
||||||
|
volume:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
|
stats.TopCorridor:
|
||||||
|
properties:
|
||||||
|
emitter_chain:
|
||||||
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
|
target_chain:
|
||||||
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
|
token_address:
|
||||||
|
type: string
|
||||||
|
token_chain:
|
||||||
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
|
txs:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
stats.TopCorridorsResult:
|
||||||
|
properties:
|
||||||
|
corridors:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/stats.TopCorridor'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
stats.TopSymbolByVolumeResult:
|
||||||
|
properties:
|
||||||
|
symbols:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/stats.TopSymbolResult'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
stats.TopSymbolResult:
|
||||||
|
properties:
|
||||||
|
symbol:
|
||||||
|
type: string
|
||||||
|
tokens:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/stats.TokenResult'
|
||||||
|
type: array
|
||||||
|
txs:
|
||||||
|
type: number
|
||||||
|
volume:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
transactions.AssetWithVolume:
|
transactions.AssetWithVolume:
|
||||||
properties:
|
properties:
|
||||||
emitterChain:
|
emitterChain:
|
||||||
|
@ -811,6 +872,7 @@ definitions:
|
||||||
- 17
|
- 17
|
||||||
- 18
|
- 18
|
||||||
- 19
|
- 19
|
||||||
|
- 20
|
||||||
- 21
|
- 21
|
||||||
- 22
|
- 22
|
||||||
- 23
|
- 23
|
||||||
|
@ -820,8 +882,19 @@ definitions:
|
||||||
- 29
|
- 29
|
||||||
- 30
|
- 30
|
||||||
- 32
|
- 32
|
||||||
|
- 34
|
||||||
|
- 35
|
||||||
- 3104
|
- 3104
|
||||||
|
- 4000
|
||||||
|
- 4001
|
||||||
|
- 4002
|
||||||
|
- 4003
|
||||||
|
- 4004
|
||||||
- 10002
|
- 10002
|
||||||
|
- 10003
|
||||||
|
- 10004
|
||||||
|
- 10005
|
||||||
|
- 10006
|
||||||
type: integer
|
type: integer
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- ChainIDUnset
|
- ChainIDUnset
|
||||||
|
@ -844,6 +917,7 @@ definitions:
|
||||||
- ChainIDNeon
|
- ChainIDNeon
|
||||||
- ChainIDTerra2
|
- ChainIDTerra2
|
||||||
- ChainIDInjective
|
- ChainIDInjective
|
||||||
|
- ChainIDOsmosis
|
||||||
- ChainIDSui
|
- ChainIDSui
|
||||||
- ChainIDAptos
|
- ChainIDAptos
|
||||||
- ChainIDArbitrum
|
- ChainIDArbitrum
|
||||||
|
@ -853,8 +927,19 @@ definitions:
|
||||||
- ChainIDBtc
|
- ChainIDBtc
|
||||||
- ChainIDBase
|
- ChainIDBase
|
||||||
- ChainIDSei
|
- ChainIDSei
|
||||||
|
- ChainIDScroll
|
||||||
|
- ChainIDMantle
|
||||||
- ChainIDWormchain
|
- ChainIDWormchain
|
||||||
|
- ChainIDCosmoshub
|
||||||
|
- ChainIDEvmos
|
||||||
|
- ChainIDKujira
|
||||||
|
- ChainIDNeutron
|
||||||
|
- ChainIDCelestia
|
||||||
- ChainIDSepolia
|
- ChainIDSepolia
|
||||||
|
- ChainIDArbitrumSepolia
|
||||||
|
- ChainIDBaseSepolia
|
||||||
|
- ChainIDOptimismSepolia
|
||||||
|
- ChainIDHolesky
|
||||||
vaa.VaaDoc:
|
vaa.VaaDoc:
|
||||||
properties:
|
properties:
|
||||||
appId:
|
appId:
|
||||||
|
@ -1575,6 +1660,27 @@ paths:
|
||||||
description: Not Found
|
description: Not Found
|
||||||
tags:
|
tags:
|
||||||
- wormholescan
|
- wormholescan
|
||||||
|
/api/v1/top-100-corridors:
|
||||||
|
get:
|
||||||
|
description: Returns a list of the top 100 tokens, sorted in descending order
|
||||||
|
by the number of transactions.
|
||||||
|
operationId: /api/v1/top-100-corridors
|
||||||
|
parameters:
|
||||||
|
- description: 'Time span, supported values: 2d and 7d (default is 2d).'
|
||||||
|
in: query
|
||||||
|
name: timeSpan
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/stats.TopCorridorsResult'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
tags:
|
||||||
|
- wormholescan
|
||||||
/api/v1/top-assets-by-volume:
|
/api/v1/top-assets-by-volume:
|
||||||
get:
|
get:
|
||||||
description: |-
|
description: |-
|
||||||
|
@ -1616,6 +1722,28 @@ paths:
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
tags:
|
tags:
|
||||||
- wormholescan
|
- wormholescan
|
||||||
|
/api/v1/top-symbols-by-volume:
|
||||||
|
get:
|
||||||
|
description: |-
|
||||||
|
Returns a list of symbols by origin chain and tokens.
|
||||||
|
The volume is calculated using the notional price of the symbol at the day the VAA was emitted.
|
||||||
|
operationId: top-symbols-by-volume
|
||||||
|
parameters:
|
||||||
|
- description: 'Time span, supported values: 7d, 15d and 30d (default is 7d).'
|
||||||
|
in: query
|
||||||
|
name: timeSpan
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/stats.TopSymbolByVolumeResult'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
tags:
|
||||||
|
- wormholescan
|
||||||
/api/v1/transactions/:
|
/api/v1/transactions/:
|
||||||
get:
|
get:
|
||||||
description: Returns transactions. Output is paginated.
|
description: Returns transactions. Output is paginated.
|
||||||
|
|
|
@ -17,3 +17,16 @@ func buildSymbolWithAssets(bucket string, t time.Time, measurement string) strin
|
||||||
start := t.Truncate(time.Hour * 24).Format(time.RFC3339Nano)
|
start := t.Truncate(time.Hour * 24).Format(time.RFC3339Nano)
|
||||||
return fmt.Sprintf(queryTemplateSymbolWithAssets, bucket, start, measurement)
|
return fmt.Sprintf(queryTemplateSymbolWithAssets, bucket, start, measurement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const queryTemplateTopCorridors = `
|
||||||
|
from(bucket: "%s")
|
||||||
|
|> range(start: %s)
|
||||||
|
|> filter(fn: (r) => r._measurement == "%s" and r._field == "count")
|
||||||
|
|> last()
|
||||||
|
|> group()
|
||||||
|
`
|
||||||
|
|
||||||
|
func buildTopCorridors(bucket string, t time.Time, measurement string) string {
|
||||||
|
start := t.Truncate(time.Hour * 24).Format(time.RFC3339Nano)
|
||||||
|
return fmt.Sprintf(queryTemplateTopCorridors, bucket, start, measurement)
|
||||||
|
}
|
||||||
|
|
|
@ -128,3 +128,90 @@ func (r *Repository) GetSymbolWithAssets(ctx context.Context, timeSpan SymbolWit
|
||||||
|
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Repository) GetTopCorridores(ctx context.Context, timeSpan TopCorridorsTimeSpan) ([]TopCorridorsDTO, error) {
|
||||||
|
var measurement string
|
||||||
|
switch timeSpan {
|
||||||
|
case TimeSpan7DaysTopCorridors:
|
||||||
|
measurement = "top_100_corridors_7_days_3h_v2"
|
||||||
|
case TimeSpan2DaysTopCorridors:
|
||||||
|
measurement = "top_100_corridors_2_days_3h_v2"
|
||||||
|
default:
|
||||||
|
measurement = "top_100_corridors_2_days_3h_v2"
|
||||||
|
}
|
||||||
|
|
||||||
|
query := buildTopCorridors(r.bucket24HoursRetention, time.Now(), measurement)
|
||||||
|
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"`
|
||||||
|
TokenChain string `mapstructure:"token_chain"`
|
||||||
|
TokenAddress string `mapstructure:"token_address"`
|
||||||
|
Txs uint64 `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 values []TopCorridorsDTO
|
||||||
|
for _, row := range rows {
|
||||||
|
|
||||||
|
// parse emitter chain
|
||||||
|
emitterChain, err := strconv.ParseUint(row.EmitterChain, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert emitter chain field to uint16. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse emitter chain
|
||||||
|
destinationChain, err := strconv.ParseUint(row.DestinationChain, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert destination chain field to uint16. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse token chain
|
||||||
|
tokenChain, err := strconv.ParseUint(row.TokenChain, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert token chain field to uint16. %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the new item to the response
|
||||||
|
value := TopCorridorsDTO{
|
||||||
|
EmitterChainID: sdk.ChainID(emitterChain),
|
||||||
|
DestinationChainID: sdk.ChainID(destinationChain),
|
||||||
|
TokenChainID: sdk.ChainID(tokenChain),
|
||||||
|
TokenAddress: row.TokenAddress,
|
||||||
|
Txs: row.Txs,
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not include invalid chain IDs in the response
|
||||||
|
if !domain.ChainIdIsValid(value.EmitterChainID) || !domain.ChainIdIsValid(value.DestinationChainID) {
|
||||||
|
r.logger.Warn("Invalid chain ID in top corridors",
|
||||||
|
zap.Uint16("emitter_chain", uint16(value.EmitterChainID)),
|
||||||
|
zap.Uint16("destination_chain", uint16(value.DestinationChainID)),
|
||||||
|
zap.Uint16("token_chain", uint16(value.TokenChainID)),
|
||||||
|
zap.String("token_address", value.TokenAddress),
|
||||||
|
zap.Uint64("txs", value.Txs),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ type Service struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
topSymbolsByVolumeKey = "wormscan:top-assets-symbol-by-volume"
|
topSymbolsByVolumeKey = "wormscan:top-assets-symbol-by-volume"
|
||||||
|
topCorridorsByCountKey = "wormscan:top-corridors-by-count"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewService create a new Service.
|
// NewService create a new Service.
|
||||||
|
@ -36,3 +37,12 @@ func (s *Service) GetSymbolWithAssets(ctx context.Context, ts SymbolWithAssetsTi
|
||||||
return s.repo.GetSymbolWithAssets(ctx, ts)
|
return s.repo.GetSymbolWithAssets(ctx, ts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetTopCorridors(ctx context.Context, ts TopCorridorsTimeSpan) ([]TopCorridorsDTO, error) {
|
||||||
|
key := topCorridorsByCountKey
|
||||||
|
key = fmt.Sprintf("%s:%s", key, ts)
|
||||||
|
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, key, s.metrics,
|
||||||
|
func() ([]TopCorridorsDTO, error) {
|
||||||
|
return s.repo.GetTopCorridores(ctx, ts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -9,11 +9,15 @@ import (
|
||||||
|
|
||||||
// SymbolWithAssetsTimeSpan is used as an input parameter for the functions `GetTopAssets` and `GetTopChainPairs`.
|
// SymbolWithAssetsTimeSpan is used as an input parameter for the functions `GetTopAssets` and `GetTopChainPairs`.
|
||||||
type SymbolWithAssetsTimeSpan string
|
type SymbolWithAssetsTimeSpan string
|
||||||
|
type TopCorridorsTimeSpan string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TimeSpan7Days SymbolWithAssetsTimeSpan = "7d"
|
TimeSpan7Days SymbolWithAssetsTimeSpan = "7d"
|
||||||
TimeSpan15Days SymbolWithAssetsTimeSpan = "15d"
|
TimeSpan15Days SymbolWithAssetsTimeSpan = "15d"
|
||||||
TimeSpan30Days SymbolWithAssetsTimeSpan = "30d"
|
TimeSpan30Days SymbolWithAssetsTimeSpan = "30d"
|
||||||
|
|
||||||
|
TimeSpan2DaysTopCorridors TopCorridorsTimeSpan = "2d"
|
||||||
|
TimeSpan7DaysTopCorridors TopCorridorsTimeSpan = "7d"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseSymbolsWithAssetsTimeSpan parses a string and returns a `SymbolsWithAssetsTimeSpan`.
|
// ParseSymbolsWithAssetsTimeSpan parses a string and returns a `SymbolsWithAssetsTimeSpan`.
|
||||||
|
@ -38,3 +42,22 @@ type SymbolWithAssetDTO struct {
|
||||||
Volume decimal.Decimal
|
Volume decimal.Decimal
|
||||||
Txs decimal.Decimal
|
Txs decimal.Decimal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseTopCorridorsTimeSpan(s string) (*TopCorridorsTimeSpan, error) {
|
||||||
|
if s == string(TimeSpan2DaysTopCorridors) ||
|
||||||
|
s == string(TimeSpan7DaysTopCorridors) {
|
||||||
|
|
||||||
|
tmp := TopCorridorsTimeSpan(s)
|
||||||
|
return &tmp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid time span: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TopCorridorsDTO struct {
|
||||||
|
EmitterChainID sdk.ChainID
|
||||||
|
DestinationChainID sdk.ChainID
|
||||||
|
TokenChainID sdk.ChainID
|
||||||
|
TokenAddress string
|
||||||
|
Txs uint64
|
||||||
|
}
|
||||||
|
|
|
@ -406,3 +406,17 @@ func ExtractSymbolWithAssetsTimeSpan(ctx *fiber.Ctx) (*stats.SymbolWithAssetsTim
|
||||||
|
|
||||||
return timeSpan, nil
|
return timeSpan, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExtractTopCorridorsTimeSpan(ctx *fiber.Ctx) (*stats.TopCorridorsTimeSpan, error) {
|
||||||
|
defaultTimeSpan := stats.TimeSpan2DaysTopCorridors
|
||||||
|
s := ctx.Query("timeSpan")
|
||||||
|
if s == "" {
|
||||||
|
return &defaultTimeSpan, nil
|
||||||
|
}
|
||||||
|
timeSpan, err := stats.ParseTopCorridorsTimeSpan(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInvalidQueryParamError(ctx, "INVALID <timeSpan> QUERY PARAMETER", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeSpan, nil
|
||||||
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ func RegisterRoutes(
|
||||||
|
|
||||||
// stats custom endpoints
|
// stats custom endpoints
|
||||||
api.Get("/top-symbols-by-volume", statsCrtl.GetTopSymbolsByVolume)
|
api.Get("/top-symbols-by-volume", statsCrtl.GetTopSymbolsByVolume)
|
||||||
|
api.Get("/top-100-corridors", statsCrtl.GetTopCorridors)
|
||||||
|
|
||||||
// operations resource
|
// operations resource
|
||||||
operations := api.Group("/operations")
|
operations := api.Group("/operations")
|
||||||
|
|
|
@ -93,3 +93,52 @@ func (c *Controller) createTopSymbolsByVolumeResult(assets []stats.SymbolWithAss
|
||||||
}
|
}
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTop100Corridors godoc
|
||||||
|
// @Description Returns a list of the top 100 tokens, sorted in descending order by the number of transactions.
|
||||||
|
// @Tags wormholescan
|
||||||
|
// @ID /api/v1/top-100-corridors
|
||||||
|
// @Param timeSpan query string false "Time span, supported values: 2d and 7d (default is 2d)."
|
||||||
|
// @Success 200 {object} stats.TopCorridorsResult
|
||||||
|
// @Failure 400
|
||||||
|
// @Failure 500
|
||||||
|
// @Router /api/v1/top-100-corridors [get]
|
||||||
|
func (c *Controller) GetTopCorridors(ctx *fiber.Ctx) error {
|
||||||
|
timeSpan, err := middleware.ExtractTopCorridorsTimeSpan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the chain activity.
|
||||||
|
corridors, err := c.srv.GetTopCorridors(ctx.Context(), *timeSpan)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("Error getting symbol with assets", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := createTop100CorridorsResult(corridors)
|
||||||
|
|
||||||
|
return ctx.JSON(TopCorridorsResult{Corridors: result})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTop100CorridorsResult(corridors []stats.TopCorridorsDTO) []*TopCorridor {
|
||||||
|
|
||||||
|
result := make([]*TopCorridor, 0, len(corridors))
|
||||||
|
for _, c := range corridors {
|
||||||
|
result = append(result, &TopCorridor{
|
||||||
|
EmitterChainID: c.EmitterChainID,
|
||||||
|
TargetChainID: c.DestinationChainID,
|
||||||
|
TokenChainID: c.TokenChainID,
|
||||||
|
TokenAddress: c.TokenAddress,
|
||||||
|
Txs: c.Txs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(result[:], func(i, j int) bool {
|
||||||
|
return result[i].Txs > result[j].Txs
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(result) >= 100 {
|
||||||
|
return result[:100]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
@ -23,3 +23,15 @@ type TokenResult struct {
|
||||||
type TopSymbolByVolumeResult struct {
|
type TopSymbolByVolumeResult struct {
|
||||||
Symbols []*TopSymbolResult `json:"symbols"`
|
Symbols []*TopSymbolResult `json:"symbols"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TopCorridorsResult struct {
|
||||||
|
Corridors []*TopCorridor `json:"corridors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TopCorridor struct {
|
||||||
|
EmitterChainID sdk.ChainID `json:"emitter_chain"`
|
||||||
|
TargetChainID sdk.ChainID `json:"target_chain"`
|
||||||
|
TokenChainID sdk.ChainID `json:"token_chain"`
|
||||||
|
TokenAddress string `json:"token_address"`
|
||||||
|
Txs uint64 `json:"txs"`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue