From 24f4421393dc69ea809f93a4cc2d82cb36f78755 Mon Sep 17 00:00:00 2001
From: Alex Ianus <aianus@coinbase.com>
Date: Fri, 6 Jun 2014 14:04:04 +0700
Subject: [PATCH] Added Money::Bank::Coinbase class to use Coinbase rates with
 the Money gem

---
 README.md                        |  33 ++
 lib/coinbase.rb                  |   1 +
 lib/coinbase/client.rb           |   6 +-
 lib/coinbase/rates.rb            |  84 ++++
 spec/fixtures/rate_response.json | 634 +++++++++++++++++++++++++++++++
 spec/rates_spec.rb               |  57 +++
 6 files changed, 814 insertions(+), 1 deletion(-)
 create mode 100644 lib/coinbase/rates.rb
 create mode 100644 spec/fixtures/rate_response.json
 create mode 100644 spec/rates_spec.rb

diff --git a/README.md b/README.md
index 572d08b..dc38373 100644
--- a/README.md
+++ b/README.md
@@ -282,6 +282,39 @@ r.oauth.access_token
 => "93865b9cae83706ae59220c013bc0afd93865b9cae83706ae59220c013bc0afd"
 ```
 
+## Exchange rates
+
+This gem also extends Money::Bank::VariableExchange with Money::Bank::Coinbase to give you access to Coinbase exchange rates.
+
+### Usage
+
+``` ruby
+cb_bank = Money::Bank::Coinbase.new
+
+# Call this before calculating exchange rates
+# This will download the rates from CB
+cb_bank.fetch_rates!
+
+# Exchange 100 USD to BTC
+# API is the same as the money gem
+cb_bank.exchange_with(Money.new(10000, :USD), :BTC) # '0.15210000'.to_money(:BTC)
+
+# Set as default bank to do arithmetic and comparisons on Money objects
+Money.default_bank = cb_bank
+money1 = Money.new(10)
+money1.bank # cb_bank
+
+Money.us_dollar(10000).exchange_to(:BTC) # '0.15210000'.to_money(:BTC)
+'1'.to_money(:BTC) > '1'.to_money(:USD) # true
+
+# Expire rates after some number of seconds (by default, rates are only updated when you call fetch_rates!)
+cb_bank.ttl_in_seconds = 3600 # Cache rates for one hour
+
+# After an hour, different rates
+cb_bank.exchange_with(Money.new(10000, :USD), :BTC) # '0.15310000'.to_money(:BTC)
+
+```
+
 ## Adding new methods
 
 You can see a [list of method calls here](https://github.com/coinbase/coinbase-ruby/blob/master/lib/coinbase/client.rb) and how they are implemented.  They are a wrapper around the [Coinbase JSON API](https://coinbase.com/api/doc).
diff --git a/lib/coinbase.rb b/lib/coinbase.rb
index 7216d95..7a185e1 100644
--- a/lib/coinbase.rb
+++ b/lib/coinbase.rb
@@ -3,3 +3,4 @@
 require "coinbase/version"
 require "coinbase/client"
 require "coinbase/oauth_client"
+require "coinbase/rates"
diff --git a/lib/coinbase/client.rb b/lib/coinbase/client.rb
index 448fb07..3f03ab6 100644
--- a/lib/coinbase/client.rb
+++ b/lib/coinbase/client.rb
@@ -12,7 +12,7 @@ class Client
 
     BASE_URI = 'https://coinbase.com/api/v1'
 
-    def initialize(api_key, api_secret, options={})
+    def initialize(api_key='', api_secret='', options={})
       @api_key = api_key
       @api_secret = api_secret
 
@@ -165,6 +165,10 @@ def spot_price currency='USD'
       r['amount'].to_money(r['currency'])
     end
 
+    def exchange_rates
+      get('/currencies/exchange_rates')
+    end
+
     # Buys
 
     def buy! qty
diff --git a/lib/coinbase/rates.rb b/lib/coinbase/rates.rb
new file mode 100644
index 0000000..a3410b6
--- /dev/null
+++ b/lib/coinbase/rates.rb
@@ -0,0 +1,84 @@
+class Money
+  module Bank
+    class Coinbase < VariableExchange
+      
+      def initialize(coinbase=nil)
+        @coinbase = coinbase || ::Coinbase::Client.new
+        super()
+      end
+
+      # @return [Integer] Returns the Time To Live (TTL) in seconds.
+      attr_reader :ttl_in_seconds
+
+      # @return [Time] Returns the time when the rates expire.
+      attr_reader :rates_expiration
+
+      ##
+      # Set the Time To Live (TTL) in seconds.
+      #
+      # @param [Integer] the seconds between an expiration and another.
+      def ttl_in_seconds=(value)
+        @ttl_in_seconds = value
+        refresh_rates_expiration! if ttl_in_seconds
+      end
+
+      ##
+      # Fetch fresh rates from Coinbase
+      def fetch_rates!(opts={})
+        fn = lambda do
+          @rates = @coinbase.exchange_rates
+          refresh_rates_expiration!
+        end
+
+        if opts[:without_mutex]
+          fn.call
+        else
+          @mutex.synchronize { fn.call }
+        end
+      end
+
+      # Refreshes all the rates if they are expired.
+      #
+      # @return [Boolean]
+      def expire_rates
+        if @ttl_in_seconds && @rates_expiration <= Time.now
+          fetch_rates!
+          true
+        else
+          false
+        end
+      end
+
+      def get_rate(from, to, opts = {})
+        expire_rates
+        super
+      end
+
+    private
+
+      ##
+      # Set the rates expiration TTL seconds from the current time.
+      #
+      # @return [Time] The next expiration.
+      def refresh_rates_expiration!
+        if @ttl_in_seconds
+          @rates_expiration = Time.now + @ttl_in_seconds 
+        end
+      end
+
+      # Return the rate hashkey for the given currencies.
+      #
+      # @param [Currency, String, Symbol] from The currency to exchange from.
+      # @param [Currency, String, Symbol] to The currency to exchange to.
+      #
+      # @return [String]
+      #
+      # @example
+      #   rate_key_for("USD", "CAD") #=> "usd_to_cad"
+      def rate_key_for(from, to)
+        "#{::Money::Currency.wrap(from).iso_code}_to_#{::Money::Currency.wrap(to).iso_code}".downcase
+      end
+
+    end
+  end
+end
diff --git a/spec/fixtures/rate_response.json b/spec/fixtures/rate_response.json
new file mode 100644
index 0000000..f9867c7
--- /dev/null
+++ b/spec/fixtures/rate_response.json
@@ -0,0 +1,634 @@
+{
+   "gbp_to_usd":"1.670983",
+   "usd_to_bwp":"8.86831",
+   "usd_to_azn":"0.7842",
+   "eur_to_usd":"1.361039",
+   "usd_to_czk":"20.18098",
+   "czk_to_btc":"7.6e-05",
+   "btc_to_mga":"1578731.7546",
+   "btc_to_djf":"116871.686671",
+   "idr_to_btc":"0.0",
+   "mnt_to_usd":"0.000551",
+   "usd_to_ngn":"162.381299",
+   "usd_to_gbp":"0.59845",
+   "irr_to_btc":"0.0",
+   "ils_to_usd":"0.287377",
+   "ars_to_usd":"0.123592",
+   "usd_to_uyu":"22.98199",
+   "uyu_to_btc":"6.7e-05",
+   "pyg_to_btc":"0.0",
+   "usd_to_yer":"214.992999",
+   "pgk_to_usd":"0.352952",
+   "xcd_to_btc":"0.000567",
+   "usd_to_fjd":"1.844267",
+   "dop_to_btc":"3.5e-05",
+   "mvr_to_usd":"0.064514",
+   "eek_to_usd":"0.085659",
+   "nzd_to_btc":"0.001289",
+   "gnf_to_btc":"0.0",
+   "usd_to_gtq":"7.781356",
+   "bmd_to_usd":"1.0",
+   "btc_to_lbp":"986372.4402",
+   "usd_to_omr":"0.385006",
+   "usd_to_sos":"944.4046",
+   "usd_to_thb":"32.67102",
+   "srd_to_btc":"0.000463",
+   "btc_to_all":"67109.732424",
+   "usd_to_vnd":"21205.233333",
+   "htg_to_usd":"0.022186",
+   "btc_to_bmd":"652.68",
+   "kpw_to_usd":"0.0",
+   "lyd_to_usd":"0.814776",
+   "tzs_to_btc":"1.0e-06",
+   "lak_to_usd":"0.000124",
+   "usd_to_idr":"11846.65",
+   "btc_to_bzd":"1303.81837",
+   "usd_to_gel":"1.77007",
+   "usd_to_kzt":"183.4134",
+   "uah_to_usd":"0.08389",
+   "usd_to_lkr":"130.345499",
+   "btc_to_zwl":"210394.665316",
+   "php_to_btc":"3.5e-05",
+   "ron_to_usd":"0.309147",
+   "btc_to_kyd":"539.569251",
+   "btc_to_cad":"713.207585",
+   "hkd_to_btc":"0.000198",
+   "usd_to_btc":"0.001532",
+   "nok_to_btc":"0.000255",
+   "top_to_usd":"0.539266",
+   "btc_to_xpf":"57257.807824",
+   "usd_to_kgs":"52.13045",
+   "usd_to_bnd":"1.256882",
+   "dzd_to_usd":"0.012628",
+   "vef_to_btc":"0.000244",
+   "usd_to_ars":"8.091152",
+   "syp_to_btc":"1.0e-05",
+   "ngn_to_btc":"9.0e-06",
+   "bgn_to_btc":"0.001066",
+   "lak_to_btc":"0.0",
+   "btc_to_cve":"52650.442454",
+   "mga_to_btc":"1.0e-06",
+   "idr_to_usd":"8.4e-05",
+   "btc_to_htg":"29418.369649",
+   "btc_to_ils":"2271.161925",
+   "mad_to_btc":"0.000186",
+   "usd_to_hnl":"20.67103",
+   "svc_to_btc":"0.000175",
+   "btc_to_ang":"1166.391374",
+   "hkd_to_usd":"0.128986",
+   "usd_to_nad":"10.73715",
+   "szl_to_btc":"0.000143",
+   "etb_to_usd":"0.051096",
+   "pkr_to_btc":"1.6e-05",
+   "usd_to_khr":"4049.893333",
+   "gnf_to_usd":"0.000142",
+   "btc_to_myr":"2109.683671",
+   "btc_to_top":"1210.310864",
+   "nad_to_btc":"0.000143",
+   "huf_to_usd":"0.004458",
+   "mkd_to_btc":"3.4e-05",
+   "lkr_to_btc":"1.2e-05",
+   "svc_to_usd":"0.114343",
+   "btc_to_lyd":"801.054397",
+   "usd_to_bam":"1.437821",
+   "usd_to_svc":"8.745614",
+   "usd_to_tzs":"1672.43",
+   "gbp_to_btc":"0.00256",
+   "btc_to_mop":"5210.920756",
+   "btc_to_jod":"462.302382",
+   "clp_to_usd":"0.001817",
+   "cad_to_usd":"0.915133",
+   "cny_to_btc":"0.000246",
+   "jod_to_btc":"0.002163",
+   "syp_to_usd":"0.006703",
+   "lvl_to_btc":"0.002969",
+   "btc_to_cny":"4070.100732",
+   "shp_to_btc":"0.00256",
+   "vef_to_usd":"0.158944",
+   "php_to_usd":"0.022789",
+   "mro_to_usd":"0.003403",
+   "tjs_to_btc":"0.000312",
+   "fkp_to_btc":"0.00256",
+   "bdt_to_usd":"0.012913",
+   "mkd_to_usd":"0.022094",
+   "lrd_to_usd":"0.011791",
+   "usd_to_kes":"87.49508",
+   "btc_to_ars":"5280.933087",
+   "usd_to_chf":"0.897733",
+   "usd_to_lak":"8060.718333",
+   "usd_to_bzd":"1.997638",
+   "usd_to_nzd":"1.188226",
+   "mvr_to_btc":"9.9e-05",
+   "usd_to_wst":"2.307868",
+   "nok_to_usd":"0.166534",
+   "tjs_to_usd":"0.203712",
+   "bob_to_usd":"0.144776",
+   "btc_to_cup":"652.582098",
+   "afn_to_btc":"2.7e-05",
+   "vnd_to_btc":"0.0",
+   "bnd_to_usd":"0.79562",
+   "byr_to_btc":"0.0",
+   "aed_to_btc":"0.000417",
+   "hnl_to_btc":"7.4e-05",
+   "mmk_to_usd":"0.001033",
+   "zmk_to_btc":"0.0",
+   "usd_to_myr":"3.23234",
+   "usd_to_ils":"3.479748",
+   "usd_to_mdl":"13.84405",
+   "djf_to_usd":"0.005585",
+   "mnt_to_btc":"1.0e-06",
+   "usd_to_sbd":"7.327961",
+   "lbp_to_usd":"0.000662",
+   "aoa_to_btc":"1.6e-05",
+   "btc_to_vnd":"13840231.691782",
+   "btc_to_lak":"5261069.641582",
+   "dkk_to_usd":"0.182357",
+   "inr_to_btc":"2.6e-05",
+   "lrd_to_btc":"1.8e-05",
+   "pab_to_btc":"0.001532",
+   "usd_to_mga":"2418.845",
+   "btc_to_awg":"1163.4021",
+   "usd_to_try":"2.119397",
+   "usd_to_qar":"3.640826",
+   "usd_to_lvl":"0.516075",
+   "bmd_to_btc":"0.001532",
+   "vuv_to_btc":"1.6e-05",
+   "usd_to_pen":"2.77272",
+   "usd_to_zmk":"5252.024745",
+   "usd_to_ttd":"6.430258",
+   "mwk_to_btc":"4.0e-06",
+   "btc_to_scr":"7956.35195",
+   "egp_to_btc":"0.000214",
+   "bam_to_btc":"0.001066",
+   "usd_to_kmf":"361.468839",
+   "btc_to_aed":"2397.253827",
+   "usd_to_shp":"0.59845",
+   "bbd_to_btc":"0.0",
+   "kes_to_usd":"0.011429",
+   "jpy_to_btc":"1.5e-05",
+   "wst_to_usd":"0.4333",
+   "gyd_to_usd":"0.004871",
+   "kgs_to_btc":"2.9e-05",
+   "btc_to_eek":"7619.500539",
+   "usd_to_php":"43.88156",
+   "sdg_to_usd":"0.175555",
+   "usd_to_isk":"113.362",
+   "scr_to_btc":"0.000126",
+   "cup_to_usd":"1.00015",
+   "std_to_btc":"0.0",
+   "usd_to_gip":"0.59845",
+   "usd_to_aud":"1.078471",
+   "usd_to_dzd":"79.18756",
+   "usd_to_xaf":"482.386502",
+   "usd_to_mro":"293.83072",
+   "clp_to_btc":"3.0e-06",
+   "bbd_to_usd":"0.0",
+   "mdl_to_btc":"0.000111",
+   "btc_to_pyg":"2883168.216969",
+   "xof_to_btc":"3.0e-06",
+   "kzt_to_btc":"8.0e-06",
+   "usd_to_irr":"25562",
+   "usd_to_vuv":"95.214999",
+   "bif_to_btc":"1.0e-06",
+   "btc_to_gtq":"5078.735434",
+   "usd_to_bif":"1555.111667",
+   "btc_to_xof":"314915.893289",
+   "btc_to_ghs":"1975.522034",
+   "btc_to_kes":"57106.288814",
+   "usd_to_bbd":"2",
+   "usd_to_afn":"57.1605",
+   "usd_to_sdg":"5.696225",
+   "uzs_to_usd":"0.000434",
+   "btc_to_dzd":"51684.136661",
+   "btc_to_uyu":"14999.885233",
+   "usd_to_mnt":"1813.833333",
+   "egp_to_usd":"0.139885",
+   "btc_to_nio":"16863.815304",
+   "usd_to_mur":"30.35299",
+   "usd_to_fkp":"0.59845",
+   "twd_to_usd":"0.033285",
+   "btc_to_bif":"1014990.282818",
+   "nio_to_btc":"5.9e-05",
+   "bhd_to_usd":"2.652471",
+   "aoa_to_usd":"0.010226",
+   "tmm_to_usd":"0.350865",
+   "btc_to_bhd":"246.064929",
+   "kyd_to_btc":"0.001853",
+   "usd_to_mzn":"31.53765",
+   "jmd_to_btc":"1.4e-05",
+   "lbp_to_btc":"1.0e-06",
+   "btc_to_ngn":"105983.026231",
+   "sek_to_btc":"0.000229",
+   "try_to_usd":"0.471832",
+   "btc_to_mkd":"29541.236659",
+   "btc_to_gbp":"390.596346",
+   "aud_to_btc":"0.001421",
+   "xpf_to_usd":"0.011399",
+   "bzd_to_btc":"0.000767",
+   "usd_to_cdf":"923.276333",
+   "btc_to_bdt":"50546.110759",
+   "cup_to_btc":"0.001532",
+   "usd_to_lsl":"10.73625",
+   "btc_to_tnd":"1067.324993",
+   "btc_to_pln":"1990.765375",
+   "usd_to_btn":"59.302038",
+   "zar_to_btc":"0.000142",
+   "bob_to_btc":"0.000222",
+   "usd_to_top":"1.854371",
+   "amd_to_btc":"4.0e-06",
+   "btc_to_syp":"97376.330223",
+   "szl_to_usd":"0.093148",
+   "gip_to_usd":"1.670983",
+   "usd_to_mvr":"15.50046",
+   "thb_to_usd":"0.030608",
+   "bgn_to_usd":"0.695934",
+   "btc_to_gnf":"4592247.777818",
+   "rwf_to_btc":"2.0e-06",
+   "cny_to_usd":"0.16036",
+   "btc_to_mzn":"20583.993402",
+   "btc_to_bob":"4508.220667",
+   "btc_to_etb":"12773.5807",
+   "ern_to_usd":"0.066878",
+   "mmk_to_btc":"2.0e-06",
+   "btc_to_uzs":"1505049.607023",
+   "usd_to_kwd":"0.282124",
+   "btc_to_twd":"19608.850321",
+   "usd_to_clp":"550.273902",
+   "ghs_to_usd":"0.330384",
+   "brl_to_btc":"0.000672",
+   "btc_to_try":"1383.288034",
+   "usd_to_dkk":"5.48374",
+   "myr_to_btc":"0.000474",
+   "btc_to_svc":"5708.087346",
+   "kmf_to_btc":"4.0e-06",
+   "mxn_to_btc":"0.000118",
+   "nio_to_usd":"0.038703",
+   "uah_to_btc":"0.000129",
+   "ttd_to_btc":"0.000238",
+   "usd_to_sgd":"1.257403",
+   "usd_to_hrk":"5.561099",
+   "btc_to_thb":"21323.721334",
+   "mwk_to_usd":"0.002535",
+   "btc_to_aud":"703.896452",
+   "btc_to_vuv":"62144.925547",
+   "btc_to_php":"28640.616581",
+   "pyg_to_usd":"0.000226",
+   "scr_to_usd":"0.082033",
+   "btc_to_kwd":"184.136692",
+   "ugx_to_usd":"0.000391",
+   "btc_to_gmd":"25848.26879",
+   "zwl_to_btc":"5.0e-06",
+   "btc_to_zar":"7021.701137",
+   "btc_to_hkd":"5060.077271",
+   "btc_to_afn":"37307.51514",
+   "btc_to_fkp":"390.596346",
+   "xaf_to_btc":"3.0e-06",
+   "usd_to_nok":"6.004781",
+   "btc_to_kzt":"119710.257912",
+   "btc_to_btn":"38705.254162",
+   "amd_to_usd":"0.002401",
+   "usd_to_syp":"149.194598",
+   "usd_to_jpy":"102.613",
+   "usd_to_sek":"6.67794",
+   "btc_to_clp":"359152.770357",
+   "sgd_to_usd":"0.79529",
+   "btc_to_qar":"2376.294314",
+   "czk_to_usd":"0.049552",
+   "usd_to_aoa":"97.787626",
+   "krw_to_usd":"0.000975",
+   "sar_to_usd":"0.266625",
+   "hnl_to_usd":"0.048377",
+   "bwp_to_usd":"0.112761",
+   "usd_to_brl":"2.278873",
+   "btc_to_bbd":"1305.36",
+   "ngn_to_usd":"0.006158",
+   "azn_to_usd":"1.275185",
+   "btc_to_mnt":"1183852.739782",
+   "usd_to_rub":"35.1272",
+   "btc_to_lvl":"336.831831",
+   "usd_to_ron":"3.234704",
+   "btc_to_azn":"511.831656",
+   "sll_to_usd":"0.0",
+   "usd_to_dop":"43.18491",
+   "pab_to_usd":"1.0",
+   "pkr_to_usd":"0.010143",
+   "usd_to_scr":"12.19028",
+   "usd_to_huf":"224.314201",
+   "bdt_to_btc":"2.0e-05",
+   "btc_to_crc":"360989.929453",
+   "std_to_usd":"5.6e-05",
+   "mzn_to_usd":"0.031708",
+   "mzn_to_btc":"4.9e-05",
+   "nzd_to_usd":"0.841591",
+   "btc_to_npr":"61959.813098",
+   "btc_to_bwp":"5788.168571",
+   "kwd_to_usd":"3.544541",
+   "omr_to_usd":"2.597362",
+   "btc_to_rwf":"443653.186183",
+   "kgs_to_usd":"0.019183",
+   "afn_to_usd":"0.017495",
+   "btc_to_egp":"4665.846803",
+   "aud_to_usd":"0.927239",
+   "usd_to_mwk":"394.478",
+   "usd_to_cny":"6.235982",
+   "rwf_to_usd":"0.001471",
+   "usd_to_zwl":"322.355006",
+   "btc_to_cdf":"602603.997022",
+   "usd_to_tjs":"4.9089",
+   "usd_to_all":"102.8218",
+   "bhd_to_btc":"0.004064",
+   "kpw_to_btc":"0.0",
+   "bam_to_usd":"0.695497",
+   "btc_to_szl":"7006.904881",
+   "usd_to_pyg":"4417.430007",
+   "gel_to_btc":"0.000866",
+   "xcd_to_usd":"0.370154",
+   "usd_to_lrd":"84.80715",
+   "sos_to_usd":"0.001059",
+   "usd_to_uzs":"2305.953311",
+   "btc_to_fjd":"1203.716186",
+   "btc_to_iqd":"768557.933073",
+   "usd_to_bsd":"1",
+   "cve_to_btc":"1.9e-05",
+   "rub_to_btc":"4.4e-05",
+   "usd_to_rsd":"84.94406",
+   "sar_to_btc":"0.000408",
+   "eek_to_btc":"0.000131",
+   "btc_to_ugx":"1667559.327218",
+   "awg_to_btc":"0.000859",
+   "btc_to_mwk":"257467.90104",
+   "huf_to_btc":"7.0e-06",
+   "ang_to_usd":"0.559572",
+   "usd_to_djf":"179.064299",
+   "usd_to_cad":"1.092737",
+   "btc_to_brl":"1487.37483",
+   "usd_to_ern":"14.952575",
+   "iqd_to_btc":"1.0e-06",
+   "btc_to_pgk":"1849.20561",
+   "usd_to_xcd":"2.70158",
+   "btn_to_btc":"2.6e-05",
+   "myr_to_usd":"0.309373",
+   "lsl_to_usd":"0.093142",
+   "crc_to_usd":"0.001808",
+   "usd_to_egp":"7.148751",
+   "btc_to_rsd":"55441.289081",
+   "gel_to_usd":"0.564949",
+   "jmd_to_usd":"0.009011",
+   "btc_to_ltl":"1655.651398",
+   "lyd_to_btc":"0.001248",
+   "nad_to_usd":"0.093135",
+   "usd_to_kyd":"0.826698",
+   "btc_to_bsd":"652.68",
+   "ltl_to_btc":"0.000604",
+   "usd_to_srd":"3.308533",
+   "usd_to_awg":"1.7825",
+   "usd_to_szl":"10.73559",
+   "chf_to_btc":"0.001707",
+   "zwl_to_usd":"0.003102",
+   "sll_to_btc":"0.0",
+   "usd_to_cop":"1898.56665",
+   "usd_to_tmm":"2.8501",
+   "btc_to_tjs":"3203.940852",
+   "sbd_to_btc":"0.000209",
+   "ern_to_btc":"0.000102",
+   "btc_to_bam":"938.43701",
+   "btc_to_gip":"390.596346",
+   "sbd_to_usd":"0.136464",
+   "mad_to_usd":"0.121316",
+   "btc_to_ern":"9759.246651",
+   "usd_to_ltl":"2.536697",
+   "qar_to_usd":"0.274663",
+   "usd_to_kpw":"900",
+   "usd_to_crc":"553.088695",
+   "usd_to_xpf":"87.727229",
+   "yer_to_btc":"7.0e-06",
+   "chf_to_usd":"1.113917",
+   "jpy_to_usd":"0.009745",
+   "ars_to_btc":"0.000189",
+   "usd_to_pgk":"2.83325",
+   "gyd_to_btc":"7.0e-06",
+   "aed_to_usd":"0.272262",
+   "usd_to_mop":"7.983883",
+   "usd_to_aed":"3.672939",
+   "qar_to_btc":"0.000421",
+   "btc_to_sll":"2822841.0",
+   "btc_to_dkk":"3579.127423",
+   "gtq_to_usd":"0.128512",
+   "sos_to_btc":"2.0e-06",
+   "dop_to_usd":"0.023156",
+   "awg_to_usd":"0.56101",
+   "btc_to_gel":"1155.289288",
+   "btc_to_lkr":"85073.900287",
+   "btc_to_lrd":"55351.930662",
+   "khr_to_usd":"0.000247",
+   "btc_to_hrk":"3629.618095",
+   "usd_to_etb":"19.57097",
+   "btc_to_sos":"616393.994328",
+   "btc_to_eur":"479.545534",
+   "usd_to_krw":"1025.500003",
+   "btc_to_dop":"28185.927059",
+   "usd_to_bob":"6.907245",
+   "btc_to_kgs":"34024.502106",
+   "cve_to_usd":"0.012396",
+   "fjd_to_usd":"0.542221",
+   "btc_to_jpy":"66973.45284",
+   "btc_to_omr":"251.285716",
+   "usd_to_gyd":"205.278749",
+   "pen_to_btc":"0.000553",
+   "btc_to_jmd":"72430.379131",
+   "npr_to_usd":"0.010534",
+   "usd_to_tnd":"1.635296",
+   "usd_to_twd":"30.04359",
+   "btc_to_nad":"7007.923062",
+   "azn_to_btc":"0.001954",
+   "btc_to_sgd":"820.68179",
+   "twd_to_btc":"5.1e-05",
+   "btc_to_sek":"4358.557879",
+   "inr_to_usd":"0.016881",
+   "crc_to_btc":"3.0e-06",
+   "usd_to_mxn":"12.94185",
+   "xaf_to_usd":"0.002073",
+   "btc_to_idr":"7732071.522",
+   "btc_to_hnl":"13491.56786",
+   "eur_to_btc":"0.002085",
+   "btc_to_sbd":"4782.813585",
+   "hrk_to_usd":"0.179821",
+   "pln_to_btc":"0.000502",
+   "gtq_to_btc":"0.000197",
+   "zmk_to_usd":"0.00019",
+   "thb_to_btc":"4.7e-05",
+   "btc_to_mvr":"10116.840233",
+   "ghs_to_btc":"0.000506",
+   "usd_to_gmd":"39.60328",
+   "usd_to_mkd":"45.26144",
+   "usd_to_eur":"0.734733",
+   "cop_to_btc":"1.0e-06",
+   "btc_to_xcd":"1763.267234",
+   "sdg_to_btc":"0.000269",
+   "btc_to_nzd":"775.531346",
+   "usd_to_uah":"11.92032",
+   "rsd_to_btc":"1.8e-05",
+   "cdf_to_btc":"2.0e-06",
+   "btn_to_usd":"0.016863",
+   "krw_to_btc":"1.0e-06",
+   "tzs_to_usd":"0.000598",
+   "pgk_to_btc":"0.000541",
+   "btc_to_byr":"6608689.584218",
+   "bsd_to_usd":"1.0",
+   "irr_to_usd":"0.0",
+   "uyu_to_usd":"0.043512",
+   "mop_to_btc":"0.000192",
+   "usd_to_ghs":"3.026785",
+   "btc_to_khr":"2643284.380582",
+   "kyd_to_usd":"1.209632",
+   "btc_to_zmk":"3427891.510567",
+   "cop_to_usd":"0.000527",
+   "btc_to_mad":"5379.989026",
+   "try_to_btc":"0.000723",
+   "xpf_to_btc":"1.7e-05",
+   "kzt_to_usd":"0.005452",
+   "usd_to_bhd":"0.377007",
+   "top_to_btc":"0.000826",
+   "ugx_to_btc":"1.0e-06",
+   "usd_to_mmk":"968.3123",
+   "usd_to_jod":"0.708314",
+   "btc_to_vef":"4106.358411",
+   "gip_to_btc":"0.00256",
+   "btc_to_mmk":"631998.071964",
+   "usd_to_xof":"482.496619",
+   "wst_to_btc":"0.000664",
+   "mdl_to_usd":"0.072233",
+   "ltl_to_usd":"0.394213",
+   "bnd_to_btc":"0.001219",
+   "usd_to_sll":"4325",
+   "usd_to_nio":"25.8378",
+   "btc_to_mro":"191777.43433",
+   "srd_to_usd":"0.302249",
+   "usd_to_lbp":"1511.265",
+   "btc_to_pkr":"64345.30041",
+   "rsd_to_usd":"0.011772",
+   "sgd_to_btc":"0.001218",
+   "yer_to_usd":"0.004651",
+   "lvl_to_usd":"1.937703",
+   "usd_to_ang":"1.78708",
+   "usd_to_sar":"3.750587",
+   "usd_to_iqd":"1177.541725",
+   "btc_to_kmf":"235923.481839",
+   "usd_to_bmd":"1",
+   "kes_to_btc":"1.8e-05",
+   "btc_to_irr":"16683806.16",
+   "btc_to_aoa":"63824.027738",
+   "btc_to_shp":"390.596346",
+   "btc_to_sdg":"3717.812133",
+   "kmf_to_usd":"0.002766",
+   "btc_to_krw":"669323.341958",
+   "btc_to_srd":"2159.413318",
+   "usd_to_bdt":"77.44394",
+   "usd_to_zar":"10.75826",
+   "bwp_to_btc":"0.000173",
+   "byr_to_usd":"9.9e-05",
+   "vnd_to_usd":"4.7e-05",
+   "usd_to_cve":"80.66808",
+   "btc_to_btc":"0.999906",
+   "btc_to_pen":"1809.69889",
+   "usd_to_hkd":"7.752769",
+   "isk_to_btc":"1.4e-05",
+   "pen_to_usd":"0.360657",
+   "btc_to_kpw":"587412.0",
+   "btc_to_tmm":"0.0",
+   "bzd_to_usd":"0.500591",
+   "npr_to_btc":"1.6e-05",
+   "btc_to_chf":"585.932374",
+   "btc_to_bgn":"937.846988",
+   "btc_to_uah":"7780.154458",
+   "pln_to_usd":"0.327854",
+   "btc_to_inr":"38664.038725",
+   "htg_to_btc":"3.4e-05",
+   "btc_to_wst":"1506.299286",
+   "btc_to_ron":"2111.226607",
+   "xof_to_usd":"0.002073",
+   "btc_to_mur":"19810.789513",
+   "usd_to_rwf":"679.74074",
+   "usd_to_pab":"1",
+   "btc_to_mxn":"8446.886658",
+   "all_to_btc":"1.5e-05",
+   "dkk_to_btc":"0.000279",
+   "usd_to_ugx":"2554.941667",
+   "tnd_to_usd":"0.61151",
+   "usd_to_lyd":"1.227331",
+   "usd_to_pkr":"98.586291",
+   "omr_to_btc":"0.003979",
+   "mga_to_usd":"0.000413",
+   "mur_to_btc":"5.0e-05",
+   "usd_to_amd":"416.522999",
+   "jod_to_usd":"1.411803",
+   "tnd_to_btc":"0.000937",
+   "shp_to_usd":"1.670983",
+   "vuv_to_usd":"0.010503",
+   "khr_to_btc":"0.0",
+   "usd_to_htg":"45.07319",
+   "iqd_to_usd":"0.000849",
+   "btc_to_sar":"2447.933123",
+   "mur_to_usd":"0.032946",
+   "lsl_to_btc":"0.000143",
+   "usd_to_jmd":"110.973799",
+   "ils_to_btc":"0.00044",
+   "zar_to_usd":"0.092952",
+   "usd_to_pln":"3.05014",
+   "bsd_to_btc":"0.001532",
+   "btc_to_rub":"22926.820896",
+   "usd_to_mad":"8.24292",
+   "usd_to_inr":"59.23889",
+   "btc_to_czk":"13171.722026",
+   "isk_to_usd":"0.008821",
+   "rub_to_usd":"0.028468",
+   "usd_to_usd":"1.0",
+   "fkp_to_usd":"1.670983",
+   "all_to_usd":"0.009726",
+   "mop_to_usd":"0.125252",
+   "usd_to_cup":"0.99985",
+   "mro_to_btc":"5.0e-06",
+   "tmm_to_btc":"0.0",
+   "fjd_to_btc":"0.000831",
+   "usd_to_gnf":"7035.986667",
+   "btc_to_huf":"146405.392709",
+   "usd_to_byr":"10125.466667",
+   "usd_to_bgn":"1.436917",
+   "ang_to_btc":"0.000857",
+   "gmd_to_usd":"0.02525",
+   "bif_to_usd":"0.000643",
+   "etb_to_btc":"7.8e-05",
+   "usd_to_std":"18006.983333",
+   "btc_to_std":"11752797.881782",
+   "btc_to_ttd":"4196.900791",
+   "ron_to_btc":"0.000474",
+   "gmd_to_btc":"3.9e-05",
+   "kwd_to_btc":"0.00543",
+   "btc_to_amd":"271856.230987",
+   "btc_to_gyd":"133981.333897",
+   "btc_to_nok":"3919.200463",
+   "btc_to_yer":"140321.630587",
+   "cdf_to_usd":"0.001083",
+   "btc_to_usd":"652.68",
+   "hrk_to_btc":"0.000275",
+   "btc_to_xaf":"314844.022125",
+   "btc_to_bnd":"820.341744",
+   "btc_to_lsl":"7007.33565",
+   "usd_to_eek":"11.674175",
+   "brl_to_usd":"0.438813",
+   "lkr_to_usd":"0.007672",
+   "btc_to_pab":"652.68",
+   "ttd_to_usd":"0.155515",
+   "btc_to_cop":"1239156.481122",
+   "btc_to_isk":"73989.11016",
+   "uzs_to_btc":"1.0e-06",
+   "mxn_to_usd":"0.077269",
+   "btc_to_mdl":"9035.734554",
+   "djf_to_btc":"9.0e-06",
+   "usd_to_npr":"94.93138",
+   "btc_to_tzs":"1091561.6124",
+   "usd_to_vef":"6.291534",
+   "cad_to_btc":"0.001402",
+   "dzd_to_btc":"1.9e-05",
+   "sek_to_usd":"0.149747"
+}
\ No newline at end of file
diff --git a/spec/rates_spec.rb b/spec/rates_spec.rb
new file mode 100644
index 0000000..d8d7a6f
--- /dev/null
+++ b/spec/rates_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+require 'fakeweb'
+require 'coinbase'
+
+describe Money::Bank::Coinbase do
+  BASE_URI = 'http://fake.com/api/v1' # switching to http (instead of https) seems to help FakeWeb
+
+  before :all do
+    @coinbase = Coinbase::Client.new '', '', {base_uri: BASE_URI}
+    FakeWeb.allow_net_connect = false
+    @rates_response = JSON.parse(File.read(File.dirname(__FILE__) + '/fixtures/rate_response.json'))
+    fake :get, '/currencies/exchange_rates', @rates_response
+    @bank = Money::Bank::Coinbase.new @coinbase
+    @bank.fetch_rates!
+    Money.default_bank = @bank
+  end
+
+  # Auth and Errors
+
+  it "exposes rates directly" do
+    @bank.get_rate(:usd, :btc).should == '0.001532'
+  end
+
+  it "lets you convert currencies" do
+    Money.us_dollar(100).exchange_to(:BTC).should == '0.001532'.to_money(:BTC)
+  end
+
+  it "lets you convert currencies with tiny rates" do
+    '1000000'.to_money(:MRO).exchange_to(:BTC).should == '5'.to_money(:BTC)
+  end
+
+  it "lets you compare currencies" do
+    '1'.to_money(:BTC).should > '1'.to_money(:USD)
+  end
+
+  it "throws an exception for unknown currencies" do
+    expect{'1'.to_money(:BTC).exchange_to(:WTF)}.to raise_error(Money::Currency::UnknownCurrency)
+  end
+
+  it "throws an exception for missing rates" do
+    expect{'1'.to_money(:EUR).exchange_to(:RON)}.to raise_error(Money::Bank::UnknownRate)
+  end
+
+  it "automatically refreshes expired rates" do
+    '1'.to_money(:BTC).exchange_to(:RON).should == '2111.22'.to_money(:RON)
+    @rates_response['btc_to_ron'] = '2000'
+    fake :get, '/currencies/exchange_rates', @rates_response
+    @bank.ttl_in_seconds = 0.2
+    sleep 0.3
+    '1'.to_money(:BTC).exchange_to(:RON).should == '2000'.to_money(:RON)
+  end
+
+  def fake method, path, body
+    FakeWeb.register_uri(method, "#{BASE_URI}#{path}", body: body.to_json)
+  end
+
+end