~singpolyma/sgx-jmp

4b045d178a06802cfee1604599bc6a2a0ec3e0bd — Stephen Paul Weber 1 year, 1 month ago b640c8e + 38d58f6
Merge branch 'simpleswap'

* simpleswap:
  Integrate SimpleSwap into alt top up
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