wormhole-explorer/analytics/cmd/token/token.go

164 lines
4.1 KiB
Go

package token
import (
"context"
"errors"
"fmt"
"math/big"
"github.com/wormhole-foundation/wormhole-explorer/common/client/parser"
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
type UnknownTokenErr struct {
detail string
}
func (e *UnknownTokenErr) Error() string {
return fmt.Sprintf("unknown token. %s", e.detail)
}
func IsUnknownTokenErr(err error) bool {
switch err.(type) {
case *UnknownTokenErr:
return true
default:
return false
}
}
type TransferredToken struct {
AppId string
FromChain sdk.ChainID
ToChain sdk.ChainID
TokenAddress sdk.Address
TokenChain sdk.ChainID
Amount *big.Int
}
func (t *TransferredToken) Clone() *TransferredToken {
if t == nil {
return nil
}
var amount *big.Int
if t.Amount != nil {
amount = new(big.Int).Set(t.Amount)
}
return &TransferredToken{
AppId: t.AppId,
FromChain: t.FromChain,
ToChain: t.ToChain,
TokenAddress: t.TokenAddress,
TokenChain: t.TokenChain,
Amount: amount,
}
}
type GetTransferredTokenByVaa func(context.Context, *sdk.VAA) (*TransferredToken, error)
type TokenResolver struct {
client parser.ParserVAAAPIClient
logger *zap.Logger
}
func NewTokenResolver(client parser.ParserVAAAPIClient, logger *zap.Logger) *TokenResolver {
return &TokenResolver{
client: client,
logger: logger,
}
}
func (r *TokenResolver) GetTransferredTokenByVaa(ctx context.Context, vaa *sdk.VAA) (*TransferredToken, error) {
// Ignore PythNet VAAs
if vaa.EmitterChain == sdk.ChainIDPythNet {
return nil, nil
}
// Parse the VAA with standarized properties
result, err := r.client.ParseVaaWithStandarizedProperties(vaa)
if err != nil {
r.logger.Error("Parsing vaa with standarized properties",
zap.String("vaaId", vaa.MessageID()),
zap.Error(err))
return nil, err
}
if result == nil {
r.logger.Error("VAA with standarized properties is empty",
zap.String("vaaId", vaa.MessageID()),
zap.Error(err))
return nil, nil
}
token, err := createToken(result, vaa.EmitterChain)
if err != nil {
r.logger.Debug("Creating transferred token",
zap.String("vaaId", vaa.MessageID()),
zap.Error(err))
return nil, &UnknownTokenErr{detail: err.Error()}
}
return token, err
}
func createToken(p *parser.ParseVaaWithStandarizedPropertiesdResponse, emitterChain sdk.ChainID) (*TransferredToken, error) {
if !domain.ChainIdIsValid(p.StandardizedProperties.TokenChain) {
return nil, fmt.Errorf("tokenChain is invalid: %d", p.StandardizedProperties.TokenChain)
}
if !domain.ChainIdIsValid(p.StandardizedProperties.ToChain) {
return nil, fmt.Errorf("toChain is invalid: %d", p.StandardizedProperties.ToChain)
}
if !domain.ChainIdIsValid(emitterChain) {
return nil, fmt.Errorf("emitterChain is invalid: %d", emitterChain)
}
if p.StandardizedProperties.TokenAddress == "" {
return nil, errors.New("tokenAddress is empty")
}
if p.StandardizedProperties.Amount == "" {
return nil, errors.New("amount is empty")
}
addressHex, err := domain.DecodeNativeAddressToHex(p.StandardizedProperties.TokenChain, p.StandardizedProperties.TokenAddress)
if err != nil {
if p.ParsedPayload != nil && p.ParsedPayload.TokenAddress != "" {
addressHex = p.ParsedPayload.TokenAddress
} else {
return nil, fmt.Errorf("cannot decode token with tokenChain [%d] tokenAddress [%s] to hex",
p.StandardizedProperties.TokenChain, p.StandardizedProperties.TokenAddress)
}
}
address, err := sdk.StringToAddress(addressHex)
if err != nil {
return nil, err
}
n := new(big.Int)
n, ok := n.SetString(p.StandardizedProperties.Amount, 10)
if !ok {
return nil, fmt.Errorf("amount [%s] is not a number", p.StandardizedProperties.Amount)
}
appId := domain.AppIdUnkonwn
if len(p.StandardizedProperties.AppIds) > 0 {
appId = p.StandardizedProperties.AppIds[0]
}
return &TransferredToken{
AppId: appId,
FromChain: emitterChain,
ToChain: p.StandardizedProperties.ToChain,
TokenAddress: address,
TokenChain: p.StandardizedProperties.TokenChain,
Amount: n,
}, nil
}