github.com/mavryk-network/mvgo@v1.19.9/mavryk/token.go (about)

     1  // Copyright (c) 2020-2022 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  package mavryk
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  
    10  	"github.com/mavryk-network/mvgo/base58"
    11  )
    12  
    13  var (
    14  	InvalidToken = Token{}
    15  
    16  	// ZeroToken is a placeholder token with zero hash and zero id that may be
    17  	// used by applications to represent tez or another default option where
    18  	// a token address is expected.
    19  	ZeroToken = NewToken(ZeroContract, Zero)
    20  )
    21  
    22  // Token represents a specialized Tezos token address that consists of
    23  // a smart contract KT1 address and a token id represented as big integer number.
    24  type Token struct {
    25  	Hash [20]byte // type is always KT1
    26  	Id   Z
    27  }
    28  
    29  func NewToken(contract Address, id Z) (t Token) {
    30  	copy(t.Hash[:], contract[1:])
    31  	t.Id = id.Clone()
    32  	return
    33  }
    34  
    35  func (t Token) Contract() Address {
    36  	return NewAddress(AddressTypeContract, t.Hash[:])
    37  }
    38  
    39  func (t Token) TokenId() Z {
    40  	return t.Id
    41  }
    42  
    43  func (t Token) Equal(b Token) bool {
    44  	return t.Hash == b.Hash && t.Id.Equal(b.Id)
    45  }
    46  
    47  func (t Token) Clone() Token {
    48  	return Token{
    49  		Hash: t.Hash,
    50  		Id:   t.Id.Clone(),
    51  	}
    52  }
    53  
    54  func (t Token) String() string {
    55  	addr := base58.CheckEncode(t.Hash[:], NOCURVE_PUBLIC_KEY_HASH_ID)
    56  	return addr + "_" + t.Id.String()
    57  }
    58  
    59  func (t *Token) UnmarshalText(data []byte) error {
    60  	idx := bytes.IndexByte(data, '_')
    61  	max := HashTypePkhNocurve.B58Len
    62  	if idx > max {
    63  		idx = max
    64  	} else if idx < 0 {
    65  		idx = len(data)
    66  	}
    67  	dec, ver, err := base58.CheckDecode(string(data[:idx]), 3, nil)
    68  	if err != nil {
    69  		if err == base58.ErrChecksum {
    70  			return ErrChecksumMismatch
    71  		}
    72  		return fmt.Errorf("tezos: invalid token address: %w", err)
    73  	}
    74  	hashLen := HashTypePkhNocurve.Len
    75  	if len(dec) != hashLen {
    76  		return fmt.Errorf("tezos: invalid token address length %d", len(dec))
    77  	}
    78  	if !bytes.Equal(ver, NOCURVE_PUBLIC_KEY_HASH_ID) {
    79  		return fmt.Errorf("tezos: invalid token address type %x", ver)
    80  	}
    81  	copy(t.Hash[:], dec)
    82  	if idx < len(data) {
    83  		// token id is optional
    84  		if err := t.Id.UnmarshalText(data[idx+1:]); err != nil {
    85  			t.Id.SetInt64(0)
    86  			return fmt.Errorf("tezos: invalid token id: %w", err)
    87  		}
    88  	} else {
    89  		t.Id.SetInt64(0)
    90  	}
    91  	return nil
    92  }
    93  
    94  func (t Token) MarshalText() ([]byte, error) {
    95  	return []byte(t.String()), nil
    96  }
    97  
    98  // Bytes returns the 20 byte (contract) hash appended with a zarith encoded token id.
    99  func (t Token) Bytes() []byte {
   100  	buf := bytes.NewBuffer(nil)
   101  	buf.Write(t.Hash[:])
   102  	t.Id.EncodeBuffer(buf)
   103  	return buf.Bytes()
   104  }
   105  
   106  func (t Token) MarshalBinary() ([]byte, error) {
   107  	return t.Bytes(), nil
   108  }
   109  
   110  func (t *Token) UnmarshalBinary(data []byte) error {
   111  	l, exp := len(data), HashTypePkhNocurve.Len
   112  	if l < exp {
   113  		return fmt.Errorf("tezos: short binary token address len %d", l)
   114  	}
   115  	copy(t.Hash[:], data)
   116  	if l > exp {
   117  		if err := t.Id.UnmarshalBinary(data[exp:]); err != nil {
   118  			return fmt.Errorf("tezos: invalid binary token id: %w", err)
   119  		}
   120  	}
   121  	return nil
   122  }
   123  
   124  func MustParseToken(addr string) Token {
   125  	t, err := ParseToken(addr)
   126  	if err != nil {
   127  		panic(err)
   128  	}
   129  	return t
   130  }
   131  
   132  func ParseToken(addr string) (Token, error) {
   133  	t := Token{}
   134  	if len(addr) == 0 {
   135  		return InvalidToken, nil
   136  	}
   137  	err := t.UnmarshalText([]byte(addr))
   138  	return t, err
   139  }
   140  
   141  // Set implements the flags.Value interface for use in command line argument parsing.
   142  func (t *Token) Set(key string) (err error) {
   143  	*t, err = ParseToken(key)
   144  	return
   145  }