coinbase-sdk 0.0.3 → 0.0.5
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.
- checksums.yaml +4 -4
- data/lib/coinbase/address.rb +45 -75
- data/lib/coinbase/asset.rb +72 -1
- data/lib/coinbase/balance.rb +51 -0
- data/lib/coinbase/balance_map.rb +22 -10
- data/lib/coinbase/middleware.rb +4 -1
- data/lib/coinbase/transfer.rb +2 -2
- data/lib/coinbase/user.rb +40 -34
- data/lib/coinbase/wallet.rb +199 -74
- data/lib/coinbase.rb +1 -25
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98cd32134ee8acb14c9dc018f39e7af188646b546998c4182cd50d7ae75f2816
|
|
4
|
+
data.tar.gz: df6ff19c63798ec10f054972aa75edef79fe65ae39c93cbe0941c77db251e5d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a209d25aa72fc185fb667a5ef7b8de617341dba449b0a02b15ad95bc0c434140932353e12258f4665b35ef60e37b5346efca76db811cc5a308cfc69b907f37b8
|
|
7
|
+
data.tar.gz: 8856f048ea3ecb664559665e2422d60b6c12d607526aea4bea75f8e8273cf0584b2206e14dcb4e99bdca3436114b03961558be36029b40614d018028f9d41183
|
data/lib/coinbase/address.rb
CHANGED
|
@@ -15,7 +15,7 @@ module Coinbase
|
|
|
15
15
|
# Returns a new Address object. Do not use this method directly. Instead, use Wallet#create_address, or use
|
|
16
16
|
# the Wallet's default_address.
|
|
17
17
|
# @param model [Coinbase::Client::Address] The underlying Address object
|
|
18
|
-
# @param key [Eth::Key] The key backing the Address
|
|
18
|
+
# @param key [Eth::Key] The key backing the Address. Can be nil.
|
|
19
19
|
def initialize(model, key)
|
|
20
20
|
@model = model
|
|
21
21
|
@key = key
|
|
@@ -35,45 +35,40 @@ module Coinbase
|
|
|
35
35
|
|
|
36
36
|
# Returns the Address ID.
|
|
37
37
|
# @return [String] The Address ID
|
|
38
|
-
def
|
|
38
|
+
def id
|
|
39
39
|
@model.address_id
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# Sets the private key backing the Address. This key is used to sign transactions.
|
|
43
|
+
# @param key [Eth::Key] The key backing the Address
|
|
44
|
+
def key=(key)
|
|
45
|
+
raise 'Private key is already set' unless @key.nil?
|
|
46
|
+
|
|
47
|
+
@key = key
|
|
48
|
+
end
|
|
49
|
+
|
|
42
50
|
# Returns the balances of the Address.
|
|
43
51
|
# @return [BalanceMap] The balances of the Address, keyed by asset ID. Ether balances are denominated
|
|
44
52
|
# in ETH.
|
|
45
|
-
def
|
|
53
|
+
def balances
|
|
46
54
|
response = Coinbase.call_api do
|
|
47
|
-
addresses_api.list_address_balances(wallet_id,
|
|
55
|
+
addresses_api.list_address_balances(wallet_id, id)
|
|
48
56
|
end
|
|
49
57
|
|
|
50
|
-
Coinbase.
|
|
58
|
+
Coinbase::BalanceMap.from_balances(response.data)
|
|
51
59
|
end
|
|
52
60
|
|
|
53
61
|
# Returns the balance of the provided Asset.
|
|
54
62
|
# @param asset_id [Symbol] The Asset to retrieve the balance for
|
|
55
63
|
# @return [BigDecimal] The balance of the Asset
|
|
56
|
-
def
|
|
57
|
-
normalized_asset_id = normalize_asset_id(asset_id)
|
|
58
|
-
|
|
64
|
+
def balance(asset_id)
|
|
59
65
|
response = Coinbase.call_api do
|
|
60
|
-
addresses_api.get_address_balance(wallet_id,
|
|
66
|
+
addresses_api.get_address_balance(wallet_id, id, Coinbase::Asset.primary_denomination(asset_id).to_s)
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
return BigDecimal('0') if response.nil?
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
case asset_id
|
|
68
|
-
when :eth
|
|
69
|
-
amount / BigDecimal(Coinbase::WEI_PER_ETHER.to_s)
|
|
70
|
-
when :gwei
|
|
71
|
-
amount / BigDecimal(Coinbase::GWEI_PER_ETHER.to_s)
|
|
72
|
-
when :usdc
|
|
73
|
-
amount / BigDecimal(Coinbase::ATOMIC_UNITS_PER_USDC.to_s)
|
|
74
|
-
else
|
|
75
|
-
amount
|
|
76
|
-
end
|
|
71
|
+
Coinbase::Balance.from_model_and_asset_id(response, asset_id).amount
|
|
77
72
|
end
|
|
78
73
|
|
|
79
74
|
# Transfers the given amount of the given Asset to the given address. Only same-Network Transfers are supported.
|
|
@@ -83,36 +78,34 @@ module Coinbase
|
|
|
83
78
|
# default address. If a String, interprets it as the address ID.
|
|
84
79
|
# @return [String] The hash of the Transfer transaction.
|
|
85
80
|
def transfer(amount, asset_id, destination)
|
|
86
|
-
raise
|
|
81
|
+
raise 'Cannot transfer from address without private key loaded' if @key.nil?
|
|
82
|
+
|
|
83
|
+
raise ArgumentError, "Unsupported asset: #{asset_id}" unless Coinbase::Asset.supported?(asset_id)
|
|
87
84
|
|
|
88
85
|
if destination.is_a?(Wallet)
|
|
89
86
|
raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != network_id
|
|
90
87
|
|
|
91
|
-
destination = destination.default_address.
|
|
88
|
+
destination = destination.default_address.id
|
|
92
89
|
elsif destination.is_a?(Address)
|
|
93
90
|
raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != network_id
|
|
94
91
|
|
|
95
|
-
destination = destination.
|
|
92
|
+
destination = destination.id
|
|
96
93
|
end
|
|
97
94
|
|
|
98
|
-
current_balance =
|
|
95
|
+
current_balance = balance(asset_id)
|
|
99
96
|
if current_balance < amount
|
|
100
97
|
raise ArgumentError, "Insufficient funds: #{amount} requested, but only #{current_balance} available"
|
|
101
98
|
end
|
|
102
99
|
|
|
103
|
-
normalized_amount = normalize_asset_amount(amount, asset_id)
|
|
104
|
-
|
|
105
|
-
normalized_asset_id = normalize_asset_id(asset_id)
|
|
106
|
-
|
|
107
100
|
create_transfer_request = {
|
|
108
|
-
amount:
|
|
101
|
+
amount: Coinbase::Asset.to_atomic_amount(amount, asset_id).to_i.to_s,
|
|
109
102
|
network_id: network_id,
|
|
110
|
-
asset_id:
|
|
103
|
+
asset_id: Coinbase::Asset.primary_denomination(asset_id).to_s,
|
|
111
104
|
destination: destination
|
|
112
105
|
}
|
|
113
106
|
|
|
114
107
|
transfer_model = Coinbase.call_api do
|
|
115
|
-
transfers_api.create_transfer(wallet_id,
|
|
108
|
+
transfers_api.create_transfer(wallet_id, id, create_transfer_request)
|
|
116
109
|
end
|
|
117
110
|
|
|
118
111
|
transfer = Coinbase::Transfer.new(transfer_model)
|
|
@@ -127,16 +120,22 @@ module Coinbase
|
|
|
127
120
|
}
|
|
128
121
|
|
|
129
122
|
transfer_model = Coinbase.call_api do
|
|
130
|
-
transfers_api.broadcast_transfer(wallet_id,
|
|
123
|
+
transfers_api.broadcast_transfer(wallet_id, id, transfer.id, broadcast_transfer_request)
|
|
131
124
|
end
|
|
132
125
|
|
|
133
126
|
Coinbase::Transfer.new(transfer_model)
|
|
134
127
|
end
|
|
135
128
|
|
|
129
|
+
# Returns whether the Address has a private key backing it to sign transactions.
|
|
130
|
+
# @return [Boolean] Whether the Address has a private key backing it to sign transactions.
|
|
131
|
+
def can_sign?
|
|
132
|
+
!@key.nil?
|
|
133
|
+
end
|
|
134
|
+
|
|
136
135
|
# Returns a String representation of the Address.
|
|
137
136
|
# @return [String] a String representation of the Address
|
|
138
137
|
def to_s
|
|
139
|
-
"Coinbase::Address{
|
|
138
|
+
"Coinbase::Address{id: '#{id}', network_id: '#{network_id}', wallet_id: '#{wallet_id}'}"
|
|
140
139
|
end
|
|
141
140
|
|
|
142
141
|
# Same as to_s.
|
|
@@ -148,75 +147,46 @@ module Coinbase
|
|
|
148
147
|
# Requests funds for the address from the faucet and returns the faucet transaction.
|
|
149
148
|
# This is only supported on testnet networks.
|
|
150
149
|
# @return [Coinbase::FaucetTransaction] The successful faucet transaction
|
|
151
|
-
# @raise [Coinbase::
|
|
150
|
+
# @raise [Coinbase::FaucetLimitReachedError] If the faucet limit has been reached for the address or user.
|
|
152
151
|
# @raise [Coinbase::Client::ApiError] If an unexpected error occurs while requesting faucet funds.
|
|
153
152
|
def faucet
|
|
154
153
|
Coinbase.call_api do
|
|
155
|
-
Coinbase::FaucetTransaction.new(addresses_api.request_faucet_funds(wallet_id,
|
|
154
|
+
Coinbase::FaucetTransaction.new(addresses_api.request_faucet_funds(wallet_id, id))
|
|
156
155
|
end
|
|
157
156
|
end
|
|
158
157
|
|
|
159
158
|
# Exports the Address's private key to a hex string.
|
|
160
159
|
# @return [String] The Address's private key as a hex String
|
|
161
160
|
def export
|
|
161
|
+
raise 'Cannot export key without private key loaded' if @key.nil?
|
|
162
|
+
|
|
162
163
|
@key.private_hex
|
|
163
164
|
end
|
|
164
165
|
|
|
165
|
-
#
|
|
166
|
-
# @return [Array<
|
|
167
|
-
def
|
|
168
|
-
|
|
166
|
+
# Returns all of the transfers associated with the address.
|
|
167
|
+
# @return [Array<Coinbase::Transfer>] The transfers associated with the address
|
|
168
|
+
def transfers
|
|
169
|
+
transfers = []
|
|
169
170
|
page = nil
|
|
170
171
|
|
|
171
172
|
loop do
|
|
173
|
+
puts "fetch transfers page: #{page}"
|
|
172
174
|
response = Coinbase.call_api do
|
|
173
|
-
transfers_api.list_transfers(wallet_id,
|
|
175
|
+
transfers_api.list_transfers(wallet_id, id, { limit: 100, page: page })
|
|
174
176
|
end
|
|
175
177
|
|
|
176
|
-
|
|
178
|
+
transfers.concat(response.data.map { |transfer| Coinbase::Transfer.new(transfer) }) if response.data
|
|
177
179
|
|
|
178
180
|
break unless response.has_more
|
|
179
181
|
|
|
180
182
|
page = response.next_page
|
|
181
183
|
end
|
|
182
184
|
|
|
183
|
-
|
|
185
|
+
transfers
|
|
184
186
|
end
|
|
185
187
|
|
|
186
188
|
private
|
|
187
189
|
|
|
188
|
-
# Normalizes the amount of the Asset to send to the atomic unit.
|
|
189
|
-
# @param amount [Integer, Float, BigDecimal] The amount to normalize
|
|
190
|
-
# @param asset_id [Symbol] The ID of the Asset being transferred
|
|
191
|
-
# @return [BigDecimal] The normalized amount in atomic units
|
|
192
|
-
def normalize_asset_amount(amount, asset_id)
|
|
193
|
-
big_amount = BigDecimal(amount.to_s)
|
|
194
|
-
|
|
195
|
-
case asset_id
|
|
196
|
-
when :eth
|
|
197
|
-
big_amount * Coinbase::WEI_PER_ETHER
|
|
198
|
-
when :gwei
|
|
199
|
-
big_amount * Coinbase::WEI_PER_GWEI
|
|
200
|
-
when :usdc
|
|
201
|
-
big_amount * Coinbase::ATOMIC_UNITS_PER_USDC
|
|
202
|
-
when :weth
|
|
203
|
-
big_amount * Coinbase::WEI_PER_ETHER
|
|
204
|
-
else
|
|
205
|
-
big_amount
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# Normalizes the asset ID to use during requests.
|
|
210
|
-
# @param asset_id [Symbol] The asset ID to normalize
|
|
211
|
-
# @return [Symbol] The normalized asset ID
|
|
212
|
-
def normalize_asset_id(asset_id)
|
|
213
|
-
if %i[wei gwei].include?(asset_id)
|
|
214
|
-
:eth
|
|
215
|
-
else
|
|
216
|
-
asset_id
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
190
|
def addresses_api
|
|
221
191
|
@addresses_api ||= Coinbase::Client::AddressesApi.new(Coinbase.configuration.api_client)
|
|
222
192
|
end
|
data/lib/coinbase/asset.rb
CHANGED
|
@@ -3,7 +3,63 @@
|
|
|
3
3
|
module Coinbase
|
|
4
4
|
# A representation of an Asset.
|
|
5
5
|
class Asset
|
|
6
|
-
|
|
6
|
+
# Retuns whether the provided asset ID is supported.
|
|
7
|
+
# @param asset_id [Symbol] The Asset ID
|
|
8
|
+
# @return [Boolean] Whether the Asset ID is supported
|
|
9
|
+
def self.supported?(asset_id)
|
|
10
|
+
!!Coinbase::SUPPORTED_ASSET_IDS[asset_id]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Converts the amount of the Asset to the atomic units of the primary denomination of the Asset.
|
|
14
|
+
# @param amount [Integer, Float, BigDecimal] The amount to normalize
|
|
15
|
+
# @param asset_id [Symbol] The ID of the Asset being transferred
|
|
16
|
+
# @return [BigDecimal] The normalized amount in atomic units
|
|
17
|
+
def self.to_atomic_amount(amount, asset_id)
|
|
18
|
+
case asset_id
|
|
19
|
+
when :eth
|
|
20
|
+
amount * BigDecimal(Coinbase::WEI_PER_ETHER.to_s)
|
|
21
|
+
when :gwei
|
|
22
|
+
amount * BigDecimal(Coinbase::WEI_PER_GWEI.to_s)
|
|
23
|
+
when :usdc
|
|
24
|
+
amount * BigDecimal(Coinbase::ATOMIC_UNITS_PER_USDC.to_s)
|
|
25
|
+
when :weth
|
|
26
|
+
amount * BigDecimal(Coinbase::WEI_PER_ETHER)
|
|
27
|
+
else
|
|
28
|
+
amount
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Converts an amount from the atomic value of the primary denomination of the provided Asset ID
|
|
33
|
+
# to whole units of the specified asset ID.
|
|
34
|
+
# @param atomic_amount [BigDecimal] The amount in atomic units
|
|
35
|
+
# @param asset_id [Symbol] The Asset ID
|
|
36
|
+
# @return [BigDecimal] The amount in whole units of the specified asset ID
|
|
37
|
+
def self.from_atomic_amount(atomic_amount, asset_id)
|
|
38
|
+
case asset_id
|
|
39
|
+
when :eth
|
|
40
|
+
atomic_amount / BigDecimal(Coinbase::WEI_PER_ETHER.to_s)
|
|
41
|
+
when :gwei
|
|
42
|
+
atomic_amount / BigDecimal(Coinbase::WEI_PER_GWEI.to_s)
|
|
43
|
+
when :usdc
|
|
44
|
+
atomic_amount / BigDecimal(Coinbase::ATOMIC_UNITS_PER_USDC.to_s)
|
|
45
|
+
when :weth
|
|
46
|
+
atomic_amount / BigDecimal(Coinbase::WEI_PER_ETHER)
|
|
47
|
+
else
|
|
48
|
+
atomic_amount
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns the primary denomination for the provided Asset ID.
|
|
53
|
+
# For assets with multiple denominations, e.g. eth can also be denominated in wei and gwei,
|
|
54
|
+
# this method will return the primary denomination.
|
|
55
|
+
# e.g. eth.
|
|
56
|
+
# @param asset_id [Symbol] The Asset ID
|
|
57
|
+
# @return [Symbol] The primary denomination for the Asset ID
|
|
58
|
+
def self.primary_denomination(asset_id)
|
|
59
|
+
return :eth if %i[wei gwei].include?(asset_id)
|
|
60
|
+
|
|
61
|
+
asset_id
|
|
62
|
+
end
|
|
7
63
|
|
|
8
64
|
# Returns a new Asset object. Do not use this method. Instead, use the Asset constants defined in
|
|
9
65
|
# the Coinbase module.
|
|
@@ -17,5 +73,20 @@ module Coinbase
|
|
|
17
73
|
@display_name = display_name
|
|
18
74
|
@address_id = address_id
|
|
19
75
|
end
|
|
76
|
+
|
|
77
|
+
attr_reader :network_id, :asset_id, :display_name, :address_id
|
|
78
|
+
|
|
79
|
+
# Returns a string representation of the Asset.
|
|
80
|
+
# @return [String] a string representation of the Asset
|
|
81
|
+
def to_s
|
|
82
|
+
"Coinbase::Asset{network_id: '#{network_id}', asset_id: '#{asset_id}', display_name: '#{display_name}'" +
|
|
83
|
+
(address_id.nil? ? '}' : ", address_id: '#{address_id}'}")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Same as to_s.
|
|
87
|
+
# @return [String] a string representation of the Balance
|
|
88
|
+
def inspect
|
|
89
|
+
to_s
|
|
90
|
+
end
|
|
20
91
|
end
|
|
21
92
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coinbase
|
|
4
|
+
# A representation of an Balance.
|
|
5
|
+
class Balance
|
|
6
|
+
# Converts a Coinbase::Client::Balance model to a Coinbase::Balance
|
|
7
|
+
# @param balance_model [Coinbase::Client::Balance] The balance fetched from the API.
|
|
8
|
+
# @return [Balance] The converted Balance object.
|
|
9
|
+
def self.from_model(balance_model)
|
|
10
|
+
asset_id = Coinbase.to_sym(balance_model.asset.asset_id.downcase)
|
|
11
|
+
|
|
12
|
+
from_model_and_asset_id(balance_model, asset_id)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Converts a Coinbase::Client::Balance model and asset ID to a Coinbase::Balance
|
|
16
|
+
# This can be used to specify a non-primary denomination that we want the balance
|
|
17
|
+
# to be converted to.
|
|
18
|
+
# @param balance_model [Coinbase::Client::Balance] The balance fetched from the API.
|
|
19
|
+
# @param asset_id [Symbol] The Asset ID of the denomination we want returned.
|
|
20
|
+
# @return [Balance] The converted Balance object.
|
|
21
|
+
def self.from_model_and_asset_id(balance_model, asset_id)
|
|
22
|
+
new(
|
|
23
|
+
amount: Coinbase::Asset.from_atomic_amount(BigDecimal(balance_model.amount), asset_id),
|
|
24
|
+
asset_id: asset_id
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns a new Balance object. Do not use this method. Instead, use Balance.from_model or
|
|
29
|
+
# Balance.from_model_and_asset_id.
|
|
30
|
+
# @param amount [BigDecimal] The amount of the Asset
|
|
31
|
+
# @param asset_id [Symbol] The Asset ID
|
|
32
|
+
def initialize(amount:, asset_id:)
|
|
33
|
+
@amount = amount
|
|
34
|
+
@asset_id = asset_id
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
attr_reader :amount, :asset_id
|
|
38
|
+
|
|
39
|
+
# Returns a string representation of the Balance.
|
|
40
|
+
# @return [String] a string representation of the Balance
|
|
41
|
+
def to_s
|
|
42
|
+
"Coinbase::Balance{amount: '#{amount.to_i}', asset_id: '#{asset_id}'}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Same as to_s.
|
|
46
|
+
# @return [String] a string representation of the Balance
|
|
47
|
+
def inspect
|
|
48
|
+
to_s
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/coinbase/balance_map.rb
CHANGED
|
@@ -5,31 +5,43 @@ require 'bigdecimal'
|
|
|
5
5
|
module Coinbase
|
|
6
6
|
# A convenience class for printing out Asset balances in a human-readable format.
|
|
7
7
|
class BalanceMap < Hash
|
|
8
|
-
#
|
|
9
|
-
# @param
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
# Converts a list of Coinbase::Client::Balance models to a Coinbase::BalanceMap.
|
|
9
|
+
# @param balances [Array<Coinbase::Client::Balance>] The list of balances fetched from the API.
|
|
10
|
+
# @return [BalanceMap] The converted BalanceMap object.
|
|
11
|
+
def self.from_balances(balances)
|
|
12
|
+
BalanceMap.new.tap do |balance_map|
|
|
13
|
+
balances.each do |balance_model|
|
|
14
|
+
balance = Coinbase::Balance.from_model(balance_model)
|
|
15
|
+
|
|
16
|
+
balance_map.add(balance)
|
|
17
|
+
end
|
|
14
18
|
end
|
|
15
19
|
end
|
|
16
20
|
|
|
21
|
+
# Adds a balance to the map.
|
|
22
|
+
# @param balance [Coinbase::Balance] The balance to add to the map.
|
|
23
|
+
def add(balance)
|
|
24
|
+
raise ArgumentError, 'balance must be a Coinbase::Balance' unless balance.is_a?(Coinbase::Balance)
|
|
25
|
+
|
|
26
|
+
self[balance.asset_id] = balance.amount
|
|
27
|
+
end
|
|
28
|
+
|
|
17
29
|
# Returns a string representation of the balance map.
|
|
18
|
-
# @return [String] The string representation of the balance
|
|
30
|
+
# @return [String] The string representation of the balance map
|
|
19
31
|
def to_s
|
|
20
32
|
to_string
|
|
21
33
|
end
|
|
22
34
|
|
|
23
35
|
# Returns a string representation of the balance map.
|
|
24
|
-
# @return [String] The string representation of the balance
|
|
36
|
+
# @return [String] The string representation of the balance map
|
|
25
37
|
def inspect
|
|
26
38
|
to_string
|
|
27
39
|
end
|
|
28
40
|
|
|
29
41
|
private
|
|
30
42
|
|
|
31
|
-
# Returns a string representation of the balance.
|
|
32
|
-
# @return [String] The string representation of the balance
|
|
43
|
+
# Returns a string representation of the balance map.
|
|
44
|
+
# @return [String] The string representation of the balance map
|
|
33
45
|
def to_string
|
|
34
46
|
result = {}
|
|
35
47
|
|
data/lib/coinbase/middleware.rb
CHANGED
|
@@ -12,8 +12,11 @@ module Coinbase
|
|
|
12
12
|
# Returns the default middleware configuration for the Coinbase SDK.
|
|
13
13
|
def self.config
|
|
14
14
|
Coinbase::Client::Configuration.default.tap do |config|
|
|
15
|
+
uri = URI(Coinbase.configuration.api_url)
|
|
16
|
+
|
|
15
17
|
config.debugging = Coinbase.configuration.debug_api
|
|
16
|
-
config.host =
|
|
18
|
+
config.host = uri.host + (uri.port ? ":#{uri.port}" : '')
|
|
19
|
+
config.scheme = uri.scheme if uri.scheme
|
|
17
20
|
config.request(:authenticator)
|
|
18
21
|
end
|
|
19
22
|
end
|
data/lib/coinbase/transfer.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Coinbase
|
|
|
37
37
|
|
|
38
38
|
# Returns the Transfer ID.
|
|
39
39
|
# @return [String] The Transfer ID
|
|
40
|
-
def
|
|
40
|
+
def id
|
|
41
41
|
@model.transfer_id
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -179,7 +179,7 @@ module Coinbase
|
|
|
179
179
|
# Returns a String representation of the Transfer.
|
|
180
180
|
# @return [String] a String representation of the Transfer
|
|
181
181
|
def to_s
|
|
182
|
-
"Coinbase::Transfer{transfer_id: '#{
|
|
182
|
+
"Coinbase::Transfer{transfer_id: '#{id}', network_id: '#{network_id}', " \
|
|
183
183
|
"from_address_id: '#{from_address_id}', destination_address_id: '#{destination_address_id}', " \
|
|
184
184
|
"asset_id: '#{asset_id}', amount: '#{amount}', transaction_hash: '#{transaction_hash}', " \
|
|
185
185
|
"transaction_link: '#{transaction_link}', status: '#{status}'}"
|
data/lib/coinbase/user.rb
CHANGED
|
@@ -15,57 +15,63 @@ module Coinbase
|
|
|
15
15
|
|
|
16
16
|
# Returns the User ID.
|
|
17
17
|
# @return [String] the User ID
|
|
18
|
-
def
|
|
18
|
+
def id
|
|
19
19
|
@model.id
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# Creates a new Wallet belonging to the User.
|
|
23
|
+
# @param network_id [String] (Optional) the ID of the blockchain network. Defaults to 'base-sepolia'.
|
|
23
24
|
# @return [Coinbase::Wallet] the new Wallet
|
|
24
|
-
def create_wallet
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
network_id: 'base-sepolia'
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
opts = { create_wallet_request: create_wallet_request }
|
|
32
|
-
|
|
33
|
-
model = Coinbase.call_api do
|
|
34
|
-
wallets_api.create_wallet(opts)
|
|
35
|
-
end
|
|
25
|
+
def create_wallet(create_wallet_options = {})
|
|
26
|
+
# For ruby 2.7 compatibility we cannot pass in keyword args when the create wallet
|
|
27
|
+
# options is empty
|
|
28
|
+
return Wallet.create if create_wallet_options.empty?
|
|
36
29
|
|
|
37
|
-
Wallet.
|
|
30
|
+
Wallet.create(**create_wallet_options)
|
|
38
31
|
end
|
|
39
32
|
|
|
40
33
|
# Imports a Wallet belonging to the User.
|
|
41
34
|
# @param data [Coinbase::Wallet::Data] the Wallet data to import
|
|
42
35
|
# @return [Coinbase::Wallet] the imported Wallet
|
|
43
36
|
def import_wallet(data)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
Wallet.import(data)
|
|
38
|
+
end
|
|
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 [Coinbase::Wallet] the Wallets belonging to the User
|
|
44
|
+
def wallets(page_size: 10, next_page_token: nil)
|
|
45
|
+
opts = {
|
|
46
|
+
limit: page_size
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
opts[:page] = next_page_token unless next_page_token.nil?
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
wallet_list = Coinbase.call_api do
|
|
52
|
+
wallets_api.list_wallets(opts)
|
|
50
53
|
end
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
# A map from wallet_id to address models.
|
|
56
|
+
address_model_map = {}
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
wallet_list.data.each do |wallet_model|
|
|
59
|
+
addresses_list = Coinbase.call_api do
|
|
60
|
+
addresses_api.list_addresses(wallet_model.id, { limit: Coinbase::Wallet::MAX_ADDRESSES })
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
address_model_map[wallet_model.id] = addresses_list.data
|
|
60
64
|
end
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
wallet_list.data.map do |wallet_model|
|
|
67
|
+
Wallet.new(wallet_model, seed: '', address_models: address_model_map[wallet_model.id])
|
|
68
|
+
end
|
|
63
69
|
end
|
|
64
70
|
|
|
65
|
-
# Saves a wallet to local file system. Wallet saved this way can be re-instantiated with
|
|
66
|
-
# provided the backup_file is available. This is an insecure method of storing wallet seeds and should
|
|
67
|
-
# for development purposes. If you call
|
|
68
|
-
# will be overwritten during the second attempt.
|
|
71
|
+
# Saves a wallet to local file system. Wallet saved this way can be re-instantiated with load_wallets_from_local
|
|
72
|
+
# function, provided the backup_file is available. This is an insecure method of storing wallet seeds and should
|
|
73
|
+
# only be used for development purposes. If you call save_wallet_locally! twice with wallets containing the same
|
|
74
|
+
# wallet_id, the backup will be overwritten during the second attempt.
|
|
69
75
|
# The default backup_file is `seeds.json` in the root folder. It can be configured by changing
|
|
70
76
|
# Coinbase.configuration.backup_file_path.
|
|
71
77
|
#
|
|
@@ -73,7 +79,7 @@ module Coinbase
|
|
|
73
79
|
# @param encrypt [bool] (Optional) Boolean representing whether the backup persisted to local file system should be
|
|
74
80
|
# encrypted or not. Data is unencrypted by default.
|
|
75
81
|
# @return [Coinbase::Wallet] the saved wallet.
|
|
76
|
-
def
|
|
82
|
+
def save_wallet_locally!(wallet, encrypt: false)
|
|
77
83
|
existing_seeds_in_store = existing_seeds
|
|
78
84
|
data = wallet.export
|
|
79
85
|
seed_to_store = data.seed
|
|
@@ -107,7 +113,7 @@ module Coinbase
|
|
|
107
113
|
|
|
108
114
|
# Loads all wallets belonging to the User with backup persisted to the local file system.
|
|
109
115
|
# @return [Map<String>Coinbase::Wallet] the map of wallet_ids to the wallets.
|
|
110
|
-
def
|
|
116
|
+
def load_wallets_from_local
|
|
111
117
|
existing_seeds_in_store = existing_seeds
|
|
112
118
|
raise ArgumentError, 'Backup file not found' if existing_seeds_in_store == {}
|
|
113
119
|
|
|
@@ -141,7 +147,7 @@ module Coinbase
|
|
|
141
147
|
# Returns a string representation of the User.
|
|
142
148
|
# @return [String] a string representation of the User
|
|
143
149
|
def to_s
|
|
144
|
-
"Coinbase::User{user_id: '#{
|
|
150
|
+
"Coinbase::User{user_id: '#{id}'}"
|
|
145
151
|
end
|
|
146
152
|
|
|
147
153
|
# Same as to_s.
|
data/lib/coinbase/wallet.rb
CHANGED
|
@@ -12,40 +12,85 @@ module Coinbase
|
|
|
12
12
|
# list their balances, and transfer Assets to other Addresses. Wallets should be created through User#create_wallet or
|
|
13
13
|
# User#import_wallet.
|
|
14
14
|
class Wallet
|
|
15
|
+
attr_reader :addresses, :model
|
|
16
|
+
|
|
17
|
+
# The maximum number of addresses in a Wallet.
|
|
18
|
+
MAX_ADDRESSES = 20
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
# Imports a Wallet from previously exported wallet data.
|
|
22
|
+
# @param data [Coinbase::Wallet::Data] the Wallet data to import
|
|
23
|
+
# @return [Coinbase::Wallet] the imported Wallet
|
|
24
|
+
def import(data)
|
|
25
|
+
raise ArgumentError, 'data must be a Coinbase::Wallet::Data object' unless data.is_a?(Data)
|
|
26
|
+
|
|
27
|
+
model = Coinbase.call_api do
|
|
28
|
+
wallets_api.get_wallet(data.wallet_id)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
address_list = Coinbase.call_api do
|
|
32
|
+
addresses_api.list_addresses(model.id, { limit: MAX_ADDRESSES })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
new(model, seed: data.seed, address_models: address_list.data)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Creates a new Wallet on the specified Network and generate a default address for it.
|
|
39
|
+
# @param network_id [String] (Optional) the ID of the blockchain network. Defaults to 'base-sepolia'.
|
|
40
|
+
# @return [Coinbase::Wallet] the new Wallet
|
|
41
|
+
def create(network_id: 'base-sepolia')
|
|
42
|
+
model = Coinbase.call_api do
|
|
43
|
+
wallets_api.create_wallet(
|
|
44
|
+
create_wallet_request: {
|
|
45
|
+
wallet: {
|
|
46
|
+
network_id: network_id
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
wallet = new(model)
|
|
53
|
+
|
|
54
|
+
wallet.create_address
|
|
55
|
+
|
|
56
|
+
wallet
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# TODO: Memoize these objects in a thread-safe way at the top-level.
|
|
62
|
+
def addresses_api
|
|
63
|
+
Coinbase::Client::AddressesApi.new(Coinbase.configuration.api_client)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def wallets_api
|
|
67
|
+
Coinbase::Client::WalletsApi.new(Coinbase.configuration.api_client)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
15
71
|
# Returns a new Wallet object. Do not use this method directly. Instead, use User#create_wallet or
|
|
16
72
|
# User#import_wallet.
|
|
17
73
|
# @param model [Coinbase::Client::Wallet] The underlying Wallet object
|
|
18
74
|
# @param seed [String] (Optional) The seed to use for the Wallet. Expects a 32-byte hexadecimal with no 0x prefix.
|
|
19
|
-
# If
|
|
20
|
-
#
|
|
75
|
+
# If nil, a new seed will be generated. If the empty string, no seed is generated, and the Wallet will be
|
|
76
|
+
# instantiated without a seed and its corresponding private keys.
|
|
77
|
+
# @param address_models [Array<Coinbase::Client::Address>] (Optional) The models of the addresses already registered
|
|
78
|
+
# with the Wallet. If not provided, the Wallet will derive the first default address.
|
|
21
79
|
# @param client [Jimson::Client] (Optional) The JSON RPC client to use for interacting with the Network
|
|
22
|
-
def initialize(model, seed: nil,
|
|
23
|
-
|
|
80
|
+
def initialize(model, seed: nil, address_models: [])
|
|
81
|
+
validate_seed_and_address_models(seed, address_models)
|
|
24
82
|
|
|
25
83
|
@model = model
|
|
26
|
-
|
|
27
|
-
@master = seed.nil? ? MoneyTree::Master.new : MoneyTree::Master.new(seed_hex: seed)
|
|
28
|
-
|
|
29
|
-
# TODO: Make Network an argument to the constructor.
|
|
30
|
-
@network_id = :base_sepolia
|
|
84
|
+
@master = master_node(seed)
|
|
31
85
|
@addresses = []
|
|
86
|
+
@private_key_index = 0
|
|
32
87
|
|
|
33
|
-
|
|
34
|
-
@address_path_prefix = "m/44'/60'/0'/0"
|
|
35
|
-
@address_index = 0
|
|
36
|
-
|
|
37
|
-
if address_count.positive?
|
|
38
|
-
address_count.times { derive_address }
|
|
39
|
-
else
|
|
40
|
-
create_address
|
|
41
|
-
# Update the model to reflect the new default address.
|
|
42
|
-
update_model
|
|
43
|
-
end
|
|
88
|
+
derive_addresses(address_models)
|
|
44
89
|
end
|
|
45
90
|
|
|
46
91
|
# Returns the Wallet ID.
|
|
47
92
|
# @return [String] The Wallet ID
|
|
48
|
-
def
|
|
93
|
+
def id
|
|
49
94
|
@model.id
|
|
50
95
|
end
|
|
51
96
|
|
|
@@ -55,6 +100,24 @@ module Coinbase
|
|
|
55
100
|
Coinbase.to_sym(@model.network_id)
|
|
56
101
|
end
|
|
57
102
|
|
|
103
|
+
# Sets the seed of the Wallet. This seed is used to derive keys and sign transactions.
|
|
104
|
+
# @param seed [String] The seed to set. Expects a 32-byte hexadecimal with no 0x prefix.
|
|
105
|
+
def seed=(seed)
|
|
106
|
+
raise ArgumentError, 'Seed must be 32 bytes' if seed.length != 64
|
|
107
|
+
raise 'Seed is already set' unless @master.nil?
|
|
108
|
+
raise 'Cannot set seed for Wallet with non-zero private key index' if @private_key_index.positive?
|
|
109
|
+
|
|
110
|
+
@master = MoneyTree::Master.new(seed_hex: seed)
|
|
111
|
+
|
|
112
|
+
@addresses.each do
|
|
113
|
+
key = derive_key
|
|
114
|
+
a = address(key.address.to_s)
|
|
115
|
+
raise "Seed does not match wallet; cannot find address #{key.address}" if a.nil?
|
|
116
|
+
|
|
117
|
+
a.key = key
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
58
121
|
# Creates a new Address in the Wallet.
|
|
59
122
|
# @return [Address] The new Address
|
|
60
123
|
def create_address
|
|
@@ -69,69 +132,49 @@ module Coinbase
|
|
|
69
132
|
}
|
|
70
133
|
}
|
|
71
134
|
address_model = Coinbase.call_api do
|
|
72
|
-
addresses_api.create_address(
|
|
135
|
+
addresses_api.create_address(id, opts)
|
|
73
136
|
end
|
|
74
137
|
|
|
138
|
+
# Auto-reload wallet to set default address on first address creation.
|
|
139
|
+
reload if addresses.empty?
|
|
140
|
+
|
|
75
141
|
cache_address(address_model, key)
|
|
76
142
|
end
|
|
77
143
|
|
|
78
144
|
# Returns the default address of the Wallet.
|
|
79
145
|
# @return [Address] The default address
|
|
80
146
|
def default_address
|
|
81
|
-
|
|
147
|
+
address(@model.default_address&.address_id)
|
|
82
148
|
end
|
|
83
149
|
|
|
84
150
|
# Returns the Address with the given ID.
|
|
85
151
|
# @param address_id [String] The ID of the Address to retrieve
|
|
86
152
|
# @return [Address] The Address
|
|
87
|
-
def
|
|
88
|
-
@addresses.find { |address| address.
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Returns the list of Addresses in the Wallet.
|
|
92
|
-
# @return [Array<Address>] The list of Addresses
|
|
93
|
-
def list_addresses
|
|
94
|
-
@addresses
|
|
153
|
+
def address(address_id)
|
|
154
|
+
@addresses.find { |address| address.id == address_id }
|
|
95
155
|
end
|
|
96
156
|
|
|
97
157
|
# Returns the list of balances of this Wallet. Balances are aggregated across all Addresses in the Wallet.
|
|
98
158
|
# @return [BalanceMap] The list of balances. The key is the Asset ID, and the value is the balance.
|
|
99
|
-
def
|
|
159
|
+
def balances
|
|
100
160
|
response = Coinbase.call_api do
|
|
101
|
-
wallets_api.list_wallet_balances(
|
|
161
|
+
wallets_api.list_wallet_balances(id)
|
|
102
162
|
end
|
|
103
163
|
|
|
104
|
-
Coinbase.
|
|
164
|
+
Coinbase::BalanceMap.from_balances(response.data)
|
|
105
165
|
end
|
|
106
166
|
|
|
107
167
|
# Returns the balance of the provided Asset. Balances are aggregated across all Addresses in the Wallet.
|
|
108
168
|
# @param asset_id [Symbol] The ID of the Asset to retrieve the balance for
|
|
109
169
|
# @return [BigDecimal] The balance of the Asset
|
|
110
|
-
def
|
|
111
|
-
normalized_asset_id = if %i[wei gwei].include?(asset_id)
|
|
112
|
-
:eth
|
|
113
|
-
else
|
|
114
|
-
asset_id
|
|
115
|
-
end
|
|
116
|
-
|
|
170
|
+
def balance(asset_id)
|
|
117
171
|
response = Coinbase.call_api do
|
|
118
|
-
wallets_api.get_wallet_balance(
|
|
172
|
+
wallets_api.get_wallet_balance(id, Coinbase::Asset.primary_denomination(asset_id).to_s)
|
|
119
173
|
end
|
|
120
174
|
|
|
121
175
|
return BigDecimal('0') if response.nil?
|
|
122
176
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
case asset_id
|
|
126
|
-
when :eth
|
|
127
|
-
amount / BigDecimal(Coinbase::WEI_PER_ETHER.to_s)
|
|
128
|
-
when :gwei
|
|
129
|
-
amount / BigDecimal(Coinbase::GWEI_PER_ETHER.to_s)
|
|
130
|
-
when :usdc
|
|
131
|
-
amount / BigDecimal(Coinbase::ATOMIC_UNITS_PER_USDC.to_s)
|
|
132
|
-
else
|
|
133
|
-
amount
|
|
134
|
-
end
|
|
177
|
+
Coinbase::Balance.from_model_and_asset_id(response, asset_id).amount
|
|
135
178
|
end
|
|
136
179
|
|
|
137
180
|
# Transfers the given amount of the given Asset to the given address. Only same-Network Transfers are supported.
|
|
@@ -143,13 +186,13 @@ module Coinbase
|
|
|
143
186
|
# @return [Transfer] The hash of the Transfer transaction.
|
|
144
187
|
def transfer(amount, asset_id, destination)
|
|
145
188
|
if destination.is_a?(Wallet)
|
|
146
|
-
raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id !=
|
|
189
|
+
raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != network_id
|
|
147
190
|
|
|
148
|
-
destination = destination.default_address.
|
|
191
|
+
destination = destination.default_address.id
|
|
149
192
|
elsif destination.is_a?(Address)
|
|
150
|
-
raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id !=
|
|
193
|
+
raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != network_id
|
|
151
194
|
|
|
152
|
-
destination = destination.
|
|
195
|
+
destination = destination.id
|
|
153
196
|
end
|
|
154
197
|
|
|
155
198
|
default_address.transfer(amount, asset_id, destination)
|
|
@@ -158,14 +201,33 @@ module Coinbase
|
|
|
158
201
|
# Exports the Wallet's data to a Data object.
|
|
159
202
|
# @return [Data] The Wallet data
|
|
160
203
|
def export
|
|
161
|
-
|
|
204
|
+
raise 'Cannot export Wallet without loaded seed' if @master.nil?
|
|
205
|
+
|
|
206
|
+
Data.new(wallet_id: id, seed: @master.seed_hex)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Requests funds from the faucet for the Wallet's default address and returns the faucet transaction.
|
|
210
|
+
# This is only supported on testnet networks.
|
|
211
|
+
# @return [Coinbase::FaucetTransaction] The successful faucet transaction
|
|
212
|
+
# @raise [Coinbase::FaucetLimitReachedError] If the faucet limit has been reached for the address or user.
|
|
213
|
+
# @raise [Coinbase::Client::ApiError] If an unexpected error occurs while requesting faucet funds.
|
|
214
|
+
def faucet
|
|
215
|
+
Coinbase.call_api do
|
|
216
|
+
Coinbase::FaucetTransaction.new(addresses_api.request_faucet_funds(id, default_address.id))
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Returns whether the Wallet has a seed with which to derive keys and sign transactions.
|
|
221
|
+
# @return [Boolean] Whether the Wallet has a seed with which to derive keys and sign transactions.
|
|
222
|
+
def can_sign?
|
|
223
|
+
!@master.nil?
|
|
162
224
|
end
|
|
163
225
|
|
|
164
226
|
# Returns a String representation of the Wallet.
|
|
165
227
|
# @return [String] a String representation of the Wallet
|
|
166
228
|
def to_s
|
|
167
|
-
"Coinbase::Wallet{wallet_id: '#{
|
|
168
|
-
"default_address: '#{default_address
|
|
229
|
+
"Coinbase::Wallet{wallet_id: '#{id}', network_id: '#{network_id}', " \
|
|
230
|
+
"default_address: '#{@model.default_address&.address_id}'}"
|
|
169
231
|
end
|
|
170
232
|
|
|
171
233
|
# Same as to_s.
|
|
@@ -202,14 +264,55 @@ module Coinbase
|
|
|
202
264
|
|
|
203
265
|
private
|
|
204
266
|
|
|
267
|
+
# Reloads the Wallet with the latest data.
|
|
268
|
+
def reload
|
|
269
|
+
@model = Coinbase.call_api do
|
|
270
|
+
wallets_api.get_wallet(id)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def master_node(seed)
|
|
275
|
+
return MoneyTree::Master.new if seed.nil?
|
|
276
|
+
return nil if seed.empty?
|
|
277
|
+
|
|
278
|
+
MoneyTree::Master.new(seed_hex: seed)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def address_path_prefix
|
|
282
|
+
# TODO: Add support for other networks.
|
|
283
|
+
@address_path_prefix ||= case network_id.to_s.split('_').first
|
|
284
|
+
when 'base'
|
|
285
|
+
"m/44'/60'/0'/0"
|
|
286
|
+
else
|
|
287
|
+
raise ArgumentError, "Unsupported network ID: #{network_id}"
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Derives the registered Addresses in the Wallet.
|
|
292
|
+
# @param address_models [Array<Coinbase::Client::Address>] The models of the addresses already registered with the
|
|
293
|
+
# Wallet
|
|
294
|
+
def derive_addresses(address_models)
|
|
295
|
+
return unless address_models.any?
|
|
296
|
+
|
|
297
|
+
# Create a map tracking which addresses are already registered with the Wallet.
|
|
298
|
+
address_map = build_address_map(address_models)
|
|
299
|
+
|
|
300
|
+
address_models.each do |address_model|
|
|
301
|
+
# Derive the addresses using the provided models.
|
|
302
|
+
derive_address(address_map, address_model)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
205
306
|
# Derives an already registered Address in the Wallet.
|
|
307
|
+
# @param address_map [Hash<String, Boolean>] The map of registered Address IDs
|
|
308
|
+
# @param address_model [Coinbase::Client::Address] The Address model
|
|
206
309
|
# @return [Address] The new Address
|
|
207
|
-
def derive_address
|
|
208
|
-
key = derive_key
|
|
310
|
+
def derive_address(address_map, address_model)
|
|
311
|
+
key = @master.nil? ? nil : derive_key
|
|
209
312
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
313
|
+
unless key.nil?
|
|
314
|
+
address_from_key = key.address.to_s
|
|
315
|
+
raise 'Invalid address' if address_map[address_from_key].nil?
|
|
213
316
|
end
|
|
214
317
|
|
|
215
318
|
cache_address(address_model, key)
|
|
@@ -218,8 +321,11 @@ module Coinbase
|
|
|
218
321
|
# Derives a key for an already registered Address in the Wallet.
|
|
219
322
|
# @return [Eth::Key] The new key
|
|
220
323
|
def derive_key
|
|
221
|
-
|
|
324
|
+
raise 'Cannot derive key for Wallet without seed loaded' if @master.nil?
|
|
325
|
+
|
|
326
|
+
path = "#{address_path_prefix}/#{@private_key_index}"
|
|
222
327
|
private_key = @master.node_for_path(path).private_key.to_hex
|
|
328
|
+
@private_key_index += 1
|
|
223
329
|
Eth::Key.new(priv: private_key)
|
|
224
330
|
end
|
|
225
331
|
|
|
@@ -230,17 +336,29 @@ module Coinbase
|
|
|
230
336
|
def cache_address(address_model, key)
|
|
231
337
|
address = Address.new(address_model, key)
|
|
232
338
|
@addresses << address
|
|
233
|
-
@address_index += 1
|
|
234
339
|
address
|
|
235
340
|
end
|
|
236
341
|
|
|
342
|
+
# Builds a Hash of the registered Addresses.
|
|
343
|
+
# @param address_models [Array<Coinbase::Client::Address>] The models of the addresses already registered with the
|
|
344
|
+
# Wallet
|
|
345
|
+
# @return [Hash<String, Boolean>] The Hash of registered Addresses
|
|
346
|
+
def build_address_map(address_models)
|
|
347
|
+
address_map = {}
|
|
348
|
+
address_models.each do |address_model|
|
|
349
|
+
address_map[address_model.address_id] = true
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
address_map
|
|
353
|
+
end
|
|
354
|
+
|
|
237
355
|
# Creates an attestation for the Address currently being created.
|
|
238
356
|
# @param key [Eth::Key] The private key of the Address
|
|
239
357
|
# @return [String] The attestation
|
|
240
358
|
def create_attestation(key)
|
|
241
359
|
public_key = key.public_key.compressed.unpack1('H*')
|
|
242
360
|
payload = {
|
|
243
|
-
wallet_id:
|
|
361
|
+
wallet_id: id,
|
|
244
362
|
public_key: public_key
|
|
245
363
|
}.to_json
|
|
246
364
|
hashed_payload = Digest::SHA256.digest(payload)
|
|
@@ -258,11 +376,18 @@ module Coinbase
|
|
|
258
376
|
new_signature_bytes.pack('C*').unpack1('H*')
|
|
259
377
|
end
|
|
260
378
|
|
|
261
|
-
#
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
379
|
+
# Validates the seed and address models passed to the constructor.
|
|
380
|
+
# @param seed [String] The seed to use for the Wallet
|
|
381
|
+
# @param address_models [Array<Coinbase::Client::Address>] The models of the addresses already registered with the
|
|
382
|
+
# Wallet
|
|
383
|
+
def validate_seed_and_address_models(seed, address_models)
|
|
384
|
+
raise ArgumentError, 'Seed must be 32 bytes' if !seed.nil? && !seed.empty? && seed.length != 64
|
|
385
|
+
|
|
386
|
+
raise ArgumentError, 'Seed must be present if address_models are provided' if seed.nil? && address_models.any?
|
|
387
|
+
|
|
388
|
+
return unless !seed.nil? && seed.empty? && address_models.empty?
|
|
389
|
+
|
|
390
|
+
raise ArgumentError, 'Seed must be empty if address_models are not provided'
|
|
266
391
|
end
|
|
267
392
|
|
|
268
393
|
def addresses_api
|
data/lib/coinbase.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative 'coinbase/address'
|
|
4
4
|
require_relative 'coinbase/asset'
|
|
5
5
|
require_relative 'coinbase/authenticator'
|
|
6
|
+
require_relative 'coinbase/balance'
|
|
6
7
|
require_relative 'coinbase/balance_map'
|
|
7
8
|
require_relative 'coinbase/client'
|
|
8
9
|
require_relative 'coinbase/constants'
|
|
@@ -18,7 +19,6 @@ require 'json'
|
|
|
18
19
|
# The Coinbase SDK.
|
|
19
20
|
module Coinbase
|
|
20
21
|
class InvalidConfiguration < StandardError; end
|
|
21
|
-
class FaucetLimitReached < StandardError; end
|
|
22
22
|
|
|
23
23
|
# Returns the configuration object.
|
|
24
24
|
# @return [Configuration] the configuration object
|
|
@@ -100,30 +100,6 @@ module Coinbase
|
|
|
100
100
|
value.to_s.gsub('-', '_').to_sym
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
# Converts a Coinbase::Client::AddressBalanceList to a BalanceMap.
|
|
104
|
-
# @param address_balance_list [Coinbase::Client::AddressBalanceList] The AddressBalanceList to convert
|
|
105
|
-
# @return [BalanceMap] The converted BalanceMap
|
|
106
|
-
def self.to_balance_map(address_balance_list)
|
|
107
|
-
balances = {}
|
|
108
|
-
|
|
109
|
-
address_balance_list.data.each do |balance|
|
|
110
|
-
asset_id = Coinbase.to_sym(balance.asset.asset_id.downcase)
|
|
111
|
-
amount = case asset_id
|
|
112
|
-
when :eth
|
|
113
|
-
BigDecimal(balance.amount) / BigDecimal(Coinbase::WEI_PER_ETHER)
|
|
114
|
-
when :usdc
|
|
115
|
-
BigDecimal(balance.amount) / BigDecimal(Coinbase::ATOMIC_UNITS_PER_USDC)
|
|
116
|
-
when :weth
|
|
117
|
-
BigDecimal(balance.amount) / BigDecimal(Coinbase::WEI_PER_ETHER)
|
|
118
|
-
else
|
|
119
|
-
BigDecimal(balance.amount)
|
|
120
|
-
end
|
|
121
|
-
balances[asset_id] = amount
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
BalanceMap.new(balances)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
103
|
# Loads the default user.
|
|
128
104
|
# @return [Coinbase::User] the default user
|
|
129
105
|
def self.load_default_user
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coinbase-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yuga Cohler
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-05-
|
|
11
|
+
date: 2024-05-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bigdecimal
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: eth
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -216,6 +230,7 @@ files:
|
|
|
216
230
|
- lib/coinbase/address.rb
|
|
217
231
|
- lib/coinbase/asset.rb
|
|
218
232
|
- lib/coinbase/authenticator.rb
|
|
233
|
+
- lib/coinbase/balance.rb
|
|
219
234
|
- lib/coinbase/balance_map.rb
|
|
220
235
|
- lib/coinbase/client.rb
|
|
221
236
|
- lib/coinbase/client/api/addresses_api.rb
|