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.
Defined Under Namespace
Modules: ServerSignerStatus Classes: Data
Constant Summary collapse
- MAX_ADDRESSES =
The maximum number of addresses in a Wallet.
20- PAGE_LIMIT =
The maximum number of wallets to fetch in a single page.
100
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.
-
.fetch(wallet_id) ⇒ Coinbase::Wallet
Fetches a Wallet by its ID.
-
.import(data) ⇒ Coinbase::Wallet
Imports a Wallet from previously exported wallet data.
-
.list ⇒ Enumerable<Coinbase::Wallet>
Enumerates the wallets for the requesting user.
Instance Method Summary collapse
-
#address(address_id) ⇒ Address
Returns the Address with the given ID.
-
#addresses ⇒ Array<Coinbase::WalletAddress>
Returns the addresses belonging to the Wallet.
-
#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) ⇒ 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) ⇒ Wallet
Returns a new Wallet object. Do not use this method directly. Instead, use User#create_wallet or User#import_wallet.
145 146 147 148 149 150 151 152 153 |
# File 'lib/coinbase/wallet.rb', line 145 def initialize(model, seed: nil) raise ArgumentError, 'model must be a Wallet' unless model.is_a?(Coinbase::Client::Wallet) @model = model return if Coinbase.use_server_signer? @master = master_node(seed) 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
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/coinbase/wallet.rb', line 75 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: Coinbase.normalize_network(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 |
.fetch(wallet_id) ⇒ Coinbase::Wallet
Fetches a Wallet by its ID. The returned wallet can be immediately used for signing operations if backed by a server signer. If the wallet is not backed by a server signer, the wallet’s seed will need to be set before it can be used for signing operations.
60 61 62 63 64 65 66 |
# File 'lib/coinbase/wallet.rb', line 60 def fetch(wallet_id) model = Coinbase.call_api do wallets_api.get_wallet(wallet_id) end new(model, seed: '') end |
.import(data) ⇒ Coinbase::Wallet
Imports a Wallet from previously exported wallet data.
34 35 36 37 38 39 40 41 42 |
# File 'lib/coinbase/wallet.rb', line 34 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 new(model, seed: data.seed) end |
.list ⇒ Enumerable<Coinbase::Wallet>
Enumerates the wallets for the requesting user. The result is an enumerator that lazily fetches from the server, and can be iterated over, converted to an array, etc…
48 49 50 51 52 |
# File 'lib/coinbase/wallet.rb', line 48 def list Coinbase::Pagination.enumerate(lambda(&method(:fetch_wallets_page))) do |wallet| Coinbase::Wallet.new(wallet, seed: '') end end |
Instance Method Details
#address(address_id) ⇒ Address
Returns the Address with the given ID.
251 252 253 |
# File 'lib/coinbase/wallet.rb', line 251 def address(address_id) addresses.find { |address| address.id == address_id } end |
#addresses ⇒ Array<Coinbase::WalletAddress>
Returns the addresses belonging to the Wallet.
157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/coinbase/wallet.rb', line 157 def addresses @addresses ||= begin address_list = Coinbase.call_api do addresses_api.list_addresses(@model.id, { limit: MAX_ADDRESSES }) end # Build the WalletAddress objects, injecting the key if available. address_list.data.each_with_index.map do |address_model, index| build_wallet_address(address_model, index) end end end |
#balance(asset_id) ⇒ BigDecimal
Returns the balance of the provided Asset. Balances are aggregated across all Addresses in the Wallet.
268 269 270 271 272 273 274 275 276 |
# File 'lib/coinbase/wallet.rb', line 268 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.
257 258 259 260 261 262 263 |
# File 'lib/coinbase/wallet.rb', line 257 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.
324 325 326 |
# File 'lib/coinbase/wallet.rb', line 324 def can_sign? !@master.nil? end |
#create_address ⇒ Address
Creates a new Address in the Wallet.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/coinbase/wallet.rb', line 212 def create_address opts = { create_address_request: {} } unless Coinbase.use_server_signer? # The index for the next address is the number of addresses already registered. private_key_index = addresses.count key = derive_key(private_key_index) 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 default_address.nil? # Cache the address in our memoized list address = WalletAddress.new(address_model, key) @addresses << address address end |
#default_address ⇒ Address
Returns the default address of the Wallet.
244 245 246 |
# File 'lib/coinbase/wallet.rb', line 244 def default_address address(@model.default_address&.address_id) end |
#export ⇒ Data
Exports the Wallet’s data to a Data object.
302 303 304 305 306 307 308 309 |
# File 'lib/coinbase/wallet.rb', line 302 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.
316 317 318 319 320 |
# File 'lib/coinbase/wallet.rb', line 316 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.
172 173 174 |
# File 'lib/coinbase/wallet.rb', line 172 def id @model.id end |
#inspect ⇒ String
Same as to_s.
418 419 420 |
# File 'lib/coinbase/wallet.rb', line 418 def inspect to_s end |
#load_seed(file_path) ⇒ String
Loads the seed of the Wallet from the given file.
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/coinbase/wallet.rb', line 373 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.
178 179 180 |
# File 'lib/coinbase/wallet.rb', line 178 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.
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 367 368 |
# File 'lib/coinbase/wallet.rb', line 336 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.
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/coinbase/wallet.rb', line 190 def seed=(seed) raise ArgumentError, 'Seed must not be empty' if seed.nil? || seed.empty? raise StandardError, 'Seed is already set' unless @master.nil? @master = master_node(seed) # If the addresses are not loaded the keys will be set on them whenever they are loaded. return if @addresses.nil? # If addresses are already loaded, set the keys on each address. addresses.each_with_index.each do |address, index| key = derive_key(index) # If we derive a key the derived address must match the address from the API. raise StandardError, 'Seed does not match wallet' unless address.id == key.address.to_s address.key = key end end |
#server_signer_status ⇒ Symbol
Returns the ServerSigner Status of the Wallet.
184 185 186 |
# File 'lib/coinbase/wallet.rb', line 184 def server_signer_status Coinbase.to_sym(@model.server_signer_status) end |
#to_s ⇒ String
Returns a String representation of the Wallet.
411 412 413 414 |
# File 'lib/coinbase/wallet.rb', line 411 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
296 297 298 |
# File 'lib/coinbase/wallet.rb', line 296 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.
285 286 287 |
# File 'lib/coinbase/wallet.rb', line 285 def transfer(amount, asset_id, destination) default_address.transfer(amount, asset_id, destination) end |