coinbase-sdk 0.0.2 → 0.0.3
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 +70 -8
- data/lib/coinbase/balance_map.rb +1 -1
- data/lib/coinbase/client/api/addresses_api.rb +69 -0
- data/lib/coinbase/client/api/transfers_api.rb +86 -0
- data/lib/coinbase/client/models/broadcast_transfer_request.rb +222 -0
- data/lib/coinbase/client/models/faucet_transaction.rb +222 -0
- data/lib/coinbase/client/models/transfer.rb +21 -1
- data/lib/coinbase/client.rb +2 -0
- data/lib/coinbase/constants.rb +4 -2
- data/lib/coinbase/errors.rb +120 -0
- data/lib/coinbase/faucet_transaction.rb +42 -0
- data/lib/coinbase/middleware.rb +1 -1
- data/lib/coinbase/transfer.rb +41 -15
- data/lib/coinbase/user.rb +120 -4
- data/lib/coinbase/wallet.rb +29 -5
- data/lib/coinbase.rb +58 -4
- metadata +20 -2
data/lib/coinbase/user.rb
CHANGED
@@ -30,7 +30,9 @@ module Coinbase
|
|
30
30
|
}
|
31
31
|
opts = { create_wallet_request: create_wallet_request }
|
32
32
|
|
33
|
-
model =
|
33
|
+
model = Coinbase.call_api do
|
34
|
+
wallets_api.create_wallet(opts)
|
35
|
+
end
|
34
36
|
|
35
37
|
Wallet.new(model)
|
36
38
|
end
|
@@ -39,18 +41,115 @@ module Coinbase
|
|
39
41
|
# @param data [Coinbase::Wallet::Data] the Wallet data to import
|
40
42
|
# @return [Coinbase::Wallet] the imported Wallet
|
41
43
|
def import_wallet(data)
|
42
|
-
model =
|
43
|
-
|
44
|
+
model = Coinbase.call_api do
|
45
|
+
wallets_api.get_wallet(data.wallet_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
address_count = Coinbase.call_api do
|
49
|
+
addresses_api.list_addresses(model.id).total_count
|
50
|
+
end
|
51
|
+
|
44
52
|
Wallet.new(model, seed: data.seed, address_count: address_count)
|
45
53
|
end
|
46
54
|
|
47
55
|
# Lists the IDs of the Wallets belonging to the User.
|
48
56
|
# @return [Array<String>] the IDs of the Wallets belonging to the User
|
49
57
|
def list_wallet_ids
|
50
|
-
wallets =
|
58
|
+
wallets = Coinbase.call_api do
|
59
|
+
wallets_api.list_wallets
|
60
|
+
end
|
61
|
+
|
51
62
|
wallets.data.map(&:id)
|
52
63
|
end
|
53
64
|
|
65
|
+
# Saves a wallet to local file system. Wallet saved this way can be re-instantiated with `load_wallets` function,
|
66
|
+
# provided the backup_file is available. This is an insecure method of storing wallet seeds and should only be used
|
67
|
+
# for development purposes. If you call save_wallet twice with wallets containing the same wallet_id, the backup
|
68
|
+
# will be overwritten during the second attempt.
|
69
|
+
# The default backup_file is `seeds.json` in the root folder. It can be configured by changing
|
70
|
+
# Coinbase.configuration.backup_file_path.
|
71
|
+
#
|
72
|
+
# @param wallet [Coinbase::Wallet] The wallet model to save.
|
73
|
+
# @param encrypt [bool] (Optional) Boolean representing whether the backup persisted to local file system should be
|
74
|
+
# encrypted or not. Data is unencrypted by default.
|
75
|
+
# @return [Coinbase::Wallet] the saved wallet.
|
76
|
+
def save_wallet(wallet, encrypt: false)
|
77
|
+
existing_seeds_in_store = existing_seeds
|
78
|
+
data = wallet.export
|
79
|
+
seed_to_store = data.seed
|
80
|
+
auth_tag = ''
|
81
|
+
iv = ''
|
82
|
+
if encrypt
|
83
|
+
shared_secret = store_encryption_key
|
84
|
+
cipher = OpenSSL::Cipher.new('aes-256-gcm').encrypt
|
85
|
+
cipher.key = OpenSSL::Digest.digest('SHA256', shared_secret)
|
86
|
+
iv = cipher.random_iv
|
87
|
+
cipher.iv = iv
|
88
|
+
cipher.auth_data = ''
|
89
|
+
encrypted_data = cipher.update(data.seed) + cipher.final
|
90
|
+
auth_tag = cipher.auth_tag.unpack1('H*')
|
91
|
+
iv = iv.unpack1('H*')
|
92
|
+
seed_to_store = encrypted_data.unpack1('H*')
|
93
|
+
end
|
94
|
+
|
95
|
+
existing_seeds_in_store[data.wallet_id] = {
|
96
|
+
seed: seed_to_store,
|
97
|
+
encrypted: encrypt,
|
98
|
+
auth_tag: auth_tag,
|
99
|
+
iv: iv
|
100
|
+
}
|
101
|
+
|
102
|
+
File.open(Coinbase.configuration.backup_file_path, 'w') do |file|
|
103
|
+
file.write(JSON.pretty_generate(existing_seeds_in_store))
|
104
|
+
end
|
105
|
+
wallet
|
106
|
+
end
|
107
|
+
|
108
|
+
# Loads all wallets belonging to the User with backup persisted to the local file system.
|
109
|
+
# @return [Map<String>Coinbase::Wallet] the map of wallet_ids to the wallets.
|
110
|
+
def load_wallets
|
111
|
+
existing_seeds_in_store = existing_seeds
|
112
|
+
raise ArgumentError, 'Backup file not found' if existing_seeds_in_store == {}
|
113
|
+
|
114
|
+
wallets = {}
|
115
|
+
existing_seeds_in_store.each do |wallet_id, seed_data|
|
116
|
+
seed = seed_data['seed']
|
117
|
+
raise ArgumentError, 'Malformed backup data' if seed.nil? || seed == ''
|
118
|
+
|
119
|
+
if seed_data['encrypted']
|
120
|
+
shared_secret = store_encryption_key
|
121
|
+
raise ArgumentError, 'Malformed encrypted seed data' if seed_data['iv'] == '' ||
|
122
|
+
seed_data['auth_tag'] == ''
|
123
|
+
|
124
|
+
cipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
|
125
|
+
cipher.key = OpenSSL::Digest.digest('SHA256', shared_secret)
|
126
|
+
iv = [seed_data['iv']].pack('H*')
|
127
|
+
cipher.iv = iv
|
128
|
+
auth_tag = [seed_data['auth_tag']].pack('H*')
|
129
|
+
cipher.auth_tag = auth_tag
|
130
|
+
cipher.auth_data = ''
|
131
|
+
hex_decoded_data = [seed_data['seed']].pack('H*')
|
132
|
+
seed = cipher.update(hex_decoded_data) + cipher.final
|
133
|
+
end
|
134
|
+
|
135
|
+
data = Coinbase::Wallet::Data.new(wallet_id: wallet_id, seed: seed)
|
136
|
+
wallets[wallet_id] = import_wallet(data)
|
137
|
+
end
|
138
|
+
wallets
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns a string representation of the User.
|
142
|
+
# @return [String] a string representation of the User
|
143
|
+
def to_s
|
144
|
+
"Coinbase::User{user_id: '#{user_id}'}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# Same as to_s.
|
148
|
+
# @return [String] a string representation of the User
|
149
|
+
def inspect
|
150
|
+
to_s
|
151
|
+
end
|
152
|
+
|
54
153
|
private
|
55
154
|
|
56
155
|
def addresses_api
|
@@ -60,5 +159,22 @@ module Coinbase
|
|
60
159
|
def wallets_api
|
61
160
|
@wallets_api ||= Coinbase::Client::WalletsApi.new(Coinbase.configuration.api_client)
|
62
161
|
end
|
162
|
+
|
163
|
+
def existing_seeds
|
164
|
+
existing_seed_data = '{}'
|
165
|
+
file_path = Coinbase.configuration.backup_file_path
|
166
|
+
existing_seed_data = File.read(file_path) if File.exist?(file_path)
|
167
|
+
output = JSON.parse(existing_seed_data)
|
168
|
+
|
169
|
+
raise ArgumentError, 'Malformed backup data' unless output.is_a?(Hash)
|
170
|
+
|
171
|
+
output
|
172
|
+
end
|
173
|
+
|
174
|
+
def store_encryption_key
|
175
|
+
pk = OpenSSL::PKey.read(Coinbase.configuration.api_key_private_key)
|
176
|
+
public_key = pk.public_key # use own public key to generate the shared secret.
|
177
|
+
pk.dh_compute_key(public_key)
|
178
|
+
end
|
63
179
|
end
|
64
180
|
end
|
data/lib/coinbase/wallet.rb
CHANGED
@@ -68,7 +68,9 @@ module Coinbase
|
|
68
68
|
attestation: attestation
|
69
69
|
}
|
70
70
|
}
|
71
|
-
address_model =
|
71
|
+
address_model = Coinbase.call_api do
|
72
|
+
addresses_api.create_address(wallet_id, opts)
|
73
|
+
end
|
72
74
|
|
73
75
|
cache_address(address_model, key)
|
74
76
|
end
|
@@ -95,7 +97,10 @@ module Coinbase
|
|
95
97
|
# Returns the list of balances of this Wallet. Balances are aggregated across all Addresses in the Wallet.
|
96
98
|
# @return [BalanceMap] The list of balances. The key is the Asset ID, and the value is the balance.
|
97
99
|
def list_balances
|
98
|
-
response =
|
100
|
+
response = Coinbase.call_api do
|
101
|
+
wallets_api.list_wallet_balances(wallet_id)
|
102
|
+
end
|
103
|
+
|
99
104
|
Coinbase.to_balance_map(response)
|
100
105
|
end
|
101
106
|
|
@@ -109,7 +114,9 @@ module Coinbase
|
|
109
114
|
asset_id
|
110
115
|
end
|
111
116
|
|
112
|
-
response =
|
117
|
+
response = Coinbase.call_api do
|
118
|
+
wallets_api.get_wallet_balance(wallet_id, normalized_asset_id.to_s)
|
119
|
+
end
|
113
120
|
|
114
121
|
return BigDecimal('0') if response.nil?
|
115
122
|
|
@@ -154,6 +161,19 @@ module Coinbase
|
|
154
161
|
Data.new(wallet_id: wallet_id, seed: @master.seed_hex)
|
155
162
|
end
|
156
163
|
|
164
|
+
# Returns a String representation of the Wallet.
|
165
|
+
# @return [String] a String representation of the Wallet
|
166
|
+
def to_s
|
167
|
+
"Coinbase::Wallet{wallet_id: '#{wallet_id}', network_id: '#{network_id}', " \
|
168
|
+
"default_address: '#{default_address.address_id}'}"
|
169
|
+
end
|
170
|
+
|
171
|
+
# Same as to_s.
|
172
|
+
# @return [String] a String representation of the Wallet
|
173
|
+
def inspect
|
174
|
+
to_s
|
175
|
+
end
|
176
|
+
|
157
177
|
# The data required to recreate a Wallet.
|
158
178
|
class Data
|
159
179
|
attr_reader :wallet_id, :seed
|
@@ -188,7 +208,9 @@ module Coinbase
|
|
188
208
|
key = derive_key
|
189
209
|
|
190
210
|
address_id = key.address.to_s
|
191
|
-
address_model =
|
211
|
+
address_model = Coinbase.call_api do
|
212
|
+
addresses_api.get_address(wallet_id, address_id)
|
213
|
+
end
|
192
214
|
|
193
215
|
cache_address(address_model, key)
|
194
216
|
end
|
@@ -238,7 +260,9 @@ module Coinbase
|
|
238
260
|
|
239
261
|
# Updates the Wallet model with the latest data.
|
240
262
|
def update_model
|
241
|
-
@model =
|
263
|
+
@model = Coinbase.call_api do
|
264
|
+
wallets_api.get_wallet(wallet_id)
|
265
|
+
end
|
242
266
|
end
|
243
267
|
|
244
268
|
def addresses_api
|
data/lib/coinbase.rb
CHANGED
@@ -6,20 +6,27 @@ require_relative 'coinbase/authenticator'
|
|
6
6
|
require_relative 'coinbase/balance_map'
|
7
7
|
require_relative 'coinbase/client'
|
8
8
|
require_relative 'coinbase/constants'
|
9
|
+
require_relative 'coinbase/errors'
|
10
|
+
require_relative 'coinbase/faucet_transaction'
|
9
11
|
require_relative 'coinbase/middleware'
|
10
12
|
require_relative 'coinbase/network'
|
11
13
|
require_relative 'coinbase/transfer'
|
12
14
|
require_relative 'coinbase/user'
|
13
15
|
require_relative 'coinbase/wallet'
|
16
|
+
require 'json'
|
14
17
|
|
15
18
|
# The Coinbase SDK.
|
16
19
|
module Coinbase
|
17
20
|
class InvalidConfiguration < StandardError; end
|
21
|
+
class FaucetLimitReached < StandardError; end
|
18
22
|
|
23
|
+
# Returns the configuration object.
|
24
|
+
# @return [Configuration] the configuration object
|
19
25
|
def self.configuration
|
20
26
|
@configuration ||= Configuration.new
|
21
27
|
end
|
22
28
|
|
29
|
+
# Configures the Coinbase SDK.
|
23
30
|
def self.configure
|
24
31
|
yield(configuration)
|
25
32
|
|
@@ -27,22 +34,54 @@ module Coinbase
|
|
27
34
|
raise InvalidConfiguration, 'API key name is not set' unless configuration.api_key_name
|
28
35
|
end
|
29
36
|
|
30
|
-
#
|
37
|
+
# Configures the Coinbase SDK from the given CDP API Key JSON file.
|
38
|
+
# @param file_path [String] (Optional) the path to the CDP API Key JSON file
|
39
|
+
# file in the root directory by default.
|
40
|
+
def self.configure_from_json(file_path = 'coinbase_cloud_api_key.json')
|
41
|
+
configuration.from_json(file_path)
|
42
|
+
|
43
|
+
raise InvalidConfiguration, 'API key private key is not set' unless configuration.api_key_private_key
|
44
|
+
raise InvalidConfiguration, 'API key name is not set' unless configuration.api_key_name
|
45
|
+
end
|
46
|
+
|
47
|
+
# Configuration object for the Coinbase SDK.
|
31
48
|
class Configuration
|
32
49
|
attr_reader :base_sepolia_rpc_url, :base_sepolia_client
|
33
|
-
attr_accessor :api_url, :api_key_name, :api_key_private_key
|
50
|
+
attr_accessor :api_url, :api_key_name, :api_key_private_key, :debug_api, :backup_file_path
|
34
51
|
|
52
|
+
# Initializes the configuration object.
|
35
53
|
def initialize
|
36
54
|
@base_sepolia_rpc_url = 'https://sepolia.base.org'
|
37
55
|
@base_sepolia_client = Jimson::Client.new(@base_sepolia_rpc_url)
|
38
56
|
@api_url = 'https://api.cdp.coinbase.com'
|
57
|
+
@debug_api = false
|
58
|
+
@backup_file_path = 'seeds.json'
|
39
59
|
end
|
40
60
|
|
61
|
+
# Sets configuration values based on the provided CDP API Key JSON file.
|
62
|
+
# @param file_path [String] (Optional) the path to the CDP API Key JSON file
|
63
|
+
# file in the root directory by default.
|
64
|
+
def from_json(file_path = 'coinbase_cloud_api_key.json')
|
65
|
+
# Expand paths to respect shortcuts like ~.
|
66
|
+
file_path = File.expand_path(file_path)
|
67
|
+
|
68
|
+
raise InvalidConfiguration, 'Invalid configuration file type' unless file_path.end_with?('.json')
|
69
|
+
|
70
|
+
file = File.read(file_path)
|
71
|
+
data = JSON.parse(file)
|
72
|
+
@api_key_name = data['name']
|
73
|
+
@api_key_private_key = data['privateKey']
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sets the Base Sepolia RPC URL.
|
77
|
+
# @param new_base_sepolia_rpc_url [String] the new Base Sepolia RPC URL
|
41
78
|
def base_sepolia_rpc_url=(new_base_sepolia_rpc_url)
|
42
79
|
@base_sepolia_rpc_url = new_base_sepolia_rpc_url
|
43
80
|
@base_sepolia_client = Jimson::Client.new(@base_sepolia_rpc_url)
|
44
81
|
end
|
45
82
|
|
83
|
+
# Returns the API client.
|
84
|
+
# @return [Coinbase::Client::ApiClient] the API client
|
46
85
|
def api_client
|
47
86
|
@api_client ||= Coinbase::Client::ApiClient.new(Middleware.config)
|
48
87
|
end
|
@@ -69,10 +108,13 @@ module Coinbase
|
|
69
108
|
|
70
109
|
address_balance_list.data.each do |balance|
|
71
110
|
asset_id = Coinbase.to_sym(balance.asset.asset_id.downcase)
|
72
|
-
amount =
|
111
|
+
amount = case asset_id
|
112
|
+
when :eth
|
73
113
|
BigDecimal(balance.amount) / BigDecimal(Coinbase::WEI_PER_ETHER)
|
74
|
-
|
114
|
+
when :usdc
|
75
115
|
BigDecimal(balance.amount) / BigDecimal(Coinbase::ATOMIC_UNITS_PER_USDC)
|
116
|
+
when :weth
|
117
|
+
BigDecimal(balance.amount) / BigDecimal(Coinbase::WEI_PER_ETHER)
|
76
118
|
else
|
77
119
|
BigDecimal(balance.amount)
|
78
120
|
end
|
@@ -82,9 +124,21 @@ module Coinbase
|
|
82
124
|
BalanceMap.new(balances)
|
83
125
|
end
|
84
126
|
|
127
|
+
# Loads the default user.
|
128
|
+
# @return [Coinbase::User] the default user
|
85
129
|
def self.load_default_user
|
86
130
|
users_api = Coinbase::Client::UsersApi.new(configuration.api_client)
|
87
131
|
user_model = users_api.get_current_user
|
88
132
|
Coinbase::User.new(user_model)
|
89
133
|
end
|
134
|
+
|
135
|
+
# Wraps a call to the Platform API to ensure that the error is caught and
|
136
|
+
# wrapped as an APIError.
|
137
|
+
def self.call_api
|
138
|
+
yield
|
139
|
+
rescue Coinbase::Client::ApiError => e
|
140
|
+
raise Coinbase::APIError.from_error(e)
|
141
|
+
rescue StandardError => e
|
142
|
+
raise e
|
143
|
+
end
|
90
144
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.3
|
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-
|
11
|
+
date: 2024-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eth
|
@@ -192,6 +192,20 @@ dependencies:
|
|
192
192
|
- - '='
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: 0.9.36
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: yard-markdown
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
195
209
|
description: Coinbase Ruby SDK for accessing Coinbase Platform APIs
|
196
210
|
email: yuga.cohler@coinbase.com
|
197
211
|
executables: []
|
@@ -216,10 +230,12 @@ files:
|
|
216
230
|
- lib/coinbase/client/models/address_list.rb
|
217
231
|
- lib/coinbase/client/models/asset.rb
|
218
232
|
- lib/coinbase/client/models/balance.rb
|
233
|
+
- lib/coinbase/client/models/broadcast_transfer_request.rb
|
219
234
|
- lib/coinbase/client/models/create_address_request.rb
|
220
235
|
- lib/coinbase/client/models/create_transfer_request.rb
|
221
236
|
- lib/coinbase/client/models/create_wallet_request.rb
|
222
237
|
- lib/coinbase/client/models/error.rb
|
238
|
+
- lib/coinbase/client/models/faucet_transaction.rb
|
223
239
|
- lib/coinbase/client/models/transfer.rb
|
224
240
|
- lib/coinbase/client/models/transfer_list.rb
|
225
241
|
- lib/coinbase/client/models/user.rb
|
@@ -227,6 +243,8 @@ files:
|
|
227
243
|
- lib/coinbase/client/models/wallet_list.rb
|
228
244
|
- lib/coinbase/client/version.rb
|
229
245
|
- lib/coinbase/constants.rb
|
246
|
+
- lib/coinbase/errors.rb
|
247
|
+
- lib/coinbase/faucet_transaction.rb
|
230
248
|
- lib/coinbase/middleware.rb
|
231
249
|
- lib/coinbase/network.rb
|
232
250
|
- lib/coinbase/transfer.rb
|