coinbase 0.0.1 → 4.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +7 -0
- data/CONTRIBUTING.md +53 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README.md +621 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/coinbase.gemspec +26 -0
- data/lib/coinbase/util.rb +16 -0
- data/lib/coinbase/wallet/adapters/em_http.rb +78 -0
- data/lib/coinbase/wallet/adapters/net_http.rb +68 -0
- data/lib/coinbase/wallet/api_client.rb +755 -0
- data/lib/coinbase/wallet/api_errors.rb +120 -0
- data/lib/coinbase/wallet/api_response.rb +41 -0
- data/lib/coinbase/wallet/ca-coinbase.crt +101 -0
- data/lib/coinbase/wallet/client.rb +101 -0
- data/lib/coinbase/wallet/coinbase-callback.pub +14 -0
- data/lib/coinbase/wallet/models/account.rb +193 -0
- data/lib/coinbase/wallet/models/address.rb +12 -0
- data/lib/coinbase/wallet/models/api_object.rb +46 -0
- data/lib/coinbase/wallet/models/checkout.rb +19 -0
- data/lib/coinbase/wallet/models/order.rb +12 -0
- data/lib/coinbase/wallet/models/transaction.rb +21 -0
- data/lib/coinbase/wallet/models/transfer.rb +13 -0
- data/lib/coinbase/wallet/models/user.rb +15 -0
- data/lib/coinbase/wallet/version.rb +5 -0
- data/lib/coinbase/wallet.rb +24 -156
- data/spec/account_spec.rb +199 -0
- data/spec/callback_signature_verification_spec.rb +16 -0
- data/spec/clients/client_spec.rb +34 -0
- data/spec/clients/oauth_client_spec.rb +56 -0
- data/spec/endpoints_spec.rb +352 -0
- data/spec/error_spec.rb +137 -0
- data/spec/models/address_spec.rb +26 -0
- data/spec/models/api_object_spec.rb +70 -0
- data/spec/models/checkout_spec.rb +52 -0
- data/spec/models/current_user_spec.rb +29 -0
- data/spec/models/order_spec.rb +52 -0
- data/spec/models/request_spec.rb +47 -0
- data/spec/models/transfer_spec.rb +64 -0
- data/spec/models/user_spec.rb +24 -0
- data/spec/spec_helper.rb +13 -0
- metadata +67 -112
- data/lib/coinbase/address.rb +0 -127
- data/lib/coinbase/asset.rb +0 -20
- data/lib/coinbase/balance_map.rb +0 -48
- data/lib/coinbase/constants.rb +0 -38
- data/lib/coinbase/network.rb +0 -55
- data/lib/coinbase/transfer.rb +0 -153
- data/lib/coinbase.rb +0 -18
@@ -0,0 +1,15 @@
|
|
1
|
+
module Coinbase
|
2
|
+
module Wallet
|
3
|
+
class User < APIObject
|
4
|
+
end
|
5
|
+
|
6
|
+
class CurrentUser < User
|
7
|
+
def update!(params = {})
|
8
|
+
@client.update_current_user(params) do |data, resp|
|
9
|
+
update(resp.data)
|
10
|
+
yield(resp.data, resp) if block_given?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/coinbase/wallet.rb
CHANGED
@@ -1,160 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require "coinbase/wallet/version"
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
require "bigdecimal"
|
5
|
+
require "json"
|
6
|
+
require "uri"
|
7
|
+
require "net/https"
|
8
|
+
|
9
|
+
require "coinbase/wallet/api_errors"
|
10
|
+
require "coinbase/wallet/api_response"
|
11
|
+
require "coinbase/wallet/api_client"
|
12
|
+
require "coinbase/wallet/adapters/net_http.rb"
|
13
|
+
require "coinbase/wallet/adapters/em_http.rb"
|
14
|
+
require "coinbase/wallet/models/api_object"
|
15
|
+
require "coinbase/wallet/models/account"
|
16
|
+
require "coinbase/wallet/models/address"
|
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"
|
23
|
+
require "coinbase/util"
|
6
24
|
|
7
25
|
module Coinbase
|
8
|
-
|
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
|
26
|
+
module Wallet
|
159
27
|
end
|
160
28
|
end
|
@@ -0,0 +1,199 @@
|
|
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 "gets address transactions" do
|
63
|
+
stub_request(:get, /#{@object.resource_path}\/addresses\/test\/transactions/)
|
64
|
+
.to_return(body: { data: mock_collection }.to_json)
|
65
|
+
expect(@object.address_transactions("test")).to eq mock_collection
|
66
|
+
end
|
67
|
+
|
68
|
+
it "creates address" do
|
69
|
+
stub_request(:post, /#{@object.resource_path}\/addresses/)
|
70
|
+
.to_return(body: { data: mock_item }.to_json)
|
71
|
+
expect(@object.create_address).to eq mock_item
|
72
|
+
end
|
73
|
+
|
74
|
+
it "gets transactions" do
|
75
|
+
stub_request(:get, /#{@object.resource_path}\/transactions/)
|
76
|
+
.to_return(body: { data: mock_collection }.to_json)
|
77
|
+
expect(@object.transactions).to eq mock_collection
|
78
|
+
end
|
79
|
+
|
80
|
+
it "gets transaction" do
|
81
|
+
stub_request(:get, /#{@object.resource_path}\/transactions\/test/)
|
82
|
+
.to_return(body: { data: mock_item }.to_json)
|
83
|
+
expect(@object.transaction('test')).to eq mock_item
|
84
|
+
end
|
85
|
+
|
86
|
+
it "sends money" do
|
87
|
+
stub_request(:post, /#{@object.resource_path}\/transactions/)
|
88
|
+
.to_return(body: { data: mock_item }.to_json)
|
89
|
+
expect(@object.send(amount: 10, to: 'example@coinbase.com')).to eq mock_item
|
90
|
+
end
|
91
|
+
|
92
|
+
it "transfers money" do
|
93
|
+
stub_request(:post, /#{@object.resource_path}\/transactions/)
|
94
|
+
.to_return(body: { data: mock_item }.to_json)
|
95
|
+
expect(@object.transfer(amount: 10, to: 'example@coinbase.com')).to eq mock_item
|
96
|
+
end
|
97
|
+
|
98
|
+
it "requests money" do
|
99
|
+
stub_request(:post, /#{@object.resource_path}\/transactions/)
|
100
|
+
.to_return(body: { data: mock_item }.to_json)
|
101
|
+
expect(@object.request(amount: 10, currency: "BTC", to: 'example@coinbase.com')).to eq mock_item
|
102
|
+
end
|
103
|
+
|
104
|
+
it "gets buys" do
|
105
|
+
stub_request(:get, /#{@object.resource_path}\/buys/)
|
106
|
+
.to_return(body: { data: mock_collection }.to_json)
|
107
|
+
expect(@object.list_buys).to eq mock_collection
|
108
|
+
end
|
109
|
+
|
110
|
+
it "gets buy" do
|
111
|
+
stub_request(:get, /#{@object.resource_path}\/buys\/test/)
|
112
|
+
.to_return(body: { data: mock_item }.to_json)
|
113
|
+
expect(@object.list_buy('test')).to eq mock_item
|
114
|
+
end
|
115
|
+
|
116
|
+
it "buys bitcoin" do
|
117
|
+
stub_request(:post, /#{@object.resource_path}\/buys/)
|
118
|
+
.to_return(body: { data: mock_item }.to_json)
|
119
|
+
expect(@object.buy(amount: 10, currency: 'BTC')).to eq mock_item
|
120
|
+
end
|
121
|
+
|
122
|
+
it "commits buy" do
|
123
|
+
stub_request(:post, /#{@object.resource_path}\/buys\/test\/commit/)
|
124
|
+
.to_return(body: { data: mock_item }.to_json)
|
125
|
+
expect(@object.commit_buy("test")).to eq mock_item
|
126
|
+
end
|
127
|
+
|
128
|
+
it "gets sells" do
|
129
|
+
stub_request(:get, /#{@object.resource_path}\/sells/)
|
130
|
+
.to_return(body: { data: mock_collection }.to_json)
|
131
|
+
expect(@object.list_sells).to eq mock_collection
|
132
|
+
end
|
133
|
+
|
134
|
+
it "gets sell" do
|
135
|
+
stub_request(:get, /#{@object.resource_path}\/sells\/test/)
|
136
|
+
.to_return(body: { data: mock_item }.to_json)
|
137
|
+
expect(@object.list_sell('test')).to eq mock_item
|
138
|
+
end
|
139
|
+
|
140
|
+
it "sells bitcoin" do
|
141
|
+
stub_request(:post, /#{@object.resource_path}\/sells/)
|
142
|
+
.to_return(body: { data: mock_item }.to_json)
|
143
|
+
expect(@object.sell(amount: 10, currency: 'BTC')).to eq mock_item
|
144
|
+
end
|
145
|
+
|
146
|
+
it "commits sell" do
|
147
|
+
stub_request(:post, /#{@object.resource_path}\/sells\/test\/commit/)
|
148
|
+
.to_return(body: { data: mock_item }.to_json)
|
149
|
+
expect(@object.commit_sell("test")).to eq mock_item
|
150
|
+
end
|
151
|
+
|
152
|
+
it "gets deposits" do
|
153
|
+
stub_request(:get, /#{@object.resource_path}\/deposits/)
|
154
|
+
.to_return(body: { data: mock_collection }.to_json)
|
155
|
+
expect(@object.list_deposits).to eq mock_collection
|
156
|
+
end
|
157
|
+
|
158
|
+
it "gets deposit" do
|
159
|
+
stub_request(:get, /#{@object.resource_path}\/deposits\/test/)
|
160
|
+
.to_return(body: { data: mock_item }.to_json)
|
161
|
+
expect(@object.list_deposit('test')).to eq mock_item
|
162
|
+
end
|
163
|
+
|
164
|
+
it "deposits bitcoin" do
|
165
|
+
stub_request(:post, /#{@object.resource_path}\/deposits/)
|
166
|
+
.to_return(body: { data: mock_item }.to_json)
|
167
|
+
expect(@object.deposit(amount: 10, currency: 'BTC')).to eq mock_item
|
168
|
+
end
|
169
|
+
|
170
|
+
it "commits deposit" do
|
171
|
+
stub_request(:post, /#{@object.resource_path}\/deposits\/test\/commit/)
|
172
|
+
.to_return(body: { data: mock_item }.to_json)
|
173
|
+
expect(@object.commit_deposit("test")).to eq mock_item
|
174
|
+
end
|
175
|
+
|
176
|
+
it "gets withdrawals" do
|
177
|
+
stub_request(:get, /#{@object.resource_path}\/withdrawals/)
|
178
|
+
.to_return(body: { data: mock_collection }.to_json)
|
179
|
+
expect(@object.list_withdrawals).to eq mock_collection
|
180
|
+
end
|
181
|
+
|
182
|
+
it "gets withdrawal" do
|
183
|
+
stub_request(:get, /#{@object.resource_path}\/withdrawals\/test/)
|
184
|
+
.to_return(body: { data: mock_item }.to_json)
|
185
|
+
expect(@object.list_withdrawal('test')).to eq mock_item
|
186
|
+
end
|
187
|
+
|
188
|
+
it "withdrawals bitcoin" do
|
189
|
+
stub_request(:post, /#{@object.resource_path}\/withdrawals/)
|
190
|
+
.to_return(body: { data: mock_item }.to_json)
|
191
|
+
expect(@object.withdraw(amount: 10, currency: 'BTC')).to eq mock_item
|
192
|
+
end
|
193
|
+
|
194
|
+
it "commits withdrawal" do
|
195
|
+
stub_request(:post, /#{@object.resource_path}\/withdrawals\/test\/commit/)
|
196
|
+
.to_return(body: { data: mock_item }.to_json)
|
197
|
+
expect(@object.commit_withdrawal("test")).to eq mock_item
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Coinbase::Wallet::APIClient do
|
4
|
+
let(:client) { Coinbase::Wallet::Client.new(api_key: 'api_key', api_secret: 'api_secret') }
|
5
|
+
let(:signature) { "6yQRl17CNj5YSHSpF+tLjb0vVsNVEv021Tyy1bTVEQ69SWlmhwmJYuMc7jiDyeW9TLy4vRqSh4g4YEyN8eoQIM57pMoNw6Lw6Oudubqwp+E3cKtLFxW0l18db3Z/vhxn5BScAutHWwT/XrmkCNaHyCsvOOGMekwrNO7mxX9QIx21FBaEejJeviSYrF8bG6MbmFEs2VGKSybf9YrElR8BxxNe/uNfCXN3P5tO8MgR5wlL3Kr4yq8e6i4WWJgD08IVTnrSnoZR6v8JkPA+fn7I0M6cy0Xzw3BRMJAvdQB97wkobu97gFqJFKsOH2u/JR1S/UNP26vL0mzuAVuKAUwlRn0SUhWEAgcM3X0UCtWLYfCIb5QqrSHwlp7lwOkVnFt329Mrpjy+jAfYYSRqzIsw4ZsRRVauy/v3CvmjPI9sUKiJ5l1FSgkpK2lkjhFgKB3WaYZWy9ZfIAI9bDyG8vSTT7IDurlUhyTweDqVNlYUsO6jaUa4KmSpg1o9eIeHxm0XBQ2c0Lv/T39KNc/VOAi1LBfPiQYMXD1e/8VuPPBTDGgzOMD3i334ppSr36+8YtApAn3D36Hr9jqAfFrugM7uPecjCGuleWsHFyNnJErT0/amIt24Nh1GoiESEq42o7Co4wZieKZ+/yeAlIUErJzK41ACVGmTnGoDUwEBXxADOdA=" }
|
6
|
+
|
7
|
+
it 'correctly verifies valid callback' do
|
8
|
+
body = %[{"order":{"id":null,"created_at":null,"status":"completed","event":null,"total_btc":{"cents":100000000,"currency_iso":"BTC"},"total_native":{"cents":1000,"currency_iso":"USD"},"total_payout":{"cents":1000,"currency_iso":"USD"},"custom":"123456789","receive_address":"mzVoQenSY6RTBgBUcpSBTBAvUMNgGWxgJn","button":{"type":"buy_now","name":"Test Item","description":null,"id":null},"transaction":{"id":"53bdfe4d091c0d74a7000003","hash":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","confirmations":0}}}]
|
9
|
+
expect(client.verify_callback(body, signature)).to be true
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'correctly rejects callback that has been tampered with' do
|
13
|
+
body = %[{"order":{"id":null,"created_at":null,"status":"mispaid","event":null,"total_btc":{"cents":100000000,"currency_iso":"BTC"},"total_native":{"cents":1000,"currency_iso":"USD"},"total_payout":{"cents":1000,"currency_iso":"USD"},"custom":"123456789","receive_address":"mzVoQenSY6RTBgBUcpSBTBAvUMNgGWxgJn","button":{"type":"buy_now","name":"Test Item","description":null,"id":null},"transaction":{"id":"53bdfe4d091c0d74a7000003","hash":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","confirmations":0}}}]
|
14
|
+
expect(client.verify_callback(body, signature)).to be false
|
15
|
+
end
|
16
|
+
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
|
+
expect(client.refresh!).to eq body
|
46
|
+
expect(client.access_token).to eq 'new_access_token'
|
47
|
+
expect(client.refresh_token).to eq 'new_refresh_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
|