M config-schema.dhall => config-schema.dhall +1 -0
@@ 41,6 41,7 @@
, rev_ai_token : Text
, server : { host : Text, port : Natural }
, sgx : Text
+, simpleswap_api_key : Text
, sip : { app : Text, realm : Text }
, sip_host : Text
, snikket_hosting_api : Text
M config.dhall.sample => config.dhall.sample +2 -1
@@ 83,5 83,6 @@ in
rev_ai_token = "",
upstream_domain = "example.net",
approved_domains = toMap { `example.com` = Some "customer_id" },
- keepgo = Some { api_key = "", access_token = "" }
+ keepgo = Some { api_key = "", access_token = "" },
+ simpleswap_api_key = ""
}
M forms/alt_top_up.rb => forms/alt_top_up.rb +10 -10
@@ 15,15 15,15 @@ render "alt_top_up/mailing_address"
render "alt_top_up/interac" if @currency == :CAD
render "alt_top_up/btc_addresses"
-add_btc_label = if !@btc_addresses || @btc_addresses.empty?
- "You have no Bitcoin addresses, would you like to create one?"
-else
- "Or, create a new Bitcoin address?"
-end
-
field(
- var: "add_btc_address",
- label: add_btc_label,
- type: "boolean",
- value: false
+ var: "http://jabber.org/protocol/commands#actions",
+ label: "Action",
+ type: "list-single",
+ options: [
+ { label: "Done", value: "complete" },
+ { label: "Add new Bitcoin address to account", value: "BTC" },
+ { label: "Get single-use Monero address", value: "XMR" },
+ { label: "Get single-use Ethereum address", value: "ETH" }
+ ],
+ value: "complete"
)
A forms/alt_top_up/btc.rb => forms/alt_top_up/btc.rb +5 -0
@@ 0,0 1,5 @@
+result!
+title "New Bitcoin Address"
+instructions "Your new address has been created"
+
+render "alt_top_up/btc_addresses"
A forms/alt_top_up/simpleswap.rb => forms/alt_top_up/simpleswap.rb +15 -0
@@ 0,0 1,15 @@
+result!
+title "Single-use Address"
+instructions "Your single-use address has been created"
+
+DESCRIPTION =
+ "You can make a payment of any amount to this address and " \
+ "it will be credited to your account at the current exchange rate " \
+ "once fully confirmed.".freeze
+
+field(
+ var: "addresses",
+ label: "Address",
+ description: DESCRIPTION,
+ value: @addresses
+)
D lib/add_bitcoin_address.rb => lib/add_bitcoin_address.rb +0 -50
@@ 1,50 0,0 @@
-# frozen_string_literal: true
-
-class AddBitcoinAddress
- def self.for(iq, alt_form, customer)
- if alt_form.parse(iq.form)[:add_btc_address]
- new(iq, customer)
- else
- DoNot.new(iq)
- end
- end
-
- def initialize(iq, customer)
- @reply = iq.reply
- @reply.status = :completed
- @customer = customer
- end
-
- def write
- @customer.add_btc_address.then do |addr|
- form.fields = [{
- var: "btc_address",
- type: "fixed",
- label: "Bitcoin Address",
- value: addr
- }]
- BLATHER << @reply
- end
- end
-
-protected
-
- def form
- form = @reply.form
- form.type = :result
- form.title = "New Bitcoin Address"
- form.instructions = "Your new address has been created"
- form
- end
-
- class DoNot
- def initialize(iq)
- @reply = iq.reply
- @reply.status = :completed
- end
-
- def write
- BLATHER << @reply
- end
- end
-end
M lib/alt_top_up_form.rb => lib/alt_top_up_form.rb +56 -5
@@ 1,5 1,7 @@
# frozen_string_literal: true
+require_relative "simple_swap"
+
class AltTopUpForm
def self.for(customer)
customer.btc_addresses.then do |addrs|
@@ 8,6 10,7 @@ class AltTopUpForm
end
def initialize(customer, btc_addresses)
+ @customer = customer
@balance = customer.balance
@currency = customer.currency
@btc_addresses = btc_addresses
@@ 23,10 26,58 @@ class AltTopUpForm
end
def parse(form)
- {
- add_btc_address: ["1", "true"].include?(
- form.field("add_btc_address")&.value.to_s
- )
- }
+ action =
+ form.field("http://jabber.org/protocol/commands#actions")&.value.to_s
+ case action
+ when "BTC"
+ BitcoinAddress.new(@customer)
+ when /\A[A-Z]{3}\Z/
+ SimpleSwapAddress.new(@customer, action, @btc_addresses.first)
+ else
+ NoOp.new
+ end
+ end
+
+ class NoOp
+ def action(*); end
+ end
+
+ class BitcoinAddress
+ def initialize(customer)
+ @customer = customer
+ end
+
+ def action(reply)
+ @customer.add_btc_address.then do |addr|
+ reply.command << FormTemplate.render(
+ "alt_top_up/btc",
+ btc_addresses: [addr]
+ )
+ end
+ end
+ end
+
+ class SimpleSwapAddress
+ def initialize(customer, currency, btc_address, simple_swap: SimpleSwap.new)
+ @customer = customer
+ @currency = currency.downcase
+ @btc_address = btc_address
+ @simple_swap = simple_swap
+ end
+
+ def btc_address
+ @btc_address || @customer.add_btc_address
+ end
+
+ def action(reply)
+ EMPromise.resolve(btc_address).then { |btc|
+ @simple_swap.fetch_addr(@currency, btc)
+ }.then do |addr|
+ reply.command << FormTemplate.render(
+ "alt_top_up/simpleswap",
+ addresses: [addr]
+ )
+ end
+ end
end
end
M lib/command.rb => lib/command.rb +7 -5
@@ 76,12 76,14 @@ class Command
def finish(text=nil, type: :info, status: :completed)
reply = @iq.reply
reply.status = status
- yield reply if block_given?
- if text
- reply.note_type = type
- reply.note_text = text
+ EMPromise.resolve(block_given? ? yield(reply) : nil).then {
+ if text
+ reply.note_type = type
+ reply.note_text = text
+ end
+ }.then do
+ EMPromise.reject(FinalStanza.new(reply))
end
- EMPromise.reject(FinalStanza.new(reply))
end
def customer
M lib/customer_finacials.rb => lib/customer_finacials.rb +2 -0
@@ 1,5 1,7 @@
# frozen_string_literal: true
+require "bigdecimal"
+
class CustomerFinancials
def initialize(customer_id)
@customer_id = customer_id
A lib/simple_swap.rb => lib/simple_swap.rb +66 -0
@@ 0,0 1,66 @@
+# frozen_string_literal: true
+
+require "em-http/middleware/json_response"
+
+class SimpleSwap
+ def initialize(api_key: CONFIG[:simpleswap_api_key])
+ @api_key = api_key
+ end
+
+ def fetch_range(currency)
+ req(
+ :aget, "get_ranges",
+ query: {
+ fixed: "false",
+ currency_from: currency,
+ currency_to: "btc"
+ }
+ ).then { |req|
+ { min: req.response["min"]&.to_d || 0, max: req.response["max"]&.to_d }
+ }
+ end
+
+ def fetch_rate(currency)
+ req(
+ :aget, "get_estimated",
+ query: {
+ fixed: "false",
+ currency_from: currency,
+ currency_to: "btc",
+ amount: 1
+ }
+ ).then { |req| req.response.to_d }
+ end
+
+ def fetch_addr(currency, target)
+ req(
+ :apost, "create_exchange",
+ body: {
+ fixed: false,
+ currency_from: currency,
+ currency_to: "btc",
+ amount: 10,
+ address_to: target
+ }.to_json
+ ).then(&method(:parse))
+ end
+
+ def parse(req)
+ return req.response["address_from"] if req.response["address_from"]
+
+ raise req.response["message"]
+ end
+
+ def req(m, path, query: {}, body: nil)
+ EM::HttpRequest.new(
+ "https://api.simpleswap.io/#{path}", tls: { verify_peer: true }
+ ).tap { |req| req.use(EM::Middleware::JSONResponse) }.public_send(
+ m,
+ head: {
+ "Accept" => "application/json", "Content-Type" => "application/json"
+ },
+ query: query.merge(api_key: @api_key),
+ body: body
+ )
+ end
+end
M sgx_jmp.rb => sgx_jmp.rb +3 -4
@@ 69,7 69,6 @@ COMMAND_MANAGER = SessionManager.new(
require_relative "lib/polyfill"
require_relative "lib/alt_top_up_form"
require_relative "lib/admin_command"
-require_relative "lib/add_bitcoin_address"
require_relative "lib/backend_sgx"
require_relative "lib/bwmsgsv2_repo"
require_relative "lib/bandwidth_iris_patch"
@@ 619,13 618,13 @@ Command.new(
list_for: ->(customer:, **) { !!customer&.currency }
) {
Command.customer.then { |customer|
- EMPromise.all([AltTopUpForm.for(customer), customer])
- }.then do |(alt_form, customer)|
+ AltTopUpForm.for(customer)
+ }.then do |alt_form|
Command.reply { |reply|
reply.allowed_actions = [:complete]
reply.command << alt_form.form
}.then do |iq|
- AddBitcoinAddress.for(iq, alt_form, customer).write
+ Command.finish { |reply| alt_form.parse(iq.form).action(reply) }
end
end
}.register(self).then(&CommandList.method(:register))
D test/test_add_bitcoin_address.rb => test/test_add_bitcoin_address.rb +0 -46
@@ 1,46 0,0 @@
-# frozen_string_literal: true
-
-require "test_helper"
-require "alt_top_up_form"
-require "add_bitcoin_address"
-
-class AddBitcoinAddressTest < Minitest::Test
- def test_for
- iq = Blather::Stanza::Iq::Command.new
- cust = customer
- AddBitcoinAddress.for(iq, AltTopUpForm.new(cust, []), cust)
- end
-
- def test_for_add_bitcoin
- iq = Blather::Stanza::Iq::Command.new
- iq.form.fields = [{ var: "add_btc_address", value: "true" }]
- cust = customer
- AddBitcoinAddress.for(iq, AltTopUpForm.new(cust, []), cust)
- end
-
- def test_write
- cust = Minitest::Mock.new
- cust.expect(:add_btc_address, EMPromise.resolve("newaddress"))
- iq = Blather::Stanza::Iq::Command.new
- AddBitcoinAddress.new(iq, cust).write.sync
- assert_mock cust
- end
- em :test_write
-
- class DoNotTest < Minitest::Test
- AddBitcoinAddress::DoNot::BLATHER = Minitest::Mock.new
-
- def test_write
- AddBitcoinAddress::DoNot::BLATHER.expect(
- :<<,
- EMPromise.resolve(nil)
- ) do |stanza|
- assert_equal :completed, stanza.status
- end
- iq = Blather::Stanza::Iq::Command.new
- AddBitcoinAddress::DoNot.new(iq).write.sync
- assert_mock AddBitcoinAddress::DoNot::BLATHER
- end
- em :test_write
- end
-end
M test/test_admin_command.rb => test/test_admin_command.rb +1 -0
@@ 1,5 1,6 @@
# frozen_string_literal: true
+require "test_helper"
require "admin_command"
BackendSgx::IQ_MANAGER = Minitest::Mock.new
M test/test_alt_top_up_form.rb => test/test_alt_top_up_form.rb +18 -14
@@ 62,32 62,36 @@ class AltTopUpFormTest < Minitest::Test
)
end
- def test_parse_true
+ def test_parse_complete
iq_form = Blather::Stanza::X.new
iq_form.fields = [
- { var: "add_btc_address", value: "true" }
+ { var: "http://jabber.org/protocol/commands#actions", value: "complete" }
]
- assert AltTopUpForm.new(customer, []).parse(iq_form)[:add_btc_address]
+ assert_kind_of(
+ AltTopUpForm::NoOp,
+ AltTopUpForm.new(customer, []).parse(iq_form)
+ )
end
- def test_parse_1
+ def test_parse_btc
iq_form = Blather::Stanza::X.new
iq_form.fields = [
- { var: "add_btc_address", value: "1" }
+ { var: "http://jabber.org/protocol/commands#actions", value: "BTC" }
]
- assert AltTopUpForm.new(customer, []).parse(iq_form)[:add_btc_address]
+ assert_kind_of(
+ AltTopUpForm::BitcoinAddress,
+ AltTopUpForm.new(customer, []).parse(iq_form)
+ )
end
- def test_parse_false
+ def test_parse_xmr
iq_form = Blather::Stanza::X.new
iq_form.fields = [
- { var: "add_btc_address", value: "false" }
+ { var: "http://jabber.org/protocol/commands#actions", value: "XMR" }
]
- refute AltTopUpForm.new(customer, []).parse(iq_form)[:add_btc_address]
- end
-
- def test_parse_not_presend
- iq_form = Blather::Stanza::X.new
- refute AltTopUpForm.new(customer, []).parse(iq_form)[:add_btc_address]
+ assert_kind_of(
+ AltTopUpForm::SimpleSwapAddress,
+ AltTopUpForm.new(customer, []).parse(iq_form)
+ )
end
end