Class: Tapyrus::TIP0020::Metadata
- Inherits:
-
Object
- Object
- Tapyrus::TIP0020::Metadata
- Defined in:
- lib/tapyrus/tip0020.rb
Overview
Token metadata class based on TIP-0020 specification
Constant Summary collapse
- CURRENT_VERSION =
"1.0"- MAX_NAME_LENGTH =
64- MAX_SYMBOL_LENGTH =
12- MAX_DESCRIPTION_LENGTH =
256- MIN_DECIMALS =
0- MAX_DECIMALS =
18- MAX_DATA_URI_SIZE =
32KB
32 * 1024
- VALID_TOKEN_TYPES =
%i[reissuable non_reissuable nft].freeze
- NFT_FIELDS =
%i[image animation_url external_url attributes].freeze
Instance Attribute Summary collapse
-
#animation_url ⇒ Object
Returns the value of attribute animation_url.
-
#attributes ⇒ Object
Returns the value of attribute attributes.
-
#decimals ⇒ Object
Returns the value of attribute decimals.
-
#description ⇒ Object
Returns the value of attribute description.
-
#external_url ⇒ Object
Returns the value of attribute external_url.
-
#icon ⇒ Object
Returns the value of attribute icon.
-
#image ⇒ Object
Returns the value of attribute image.
-
#issuer ⇒ Object
Returns the value of attribute issuer.
-
#name ⇒ Object
Returns the value of attribute name.
-
#properties ⇒ Object
Returns the value of attribute properties.
-
#symbol ⇒ Object
Returns the value of attribute symbol.
-
#terms ⇒ Object
Returns the value of attribute terms.
-
#token_type ⇒ Object
Returns the value of attribute token_type.
-
#version ⇒ Object
Returns the value of attribute version.
-
#website ⇒ Object
Returns the value of attribute website.
Class Method Summary collapse
-
.parse(json_str, token_type:) ⇒ Metadata
Parse from JSON string.
Instance Method Summary collapse
-
#canonicalize ⇒ String
Canonicalize metadata according to RFC 8785 (JCS).
-
#commitment(pubkey) ⇒ String
Calculate P2C commitment: c = SHA256(P || h).
-
#commitment_hex(pubkey) ⇒ String
Calculate P2C commitment and return as hex string.
-
#derive_color_id(pubkey: nil, out_point: nil) ⇒ Tapyrus::Color::ColorIdentifier
Create ColorIdentifier based on token type.
-
#derive_p2c_address(pubkey) ⇒ String
Derive P2C address.
-
#derive_p2c_pubkey(pubkey) ⇒ String
Derive P2C public key: P' = P + c * G.
-
#digest ⇒ String
Calculate SHA256 hash of canonicalized metadata.
-
#digest_hex ⇒ String
Calculate SHA256 hash and return as hex string.
-
#initialize(token_type:, version: CURRENT_VERSION, name:, symbol:, decimals: 0, description: nil, icon: nil, issuer: nil, website: nil, terms: nil, properties: nil, image: nil, animation_url: nil, external_url: nil, attributes: nil) ⇒ Metadata
constructor
A new instance of Metadata.
-
#to_h ⇒ Hash
Convert to Hash.
-
#validate! ⇒ Object
Validate metadata fields.
-
#validate_issuer! ⇒ Object
Validate issuer object fields.
-
#validate_nft_fields! ⇒ Object
Validate NFT-specific fields are only used with NFT token type.
Constructor Details
#initialize(token_type:, version: CURRENT_VERSION, name:, symbol:, decimals: 0, description: nil, icon: nil, issuer: nil, website: nil, terms: nil, properties: nil, image: nil, animation_url: nil, external_url: nil, attributes: nil) ⇒ Metadata
Returns a new instance of Metadata.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/tapyrus/tip0020.rb', line 50 def initialize( token_type:, version: CURRENT_VERSION, name:, symbol:, decimals: 0, description: nil, icon: nil, issuer: nil, website: nil, terms: nil, properties: nil, image: nil, animation_url: nil, external_url: nil, attributes: nil ) @token_type = token_type @version = version @name = name @symbol = symbol @decimals = decimals @description = description @icon = icon @issuer = issuer @website = website @terms = terms @properties = properties @image = image @animation_url = animation_url @external_url = external_url @attributes = attributes validate! end |
Instance Attribute Details
#animation_url ⇒ Object
Returns the value of attribute animation_url.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def animation_url @animation_url end |
#attributes ⇒ Object
Returns the value of attribute attributes.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def attributes @attributes end |
#decimals ⇒ Object
Returns the value of attribute decimals.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def decimals @decimals end |
#description ⇒ Object
Returns the value of attribute description.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def description @description end |
#external_url ⇒ Object
Returns the value of attribute external_url.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def external_url @external_url end |
#icon ⇒ Object
Returns the value of attribute icon.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def icon @icon end |
#image ⇒ Object
Returns the value of attribute image.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def image @image end |
#issuer ⇒ Object
Returns the value of attribute issuer.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def issuer @issuer end |
#name ⇒ Object
Returns the value of attribute name.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def name @name end |
#properties ⇒ Object
Returns the value of attribute properties.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def properties @properties end |
#symbol ⇒ Object
Returns the value of attribute symbol.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def symbol @symbol end |
#terms ⇒ Object
Returns the value of attribute terms.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def terms @terms end |
#token_type ⇒ Object
Returns the value of attribute token_type.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def token_type @token_type end |
#version ⇒ Object
Returns the value of attribute version.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def version @version end |
#website ⇒ Object
Returns the value of attribute website.
19 20 21 |
# File 'lib/tapyrus/tip0020.rb', line 19 def website @website end |
Class Method Details
.parse(json_str, token_type:) ⇒ Metadata
Parse from JSON string
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/tapyrus/tip0020.rb', line 243 def self.parse(json_str, token_type:) data = JSON.parse(json_str, symbolize_names: true) new( token_type: token_type, version: data[:version] || CURRENT_VERSION, name: data[:name], symbol: data[:symbol], decimals: data[:decimals] || 0, description: data[:description], icon: data[:icon], issuer: data[:issuer], website: data[:website], terms: data[:terms], properties: data[:properties], image: data[:image], animation_url: data[:animation_url], external_url: data[:external_url], attributes: data[:attributes] ) end |
Instance Method Details
#canonicalize ⇒ String
Canonicalize metadata according to RFC 8785 (JCS)
159 160 161 |
# File 'lib/tapyrus/tip0020.rb', line 159 def canonicalize jcs_serialize(to_h) end |
#commitment(pubkey) ⇒ String
Calculate P2C commitment: c = SHA256(P || h)
178 179 180 181 182 |
# File 'lib/tapyrus/tip0020.rb', line 178 def commitment(pubkey) pubkey_bin = pubkey.htb raise ArgumentError, "pubkey must be 33 bytes compressed public key" unless pubkey_bin.bytesize == 33 Tapyrus.sha256(pubkey_bin + digest) end |
#commitment_hex(pubkey) ⇒ String
Calculate P2C commitment and return as hex string
187 188 189 |
# File 'lib/tapyrus/tip0020.rb', line 187 def commitment_hex(pubkey) commitment(pubkey).bth end |
#derive_color_id(pubkey: nil, out_point: nil) ⇒ Tapyrus::Color::ColorIdentifier
Create ColorIdentifier based on token type
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/tapyrus/tip0020.rb', line 223 def derive_color_id(pubkey: nil, out_point: nil) case token_type when :reissuable raise ArgumentError, "pubkey is required for reissuable token" unless pubkey p2c_pubkey = derive_p2c_pubkey(pubkey) script = Tapyrus::Script.to_p2pkh(Tapyrus::Key.new(pubkey: p2c_pubkey).hash160) Tapyrus::Color::ColorIdentifier.reissuable(script) when :non_reissuable raise ArgumentError, "out_point is required for non_reissuable token" unless out_point Tapyrus::Color::ColorIdentifier.non_reissuable(out_point) when :nft raise ArgumentError, "out_point is required for nft token" unless out_point Tapyrus::Color::ColorIdentifier.nft(out_point) end end |
#derive_p2c_address(pubkey) ⇒ String
Derive P2C address
214 215 216 217 |
# File 'lib/tapyrus/tip0020.rb', line 214 def derive_p2c_address(pubkey) p2c_pubkey = derive_p2c_pubkey(pubkey) Tapyrus::Key.new(pubkey: p2c_pubkey).to_p2pkh end |
#derive_p2c_pubkey(pubkey) ⇒ String
Derive P2C public key: P' = P + c * G
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/tapyrus/tip0020.rb', line 195 def derive_p2c_pubkey(pubkey) c = commitment(pubkey) c_int = c.bth.to_i(16) # P + c * G group = ECDSA::Group::Secp256k1 point_p = Tapyrus::Key.new(pubkey: pubkey).to_point point_cg = group.generator * c_int point_p_prime = point_p + point_cg raise ArgumentError, "P2C derivation resulted in point at infinity" if point_p_prime.infinity? # Compress the result ECDSA::Format::PointOctetString.encode(point_p_prime, compression: true).bth end |
#digest ⇒ String
Calculate SHA256 hash of canonicalized metadata
165 166 167 |
# File 'lib/tapyrus/tip0020.rb', line 165 def digest Tapyrus.sha256(canonicalize) end |
#digest_hex ⇒ String
Calculate SHA256 hash and return as hex string
171 172 173 |
# File 'lib/tapyrus/tip0020.rb', line 171 def digest_hex digest.bth end |
#to_h ⇒ Hash
Convert to Hash
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/tapyrus/tip0020.rb', line 140 def to_h result = { version: version, name: name, symbol: symbol } result[:decimals] = decimals if decimals != 0 result[:description] = description if description result[:icon] = icon if icon result[:issuer] = issuer if issuer result[:website] = website if website result[:terms] = terms if terms result[:properties] = properties if properties # NFT fields result[:image] = image if image result[:animation_url] = animation_url if animation_url result[:external_url] = external_url if external_url result[:attributes] = attributes if attributes result end |
#validate! ⇒ Object
Validate metadata fields
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/tapyrus/tip0020.rb', line 87 def validate! raise ArgumentError, "token_type is required" if token_type.nil? unless VALID_TOKEN_TYPES.include?(token_type) raise ArgumentError, "token_type must be one of #{VALID_TOKEN_TYPES.join(", ")}" end validate_nft_fields! raise ArgumentError, "version is required" if version.nil? || version.empty? raise ArgumentError, "version must be #{CURRENT_VERSION}" unless version == CURRENT_VERSION raise ArgumentError, "name is required" if name.nil? || name.empty? raise ArgumentError, "name must be #{MAX_NAME_LENGTH} characters or less" if name.length > MAX_NAME_LENGTH raise ArgumentError, "symbol is required" if symbol.nil? || symbol.empty? if symbol.length > MAX_SYMBOL_LENGTH raise ArgumentError, "symbol must be #{MAX_SYMBOL_LENGTH} characters or less" end if decimals < MIN_DECIMALS || decimals > MAX_DECIMALS raise ArgumentError, "decimals must be between #{MIN_DECIMALS} and #{MAX_DECIMALS}" end if description && description.length > MAX_DESCRIPTION_LENGTH raise ArgumentError, "description must be #{MAX_DESCRIPTION_LENGTH} characters or less" end raise ArgumentError, "icon must be an HTTPS URL or Data URI" if icon && !valid_icon_format?(icon) raise ArgumentError, "website must be an HTTPS URL" if website && !valid_https_url?(website) raise ArgumentError, "terms must be an HTTPS URL" if terms && !valid_https_url?(terms) raise ArgumentError, "image must be an HTTPS URL or Data URI" if image && !valid_media_url?(image) if animation_url && !valid_media_url?(animation_url) raise ArgumentError, "animation_url must be an HTTPS URL or Data URI" end raise ArgumentError, "external_url must be an HTTPS URL" if external_url && !valid_https_url?(external_url) validate_issuer! if issuer end |
#validate_issuer! ⇒ Object
Validate issuer object fields
120 121 122 123 124 125 126 |
# File 'lib/tapyrus/tip0020.rb', line 120 def validate_issuer! return unless issuer.is_a?(Hash) issuer_url = issuer[:url] || issuer["url"] raise ArgumentError, "issuer.url must be an HTTPS URL" if issuer_url && !valid_https_url?(issuer_url) issuer_email = issuer[:email] || issuer["email"] raise ArgumentError, "issuer.email must be a valid email address" if issuer_email && !valid_email?(issuer_email) end |
#validate_nft_fields! ⇒ Object
Validate NFT-specific fields are only used with NFT token type
130 131 132 133 134 135 136 |
# File 'lib/tapyrus/tip0020.rb', line 130 def validate_nft_fields! return if token_type == :nft nft_fields_present = NFT_FIELDS.select { |field| send(field) } unless nft_fields_present.empty? raise ArgumentError, "#{nft_fields_present.join(", ")} can only be used with NFT token type" end end |