246 lines
7.0 KiB
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
|
|
}
|