coinbase-sdk 0.0.7 → 0.0.9

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 (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