wormhole-explorer/api/routes/wormscan/operations/response.go

246 lines
7.0 KiB
Go

package operations
import (
"strconv"
"strings"
"time"
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/operations"
"github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
// OperationResponse definition.
type OperationResponse struct {
ID string `json:"id"`
EmitterChain sdk.ChainID `json:"emitterChain"`
EmitterAddress EmitterAddress `json:"emitterAddress"`
Sequence string `json:"sequence"`
Vaa *Vaa `json:"vaa,omitempty"`
Content *Content `json:"content,omitempty"`
SourceChain *SourceChain `json:"sourceChain,omitempty"`
TargetChain *TargetChain `json:"targetChain,omitempty"`
Data map[string]any `json:"data,omitempty"`
}
// EmitterAddress definition.
type EmitterAddress struct {
Hex string `json:"hex,omitempty"`
Native string `json:"native,omitempty"`
}
type Vaa struct {
Raw []byte `json:"raw,omitempty"`
GuardianSetIndex uint32 `json:"guardianSetIndex"`
}
// Content definition.
type Content struct {
Payload map[string]any `json:"payload,omitempty"`
StandardizedProperties *operations.StandardizedProperties `json:"standarizedProperties,omitempty"`
}
// SourceChain definition.
type SourceChain struct {
ChainId sdk.ChainID `json:"chainId"`
Timestamp *time.Time `json:"timestamp"`
Transaction Transaction `json:"transaction"`
From string `json:"from"`
Status string `json:"status"`
Data *Data `json:"attribute,omitempty"`
}
// TxHash definition.
type Transaction struct {
TxHash string `json:"txHash"`
SecondTxHash *string `json:"secondTxHash,omitempty"`
}
// TargetChain definition.
type TargetChain struct {
ChainId sdk.ChainID `json:"chainId"`
Timestamp *time.Time `json:"timestamp"`
Transaction Transaction `json:"transaction"`
Status string `json:"status"`
From string `json:"from"`
To string `json:"to"`
}
// Data represents a custom attribute for a origin transaction.
type Data struct {
Type string `bson:"type" json:"type"`
Value map[string]any `bson:"value" json:"value"`
}
type ListOperationResponse struct {
Operations []*OperationResponse `json:"operations"`
}
// toOperationResponse converts an operations.OperationDto to an OperationResponse.
func toOperationResponse(operation *operations.OperationDto, log *zap.Logger) (*OperationResponse, error) {
// Get emitter chain, address and sequence from operation.
chainID, address, sequence, err := getChainEmitterSequence(operation)
if err != nil {
log.Error("Error parsing chainId, address, sequence from operation ID",
zap.Error(err),
zap.String("operationID", operation.ID))
return nil, err
}
// Get emitter native address from chainID and address.
emitterNativeAddress, err := domain.TranslateEmitterAddress(chainID, address)
if err != nil {
log.Warn("failed to translate emitter address",
zap.Stringer("chain", chainID),
zap.String("address", address),
zap.Error(err),
)
}
// Get vaa from operation.
var vaa *Vaa
if operation.Vaa != nil {
vaa = &Vaa{
Raw: operation.Vaa.Vaa,
GuardianSetIndex: operation.Vaa.GuardianSetIndex,
}
}
// Get content from operation.
var content Content
if len(operation.Payload) > 0 || operation.StandardizedProperties != nil {
content = Content{
Payload: operation.Payload,
StandardizedProperties: operation.StandardizedProperties,
}
}
// Get sourceChain and targetChain events
sourceChain, targetChain := getChainEvents(chainID, operation)
r := OperationResponse{
ID: operation.ID,
EmitterChain: chainID,
EmitterAddress: EmitterAddress{
Hex: address,
Native: emitterNativeAddress,
},
Sequence: sequence,
Vaa: vaa,
Content: &content,
Data: getAdditionalData(operation),
SourceChain: sourceChain,
TargetChain: targetChain,
}
return &r, nil
}
// getChainEmitterSequence returns the chainID, address, sequence for the given operation.
func getChainEmitterSequence(operation *operations.OperationDto) (sdk.ChainID, string, string, error) {
if operation.Vaa != nil {
return operation.Vaa.EmitterChain, operation.Vaa.EmitterAddr, operation.Vaa.Sequence, nil
} else {
// Get emitter chain, address, sequence by operation ID.
id := strings.Split(operation.ID, "/")
if len(id) != 3 {
return 0, "", "", errors.ErrInternalError
}
chainID, err := strconv.ParseUint(id[0], 10, 16)
if err != nil {
return 0, "", "", err
}
return sdk.ChainID(chainID), id[1], id[2], nil
}
}
func getAdditionalData(operation *operations.OperationDto) map[string]interface{} {
ok := operation.Symbol == "" && operation.TokenAmount == "" && operation.UsdAmount == ""
if ok {
return nil
}
return map[string]interface{}{
"symbol": operation.Symbol,
"tokenAmount": operation.TokenAmount,
"usdAmount": operation.UsdAmount,
}
}
// getChainEvents returns the sourceChain and targetChain events for the given operation.
func getChainEvents(chainID sdk.ChainID, operation *operations.OperationDto) (*SourceChain, *TargetChain) {
if operation.SourceTx == nil && operation.DestinationTx == nil {
return nil, nil
}
// build sourceChain
var sourceChain *SourceChain
if operation.SourceTx != nil {
var data *Data
if operation.SourceTx.Attribute != nil {
data = &Data{
Type: operation.SourceTx.Attribute.Type,
Value: operation.SourceTx.Attribute.Value,
}
}
// transactions
var secondTxHash *string
if data != nil {
attributeTxHash, ok := data.Value["originTxHash"]
if ok {
txHash, ok := attributeTxHash.(string)
if ok {
secondTxHash = &txHash
}
}
}
transaction := Transaction{
TxHash: operation.SourceTx.TxHash,
SecondTxHash: secondTxHash,
}
sourceChain = &SourceChain{
ChainId: chainID,
Timestamp: operation.SourceTx.Timestamp,
Transaction: transaction,
From: operation.SourceTx.From,
Status: operation.SourceTx.Status,
Data: data,
}
}
// build targetChain
var targetChain *TargetChain
if operation.DestinationTx != nil {
targetChain = &TargetChain{
ChainId: operation.DestinationTx.ChainID,
Timestamp: operation.DestinationTx.Timestamp,
Transaction: Transaction{
TxHash: operation.DestinationTx.TxHash,
},
Status: operation.DestinationTx.Status,
From: operation.DestinationTx.From,
To: operation.DestinationTx.To,
}
}
return sourceChain, targetChain
}
func toListOperationResponse(operations []*operations.OperationDto, log *zap.Logger) ListOperationResponse {
response := ListOperationResponse{
Operations: make([]*OperationResponse, 0, len(operations)),
}
for i := range operations {
r, err := toOperationResponse(operations[i], log)
if err == nil {
response.Operations = append(response.Operations, r)
}
}
return response
}