Get top assets by volume (#302)
### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/276 This pull request implements the endpoint `GET /api/v1/top-assets-by-volume`, which returns the assets that have the highest volume. 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:
parent
3867a2eb24
commit
a21f40ed55
|
@ -0,0 +1,19 @@
|
|||
import "date"
|
||||
|
||||
option task = {
|
||||
name: "asset volume with 24-hour granularity",
|
||||
every: 24h,
|
||||
}
|
||||
|
||||
start = date.sub(from: now(), d: 24h)
|
||||
stop = now()
|
||||
|
||||
from(bucket: "wormscan-24hours-mainnet-staging")
|
||||
|> 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"])
|
||||
|> sum(column: "_value")
|
||||
|> set(key: "_measurement", value: "vaa_volume_24h")
|
||||
|> map(fn: (r) => ({r with _time: start}))
|
||||
|> to(bucket: "wormscan-30days-mainnet-staging")
|
|
@ -1,6 +1,7 @@
|
|||
package transactions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
|
||||
|
@ -18,6 +19,37 @@ type Scorecards struct {
|
|||
Volume24h string
|
||||
}
|
||||
|
||||
// AssetDTO is used for the return value of the function `GetTopAssetsByVolume`.
|
||||
type AssetDTO struct {
|
||||
EmitterChain sdk.ChainID
|
||||
TokenChain sdk.ChainID
|
||||
TokenAddress string
|
||||
Volume string
|
||||
}
|
||||
|
||||
// TopAssetsTimerange is used as an input parameter for the function `GetTopAssetsByVolume`.
|
||||
type TopAssetsTimerange string
|
||||
|
||||
const (
|
||||
TopAssetsTimerange7Days TopAssetsTimerange = "7d"
|
||||
TopAssetsTimerange15Days TopAssetsTimerange = "15d"
|
||||
TopAssetsTimerange30Days TopAssetsTimerange = "30d"
|
||||
)
|
||||
|
||||
// NewTopAssetsTimerange parses a string and returns a `TopAssetsTimerange`.
|
||||
func NewTopAssetsTimerange(s string) (*TopAssetsTimerange, error) {
|
||||
|
||||
if s == string(TopAssetsTimerange7Days) ||
|
||||
s == string(TopAssetsTimerange15Days) ||
|
||||
s == string(TopAssetsTimerange30Days) {
|
||||
|
||||
tmp := TopAssetsTimerange(s)
|
||||
return &tmp, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid timerange: %s", s)
|
||||
}
|
||||
|
||||
type GlobalTransactionDoc struct {
|
||||
ID string `bson:"_id" json:"id"`
|
||||
OriginTx *OriginTx `bson:"originTx" json:"originTx"`
|
||||
|
|
|
@ -3,6 +3,7 @@ package transactions
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -67,28 +68,139 @@ from(bucket: "%s")
|
|||
|> filter(fn:(r) => r._field == "volume")
|
||||
|> drop(columns: ["_measurement", "app_id", "destination_address", "destination_chain", "token_address", "token_chain"])
|
||||
|> sum(column: "_value")
|
||||
|> toString()
|
||||
`
|
||||
|
||||
const queryTemplateTopAssetsByVolume = `
|
||||
import "date"
|
||||
|
||||
// Get historic volumes from the summarized metric.
|
||||
summarized = from(bucket: "%s")
|
||||
|> range(start: -%s)
|
||||
|> filter(fn: (r) => r["_measurement"] == "vaa_volume_24h")
|
||||
|> group(columns: ["emitter_chain", "token_address", "token_chain"])
|
||||
|
||||
// Get the current day's volume 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")
|
||||
|> group(columns: ["emitter_chain", "token_address", "token_chain"])
|
||||
|
||||
// Merge all results, compute the sum, return the top 7 volumes.
|
||||
union(tables: [summarized, raw])
|
||||
|> group(columns: ["emitter_chain", "token_address", "token_chain"])
|
||||
|> sum()
|
||||
|> group()
|
||||
|> top(columns: ["_value"], n: 7)
|
||||
`
|
||||
|
||||
type Repository struct {
|
||||
influxCli influxdb2.Client
|
||||
queryAPI api.QueryAPI
|
||||
bucket string
|
||||
db *mongo.Database
|
||||
collections struct {
|
||||
influxCli influxdb2.Client
|
||||
queryAPI api.QueryAPI
|
||||
bucketInfiniteRetention string
|
||||
bucket30DaysRetention string
|
||||
bucket24HoursRetention string
|
||||
db *mongo.Database
|
||||
collections struct {
|
||||
globalTransactions *mongo.Collection
|
||||
}
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewRepository(client influxdb2.Client, org, bucket string, db *mongo.Database, logger *zap.Logger) *Repository {
|
||||
queryAPI := client.QueryAPI(org)
|
||||
return &Repository{influxCli: client,
|
||||
queryAPI: queryAPI,
|
||||
bucket: bucket,
|
||||
db: db,
|
||||
collections: struct{ globalTransactions *mongo.Collection }{globalTransactions: db.Collection("globalTransactions")},
|
||||
logger: logger}
|
||||
func NewRepository(
|
||||
client influxdb2.Client,
|
||||
org string,
|
||||
bucket24HoursRetention, bucket30DaysRetention, bucketInfiniteRetention string,
|
||||
db *mongo.Database,
|
||||
logger *zap.Logger,
|
||||
) *Repository {
|
||||
|
||||
r := Repository{
|
||||
influxCli: client,
|
||||
queryAPI: client.QueryAPI(org),
|
||||
bucket24HoursRetention: bucket24HoursRetention,
|
||||
bucket30DaysRetention: bucket30DaysRetention,
|
||||
bucketInfiniteRetention: bucketInfiniteRetention,
|
||||
db: db,
|
||||
collections: struct{ globalTransactions *mongo.Collection }{globalTransactions: db.Collection("globalTransactions")},
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
return &r
|
||||
}
|
||||
|
||||
func (r *Repository) GetTopAssetsByVolume(ctx context.Context, timerange *TopAssetsTimerange) ([]AssetDTO, error) {
|
||||
|
||||
// Submit the query to InfluxDB
|
||||
query := fmt.Sprintf(queryTemplateTopAssetsByVolume, r.bucket30DaysRetention, *timerange, r.bucket24HoursRetention)
|
||||
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"`
|
||||
TokenChain string `mapstructure:"token_chain"`
|
||||
TokenAddress string `mapstructure:"token_address"`
|
||||
Volume 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 []AssetDTO
|
||||
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 token chain
|
||||
tokenChain, err := strconv.ParseUint(rows[i].TokenChain, 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert token chain field to uint16")
|
||||
}
|
||||
|
||||
// append the new item to the response
|
||||
asset := AssetDTO{
|
||||
EmitterChain: sdk.ChainID(emitterChain),
|
||||
TokenChain: sdk.ChainID(tokenChain),
|
||||
TokenAddress: rows[i].TokenAddress,
|
||||
Volume: convertToDecimal(rows[i].Volume),
|
||||
}
|
||||
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 {
|
||||
|
||||
// If the amount is less than 1, just use a format mask.
|
||||
if amount < 1_0000_0000 {
|
||||
return fmt.Sprintf("0.%08d", amount)
|
||||
}
|
||||
|
||||
// If the amount is equal or greater than 1, we need to insert a dot 8 digits from the end.
|
||||
s := fmt.Sprintf("%d", amount)
|
||||
l := len(s)
|
||||
result := s[:l-8] + "." + s[l-8:]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Repository) FindChainActivity(ctx context.Context, q *ChainActivityQuery) ([]ChainActivityResult, error) {
|
||||
|
@ -122,9 +234,9 @@ func (r *Repository) buildFindVolumeQuery(q *ChainActivityQuery) string {
|
|||
}
|
||||
if q.HasAppIDS() {
|
||||
apps := `["` + strings.Join(q.GetAppIDs(), `","`) + `"]`
|
||||
return fmt.Sprintf(queryTemplateWithApps, r.bucket, start, stop, apps, operation)
|
||||
return fmt.Sprintf(queryTemplateWithApps, r.bucketInfiniteRetention, start, stop, apps, operation)
|
||||
}
|
||||
return fmt.Sprintf(queryTemplate, r.bucket, start, stop, operation)
|
||||
return fmt.Sprintf(queryTemplate, r.bucketInfiniteRetention, start, stop, operation)
|
||||
}
|
||||
|
||||
func (r *Repository) GetScorecards(ctx context.Context) (*Scorecards, error) {
|
||||
|
@ -159,7 +271,7 @@ func (r *Repository) GetScorecards(ctx context.Context) (*Scorecards, error) {
|
|||
func (r *Repository) getTotalTxCount(ctx context.Context) (string, error) {
|
||||
|
||||
// query 24h transactions
|
||||
query := fmt.Sprintf(queryTemplateTotalTxCount, r.bucket)
|
||||
query := fmt.Sprintf(queryTemplateTotalTxCount, r.bucketInfiniteRetention)
|
||||
result, err := r.queryAPI.Query(ctx, query)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to query total transaction count", zap.Error(err))
|
||||
|
@ -187,7 +299,7 @@ func (r *Repository) getTotalTxCount(ctx context.Context) (string, error) {
|
|||
func (r *Repository) getTxCount24h(ctx context.Context) (string, error) {
|
||||
|
||||
// query 24h transactions
|
||||
query := fmt.Sprintf(queryTemplateTxCount24h, r.bucket)
|
||||
query := fmt.Sprintf(queryTemplateTxCount24h, r.bucketInfiniteRetention)
|
||||
result, err := r.queryAPI.Query(ctx, query)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to query 24h transactions", zap.Error(err))
|
||||
|
@ -215,7 +327,7 @@ func (r *Repository) getTxCount24h(ctx context.Context) (string, error) {
|
|||
func (r *Repository) getVolume24h(ctx context.Context) (string, error) {
|
||||
|
||||
// query 24h volume
|
||||
query := fmt.Sprintf(queryTemplateVolume24h, r.bucket)
|
||||
query := fmt.Sprintf(queryTemplateVolume24h, r.bucketInfiniteRetention)
|
||||
result, err := r.queryAPI.Query(ctx, query)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to query 24h volume", zap.Error(err))
|
||||
|
@ -231,22 +343,14 @@ func (r *Repository) getVolume24h(ctx context.Context) (string, error) {
|
|||
|
||||
// deserialize the row returned
|
||||
row := struct {
|
||||
Value string `mapstructure:"_value"`
|
||||
Value int64 `mapstructure:"_value"`
|
||||
}{}
|
||||
if err := mapstructure.Decode(result.Record().Values(), &row); err != nil {
|
||||
return "", fmt.Errorf("failed to decode 24h volume count query response: %w", err)
|
||||
}
|
||||
|
||||
// If there is less than 1 USD un volume, round it down to 0 to make math simpler in the next step
|
||||
l := len(row.Value)
|
||||
if l < 9 {
|
||||
return "0.00000000", nil
|
||||
}
|
||||
|
||||
// Turn the integer amount into a decimal.
|
||||
// The number always has 8 decimals, so we just need to insert a dot 8 digits from the end.
|
||||
volume := row.Value[:l-8] + "." + row.Value[l-8:]
|
||||
|
||||
// convert the volume to a string and return
|
||||
volume := convertToDecimal(row.Value)
|
||||
return volume, nil
|
||||
}
|
||||
|
||||
|
@ -272,7 +376,7 @@ func (r *Repository) GetTransactionCount(ctx context.Context, q *TransactionCoun
|
|||
}
|
||||
|
||||
func (r *Repository) buildLastTrxQuery(q *TransactionCountQuery) string {
|
||||
return fmt.Sprintf(queryTemplateVaaCount, r.bucket, q.TimeSpan, q.SampleRate)
|
||||
return fmt.Sprintf(queryTemplateVaaCount, r.bucketInfiniteRetention, q.TimeSpan, q.SampleRate)
|
||||
}
|
||||
|
||||
func (r *Repository) FindGlobalTransactionByID(ctx context.Context, q *GlobalTransactionQuery) (*GlobalTransactionDoc, error) {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package transactions
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_convertToDecimal(t *testing.T) {
|
||||
|
||||
tcs := []struct {
|
||||
input int64
|
||||
output string
|
||||
}{
|
||||
{
|
||||
input: 1,
|
||||
output: "0.00000001",
|
||||
},
|
||||
{
|
||||
input: 1000_0000,
|
||||
output: "0.10000000",
|
||||
},
|
||||
{
|
||||
input: 1_0000_0000,
|
||||
output: "1.00000000",
|
||||
},
|
||||
{
|
||||
input: 1234_5678_1234,
|
||||
output: "1234.56781234",
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tcs {
|
||||
tc := tcs[i]
|
||||
|
||||
result := convertToDecimal(tc.input)
|
||||
if result != tc.output {
|
||||
t.Errorf("expected %s, got %s", tc.output, result)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -28,6 +28,10 @@ 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)
|
||||
}
|
||||
|
||||
// GetChainActivity get chain activity.
|
||||
func (s *Service) GetChainActivity(ctx context.Context, q *ChainActivityQuery) ([]ChainActivityResult, error) {
|
||||
return s.repo.FindChainActivity(ctx, q)
|
||||
|
|
|
@ -42,10 +42,12 @@ type AppConfig struct {
|
|||
P2pNetwork string
|
||||
PprofEnabled bool
|
||||
Influx struct {
|
||||
URL string
|
||||
Token string
|
||||
Organization string
|
||||
Bucket string
|
||||
URL string
|
||||
Token string
|
||||
Organization string
|
||||
Bucket24HoursRetention string
|
||||
Bucket30DaysRetention string
|
||||
BucketInfiniteRetention string
|
||||
}
|
||||
}
|
||||
|
||||
|
|
10
api/main.go
10
api/main.go
|
@ -112,7 +112,15 @@ func main() {
|
|||
governorRepo := governor.NewRepository(db, rootLogger)
|
||||
infrastructureRepo := infrastructure.NewRepository(db, rootLogger)
|
||||
heartbeatsRepo := heartbeats.NewRepository(db, rootLogger)
|
||||
transactionsRepo := transactions.NewRepository(influxCli, cfg.Influx.Organization, cfg.Influx.Bucket, db, rootLogger)
|
||||
transactionsRepo := transactions.NewRepository(
|
||||
influxCli,
|
||||
cfg.Influx.Organization,
|
||||
cfg.Influx.Bucket24HoursRetention,
|
||||
cfg.Influx.Bucket30DaysRetention,
|
||||
cfg.Influx.BucketInfiniteRetention,
|
||||
db,
|
||||
rootLogger,
|
||||
)
|
||||
|
||||
// Set up services
|
||||
addressService := address.NewService(addressRepo, rootLogger)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/transactions"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/api/response"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/api/types"
|
||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
|
@ -298,6 +299,18 @@ 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) {
|
||||
|
||||
s := ctx.Query("timerange")
|
||||
timerange, err := transactions.NewTopAssetsTimerange(s)
|
||||
if err != nil {
|
||||
return nil, response.NewInvalidQueryParamError(ctx, "INVALID <timerange> QUERY PARAMETER", nil)
|
||||
}
|
||||
|
||||
return timerange, nil
|
||||
}
|
||||
|
||||
func ExtractTimeRange(ctx *fiber.Ctx) (*time.Time, *time.Time, error) {
|
||||
startTime, err := ExtractTime(ctx, "start_time")
|
||||
if err != nil {
|
||||
|
|
|
@ -67,6 +67,7 @@ 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)
|
||||
|
||||
// vaas resource
|
||||
vaas := api.Group("/vaas")
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/shopspring/decimal"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/transactions"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/api/middleware"
|
||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -84,6 +85,57 @@ 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.
|
||||
// @Tags Wormscan
|
||||
// @ID get-top-assets-by-volume
|
||||
// @Success 200 {object} TopAssetsByVolumeResponse
|
||||
// @Failure 500
|
||||
// @Router /api/v1/top-assets-by-volume [get]
|
||||
func (c *Controller) GetTopAssetsByVolume(ctx *fiber.Ctx) error {
|
||||
|
||||
// Extract query parameters
|
||||
timerange, err := middleware.ExtractTopAssetsTimerange(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Query assets from the database
|
||||
assetDTOs, err := c.srv.GetTopAssetsByVolume(ctx.Context(), timerange)
|
||||
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{
|
||||
Assets: make([]AssetWithVolume, 0, len(assetDTOs)),
|
||||
}
|
||||
for i := range assetDTOs {
|
||||
|
||||
// Look up the token symbol
|
||||
tokenMeta, ok := domain.GetTokenByAddress(assetDTOs[i].TokenChain, assetDTOs[i].TokenAddress)
|
||||
if !ok {
|
||||
c.logger.Warn("failed to obtain token metadata in top volume chart",
|
||||
zap.String("token_chain", assetDTOs[i].TokenChain.String()),
|
||||
zap.String("token_address", assetDTOs[i].TokenAddress),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// Populate the response struct
|
||||
asset := AssetWithVolume{
|
||||
EmitterChain: assetDTOs[i].EmitterChain,
|
||||
Volume: assetDTOs[i].Volume,
|
||||
Symbol: tokenMeta.Symbol,
|
||||
}
|
||||
response.Assets = append(response.Assets, asset)
|
||||
}
|
||||
|
||||
return ctx.JSON(response)
|
||||
|
||||
}
|
||||
|
||||
// GetChainActivity godoc
|
||||
// @Description Returns a list of tx by source chain and destination chain.
|
||||
// @Tags Wormscan
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package transactions
|
||||
|
||||
import "github.com/shopspring/decimal"
|
||||
import (
|
||||
"github.com/shopspring/decimal"
|
||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
)
|
||||
|
||||
type Tx struct {
|
||||
Chain int `json:"chain"`
|
||||
|
@ -40,3 +43,14 @@ type ScorecardsResponse struct {
|
|||
// Volume transferred through the token bridge in the last 24 hours, in USD.
|
||||
Volume24h string `json:"24h_volume"`
|
||||
}
|
||||
|
||||
// TopAssetsByVolumeResponse is the "200 OK" response model for `GET /api/v1/top-assets-by-volume`.
|
||||
type TopAssetsByVolumeResponse struct {
|
||||
Assets []AssetWithVolume `json:"assets"`
|
||||
}
|
||||
|
||||
type AssetWithVolume struct {
|
||||
EmitterChain sdk.ChainID `json:"emitterChain`
|
||||
Symbol string `json:"symbol"`
|
||||
Volume string `json:"volume"`
|
||||
}
|
||||
|
|
|
@ -15,8 +15,13 @@ func (s Symbol) String() string {
|
|||
|
||||
// TokenMetadata contains information about a token supported by Portal Token Bridge.
|
||||
type TokenMetadata struct {
|
||||
// Symbol is the unique name of the token.
|
||||
//
|
||||
// It does not always coincide with the name exchanges use to list the token,
|
||||
// which is stored in the field `UnderlyingSymbol`.
|
||||
Symbol string
|
||||
// UnderlyingSymbol is the name that crypto exchanges use to list the underlying asset represented by this token.
|
||||
// For example, the underlying symbol of the token "WFTM (wrapped fantom)" is "FTM".
|
||||
// For example, the underlying symbol of the token "USDCso (USDC minted on Solana)" is "USDC".
|
||||
UnderlyingSymbol Symbol
|
||||
Decimals uint8
|
||||
CoingeckoID string
|
||||
|
@ -106,6 +111,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDSolana,
|
||||
TokenAddress: "069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f00000000001",
|
||||
Symbol: "SOL",
|
||||
UnderlyingSymbol: "SOL",
|
||||
Decimals: 9,
|
||||
CoingeckoID: "solana",
|
||||
|
@ -117,6 +123,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDSolana,
|
||||
TokenAddress: "b953b5f8dd5457a2a0f0d41903409785b9d84d4045614faa4f505ee132dcd769",
|
||||
Symbol: "DUST",
|
||||
UnderlyingSymbol: "DUST",
|
||||
Decimals: 9,
|
||||
CoingeckoID: "dust-protocol",
|
||||
|
@ -128,6 +135,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDSolana,
|
||||
TokenAddress: "c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61",
|
||||
Symbol: "USDCso",
|
||||
UnderlyingSymbol: "USDC",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "usd-coin",
|
||||
|
@ -139,6 +147,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDSolana,
|
||||
TokenAddress: "ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e208264",
|
||||
Symbol: "USDTso",
|
||||
UnderlyingSymbol: "USDT",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "tether",
|
||||
|
@ -150,6 +159,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
// * https://api.wormscan.io/api/v1/vaas/1/ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5/289681?parsedPayload=true
|
||||
TokenChain: sdk.ChainIDSolana,
|
||||
TokenAddress: "dd40a2f6f423e4c3990a83eac3d9d9c1fe625b36cbc5e4a6d553544552a867ee",
|
||||
Symbol: "BRZ",
|
||||
UnderlyingSymbol: "BRZ",
|
||||
Decimals: 4,
|
||||
CoingeckoID: "brz",
|
||||
|
@ -161,6 +171,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDSolana,
|
||||
TokenAddress: "45a5161476cc9df6ef8583b581a3111b4416ebcea65f4eca5bb961124c3399df",
|
||||
Symbol: "XTAG",
|
||||
UnderlyingSymbol: "XTAG",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "xhashtag",
|
||||
|
@ -172,6 +183,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDSolana,
|
||||
TokenAddress: "0e167d0db0259fb83bca338947ce42fe2c34b803285c7e99b26874bd83bac0a8",
|
||||
Symbol: "ZBC",
|
||||
UnderlyingSymbol: "ZBC",
|
||||
Decimals: 8,
|
||||
CoingeckoID: "zebec-protocol",
|
||||
|
@ -183,6 +195,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDEthereum,
|
||||
TokenAddress: "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
Symbol: "USDCet",
|
||||
UnderlyingSymbol: "USDC",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "usd-coin",
|
||||
|
@ -194,6 +207,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDEthereum,
|
||||
TokenAddress: "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||
Symbol: "ETH",
|
||||
UnderlyingSymbol: "ETH",
|
||||
Decimals: 8,
|
||||
CoingeckoID: "ethereum",
|
||||
|
@ -205,6 +219,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDEthereum,
|
||||
TokenAddress: "000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7",
|
||||
Symbol: "USDTet",
|
||||
UnderlyingSymbol: "USDT",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "tether",
|
||||
|
@ -216,6 +231,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDEthereum,
|
||||
TokenAddress: "0000000000000000000000007659ce147d0e714454073a5dd7003544234b6aa0",
|
||||
Symbol: "XCAD",
|
||||
UnderlyingSymbol: "XCAD",
|
||||
Decimals: 9,
|
||||
CoingeckoID: "xcad-network",
|
||||
|
@ -227,6 +243,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDEthereum,
|
||||
TokenAddress: "000000000000000000000000fd09911130e6930bf87f2b0554c44f400bd80d3e",
|
||||
Symbol: "ETHIX",
|
||||
UnderlyingSymbol: "ETHIX",
|
||||
Decimals: 8,
|
||||
CoingeckoID: "ethichub",
|
||||
|
@ -238,6 +255,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDEthereum,
|
||||
TokenAddress: "0000000000000000000000005de8ab7e27f6e7a1fff3e5b337584aa43961beef",
|
||||
Symbol: "SDEX",
|
||||
UnderlyingSymbol: "SDEX",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "smardex",
|
||||
|
@ -249,6 +267,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDEthereum,
|
||||
TokenAddress: "000000000000000000000000727f064a78dc734d33eec18d5370aef32ffd46e4",
|
||||
Symbol: "ORION",
|
||||
UnderlyingSymbol: "ORION",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "orion-money",
|
||||
|
@ -260,6 +279,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDEthereum,
|
||||
TokenAddress: "0000000000000000000000006b3595068778dd592e39a122f4f5a5cf09c90fe2",
|
||||
Symbol: "SUSHI",
|
||||
UnderlyingSymbol: "SUSHI",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "sushi",
|
||||
|
@ -271,6 +291,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
// * https://api.wormscan.io/api/v1/vaas/4/000000000000000000000000b6f6d86a8f9879a9c87f643768d9efc38c1da6e7/243784?parsedPayload=true
|
||||
TokenChain: sdk.ChainIDTerra,
|
||||
TokenAddress: "010000000000000000000000000000000000000000000000000000756c756e61",
|
||||
Symbol: "LUNC",
|
||||
UnderlyingSymbol: "LUNC",
|
||||
CoingeckoID: "terra-luna",
|
||||
Decimals: 6,
|
||||
|
@ -282,6 +303,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDTerra,
|
||||
TokenAddress: "0100000000000000000000000000000000000000000000000000000075757364",
|
||||
Symbol: "UST",
|
||||
UnderlyingSymbol: "UST",
|
||||
Decimals: 8,
|
||||
CoingeckoID: "terrausd-wormhole",
|
||||
|
@ -293,6 +315,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDBSC,
|
||||
TokenAddress: "00000000000000000000000055d398326f99059ff775485246999027b3197955",
|
||||
Symbol: "USDTbs",
|
||||
UnderlyingSymbol: "USDT",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "tether",
|
||||
|
@ -304,6 +327,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDBSC,
|
||||
TokenAddress: "0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d",
|
||||
Symbol: "USDCbs",
|
||||
UnderlyingSymbol: "USDC",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "usd-coin",
|
||||
|
@ -315,6 +339,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDBSC,
|
||||
TokenAddress: "000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
|
||||
Symbol: "BNB",
|
||||
UnderlyingSymbol: "BNB",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "binancecoin",
|
||||
|
@ -326,6 +351,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDBSC,
|
||||
TokenAddress: "000000000000000000000000ad6742a35fb341a9cc6ad674738dd8da98b94fb1",
|
||||
Symbol: "WOM",
|
||||
UnderlyingSymbol: "WOM",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "wombat-exchange",
|
||||
|
@ -337,6 +363,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDBSC,
|
||||
TokenAddress: "000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d56",
|
||||
Symbol: "BUSDbs",
|
||||
UnderlyingSymbol: "BUSD",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "binance-usd",
|
||||
|
@ -347,7 +374,8 @@ var tokenMetadata = []TokenMetadata{
|
|||
// * https://api.wormscan.io/api/v1/vaas/4/000000000000000000000000b6f6d86a8f9879a9c87f643768d9efc38c1da6e7/244486?parsedPayload=true
|
||||
{
|
||||
TokenChain: sdk.ChainIDBSC,
|
||||
TokenAddress: "",
|
||||
TokenAddress: "0000000000000000000000004b8285ab433d8f69cb48d5ad62b415ed1a221e4f",
|
||||
Symbol: "MCRT",
|
||||
UnderlyingSymbol: "MCRT",
|
||||
Decimals: 8,
|
||||
CoingeckoID: "magiccraft",
|
||||
|
@ -359,6 +387,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDPolygon,
|
||||
TokenAddress: "0000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa84174",
|
||||
Symbol: "USDCpo",
|
||||
UnderlyingSymbol: "USDC",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "usd-coin",
|
||||
|
@ -370,6 +399,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDPolygon,
|
||||
TokenAddress: "000000000000000000000000c2132d05d31c914a87c6611c10748aeb04b58e8f",
|
||||
Symbol: "USDTpo",
|
||||
UnderlyingSymbol: "USDT",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "tether",
|
||||
|
@ -381,6 +411,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDPolygon,
|
||||
TokenAddress: "0000000000000000000000000d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
|
||||
Symbol: "MATICpo",
|
||||
UnderlyingSymbol: "MATIC",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "matic-network",
|
||||
|
@ -392,6 +423,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDAvalanche,
|
||||
TokenAddress: "000000000000000000000000b31f66aa3c1e785363f0875a1b74e27b85fd66c7",
|
||||
Symbol: "AVAX",
|
||||
UnderlyingSymbol: "AVAX",
|
||||
Decimals: 18,
|
||||
CoingeckoID: "avalanche-2",
|
||||
|
@ -403,6 +435,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDAvalanche,
|
||||
TokenAddress: "000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e",
|
||||
Symbol: "USDCav",
|
||||
UnderlyingSymbol: "USDC",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "usd-coin",
|
||||
|
@ -414,9 +447,10 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDFantom,
|
||||
TokenAddress: "00000000000000000000000021be370d5312f44cb42ce377bc9b8a0cef1a4c83",
|
||||
UnderlyingSymbol: "FTM",
|
||||
Symbol: "WFTM",
|
||||
UnderlyingSymbol: "WFTM",
|
||||
Decimals: 8,
|
||||
CoingeckoID: "fantom",
|
||||
CoingeckoID: "wrapped-fantom",
|
||||
},
|
||||
// SUI
|
||||
//
|
||||
|
@ -425,35 +459,19 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDSui,
|
||||
TokenAddress: "9258181f5ceac8dbffb7030890243caed69a9599d2886d957a9cb7656af3bdb3",
|
||||
Symbol: "SUI",
|
||||
UnderlyingSymbol: "SUI",
|
||||
Decimals: 9,
|
||||
CoingeckoID: "sui",
|
||||
},
|
||||
{
|
||||
//TODO find the ContractAddress, decimals and an example VAA for this token.
|
||||
TokenChain: sdk.ChainIDAcala,
|
||||
UnderlyingSymbol: "ACA",
|
||||
CoingeckoID: "acala",
|
||||
},
|
||||
{
|
||||
//TODO find the ContractAddress, decimals and an example VAA for this token.
|
||||
TokenChain: sdk.ChainIDAlgorand,
|
||||
UnderlyingSymbol: "ALGO",
|
||||
CoingeckoID: "algorand",
|
||||
},
|
||||
{
|
||||
//TODO find the ContractAddress, decimals and an example VAA for this token.
|
||||
TokenChain: sdk.ChainIDAptos,
|
||||
UnderlyingSymbol: "APT",
|
||||
CoingeckoID: "aptos",
|
||||
},
|
||||
// USD Coin
|
||||
// USD Coin (aptos)
|
||||
//
|
||||
// Examples:
|
||||
// * https://api.wormscan.io/api/v1/vaas/5/0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde/101667?parsedPayload=true
|
||||
{
|
||||
TokenChain: sdk.ChainIDAptos,
|
||||
TokenAddress: "6155e0a106aeb3b0944388613027aee11c84921969ff775727e8046b17b17154",
|
||||
Symbol: "USDCap",
|
||||
UnderlyingSymbol: "USDC",
|
||||
Decimals: 6,
|
||||
CoingeckoID: "usd-coin",
|
||||
|
@ -465,6 +483,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
// * https://api.wormscan.io/api/v1/vaas/16/000000000000000000000000b1731c586ca89a23809861c6103f0b96b3f57d92/5897?parsedPayload=true
|
||||
TokenChain: sdk.ChainIDMoonbeam,
|
||||
TokenAddress: "000000000000000000000000acc15dc74880c9944775448304b263d191c6077f",
|
||||
Symbol: "WGLMR",
|
||||
UnderlyingSymbol: "WGLMR",
|
||||
Decimals: 8,
|
||||
CoingeckoID: "moonbeam",
|
||||
|
@ -476,6 +495,7 @@ var tokenMetadata = []TokenMetadata{
|
|||
{
|
||||
TokenChain: sdk.ChainIDMoonbeam,
|
||||
TokenAddress: "00000000000000000000000030d2a9f5fdf90ace8c17952cbb4ee48a55d916a7",
|
||||
Symbol: "WETH",
|
||||
UnderlyingSymbol: "WETH",
|
||||
Decimals: 8,
|
||||
CoingeckoID: "weth",
|
||||
|
|
Loading…
Reference in New Issue