Class: OpenReceive::Server::Swap::FixedFloatProvider
- Inherits:
-
Object
- Object
- OpenReceive::Server::Swap::FixedFloatProvider
- Defined in:
- lib/openreceive/server/swap/fixedfloat.rb
Overview
Ruby port of packages/js/node/src/swap/fixedfloat.ts: the production FixedFloat(-compatible) swap provider. HMAC-signed API calls over an injectable HTTP transport, quote/create/status/refund flows, and the same order/state normalization as the JS engine.
Orders are plain string-keyed hashes with the JS SwapOrder field names (provider, provider_order_id, provider_token, pay_in_asset, deposit_address, deposit_amount, expires_at, state, ...).
Constant Summary collapse
- DEFAULT_BASE_URL =
"https://ff.io"- DEFAULT_CCIES_CACHE_SECONDS =
24 * 60 * 60
- DEFAULT_RATES_CACHE_SECONDS =
FixedFloatRates::REFRESH_SECONDS
- DEFAULT_REQUEST_TIMEOUT_MS =
10_000- DEFAULT_DEPOSIT_WINDOW_SECONDS =
10 * 60
- DEFAULT_SETTLEMENT_SLA_SECONDS =
15 * 60
- DEFAULT_INVOICE_EXPIRY_MARGIN_SECONDS =
Margin above deposit_window + settlement_sla. Five minutes keeps the shadow invoice alive through a plausible 30-minute provider order.
5 * 60
- PROVIDER_ID_PATTERN =
/\A[a-z0-9][a-z0-9_-]{0,63}\z/
Instance Attribute Summary collapse
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Class Method Summary collapse
- .amount_msats_to_btc_string(amount_msats) ⇒ Object
- .as_record(value) ⇒ Object
- .deserialize_currency_resolution(value) ⇒ Object
- .format_api_error_message(path, status, msg) ⇒ Object
- .normalize_status(status, emergency, refund_tx_id) ⇒ Object
-
.persisted_status(fallback) ⇒ Object
The persisted order's own state fields, carried through a thin poll body.
- .rate_pair_keys(resolution) ⇒ Object
- .read_currencies(data) ⇒ Object
-
.read_decimal_amount(value, label) ⇒ Object
Absent means absent; present-but-unparsable is a provider contract break and raises rather than dropping the amount from the order.
- .read_emergency_repeat(emergency) ⇒ Object
- .read_nested_string(value, path) ⇒ Object
-
.read_order_fee(record) ⇒ Object
FixedFloat reports the USD equivalents of both sides of the exchange; their gap is the swap fee the payer absorbs, so both are surfaced to explain the price.
- .read_provider_id(id) ⇒ Object
- .read_string(value) ⇒ Object
- .read_string_array(value) ⇒ Object
- .read_unix_seconds(value) ⇒ Object
- .refund_path_state?(state) ⇒ Boolean
- .refund_reason_from_emergency_statuses(statuses) ⇒ Object
- .required_expires_at(expires_at) ⇒ Object
- .required_string(value, field) ⇒ Object
- .serialize_currency_resolution(resolution) ⇒ Object
Instance Method Summary collapse
-
#attach_api_request_logger(logger) ⇒ Object
Sinks for outbound API requests/responses.
- #attach_api_response_logger(logger) ⇒ Object
-
#attach_swap_cache(cache) ⇒ Object
Attach a disposable process-local cache for provider catalogs/rates.
- #attach_weight_budget(budget) ⇒ Object
- #create_swap(pay_in_asset:, bolt11:, invoice_amount_msats:) ⇒ Object
- #get_status(order) ⇒ Object
-
#initialize(key:, secret:, id: "fixedfloat", base_url: nil, lightning_ccy: nil, http: nil, now: nil, cache_seconds: nil, rates_cache_seconds: nil, request_timeout_ms: nil, invoice_expiry_seconds: nil, deposit_window_seconds: nil, settlement_sla_seconds: nil, invoice_expiry_margin_seconds: nil) ⇒ FixedFloatProvider
constructor
A new instance of FixedFloatProvider.
- #invoice_expiry_seconds(pay_in_asset: nil) ⇒ Object
- #pay_in_asset_catalog ⇒ Object
- #quote(pay_in_asset:, invoice_amount_msats:) ⇒ Object
- #request_refund(order, refund_address) ⇒ Object
- #supported_pay_in_assets ⇒ Object
Constructor Details
#initialize(key:, secret:, id: "fixedfloat", base_url: nil, lightning_ccy: nil, http: nil, now: nil, cache_seconds: nil, rates_cache_seconds: nil, request_timeout_ms: nil, invoice_expiry_seconds: nil, deposit_window_seconds: nil, settlement_sla_seconds: nil, invoice_expiry_margin_seconds: nil) ⇒ FixedFloatProvider
Returns a new instance of FixedFloatProvider.
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 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 67 def initialize(key:, secret:, id: "fixedfloat", base_url: nil, lightning_ccy: nil, http: nil, now: nil, cache_seconds: nil, rates_cache_seconds: nil, request_timeout_ms: nil, invoice_expiry_seconds: nil, deposit_window_seconds: nil, settlement_sla_seconds: nil, invoice_expiry_margin_seconds: nil) @name = self.class.read_provider_id(id) raise ArgumentError, "FixedFloat-compatible API key must not be empty." if key.to_s.strip.empty? if secret.to_s.strip.empty? raise ArgumentError, "FixedFloat-compatible API secret must not be empty." end @key = key @secret = secret @base_url = (base_url || DEFAULT_BASE_URL).sub(%r{/+\z}, "") normalized_lightning = lightning_ccy.to_s.strip @lightning_ccy = normalized_lightning.empty? ? nil : normalized_lightning @http = http || Swap.method(:default_http_request) @now = now || -> { Time.now.to_i } @cache_seconds = cache_seconds || DEFAULT_CCIES_CACHE_SECONDS @rates_cache_seconds = rates_cache_seconds || DEFAULT_RATES_CACHE_SECONDS unless @rates_cache_seconds.is_a?(Integer) && @rates_cache_seconds.positive? raise ArgumentError, "FixedFloat rates_cache_seconds must be a positive safe integer." end @request_timeout_ms = request_timeout_ms || DEFAULT_REQUEST_TIMEOUT_MS unless @request_timeout_ms.is_a?(Integer) && @request_timeout_ms.positive? raise ArgumentError, "FixedFloat request_timeout_ms must be a positive safe integer." end deposit_window = deposit_window_seconds || DEFAULT_DEPOSIT_WINDOW_SECONDS settlement_sla = settlement_sla_seconds || DEFAULT_SETTLEMENT_SLA_SECONDS expiry_margin = invoice_expiry_margin_seconds || DEFAULT_INVOICE_EXPIRY_MARGIN_SECONDS { "FixedFloat deposit_window_seconds" => deposit_window, "FixedFloat settlement_sla_seconds" => settlement_sla, "FixedFloat invoice_expiry_margin_seconds" => expiry_margin }.each do |label, value| unless value.is_a?(Integer) && value >= 0 raise ArgumentError, "#{label} must be a non-negative safe integer." end end minimum_expiry = deposit_window + settlement_sla + expiry_margin @invoice_expiry_seconds = invoice_expiry_seconds || minimum_expiry unless @invoice_expiry_seconds.is_a?(Integer) && @invoice_expiry_seconds >= minimum_expiry raise ArgumentError, "FixedFloat provider #{@name.inspect}: invoice_expiry_seconds " \ "(#{@invoice_expiry_seconds}) must be at least #{minimum_expiry} = " \ "deposit_window(#{deposit_window}) + settlement_sla(#{settlement_sla}) + " \ "margin(#{expiry_margin}). Omit invoice_expiry_seconds to auto-derive it, " \ "or raise it above that floor." end @cache = nil @weight_budget = nil @api_request_logger = nil @api_response_logger = nil end |
Instance Attribute Details
#name ⇒ Object (readonly)
Returns the value of attribute name.
65 66 67 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 65 def name @name end |
Class Method Details
.amount_msats_to_btc_string(amount_msats) ⇒ Object
582 583 584 585 586 587 588 589 590 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 582 def amount_msats_to_btc_string(amount_msats) unless amount_msats.is_a?(Integer) && amount_msats.positive? raise ArgumentError, "invoice_amount_msats must be a positive safe integer." end sats = (amount_msats + 999) / 1000 whole_btc = sats / 100_000_000 fractional = (sats % 100_000_000).to_s.rjust(8, "0").sub(/0+\z/, "") fractional.empty? ? whole_btc.to_s : "#{whole_btc}.#{fractional}" end |
.as_record(value) ⇒ Object
769 770 771 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 769 def as_record(value) value.is_a?(Hash) ? value : {} end |
.deserialize_currency_resolution(value) ⇒ Object
753 754 755 756 757 758 759 760 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 753 def deserialize_currency_resolution(value) parsed = JSON.parse(value) { "fetched_at" => parsed.fetch("fetched_at"), "pay_in" => parsed.fetch("pay_in").to_h, "lightning" => parsed.fetch("lightning") } end |
.format_api_error_message(path, status, msg) ⇒ Object
592 593 594 595 596 597 598 599 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 592 def (path, status, msg) = read_string(msg) if .nil? "FixedFloat #{path} failed with HTTP #{status}." else "FixedFloat #{path} failed with HTTP #{status}: #{}" end end |
.normalize_status(status, emergency, refund_tx_id) ⇒ Object
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 612 def normalize_status(status, emergency, refund_tx_id) normalized = status.to_s.upcase if !refund_tx_id.nil? && %w[DONE FINISHED].include?(normalized) return { "state" => "refunded" } end case normalized when "NEW" then return { "state" => "awaiting_deposit" } when "PENDING" then return { "state" => "confirming" } when "EXCHANGE" then return { "state" => "exchanging" } when "WITHDRAW" then return { "state" => "paying_invoice" } when "DONE" then return { "state" => "completed" } when "EXPIRED" then return { "state" => "expired" } end if normalized == "EMERGENCY" choice = read_string(emergency["choice"])&.upcase statuses = read_string_array(emergency["status"]).map(&:upcase) refund_reason = refund_reason_from_emergency_statuses(statuses) if choice == "REFUND" && !refund_tx_id.nil? result = { "state" => "refunded" } result["refund_reason"] = refund_reason unless refund_reason.nil? return result end if choice == "REFUND" result = { "state" => "refund_pending" } result["refund_reason"] = refund_reason unless refund_reason.nil? return result end if choice == "EXCHANGE" return { "state" => "attention", "attention" => true, "attention_reason" => "provider_reported_emergency" } end if (statuses & %w[MORE OVER OVERPAID]).any? return { "state" => "attention", "attention" => true, "attention_reason" => "provider_reported_emergency" } end result = { "state" => "refund_required" } result["refund_reason"] = refund_reason unless refund_reason.nil? return result end return { "state" => "failed" } if normalized.include?("FAIL") # An unrecognized status is NOT a provider-reported emergency: # label it as unknown so operators land on the right runbook section. { "state" => "attention", "attention" => true, "attention_reason" => "provider_status_unrecognized" } end |
.persisted_status(fallback) ⇒ Object
The persisted order's own state fields, carried through a thin poll body.
697 698 699 700 701 702 703 704 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 697 def persisted_status(fallback) { "state" => fallback["state"], "attention" => fallback["attention"], "attention_reason" => fallback["attention_reason"], "refund_reason" => fallback["refund_reason"] }.compact end |
.rate_pair_keys(resolution) ⇒ Object
762 763 764 765 766 767 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 762 def rate_pair_keys(resolution) lightning_code = resolution.fetch("lightning").fetch("code") resolution.fetch("pay_in").values.map do |currency| FixedFloatRates.pair_key(currency.fetch("code"), lightning_code) end.uniq end |
.read_currencies(data) ⇒ Object
706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 706 def read_currencies(data) record = as_record(data) items = if data.is_a?(Array) data elsif record["ccies"].is_a?(Array) record["ccies"] elsif record["currencies"].is_a?(Array) record["currencies"] else [] end currencies = [] items.each do |item| row = as_record(item) code = read_string(row["code"]) || read_string(row["ticker"]) coin = read_string(row["coin"]) || read_string(row["currency"]) || read_string(row["symbol"]) network = read_string(row["network"]) || read_string(row["chain"]) || read_string(row["networkName"]) || read_string(row["name"]) next if code.nil? || coin.nil? || network.nil? currency = { "code" => code, "coin" => coin.upcase, "network" => network } currency["recv"] = row["recv"] if [true, false].include?(row["recv"]) currency["send"] = row["send"] if [true, false].include?(row["send"]) currencies << currency end currencies end |
.read_decimal_amount(value, label) ⇒ Object
Absent means absent; present-but-unparsable is a provider contract break and raises rather than dropping the amount from the order.
681 682 683 684 685 686 687 688 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 681 def read_decimal_amount(value, label) return nil if value.nil? unless /\A[0-9]+(\.[0-9]+)?\z/.match?(value) raise "FixedFloat #{label} is not a decimal amount." end value end |
.read_emergency_repeat(emergency) ⇒ Object
736 737 738 739 740 741 742 743 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 736 def read_emergency_repeat(emergency) value = emergency["repeat"] return value if [true, false].include?(value) return false if value == 0 || value == "0" # rubocop:disable Style/NumericPredicate return true if value == 1 || value == "1" nil end |
.read_nested_string(value, path) ⇒ Object
773 774 775 776 777 778 779 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 773 def read_nested_string(value, path) current = value path.each do |key| current = as_record(current)[key] end read_string(current) end |
.read_order_fee(record) ⇒ Object
FixedFloat reports the USD equivalents of both sides of the exchange; their gap is the swap fee the payer absorbs, so both are surfaced to explain the price.
604 605 606 607 608 609 610 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 604 def read_order_fee(record) pay_in_fiat = read_nested_string(record, %w[from usd]) payout_fiat = read_nested_string(record, %w[to usd]) return nil if pay_in_fiat.nil? || payout_fiat.nil? { "currency" => "USD", "pay_in_fiat" => pay_in_fiat, "payout_fiat" => payout_fiat } end |
.read_provider_id(id) ⇒ Object
121 122 123 124 125 126 127 128 129 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 121 def self.read_provider_id(id) normalized = id.to_s.strip unless PROVIDER_ID_PATTERN.match?(normalized) raise ArgumentError, "FixedFloat-compatible provider id must use lowercase letters, numbers, " \ "underscores, or hyphens." end normalized end |
.read_string(value) ⇒ Object
781 782 783 784 785 786 787 788 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 781 def read_string(value) return value if value.is_a?(String) && !value.empty? if value.is_a?(Numeric) && (!value.respond_to?(:finite?) || value.finite?) return OpenReceive::Rates.number_to_plain_decimal_string(value) end nil end |
.read_string_array(value) ⇒ Object
790 791 792 793 794 795 796 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 790 def read_string_array(value) if value.is_a?(Array) return value.filter_map { |item| read_string(item) } end string = read_string(value) string.nil? ? [] : [string] end |
.read_unix_seconds(value) ⇒ Object
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 805 def read_unix_seconds(value) numeric = if value.is_a?(String) begin Integer(value, 10) rescue ArgumentError begin rational = Rational(value) rational.denominator == 1 ? rational.numerator : nil rescue ArgumentError, ZeroDivisionError nil end end else value end return nil unless numeric.is_a?(Numeric) return nil unless numeric == numeric.to_i && numeric >= 0 return nil if numeric.to_i > FixedFloatRates::MAX_SAFE_INTEGER numeric.to_i end |
.refund_path_state?(state) ⇒ Boolean
675 676 677 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 675 def refund_path_state?(state) %w[refund_required refund_pending refunded].include?(state) end |
.refund_reason_from_emergency_statuses(statuses) ⇒ Object
665 666 667 668 669 670 671 672 673 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 665 def refund_reason_from_emergency_statuses(statuses) less = statuses.include?("LESS") expired = statuses.include?("EXPIRED") return "underpaid_and_late" if less && expired return "underpaid" if less return "late_deposit" if expired nil end |
.required_expires_at(expires_at) ⇒ Object
690 691 692 693 694 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 690 def required_expires_at(expires_at) raise "FixedFloat order is missing time.expiration." if expires_at.nil? expires_at end |
.required_string(value, field) ⇒ Object
798 799 800 801 802 803 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 798 def required_string(value, field) string = read_string(value) raise "FixedFloat response missing #{field}." if string.nil? string end |
.serialize_currency_resolution(resolution) ⇒ Object
745 746 747 748 749 750 751 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 745 def serialize_currency_resolution(resolution) JSON.generate( "fetched_at" => resolution.fetch("fetched_at"), "pay_in" => resolution.fetch("pay_in").to_a, "lightning" => resolution.fetch("lightning") ) end |
Instance Method Details
#attach_api_request_logger(logger) ⇒ Object
Sinks for outbound API requests/responses. The caller is responsible for sanitizing nested secrets (e.g. the order token on status/refund bodies); the API key and HMAC signature live in headers and are deliberately never logged.
141 142 143 144 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 141 def attach_api_request_logger(logger) @api_request_logger = logger nil end |
#attach_api_response_logger(logger) ⇒ Object
146 147 148 149 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 146 def attach_api_response_logger(logger) @api_response_logger = logger nil end |
#attach_swap_cache(cache) ⇒ Object
Attach a disposable process-local cache for provider catalogs/rates.
132 133 134 135 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 132 def attach_swap_cache(cache) @cache = cache nil end |
#attach_weight_budget(budget) ⇒ Object
151 152 153 154 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 151 def attach_weight_budget(budget) @weight_budget = budget nil end |
#create_swap(pay_in_asset:, bolt11:, invoice_amount_msats:) ⇒ Object
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 240 def create_swap(pay_in_asset:, bolt11:, invoice_amount_msats:) resolution = resolve_currencies from_ccy = required_currency(resolution, pay_in_asset) to_ccy = resolution.fetch("lightning").fetch("code") data = post("create", "type" => "fixed", "fromCcy" => from_ccy, "toCcy" => to_ccy, "direction" => "to", "amount" => self.class.amount_msats_to_btc_string(invoice_amount_msats), "toAddress" => bolt11) order = normalize_order(data, pay_in_asset: pay_in_asset) # FixedFloat order objects do not always carry the USD equivalents # (from.usd / to.usd) that explain the swap fee, so backfill them # from a best-effort /price lookup for the same trade. A failure # just leaves the fee off the deposit panel. return order unless order["fee"].nil? fee = fetch_order_fee(from_ccy, to_ccy, invoice_amount_msats) fee.nil? ? order : order.merge("fee" => fee) end |
#get_status(order) ⇒ Object
262 263 264 265 266 267 268 269 270 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 262 def get_status(order) stored = OpenReceive.stringify(order) data = post("order", "id" => stored.fetch("provider_order_id"), "token" => stored.fetch("provider_token")) stored.merge( normalize_order(data, pay_in_asset: stored["pay_in_asset"], fallback: stored) ) end |
#invoice_expiry_seconds(pay_in_asset: nil) ⇒ Object
184 185 186 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 184 def invoice_expiry_seconds(pay_in_asset: nil) @invoice_expiry_seconds end |
#pay_in_asset_catalog ⇒ Object
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 160 def pay_in_asset_catalog resolution = resolve_currencies # /ccies reports only availability and display metadata per currency # — it carries no amount limits. Per-pair min/max come from the # public XML rates export, cached in this process so the # payment-method screen never hits /price. rates = resolve_rates_index(resolution) resolution.fetch("pay_in").map do |pay_in_asset, currency| pair = rates.fetch("pairs")[ FixedFloatRates.pair_key(currency.fetch("code"), resolution.fetch("lightning").fetch("code")) ] if pair.nil? { "pay_asset" => pay_in_asset, "available" => false, "unavailable_reason" => "pair_temporarily_unavailable", "unavailable_message" => Swap.("pair_temporarily_unavailable") } else { "pay_asset" => pay_in_asset }.merge(FixedFloatRates.invoice_limits(pair)) end end end |
#quote(pay_in_asset:, invoice_amount_msats:) ⇒ Object
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 188 def quote(pay_in_asset:, invoice_amount_msats:) # Indicative quote from the process-local XML rates cache. /create is # still the binding rate. Rates refresh failures raise (fail closed) # so the service can skip this provider and try the next configured # LSC connection. resolution = resolve_currencies from_ccy = required_currency(resolution, pay_in_asset) rates = resolve_rates_index(resolution) begin pair = rates.fetch("pairs")[ FixedFloatRates.pair_key(from_ccy, resolution.fetch("lightning").fetch("code")) ] if pair.nil? return unavailable_quote(pay_in_asset, "pair_temporarily_unavailable") end limits = FixedFloatRates.invoice_limits(pair) pay_amount = FixedFloatRates.quote_pay_amount( pair: pair, invoice_amount_msats: invoice_amount_msats ) if pay_amount.nil? return unavailable_quote(pay_in_asset, "pair_temporarily_unavailable", limits) end # Prefer invoice-side limits when conversion succeeded; also # compare the indicative pay amount to XML min/max so padded <out> # decimals cannot leave a below-min asset selectable. pay_below_min = FixedFloatRates.compare_decimal_amounts(pay_amount, limits.fetch("minimum_pay_amount")) == -1 pay_above_max = FixedFloatRates.compare_decimal_amounts(pay_amount, limits.fetch("maximum_pay_amount")) == 1 minimum_msats = limits["minimum_invoice_amount_msats"] maximum_msats = limits["maximum_invoice_amount_msats"] amount_too_small = pay_below_min || (!minimum_msats.nil? && invoice_amount_msats < minimum_msats) amount_too_large = pay_above_max || (!maximum_msats.nil? && invoice_amount_msats > maximum_msats) if amount_too_small || amount_too_large reason = amount_too_small ? "amount_too_small" : "amount_too_large" return unavailable_quote(pay_in_asset, reason, limits) end { "pay_amount" => pay_amount, "pay_asset" => pay_in_asset, "available" => true, "provider" => @name }.merge(limits) rescue StandardError => e # Pair-math / limit errors stay as unavailable quotes. Rates and # network failures already raised above from resolve_rates_index # and must not be swallowed here. reason = Swap.classify_fixedfloat_quote_error(e) unavailable_quote(pay_in_asset, reason) end end |
#request_refund(order, refund_address) ⇒ Object
272 273 274 275 276 277 278 279 280 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 272 def request_refund(order, refund_address) stored = OpenReceive.stringify(order) post("emergency", "id" => stored.fetch("provider_order_id"), "token" => stored.fetch("provider_token"), "choice" => "REFUND", "address" => refund_address) nil end |
#supported_pay_in_assets ⇒ Object
156 157 158 |
# File 'lib/openreceive/server/swap/fixedfloat.rb', line 156 def supported_pay_in_assets resolve_currencies.fetch("pay_in").keys end |