coinbase-sdk 0.0.1 → 0.0.3

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coinbase/address.rb +152 -51
  3. data/lib/coinbase/asset.rb +2 -1
  4. data/lib/coinbase/authenticator.rb +52 -0
  5. data/lib/coinbase/balance_map.rb +2 -2
  6. data/lib/coinbase/client/api/addresses_api.rb +454 -0
  7. data/lib/coinbase/client/api/transfers_api.rb +342 -0
  8. data/lib/coinbase/client/api/users_api.rb +79 -0
  9. data/lib/coinbase/client/api/wallets_api.rb +348 -0
  10. data/lib/coinbase/client/api_client.rb +431 -0
  11. data/lib/coinbase/client/api_error.rb +58 -0
  12. data/lib/coinbase/client/configuration.rb +375 -0
  13. data/lib/coinbase/client/models/address.rb +273 -0
  14. data/lib/coinbase/client/models/address_balance_list.rb +275 -0
  15. data/lib/coinbase/client/models/address_list.rb +275 -0
  16. data/lib/coinbase/client/models/asset.rb +260 -0
  17. data/lib/coinbase/client/models/balance.rb +239 -0
  18. data/lib/coinbase/client/models/broadcast_transfer_request.rb +222 -0
  19. data/lib/coinbase/client/models/create_address_request.rb +239 -0
  20. data/lib/coinbase/client/models/create_transfer_request.rb +273 -0
  21. data/lib/coinbase/client/models/create_wallet_request.rb +221 -0
  22. data/lib/coinbase/client/models/error.rb +278 -0
  23. data/lib/coinbase/client/models/faucet_transaction.rb +222 -0
  24. data/lib/coinbase/client/models/transfer.rb +413 -0
  25. data/lib/coinbase/client/models/transfer_list.rb +275 -0
  26. data/lib/coinbase/client/models/user.rb +231 -0
  27. data/lib/coinbase/client/models/wallet.rb +241 -0
  28. data/lib/coinbase/client/models/wallet_list.rb +275 -0
  29. data/lib/coinbase/client/version.rb +15 -0
  30. data/lib/coinbase/client.rb +59 -0
  31. data/lib/coinbase/constants.rb +8 -2
  32. data/lib/coinbase/errors.rb +120 -0
  33. data/lib/coinbase/faucet_transaction.rb +42 -0
  34. data/lib/coinbase/middleware.rb +21 -0
  35. data/lib/coinbase/network.rb +2 -2
  36. data/lib/coinbase/transfer.rb +106 -65
  37. data/lib/coinbase/user.rb +180 -0
  38. data/lib/coinbase/wallet.rb +168 -52
  39. data/lib/coinbase.rb +127 -9
  40. metadata +92 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2952835a61e4e744db044ab91e1cf58000d578967783d6faf9da77e495790172
4
- data.tar.gz: '00378e6bfb704f649d3943bd26da6712e41a9852ca2e4650c168ea098061a8b1'
3
+ metadata.gz: cac6b46a83009db9f165f0a2c4396122a2bf4bfa5bf895867b135695c346465e
4
+ data.tar.gz: 0b8cfea979b389b9890a09104b8c7533e334873a789b19d7f704789cdcf6a70d
5
5
  SHA512:
6
- metadata.gz: 10a9d9e6288bc1ab73b94be7e0cab8a37e64fa0398e644e570d385c342dd4a46d084e454f56acff0692c82424801ad92479a3ca7e4745d860c51086f28f559dd
7
- data.tar.gz: 131176725ada80cb046882a5fce27ec6355dd7395adf33cadace6f17507b326563ae4f945ceebc4566a10f94e815fda0fbcc3d735e7f7959d4a0b1da65e29b1e
6
+ metadata.gz: bccd3af15fec48789603988eadf3c67ad4ec67e5b3081fd2f06c7877d6ac35c6d2eda16db533ff149cf2f9f38d59ba38527284a0205529c84da6ac781dad8e3a
7
+ data.tar.gz: 072d5372f38da2b9a3eb97a024f4a289ac7ef1906df8cf1da22de0b37c77e18b9a724db7771019fc9c6eb3834720f48010edb869f310dcc114429cb0e0bd1403
@@ -2,65 +2,77 @@
2
2
 
3
3
  require_relative 'balance_map'
4
4
  require_relative 'constants'
5
+ require_relative 'wallet'
5
6
  require 'bigdecimal'
6
7
  require 'eth'
7
8
  require 'jimson'
8
9
 
9
10
  module Coinbase
10
11
  # A representation of a blockchain Address, which is a user-controlled account on a Network. Addresses are used to
11
- # send and receive Assets, and should be created using {link:Wallet#create_address}. Addresses require a
12
- # {link:Eth::Key} to sign transaction data.
12
+ # send and receive Assets, and should be created using Wallet#create_address. Addresses require an
13
+ # Eth::Key to sign transaction data.
13
14
  class Address
14
- attr_reader :network_id, :address_id, :wallet_id
15
-
16
- # Returns a new Address object.
17
- # @param network_id [Symbol] The ID of the Network on which the Address exists
18
- # @param address_id [String] The ID of the Address. On EVM Networks, for example, this is a hash of the public key.
19
- # @param wallet_id [String] The ID of the Wallet to which the Address belongs
15
+ # Returns a new Address object. Do not use this method directly. Instead, use Wallet#create_address, or use
16
+ # the Wallet's default_address.
17
+ # @param model [Coinbase::Client::Address] The underlying Address object
20
18
  # @param key [Eth::Key] The key backing the Address
21
- # @param client [Jimson::Client] (Optional) The JSON RPC client to use for interacting with the Network
22
- def initialize(network_id, address_id, wallet_id, key,
23
- client: Jimson::Client.new(Coinbase.base_sepolia_rpc_url))
24
- # TODO: Don't require key.
25
- @network_id = network_id
26
- @address_id = address_id
27
- @wallet_id = wallet_id
19
+ def initialize(model, key)
20
+ @model = model
28
21
  @key = key
29
- @client = client
30
22
  end
31
23
 
32
- # Returns the balances of the Address. Currently only ETH balances are supported.
24
+ # Returns the Network ID of the Address.
25
+ # @return [Symbol] The Network ID
26
+ def network_id
27
+ Coinbase.to_sym(@model.network_id)
28
+ end
29
+
30
+ # Returns the Wallet ID of the Address.
31
+ # @return [String] The Wallet ID
32
+ def wallet_id
33
+ @model.wallet_id
34
+ end
35
+
36
+ # Returns the Address ID.
37
+ # @return [String] The Address ID
38
+ def address_id
39
+ @model.address_id
40
+ end
41
+
42
+ # Returns the balances of the Address.
33
43
  # @return [BalanceMap] The balances of the Address, keyed by asset ID. Ether balances are denominated
34
44
  # in ETH.
35
45
  def list_balances
36
- # TODO: Handle multiple currencies.
37
- eth_balance_in_wei = BigDecimal(@client.eth_getBalance(@address_id, 'latest').to_i(16).to_s)
38
- eth_balance = BigDecimal(eth_balance_in_wei / BigDecimal(Coinbase::WEI_PER_ETHER.to_s))
46
+ response = Coinbase.call_api do
47
+ addresses_api.list_address_balances(wallet_id, address_id)
48
+ end
39
49
 
40
- BalanceMap.new({ eth: eth_balance })
50
+ Coinbase.to_balance_map(response)
41
51
  end
42
52
 
43
- # Returns the balance of the provided Asset. Currently only ETH is supported.
53
+ # Returns the balance of the provided Asset.
44
54
  # @param asset_id [Symbol] The Asset to retrieve the balance for
45
55
  # @return [BigDecimal] The balance of the Asset
46
56
  def get_balance(asset_id)
47
- normalized_asset_id = if %i[wei gwei].include?(asset_id)
48
- :eth
49
- else
50
- asset_id
51
- end
57
+ normalized_asset_id = normalize_asset_id(asset_id)
58
+
59
+ response = Coinbase.call_api do
60
+ addresses_api.get_address_balance(wallet_id, address_id, normalized_asset_id.to_s)
61
+ end
62
+
63
+ return BigDecimal('0') if response.nil?
52
64
 
53
- eth_balance = list_balances[normalized_asset_id] || BigDecimal(0)
65
+ amount = BigDecimal(response.amount)
54
66
 
55
67
  case asset_id
56
68
  when :eth
57
- eth_balance
69
+ amount / BigDecimal(Coinbase::WEI_PER_ETHER.to_s)
58
70
  when :gwei
59
- eth_balance * Coinbase::GWEI_PER_ETHER
60
- when :wei
61
- eth_balance * Coinbase::WEI_PER_ETHER
71
+ amount / BigDecimal(Coinbase::GWEI_PER_ETHER.to_s)
72
+ when :usdc
73
+ amount / BigDecimal(Coinbase::ATOMIC_UNITS_PER_USDC.to_s)
62
74
  else
63
- BigDecimal(0)
75
+ amount
64
76
  end
65
77
  end
66
78
 
@@ -71,15 +83,14 @@ module Coinbase
71
83
  # default address. If a String, interprets it as the address ID.
72
84
  # @return [String] The hash of the Transfer transaction.
73
85
  def transfer(amount, asset_id, destination)
74
- # TODO: Handle multiple currencies.
75
86
  raise ArgumentError, "Unsupported asset: #{asset_id}" unless Coinbase::SUPPORTED_ASSET_IDS[asset_id]
76
87
 
77
88
  if destination.is_a?(Wallet)
78
- raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != @network_id
89
+ raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != network_id
79
90
 
80
91
  destination = destination.default_address.address_id
81
92
  elsif destination.is_a?(Address)
82
- raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != @network_id
93
+ raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != network_id
83
94
 
84
95
  destination = destination.address_id
85
96
  end
@@ -89,39 +100,129 @@ module Coinbase
89
100
  raise ArgumentError, "Insufficient funds: #{amount} requested, but only #{current_balance} available"
90
101
  end
91
102
 
92
- transfer = Coinbase::Transfer.new(@network_id, @wallet_id, @address_id, amount, asset_id, destination,
93
- client: @client)
103
+ normalized_amount = normalize_asset_amount(amount, asset_id)
104
+
105
+ normalized_asset_id = normalize_asset_id(asset_id)
106
+
107
+ create_transfer_request = {
108
+ amount: normalized_amount.to_i.to_s,
109
+ network_id: network_id,
110
+ asset_id: normalized_asset_id.to_s,
111
+ destination: destination
112
+ }
113
+
114
+ transfer_model = Coinbase.call_api do
115
+ transfers_api.create_transfer(wallet_id, address_id, create_transfer_request)
116
+ end
117
+
118
+ transfer = Coinbase::Transfer.new(transfer_model)
94
119
 
95
120
  transaction = transfer.transaction
96
121
  transaction.sign(@key)
97
- @client.eth_sendRawTransaction("0x#{transaction.hex}")
98
122
 
99
- transfer
123
+ signed_payload = transaction.hex
124
+
125
+ broadcast_transfer_request = {
126
+ signed_payload: signed_payload
127
+ }
128
+
129
+ transfer_model = Coinbase.call_api do
130
+ transfers_api.broadcast_transfer(wallet_id, address_id, transfer.transfer_id, broadcast_transfer_request)
131
+ end
132
+
133
+ Coinbase::Transfer.new(transfer_model)
100
134
  end
101
135
 
102
- # Returns the address as a string.
103
- # @return [String] The address
136
+ # Returns a String representation of the Address.
137
+ # @return [String] a String representation of the Address
104
138
  def to_s
105
- @address_id
139
+ "Coinbase::Address{address_id: '#{address_id}', network_id: '#{network_id}', wallet_id: '#{wallet_id}'}"
140
+ end
141
+
142
+ # Same as to_s.
143
+ # @return [String] a String representation of the Address
144
+ def inspect
145
+ to_s
146
+ end
147
+
148
+ # Requests funds for the address from the faucet and returns the faucet transaction.
149
+ # This is only supported on testnet networks.
150
+ # @return [Coinbase::FaucetTransaction] The successful faucet transaction
151
+ # @raise [Coinbase::FaucetLimitReached] If the faucet limit has been reached for the address or user.
152
+ # @raise [Coinbase::Client::ApiError] If an unexpected error occurs while requesting faucet funds.
153
+ def faucet
154
+ Coinbase.call_api do
155
+ Coinbase::FaucetTransaction.new(addresses_api.request_faucet_funds(wallet_id, address_id))
156
+ end
157
+ end
158
+
159
+ # Exports the Address's private key to a hex string.
160
+ # @return [String] The Address's private key as a hex String
161
+ def export
162
+ @key.private_hex
163
+ end
164
+
165
+ # Lists the IDs of all Transfers associated with the given Wallet and Address.
166
+ # @return [Array<String>] The IDs of all Transfers belonging to the Wallet and Address
167
+ def list_transfer_ids
168
+ transfer_ids = []
169
+ page = nil
170
+
171
+ loop do
172
+ response = Coinbase.call_api do
173
+ transfers_api.list_transfers(wallet_id, address_id, { limit: 100, page: page })
174
+ end
175
+
176
+ transfer_ids.concat(response.data.map(&:transfer_id)) if response.data
177
+
178
+ break unless response.has_more
179
+
180
+ page = response.next_page
181
+ end
182
+
183
+ transfer_ids
106
184
  end
107
185
 
108
186
  private
109
187
 
110
- # Normalizes the amount of ETH to send based on the asset ID.
188
+ # Normalizes the amount of the Asset to send to the atomic unit.
111
189
  # @param amount [Integer, Float, BigDecimal] The amount to normalize
112
190
  # @param asset_id [Symbol] The ID of the Asset being transferred
113
- # @return [BigDecimal] The normalized amount in units of ETH
114
- def normalize_eth_amount(amount, asset_id)
191
+ # @return [BigDecimal] The normalized amount in atomic units
192
+ def normalize_asset_amount(amount, asset_id)
193
+ big_amount = BigDecimal(amount.to_s)
194
+
115
195
  case asset_id
116
196
  when :eth
117
- amount.is_a?(BigDecimal) ? amount : BigDecimal(amount.to_s)
197
+ big_amount * Coinbase::WEI_PER_ETHER
118
198
  when :gwei
119
- BigDecimal(amount / Coinbase::GWEI_PER_ETHER)
120
- when :wei
121
- BigDecimal(amount / Coinbase::WEI_PER_ETHER)
199
+ big_amount * Coinbase::WEI_PER_GWEI
200
+ when :usdc
201
+ big_amount * Coinbase::ATOMIC_UNITS_PER_USDC
202
+ when :weth
203
+ big_amount * Coinbase::WEI_PER_ETHER
122
204
  else
123
- raise ArgumentError, "Unsupported asset: #{asset_id}"
205
+ big_amount
124
206
  end
125
207
  end
208
+
209
+ # Normalizes the asset ID to use during requests.
210
+ # @param asset_id [Symbol] The asset ID to normalize
211
+ # @return [Symbol] The normalized asset ID
212
+ def normalize_asset_id(asset_id)
213
+ if %i[wei gwei].include?(asset_id)
214
+ :eth
215
+ else
216
+ asset_id
217
+ end
218
+ end
219
+
220
+ def addresses_api
221
+ @addresses_api ||= Coinbase::Client::AddressesApi.new(Coinbase.configuration.api_client)
222
+ end
223
+
224
+ def transfers_api
225
+ @transfers_api ||= Coinbase::Client::TransfersApi.new(Coinbase.configuration.api_client)
226
+ end
126
227
  end
127
228
  end
@@ -5,7 +5,8 @@ module Coinbase
5
5
  class Asset
6
6
  attr_reader :network_id, :asset_id, :display_name, :address_id
7
7
 
8
- # Returns a new Asset object.
8
+ # Returns a new Asset object. Do not use this method. Instead, use the Asset constants defined in
9
+ # the Coinbase module.
9
10
  # @param network_id [Symbol] The ID of the Network to which the Asset belongs
10
11
  # @param asset_id [Symbol] The Asset ID
11
12
  # @param display_name [String] The Asset's display name
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'jwt'
5
+ require 'openssl'
6
+ require 'securerandom'
7
+
8
+ module Coinbase
9
+ # A class that builds JWTs for authenticating with the Coinbase Platform APIs.
10
+ class Authenticator < Faraday::Middleware
11
+ # Initializes the Authenticator.
12
+ # @param app [Faraday::Connection] The Faraday connection
13
+ def initialize(app)
14
+ super(app)
15
+ @app = app
16
+ end
17
+
18
+ # Processes the request by adding the JWT to the Authorization header.
19
+ # @param env [Faraday::Env] The Faraday request environment
20
+ def call(env)
21
+ method = env.method.downcase.to_sym
22
+ uri = env.url.to_s
23
+ uri_without_protocol = URI(uri).host
24
+ token = build_jwt("#{method.upcase} #{uri_without_protocol}#{env.url.path}")
25
+ env.request_headers['Authorization'] = "Bearer #{token}"
26
+ @app.call(env)
27
+ end
28
+
29
+ # Builds the JWT for the given API endpoint URI. The JWT is signed with the API key's private key.
30
+ # @param uri [String] The API endpoint URI
31
+ # @return [String] The JWT
32
+ def build_jwt(uri)
33
+ header = {
34
+ typ: 'JWT',
35
+ kid: Coinbase.configuration.api_key_name,
36
+ nonce: SecureRandom.hex(16)
37
+ }
38
+
39
+ claims = {
40
+ sub: Coinbase.configuration.api_key_name,
41
+ iss: 'coinbase-cloud',
42
+ aud: ['cdp_service'],
43
+ nbf: Time.now.to_i,
44
+ exp: Time.now.to_i + 60, # Expiration time: 1 minute from now.
45
+ uris: [uri]
46
+ }
47
+
48
+ private_key = OpenSSL::PKey.read(Coinbase.configuration.api_key_private_key)
49
+ JWT.encode(claims, private_key, 'ES256', header)
50
+ end
51
+ end
52
+ end
@@ -3,7 +3,7 @@
3
3
  require 'bigdecimal'
4
4
 
5
5
  module Coinbase
6
- # A convenience class for printing out crypto asset balances in a human-readable format.
6
+ # A convenience class for printing out Asset balances in a human-readable format.
7
7
  class BalanceMap < Hash
8
8
  # Returns a new BalanceMap object.
9
9
  # @param hash [Map<Symbol, BigDecimal>] The hash to initialize with
@@ -42,7 +42,7 @@ module Coinbase
42
42
  result[asset_id] = str
43
43
  end
44
44
 
45
- result
45
+ result.to_s
46
46
  end
47
47
  end
48
48
  end