coinbase-sdk 0.0.8 → 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 +1 -1
  6. data/lib/coinbase/authenticator.rb +2 -0
  7. data/lib/coinbase/client/api/addresses_api.rb +1 -1
  8. data/lib/coinbase/client/api/assets_api.rb +1 -1
  9. data/lib/coinbase/client/api/external_addresses_api.rb +1 -1
  10. data/lib/coinbase/client/api/server_signers_api.rb +1 -1
  11. data/lib/coinbase/client/api/stake_api.rb +1 -1
  12. data/lib/coinbase/client/api/trades_api.rb +1 -1
  13. data/lib/coinbase/client/api/transfers_api.rb +1 -1
  14. data/lib/coinbase/client/api/users_api.rb +1 -1
  15. data/lib/coinbase/client/api/wallets_api.rb +1 -1
  16. data/lib/coinbase/client/api_client.rb +3 -3
  17. data/lib/coinbase/client/api_error.rb +1 -1
  18. data/lib/coinbase/client/configuration.rb +1 -1
  19. data/lib/coinbase/client/models/address.rb +1 -1
  20. data/lib/coinbase/client/models/address_balance_list.rb +1 -1
  21. data/lib/coinbase/client/models/address_list.rb +1 -1
  22. data/lib/coinbase/client/models/asset.rb +1 -1
  23. data/lib/coinbase/client/models/balance.rb +1 -1
  24. data/lib/coinbase/client/models/broadcast_trade_request.rb +1 -1
  25. data/lib/coinbase/client/models/broadcast_transfer_request.rb +1 -1
  26. data/lib/coinbase/client/models/build_staking_operation_request.rb +1 -1
  27. data/lib/coinbase/client/models/create_address_request.rb +1 -1
  28. data/lib/coinbase/client/models/create_server_signer_request.rb +22 -5
  29. data/lib/coinbase/client/models/create_trade_request.rb +1 -1
  30. data/lib/coinbase/client/models/create_transfer_request.rb +1 -1
  31. data/lib/coinbase/client/models/create_wallet_request.rb +1 -1
  32. data/lib/coinbase/client/models/create_wallet_request_wallet.rb +1 -1
  33. data/lib/coinbase/client/models/error.rb +1 -1
  34. data/lib/coinbase/client/models/faucet_transaction.rb +23 -5
  35. data/lib/coinbase/client/models/feature.rb +1 -1
  36. data/lib/coinbase/client/models/fetch_staking_rewards200_response.rb +1 -1
  37. data/lib/coinbase/client/models/fetch_staking_rewards_request.rb +2 -15
  38. data/lib/coinbase/client/models/get_staking_context_request.rb +1 -1
  39. data/lib/coinbase/client/models/partial_eth_staking_context.rb +4 -7
  40. data/lib/coinbase/client/models/seed_creation_event.rb +1 -1
  41. data/lib/coinbase/client/models/seed_creation_event_result.rb +1 -1
  42. data/lib/coinbase/client/models/server_signer.rb +22 -5
  43. data/lib/coinbase/client/models/server_signer_event.rb +1 -1
  44. data/lib/coinbase/client/models/server_signer_event_event.rb +1 -1
  45. data/lib/coinbase/client/models/server_signer_event_list.rb +1 -1
  46. data/lib/coinbase/client/models/server_signer_list.rb +1 -1
  47. data/lib/coinbase/client/models/signature_creation_event.rb +1 -1
  48. data/lib/coinbase/client/models/signature_creation_event_result.rb +1 -1
  49. data/lib/coinbase/client/models/staking_context.rb +1 -1
  50. data/lib/coinbase/client/models/staking_context_context.rb +1 -1
  51. data/lib/coinbase/client/models/staking_operation.rb +15 -12
  52. data/lib/coinbase/client/models/staking_reward.rb +21 -5
  53. data/lib/coinbase/client/models/staking_reward_format.rb +40 -0
  54. data/lib/coinbase/client/models/trade.rb +1 -1
  55. data/lib/coinbase/client/models/trade_list.rb +1 -1
  56. data/lib/coinbase/client/models/transaction.rb +1 -1
  57. data/lib/coinbase/client/models/transaction_type.rb +1 -1
  58. data/lib/coinbase/client/models/transfer.rb +1 -1
  59. data/lib/coinbase/client/models/transfer_list.rb +1 -1
  60. data/lib/coinbase/client/models/user.rb +1 -1
  61. data/lib/coinbase/client/models/wallet.rb +1 -1
  62. data/lib/coinbase/client/models/wallet_list.rb +1 -1
  63. data/lib/coinbase/client/version.rb +1 -1
  64. data/lib/coinbase/client.rb +2 -1
  65. data/lib/coinbase/errors.rb +7 -0
  66. data/lib/coinbase/faucet_transaction.rb +5 -4
  67. data/lib/coinbase/pagination.rb +26 -0
  68. data/lib/coinbase/staking_operation.rb +29 -0
  69. data/lib/coinbase/staking_reward.rb +79 -0
  70. data/lib/coinbase/transaction.rb +6 -0
  71. data/lib/coinbase/user.rb +5 -51
  72. data/lib/coinbase/wallet.rb +95 -100
  73. data/lib/coinbase.rb +11 -0
  74. metadata +8 -19
  75. data/lib/coinbase/client/api/balances_api.rb +0 -97
  76. data/lib/coinbase/client/api/transfer_api.rb +0 -114
  77. data/lib/coinbase/client/models/request_faucet_funds200_response.rb +0 -222
@@ -6,7 +6,7 @@
6
6
  The version of the OpenAPI document: 0.0.1-alpha
7
7
  Contact: yuga.cohler@coinbase.com
8
8
  Generated by: https://openapi-generator.tech
9
- Generator version: 7.5.0
9
+ Generator version: 7.6.0
10
10
 
11
11
  =end
12
12
 
@@ -6,7 +6,7 @@
6
6
  The version of the OpenAPI document: 0.0.1-alpha
7
7
  Contact: yuga.cohler@coinbase.com
8
8
  Generated by: https://openapi-generator.tech
9
- Generator version: 7.5.0
9
+ Generator version: 7.6.0
10
10
 
11
11
  =end
12
12
 
@@ -6,7 +6,7 @@
6
6
  The version of the OpenAPI document: 0.0.1-alpha
7
7
  Contact: yuga.cohler@coinbase.com
8
8
  Generated by: https://openapi-generator.tech
9
- Generator version: 7.5.0
9
+ Generator version: 7.6.0
10
10
 
11
11
  =end
12
12
 
@@ -6,7 +6,7 @@
6
6
  The version of the OpenAPI document: 0.0.1-alpha
7
7
  Contact: yuga.cohler@coinbase.com
8
8
  Generated by: https://openapi-generator.tech
9
- Generator version: 7.5.0
9
+ Generator version: 7.6.0
10
10
 
11
11
  =end
12
12
 
@@ -6,7 +6,7 @@
6
6
  The version of the OpenAPI document: 0.0.1-alpha
7
7
  Contact: yuga.cohler@coinbase.com
8
8
  Generated by: https://openapi-generator.tech
9
- Generator version: 7.5.0
9
+ Generator version: 7.6.0
10
10
 
11
11
  =end
12
12
 
@@ -51,6 +51,7 @@ Coinbase::Client.autoload :StakingContext, 'coinbase/client/models/staking_conte
51
51
  Coinbase::Client.autoload :StakingContextContext, 'coinbase/client/models/staking_context_context'
52
52
  Coinbase::Client.autoload :StakingOperation, 'coinbase/client/models/staking_operation'
53
53
  Coinbase::Client.autoload :StakingReward, 'coinbase/client/models/staking_reward'
54
+ Coinbase::Client.autoload :StakingRewardFormat, 'coinbase/client/models/staking_reward_format'
54
55
  Coinbase::Client.autoload :Trade, 'coinbase/client/models/trade'
55
56
  Coinbase::Client.autoload :TradeList, 'coinbase/client/models/trade_list'
56
57
  Coinbase::Client.autoload :Transaction, 'coinbase/client/models/transaction'
@@ -56,6 +56,13 @@ module Coinbase
56
56
  end
57
57
  end
58
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
+
59
66
  class UnimplementedError < APIError; end
60
67
  class UnauthorizedError < APIError; end
61
68
  class InternalError < APIError; 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
@@ -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
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'digest'
4
- require 'jimson'
5
4
  require 'json'
6
5
  require 'money-tree'
7
6
  require 'securerandom'
@@ -9,14 +8,14 @@ require 'securerandom'
9
8
  module Coinbase
10
9
  # A representation of a Wallet. Wallets come with a single default Address, but can expand to have a set of Addresses,
11
10
  # each of which can hold a balance of one or more Assets. Wallets can create new Addresses, list their addresses,
12
- # list their balances, and transfer Assets to other Addresses. Wallets should be created through User#create_wallet or
13
- # User#import_wallet.
11
+ # list their balances, and transfer Assets to other Addresses.
14
12
  class Wallet
15
- attr_reader :addresses, :model
16
-
17
13
  # The maximum number of addresses in a Wallet.
18
14
  MAX_ADDRESSES = 20
19
15
 
16
+ # The maximum number of wallets to fetch in a single page.
17
+ PAGE_LIMIT = 100
18
+
20
19
  # A representation of ServerSigner status in a Wallet.
21
20
  module ServerSignerStatus
22
21
  # The Wallet is awaiting seed creation by the ServerSigner. At this point,
@@ -39,11 +38,31 @@ module Coinbase
39
38
  wallets_api.get_wallet(data.wallet_id)
40
39
  end
41
40
 
42
- address_list = Coinbase.call_api do
43
- addresses_api.list_addresses(model.id, { limit: MAX_ADDRESSES })
41
+ new(model, seed: data.seed)
42
+ end
43
+
44
+ # Enumerates the wallets for the requesting user.
45
+ # The result is an enumerator that lazily fetches from the server, and can be iterated over,
46
+ # converted to an array, etc...
47
+ # @return [Enumerable<Coinbase::Wallet>] Enumerator that returns wallets
48
+ def list
49
+ Coinbase::Pagination.enumerate(lambda(&method(:fetch_wallets_page))) do |wallet|
50
+ Coinbase::Wallet.new(wallet, seed: '')
51
+ end
52
+ end
53
+
54
+ # Fetches a Wallet by its ID.
55
+ # The returned wallet can be immediately used for signing operations if backed by a server signer.
56
+ # If the wallet is not backed by a server signer, the wallet's seed will need to be set before
57
+ # it can be used for signing operations.
58
+ # @param wallet_id [String] The ID of the Wallet to fetch
59
+ # @return [Coinbase::Wallet] The fetched Wallet
60
+ def fetch(wallet_id)
61
+ model = Coinbase.call_api do
62
+ wallets_api.get_wallet(wallet_id)
44
63
  end
45
64
 
46
- new(model, seed: data.seed, address_models: address_list.data)
65
+ new(model, seed: '')
47
66
  end
48
67
 
49
68
  # Creates a new Wallet on the specified Network and generate a default address for it.
@@ -58,7 +77,7 @@ module Coinbase
58
77
  wallets_api.create_wallet(
59
78
  create_wallet_request: {
60
79
  wallet: {
61
- network_id: network_id,
80
+ network_id: Coinbase.normalize_network(network_id),
62
81
  use_server_signer: Coinbase.use_server_signer?
63
82
  }
64
83
  }
@@ -103,7 +122,6 @@ module Coinbase
103
122
  self
104
123
  end
105
124
 
106
- # TODO: Memoize these objects in a thread-safe way at the top-level.
107
125
  def addresses_api
108
126
  Coinbase::Client::AddressesApi.new(Coinbase.configuration.api_client)
109
127
  end
@@ -111,6 +129,10 @@ module Coinbase
111
129
  def wallets_api
112
130
  Coinbase::Client::WalletsApi.new(Coinbase.configuration.api_client)
113
131
  end
132
+
133
+ def fetch_wallets_page(page)
134
+ wallets_api.list_wallets({ limit: PAGE_LIMIT, page: page })
135
+ end
114
136
  end
115
137
 
116
138
  # Returns a new Wallet object. Do not use this method directly. Instead, use User#create_wallet or
@@ -119,21 +141,30 @@ module Coinbase
119
141
  # @param seed [String] (Optional) The seed to use for the Wallet. Expects a 32-byte hexadecimal with no 0x prefix.
120
142
  # If nil, a new seed will be generated. If the empty string, no seed is generated, and the Wallet will be
121
143
  # instantiated without a seed and its corresponding private keys.
122
- # @param address_models [Array<Coinbase::Client::Address>] (Optional) The models of the addresses already registered
123
144
  # with the Wallet. If not provided, the Wallet will derive the first default address.
124
- # @param client [Jimson::Client] (Optional) The JSON RPC client to use for interacting with the Network
125
- def initialize(model, seed: nil, address_models: [])
126
- validate_seed_and_address_models(seed, address_models) unless Coinbase.use_server_signer?
145
+ def initialize(model, seed: nil)
146
+ raise ArgumentError, 'model must be a Wallet' unless model.is_a?(Coinbase::Client::Wallet)
127
147
 
128
148
  @model = model
129
- @addresses = []
130
149
 
131
- unless Coinbase.use_server_signer?
132
- @master = master_node(seed)
133
- @private_key_index = 0
134
- end
150
+ return if Coinbase.use_server_signer?
151
+
152
+ @master = master_node(seed)
153
+ end
154
+
155
+ # Returns the addresses belonging to the Wallet.
156
+ # @return [Array<Coinbase::WalletAddress>] The addresses belonging to the Wallet
157
+ def addresses
158
+ @addresses ||= begin
159
+ address_list = Coinbase.call_api do
160
+ addresses_api.list_addresses(@model.id, { limit: MAX_ADDRESSES })
161
+ end
135
162
 
136
- derive_addresses(address_models)
163
+ # Build the WalletAddress objects, injecting the key if available.
164
+ address_list.data.each_with_index.map do |address_model, index|
165
+ build_wallet_address(address_model, index)
166
+ end
167
+ end
137
168
  end
138
169
 
139
170
  # Returns the Wallet ID.
@@ -157,18 +188,22 @@ module Coinbase
157
188
  # Sets the seed of the Wallet. This seed is used to derive keys and sign transactions.
158
189
  # @param seed [String] The seed to set. Expects a 32-byte hexadecimal with no 0x prefix.
159
190
  def seed=(seed)
160
- raise ArgumentError, 'Seed must be 32 bytes' if seed.length != 64
161
- raise 'Seed is already set' unless @master.nil?
162
- raise 'Cannot set seed for Wallet with non-zero private key index' if @private_key_index.positive?
191
+ raise ArgumentError, 'Seed must not be empty' if seed.nil? || seed.empty?
192
+ raise StandardError, 'Seed is already set' unless @master.nil?
193
+
194
+ @master = master_node(seed)
163
195
 
164
- @master = MoneyTree::Master.new(seed_hex: seed)
196
+ # If the addresses are not loaded the keys will be set on them whenever they are loaded.
197
+ return if @addresses.nil?
165
198
 
166
- @addresses.each do
167
- key = derive_key
168
- a = address(key.address.to_s)
169
- raise "Seed does not match wallet; cannot find address #{key.address}" if a.nil?
199
+ # If addresses are already loaded, set the keys on each address.
200
+ addresses.each_with_index.each do |address, index|
201
+ key = derive_key(index)
170
202
 
171
- a.key = key
203
+ # If we derive a key the derived address must match the address from the API.
204
+ raise StandardError, 'Seed does not match wallet' unless address.id == key.address.to_s
205
+
206
+ address.key = key
172
207
  end
173
208
  end
174
209
 
@@ -178,7 +213,10 @@ module Coinbase
178
213
  opts = { create_address_request: {} }
179
214
 
180
215
  unless Coinbase.use_server_signer?
181
- key = derive_key
216
+ # The index for the next address is the number of addresses already registered.
217
+ private_key_index = addresses.count
218
+
219
+ key = derive_key(private_key_index)
182
220
 
183
221
  opts = {
184
222
  create_address_request: {
@@ -193,9 +231,12 @@ module Coinbase
193
231
  end
194
232
 
195
233
  # Auto-reload wallet to set default address on first address creation.
196
- reload if addresses.empty?
234
+ reload if default_address.nil?
197
235
 
198
- cache_address(address_model, key)
236
+ # Cache the address in our memoized list
237
+ address = WalletAddress.new(address_model, key)
238
+ @addresses << address
239
+ address
199
240
  end
200
241
 
201
242
  # Returns the default address of the Wallet.
@@ -208,7 +249,7 @@ module Coinbase
208
249
  # @param address_id [String] The ID of the Address to retrieve
209
250
  # @return [Address] The Address
210
251
  def address(address_id)
211
- @addresses.find { |address| address.id == address_id }
252
+ addresses.find { |address| address.id == address_id }
212
253
  end
213
254
 
214
255
  # Returns the list of balances of this Wallet. Balances are aggregated across all Addresses in the Wallet.
@@ -413,10 +454,13 @@ module Coinbase
413
454
  end
414
455
  end
415
456
 
457
+ # Returns the master node for the given seed.
416
458
  def master_node(seed)
417
459
  return MoneyTree::Master.new if seed.nil?
418
460
  return nil if seed.empty?
419
461
 
462
+ validate_seed(seed)
463
+
420
464
  MoneyTree::Master.new(seed_hex: seed)
421
465
  end
422
466
 
@@ -430,68 +474,15 @@ module Coinbase
430
474
  end
431
475
  end
432
476
 
433
- # Derives the registered Addresses in the Wallet.
434
- # @param address_models [Array<Coinbase::Client::Address>] The models of the addresses already registered with the
435
- # Wallet
436
- def derive_addresses(address_models)
437
- return unless address_models.any?
438
-
439
- # Create a map tracking which addresses are already registered with the Wallet.
440
- address_map = build_address_map(address_models)
441
-
442
- address_models.each do |address_model|
443
- # Derive the addresses using the provided models.
444
- derive_address(address_map, address_model)
445
- end
446
- end
447
-
448
- # Derives an already registered Address in the Wallet.
449
- # @param address_map [Hash<String, Boolean>] The map of registered Address IDs
450
- # @param address_model [Coinbase::Client::Address] The Address model
451
- # @return [Address] The new Address
452
- def derive_address(address_map, address_model)
453
- key = @master.nil? ? nil : derive_key
454
-
455
- unless key.nil?
456
- address_from_key = key.address.to_s
457
- raise 'Invalid address' if address_map[address_from_key].nil?
458
- end
459
-
460
- cache_address(address_model, key)
461
- end
462
-
463
- # Derives a key for an already registered Address in the Wallet.
477
+ # Derives a key for the given address index.
464
478
  # @return [Eth::Key] The new key
465
- def derive_key
479
+ def derive_key(index)
466
480
  raise 'Cannot derive key for Wallet without seed loaded' if @master.nil?
467
481
 
468
- path = "#{address_path_prefix}/#{@private_key_index}"
482
+ path = "#{address_path_prefix}/#{index}"
469
483
  private_key = @master.node_for_path(path).private_key.to_hex
470
- @private_key_index += 1
471
- Eth::Key.new(priv: private_key)
472
- end
473
-
474
- # Caches an Address on the client-side and increments the address index.
475
- # @param address_model [Coinbase::Client::Address] The Address model
476
- # @param key [Eth::Key] The private key of the Address
477
- # @return [Address] The new Address
478
- def cache_address(address_model, key)
479
- address = Address.new(address_model, key)
480
- @addresses << address
481
- address
482
- end
483
484
 
484
- # Builds a Hash of the registered Addresses.
485
- # @param address_models [Array<Coinbase::Client::Address>] The models of the addresses already registered with the
486
- # Wallet
487
- # @return [Hash<String, Boolean>] The Hash of registered Addresses
488
- def build_address_map(address_models)
489
- address_map = {}
490
- address_models.each do |address_model|
491
- address_map[address_model.address_id] = true
492
- end
493
-
494
- address_map
485
+ Eth::Key.new(priv: private_key)
495
486
  end
496
487
 
497
488
  # Creates an attestation for the Address currently being created.
@@ -520,16 +511,9 @@ module Coinbase
520
511
 
521
512
  # Validates the seed and address models passed to the constructor.
522
513
  # @param seed [String] The seed to use for the Wallet
523
- # @param address_models [Array<Coinbase::Client::Address>] The models of the addresses already registered with the
524
- # Wallet
525
- def validate_seed_and_address_models(seed, address_models)
526
- raise ArgumentError, 'Seed must be 32 bytes' if !seed.nil? && !seed.empty? && seed.length != 64
527
-
528
- raise ArgumentError, 'Seed must be present if address_models are provided' if seed.nil? && address_models.any?
529
-
530
- return unless !seed.nil? && seed.empty? && address_models.empty?
531
-
532
- raise ArgumentError, 'Seed must be empty if address_models are not provided'
514
+ # @raise [ArgumentError] If the seed is invalid
515
+ def validate_seed(seed)
516
+ raise ArgumentError, 'Seed must be 32 bytes' unless seed.length == 64
533
517
  end
534
518
 
535
519
  # Loads the Hash of Wallet seeds from the given file.
@@ -551,6 +535,17 @@ module Coinbase
551
535
  pk.dh_compute_key(public_key)
552
536
  end
553
537
 
538
+ def build_wallet_address(address_model, index)
539
+ # Return an unhydrated wallet address is no master seed is set.
540
+ return WalletAddress.new(address_model, nil) if @master.nil?
541
+
542
+ key = derive_key(index)
543
+
544
+ raise StandardError, 'Seed does not match wallet' unless address_model.address_id == key.address.to_s
545
+
546
+ WalletAddress.new(address_model, key)
547
+ end
548
+
554
549
  def addresses_api
555
550
  @addresses_api ||= Coinbase::Client::AddressesApi.new(Coinbase.configuration.api_client)
556
551
  end
data/lib/coinbase.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'coinbase/address'
4
+ require_relative 'coinbase/address/wallet_address'
5
+ require_relative 'coinbase/address/external_address'
4
6
  require_relative 'coinbase/asset'
5
7
  require_relative 'coinbase/authenticator'
6
8
  require_relative 'coinbase/balance'
@@ -11,12 +13,15 @@ require_relative 'coinbase/errors'
11
13
  require_relative 'coinbase/faucet_transaction'
12
14
  require_relative 'coinbase/middleware'
13
15
  require_relative 'coinbase/network'
16
+ require_relative 'coinbase/pagination'
14
17
  require_relative 'coinbase/trade'
15
18
  require_relative 'coinbase/transfer'
16
19
  require_relative 'coinbase/transaction'
17
20
  require_relative 'coinbase/user'
18
21
  require_relative 'coinbase/wallet'
19
22
  require_relative 'coinbase/server_signer'
23
+ require_relative 'coinbase/staking_operation'
24
+ require_relative 'coinbase/staking_reward'
20
25
  require 'json'
21
26
 
22
27
  # The Coinbase SDK.
@@ -129,4 +134,10 @@ module Coinbase
129
134
  def self.use_server_signer?
130
135
  Coinbase.configuration.use_server_signer
131
136
  end
137
+
138
+ # Returns whether the SDK is configured.
139
+ # @return [bool] whether the SDK is configured
140
+ def self.configured?
141
+ !Coinbase.configuration.api_key_name.nil? && !Coinbase.configuration.api_key_private_key.nil?
142
+ end
132
143
  end