wormhole-explorer/api/handlers/transactions/service.go

213 lines
6.9 KiB
Go
Raw Normal View History

package transactions
import (
"context"
errors "errors"
"fmt"
"github.com/valyala/fasthttp"
"strings"
"time"
"github.com/wormhole-foundation/wormhole-explorer/api/cacheable"
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
"github.com/wormhole-foundation/wormhole-explorer/api/internal/metrics"
Add endpoint `GET /api/v1/transactions` (#388) ### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/385 This pull request implements a new endpoint, `GET /api/v1/transactions`, which will be consumed by the wormhole explorer UI. The endpoint returns a paginated list of transactions, in which each element contains a brief overview of the transaction (ID, txHash, status, etc.). It exposes offset-based pagination via the parameters `page` and `pageSize`. Also, results can be obtained for a specific address by using the `address` query parameter. The response model looks like this: ```json { "transactions": [ { "id": "1/5ec18c34b47c63d17ab43b07b9b2319ea5ee2d163bce2e467000174e238c8e7f/12965", "timestamp": "2023-06-08T19:30:19Z", "txHash": "a302c4ab2d6b9a6003951d2e91f8fdbb83cfa20f6ffb588b95ef0290aab37066", "originChain": 1, "status": "ongoing" }, { "id": "22/0000000000000000000000000000000000000000000000000000000000000001/18308", "timestamp": "2023-06-08T19:17:14Z", "txHash": "00000000000000000000000000000000000000000000000000000000000047e7", "originChain": 22, "destinationAddress": "0x00000000000000000000000067e8a40816a983fbe3294aaebd0cc2391815b86b", "destinationChain": 5, "tokenAmount": "0.12", "usdAmount": "0.12012", "symbol": "USDC", "status": "completed" }, ... ] } ``` ### Limitations of the current implementation 1. Doesn't return the total number of results (this may result in a performance issue when we filter by address) 2. Can only filter by receiver address (we don't have sender information in the database yet)
2023-06-12 07:43:48 -07:00
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache"
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
"github.com/wormhole-foundation/wormhole-explorer/common/types"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
type Service struct {
repo *Repository
cache cache.Cache
expiration time.Duration
supportedChainIDs map[vaa.ChainID]string
tokenProvider *domain.TokenProvider
metrics metrics.Metrics
logger *zap.Logger
}
const (
lastTxsKey = "wormscan:last-txs"
scorecardsKey = "wormscan:scorecards"
topAssetsByVolumeKey = "wormscan:top-assets-by-volume"
topChainPairsByNumTransfersKey = "wormscan:top-chain-pairs-by-num-transfers"
chainActivityKey = "wormscan:chain-activity"
chainActivityTopsKey = "wormscan:chain-activity-tops"
)
// NewService create a new Service.
func NewService(repo *Repository, cache cache.Cache, expiration time.Duration, tokenProvider *domain.TokenProvider, metrics metrics.Metrics, logger *zap.Logger) *Service {
supportedChainIDs := domain.GetSupportedChainIDs()
return &Service{repo: repo, supportedChainIDs: supportedChainIDs,
cache: cache, expiration: expiration, tokenProvider: tokenProvider, metrics: metrics,
logger: logger.With(zap.String("module", "TransactionService"))}
}
// GetTransactionCount get the last transactions.
func (s *Service) GetTransactionCount(ctx context.Context, q *TransactionCountQuery) ([]TransactionCountResult, error) {
key := fmt.Sprintf("%s:%s:%s:%v", lastTxsKey, q.TimeSpan, q.SampleRate, q.CumulativeSum)
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, key, s.metrics,
func() ([]TransactionCountResult, error) {
return s.repo.GetTransactionCount(ctx, q)
})
}
func (s *Service) GetScorecards(ctx context.Context) (*Scorecards, error) {
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, scorecardsKey, s.metrics,
func() (*Scorecards, error) {
return s.repo.GetScorecards(ctx)
})
}
func (s *Service) GetTopAssets(ctx context.Context, timeSpan *TopStatisticsTimeSpan) ([]AssetDTO, error) {
key := topAssetsByVolumeKey
if timeSpan != nil {
key = fmt.Sprintf("%s:%s", key, *timeSpan)
}
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, key, s.metrics,
func() ([]AssetDTO, error) {
return s.repo.GetTopAssets(ctx, timeSpan)
})
}
func (s *Service) GetTopChainPairs(ctx context.Context, timeSpan *TopStatisticsTimeSpan) ([]ChainPairDTO, error) {
key := topChainPairsByNumTransfersKey
if timeSpan != nil {
key = fmt.Sprintf("%s:%s", key, *timeSpan)
}
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, key, s.metrics,
func() ([]ChainPairDTO, error) {
return s.repo.GetTopChainPairs(ctx, timeSpan)
})
}
// GetChainActivity get chain activity.
func (s *Service) GetChainActivity(ctx context.Context, q *ChainActivityQuery) ([]ChainActivityResult, error) {
key := fmt.Sprintf("%s:%s:%v:%s", chainActivityKey, q.TimeSpan, q.IsNotional, strings.Join(q.GetAppIDs(), ","))
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, key, s.metrics,
func() ([]ChainActivityResult, error) {
return s.repo.FindChainActivity(ctx, q)
})
}
// FindGlobalTransactionByID find a global transaction by id.
func (s *Service) FindGlobalTransactionByID(ctx context.Context, chainID vaa.ChainID, emitter *types.Address, seq string) (*GlobalTransactionDoc, error) {
2023-03-16 11:42:34 -07:00
key := fmt.Sprintf("%d/%s/%s", chainID, emitter.Hex(), seq)
q := GlobalTransactionQuery{id: key}
return s.repo.FindGlobalTransactionByID(ctx, &q)
}
// GetTokenByChainAndAddress get token by chain and address.
func (s *Service) GetTokenByChainAndAddress(ctx context.Context, chainID vaa.ChainID, tokenAddress *types.Address) (*Token, error) {
// check if chainID is valid
if _, ok := s.supportedChainIDs[chainID]; !ok {
return nil, errs.ErrNotFound
}
//get token by contractID (chainID + tokenAddress)
tokenMetadata, ok := s.tokenProvider.GetTokenByAddress(chainID, tokenAddress.Hex())
if !ok {
return nil, errs.ErrNotFound
}
return &Token{
Symbol: tokenMetadata.Symbol,
CoingeckoID: tokenMetadata.CoingeckoID,
Decimals: tokenMetadata.Decimals,
}, nil
}
Add endpoint `GET /api/v1/transactions` (#388) ### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/385 This pull request implements a new endpoint, `GET /api/v1/transactions`, which will be consumed by the wormhole explorer UI. The endpoint returns a paginated list of transactions, in which each element contains a brief overview of the transaction (ID, txHash, status, etc.). It exposes offset-based pagination via the parameters `page` and `pageSize`. Also, results can be obtained for a specific address by using the `address` query parameter. The response model looks like this: ```json { "transactions": [ { "id": "1/5ec18c34b47c63d17ab43b07b9b2319ea5ee2d163bce2e467000174e238c8e7f/12965", "timestamp": "2023-06-08T19:30:19Z", "txHash": "a302c4ab2d6b9a6003951d2e91f8fdbb83cfa20f6ffb588b95ef0290aab37066", "originChain": 1, "status": "ongoing" }, { "id": "22/0000000000000000000000000000000000000000000000000000000000000001/18308", "timestamp": "2023-06-08T19:17:14Z", "txHash": "00000000000000000000000000000000000000000000000000000000000047e7", "originChain": 22, "destinationAddress": "0x00000000000000000000000067e8a40816a983fbe3294aaebd0cc2391815b86b", "destinationChain": 5, "tokenAmount": "0.12", "usdAmount": "0.12012", "symbol": "USDC", "status": "completed" }, ... ] } ``` ### Limitations of the current implementation 1. Doesn't return the total number of results (this may result in a performance issue when we filter by address) 2. Can only filter by receiver address (we don't have sender information in the database yet)
2023-06-12 07:43:48 -07:00
func (s *Service) ListTransactions(
ctx context.Context,
pagination *pagination.Pagination,
) ([]TransactionDto, error) {
Add endpoint `GET /api/v1/transactions` (#388) ### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/385 This pull request implements a new endpoint, `GET /api/v1/transactions`, which will be consumed by the wormhole explorer UI. The endpoint returns a paginated list of transactions, in which each element contains a brief overview of the transaction (ID, txHash, status, etc.). It exposes offset-based pagination via the parameters `page` and `pageSize`. Also, results can be obtained for a specific address by using the `address` query parameter. The response model looks like this: ```json { "transactions": [ { "id": "1/5ec18c34b47c63d17ab43b07b9b2319ea5ee2d163bce2e467000174e238c8e7f/12965", "timestamp": "2023-06-08T19:30:19Z", "txHash": "a302c4ab2d6b9a6003951d2e91f8fdbb83cfa20f6ffb588b95ef0290aab37066", "originChain": 1, "status": "ongoing" }, { "id": "22/0000000000000000000000000000000000000000000000000000000000000001/18308", "timestamp": "2023-06-08T19:17:14Z", "txHash": "00000000000000000000000000000000000000000000000000000000000047e7", "originChain": 22, "destinationAddress": "0x00000000000000000000000067e8a40816a983fbe3294aaebd0cc2391815b86b", "destinationChain": 5, "tokenAmount": "0.12", "usdAmount": "0.12012", "symbol": "USDC", "status": "completed" }, ... ] } ``` ### Limitations of the current implementation 1. Doesn't return the total number of results (this may result in a performance issue when we filter by address) 2. Can only filter by receiver address (we don't have sender information in the database yet)
2023-06-12 07:43:48 -07:00
input := FindTransactionsInput{
sort: true,
pagination: pagination,
}
return s.repo.FindTransactions(ctx, &input)
Add endpoint `GET /api/v1/transactions` (#388) ### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/385 This pull request implements a new endpoint, `GET /api/v1/transactions`, which will be consumed by the wormhole explorer UI. The endpoint returns a paginated list of transactions, in which each element contains a brief overview of the transaction (ID, txHash, status, etc.). It exposes offset-based pagination via the parameters `page` and `pageSize`. Also, results can be obtained for a specific address by using the `address` query parameter. The response model looks like this: ```json { "transactions": [ { "id": "1/5ec18c34b47c63d17ab43b07b9b2319ea5ee2d163bce2e467000174e238c8e7f/12965", "timestamp": "2023-06-08T19:30:19Z", "txHash": "a302c4ab2d6b9a6003951d2e91f8fdbb83cfa20f6ffb588b95ef0290aab37066", "originChain": 1, "status": "ongoing" }, { "id": "22/0000000000000000000000000000000000000000000000000000000000000001/18308", "timestamp": "2023-06-08T19:17:14Z", "txHash": "00000000000000000000000000000000000000000000000000000000000047e7", "originChain": 22, "destinationAddress": "0x00000000000000000000000067e8a40816a983fbe3294aaebd0cc2391815b86b", "destinationChain": 5, "tokenAmount": "0.12", "usdAmount": "0.12012", "symbol": "USDC", "status": "completed" }, ... ] } ``` ### Limitations of the current implementation 1. Doesn't return the total number of results (this may result in a performance issue when we filter by address) 2. Can only filter by receiver address (we don't have sender information in the database yet)
2023-06-12 07:43:48 -07:00
}
func (s *Service) ListTransactionsByAddress(
ctx context.Context,
address string,
Add endpoint `GET /api/v1/transactions` (#388) ### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/385 This pull request implements a new endpoint, `GET /api/v1/transactions`, which will be consumed by the wormhole explorer UI. The endpoint returns a paginated list of transactions, in which each element contains a brief overview of the transaction (ID, txHash, status, etc.). It exposes offset-based pagination via the parameters `page` and `pageSize`. Also, results can be obtained for a specific address by using the `address` query parameter. The response model looks like this: ```json { "transactions": [ { "id": "1/5ec18c34b47c63d17ab43b07b9b2319ea5ee2d163bce2e467000174e238c8e7f/12965", "timestamp": "2023-06-08T19:30:19Z", "txHash": "a302c4ab2d6b9a6003951d2e91f8fdbb83cfa20f6ffb588b95ef0290aab37066", "originChain": 1, "status": "ongoing" }, { "id": "22/0000000000000000000000000000000000000000000000000000000000000001/18308", "timestamp": "2023-06-08T19:17:14Z", "txHash": "00000000000000000000000000000000000000000000000000000000000047e7", "originChain": 22, "destinationAddress": "0x00000000000000000000000067e8a40816a983fbe3294aaebd0cc2391815b86b", "destinationChain": 5, "tokenAmount": "0.12", "usdAmount": "0.12012", "symbol": "USDC", "status": "completed" }, ... ] } ``` ### Limitations of the current implementation 1. Doesn't return the total number of results (this may result in a performance issue when we filter by address) 2. Can only filter by receiver address (we don't have sender information in the database yet)
2023-06-12 07:43:48 -07:00
pagination *pagination.Pagination,
) ([]TransactionDto, error) {
Add endpoint `GET /api/v1/transactions` (#388) ### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/385 This pull request implements a new endpoint, `GET /api/v1/transactions`, which will be consumed by the wormhole explorer UI. The endpoint returns a paginated list of transactions, in which each element contains a brief overview of the transaction (ID, txHash, status, etc.). It exposes offset-based pagination via the parameters `page` and `pageSize`. Also, results can be obtained for a specific address by using the `address` query parameter. The response model looks like this: ```json { "transactions": [ { "id": "1/5ec18c34b47c63d17ab43b07b9b2319ea5ee2d163bce2e467000174e238c8e7f/12965", "timestamp": "2023-06-08T19:30:19Z", "txHash": "a302c4ab2d6b9a6003951d2e91f8fdbb83cfa20f6ffb588b95ef0290aab37066", "originChain": 1, "status": "ongoing" }, { "id": "22/0000000000000000000000000000000000000000000000000000000000000001/18308", "timestamp": "2023-06-08T19:17:14Z", "txHash": "00000000000000000000000000000000000000000000000000000000000047e7", "originChain": 22, "destinationAddress": "0x00000000000000000000000067e8a40816a983fbe3294aaebd0cc2391815b86b", "destinationChain": 5, "tokenAmount": "0.12", "usdAmount": "0.12012", "symbol": "USDC", "status": "completed" }, ... ] } ``` ### Limitations of the current implementation 1. Doesn't return the total number of results (this may result in a performance issue when we filter by address) 2. Can only filter by receiver address (we don't have sender information in the database yet)
2023-06-12 07:43:48 -07:00
return s.repo.ListTransactionsByAddress(ctx, address, pagination)
}
func (s *Service) GetTransactionByID(
ctx context.Context,
chain vaa.ChainID,
emitter *types.Address,
seq string,
) (*TransactionDto, error) {
// Execute the database query
input := FindTransactionsInput{
id: fmt.Sprintf("%d/%s/%s", chain, emitter.Hex(), seq),
}
output, err := s.repo.FindTransactions(ctx, &input)
if err != nil {
return nil, err
}
if len(output) == 0 {
return nil, errs.ErrNotFound
}
// Return matching document
return &output[0], nil
}
func (s *Service) GetTokenProvider() *domain.TokenProvider {
return s.tokenProvider
}
func (s *Service) GetChainActivityTops(ctx *fasthttp.RequestCtx, q ChainActivityTopsQuery) (ChainActivityTopResults, error) {
timeDuration := q.To.Sub(q.From)
if q.Timespan == Hour && timeDuration > 15*24*time.Hour {
return nil, errors.New("time range is too large for hourly data. Max time range allowed: 15 days")
}
if q.Timespan == Day {
if timeDuration < 24*time.Hour {
return nil, errors.New("time range is too small for daily data. Min time range allowed: 2 day")
}
if timeDuration > 365*24*time.Hour {
return nil, errors.New("time range is too large for daily data. Max time range allowed: 1 year")
}
}
if q.Timespan == Month {
if timeDuration < 30*24*time.Hour {
return nil, errors.New("time range is too small for monthly data. Min time range allowed: 60 days")
}
if timeDuration > 10*365*24*time.Hour {
return nil, errors.New("time range is too large for monthly data. Max time range allowed: 1 year")
}
}
if q.Timespan == Year {
if timeDuration < 365*24*time.Hour {
return nil, errors.New("time range is too small for yearly data. Min time range allowed: 1 year")
}
if timeDuration > 10*365*24*time.Hour {
return nil, errors.New("time range is too large for yearly data. Max time range allowed: 10 year")
}
}
return s.repo.FindChainActivityTops(ctx, q)
}