money-unirate-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 99d6c31d3a85db82760f94c04367f876936afcc29c9c4170970cd62c3bd19916
4
+ data.tar.gz: 91ffe4ab7c550ad26cb2e4d6c7400805e71acfbba96bb1c15276c977a0e95935
5
+ SHA512:
6
+ metadata.gz: 6be26a0a659e674438012436dcc29fcdf654c8ed71a07d90fa136fa8e79b686b02d9f3b9a31b7618762cd650dbb82516baa9c73c3bedf5593207583d5755af0e
7
+ data.tar.gz: 2458c612f096945ce4d93d72048fc19ab6c18efe310e8fa6b358a1c0e60a646896e2b65b342912a4fd353273e6af08a23d8e06a45d9a8f1719e34b5d72d02de5
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2026-05-30
8
+
9
+ ### Added
10
+
11
+ - `Money::Bank::UniRate` — a `Money::Bank::VariableExchange` subclass backed by
12
+ the UniRate API (https://unirateapi.com).
13
+ - Single-base snapshot fetch from `GET /api/rates`; all cross-rates derived on
14
+ demand so a TTL refresh never serves a stale pair.
15
+ - Lazy fetch on first conversion, optional `ttl_in_seconds` refresh, and
16
+ `flush_rates` to force a refresh.
17
+ - Error mapping (`UniRateError`) for auth (401), Pro-gated (403), not-found
18
+ (404), rate-limit (429), network, and malformed-response failures.
19
+ - Zero runtime dependencies beyond the `money` gem (pure stdlib HTTP).
20
+
21
+ [0.1.0]: https://github.com/UniRate-API/money-unirate-api/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Unirate Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # money-unirate-api
2
+
3
+ A [`Money::Bank`](https://github.com/RubyMoney/money) implementation backed by
4
+ the [UniRate API](https://unirateapi.com) — free, real-time currency exchange
5
+ rates for the [`money`](https://rubygems.org/gems/money) gem.
6
+
7
+ - Drop-in `Money::Bank::UniRate` for `Money.default_bank`
8
+ - One HTTP call per snapshot: fetches a single base currency, **derives every
9
+ cross-rate on demand** (so a refresh never serves a stale pair)
10
+ - Lazy fetch on first conversion + optional TTL-based refresh
11
+ - 170+ currencies (fiat + crypto) via UniRate
12
+ - Free tier, no credit card required
13
+ - Zero runtime dependencies beyond `money` (pure stdlib `net/http` + `json`)
14
+
15
+ > **Affiliation:** this gem is maintained by the UniRate team. It talks to the
16
+ > UniRate API. If you only need euro-area rates the ECB feed (e.g.
17
+ > `eu_central_bank`) may suit you better; for a broad multi-currency source on
18
+ > a free tier, UniRate is a good fit.
19
+
20
+ ## Requirements
21
+
22
+ - Ruby 3.0+
23
+ - `money` 6.13+
24
+
25
+ ## Installation
26
+
27
+ ```ruby
28
+ # Gemfile
29
+ gem "money-unirate-api"
30
+ ```
31
+
32
+ ```bash
33
+ bundle install
34
+ # or
35
+ gem install money-unirate-api
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ ```ruby
41
+ require "money"
42
+ require "money/bank/uni_rate"
43
+
44
+ Money.default_bank = Money::Bank::UniRate.new(
45
+ api_key: ENV.fetch("UNIRATE_API_KEY")
46
+ )
47
+
48
+ Money.new(100_00, "USD").exchange_to("EUR") # => #<Money fractional:9200 currency:EUR>
49
+ Money.new(50_00, "EUR").exchange_to("GBP") # cross-rate derived from the USD snapshot
50
+ ```
51
+
52
+ Get a free API key at [unirateapi.com](https://unirateapi.com).
53
+
54
+ ## Configuration
55
+
56
+ ```ruby
57
+ Money::Bank::UniRate.new(
58
+ api_key: ENV.fetch("UNIRATE_API_KEY"), # falls back to ENV["UNIRATE_API_KEY"]
59
+ base_currency: "USD", # currency the snapshot is fetched against
60
+ ttl_in_seconds: 3600, # re-fetch interval; nil (default) = fetch once and cache
61
+ timeout: 30 # per-request timeout in seconds
62
+ )
63
+ ```
64
+
65
+ | Option | Default | Description |
66
+ |---|---|---|
67
+ | `api_key` | `ENV["UNIRATE_API_KEY"]` | UniRate API key (required) |
68
+ | `base_currency` | `"USD"` | Currency the single snapshot is fetched against |
69
+ | `ttl_in_seconds` | `nil` | Seconds before an automatic re-fetch; `nil` fetches once |
70
+ | `timeout` | `30` | Per-request open/read timeout |
71
+
72
+ ## How it works
73
+
74
+ UniRate's `/api/rates` returns every rate for one base currency in a single
75
+ response. This bank fetches that snapshot and stores `base → currency` rates.
76
+ Any pair you ask for is derived from those:
77
+
78
+ ```
79
+ rate(FROM, TO) = rate(base, TO) / rate(base, FROM)
80
+ ```
81
+
82
+ Cross-rates are computed **per call**, not cached in the rate store — so when
83
+ the snapshot refreshes (via `ttl_in_seconds` or `flush_rates`), there are no
84
+ stale derived pairs left behind.
85
+
86
+ ```ruby
87
+ bank = Money::Bank::UniRate.new(api_key: "...", ttl_in_seconds: 3600)
88
+
89
+ bank.update_rates # warm the cache up front (optional; otherwise lazy)
90
+ bank.get_rate("EUR", "GBP") # derived from the base snapshot
91
+ bank.expired? # => false until the TTL elapses
92
+ bank.flush_rates # force a re-fetch on the next conversion
93
+ ```
94
+
95
+ ## Error handling
96
+
97
+ Every failure raises `Money::Bank::UniRateError`:
98
+
99
+ ```ruby
100
+ begin
101
+ Money.new(100, "USD").exchange_to("EUR", bank)
102
+ rescue Money::Bank::UniRateError => e
103
+ warn "FX lookup failed: #{e.message}"
104
+ end
105
+ ```
106
+
107
+ Mapped responses: `401` (bad/missing key), `403` (Pro-gated endpoint), `404`
108
+ (unknown currency), `429` (rate limit), network errors, and malformed responses.
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ bundle install
114
+ bundle exec rspec # ~20 WebMock-based mock tests
115
+ bundle exec rubocop
116
+ ```
117
+
118
+ <!-- unirate-ecosystem-footer:start -->
119
+ ## Other UniRate clients
120
+
121
+ UniRate ships official client libraries and framework integrations across the
122
+ ecosystem. The repos below are all maintained under the
123
+ [UniRate-API](https://github.com/UniRate-API) org.
124
+
125
+ - **Languages:** [Python](https://github.com/UniRate-API/unirate-api-python) · [Node.js / TypeScript](https://github.com/UniRate-API/unirate-api-nodejs) · [Go](https://github.com/UniRate-API/unirate-api-go) · [Rust](https://github.com/UniRate-API/unirate-api-rust) · [Java](https://github.com/UniRate-API/unirate-api-java) · [Ruby](https://github.com/UniRate-API/unirate-api-ruby) · [PHP](https://github.com/UniRate-API/unirate-api-php) · [.NET](https://github.com/UniRate-API/unirate-api-dotnet) · [Swift](https://github.com/UniRate-API/unirate-api-swift)
126
+ - **Web frameworks:** [NestJS](https://github.com/UniRate-API/nestjs-unirate) · [Django / Wagtail](https://github.com/UniRate-API/wagtail-unirate) · [FastAPI](https://github.com/UniRate-API/fastapi-unirate) · [Flask](https://github.com/UniRate-API/flask-unirate) · [React](https://github.com/UniRate-API/react-unirate) · [tRPC](https://github.com/UniRate-API/trpc-unirate)
127
+ - **Static-site generators:** [Astro](https://github.com/UniRate-API/astro-unirate) · [Eleventy](https://github.com/UniRate-API/eleventy-unirate) · [Hugo](https://github.com/UniRate-API/hugo-unirate)
128
+ - **Data / orchestration:** [Airflow](https://github.com/UniRate-API/airflow-provider-unirate) · [dbt](https://github.com/UniRate-API/dbt-unirate) · [LangChain](https://github.com/UniRate-API/langchain-unirate)
129
+ - **Workflow / no-code:** [n8n](https://github.com/UniRate-API/n8n-nodes-unirate) · [Google Sheets](https://github.com/UniRate-API/unirate-sheets) · [MCP server](https://github.com/UniRate-API/unirate-mcp)
130
+ - **Editors / tools:** [VS Code](https://github.com/UniRate-API/vscode-unirate) · [Obsidian](https://github.com/UniRate-API/obsidian-currency)
131
+ - **Specialty bridges:** [NodaMoney (.NET)](https://github.com/UniRate-API/UniRateApi.NodaMoney) · [money gem (Ruby)](https://github.com/UniRate-API/money-unirate-api)
132
+
133
+ Get a free API key at [unirateapi.com](https://unirateapi.com).
134
+ <!-- unirate-ecosystem-footer:end -->
135
+
136
+ ## License
137
+
138
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Standalone version constant. It deliberately lives in its own top-level
4
+ # module so the gemspec can read the version *without* loading the `money`
5
+ # gem: `Money` is a class there, and reopening it as a module at gem-build
6
+ # time (before `money` is installed) would raise a TypeError.
7
+ module MoneyUniRateApi
8
+ VERSION = "0.1.0"
9
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "json"
5
+ require "net/http"
6
+ require "uri"
7
+
8
+ require "money"
9
+ require_relative "uni_rate/version"
10
+
11
+ class Money
12
+ module Bank
13
+ # Raised for any UniRate-specific failure: a network problem, an auth
14
+ # error, a Pro-gated endpoint, or an unexpected response shape.
15
+ class UniRateError < StandardError; end
16
+
17
+ # A {Money::Bank} implementation backed by the UniRate API
18
+ # (https://unirateapi.com).
19
+ #
20
+ # It fetches a single-base rate snapshot from `GET /api/rates` and derives
21
+ # every cross-rate from it on demand, so one HTTP call covers all currency
22
+ # pairs. Derived cross-rates are computed per call (not cached in the
23
+ # store) so a TTL refresh can never serve a stale pair.
24
+ #
25
+ # require "money/bank/uni_rate"
26
+ #
27
+ # Money.default_bank = Money::Bank::UniRate.new(api_key: ENV["UNIRATE_API_KEY"])
28
+ # Money.new(100_00, "USD").exchange_to("EUR") # => #<Money fractional:9200 currency:EUR>
29
+ #
30
+ # Rates are fetched lazily on the first conversion and re-fetched once
31
+ # +ttl_in_seconds+ has elapsed. Leave +ttl_in_seconds+ as +nil+ to fetch
32
+ # exactly once and cache for the life of the object; call {#flush_rates}
33
+ # to force a refresh on the next conversion.
34
+ class UniRate < Money::Bank::VariableExchange
35
+ DEFAULT_BASE_URL = "https://api.unirateapi.com"
36
+ DEFAULT_BASE_CURRENCY = "USD"
37
+ DEFAULT_TIMEOUT = 30
38
+
39
+ attr_reader :base_currency, :rates_updated_at
40
+ attr_accessor :ttl_in_seconds
41
+
42
+ # @param api_key [String] UniRate API key (falls back to ENV["UNIRATE_API_KEY"])
43
+ # @param base_currency [String] currency the snapshot is fetched against
44
+ # @param ttl_in_seconds [Integer, nil] re-fetch interval; nil = fetch once
45
+ # @param base_url [String] base URL override (for testing)
46
+ # @param timeout [Integer, Float] per-request timeout in seconds
47
+ # @param store [Money::RatesStore::Memory] rate store (defaults to in-memory)
48
+ def initialize(api_key: ENV.fetch("UNIRATE_API_KEY", nil),
49
+ base_currency: DEFAULT_BASE_CURRENCY,
50
+ ttl_in_seconds: nil,
51
+ base_url: DEFAULT_BASE_URL,
52
+ timeout: DEFAULT_TIMEOUT,
53
+ store: Money::RatesStore::Memory.new,
54
+ &block)
55
+ if api_key.nil? || api_key.to_s.empty?
56
+ raise ArgumentError, "api_key is required (pass api_key: or set UNIRATE_API_KEY)"
57
+ end
58
+
59
+ @api_key = api_key
60
+ @base_currency = base_currency.to_s.upcase
61
+ @ttl_in_seconds = ttl_in_seconds
62
+ @base_url = base_url
63
+ @timeout = timeout
64
+ @rates_updated_at = nil
65
+ super(store, &block)
66
+ end
67
+
68
+ # Fetch the latest snapshot and store base->currency rates. Returns the
69
+ # raw { "EUR" => BigDecimal, ... } map. Called lazily on the first
70
+ # conversion; safe to call manually to warm the cache.
71
+ def update_rates
72
+ rates = fetch_rates
73
+ add_rate(base_currency, base_currency, 1)
74
+ rates.each { |code, value| add_rate(base_currency, code, value) }
75
+ @rates_updated_at = Time.now
76
+ rates
77
+ end
78
+
79
+ # True when rates have never been fetched, or +ttl_in_seconds+ elapsed.
80
+ def expired?
81
+ return true if rates_updated_at.nil?
82
+ return false if ttl_in_seconds.nil?
83
+
84
+ Time.now - rates_updated_at > ttl_in_seconds
85
+ end
86
+
87
+ # Exchange rate from +from+ to +to+, deriving cross-rates from the
88
+ # single-base snapshot. Auto-refreshes when {#expired?}.
89
+ #
90
+ # @return [BigDecimal]
91
+ def get_rate(from, to, _opts = {})
92
+ from_iso = Money::Currency.wrap(from).iso_code
93
+ to_iso = Money::Currency.wrap(to).iso_code
94
+
95
+ update_rates if expired?
96
+ return BigDecimal(1) if from_iso == to_iso
97
+
98
+ base_rate(to_iso) / base_rate(from_iso)
99
+ end
100
+
101
+ # Force a refresh on the next conversion (does not hit the network now).
102
+ def flush_rates
103
+ @rates_updated_at = nil
104
+ self
105
+ end
106
+
107
+ private
108
+
109
+ # base_currency -> iso rate (1 for the base itself). Raises if absent.
110
+ def base_rate(iso)
111
+ return BigDecimal(1) if iso == base_currency
112
+
113
+ rate = store.get_rate(base_currency, iso)
114
+ raise UniRateError, "No UniRate rate available for #{base_currency}->#{iso}" if rate.nil?
115
+
116
+ rate
117
+ end
118
+
119
+ def fetch_rates
120
+ body = http_get("/api/rates", "from" => base_currency)
121
+ rates = body["rates"]
122
+ raise UniRateError, 'Unexpected UniRate response: missing "rates"' unless rates.is_a?(Hash)
123
+
124
+ rates.each_with_object({}) do |(code, value), out|
125
+ out[code.to_s.upcase] = BigDecimal(value.to_s)
126
+ end
127
+ end
128
+
129
+ def http_get(path, params)
130
+ uri = URI.parse(@base_url)
131
+ uri.path = path
132
+ uri.query = URI.encode_www_form(params.merge("api_key" => @api_key))
133
+
134
+ req = Net::HTTP::Get.new(uri)
135
+ req["Accept"] = "application/json"
136
+ req["User-Agent"] = "money-unirate-api/#{MoneyUniRateApi::VERSION}"
137
+
138
+ parse(perform(uri, req))
139
+ end
140
+
141
+ def perform(uri, req)
142
+ Net::HTTP.start(uri.hostname, uri.port,
143
+ use_ssl: uri.scheme == "https",
144
+ open_timeout: @timeout,
145
+ read_timeout: @timeout) { |http| http.request(req) }
146
+ rescue StandardError => e
147
+ raise UniRateError, "Network error talking to UniRate: #{e.message}"
148
+ end
149
+
150
+ def parse(response)
151
+ status = response.code.to_i
152
+ case status
153
+ when 200..299
154
+ JSON.parse(response.body.to_s)
155
+ when 401 then raise UniRateError, "Missing or invalid UniRate API key (HTTP 401)"
156
+ when 403 then raise UniRateError, "This UniRate endpoint requires a Pro subscription (HTTP 403)"
157
+ when 404 then raise UniRateError, "Currency not found or no data available (HTTP 404)"
158
+ when 429 then raise UniRateError, "UniRate rate limit exceeded (HTTP 429)"
159
+ else
160
+ raise UniRateError, "UniRate API error (HTTP #{status}): #{response.body}"
161
+ end
162
+ rescue JSON::ParserError => e
163
+ raise UniRateError, "Failed to parse UniRate response: #{e.message}"
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Convenience entry point matching the gem name. Equivalent to
4
+ # `require "money/bank/uni_rate"`.
5
+ require_relative "money/bank/uni_rate"
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "money/bank/uni_rate/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "money-unirate-api"
9
+ spec.version = MoneyUniRateApi::VERSION
10
+ spec.authors = ["Unirate Team"]
11
+ spec.email = ["admin@unirateapi.com"]
12
+
13
+ spec.summary = "UniRate API exchange-rate bank for the money gem."
14
+ spec.description = "A Money::Bank implementation backed by the UniRate API " \
15
+ "(https://unirateapi.com) — free currency exchange rates " \
16
+ "for RubyMoney. Fetches one base snapshot and derives all " \
17
+ "cross-rates, with optional TTL-based refresh."
18
+ spec.homepage = "https://github.com/UniRate-API/money-unirate-api"
19
+ spec.license = "MIT"
20
+
21
+ spec.required_ruby_version = ">= 3.0"
22
+
23
+ spec.metadata = {
24
+ "homepage_uri" => spec.homepage,
25
+ "source_code_uri" => spec.homepage,
26
+ "bug_tracker_uri" => "#{spec.homepage}/issues",
27
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
28
+ "documentation_uri" => "https://unirateapi.com",
29
+ "rubygems_mfa_required" => "true"
30
+ }
31
+
32
+ spec.files = Dir[
33
+ "lib/**/*.rb",
34
+ "README.md",
35
+ "CHANGELOG.md",
36
+ "LICENSE",
37
+ "money-unirate-api.gemspec"
38
+ ]
39
+ spec.require_paths = ["lib"]
40
+
41
+ spec.add_dependency "money", ">= 6.13", "< 7"
42
+
43
+ spec.add_development_dependency "rake", "~> 13.0"
44
+ spec.add_development_dependency "rspec", "~> 3.12"
45
+ spec.add_development_dependency "rubocop", "~> 1.60"
46
+ spec.add_development_dependency "webmock", "~> 3.19"
47
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: money-unirate-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Unirate Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-05-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: money
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.13'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '6.13'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.12'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.12'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rubocop
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.60'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.60'
75
+ - !ruby/object:Gem::Dependency
76
+ name: webmock
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.19'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.19'
89
+ description: A Money::Bank implementation backed by the UniRate API (https://unirateapi.com)
90
+ — free currency exchange rates for RubyMoney. Fetches one base snapshot and derives
91
+ all cross-rates, with optional TTL-based refresh.
92
+ email:
93
+ - admin@unirateapi.com
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files: []
97
+ files:
98
+ - CHANGELOG.md
99
+ - LICENSE
100
+ - README.md
101
+ - lib/money-unirate-api.rb
102
+ - lib/money/bank/uni_rate.rb
103
+ - lib/money/bank/uni_rate/version.rb
104
+ - money-unirate-api.gemspec
105
+ homepage: https://github.com/UniRate-API/money-unirate-api
106
+ licenses:
107
+ - MIT
108
+ metadata:
109
+ homepage_uri: https://github.com/UniRate-API/money-unirate-api
110
+ source_code_uri: https://github.com/UniRate-API/money-unirate-api
111
+ bug_tracker_uri: https://github.com/UniRate-API/money-unirate-api/issues
112
+ changelog_uri: https://github.com/UniRate-API/money-unirate-api/blob/main/CHANGELOG.md
113
+ documentation_uri: https://unirateapi.com
114
+ rubygems_mfa_required: 'true'
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '3.0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubygems_version: 3.5.22
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: UniRate API exchange-rate bank for the money gem.
134
+ test_files: []