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 }