Module: OpenReceive::Server::Swap

Defined in:
lib/openreceive/server/swap.rb,
lib/openreceive/server/swap/assets.rb,
lib/openreceive/server/swap/fixedfloat.rb,
lib/openreceive/server/swap/rates_feed.rb,
lib/openreceive/server/swap/weight_budget.rb,
lib/openreceive/server/swap/transient_cache.rb

Overview

Automated swaps: the FixedFloat(-compatible) provider, the asset catalog, rates/limits caching, and the LSC connection factories. Ruby port of packages/js/node/src/swap/ plus the LSC provider factory from packages/js/node/src/lsc-uri.ts.

Defined Under Namespace

Modules: Assets, FixedFloatRates Classes: FixedFloatApiError, FixedFloatProvider, SwapProviderWeightBudget, TransientSwapCache, WeightBudgetError

Class Method Summary collapse

Class Method Details

.amount_too_large_message?(message) ⇒ Boolean

Returns:

  • (Boolean)


136
137
138
# File 'lib/openreceive/server/swap.rb', line 136

def amount_too_large_message?(message)
  message.include?("max") || message.include?("large") || message.include?("limit_max")
end

.amount_too_small_message?(message) ⇒ Boolean

Returns:

  • (Boolean)


131
132
133
134
# File 'lib/openreceive/server/swap.rb', line 131

def amount_too_small_message?(message)
  message.include?("min") || message.include?("small") ||
    message.include?("out of limits") || message.include?("limit_min")
end

.availability_message(reason) ⇒ Object

Payer-facing copy per availability reason (mirrors fixedFloatAvailabilityMessage).



89
90
91
92
93
94
95
96
# File 'lib/openreceive/server/swap.rb', line 89

def availability_message(reason)
  return "This invoice is below the provider minimum." if reason == "amount_too_small"
  return "This invoice is above the provider maximum." if reason == "amount_too_large"
  return "The swap provider is rate limited." if reason == "provider_rate_limited"
  return "The swap provider is temporarily unreachable." if reason == "provider_unreachable"

  "This payment route is temporarily unavailable."
end

.classify_fixedfloat_quote_error(error) ⇒ Object

Map a quote-path failure to a SwapAvailabilityReason. The Ruby quote math reads its limits with fetch, so unlike the JS twin (whose pair math returns an unavailable quote instead of raising) this path is reachable and keeps its classifier.



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
# File 'lib/openreceive/server/swap.rb', line 102

def classify_fixedfloat_quote_error(error)
  return "provider_rate_limited" if weight_budget_error?(error)

  if error.is_a?(FixedFloatApiError)
    return "provider_rate_limited" if error.kind == "rate_limited" || error.http_status == 429
    if %w[timeout network invalid_json].include?(error.kind) ||
       (!error.http_status.nil? && error.http_status >= 500)
      return "provider_unreachable"
    end
    message = (error.fixedfloat_message || error.message).downcase
    return "amount_too_small" if amount_too_small_message?(message)
    return "amount_too_large" if amount_too_large_message?(message)

    return "pair_temporarily_unavailable"
  end

  message = (error.is_a?(StandardError) ? error.message : error.to_s).downcase
  if message.include?("rate") || message.include?("429") || message.include?("weight budget")
    return "provider_rate_limited"
  end
  if message.include?("fetch") || message.include?("network") || message.include?("timeout")
    return "provider_unreachable"
  end
  return "amount_too_small" if amount_too_small_message?(message)
  return "amount_too_large" if amount_too_large_message?(message)

  "pair_temporarily_unavailable"
end

.default_http_request(method:, url:, headers:, body: nil, timeout_ms: nil) ⇒ Object

Default HTTP transport on stdlib Net::HTTP. Injectable replacements must be callable as call(method:, url:, headers:, body:, timeout_ms:) and return a Hash with :status (Integer) and :body (String).



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/openreceive/server/swap.rb', line 51

def default_http_request(method:, url:, headers:, body: nil, timeout_ms: nil)
  uri = URI.parse(url)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == "https"
  unless timeout_ms.nil?
    seconds = timeout_ms / 1000.0
    http.open_timeout = seconds
    http.read_timeout = seconds
    http.write_timeout = seconds if http.respond_to?(:write_timeout=)
  end
  request =
    if method.to_s.upcase == "POST"
      Net::HTTP::Post.new(uri.request_uri)
    else
      Net::HTTP::Get.new(uri.request_uri)
    end
  headers.each { |key, value| request[key] = value }
  request.body = body unless body.nil?
  response = http.start { |connection| connection.request(request) }
  { status: Integer(response.code), body: response.body.to_s }
end

.fixedfloat_provider(**options) ⇒ Object



23
24
25
# File 'lib/openreceive/server/swap.rb', line 23

def fixedfloat_provider(**options)
  FixedFloatProvider.new(**options)
end

.providers_from_connections(connections, http: nil, now: nil) ⇒ Object

Build one provider per parsed LSC connection (the hashes produced by OpenReceive::Server::LscUri.parse / read_environment).



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/openreceive/server/swap.rb', line 29

def providers_from_connections(connections, http: nil, now: nil)
  Array(connections).map do |connection|
    FixedFloatProvider.new(
      id: connection.fetch("provider_id"),
      base_url: connection.fetch("base_url"),
      key: connection.fetch("key"),
      secret: connection.fetch("secret"),
      http: http,
      now: now
    )
  end
end

.providers_from_environment(env = ENV, http: nil, now: nil) ⇒ Object

Mirror of createLscSwapProvidersFromEnvironment: LSC_URI_PRIMARY first, LSC_URI_BACKUP second — the order the service fails over in.



44
45
46
# File 'lib/openreceive/server/swap.rb', line 44

def providers_from_environment(env = ENV, http: nil, now: nil)
  providers_from_connections(LscUri.read_environment(env), http: http, now: now)
end

.timeout_error?(error) ⇒ Boolean

Timeout classification shared by the API client and the rates feed (the Ruby analogue of the JS AbortError check).

Returns:

  • (Boolean)


75
76
77
78
79
80
81
# File 'lib/openreceive/server/swap.rb', line 75

def timeout_error?(error)
  return true if defined?(Net::OpenTimeout) && error.is_a?(Net::OpenTimeout)
  return true if defined?(Net::ReadTimeout) && error.is_a?(Net::ReadTimeout)
  return true if error.is_a?(Timeout::Error)

  error.is_a?(StandardError) && error.message.to_s.downcase.include?("abort")
end

.weight_budget_error?(error) ⇒ Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/openreceive/server/swap.rb', line 83

def weight_budget_error?(error)
  error.respond_to?(:weight_budget?) && error.weight_budget? == true
end