Class: MixinBot::Invoice

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

Constant Summary collapse

INVOICE_PREFIX =
'MIN'
INVOICE_VERSION =
0x00

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**args) ⇒ Invoice

Returns a new instance of Invoice.



10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/mixin_bot/invoice.rb', line 10

def initialize(**args)
  args = args.with_indifferent_access

  if args[:address]
    @address = args[:address]
    decode
  else
    @version = args[:version] || INVOICE_VERSION
    @recipient = args[:recipient]
    @entries = args[:entries] || []
    encode
  end
end

Instance Attribute Details

#addressObject

Returns the value of attribute address.



8
9
10
# File 'lib/mixin_bot/invoice.rb', line 8

def address
  @address
end

#entriesObject

Returns the value of attribute entries.



8
9
10
# File 'lib/mixin_bot/invoice.rb', line 8

def entries
  @entries
end

#recipientObject

Returns the value of attribute recipient.



8
9
10
# File 'lib/mixin_bot/invoice.rb', line 8

def recipient
  @recipient
end

#versionObject

Returns the value of attribute version.



8
9
10
# File 'lib/mixin_bot/invoice.rb', line 8

def version
  @version
end

Instance Method Details

#decodeObject

Raises:



63
64
65
66
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/mixin_bot/invoice.rb', line 63

def decode
  prefix = address[0..2]
  raise MixinBot::InvalidInvoiceFormatError, 'invalid invoice prefix' unless prefix == INVOICE_PREFIX

  data = Base64.urlsafe_decode64(address[3..])
  raise MixinBot::InvalidInvoiceFormatError, 'invalid invoice payload size' if data.size < 3 + 23 + 1

  payload = data[...-4]
  checksum = SHA3::Digest::SHA256.digest(INVOICE_PREFIX + payload)[0...4]
  raise MixinBot::InvalidInvoiceFormatError, 'invalid invoice checksum' unless checksum == data[-4..]

  payload = payload.bytes

  # Read version
  self.version = MixinBot.utils.decode_int payload.shift(1)
  raise MixinBot::InvalidInvoiceFormatError, 'invalid invoice version' unless version == INVOICE_VERSION

  # Read recipient with proper size handling
  recipient_size = MixinBot.utils.decode_uint16 payload.shift(2)
  recipient_bytes = payload.shift(recipient_size)
  self.recipient = MixinBot::MixAddress.new(payload: recipient_bytes.pack('C*'))

  # decode entries
  entries_size = MixinBot.utils.decode_int payload.shift(1)
  entries = []
  entries_size.times do
    next if payload.empty?

    trace_id_bytes = payload.shift(16)
    trace_id = MixinBot::UUID.new(raw: trace_id_bytes.pack('C*')).unpacked

    asset_id_bytes = payload.shift(16)
    asset_id = MixinBot::UUID.new(raw: asset_id_bytes.pack('C*')).unpacked

    amount_size = MixinBot.utils.decode_int payload.shift(1)
    amount_bytes = payload.shift(amount_size)
    amount = amount_bytes.pack('C*').to_d

    extra_size = MixinBot.utils.decode_int payload.shift(2)
    extra = payload.shift(extra_size).pack('C*')

    references_count = MixinBot.utils.decode_int payload.shift(1)
    hash_references = []
    index_references = []

    references_count.times do
      rv = MixinBot.utils.decode_int payload.shift(1)
      case rv
      when 0
        hash_references << payload.shift(32).pack('C*').unpack1('H*')
      when 1
        index_references << MixinBot.utils.decode_int(payload.shift(1))
      else
        raise MixinBot::InvalidInvoiceFormatError, "invalid invoice reference type: #{rv}"
      end
    end

    entries << InvoiceEntry.new(trace_id:, asset_id:, amount:, extra:, index_references:, hash_references:)
  end

  self.entries = entries
end

#encodeObject



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/mixin_bot/invoice.rb', line 33

def encode
  # Start with empty payload
  payload = []

  # Add version
  payload += MixinBot.utils.encode_int(version)

  # Add recipient - ensure we're using the raw payload bytes
  recipient_payload = recipient.payload
  recipient_bytes = recipient_payload.is_a?(String) ? recipient_payload.bytes : recipient_payload
  payload += MixinBot.utils.encode_uint16(recipient_bytes.size)

  payload += recipient_bytes

  # Add entries
  payload += MixinBot.utils.encode_int(entries.size)
  entries.each do |entry|
    payload += entry.encode
  end

  # Convert payload to binary string
  payload = payload.pack('C*')

  # Calculate checksum
  checksum = SHA3::Digest::SHA256.digest(INVOICE_PREFIX + payload)[0...4]

  # Combine everything and encode to base64
  self.address = INVOICE_PREFIX + Base64.urlsafe_encode64(payload + checksum, padding: false)
end

#to_hashObject



24
25
26
27
28
29
30
31
# File 'lib/mixin_bot/invoice.rb', line 24

def to_hash
  {
    version:,
    address:,
    recipient: recipient.address,
    entries: entries.map(&:to_hash)
  }
end