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"
|
||||
)
|
||||
|
||||
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 {
|
||||
AppId string
|
||||
|
@ -62,20 +77,6 @@ func (r *TokenResolver) GetTransferredTokenByVaa(ctx context.Context, vaa *sdk.V
|
|||
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
|
||||
result, err := r.client.ParseVaaWithStandarizedProperties(vaa)
|
||||
if err != nil {
|
||||
|
@ -97,7 +98,7 @@ func (r *TokenResolver) GetTransferredTokenByVaa(ctx context.Context, vaa *sdk.V
|
|||
r.logger.Debug("Creating transferred token",
|
||||
zap.String("vaaId", vaa.MessageID()),
|
||||
zap.Error(err))
|
||||
return nil, ErrUnknownToken
|
||||
return nil, &UnknownTokenErr{detail: err.Error()}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
if s.TokenChain.String() == sdk.ChainIDUnset.String() {
|
||||
return nil, errors.New("tokenChain is unset")
|
||||
if !domain.ChainIdIsValid(s.TokenChain) {
|
||||
return nil, fmt.Errorf("tokenChain is invalid: %d", s.TokenChain)
|
||||
}
|
||||
|
||||
if s.ToChain.String() == sdk.ChainIDUnset.String() {
|
||||
return nil, errors.New("toChain is unset")
|
||||
if !domain.ChainIdIsValid(s.ToChain) {
|
||||
return nil, fmt.Errorf("toChain is invalid: %d", s.ToChain)
|
||||
}
|
||||
|
||||
if emitterChain.String() == sdk.ChainIDUnset.String() {
|
||||
return nil, errors.New("emitterChain is unset")
|
||||
if !domain.ChainIdIsValid(emitterChain) {
|
||||
return nil, fmt.Errorf("emitterChain is invalid: %d", emitterChain)
|
||||
}
|
||||
|
||||
if s.TokenAddress == "" {
|
||||
|
|
|
@ -96,7 +96,7 @@ func (m *Metric) Push(ctx context.Context, params *Params) error {
|
|||
|
||||
transferredToken, err := m.getTransferredTokenByVaa(ctx, params.Vaa)
|
||||
if err != nil {
|
||||
if err != token.ErrUnknownToken {
|
||||
if !token.IsUnknownTokenErr(err) {
|
||||
m.logger.Error("Failed to obtain transferred token for this VAA",
|
||||
zap.String("trackId", params.TrackID),
|
||||
zap.String("vaaId", params.Vaa.MessageID()),
|
||||
|
@ -129,8 +129,8 @@ func (m *Metric) Push(ctx context.Context, params *Params) error {
|
|||
)
|
||||
|
||||
} else {
|
||||
|
||||
m.logger.Warn("Cannot obtain transferred token for this VAA",
|
||||
zap.Error(err),
|
||||
zap.String("trackId", params.TrackID),
|
||||
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 {
|
||||
m.logger.Info("Transaction processed successfully",
|
||||
zap.String("trackId", params.TrackID),
|
||||
zap.Bool("isVaaSigned", isVaaSigned),
|
||||
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": {
|
||||
"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.",
|
||||
|
@ -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/": {
|
||||
"get": {
|
||||
"description": "Returns transactions. Output is paginated.",
|
||||
|
@ -2410,11 +2472,20 @@ const docTemplate = `{
|
|||
"relays.DeliveryReponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"budget": {
|
||||
"type": "string"
|
||||
},
|
||||
"execution": {
|
||||
"$ref": "#/definitions/relays.ResultExecutionResponse"
|
||||
},
|
||||
"maxRefund": {
|
||||
"type": "string"
|
||||
},
|
||||
"relayGasUsed": {
|
||||
"type": "integer"
|
||||
},
|
||||
"targetChainDecimals": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2485,6 +2556,9 @@ const docTemplate = `{
|
|||
"instructions": {
|
||||
"$ref": "#/definitions/relays.InstructionsResponse"
|
||||
},
|
||||
"maxAttempts": {
|
||||
"type": "integer"
|
||||
},
|
||||
"toTxHash": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -3025,6 +3181,7 @@ const docTemplate = `{
|
|||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
|
@ -3034,8 +3191,19 @@ const docTemplate = `{
|
|||
29,
|
||||
30,
|
||||
32,
|
||||
34,
|
||||
35,
|
||||
3104,
|
||||
10002
|
||||
4000,
|
||||
4001,
|
||||
4002,
|
||||
4003,
|
||||
4004,
|
||||
10002,
|
||||
10003,
|
||||
10004,
|
||||
10005,
|
||||
10006
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ChainIDUnset",
|
||||
|
@ -3058,6 +3226,7 @@ const docTemplate = `{
|
|||
"ChainIDNeon",
|
||||
"ChainIDTerra2",
|
||||
"ChainIDInjective",
|
||||
"ChainIDOsmosis",
|
||||
"ChainIDSui",
|
||||
"ChainIDAptos",
|
||||
"ChainIDArbitrum",
|
||||
|
@ -3067,8 +3236,19 @@ const docTemplate = `{
|
|||
"ChainIDBtc",
|
||||
"ChainIDBase",
|
||||
"ChainIDSei",
|
||||
"ChainIDScroll",
|
||||
"ChainIDMantle",
|
||||
"ChainIDWormchain",
|
||||
"ChainIDSepolia"
|
||||
"ChainIDCosmoshub",
|
||||
"ChainIDEvmos",
|
||||
"ChainIDKujira",
|
||||
"ChainIDNeutron",
|
||||
"ChainIDCelestia",
|
||||
"ChainIDSepolia",
|
||||
"ChainIDArbitrumSepolia",
|
||||
"ChainIDBaseSepolia",
|
||||
"ChainIDOptimismSepolia",
|
||||
"ChainIDHolesky"
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
"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.",
|
||||
|
@ -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/": {
|
||||
"get": {
|
||||
"description": "Returns transactions. Output is paginated.",
|
||||
|
@ -2403,11 +2465,20 @@
|
|||
"relays.DeliveryReponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"budget": {
|
||||
"type": "string"
|
||||
},
|
||||
"execution": {
|
||||
"$ref": "#/definitions/relays.ResultExecutionResponse"
|
||||
},
|
||||
"maxRefund": {
|
||||
"type": "string"
|
||||
},
|
||||
"relayGasUsed": {
|
||||
"type": "integer"
|
||||
},
|
||||
"targetChainDecimals": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2478,6 +2549,9 @@
|
|||
"instructions": {
|
||||
"$ref": "#/definitions/relays.InstructionsResponse"
|
||||
},
|
||||
"maxAttempts": {
|
||||
"type": "integer"
|
||||
},
|
||||
"toTxHash": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -3018,6 +3174,7 @@
|
|||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
|
@ -3027,8 +3184,19 @@
|
|||
29,
|
||||
30,
|
||||
32,
|
||||
34,
|
||||
35,
|
||||
3104,
|
||||
10002
|
||||
4000,
|
||||
4001,
|
||||
4002,
|
||||
4003,
|
||||
4004,
|
||||
10002,
|
||||
10003,
|
||||
10004,
|
||||
10005,
|
||||
10006
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ChainIDUnset",
|
||||
|
@ -3051,6 +3219,7 @@
|
|||
"ChainIDNeon",
|
||||
"ChainIDTerra2",
|
||||
"ChainIDInjective",
|
||||
"ChainIDOsmosis",
|
||||
"ChainIDSui",
|
||||
"ChainIDAptos",
|
||||
"ChainIDArbitrum",
|
||||
|
@ -3060,8 +3229,19 @@
|
|||
"ChainIDBtc",
|
||||
"ChainIDBase",
|
||||
"ChainIDSei",
|
||||
"ChainIDScroll",
|
||||
"ChainIDMantle",
|
||||
"ChainIDWormchain",
|
||||
"ChainIDSepolia"
|
||||
"ChainIDCosmoshub",
|
||||
"ChainIDEvmos",
|
||||
"ChainIDKujira",
|
||||
"ChainIDNeutron",
|
||||
"ChainIDCelestia",
|
||||
"ChainIDSepolia",
|
||||
"ChainIDArbitrumSepolia",
|
||||
"ChainIDBaseSepolia",
|
||||
"ChainIDOptimismSepolia",
|
||||
"ChainIDHolesky"
|
||||
]
|
||||
},
|
||||
"vaa.VaaDoc": {
|
||||
|
|
|
@ -398,10 +398,16 @@ definitions:
|
|||
type: object
|
||||
relays.DeliveryReponse:
|
||||
properties:
|
||||
budget:
|
||||
type: string
|
||||
execution:
|
||||
$ref: '#/definitions/relays.ResultExecutionResponse'
|
||||
maxRefund:
|
||||
type: string
|
||||
relayGasUsed:
|
||||
type: integer
|
||||
targetChainDecimals:
|
||||
type: integer
|
||||
type: object
|
||||
relays.InstructionsResponse:
|
||||
properties:
|
||||
|
@ -447,6 +453,8 @@ definitions:
|
|||
type: string
|
||||
instructions:
|
||||
$ref: '#/definitions/relays.InstructionsResponse'
|
||||
maxAttempts:
|
||||
type: integer
|
||||
toTxHash:
|
||||
type: string
|
||||
type: object
|
||||
|
@ -596,6 +604,59 @@ definitions:
|
|||
next:
|
||||
type: string
|
||||
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:
|
||||
properties:
|
||||
emitterChain:
|
||||
|
@ -811,6 +872,7 @@ definitions:
|
|||
- 17
|
||||
- 18
|
||||
- 19
|
||||
- 20
|
||||
- 21
|
||||
- 22
|
||||
- 23
|
||||
|
@ -820,8 +882,19 @@ definitions:
|
|||
- 29
|
||||
- 30
|
||||
- 32
|
||||
- 34
|
||||
- 35
|
||||
- 3104
|
||||
- 4000
|
||||
- 4001
|
||||
- 4002
|
||||
- 4003
|
||||
- 4004
|
||||
- 10002
|
||||
- 10003
|
||||
- 10004
|
||||
- 10005
|
||||
- 10006
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- ChainIDUnset
|
||||
|
@ -844,6 +917,7 @@ definitions:
|
|||
- ChainIDNeon
|
||||
- ChainIDTerra2
|
||||
- ChainIDInjective
|
||||
- ChainIDOsmosis
|
||||
- ChainIDSui
|
||||
- ChainIDAptos
|
||||
- ChainIDArbitrum
|
||||
|
@ -853,8 +927,19 @@ definitions:
|
|||
- ChainIDBtc
|
||||
- ChainIDBase
|
||||
- ChainIDSei
|
||||
- ChainIDScroll
|
||||
- ChainIDMantle
|
||||
- ChainIDWormchain
|
||||
- ChainIDCosmoshub
|
||||
- ChainIDEvmos
|
||||
- ChainIDKujira
|
||||
- ChainIDNeutron
|
||||
- ChainIDCelestia
|
||||
- ChainIDSepolia
|
||||
- ChainIDArbitrumSepolia
|
||||
- ChainIDBaseSepolia
|
||||
- ChainIDOptimismSepolia
|
||||
- ChainIDHolesky
|
||||
vaa.VaaDoc:
|
||||
properties:
|
||||
appId:
|
||||
|
@ -1575,6 +1660,27 @@ paths:
|
|||
description: Not Found
|
||||
tags:
|
||||
- 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:
|
||||
get:
|
||||
description: |-
|
||||
|
@ -1616,6 +1722,28 @@ paths:
|
|||
description: Internal Server Error
|
||||
tags:
|
||||
- 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/:
|
||||
get:
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
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 (
|
||||
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.
|
||||
|
@ -36,3 +37,12 @@ func (s *Service) GetSymbolWithAssets(ctx context.Context, ts SymbolWithAssetsTi
|
|||
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`.
|
||||
type SymbolWithAssetsTimeSpan string
|
||||
type TopCorridorsTimeSpan string
|
||||
|
||||
const (
|
||||
TimeSpan7Days SymbolWithAssetsTimeSpan = "7d"
|
||||
TimeSpan15Days SymbolWithAssetsTimeSpan = "15d"
|
||||
TimeSpan30Days SymbolWithAssetsTimeSpan = "30d"
|
||||
|
||||
TimeSpan2DaysTopCorridors TopCorridorsTimeSpan = "2d"
|
||||
TimeSpan7DaysTopCorridors TopCorridorsTimeSpan = "7d"
|
||||
)
|
||||
|
||||
// ParseSymbolsWithAssetsTimeSpan parses a string and returns a `SymbolsWithAssetsTimeSpan`.
|
||||
|
@ -38,3 +42,22 @@ type SymbolWithAssetDTO struct {
|
|||
Volume 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
|
||||
}
|
||||
|
||||
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
|
||||
api.Get("/top-symbols-by-volume", statsCrtl.GetTopSymbolsByVolume)
|
||||
api.Get("/top-100-corridors", statsCrtl.GetTopCorridors)
|
||||
|
||||
// operations resource
|
||||
operations := api.Group("/operations")
|
||||
|
|
|
@ -93,3 +93,52 @@ func (c *Controller) createTopSymbolsByVolumeResult(assets []stats.SymbolWithAss
|
|||
}
|
||||
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 {
|
||||
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