Class: MixinBot::Utils::Nfo

Inherits:
Object
  • Object
show all
Defined in:
lib/mixin_bot/utils/nfo.rb

Constant Summary collapse

NFT_MEMO_PREFIX =
'NFO'
NFT_MEMO_VERSION =
0x00
NFT_MEMO_DEFAULT_CHAIN =
'43d61dcd-e413-450d-80b8-101d5e903357'
NFT_MEMO_DEFAULT_CLASS =
'3c8c161a18ae2c8b14fda1216fff7da88c419b5d'
NULL_UUID =
'00000000-0000-0000-0000-000000000000'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**kwargs) ⇒ Nfo

Returns a new instance of Nfo.



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/mixin_bot/utils/nfo.rb', line 15

def initialize(**kwargs)
  @prefix = NFT_MEMO_PREFIX
  @version = NFT_MEMO_VERSION
  @mask = kwargs[:mask] || 0
  @chain = kwargs[:chain] || NFT_MEMO_DEFAULT_CHAIN
  @nm_class = kwargs[:nm_class] || NFT_MEMO_DEFAULT_CLASS
  @collection = kwargs[:collection] || NULL_UUID
  @token = kwargs[:token].presence&.to_i
  @extra = kwargs[:extra]
  @memo = kwargs[:memo]
  @hex = kwargs[:hex]
end

Instance Attribute Details

#chainObject

Returns the value of attribute chain.



13
14
15
# File 'lib/mixin_bot/utils/nfo.rb', line 13

def chain
  @chain
end

#collectionObject

Returns the value of attribute collection.



13
14
15
# File 'lib/mixin_bot/utils/nfo.rb', line 13

def collection
  @collection
end

#extraObject

Returns the value of attribute extra.



13
14
15
# File 'lib/mixin_bot/utils/nfo.rb', line 13

def extra
  @extra
end

#hexObject

Returns the value of attribute hex.



13
14
15
# File 'lib/mixin_bot/utils/nfo.rb', line 13

def hex
  @hex
end

#maskObject

Returns the value of attribute mask.



13
14
15
# File 'lib/mixin_bot/utils/nfo.rb', line 13

def mask
  @mask
end

#memoObject

Returns the value of attribute memo.



13
14
15
# File 'lib/mixin_bot/utils/nfo.rb', line 13

def memo
  @memo
end

#nm_classObject

Returns the value of attribute nm_class.



13
14
15
# File 'lib/mixin_bot/utils/nfo.rb', line 13

def nm_class
  @nm_class
end

#prefixObject (readonly)

Returns the value of attribute prefix.



12
13
14
# File 'lib/mixin_bot/utils/nfo.rb', line 12

def prefix
  @prefix
end

#rawObject (readonly)

Returns the value of attribute raw.



12
13
14
# File 'lib/mixin_bot/utils/nfo.rb', line 12

def raw
  @raw
end

#tokenObject

Returns the value of attribute token.



13
14
15
# File 'lib/mixin_bot/utils/nfo.rb', line 13

def token
  @token
end

#versionObject (readonly)

Returns the value of attribute version.



12
13
14
# File 'lib/mixin_bot/utils/nfo.rb', line 12

def version
  @version
end

Instance Method Details

#decodeObject



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/mixin_bot/utils/nfo.rb', line 106

def decode
  @raw =
    if memo.present?
      Base64.urlsafe_decode64 memo
    elsif hex.present?
      [hex].pack('H*')
    else
      raise InvalidNfoFormatError, 'memo or hex is required'
    end

  @hex = raw.unpack1('H*') if hex.blank?
  @memo = Base64.urlsafe_encode64 raw, padding: false if memo.blank?

  decode_bytes
  self
end

#decode_bytesObject



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/mixin_bot/utils/nfo.rb', line 123

def decode_bytes
  bytes = raw.bytes

  _prefix = bytes.shift(3).pack('C*')
  raise MixinBot::InvalidNfoFormatError, "NFO prefix #{_prefix}" if _prefix != prefix

  _version = bytes.shift
  raise MixinBot::InvalidNfoFormatError, "NFO version #{prefix}" if _version != version

  hint = bytes.shift
  if hint == 1
    @mask = bytes.shift(8).reverse.pack('C*').unpack1('Q*')

    @chain = MixinBot::Utils::UUID.new(hex: bytes.shift(16).pack('C*').unpack1('H*')).unpacked

    class_length = bytes.shift
    @nm_class = bytes.shift(class_length).pack('C*').unpack1('H*')

    collection_length = bytes.shift
    @collection = MixinBot::Utils::UUID.new(hex: bytes.shift(collection_length).pack('C*').unpack1('H*')).unpacked

    token_length = bytes.shift
    @token = MixinBot::Utils.bytes_to_int bytes.shift(token_length)
  end

  extra_length = bytes.shift
  @extra = bytes.shift(extra_length).pack('C*').unpack1('H*')

  self
end

#encodeObject



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/mixin_bot/utils/nfo.rb', line 68

def encode
  bytes = []

  bytes += prefix.bytes
  bytes += [version]

  if mask != 0
    bytes += [1]
    bytes += MixinBot::Utils.encode_unit_64 mask
    bytes += MixinBot::Utils::UUID.new(hex: chain).packed.bytes

    class_bytes = [nm_class].pack('H*').bytes
    bytes += MixinBot::Utils.bytes_of class_bytes.size
    bytes += class_bytes

    collection_bytes = collection.split('-').pack('H* H* H* H* H*').bytes
    bytes += MixinBot::Utils.bytes_of collection_bytes.size
    bytes += collection_bytes

    # token_bytes = memo[:token].split('-').pack('H* H* H* H* H*').bytes
    token_bytes = MixinBot::Utils.bytes_of token
    bytes += MixinBot::Utils.bytes_of token_bytes.size
    bytes += token_bytes
  else
    bytes += [0]
  end

  extra_bytes = [extra].pack('H*').bytes
  bytes += MixinBot::Utils.bytes_of extra_bytes.size
  bytes += extra_bytes

  @raw = bytes.pack('C*')
  @hex = raw.unpack1('H*')
  @memo = Base64.urlsafe_encode64 raw, padding: false

  self
end

#mark(*indexes) ⇒ Object



60
61
62
63
64
65
66
# File 'lib/mixin_bot/utils/nfo.rb', line 60

def mark(*indexes)
  indexes.map do |index|
    raise ArgumentError, "invalid NFO memo index #{index}" if index >= 64 || index.negative?

    @mask = mask ^ (1 << index)
  end
end

#mint_memoObject



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/mixin_bot/utils/nfo.rb', line 28

def mint_memo
  raise MixinBot::InvalidNfoFormatError, 'token is required' if token.blank?
  raise MixinBot::InvalidNfoFormatError, 'extra must be 256-bit string' if extra.blank? || extra.size != 64

  @collection = NULL_UUID if collection.blank?
  @chain = NFT_MEMO_DEFAULT_CHAIN
  @nm_class = NFT_MEMO_DEFAULT_CLASS
  mark 0
  encode

  memo
end

#to_hObject



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/mixin_bot/utils/nfo.rb', line 154

def to_h
  hash = {
    prefix: prefix,
    version: version,
    mask: mask,
    chain: chain,
    class: nm_class,
    collection: collection,
    token: token,
    extra: extra,
    memo: memo,
    hex: hex
  }

  hash.each do |key, value|
    hash.delete key if value.blank?
  end

  hash
end

#unique_token_idObject



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/mixin_bot/utils/nfo.rb', line 41

def unique_token_id
  bytes = []
  bytes += MixinBot::Utils::UUID.new(hex: chain).packed.bytes
  bytes += [nm_class].pack('H*').bytes
  bytes += MixinBot::Utils::UUID.new(hex: collection).packed.bytes
  bytes += MixinBot::Utils.bytes_of token

  md5 = Digest::MD5.new
  md5.update bytes.pack('c*')
  digest = [md5.hexdigest].pack('H*').bytes

  digest[6] = (digest[6] & 0x0f) | 0x30
  digest[8] = (digest[8] & 0x3f) | 0x80

  hex = digest.pack('c*').unpack1('H*')

  MixinBot::Utils::UUID.new(hex: hex).unpacked
end