Class: Bitcoin::BIP321URI

Inherits:
Object
  • Object
show all
Defined in:
lib/bitcoin/bip321_uri.rb

Overview

BIP-321 URI

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(address: nil, amount: nil, label: nil, message: nil, other_params: {}, pop: nil, lightning: nil, lno: nil, pay: nil, sp: nil, req_pop: false, query_addrs: []) ⇒ BIP321URI

Constructor

Parameters:

  • address (String) (defaults to: nil)
  • amount (BigDecimal, Integer) (defaults to: nil)
  • label (String) (defaults to: nil)
  • message (String) (defaults to: nil)
  • other_params (Hash) (defaults to: {})
  • pop (String) (defaults to: nil)
  • lightning (String) (defaults to: nil)

    BOLT11 invoice.

  • lno (String) (defaults to: nil)

    BOLT12 offer.

  • pay (String) (defaults to: nil)

    BIP-351 private address.

  • sp (String) (defaults to: nil)

    BIP-352 silent payment address.

  • req_pop (Boolean) (defaults to: false)

    whether pop is required or not.

  • query_addrs (Array) (defaults to: [])

    A list of addresses to be placed as query parameters.

Raises:

  • (ArgumentError)


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
62
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
# File 'lib/bitcoin/bip321_uri.rb', line 36

def initialize(address: nil, amount: nil, label: nil, message: nil, other_params: {},
               pop:nil, lightning: nil, lno: nil, pay:nil, sp: nil, req_pop: false, query_addrs: [])
  if address
    Bitcoin::Script.parse_from_addr(address)
    @addr = address
  end
  if amount
    amount = BigDecimal(amount) if amount.is_a?(Integer)
    raise ArgumentError, "amount must be BigDecimal or integer." unless amount.is_a?(BigDecimal)
    @amount = amount
  end
  raise ArgumentError, "label must be string." if label && !label.is_a?(String)
  @label = label

  raise ArgumentError, "message must be string." if message && !message.is_a?(String)
  @message = message

  raise ArgumentError, "pop must be string." if pop && !pop.is_a?(String)
  @pop = pop

  raise ArgumentError, "lightning must be string." if lightning && !lightning.is_a?(String)
  @lightning = lightning

  raise ArgumentError, "lno must be string." if lno && !lno.is_a?(String)
  @lno = lno

  raise ArgumentError, "pay must be string." if pay && !pay.is_a?(String)
  @pay = pay

  if sp
    raise ArgumentError, "sp must be string." unless sp.is_a?(String)
    begin
      Bech32::SilentPaymentAddr.parse(sp)
      @sp = sp
    rescue ArgumentError
      raise ArgumentError, "Invalid sp address specified."
    end
  end

  raise ArgumentError, 'pop is required, if req_pop is true.' if req_pop && pop.nil?
  @req_pop = req_pop

  @query_addrs = query_addrs.map do |addr|
    Bitcoin::Script.parse_from_addr(addr)
    addr
  end

  raise ArgumentError, "other_params must be Hash." unless other_params.is_a?(Hash)
  other_params.keys.each do |key|
    raise ArgumentError, 'An unsupported reqparam is included.' if key.start_with?('req-')
  end
  @other_params = other_params
end

Instance Attribute Details

#addrObject (readonly)

Returns the value of attribute addr.



9
10
11
# File 'lib/bitcoin/bip321_uri.rb', line 9

def addr
  @addr
end

#amountObject (readonly)

Returns the value of attribute amount.



10
11
12
# File 'lib/bitcoin/bip321_uri.rb', line 10

def amount
  @amount
end

#labelObject (readonly)

Returns the value of attribute label.



11
12
13
# File 'lib/bitcoin/bip321_uri.rb', line 11

def label
  @label
end

#lightningObject (readonly)

BOLT11 invoice



14
15
16
# File 'lib/bitcoin/bip321_uri.rb', line 14

def lightning
  @lightning
end

#lnoObject (readonly)

BOLT12 offer



15
16
17
# File 'lib/bitcoin/bip321_uri.rb', line 15

def lno
  @lno
end

#messageObject (readonly)

Returns the value of attribute message.



12
13
14
# File 'lib/bitcoin/bip321_uri.rb', line 12

def message
  @message
end

#other_paramsObject (readonly)

Returns the value of attribute other_params.



21
22
23
# File 'lib/bitcoin/bip321_uri.rb', line 21

def other_params
  @other_params
end

#payObject (readonly)

BIP-351 private address



16
17
18
# File 'lib/bitcoin/bip321_uri.rb', line 16

def pay
  @pay
end

#popObject (readonly)

proof of payment



13
14
15
# File 'lib/bitcoin/bip321_uri.rb', line 13

def pop
  @pop
end

#query_addrsObject (readonly)

Returns the value of attribute query_addrs.



19
20
21
# File 'lib/bitcoin/bip321_uri.rb', line 19

def query_addrs
  @query_addrs
end

#req_popObject (readonly)

Returns the value of attribute req_pop.



18
19
20
# File 'lib/bitcoin/bip321_uri.rb', line 18

def req_pop
  @req_pop
end

#spObject (readonly)

BIP-352 silent payment address



17
18
19
# File 'lib/bitcoin/bip321_uri.rb', line 17

def sp
  @sp
end

Class Method Details

.parse(uri) ⇒ Bitcoin::BIP321URI

Parse BIP-321 URI string.

Parameters:

Returns:

Raises:

  • (ArgumentError)


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
125
126
127
128
129
130
# File 'lib/bitcoin/bip321_uri.rb', line 94

def self.parse(uri)
  raise ArgumentError, "uri must be string." unless uri.is_a?(String)
  raise ArgumentError, "Invalid uri scheme." unless uri.downcase.start_with?('bitcoin:')
  uri = uri[8..-1]
  addr, params = uri.split('?', 2)
  req_pop = false
  query_addrs = []
  params = if params
             decoded = URI.decode_www_form(params)
             decoded = decoded.map do |k, v|
               if k == 'req-pop'
                 req_pop = true
                 k = 'pop'
               end
               if %w[bc tb].include?(k.downcase)
                 raise ArgumentError, "#{k} not allowed in current network." unless Bitcoin.chain_params.bech32_hrp == k.downcase
                 query_addrs << v
                 nil
               else
                 [k, v]
               end
             end.compact
             keys = decoded.map(&:first)
             duplicate_key = keys.detect { |key| keys.count(key) > 1 }
             raise ArgumentError, "#{duplicate_key} must not appear twice." if duplicate_key
             decoded.to_h.except('')
           else
             {}
           end
  addr = nil if addr.empty?
  amount = params['amount'] ? BigDecimal(params['amount']) : nil
  excluded_keys = %w[amount label message pop lightning lno pay sp]
  others = params.except(*excluded_keys)
  BIP321URI.new(address: addr, amount: amount, label: params['label'], message: params['message'],
                pop: params['pop'], lightning: params['lightning'], lno: params['lno'],
                pay: params['pay'], sp: params['sp'], other_params: others, req_pop: req_pop, query_addrs: query_addrs)
end

Instance Method Details

#addressesArray

Get all addresses contained in the URI body and query parameters

Returns:

  • (Array)

    An array of address.



134
135
136
137
138
# File 'lib/bitcoin/bip321_uri.rb', line 134

def addresses
  addrs = []
  addrs << @addr if @addr
  addrs + @query_addrs
end

#satoshiInteger?

Payment amount (satoshi unit)

Returns:



142
143
144
# File 'lib/bitcoin/bip321_uri.rb', line 142

def satoshi
  amount.nil? ? nil : (amount * 100_000_000).to_i
end

#to_sObject



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/bitcoin/bip321_uri.rb', line 146

def to_s
  uri = 'bitcoin:'
  uri << addr if addr
  base_params = {}
  base_params['amount'] = amount.to_s('f').sub(/\.0+$/, '') if amount
  base_params['label'] = label if label
  base_params['message'] = message if message
  pop_label = req_pop ? 'req-pop' : 'pop'
  base_params[pop_label] = pop if pop
  base_params['lightning'] = lightning if lightning
  base_params['lno'] = lno if lno
  base_params['sp'] = sp if sp
  base_params['pay'] = pay if pay

  all_params = base_params.merge other_params
  params = all_params.map do |k, v|
    "#{k}=#{CGI.escape(v).gsub('+', '%20')}"
  end.join('&')
  uri << "?#{params}" unless params.empty?
  unless query_addrs.empty?
    uri << '?' unless uri.include?('?')
    uri << query_addrs.map {|addr| "#{Bitcoin.chain_params.bech32_hrp}=#{addr}"}.join('&')
  end
  uri
end