coinbase-sdk 0.0.8 → 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 +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