coinbase-sdk 0.0.5 → 0.0.7
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 +113 -40
- data/lib/coinbase/asset.rb +18 -4
- data/lib/coinbase/authenticator.rb +1 -1
- data/lib/coinbase/client/api/server_signers_api.rb +429 -0
- data/lib/coinbase/client/api/stake_api.rb +86 -0
- data/lib/coinbase/client/api/trades_api.rb +342 -0
- data/lib/coinbase/client/models/broadcast_trade_request.rb +232 -0
- data/lib/coinbase/client/models/build_staking_operation_request.rb +291 -0
- data/lib/coinbase/client/models/create_address_request.rb +0 -14
- data/lib/coinbase/client/models/create_server_signer_request.rb +239 -0
- data/lib/coinbase/client/models/create_trade_request.rb +256 -0
- data/lib/coinbase/client/models/create_wallet_request.rb +1 -1
- data/lib/coinbase/client/models/create_wallet_request_wallet.rb +233 -0
- data/lib/coinbase/client/models/feature.rb +42 -0
- data/lib/coinbase/client/models/request_faucet_funds200_response.rb +222 -0
- data/lib/coinbase/client/models/seed_creation_event.rb +240 -0
- data/lib/coinbase/client/models/seed_creation_event_result.rb +274 -0
- data/lib/coinbase/client/models/server_signer.rb +235 -0
- data/lib/coinbase/client/models/server_signer_event.rb +239 -0
- data/lib/coinbase/client/models/server_signer_event_event.rb +105 -0
- data/lib/coinbase/client/models/server_signer_event_list.rb +275 -0
- data/lib/coinbase/client/models/server_signer_list.rb +275 -0
- data/lib/coinbase/client/models/signature_creation_event.rb +363 -0
- data/lib/coinbase/client/models/signature_creation_event_result.rb +329 -0
- data/lib/coinbase/client/models/staking_operation.rb +222 -0
- data/lib/coinbase/client/models/trade.rb +365 -0
- data/lib/coinbase/client/models/trade_list.rb +275 -0
- data/lib/coinbase/client/models/transaction.rb +338 -0
- data/lib/coinbase/client/models/transaction_type.rb +39 -0
- data/lib/coinbase/client/models/transfer.rb +33 -1
- data/lib/coinbase/client/models/wallet.rb +74 -4
- data/lib/coinbase/client.rb +23 -0
- data/lib/coinbase/errors.rb +3 -0
- data/lib/coinbase/server_signer.rb +57 -0
- data/lib/coinbase/trade.rb +147 -0
- data/lib/coinbase/transaction.rb +125 -0
- data/lib/coinbase/transfer.rb +38 -71
- data/lib/coinbase/user.rb +14 -89
- data/lib/coinbase/wallet.rb +188 -27
- data/lib/coinbase.rb +19 -4
- metadata +43 -2
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'client'
|
4
|
+
|
5
|
+
module Coinbase
|
6
|
+
# A representation of a Server-Signer. Server-Signers are assigned to sign transactions for a Wallet.
|
7
|
+
class ServerSigner
|
8
|
+
# Returns a new Server-Signer object. Do not use this method directly. Instead, use ServerSigner.default.
|
9
|
+
def initialize(model)
|
10
|
+
@model = model
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# Returns the default ServerSigner for the CDP Project.
|
15
|
+
# @return [Coinbase::ServerSigner] the default Server-Signer
|
16
|
+
def default
|
17
|
+
response = Coinbase.call_api do
|
18
|
+
server_signers_api.list_server_signers
|
19
|
+
end
|
20
|
+
|
21
|
+
raise 'No Server-Signer is associated with the project' if response.data.empty?
|
22
|
+
|
23
|
+
new(response.data.first)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def server_signers_api
|
29
|
+
Coinbase::Client::ServerSignersApi.new(Coinbase.configuration.api_client)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the Server-Signer ID.
|
34
|
+
# @return [String] the Server-Signer ID
|
35
|
+
def id
|
36
|
+
@model.server_signer_id
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the IDs of the Wallet's the Server-Signer can sign for.
|
40
|
+
# @return [Array<String>] the wallet IDs
|
41
|
+
def wallets
|
42
|
+
@model.wallets
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a string representation of the Server-Signer.
|
46
|
+
# @return [String] a string representation of the Server-Signer
|
47
|
+
def to_s
|
48
|
+
"Coinbase::ServerSigner{server_signer_id: '#{id}', wallets: [#{wallets.join(', ')}]}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Same as to_s.
|
52
|
+
# @return [String] a string representation of the Server-Signer
|
53
|
+
def inspect
|
54
|
+
to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'constants'
|
4
|
+
require 'bigdecimal'
|
5
|
+
require 'eth'
|
6
|
+
|
7
|
+
module Coinbase
|
8
|
+
# A representation of a Trade, which trades an amount of an Asset to another Asset on a Network.
|
9
|
+
# The fee is assumed to be paid in the native Asset of the Network.
|
10
|
+
# Trades should be created through Wallet#trade or # Address#trade.
|
11
|
+
class Trade
|
12
|
+
# Returns a new Trade object. Do not use this method directly. Instead, use Wallet#trade or
|
13
|
+
# Address#trade.
|
14
|
+
# @param model [Coinbase::Client::Trade] The underlying Trade object
|
15
|
+
def initialize(model)
|
16
|
+
raise unless model.is_a?(Coinbase::Client::Trade)
|
17
|
+
|
18
|
+
@model = model
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the Trade ID.
|
22
|
+
# @return [String] The Trade ID
|
23
|
+
def id
|
24
|
+
@model.trade_id
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the Network ID of the Trade.
|
28
|
+
# @return [Symbol] The Network ID
|
29
|
+
def network_id
|
30
|
+
Coinbase.to_sym(@model.network_id)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the Wallet ID of the Trade.
|
34
|
+
# @return [String] The Wallet ID
|
35
|
+
def wallet_id
|
36
|
+
@model.wallet_id
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the Address ID of the Trade.
|
40
|
+
# @return [String] The Address ID
|
41
|
+
def address_id
|
42
|
+
@model.address_id
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the From Asset ID of the Trade.
|
46
|
+
# @return [Symbol] The From Asset ID
|
47
|
+
def from_asset_id
|
48
|
+
@model.from_asset.asset_id.to_sym
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the amount of the from asset for the Trade.
|
52
|
+
# @return [BigDecimal] The amount of the from asset
|
53
|
+
def from_amount
|
54
|
+
BigDecimal(@model.from_amount) / BigDecimal(10).power(@model.from_asset.decimals)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the To Asset ID of the Trade.
|
58
|
+
# @return [Symbol] The To Asset ID
|
59
|
+
def to_asset_id
|
60
|
+
@model.to_asset.asset_id.to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the amount of the to asset for the Trade.
|
64
|
+
# @return [BigDecimal] The amount of the to asset
|
65
|
+
def to_amount
|
66
|
+
BigDecimal(@model.to_amount) / BigDecimal(10).power(@model.to_asset.decimals)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the Trade transaction.
|
70
|
+
# @return [Coinbase::Transaction] The Trade transaction
|
71
|
+
def transaction
|
72
|
+
@transaction ||= Coinbase::Transaction.new(@model.transaction)
|
73
|
+
end
|
74
|
+
|
75
|
+
def approve_transaction
|
76
|
+
@approve_transaction ||= @model.approve_transaction ? Coinbase::Transaction.new(@model.approve_transaction) : nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the status of the Trade.
|
80
|
+
# @return [Symbol] The status
|
81
|
+
def status
|
82
|
+
transaction.status
|
83
|
+
end
|
84
|
+
|
85
|
+
# Waits until the Trade is completed or failed by polling the Network at the given interval. Raises a
|
86
|
+
# Timeout::Error if the Trade takes longer than the given timeout.
|
87
|
+
# @param interval_seconds [Integer] The interval at which to poll the Network, in seconds
|
88
|
+
# @param timeout_seconds [Integer] The maximum amount of time to wait for the Trade to complete, in seconds
|
89
|
+
# @return [Trade] The completed Trade object
|
90
|
+
def wait!(interval_seconds = 0.2, timeout_seconds = 10)
|
91
|
+
start_time = Time.now
|
92
|
+
|
93
|
+
loop do
|
94
|
+
reload
|
95
|
+
|
96
|
+
# Wait for the trade transaction to be in a terminal state.
|
97
|
+
# The approve transaction is optional and must last first, so we don't need to wait for it.
|
98
|
+
# We may want to handle a situation where the approve transaction fails and the
|
99
|
+
# trade transaction does not ever get broadcast.
|
100
|
+
break if transaction.terminal_state?
|
101
|
+
|
102
|
+
raise Timeout::Error, 'Trade timed out' if Time.now - start_time > timeout_seconds
|
103
|
+
|
104
|
+
self.sleep interval_seconds
|
105
|
+
end
|
106
|
+
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
# Reloads the Trade model with the latest version from the server side.
|
111
|
+
# @return [Trade] The most recent version of Trade from the server.
|
112
|
+
def reload
|
113
|
+
@model = Coinbase.call_api do
|
114
|
+
trades_api.get_trade(wallet_id, address_id, id)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Update the memoized transaction.
|
118
|
+
@transaction = Coinbase::Transaction.new(@model.transaction)
|
119
|
+
|
120
|
+
# Update the memoized approve transaction if it exists.
|
121
|
+
@approve_transaction = @model.approve_transaction ? Coinbase::Transaction.new(@model.approve_transaction) : nil
|
122
|
+
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns a String representation of the Trade.
|
127
|
+
# @return [String] a String representation of the Trade
|
128
|
+
def to_s
|
129
|
+
"Coinbase::Trade{transfer_id: '#{id}', network_id: '#{network_id}', " \
|
130
|
+
"address_id: '#{address_id}', from_asset_id: '#{from_asset_id}', " \
|
131
|
+
"to_asset_id: '#{to_asset_id}', from_amount: '#{from_amount}', " \
|
132
|
+
"to_amount: '#{to_amount}' status: '#{status}'}"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Same as to_s.
|
136
|
+
# @return [String] a String representation of the Trade
|
137
|
+
def inspect
|
138
|
+
to_s
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def trades_api
|
144
|
+
@trades_api ||= Coinbase::Client::TradesApi.new(Coinbase.configuration.api_client)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'constants'
|
4
|
+
require 'bigdecimal'
|
5
|
+
require 'eth'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Coinbase
|
9
|
+
# A representation of an onchain Transaction.
|
10
|
+
# Transactions should be constructed via higher level abstractions like Trade or Transfer.
|
11
|
+
class Transaction
|
12
|
+
# A representation of a Transaction status.
|
13
|
+
module Status
|
14
|
+
# The Transaction is awaiting being broadcast to the Network.
|
15
|
+
# At this point, transaction hashes may not yet be assigned.
|
16
|
+
PENDING = 'pending'
|
17
|
+
|
18
|
+
# The Transaction has been broadcast to the Network.
|
19
|
+
# At this point, at least the transaction hash should be assigned.
|
20
|
+
BROADCAST = 'broadcast'
|
21
|
+
|
22
|
+
# The Transaction is complete and has confirmed on the Network.
|
23
|
+
COMPLETE = 'complete'
|
24
|
+
|
25
|
+
# The Transaction has failed for some reason.
|
26
|
+
FAILED = 'failed'
|
27
|
+
|
28
|
+
# The states that are considered terminal on-chain.
|
29
|
+
TERMINAL_STATES = [COMPLETE, FAILED].freeze
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a new Transaction object. Do not use this method directly.
|
33
|
+
# @param model [Coinbase::Client::Transaction] The underlying Transaction object
|
34
|
+
def initialize(model)
|
35
|
+
raise unless model.is_a?(Coinbase::Client::Transaction)
|
36
|
+
|
37
|
+
@model = model
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the Unsigned Payload of the Transaction.
|
41
|
+
# @return [String] The Unsigned Payload
|
42
|
+
def unsigned_payload
|
43
|
+
@model.unsigned_payload
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the Signed Payload of the Transaction.
|
47
|
+
# @return [String] The Signed Payload
|
48
|
+
def signed_payload
|
49
|
+
@model.signed_payload
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the Transaction Hash of the Transaction.
|
53
|
+
# @return [String] The Transaction Hash
|
54
|
+
def transaction_hash
|
55
|
+
@model.transaction_hash
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the status of the Transaction.
|
59
|
+
# @return [Symbol] The status
|
60
|
+
def status
|
61
|
+
@model.status
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the from address for the Transaction.
|
65
|
+
# @return [String] The from address
|
66
|
+
def from_address_id
|
67
|
+
@model.from_address_id
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns whether the Transaction is in a terminal state.
|
71
|
+
# @return [Boolean] Whether the Transaction is in a terminal state
|
72
|
+
def terminal_state?
|
73
|
+
Status::TERMINAL_STATES.include?(status)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the link to the transaction on the blockchain explorer.
|
77
|
+
# @return [String] The link to the transaction on the blockchain explorer
|
78
|
+
def transaction_link
|
79
|
+
@model.transaction_link
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the underlying raw transaction.
|
83
|
+
# @return [Eth::Tx::Eip1559] The raw transaction
|
84
|
+
def raw
|
85
|
+
return @raw unless @raw.nil?
|
86
|
+
|
87
|
+
raw_payload = [unsigned_payload].pack('H*')
|
88
|
+
parsed_payload = JSON.parse(raw_payload)
|
89
|
+
|
90
|
+
params = {
|
91
|
+
chain_id: parsed_payload['chainId'].to_i(16),
|
92
|
+
nonce: parsed_payload['nonce'].to_i(16),
|
93
|
+
priority_fee: parsed_payload['maxPriorityFeePerGas'].to_i(16),
|
94
|
+
max_gas_fee: parsed_payload['maxFeePerGas'].to_i(16),
|
95
|
+
gas_limit: parsed_payload['gas'].to_i(16), # TODO: Handle multiple currencies.
|
96
|
+
from: Eth::Address.new(from_address_id),
|
97
|
+
to: Eth::Address.new(parsed_payload['to']),
|
98
|
+
value: parsed_payload['value'].to_i(16),
|
99
|
+
data: parsed_payload['input'] || ''
|
100
|
+
}
|
101
|
+
|
102
|
+
@raw = Eth::Tx::Eip1559.new(Eth::Tx.validate_eip1559_params(params))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Signs the Transaction with the provided key and returns the hex signing payload.
|
106
|
+
# @return [String] The hex-encoded signed payload
|
107
|
+
def sign(key)
|
108
|
+
raw.sign(key)
|
109
|
+
|
110
|
+
raw.hex
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns a String representation of the Transaction.
|
114
|
+
# @return [String] a String representation of the Transaction
|
115
|
+
def to_s
|
116
|
+
"Coinbase::Transaction{transaction_hash: '#{transaction_hash}', status: '#{status}'}"
|
117
|
+
end
|
118
|
+
|
119
|
+
# Same as to_s.
|
120
|
+
# @return [String] a String representation of the Transaction
|
121
|
+
def inspect
|
122
|
+
to_s
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/coinbase/transfer.rb
CHANGED
@@ -11,27 +11,12 @@ module Coinbase
|
|
11
11
|
# in the native Asset of the Network. Transfers should be created through Wallet#transfer or
|
12
12
|
# Address#transfer.
|
13
13
|
class Transfer
|
14
|
-
# A representation of a Transfer status.
|
15
|
-
module Status
|
16
|
-
# The Transfer is awaiting being broadcast to the Network. At this point, transaction
|
17
|
-
# hashes may not yet be assigned.
|
18
|
-
PENDING = :pending
|
19
|
-
|
20
|
-
# The Transfer has been broadcast to the Network. At this point, at least the transaction hash
|
21
|
-
# should be assigned.
|
22
|
-
BROADCAST = :broadcast
|
23
|
-
|
24
|
-
# The Transfer is complete, and has confirmed on the Network.
|
25
|
-
COMPLETE = :complete
|
26
|
-
|
27
|
-
# The Transfer has failed for some reason.
|
28
|
-
FAILED = :failed
|
29
|
-
end
|
30
|
-
|
31
14
|
# Returns a new Transfer object. Do not use this method directly. Instead, use Wallet#transfer or
|
32
15
|
# Address#transfer.
|
33
16
|
# @param model [Coinbase::Client::Transfer] The underlying Transfer object
|
34
17
|
def initialize(model)
|
18
|
+
raise unless model.is_a?(Coinbase::Client::Transfer)
|
19
|
+
|
35
20
|
@model = model
|
36
21
|
end
|
37
22
|
|
@@ -65,6 +50,10 @@ module Coinbase
|
|
65
50
|
@model.destination
|
66
51
|
end
|
67
52
|
|
53
|
+
def asset
|
54
|
+
@asset ||= Coinbase::Asset.from_model(@model.asset)
|
55
|
+
end
|
56
|
+
|
68
57
|
# Returns the Asset ID of the Transfer.
|
69
58
|
# @return [Symbol] The Asset ID
|
70
59
|
def asset_id
|
@@ -74,12 +63,7 @@ module Coinbase
|
|
74
63
|
# Returns the amount of the asset for the Transfer.
|
75
64
|
# @return [BigDecimal] The amount of the asset
|
76
65
|
def amount
|
77
|
-
|
78
|
-
when :eth
|
79
|
-
BigDecimal(@model.amount) / BigDecimal(Coinbase::WEI_PER_ETHER.to_s)
|
80
|
-
else
|
81
|
-
BigDecimal(@model.amount)
|
82
|
-
end
|
66
|
+
BigDecimal(@model.amount) / BigDecimal(10).power(@model.asset.decimals)
|
83
67
|
end
|
84
68
|
|
85
69
|
# Returns the link to the transaction on the blockchain explorer.
|
@@ -101,60 +85,35 @@ module Coinbase
|
|
101
85
|
@model.signed_payload
|
102
86
|
end
|
103
87
|
|
88
|
+
# Returns the Transfer transaction.
|
89
|
+
# @return [Coinbase::Transaction] The Transfer transaction
|
90
|
+
def transaction
|
91
|
+
@transaction ||= Coinbase::Transaction.new(@model.transaction)
|
92
|
+
end
|
93
|
+
|
104
94
|
# Returns the Transaction Hash of the Transfer.
|
105
95
|
# @return [String] The Transaction Hash
|
106
96
|
def transaction_hash
|
107
97
|
@model.transaction_hash
|
108
98
|
end
|
109
99
|
|
110
|
-
# Returns the underlying Transfer transaction, creating it if it has not been yet.
|
111
|
-
# @return [Eth::Tx::Eip1559] The Transfer transaction
|
112
|
-
def transaction
|
113
|
-
return @transaction unless @transaction.nil?
|
114
|
-
|
115
|
-
raw_payload = [unsigned_payload].pack('H*')
|
116
|
-
parsed_payload = JSON.parse(raw_payload)
|
117
|
-
|
118
|
-
params = {
|
119
|
-
chain_id: parsed_payload['chainId'].to_i(16),
|
120
|
-
nonce: parsed_payload['nonce'].to_i(16),
|
121
|
-
priority_fee: parsed_payload['maxPriorityFeePerGas'].to_i(16),
|
122
|
-
max_gas_fee: parsed_payload['maxFeePerGas'].to_i(16),
|
123
|
-
gas_limit: parsed_payload['gas'].to_i(16), # TODO: Handle multiple currencies.
|
124
|
-
from: Eth::Address.new(from_address_id),
|
125
|
-
to: Eth::Address.new(parsed_payload['to']),
|
126
|
-
value: parsed_payload['value'].to_i(16),
|
127
|
-
data: parsed_payload['input'] || ''
|
128
|
-
}
|
129
|
-
|
130
|
-
@transaction = Eth::Tx::Eip1559.new(Eth::Tx.validate_eip1559_params(params))
|
131
|
-
@transaction
|
132
|
-
end
|
133
|
-
|
134
100
|
# Returns the status of the Transfer.
|
135
101
|
# @return [Symbol] The status
|
136
102
|
def status
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
elsif onchain_transaction['blockHash'].nil?
|
146
|
-
# If the transaction has been broadcast but hasn't been included in a block, it is
|
147
|
-
# broadcast.
|
148
|
-
Status::BROADCAST
|
149
|
-
else
|
150
|
-
transaction_receipt = Coinbase.configuration.base_sepolia_client.eth_getTransactionReceipt(transaction_hash)
|
151
|
-
|
152
|
-
if transaction_receipt['status'].to_i(16) == 1
|
153
|
-
Status::COMPLETE
|
154
|
-
else
|
155
|
-
Status::FAILED
|
156
|
-
end
|
103
|
+
transaction.status
|
104
|
+
end
|
105
|
+
|
106
|
+
# Reload reloads the Transfer model with the latest version from the server side.
|
107
|
+
# @return [Transfer] The most recent version of Transfer from the server.
|
108
|
+
def reload
|
109
|
+
@model = Coinbase.call_api do
|
110
|
+
transfers_api.get_transfer(wallet_id, from_address_id, id)
|
157
111
|
end
|
112
|
+
|
113
|
+
# Update memoized transaction.
|
114
|
+
@transaction = Coinbase::Transaction.new(@model.transaction)
|
115
|
+
|
116
|
+
self
|
158
117
|
end
|
159
118
|
|
160
119
|
# Waits until the Transfer is completed or failed by polling the Network at the given interval. Raises a
|
@@ -162,11 +121,13 @@ module Coinbase
|
|
162
121
|
# @param interval_seconds [Integer] The interval at which to poll the Network, in seconds
|
163
122
|
# @param timeout_seconds [Integer] The maximum amount of time to wait for the Transfer to complete, in seconds
|
164
123
|
# @return [Transfer] The completed Transfer object
|
165
|
-
def wait!(interval_seconds = 0.2, timeout_seconds =
|
124
|
+
def wait!(interval_seconds = 0.2, timeout_seconds = 20)
|
166
125
|
start_time = Time.now
|
167
126
|
|
168
127
|
loop do
|
169
|
-
|
128
|
+
reload
|
129
|
+
|
130
|
+
return self if transaction.terminal_state?
|
170
131
|
|
171
132
|
raise Timeout::Error, 'Transfer timed out' if Time.now - start_time > timeout_seconds
|
172
133
|
|
@@ -181,8 +142,8 @@ module Coinbase
|
|
181
142
|
def to_s
|
182
143
|
"Coinbase::Transfer{transfer_id: '#{id}', network_id: '#{network_id}', " \
|
183
144
|
"from_address_id: '#{from_address_id}', destination_address_id: '#{destination_address_id}', " \
|
184
|
-
"asset_id: '#{asset_id}', amount: '#{amount}',
|
185
|
-
"
|
145
|
+
"asset_id: '#{asset_id}', amount: '#{amount}', transaction_link: '#{transaction_link}', " \
|
146
|
+
"status: '#{status}'}"
|
186
147
|
end
|
187
148
|
|
188
149
|
# Same as to_s.
|
@@ -190,5 +151,11 @@ module Coinbase
|
|
190
151
|
def inspect
|
191
152
|
to_s
|
192
153
|
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def transfers_api
|
158
|
+
@transfers_api ||= Coinbase::Client::TransfersApi.new(Coinbase.configuration.api_client)
|
159
|
+
end
|
193
160
|
end
|
194
161
|
end
|
data/lib/coinbase/user.rb
CHANGED
@@ -40,7 +40,8 @@ module Coinbase
|
|
40
40
|
# Lists the Wallets belonging to the User.
|
41
41
|
# @param page_size [Integer] (Optional) the number of Wallets to return per page. Defaults to 10
|
42
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
|
43
|
+
# @return [Array<Coinbase::Wallet, String>] the Wallets belonging to the User and the pagination token, if
|
44
|
+
# any.
|
44
45
|
def wallets(page_size: 10, next_page_token: nil)
|
45
46
|
opts = {
|
46
47
|
limit: page_size
|
@@ -63,85 +64,26 @@ module Coinbase
|
|
63
64
|
address_model_map[wallet_model.id] = addresses_list.data
|
64
65
|
end
|
65
66
|
|
66
|
-
wallet_list.data.map do |wallet_model|
|
67
|
+
wallets = wallet_list.data.map do |wallet_model|
|
67
68
|
Wallet.new(wallet_model, seed: '', address_models: address_model_map[wallet_model.id])
|
68
69
|
end
|
70
|
+
|
71
|
+
[wallets, wallet_list.next_page]
|
69
72
|
end
|
70
73
|
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
#
|
78
|
-
# @param wallet [Coinbase::Wallet] The wallet model to save.
|
79
|
-
# @param encrypt [bool] (Optional) Boolean representing whether the backup persisted to local file system should be
|
80
|
-
# encrypted or not. Data is unencrypted by default.
|
81
|
-
# @return [Coinbase::Wallet] the saved wallet.
|
82
|
-
def save_wallet_locally!(wallet, encrypt: false)
|
83
|
-
existing_seeds_in_store = existing_seeds
|
84
|
-
data = wallet.export
|
85
|
-
seed_to_store = data.seed
|
86
|
-
auth_tag = ''
|
87
|
-
iv = ''
|
88
|
-
if encrypt
|
89
|
-
shared_secret = store_encryption_key
|
90
|
-
cipher = OpenSSL::Cipher.new('aes-256-gcm').encrypt
|
91
|
-
cipher.key = OpenSSL::Digest.digest('SHA256', shared_secret)
|
92
|
-
iv = cipher.random_iv
|
93
|
-
cipher.iv = iv
|
94
|
-
cipher.auth_data = ''
|
95
|
-
encrypted_data = cipher.update(data.seed) + cipher.final
|
96
|
-
auth_tag = cipher.auth_tag.unpack1('H*')
|
97
|
-
iv = iv.unpack1('H*')
|
98
|
-
seed_to_store = encrypted_data.unpack1('H*')
|
74
|
+
# Returns the Wallet with the given ID.
|
75
|
+
# @param wallet_id [String] the ID of the Wallet
|
76
|
+
# @return [Coinbase::Wallet] the unhydrated Wallet
|
77
|
+
def wallet(wallet_id)
|
78
|
+
wallet_model = Coinbase.call_api do
|
79
|
+
wallets_api.get_wallet(wallet_id)
|
99
80
|
end
|
100
81
|
|
101
|
-
|
102
|
-
|
103
|
-
encrypted: encrypt,
|
104
|
-
auth_tag: auth_tag,
|
105
|
-
iv: iv
|
106
|
-
}
|
107
|
-
|
108
|
-
File.open(Coinbase.configuration.backup_file_path, 'w') do |file|
|
109
|
-
file.write(JSON.pretty_generate(existing_seeds_in_store))
|
82
|
+
addresses_list = Coinbase.call_api do
|
83
|
+
addresses_api.list_addresses(wallet_model.id, { limit: Coinbase::Wallet::MAX_ADDRESSES })
|
110
84
|
end
|
111
|
-
wallet
|
112
|
-
end
|
113
85
|
|
114
|
-
|
115
|
-
# @return [Map<String>Coinbase::Wallet] the map of wallet_ids to the wallets.
|
116
|
-
def load_wallets_from_local
|
117
|
-
existing_seeds_in_store = existing_seeds
|
118
|
-
raise ArgumentError, 'Backup file not found' if existing_seeds_in_store == {}
|
119
|
-
|
120
|
-
wallets = {}
|
121
|
-
existing_seeds_in_store.each do |wallet_id, seed_data|
|
122
|
-
seed = seed_data['seed']
|
123
|
-
raise ArgumentError, 'Malformed backup data' if seed.nil? || seed == ''
|
124
|
-
|
125
|
-
if seed_data['encrypted']
|
126
|
-
shared_secret = store_encryption_key
|
127
|
-
raise ArgumentError, 'Malformed encrypted seed data' if seed_data['iv'] == '' ||
|
128
|
-
seed_data['auth_tag'] == ''
|
129
|
-
|
130
|
-
cipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
|
131
|
-
cipher.key = OpenSSL::Digest.digest('SHA256', shared_secret)
|
132
|
-
iv = [seed_data['iv']].pack('H*')
|
133
|
-
cipher.iv = iv
|
134
|
-
auth_tag = [seed_data['auth_tag']].pack('H*')
|
135
|
-
cipher.auth_tag = auth_tag
|
136
|
-
cipher.auth_data = ''
|
137
|
-
hex_decoded_data = [seed_data['seed']].pack('H*')
|
138
|
-
seed = cipher.update(hex_decoded_data) + cipher.final
|
139
|
-
end
|
140
|
-
|
141
|
-
data = Coinbase::Wallet::Data.new(wallet_id: wallet_id, seed: seed)
|
142
|
-
wallets[wallet_id] = import_wallet(data)
|
143
|
-
end
|
144
|
-
wallets
|
86
|
+
Wallet.new(wallet_model, seed: '', address_models: addresses_list.data)
|
145
87
|
end
|
146
88
|
|
147
89
|
# Returns a string representation of the User.
|
@@ -165,22 +107,5 @@ module Coinbase
|
|
165
107
|
def wallets_api
|
166
108
|
@wallets_api ||= Coinbase::Client::WalletsApi.new(Coinbase.configuration.api_client)
|
167
109
|
end
|
168
|
-
|
169
|
-
def existing_seeds
|
170
|
-
existing_seed_data = '{}'
|
171
|
-
file_path = Coinbase.configuration.backup_file_path
|
172
|
-
existing_seed_data = File.read(file_path) if File.exist?(file_path)
|
173
|
-
output = JSON.parse(existing_seed_data)
|
174
|
-
|
175
|
-
raise ArgumentError, 'Malformed backup data' unless output.is_a?(Hash)
|
176
|
-
|
177
|
-
output
|
178
|
-
end
|
179
|
-
|
180
|
-
def store_encryption_key
|
181
|
-
pk = OpenSSL::PKey.read(Coinbase.configuration.api_key_private_key)
|
182
|
-
public_key = pk.public_key # use own public key to generate the shared secret.
|
183
|
-
pk.dh_compute_key(public_key)
|
184
|
-
end
|
185
110
|
end
|
186
111
|
end
|