Class: Coinbase::Wallet
- Inherits:
-
Object
- Object
- Coinbase::Wallet
- Defined in:
- lib/coinbase/wallet.rb
Overview
A representation of a Wallet. Wallets come with a single default Address, but can expand to have a set of Addresses, each of which can hold a balance of one or more Assets. Wallets can create new Addresses, list their addresses, list their balances, and transfer Assets to other Addresses. Wallets should be created through User#create_wallet or User#import_wallet.
Defined Under Namespace
Modules: ServerSignerStatus Classes: Data
Constant Summary collapse
- MAX_ADDRESSES =
The maximum number of addresses in a Wallet.
20
Instance Attribute Summary collapse
-
#addresses ⇒ Object
readonly
Returns the value of attribute addresses.
-
#model ⇒ Object
readonly
Returns the value of attribute model.
Class Method Summary collapse
-
.create(network_id: 'base-sepolia', interval_seconds: 0.2, timeout_seconds: 20) ⇒ Coinbase::Wallet
Creates a new Wallet on the specified Network and generate a default address for it.
-
.import(data) ⇒ Coinbase::Wallet
Imports a Wallet from previously exported wallet data.
Instance Method Summary collapse
-
#address(address_id) ⇒ Address
Returns the Address with the given ID.
-
#balance(asset_id) ⇒ BigDecimal
Returns the balance of the provided Asset.
-
#balances ⇒ BalanceMap
Returns the list of balances of this Wallet.
-
#can_sign? ⇒ Boolean
Returns whether the Wallet has a seed with which to derive keys and sign transactions.
-
#create_address ⇒ Address
Creates a new Address in the Wallet.
-
#default_address ⇒ Address
Returns the default address of the Wallet.
-
#export ⇒ Data
Exports the Wallet’s data to a Data object.
-
#faucet ⇒ Coinbase::FaucetTransaction
Requests funds from the faucet for the Wallet’s default address and returns the faucet transaction.
-
#id ⇒ String
Returns the Wallet ID.
-
#initialize(model, seed: nil, address_models: []) ⇒ Wallet
constructor
Returns a new Wallet object.
-
#inspect ⇒ String
Same as to_s.
-
#load_seed(file_path) ⇒ String
Loads the seed of the Wallet from the given file.
-
#network_id ⇒ Symbol
Returns the Network ID of the Wallet.
-
#save_seed!(file_path, encrypt: false) ⇒ String
Saves the seed of the Wallet to the given file.
-
#seed=(seed) ⇒ Object
Sets the seed of the Wallet.
-
#server_signer_status ⇒ Symbol
Returns the ServerSigner Status of the Wallet.
-
#to_s ⇒ String
Returns a String representation of the Wallet.
-
#trade(amount, from_asset_id, to_asset_id) ⇒ Coinbase::Trade
Trades the given amount of the given Asset for another Asset.
-
#transfer(amount, asset_id, destination) ⇒ Coinbase::Transfer
Transfers the given amount of the given Asset to the specified address or wallet.
Constructor Details
#initialize(model, seed: nil, address_models: []) ⇒ Wallet
Returns a new Wallet object. Do not use this method directly. Instead, use User#create_wallet or User#import_wallet.
125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/coinbase/wallet.rb', line 125 def initialize(model, seed: nil, address_models: []) validate_seed_and_address_models(seed, address_models) unless Coinbase.use_server_signer? @model = model @addresses = [] unless Coinbase.use_server_signer? @master = master_node(seed) @private_key_index = 0 end derive_addresses(address_models) end |
Instance Attribute Details
#addresses ⇒ Object (readonly)
Returns the value of attribute addresses.
15 16 17 |
# File 'lib/coinbase/wallet.rb', line 15 def addresses @addresses end |
#model ⇒ Object (readonly)
Returns the value of attribute model.
15 16 17 |
# File 'lib/coinbase/wallet.rb', line 15 def model @model end |
Class Method Details
.create(network_id: 'base-sepolia', interval_seconds: 0.2, timeout_seconds: 20) ⇒ Coinbase::Wallet
Creates a new Wallet on the specified Network and generate a default address for it. have an active seed, if using a ServerSigner, in seconds create a seed for the Wallet, in seconds
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/coinbase/wallet.rb', line 56 def create(network_id: 'base-sepolia', interval_seconds: 0.2, timeout_seconds: 20) model = Coinbase.call_api do wallets_api.create_wallet( create_wallet_request: { wallet: { network_id: network_id, use_server_signer: Coinbase.use_server_signer? } } ) end wallet = new(model) # When used with a ServerSigner, the Signer must first register # with the Wallet before addresses can be created. wait_for_signer(wallet.id, interval_seconds, timeout_seconds) if Coinbase.use_server_signer? wallet.create_address wallet end |
.import(data) ⇒ Coinbase::Wallet
Imports a Wallet from previously exported wallet data.
35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/coinbase/wallet.rb', line 35 def import(data) raise ArgumentError, 'data must be a Coinbase::Wallet::Data object' unless data.is_a?(Data) model = Coinbase.call_api do wallets_api.get_wallet(data.wallet_id) end address_list = Coinbase.call_api do addresses_api.list_addresses(model.id, { limit: MAX_ADDRESSES }) end new(model, seed: data.seed, address_models: address_list.data) end |
Instance Method Details
#address(address_id) ⇒ Address
Returns the Address with the given ID.
210 211 212 |
# File 'lib/coinbase/wallet.rb', line 210 def address(address_id) @addresses.find { |address| address.id == address_id } end |
#balance(asset_id) ⇒ BigDecimal
Returns the balance of the provided Asset. Balances are aggregated across all Addresses in the Wallet.
227 228 229 230 231 232 233 234 235 |
# File 'lib/coinbase/wallet.rb', line 227 def balance(asset_id) response = Coinbase.call_api do wallets_api.get_wallet_balance(id, Coinbase::Asset.primary_denomination(asset_id).to_s) end return BigDecimal('0') if response.nil? Coinbase::Balance.from_model_and_asset_id(response, asset_id).amount end |
#balances ⇒ BalanceMap
Returns the list of balances of this Wallet. Balances are aggregated across all Addresses in the Wallet.
216 217 218 219 220 221 222 |
# File 'lib/coinbase/wallet.rb', line 216 def balances response = Coinbase.call_api do wallets_api.list_wallet_balances(id) end Coinbase::BalanceMap.from_balances(response.data) end |
#can_sign? ⇒ Boolean
Returns whether the Wallet has a seed with which to derive keys and sign transactions.
283 284 285 |
# File 'lib/coinbase/wallet.rb', line 283 def can_sign? !@master.nil? end |
#create_address ⇒ Address
Creates a new Address in the Wallet.
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/coinbase/wallet.rb', line 177 def create_address opts = { create_address_request: {} } unless Coinbase.use_server_signer? key = derive_key opts = { create_address_request: { public_key: key.public_key.compressed.unpack1('H*'), attestation: create_attestation(key) } } end address_model = Coinbase.call_api do addresses_api.create_address(id, opts) end # Auto-reload wallet to set default address on first address creation. reload if addresses.empty? cache_address(address_model, key) end |
#default_address ⇒ Address
Returns the default address of the Wallet.
203 204 205 |
# File 'lib/coinbase/wallet.rb', line 203 def default_address address(@model.default_address&.address_id) end |
#export ⇒ Data
Exports the Wallet’s data to a Data object.
261 262 263 264 265 266 267 268 |
# File 'lib/coinbase/wallet.rb', line 261 def export # TODO: Improve this check by relying on the backend data to decide whether a wallet is server-signer backed. raise 'Cannot export data for Server-Signer backed Wallet' if Coinbase.use_server_signer? raise 'Cannot export Wallet without loaded seed' if @master.nil? Data.new(wallet_id: id, seed: @master.seed_hex) end |
#faucet ⇒ Coinbase::FaucetTransaction
Requests funds from the faucet for the Wallet’s default address and returns the faucet transaction. This is only supported on testnet networks.
275 276 277 278 279 |
# File 'lib/coinbase/wallet.rb', line 275 def faucet Coinbase.call_api do Coinbase::FaucetTransaction.new(addresses_api.request_faucet_funds(id, default_address.id)) end end |
#id ⇒ String
Returns the Wallet ID.
141 142 143 |
# File 'lib/coinbase/wallet.rb', line 141 def id @model.id end |
#inspect ⇒ String
Same as to_s.
377 378 379 |
# File 'lib/coinbase/wallet.rb', line 377 def inspect to_s end |
#load_seed(file_path) ⇒ String
Loads the seed of the Wallet from the given file.
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'lib/coinbase/wallet.rb', line 332 def load_seed(file_path) raise 'Wallet already has seed loaded' unless @master.nil? existing_seeds_in_store = existing_seeds(file_path) raise ArgumentError, "File #{file_path} does not contain seed data" if existing_seeds_in_store == {} if existing_seeds_in_store[id].nil? raise ArgumentError, "File #{file_path} does not contain seed data for wallet #{id}" end seed_data = existing_seeds_in_store[id] local_seed = seed_data['seed'] raise ArgumentError, 'Seed data is malformed' if local_seed.nil? || local_seed == '' if seed_data['encrypted'] raise ArgumentError, 'Encrypted seed data is malformed' if seed_data['iv'] == '' || seed_data['auth_tag'] == '' cipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt cipher.key = OpenSSL::Digest.digest('SHA256', encryption_key) iv = [seed_data['iv']].pack('H*') cipher.iv = iv auth_tag = [seed_data['auth_tag']].pack('H*') cipher.auth_tag = auth_tag cipher.auth_data = '' hex_decoded_data = [seed_data['seed']].pack('H*') local_seed = cipher.update(hex_decoded_data) + cipher.final end self.seed = local_seed "Successfully loaded seed for wallet #{id} from #{file_path}." end |
#network_id ⇒ Symbol
Returns the Network ID of the Wallet.
147 148 149 |
# File 'lib/coinbase/wallet.rb', line 147 def network_id Coinbase.to_sym(@model.network_id) end |
#save_seed!(file_path, encrypt: false) ⇒ String
Saves the seed of the Wallet to the given file. Wallets whose seeds are saved this way can be rehydrated using load_seed. A single file can be used for multiple Wallet seeds. This is an insecure method of storing Wallet seeds and should only be used for development purposes.
encrypted or not. Data is unencrypted by default.
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/coinbase/wallet.rb', line 295 def save_seed!(file_path, encrypt: false) raise 'Wallet does not have seed loaded' if @master.nil? existing_seeds_in_store = existing_seeds(file_path) seed_to_store = @master.seed_hex auth_tag = '' iv = '' if encrypt cipher = OpenSSL::Cipher.new('aes-256-gcm').encrypt cipher.key = OpenSSL::Digest.digest('SHA256', encryption_key) iv = cipher.random_iv cipher.iv = iv cipher.auth_data = '' encrypted_data = cipher.update(@master.seed_hex) + cipher.final auth_tag = cipher.auth_tag.unpack1('H*') iv = iv.unpack1('H*') seed_to_store = encrypted_data.unpack1('H*') end existing_seeds_in_store[id] = { seed: seed_to_store, encrypted: encrypt, auth_tag: auth_tag, iv: iv } File.open(file_path, 'w') do |file| file.write(JSON.pretty_generate(existing_seeds_in_store)) end "Successfully saved seed for wallet #{id} to #{file_path}." end |
#seed=(seed) ⇒ Object
Sets the seed of the Wallet. This seed is used to derive keys and sign transactions.
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/coinbase/wallet.rb', line 159 def seed=(seed) raise ArgumentError, 'Seed must be 32 bytes' if seed.length != 64 raise 'Seed is already set' unless @master.nil? raise 'Cannot set seed for Wallet with non-zero private key index' if @private_key_index.positive? @master = MoneyTree::Master.new(seed_hex: seed) @addresses.each do key = derive_key a = address(key.address.to_s) raise "Seed does not match wallet; cannot find address #{key.address}" if a.nil? a.key = key end end |
#server_signer_status ⇒ Symbol
Returns the ServerSigner Status of the Wallet.
153 154 155 |
# File 'lib/coinbase/wallet.rb', line 153 def server_signer_status Coinbase.to_sym(@model.server_signer_status) end |
#to_s ⇒ String
Returns a String representation of the Wallet.
370 371 372 373 |
# File 'lib/coinbase/wallet.rb', line 370 def to_s "Coinbase::Wallet{wallet_id: '#{id}', network_id: '#{network_id}', " \ "default_address: '#{@model.default_address&.address_id}'}" end |
#trade(amount, from_asset_id, to_asset_id) ⇒ Coinbase::Trade
Trades the given amount of the given Asset for another Asset. Currently only the default_address is used to source the Trade
255 256 257 |
# File 'lib/coinbase/wallet.rb', line 255 def trade(amount, from_asset_id, to_asset_id) default_address.trade(amount, from_asset_id, to_asset_id) end |
#transfer(amount, asset_id, destination) ⇒ Coinbase::Transfer
Transfers the given amount of the given Asset to the specified address or wallet. Only same-network Transfers are supported. Currently only the default_address is used to source the Transfer.
244 245 246 |
# File 'lib/coinbase/wallet.rb', line 244 def transfer(amount, asset_id, destination) default_address.transfer(amount, asset_id, destination) end |