2023-03-07 11:25:42 -08:00
|
|
|
package transactions
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-03-15 12:52:50 -07:00
|
|
|
"fmt"
|
2023-06-20 06:34:20 -07:00
|
|
|
"strings"
|
|
|
|
"time"
|
2023-03-07 11:25:42 -08:00
|
|
|
|
2023-06-20 06:34:20 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/api/cacheable"
|
2023-07-12 08:51:52 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
2023-05-31 06:29:16 -07:00
|
|
|
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
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"
|
2023-03-15 12:52:50 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/api/types"
|
2023-06-20 06:34:20 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache"
|
2023-05-31 06:29:16 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
2023-03-15 12:52:50 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
2023-03-07 11:25:42 -08:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Service struct {
|
2023-05-31 06:29:16 -07:00
|
|
|
repo *Repository
|
2023-06-20 06:34:20 -07:00
|
|
|
cache cache.Cache
|
2023-06-21 13:00:29 -07:00
|
|
|
expiration time.Duration
|
2023-05-31 06:29:16 -07:00
|
|
|
supportedChainIDs map[vaa.ChainID]string
|
|
|
|
logger *zap.Logger
|
2023-03-07 11:25:42 -08:00
|
|
|
}
|
|
|
|
|
2023-06-21 13:00:29 -07:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
2023-03-07 11:25:42 -08:00
|
|
|
// NewService create a new Service.
|
2023-06-21 13:00:29 -07:00
|
|
|
func NewService(repo *Repository, cache cache.Cache, expiration time.Duration, logger *zap.Logger) *Service {
|
2023-05-31 06:29:16 -07:00
|
|
|
supportedChainIDs := domain.GetSupportedChainIDs()
|
2023-06-21 13:00:29 -07:00
|
|
|
return &Service{repo: repo, supportedChainIDs: supportedChainIDs,
|
|
|
|
cache: cache, expiration: expiration, logger: logger.With(zap.String("module", "TransactionService"))}
|
2023-03-07 11:25:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetTransactionCount get the last transactions.
|
|
|
|
func (s *Service) GetTransactionCount(ctx context.Context, q *TransactionCountQuery) ([]TransactionCountResult, error) {
|
2023-06-21 13:00:29 -07:00
|
|
|
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,
|
|
|
|
func() ([]TransactionCountResult, error) {
|
|
|
|
return s.repo.GetTransactionCount(ctx, q)
|
|
|
|
})
|
2023-03-07 11:25:42 -08:00
|
|
|
}
|
|
|
|
|
2023-04-20 12:01:10 -07:00
|
|
|
func (s *Service) GetScorecards(ctx context.Context) (*Scorecards, error) {
|
2023-06-21 13:00:29 -07:00
|
|
|
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, scorecardsKey,
|
|
|
|
func() (*Scorecards, error) {
|
|
|
|
return s.repo.GetScorecards(ctx)
|
|
|
|
})
|
2023-04-20 12:01:10 -07:00
|
|
|
}
|
|
|
|
|
2023-05-12 09:05:18 -07:00
|
|
|
func (s *Service) GetTopAssets(ctx context.Context, timeSpan *TopStatisticsTimeSpan) ([]AssetDTO, error) {
|
2023-06-21 13:00:29 -07:00
|
|
|
key := topAssetsByVolumeKey
|
|
|
|
if timeSpan != nil {
|
|
|
|
key = fmt.Sprintf("%s:%s", key, *timeSpan)
|
|
|
|
}
|
|
|
|
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, key,
|
|
|
|
func() ([]AssetDTO, error) {
|
|
|
|
return s.repo.GetTopAssets(ctx, timeSpan)
|
|
|
|
})
|
2023-05-12 09:05:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) GetTopChainPairs(ctx context.Context, timeSpan *TopStatisticsTimeSpan) ([]ChainPairDTO, error) {
|
2023-06-21 13:00:29 -07:00
|
|
|
key := topChainPairsByNumTransfersKey
|
|
|
|
if timeSpan != nil {
|
|
|
|
key = fmt.Sprintf("%s:%s", key, *timeSpan)
|
|
|
|
}
|
|
|
|
return cacheable.GetOrLoad(ctx, s.logger, s.cache, s.expiration, key,
|
|
|
|
func() ([]ChainPairDTO, error) {
|
|
|
|
return s.repo.GetTopChainPairs(ctx, timeSpan)
|
|
|
|
})
|
2023-05-10 13:39:18 -07:00
|
|
|
}
|
|
|
|
|
2023-03-07 11:25:42 -08:00
|
|
|
// GetChainActivity get chain activity.
|
|
|
|
func (s *Service) GetChainActivity(ctx context.Context, q *ChainActivityQuery) ([]ChainActivityResult, error) {
|
2023-06-21 13:00:29 -07:00
|
|
|
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,
|
2023-06-20 06:34:20 -07:00
|
|
|
func() ([]ChainActivityResult, error) {
|
|
|
|
return s.repo.FindChainActivity(ctx, q)
|
|
|
|
})
|
2023-03-07 11:25:42 -08:00
|
|
|
}
|
2023-03-15 12:52:50 -07:00
|
|
|
|
|
|
|
// 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-04-25 11:34:29 -07:00
|
|
|
|
2023-03-16 11:42:34 -07:00
|
|
|
key := fmt.Sprintf("%d/%s/%s", chainID, emitter.Hex(), seq)
|
2023-04-25 11:34:29 -07:00
|
|
|
q := GlobalTransactionQuery{id: key}
|
|
|
|
|
|
|
|
return s.repo.FindGlobalTransactionByID(ctx, &q)
|
2023-03-15 12:52:50 -07:00
|
|
|
}
|
2023-05-31 06:29:16 -07:00
|
|
|
|
|
|
|
// 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 := domain.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,
|
2023-07-12 08:51:52 -07:00
|
|
|
) ([]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
|
|
|
|
2023-07-12 08:51:52 -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 *types.Address,
|
|
|
|
pagination *pagination.Pagination,
|
2023-07-12 08:51:52 -07:00
|
|
|
) ([]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)
|
|
|
|
}
|
2023-07-12 08:51:52 -07:00
|
|
|
|
|
|
|
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, errors.ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return matching document
|
|
|
|
return &output[0], nil
|
|
|
|
}
|