coinbase-sdk 0.0.7 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/lib/coinbase/address/external_address.rb +173 -0
  3. data/lib/coinbase/address/wallet_address.rb +219 -0
  4. data/lib/coinbase/address.rb +32 -219
  5. data/lib/coinbase/asset.rb +76 -66
  6. data/lib/coinbase/authenticator.rb +2 -0
  7. data/lib/coinbase/balance.rb +10 -6
  8. data/lib/coinbase/client/api/addresses_api.rb +1 -1
  9. data/lib/coinbase/client/api/assets_api.rb +91 -0
  10. data/lib/coinbase/client/api/external_addresses_api.rb +242 -0
  11. data/lib/coinbase/client/api/server_signers_api.rb +1 -1
  12. data/lib/coinbase/client/api/stake_api.rb +157 -7
  13. data/lib/coinbase/client/api/trades_api.rb +1 -1
  14. data/lib/coinbase/client/api/transfers_api.rb +1 -1
  15. data/lib/coinbase/client/api/users_api.rb +1 -1
  16. data/lib/coinbase/client/api/wallets_api.rb +1 -1
  17. data/lib/coinbase/client/api_client.rb +3 -3
  18. data/lib/coinbase/client/api_error.rb +1 -1
  19. data/lib/coinbase/client/configuration.rb +1 -1
  20. data/lib/coinbase/client/models/address.rb +1 -1
  21. data/lib/coinbase/client/models/address_balance_list.rb +1 -1
  22. data/lib/coinbase/client/models/address_list.rb +1 -1
  23. data/lib/coinbase/client/models/asset.rb +1 -1
  24. data/lib/coinbase/client/models/balance.rb +1 -1
  25. data/lib/coinbase/client/models/broadcast_trade_request.rb +1 -1
  26. data/lib/coinbase/client/models/broadcast_transfer_request.rb +1 -1
  27. data/lib/coinbase/client/models/build_staking_operation_request.rb +1 -1
  28. data/lib/coinbase/client/models/create_address_request.rb +1 -1
  29. data/lib/coinbase/client/models/create_server_signer_request.rb +22 -5
  30. data/lib/coinbase/client/models/create_trade_request.rb +1 -1
  31. data/lib/coinbase/client/models/create_transfer_request.rb +1 -1
  32. data/lib/coinbase/client/models/create_wallet_request.rb +1 -1
  33. data/lib/coinbase/client/models/create_wallet_request_wallet.rb +1 -1
  34. data/lib/coinbase/client/models/error.rb +1 -1
  35. data/lib/coinbase/client/models/faucet_transaction.rb +23 -5
  36. data/lib/coinbase/client/models/feature.rb +1 -1
  37. data/lib/coinbase/client/models/fetch_staking_rewards200_response.rb +258 -0
  38. data/lib/coinbase/client/models/fetch_staking_rewards_request.rb +330 -0
  39. data/lib/coinbase/client/models/get_staking_context_request.rb +274 -0
  40. data/lib/coinbase/client/models/partial_eth_staking_context.rb +254 -0
  41. data/lib/coinbase/client/models/seed_creation_event.rb +1 -1
  42. data/lib/coinbase/client/models/seed_creation_event_result.rb +1 -1
  43. data/lib/coinbase/client/models/server_signer.rb +22 -5
  44. data/lib/coinbase/client/models/server_signer_event.rb +1 -1
  45. data/lib/coinbase/client/models/server_signer_event_event.rb +1 -1
  46. data/lib/coinbase/client/models/server_signer_event_list.rb +1 -1
  47. data/lib/coinbase/client/models/server_signer_list.rb +1 -1
  48. data/lib/coinbase/client/models/signature_creation_event.rb +1 -1
  49. data/lib/coinbase/client/models/signature_creation_event_result.rb +1 -1
  50. data/lib/coinbase/client/models/{request_faucet_funds200_response.rb → staking_context.rb} +16 -16
  51. data/lib/coinbase/client/models/staking_context_context.rb +104 -0
  52. data/lib/coinbase/client/models/staking_operation.rb +15 -12
  53. data/lib/coinbase/client/models/staking_reward.rb +324 -0
  54. data/lib/coinbase/client/models/staking_reward_format.rb +40 -0
  55. data/lib/coinbase/client/models/trade.rb +1 -1
  56. data/lib/coinbase/client/models/trade_list.rb +1 -1
  57. data/lib/coinbase/client/models/transaction.rb +1 -1
  58. data/lib/coinbase/client/models/transaction_type.rb +1 -1
  59. data/lib/coinbase/client/models/transfer.rb +1 -1
  60. data/lib/coinbase/client/models/transfer_list.rb +1 -1
  61. data/lib/coinbase/client/models/user.rb +1 -1
  62. data/lib/coinbase/client/models/wallet.rb +1 -1
  63. data/lib/coinbase/client/models/wallet_list.rb +1 -1
  64. data/lib/coinbase/client/version.rb +1 -1
  65. data/lib/coinbase/client.rb +11 -1
  66. data/lib/coinbase/constants.rb +2 -27
  67. data/lib/coinbase/errors.rb +56 -64
  68. data/lib/coinbase/faucet_transaction.rb +5 -4
  69. data/lib/coinbase/network.rb +6 -20
  70. data/lib/coinbase/pagination.rb +26 -0
  71. data/lib/coinbase/staking_operation.rb +29 -0
  72. data/lib/coinbase/staking_reward.rb +79 -0
  73. data/lib/coinbase/transaction.rb +6 -0
  74. data/lib/coinbase/user.rb +5 -51
  75. data/lib/coinbase/wallet.rb +97 -102
  76. data/lib/coinbase.rb +19 -11
  77. metadata +17 -17
@@ -6,97 +6,63 @@ require 'json'
6
6
  module Coinbase
7
7
  # A wrapper for API errors to provide more context.
8
8
  class APIError < StandardError
9
- attr_reader :http_code, :api_code, :api_message
9
+ attr_reader :http_code, :api_code, :api_message, :handled
10
10
 
11
11
  # Initializes a new APIError object.
12
12
  # @param err [Coinbase::Client::APIError] The underlying error object.
13
- def initialize(err)
14
- super
13
+ def initialize(err, code: nil, message: nil, unhandled: false)
15
14
  @http_code = err.code
15
+ @api_code = code
16
+ @api_message = message
17
+ @handled = code && message && !unhandled
16
18
 
17
- return unless err.response_body
18
-
19
- body = JSON.parse(err.response_body)
20
- @api_code = body['code']
21
- @api_message = body['message']
19
+ super(err)
22
20
  end
23
21
 
24
22
  # Creates a specific APIError based on the API error code.
25
23
  # @param err [Coinbase::Client::APIError] The underlying error object.
26
24
  # @return [APIError] The specific APIError object.
27
- # rubocop:disable Metrics/MethodLength
28
25
  def self.from_error(err)
29
26
  raise ArgumentError, 'Argument must be a Coinbase::Client::APIError' unless err.is_a? Coinbase::Client::ApiError
30
27
  return APIError.new(err) unless err.response_body
31
28
 
32
- body = JSON.parse(err.response_body)
29
+ begin
30
+ body = JSON.parse(err.response_body)
31
+ rescue JSON::ParserError
32
+ return APIError.new(err)
33
+ end
33
34
 
34
- case body['code']
35
- when 'unimplemented'
36
- UnimplementedError.new(err)
37
- when 'unauthorized'
38
- UnauthorizedError.new(err)
39
- when 'internal'
40
- InternalError.new(err)
41
- when 'not_found'
42
- NotFoundError.new(err)
43
- when 'invalid_wallet_id'
44
- InvalidWalletIDError.new(err)
45
- when 'invalid_address_id'
46
- InvalidAddressIDError.new(err)
47
- when 'invalid_wallet'
48
- InvalidWalletError.new(err)
49
- when 'invalid_address'
50
- InvalidAddressError.new(err)
51
- when 'invalid_amount'
52
- InvalidAmountError.new(err)
53
- when 'invalid_transfer_id'
54
- InvalidTransferIDError.new(err)
55
- when 'invalid_page_token'
56
- InvalidPageError.new(err)
57
- when 'invalid_page_limit'
58
- InvalidLimitError.new(err)
59
- when 'already_exists'
60
- AlreadyExistsError.new(err)
61
- when 'malformed_request'
62
- MalformedRequestError.new(err)
63
- when 'unsupported_asset'
64
- UnsupportedAssetError.new(err)
65
- when 'invalid_asset_id'
66
- InvalidAssetIDError.new(err)
67
- when 'invalid_destination'
68
- InvalidDestinationError.new(err)
69
- when 'invalid_network_id'
70
- InvalidNetworkIDError.new(err)
71
- when 'resource_exhausted'
72
- ResourceExhaustedError.new(err)
73
- when 'faucet_limit_reached'
74
- FaucetLimitReachedError.new(err)
75
- when 'invalid_signed_payload'
76
- InvalidSignedPayloadError.new(err)
77
- when 'invalid_transfer_status'
78
- InvalidTransferStatusError.new(err)
79
- when 'network_feature_unsupported'
80
- NetworkFeatureUnsupportedError.new(err)
35
+ message = body['message']
36
+ code = body['code']
37
+
38
+ if ERROR_CODE_TO_ERROR_CLASS.key?(code)
39
+ ERROR_CODE_TO_ERROR_CLASS[code].new(err, code: code, message: message)
81
40
  else
82
- APIError.new(err)
41
+ APIError.new(err, code: code, message: message, unhandled: true)
83
42
  end
84
43
  end
85
- # rubocop:enable Metrics/MethodLength
86
44
 
87
- # Returns a String representation of the APIError.
88
- # @return [String] a String representation of the APIError
45
+ # Override to_s to display a friendly error message
89
46
  def to_s
90
- "APIError{http_code: #{@http_code}, api_code: #{@api_code}, api_message: #{@api_message}}"
47
+ # For handled errors, display just the API message as that provides sufficient context.
48
+ return api_message if handled
49
+
50
+ # For unhandled errors, display the full error message
51
+ super
91
52
  end
92
53
 
93
- # Same as to_s.
94
- # @return [String] a String representation of the APIError
95
54
  def inspect
96
55
  to_s
97
56
  end
98
57
  end
99
58
 
59
+ # An error raised when an operation is attempted with insufficient funds.
60
+ class InsufficientFundsError < StandardError
61
+ def initialize(expected, exact, msg = 'Insufficient funds')
62
+ super("#{msg}: have #{exact}, need #{expected}.")
63
+ end
64
+ end
65
+
100
66
  class UnimplementedError < APIError; end
101
67
  class UnauthorizedError < APIError; end
102
68
  class InternalError < APIError; end
@@ -120,4 +86,30 @@ module Coinbase
120
86
  class InvalidSignedPayloadError < APIError; end
121
87
  class InvalidTransferStatusError < APIError; end
122
88
  class NetworkFeatureUnsupportedError < APIError; end
89
+
90
+ ERROR_CODE_TO_ERROR_CLASS = {
91
+ 'unimplemented' => UnimplementedError,
92
+ 'unauthorized' => UnauthorizedError,
93
+ 'internal' => InternalError,
94
+ 'not_found' => NotFoundError,
95
+ 'invalid_wallet_id' => InvalidWalletIDError,
96
+ 'invalid_address_id' => InvalidAddressIDError,
97
+ 'invalid_wallet' => InvalidWalletError,
98
+ 'invalid_address' => InvalidAddressError,
99
+ 'invalid_amount' => InvalidAmountError,
100
+ 'invalid_transfer_id' => InvalidTransferIDError,
101
+ 'invalid_page_token' => InvalidPageError,
102
+ 'invalid_page_limit' => InvalidLimitError,
103
+ 'already_exists' => AlreadyExistsError,
104
+ 'malformed_request' => MalformedRequestError,
105
+ 'unsupported_asset' => UnsupportedAssetError,
106
+ 'invalid_asset_id' => InvalidAssetIDError,
107
+ 'invalid_destination' => InvalidDestinationError,
108
+ 'invalid_network_id' => InvalidNetworkIDError,
109
+ 'resource_exhausted' => ResourceExhaustedError,
110
+ 'faucet_limit_reached' => FaucetLimitReachedError,
111
+ 'invalid_signed_payload' => InvalidSignedPayloadError,
112
+ 'invalid_transfer_status' => InvalidTransferStatusError,
113
+ 'network_feature_unsupported' => NetworkFeatureUnsupportedError
114
+ }.freeze
123
115
  end
@@ -12,8 +12,6 @@ module Coinbase
12
12
  @model = model
13
13
  end
14
14
 
15
- attr_reader :model
16
-
17
15
  # Returns the transaction hash.
18
16
  # @return [String] The onchain transaction hash
19
17
  def transaction_hash
@@ -23,8 +21,7 @@ module Coinbase
23
21
  # Returns the link to the transaction on the blockchain explorer.
24
22
  # @return [String] The link to the transaction on the blockchain explorer
25
23
  def transaction_link
26
- # TODO: Parameterize this by Network.
27
- "https://sepolia.basescan.org/tx/#{transaction_hash}"
24
+ model.transaction_link
28
25
  end
29
26
 
30
27
  # Returns a String representation of the FaucetTransaction.
@@ -38,5 +35,9 @@ module Coinbase
38
35
  def inspect
39
36
  to_s
40
37
  end
38
+
39
+ private
40
+
41
+ attr_reader :model
41
42
  end
42
43
  end
@@ -12,31 +12,15 @@ module Coinbase
12
12
  # @param protocol_family [String] The protocol family to which the Network belongs
13
13
  # (e.g., "evm")
14
14
  # @param is_testnet [Boolean] Whether the Network is a testnet
15
- # @param assets [Array<Asset>] The Assets supported by the Network
16
15
  # @param native_asset_id [String] The ID of the Network's native Asset
17
16
  # @param chain_id [Integer] The Chain ID of the Network
18
- def initialize(network_id:, display_name:, protocol_family:, is_testnet:, assets:, native_asset_id:, chain_id:)
17
+ def initialize(network_id:, display_name:, protocol_family:, is_testnet:, native_asset_id:, chain_id:)
19
18
  @network_id = network_id
20
19
  @display_name = display_name
21
20
  @protocol_family = protocol_family
22
21
  @is_testnet = is_testnet
22
+ @native_asset_id = native_asset_id
23
23
  @chain_id = chain_id
24
-
25
- @asset_map = {}
26
- assets.each do |asset|
27
- @asset_map[asset.asset_id] = asset
28
- end
29
-
30
- raise ArgumentError, 'Native Asset not found' unless @asset_map.key?(native_asset_id)
31
-
32
- @native_asset = @asset_map[native_asset_id]
33
- end
34
-
35
- # Lists the Assets supported by the Network.
36
- #
37
- # @return [Array<Asset>] The Assets supported by the Network
38
- def list_assets
39
- @asset_map.values
40
24
  end
41
25
 
42
26
  # Gets the Asset with the given ID.
@@ -44,12 +28,14 @@ module Coinbase
44
28
  # @param asset_id [Symbol] The ID of the Asset
45
29
  # @return [Asset] The Asset with the given ID
46
30
  def get_asset(asset_id)
47
- @asset_map[asset_id]
31
+ Asset.fetch(@network_id, asset_id)
48
32
  end
49
33
 
50
34
  # Gets the native Asset of the Network.
51
35
  #
52
36
  # @return [Asset] The native Asset of the Network
53
- attr_reader :native_asset
37
+ def native_asset
38
+ @native_asset ||= get_asset(@native_asset_id)
39
+ end
54
40
  end
55
41
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coinbase
4
+ # A module of helper methods for paginating through resources.
5
+ module Pagination
6
+ def self.enumerate(fetcher, &build_resource)
7
+ Enumerator.new do |yielder|
8
+ page = nil
9
+
10
+ loop do
11
+ response = Coinbase.call_api { fetcher.call(page) }
12
+
13
+ break if response.data.empty?
14
+
15
+ response.data.each do |model|
16
+ yielder << build_resource.call(model)
17
+ end
18
+
19
+ break unless response.has_more
20
+
21
+ page = response.next_page
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coinbase
4
+ # A representation of a staking operation (stake, unstake, claim rewards, etc). It
5
+ # may have multiple steps with some being transactions to sign, and others to wait.
6
+ # @attr_reader [Array<Coinbase::Transaction>] transactions The list of current
7
+ # transactions associated with the operation
8
+ class StakingOperation
9
+ attr_reader :transactions
10
+
11
+ # Returns a new StakingOperation object.
12
+ # @param model [Coinbase::Client::StakingOperation] The underlying StakingOperation object
13
+ def initialize(model)
14
+ @model = model
15
+
16
+ @transactions = model.transactions.map do |transaction_model|
17
+ Transaction.new(transaction_model)
18
+ end
19
+ end
20
+
21
+ # Signs the Open Transactions with the provided key
22
+ # @param key [Eth::Key] The key to sign the transactions with
23
+ def sign(key)
24
+ transactions.each do |transaction|
25
+ transaction.sign(key) unless transaction.signed?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Coinbase
6
+ # A representation of a staking reward earned on a network for a given asset.
7
+ class StakingReward
8
+ # Returns a list of StakingRewards for the provided network, asset, and addresses.
9
+ # @param network_id [Symbol] The network ID
10
+ # @param asset_id [Symbol] The asset ID
11
+ # @param address_ids [Array<String>] The address IDs
12
+ # @param start_time [Time] The start time. Defaults to one month ago.
13
+ # @param end_time [Time] The end time. Defaults to the current time.
14
+ # @param format [Symbol] The format to return the rewards in. (:usd, :native) Defaults to :usd.
15
+ # @return [Enumerable<Coinbase::StakingReward>] The staking rewards
16
+ def self.list(network_id, asset_id, address_ids, start_time: DateTime.now.prev_month(1), end_time: DateTime.now,
17
+ format: :usd)
18
+ asset = Coinbase.call_api do
19
+ Asset.fetch(network_id, asset_id)
20
+ end
21
+ Coinbase::Pagination.enumerate(
22
+ ->(page) { list_page(network_id, asset_id, address_ids, start_time, end_time, page, format) }
23
+ ) do |staking_reward|
24
+ new(staking_reward, asset, format)
25
+ end
26
+ end
27
+
28
+ # Returns a new StakingReward object.
29
+ # @param model [Coinbase::Client::StakingReward] The underlying StakingReward object
30
+ def initialize(model, asset, format)
31
+ @model = model
32
+ @asset = asset
33
+ @format = format
34
+ end
35
+
36
+ # Returns the amount of the StakingReward.
37
+ # @return [BigDecimal] The amount
38
+ def amount
39
+ return BigDecimal(@model.amount.to_i) / BigDecimal(100) if @format == :usd
40
+
41
+ @asset.from_atomic_amount(@model.amount.to_i)
42
+ end
43
+
44
+ # Returns the date of the StakingReward.
45
+ # @return [Time] The date
46
+ def date
47
+ @model.date
48
+ end
49
+
50
+ # Returns a string representation of the StakingReward.
51
+ # @return [String] a string representation of the StakingReward
52
+ def to_s
53
+ "Coinbase::StakingReward{amount: '#{amount}'}"
54
+ end
55
+
56
+ # Same as to_s.
57
+ # @return [String] a string representation of the StakingReward
58
+ def inspect
59
+ to_s
60
+ end
61
+
62
+ def self.stake_api
63
+ Coinbase::Client::StakeApi.new(Coinbase.configuration.api_client)
64
+ end
65
+
66
+ def self.list_page(network_id, asset_id, address_ids, start_time, end_time, page, format)
67
+ req = {
68
+ network_id: Coinbase.normalize_network(network_id),
69
+ asset_id: asset_id,
70
+ address_ids: address_ids,
71
+ start_time: start_time.iso8601,
72
+ end_time: end_time.iso8601,
73
+ format: format,
74
+ next_page: page
75
+ }
76
+ stake_api.fetch_staking_rewards(req)
77
+ end
78
+ end
79
+ end
@@ -110,6 +110,12 @@ module Coinbase
110
110
  raw.hex
111
111
  end
112
112
 
113
+ # Returns whether the Transaction has been signed.
114
+ # @return [Boolean] Whether the Transaction has been signed
115
+ def signed?
116
+ Eth::Tx.signed?(raw)
117
+ end
118
+
113
119
  # Returns a String representation of the Transaction.
114
120
  # @return [String] a String representation of the Transaction
115
121
  def to_s
data/lib/coinbase/user.rb CHANGED
@@ -37,53 +37,17 @@ module Coinbase
37
37
  Wallet.import(data)
38
38
  end
39
39
 
40
- # Lists the Wallets belonging to the User.
41
- # @param page_size [Integer] (Optional) the number of Wallets to return per page. Defaults to 10
42
- # @param next_page_token [String] (Optional) the token for the next page of Wallets
43
- # @return [Array<Coinbase::Wallet, String>] the Wallets belonging to the User and the pagination token, if
44
- # any.
45
- def wallets(page_size: 10, next_page_token: nil)
46
- opts = {
47
- limit: page_size
48
- }
49
-
50
- opts[:page] = next_page_token unless next_page_token.nil?
51
-
52
- wallet_list = Coinbase.call_api do
53
- wallets_api.list_wallets(opts)
54
- end
55
-
56
- # A map from wallet_id to address models.
57
- address_model_map = {}
58
-
59
- wallet_list.data.each do |wallet_model|
60
- addresses_list = Coinbase.call_api do
61
- addresses_api.list_addresses(wallet_model.id, { limit: Coinbase::Wallet::MAX_ADDRESSES })
62
- end
63
-
64
- address_model_map[wallet_model.id] = addresses_list.data
65
- end
66
-
67
- wallets = wallet_list.data.map do |wallet_model|
68
- Wallet.new(wallet_model, seed: '', address_models: address_model_map[wallet_model.id])
69
- end
70
-
71
- [wallets, wallet_list.next_page]
40
+ # Enumerates the Wallets belonging to the User.
41
+ # @return [Enumerator<Coinbase::Wallet>] the Wallets belonging to the User
42
+ def wallets
43
+ Wallet.list
72
44
  end
73
45
 
74
46
  # Returns the Wallet with the given ID.
75
47
  # @param wallet_id [String] the ID of the Wallet
76
48
  # @return [Coinbase::Wallet] the unhydrated Wallet
77
49
  def wallet(wallet_id)
78
- wallet_model = Coinbase.call_api do
79
- wallets_api.get_wallet(wallet_id)
80
- end
81
-
82
- addresses_list = Coinbase.call_api do
83
- addresses_api.list_addresses(wallet_model.id, { limit: Coinbase::Wallet::MAX_ADDRESSES })
84
- end
85
-
86
- Wallet.new(wallet_model, seed: '', address_models: addresses_list.data)
50
+ Wallet.fetch(wallet_id)
87
51
  end
88
52
 
89
53
  # Returns a string representation of the User.
@@ -97,15 +61,5 @@ module Coinbase
97
61
  def inspect
98
62
  to_s
99
63
  end
100
-
101
- private
102
-
103
- def addresses_api
104
- @addresses_api ||= Coinbase::Client::AddressesApi.new(Coinbase.configuration.api_client)
105
- end
106
-
107
- def wallets_api
108
- @wallets_api ||= Coinbase::Client::WalletsApi.new(Coinbase.configuration.api_client)
109
- end
110
64
  end
111
65
  end