coinbase 0.0.1 → 4.0.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.

Potentially problematic release.


This version of coinbase might be problematic. Click here for more details.

Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +67 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +578 -0
  7. data/Rakefile +7 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +7 -0
  10. data/coinbase.gemspec +30 -0
  11. data/lib/coinbase/wallet/adapters/em_http.rb +64 -0
  12. data/lib/coinbase/wallet/adapters/net_http.rb +62 -0
  13. data/lib/coinbase/wallet/api_client.rb +659 -0
  14. data/lib/coinbase/wallet/api_errors.rb +112 -0
  15. data/lib/coinbase/wallet/api_response.rb +37 -0
  16. data/lib/coinbase/wallet/client.rb +98 -0
  17. data/lib/coinbase/wallet/models/account.rb +187 -0
  18. data/lib/coinbase/wallet/models/api_object.rb +46 -0
  19. data/lib/coinbase/wallet/models/checkout.rb +19 -0
  20. data/lib/coinbase/wallet/models/order.rb +12 -0
  21. data/lib/coinbase/wallet/models/transaction.rb +21 -0
  22. data/lib/coinbase/wallet/models/transfer.rb +13 -0
  23. data/lib/coinbase/wallet/models/user.rb +15 -0
  24. data/lib/coinbase/wallet/version.rb +5 -0
  25. data/lib/coinbase/wallet.rb +23 -156
  26. data/spec/account_spec.rb +193 -0
  27. data/spec/clients/client_spec.rb +34 -0
  28. data/spec/clients/oauth_client_spec.rb +56 -0
  29. data/spec/endpoints_spec.rb +346 -0
  30. data/spec/error_spec.rb +130 -0
  31. data/spec/models/api_object_spec.rb +63 -0
  32. data/spec/models/checkout_spec.rb +52 -0
  33. data/spec/models/current_user_spec.rb +29 -0
  34. data/spec/models/order_spec.rb +52 -0
  35. data/spec/models/request_spec.rb +47 -0
  36. data/spec/models/transfer_spec.rb +64 -0
  37. data/spec/models/user_spec.rb +24 -0
  38. data/spec/spec_helper.rb +13 -0
  39. metadata +72 -82
  40. data/lib/coinbase/address.rb +0 -127
  41. data/lib/coinbase/asset.rb +0 -20
  42. data/lib/coinbase/balance_map.rb +0 -48
  43. data/lib/coinbase/constants.rb +0 -38
  44. data/lib/coinbase/network.rb +0 -55
  45. data/lib/coinbase/transfer.rb +0 -153
  46. data/lib/coinbase.rb +0 -18
@@ -1,160 +1,27 @@
1
- # frozen_string_literal: true
2
-
3
- require 'jimson'
4
- require 'money-tree'
5
- require 'securerandom'
1
+ require "coinbase/wallet/version"
2
+
3
+ require "base64"
4
+ require "bigdecimal"
5
+ require "json"
6
+ require "uri"
7
+ require "net/https"
8
+ require "em-http"
9
+
10
+ require "coinbase/wallet/api_errors"
11
+ require "coinbase/wallet/api_response"
12
+ require "coinbase/wallet/api_client"
13
+ require "coinbase/wallet/adapters/net_http.rb"
14
+ require "coinbase/wallet/adapters/em_http.rb"
15
+ require "coinbase/wallet/models/api_object"
16
+ require "coinbase/wallet/models/account"
17
+ require "coinbase/wallet/models/user"
18
+ require "coinbase/wallet/models/transaction"
19
+ require "coinbase/wallet/models/transfer"
20
+ require "coinbase/wallet/models/order"
21
+ require "coinbase/wallet/models/checkout"
22
+ require "coinbase/wallet/client"
6
23
 
7
24
  module Coinbase
8
- # A representation of a Wallet. Wallets come with a single default Address, but can expand to have a set of Addresses,
9
- # each of which can hold a balance of one or more Assets. Wallets can create new Addresses, list their addresses,
10
- # list their balances, and transfer Assets to other Addresses.
11
- class Wallet
12
- attr_reader :wallet_id, :network_id
13
-
14
- # Returns a new Wallet object.
15
- # @param seed [Integer] (Optional) The seed to use for the Wallet. Expects a 32-byte hexadecimal. If not provided,
16
- # a new seed will be generated.
17
- # @param address_count [Integer] (Optional) The number of addresses to generate for the Wallet. If not provided,
18
- # a single address will be generated.
19
- # @param client [Jimson::Client] (Optional) The JSON RPC client to use for interacting with the Network
20
- def initialize(seed: nil, address_count: 1, client: Jimson::Client.new(ENV.fetch('BASE_SEPOLIA_RPC_URL', nil)))
21
- raise ArgumentError, 'Seed must be 32 bytes' if !seed.nil? && seed.length != 64
22
- raise ArgumentError, 'Address count must be positive' if address_count < 1
23
-
24
- @master = seed.nil? ? MoneyTree::Master.new : MoneyTree::Master.new(seed_hex: seed)
25
-
26
- @wallet_id = SecureRandom.uuid
27
- # TODO: Make Network an argument to the constructor.
28
- @network_id = :base_sepolia
29
- @addresses = []
30
-
31
- # TODO: Adjust derivation path prefix based on network protocol.
32
- @address_path_prefix = "m/44'/60'/0'/0"
33
- @address_index = 0
34
-
35
- @client = client
36
-
37
- address_count.times { create_address }
38
- end
39
-
40
- # Creates a new Address in the Wallet.
41
- # @return [Address] The new Address
42
- def create_address
43
- # TODO: Register with server.
44
- path = "#{@address_path_prefix}/#{@address_index}"
45
- private_key = @master.node_for_path(path).private_key.to_hex
46
- key = Eth::Key.new(priv: private_key)
47
- address = Address.new(@network_id, key.address.address, @wallet_id, key, client: @client)
48
- @addresses << address
49
- @address_index += 1
50
- address
51
- end
52
-
53
- # Returns the default address of the Wallet.
54
- # @return [Address] The default address
55
- def default_address
56
- @addresses.first
57
- end
58
-
59
- # Returns the Address with the given ID.
60
- # @param address_id [String] The ID of the Address to retrieve
61
- # @return [Address] The Address
62
- def get_address(address_id)
63
- @addresses.find { |address| address.address_id == address_id }
64
- end
65
-
66
- # Returns the list of Addresses in the Wallet.
67
- # @return [Array<Address>] The list of Addresses
68
- def list_addresses
69
- # TODO: Register with server.
70
- @addresses
71
- end
72
-
73
- # Returns the list of balances of this Wallet. Balances are aggregated across all Addresses in the Wallet.
74
- # @return [BalanceMap] The list of balances. The key is the Asset ID, and the value is the balance.
75
- def list_balances
76
- balance_map = BalanceMap.new
77
-
78
- @addresses.each do |address|
79
- address.list_balances.each do |asset_id, balance|
80
- balance_map[asset_id] ||= BigDecimal(0)
81
- current_balance = balance_map[asset_id]
82
- new_balance = balance + current_balance
83
- balance_map[asset_id] = new_balance
84
- end
85
- end
86
-
87
- balance_map
88
- end
89
-
90
- # Returns the balance of the provided Asset. Balances are aggregated across all Addresses in the Wallet.
91
- # @param asset_id [Symbol] The ID of the Asset to retrieve the balance for
92
- # @return [BigDecimal] The balance of the Asset
93
- def get_balance(asset_id)
94
- normalized_asset_id = if %i[wei gwei].include?(asset_id)
95
- :eth
96
- else
97
- asset_id
98
- end
99
-
100
- eth_balance = list_balances[normalized_asset_id] || BigDecimal(0)
101
-
102
- case asset_id
103
- when :eth
104
- eth_balance
105
- when :gwei
106
- eth_balance * Coinbase::GWEI_PER_ETHER
107
- when :wei
108
- eth_balance * Coinbase::WEI_PER_ETHER
109
- else
110
- BigDecimal(0)
111
- end
112
- end
113
-
114
- # Transfers the given amount of the given Asset to the given address. Only same-Network Transfers are supported.
115
- # Currently only the default_address is used to source the Transfer.
116
- # @param amount [Integer, Float, BigDecimal] The amount of the Asset to send
117
- # @param asset_id [Symbol] The ID of the Asset to send
118
- # @param destination [Wallet | Address | String] The destination of the transfer. If a Wallet, sends to the Wallet's
119
- # default address. If a String, interprets it as the address ID.
120
- # @return [Transfer] The hash of the Transfer transaction.
121
- def transfer(amount, asset_id, destination)
122
- if destination.is_a?(Wallet)
123
- raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != @network_id
124
-
125
- destination = destination.default_address.address_id
126
- elsif destination.is_a?(Address)
127
- raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != @network_id
128
-
129
- destination = destination.address_id
130
- end
131
-
132
- default_address.transfer(amount, asset_id, destination)
133
- end
134
-
135
- # Exports the Wallet's data to a WalletData object.
136
- # @return [WalletData] The Wallet data
137
- def export
138
- WalletData.new(@master.seed_hex, @addresses.length)
139
- end
140
-
141
- # The data required to recreate a Wallet.
142
- class WalletData
143
- attr_reader :seed, :address_count
144
-
145
- # Returns a new WalletData object.
146
- # @param seed [String] The seed of the Wallet
147
- # @param address_count [Integer] The number of addresses in the Wallet
148
- def initialize(seed, address_count)
149
- @seed = seed
150
- @address_count = address_count
151
- end
152
- end
153
-
154
- # Returns the data required to recreate the Wallet.
155
- # @return [WalletData] The Wallet data
156
- def to_data
157
- WalletData.new(@master.seed_hex, @addresses.length)
158
- end
25
+ module Wallet
159
26
  end
160
27
  end
@@ -0,0 +1,193 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coinbase::Wallet::Account do
4
+ before :all do
5
+ @object_data = {
6
+ 'id' => '2bbf394c-193b-5b2a-9155-3b4732659ede',
7
+ 'name' => 'My Wallet',
8
+ 'primary' => true,
9
+ 'type' => 'wallet',
10
+ 'currency' => 'BTC',
11
+ 'balance' => {
12
+ 'amount' => '39.59000000',
13
+ 'currency' => 'BTC'
14
+ },
15
+ 'native_balance' => {
16
+ 'amount' => '395.90',
17
+ 'currency' => 'USD'
18
+ },
19
+ 'created_at' => '2015-01-31T20:49:02Z',
20
+ 'updated_at' => '2015-01-31T20:49:02Z',
21
+ 'resource' => 'account',
22
+ 'resource_path' => '/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede'
23
+ }
24
+
25
+ @client = Coinbase::Wallet::Client.new(api_key: 'api_key', api_secret: 'api_secret')
26
+ @object = Coinbase::Wallet::Account.new(@client, @object_data)
27
+ end
28
+
29
+ it "updates itself" do
30
+ ret = @object_data.clone
31
+ ret['name'] = 'new name'
32
+ stub_request(:put, /#{@object.resource_path}/)
33
+ .to_return(body: { data: ret }.to_json)
34
+ @object.update!(name: "new name")
35
+ expect(@object.name).to eq "new name"
36
+ end
37
+
38
+ it "makes itself primary" do
39
+ stub_request(:post, /#{@object.resource_path}\/primary/)
40
+ .to_return(body: { data: mock_item }.to_json)
41
+ expect(@object.make_primary!).to eq mock_item
42
+ end
43
+
44
+ it "deletes itself" do
45
+ stub_request(:delete, /#{@object.resource_path}/)
46
+ .to_return(body: { data: mock_item }.to_json)
47
+ expect(@object.delete!).to eq mock_item
48
+ end
49
+
50
+ it "gets addresses" do
51
+ stub_request(:get, /#{@object.resource_path}\/addresses/)
52
+ .to_return(body: { data: mock_collection }.to_json)
53
+ expect(@object.addresses).to eq mock_collection
54
+ end
55
+
56
+ it "gets address" do
57
+ stub_request(:get, /#{@object.resource_path}\/addresses\/test/)
58
+ .to_return(body: { data: mock_item }.to_json)
59
+ expect(@object.address("test")).to eq mock_item
60
+ end
61
+
62
+ it "creates address" do
63
+ stub_request(:post, /#{@object.resource_path}\/addresses/)
64
+ .to_return(body: { data: mock_item }.to_json)
65
+ expect(@object.create_address).to eq mock_item
66
+ end
67
+
68
+ it "gets transactions" do
69
+ stub_request(:get, /#{@object.resource_path}\/transactions/)
70
+ .to_return(body: { data: mock_collection }.to_json)
71
+ expect(@object.transactions).to eq mock_collection
72
+ end
73
+
74
+ it "gets transaction" do
75
+ stub_request(:get, /#{@object.resource_path}\/transactions\/test/)
76
+ .to_return(body: { data: mock_item }.to_json)
77
+ expect(@object.transaction('test')).to eq mock_item
78
+ end
79
+
80
+ it "sends money" do
81
+ stub_request(:post, /#{@object.resource_path}\/transactions/)
82
+ .to_return(body: { data: mock_item }.to_json)
83
+ expect(@object.send(amount: 10, to: 'example@coinbase.com')).to eq mock_item
84
+ end
85
+
86
+ it "transfers money" do
87
+ stub_request(:post, /#{@object.resource_path}\/transactions/)
88
+ .to_return(body: { data: mock_item }.to_json)
89
+ expect(@object.transfer(amount: 10, to: 'example@coinbase.com')).to eq mock_item
90
+ end
91
+
92
+ it "requests money" do
93
+ stub_request(:post, /#{@object.resource_path}\/transactions/)
94
+ .to_return(body: { data: mock_item }.to_json)
95
+ expect(@object.request(amount: 10, currency: "BTC", to: 'example@coinbase.com')).to eq mock_item
96
+ end
97
+
98
+ it "gets buys" do
99
+ stub_request(:get, /#{@object.resource_path}\/buys/)
100
+ .to_return(body: { data: mock_collection }.to_json)
101
+ expect(@object.list_buys).to eq mock_collection
102
+ end
103
+
104
+ it "gets buy" do
105
+ stub_request(:get, /#{@object.resource_path}\/buys\/test/)
106
+ .to_return(body: { data: mock_item }.to_json)
107
+ expect(@object.list_buy('test')).to eq mock_item
108
+ end
109
+
110
+ it "buys bitcoin" do
111
+ stub_request(:post, /#{@object.resource_path}\/buys/)
112
+ .to_return(body: { data: mock_item }.to_json)
113
+ expect(@object.buy(amount: 10, currency: 'BTC')).to eq mock_item
114
+ end
115
+
116
+ it "commits buy" do
117
+ stub_request(:post, /#{@object.resource_path}\/buys\/test\/commit/)
118
+ .to_return(body: { data: mock_item }.to_json)
119
+ expect(@object.commit_buy("test")).to eq mock_item
120
+ end
121
+
122
+ it "gets sells" do
123
+ stub_request(:get, /#{@object.resource_path}\/sells/)
124
+ .to_return(body: { data: mock_collection }.to_json)
125
+ expect(@object.list_sells).to eq mock_collection
126
+ end
127
+
128
+ it "gets sell" do
129
+ stub_request(:get, /#{@object.resource_path}\/sells\/test/)
130
+ .to_return(body: { data: mock_item }.to_json)
131
+ expect(@object.list_sell('test')).to eq mock_item
132
+ end
133
+
134
+ it "sells bitcoin" do
135
+ stub_request(:post, /#{@object.resource_path}\/sells/)
136
+ .to_return(body: { data: mock_item }.to_json)
137
+ expect(@object.sell(amount: 10, currency: 'BTC')).to eq mock_item
138
+ end
139
+
140
+ it "commits sell" do
141
+ stub_request(:post, /#{@object.resource_path}\/sells\/test\/commit/)
142
+ .to_return(body: { data: mock_item }.to_json)
143
+ expect(@object.commit_sell("test")).to eq mock_item
144
+ end
145
+
146
+ it "gets deposits" do
147
+ stub_request(:get, /#{@object.resource_path}\/deposits/)
148
+ .to_return(body: { data: mock_collection }.to_json)
149
+ expect(@object.list_deposits).to eq mock_collection
150
+ end
151
+
152
+ it "gets deposit" do
153
+ stub_request(:get, /#{@object.resource_path}\/deposits\/test/)
154
+ .to_return(body: { data: mock_item }.to_json)
155
+ expect(@object.list_deposit('test')).to eq mock_item
156
+ end
157
+
158
+ it "deposits bitcoin" do
159
+ stub_request(:post, /#{@object.resource_path}\/deposits/)
160
+ .to_return(body: { data: mock_item }.to_json)
161
+ expect(@object.deposit(amount: 10, currency: 'BTC')).to eq mock_item
162
+ end
163
+
164
+ it "commits deposit" do
165
+ stub_request(:post, /#{@object.resource_path}\/deposits\/test\/commit/)
166
+ .to_return(body: { data: mock_item }.to_json)
167
+ expect(@object.commit_deposit("test")).to eq mock_item
168
+ end
169
+
170
+ it "gets withdrawals" do
171
+ stub_request(:get, /#{@object.resource_path}\/withdrawals/)
172
+ .to_return(body: { data: mock_collection }.to_json)
173
+ expect(@object.list_withdrawals).to eq mock_collection
174
+ end
175
+
176
+ it "gets withdrawal" do
177
+ stub_request(:get, /#{@object.resource_path}\/withdrawals\/test/)
178
+ .to_return(body: { data: mock_item }.to_json)
179
+ expect(@object.list_withdrawal('test')).to eq mock_item
180
+ end
181
+
182
+ it "withdrawals bitcoin" do
183
+ stub_request(:post, /#{@object.resource_path}\/withdrawals/)
184
+ .to_return(body: { data: mock_item }.to_json)
185
+ expect(@object.withdraw(amount: 10, currency: 'BTC')).to eq mock_item
186
+ end
187
+
188
+ it "commits withdrawal" do
189
+ stub_request(:post, /#{@object.resource_path}\/withdrawals\/test\/commit/)
190
+ .to_return(body: { data: mock_item }.to_json)
191
+ expect(@object.commit_withdrawal("test")).to eq mock_item
192
+ end
193
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'timecop'
3
+
4
+ describe Coinbase::Wallet::Client do
5
+ let(:client) { Coinbase::Wallet::Client.new(api_key: 'api_key', api_secret: 'api_secret') }
6
+
7
+ it 'supplies correct headers' do
8
+ time = Time.utc(2015, 7, 1, 0, 0, 0)
9
+ timestamp = 1435708800
10
+
11
+ stub_request(:get, 'https://api.coinbase.com/v2/user')
12
+ .with('headers' => {
13
+ 'CB-ACCESS-KEY' => 'api_key',
14
+ 'CB-ACCESS-SIGN' => '9a413abc5a25a949932cd2b9963906d543f2df935c3a56159e24edb2095d78ee',
15
+ 'CB-ACCESS-TIMESTAMP' => timestamp.to_s,
16
+ 'CB-VERSION' => Coinbase::Wallet::API_VERSION,
17
+ })
18
+ .to_return(body: { data: mock_item }.to_json)
19
+
20
+ stub_request(:post, 'https://api.coinbase.com/v2/accounts')
21
+ .with('headers' => {
22
+ 'CB-ACCESS-KEY' => 'api_key',
23
+ 'CB-ACCESS-SIGN' => '3d4a73da32fc7fa55862865efd60c0cc994e5616d138a3c3b605a3ed504b235c',
24
+ 'CB-ACCESS-TIMESTAMP' => timestamp,
25
+ 'CB-VERSION' => Coinbase::Wallet::API_VERSION,
26
+ })
27
+ .to_return(body: { data: mock_item }.to_json)
28
+
29
+ Timecop.freeze(time) do
30
+ expect { client.current_user }.to_not raise_error
31
+ expect { client.create_account(name: "new wallet") }.to_not raise_error
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coinbase::Wallet::OAuthClient do
4
+ let(:client) { Coinbase::Wallet::OAuthClient.new(access_token: 'access_token', refresh_token: 'refresh_token') }
5
+
6
+ it 'handles init with access token and refresh token' do
7
+ stub_request(:get, /.*/)
8
+ .with('headers' => {
9
+ 'Authorization' => 'Bearer access_token',
10
+ 'CB-VERSION' => Coinbase::Wallet::API_VERSION,
11
+ })
12
+ .to_return(body: { data: { id: 'id', resource: 'user',
13
+ resource_path: '/v2/user' } }.to_json,
14
+ status: 200)
15
+
16
+ expect { client.current_user }.to_not raise_error
17
+ end
18
+
19
+ it 'handles init with access token' do
20
+ stub_request(:get, /.*/)
21
+ .to_return(body: { data: { id: 'id', resource: 'user',
22
+ resource_path: '/v2/user' } }.to_json,
23
+ status: 200)
24
+
25
+ client = Coinbase::Wallet::OAuthClient.new(access_token: 'access_token')
26
+ expect { client.current_user }.to_not raise_error
27
+ end
28
+
29
+ it '#refresh!' do
30
+ body = {
31
+ 'access_token' => 'new_access_token',
32
+ 'token_type'=> 'bearer',
33
+ 'expires_in'=> 7200,
34
+ 'refresh_token'=> 'new_refresh_token',
35
+ 'scope'=> 'wallet:user:read'
36
+ }
37
+
38
+ stub_request(:post, 'https://api.coinbase.com/oauth/token')
39
+ .with(body: {
40
+ grant_type: 'refresh_token',
41
+ refresh_token: 'refresh_token'
42
+ })
43
+ .to_return(body: body.to_json, status: 200)
44
+
45
+ client.refresh!
46
+ expect(client.refresh!).to eq body
47
+ expect(client.access_token).to eq 'new_access_token'
48
+ end
49
+
50
+ it '#revoke!' do
51
+ stub_request(:post, "https://api.coinbase.com/oauth/revoke")
52
+ .with(body: { token: 'access_token' })
53
+ .to_return(body: mock_item.to_json)
54
+ expect(client.revoke!).to eq mock_item
55
+ end
56
+ end