Class: MixinBot::Nfo

Inherits:
Object
  • Object
show all
Defined in:
lib/mixin_bot/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.



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

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.



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

def chain
  @chain
end

#collectionObject

Returns the value of attribute collection.



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

def collection
  @collection
end

#extraObject

Returns the value of attribute extra.



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

def extra
  @extra
end

#hexObject

Returns the value of attribute hex.



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

def hex
  @hex
end

#maskObject

Returns the value of attribute mask.



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

def mask
  @mask
end

#memoObject

Returns the value of attribute memo.



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

def memo
  @memo
end

#nm_classObject

Returns the value of attribute nm_class.



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

def nm_class
  @nm_class
end

#prefixObject (readonly)

Returns the value of attribute prefix.



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

def prefix
  @prefix
end

#rawObject (readonly)

Returns the value of attribute raw.



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

def raw
  @raw
end

#tokenObject

Returns the value of attribute token.



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

def token
  @token
end

#versionObject (readonly)

Returns the value of attribute version.



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

def version
  @version
end

Instance Method Details

#decodeObject



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

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



122
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
# File 'lib/mixin_bot/nfo.rb', line 122

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::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::UUID.new(hex: bytes.shift(collection_length).pack('C*').unpack1('H*')).unpacked

    token_length = bytes.shift
    @token = MixinBot.utils.decode_int bytes.shift(token_length)
  end

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

  self
end

#encodeObject



67
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
# File 'lib/mixin_bot/nfo.rb', line 67

def encode
  bytes = []

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

  if mask.zero?
    bytes += [0]
  else
    bytes += [1]
    bytes += MixinBot.utils.encode_uint_64 mask
    bytes += MixinBot::UUID.new(hex: chain).packed.bytes

    class_bytes = [nm_class].pack('H*').bytes
    bytes += MixinBot.utils.encode_int class_bytes.size
    bytes += class_bytes

    collection_bytes = collection.split('-').pack('H* H* H* H* H*').bytes
    bytes += MixinBot.utils.encode_int collection_bytes.size
    bytes += collection_bytes

    # token_bytes = memo[:token].split('-').pack('H* H* H* H* H*').bytes
    token_bytes = MixinBot.utils.encode_int token
    bytes += MixinBot.utils.encode_int token_bytes.size
    bytes += token_bytes
  end

  extra_bytes = [extra].pack('H*').bytes
  bytes += MixinBot.utils.encode_int 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



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

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



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

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



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

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

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

  hash
end

#unique_token_idObject



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

def unique_token_id
  bytes = []
  bytes += MixinBot::UUID.new(hex: chain).packed.bytes
  bytes += [nm_class].pack('H*').bytes
  bytes += MixinBot::UUID.new(hex: collection).packed.bytes
  bytes += MixinBot.utils.encode_int 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::UUID.new(hex:).unpacked
end