Class: Coinbase::Wallet

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/coinbase/wallet.rb,
lib/coinbase/wallet/data.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

Classes: Data

Constant Summary collapse

MAX_ADDRESSES =

The maximum number of addresses in a Wallet.

20

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, seed: nil) ⇒ Wallet

Returns a new Wallet object. Do not use this method directly. Instead use Coinbase::Wallet.create.

Parameters:

  • model (Coinbase::Client::Wallet)

    The underlying Wallet object

  • seed (String) (defaults to: nil)

    (Optional) The seed to use for the Wallet. Expects a 32-byte hexadecimal with no 0x prefix. If nil, a new seed will be generated. If the empty string, no seed is generated, and the Wallet will be instantiated without a seed and its corresponding private keys. with the Wallet. If not provided, the Wallet will derive the first default address.

Raises:

  • (ArgumentError)


135
136
137
138
139
140
141
142
143
# File 'lib/coinbase/wallet.rb', line 135

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: Coinbase.default_network, 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

Parameters:

  • network (Coinbase::Network, Symbol) (defaults to: Coinbase.default_network)

    (Optional) The network object or ID to create the Wallet on. When omitted this uses the SDK configured default network.

  • interval_seconds (Integer) (defaults to: 0.2)

    The interval at which to poll the CDPService for the Wallet to

  • timeout_seconds (Integer) (defaults to: 20)

    The maximum amount of time to wait for the ServerSigner to

Returns:



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/coinbase/wallet.rb', line 66

def create(network: Coinbase.default_network, 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),
          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.

Parameters:

  • wallet_id (String)

    The ID of the Wallet to fetch

Returns:



50
51
52
53
54
55
56
# File 'lib/coinbase/wallet.rb', line 50

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.

Parameters:

Returns:

Raises:

  • (ArgumentError)


24
25
26
27
28
29
30
31
32
# File 'lib/coinbase/wallet.rb', line 24

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

.listEnumerable<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…

Returns:



38
39
40
41
42
# File 'lib/coinbase/wallet.rb', line 38

def list
  Coinbase::Pagination.enumerate(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.

Parameters:

  • address_id (String)

    The ID of the Address to retrieve

Returns:



362
363
364
# File 'lib/coinbase/wallet.rb', line 362

def address(address_id)
  addresses.find { |address| address.id == address_id }
end

#addressesArray<Coinbase::WalletAddress>

Returns the addresses belonging to the Wallet.

Returns:



267
268
269
270
271
# File 'lib/coinbase/wallet.rb', line 267

def addresses
  return @addresses unless @addresses.nil?

  set_addresses
end

#balance(asset_id) ⇒ BigDecimal

Returns the balance of the provided Asset. Balances are aggregated across all Addresses in the Wallet.

Parameters:

  • asset_id (Symbol)

    The ID of the Asset to retrieve the balance for

Returns:

  • (BigDecimal)

    The balance of the Asset



379
380
381
382
383
384
385
386
387
# File 'lib/coinbase/wallet.rb', line 379

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

#balancesBalanceMap

Returns the list of balances of this Wallet. Balances are aggregated across all Addresses in the Wallet.

Returns:

  • (BalanceMap)

    The list of balances. The key is the Asset ID, and the value is the balance.



368
369
370
371
372
373
374
# File 'lib/coinbase/wallet.rb', line 368

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.

Returns:

  • (Boolean)

    Whether the Wallet has a seed with which to derive keys and sign transactions.



402
403
404
# File 'lib/coinbase/wallet.rb', line 402

def can_sign?
  !@master.nil?
end

#claim_stakeCoinbase::StakingOperation

Claims stake of the given amount of the given Asset for the default address.

Parameters:

  • amount (Integer, Float, BigDecimal)

    The amount of the Asset to claim_stake.

  • asset_id (Symbol)

    The ID of the Asset to claim_stake.

  • mode (Symbol)

    (Optional) The staking mode. Defaults to :default.

  • options (Hash)

    (Optional) Additional options for the unstaking operation.

Returns:



# File 'lib/coinbase/wallet.rb', line 188

#claimable_balanceBigDecimal

Retrieves the claimable balance for the supplied asset. Currently only the default_address is used to source the claimable balance.

Parameters:

  • asset_id (Symbol)

    The asset to retrieve the claimable balance for

  • mode (Symbol)

    The staking mode. Defaults to :default.

  • options (Hash)

    Additional options for the staking operation

Returns:

  • (BigDecimal)

    The claimable balance



# File 'lib/coinbase/wallet.rb', line 221

#create_addressAddress

Creates a new Address in the Wallet.

Returns:



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/coinbase/wallet.rb', line 315

def create_address
  req = {}

  # Ensure that the address cache is set before creating a new address.
  # This ensures that for a server signer, the addresses have been loaded and we
  # can create a new address and add it to a cache.
  set_addresses if @addresses.nil?

  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)

    req = {
      public_key: key.public_key.compressed.unpack1('H*'),
      attestation: create_attestation(key),
      address_index: private_key_index
    }
  end

  address_model = Coinbase.call_api do
    addresses_api.create_address(id, { create_address_request: req })
  end

  # Default address can be nil because either this is the first address being
  # created for this wallet or the addresses cache has not yet been loaded.

  # If the default address is nil, we must reload the wallet model after creating
  # the address, in order for the default address to be set.
  reload if default_address.nil?

  # The addreses cache is already created, so we can add the new address to the cache.
  address = WalletAddress.new(address_model, key)
  @addresses << address
  address
end

#create_webhook(notification_uri:) ⇒ Coinbase::Client::Webhook

Creates a new webhook on the current wallet for tracking wallet activity events.

Parameters:

  • notification_uri (String)

    The URI to which the webhook notifications will be sent.

Returns:



490
491
492
493
494
495
496
497
498
499
# File 'lib/coinbase/wallet.rb', line 490

def create_webhook(notification_uri:)
  Coinbase.call_api do
    webhooks_api.create_wallet_webhook(
      id,
      create_wallet_webhook_request: {
        notification_uri: notification_uri
      }
    )
  end
end

#default_addressAddress

Returns the default address of the Wallet.

Returns:

  • (Address)

    The default address



355
356
357
# File 'lib/coinbase/wallet.rb', line 355

def default_address
  address(@model.default_address&.address_id)
end

#deploy_multi_tokenCoinbase::SmartContract

Deploys a new ERC1155 multi-token contract with the given URI.

Parameters:

  • uri (String)

    The URI for the token metadata, where #id will be replaced with the token ID.

Returns:

Raises:



261
262
263
# File 'lib/coinbase/wallet.rb', line 261

def_delegators :default_address, :transfer, :trade, :faucet, :stake, :unstake, :claim_stake, :staking_balances,
:stakeable_balance, :unstakeable_balance, :claimable_balance, :sign_payload, :invoke_contract,
:deploy_token, :deploy_nft, :deploy_multi_token

#deploy_nftCoinbase::SmartContract

Deploys a new ERC721 NFT contract with the given name, symbol, and base URI.

Parameters:

  • name (String)

    The name of the NFT contract.

  • symbol (String)

    The symbol of the NFT contract.

  • base_uri (String)

    The base URI for the NFT contract.

Returns:

Raises:



# File 'lib/coinbase/wallet.rb', line 247

#deploy_tokenCoinbase::SmartContract

Deploys a new ERC20 token contract with the given name, symbol, and total supply. whole units.

Parameters:

  • name (String)

    The name of the token.

  • symbol (String)

    The symbol of the token.

  • total_supply (Integer, BigDecimal)

    The total supply of the token, denominated in

Returns:

Raises:



# File 'lib/coinbase/wallet.rb', line 238

#exportCoinbase::Wallet::Data

Exports the Wallet's data to a Data object.

Returns:



391
392
393
394
395
396
397
398
# File 'lib/coinbase/wallet.rb', line 391

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

#faucetCoinbase::FaucetTransaction

Requests funds from the faucet for the Wallet's default address and returns the faucet transaction. This is only supported on testnet networks.

Parameters:

  • asset_id (Symbol)

    The ID of the Asset to transfer to the wallet.

Returns:

Raises:



# File 'lib/coinbase/wallet.rb', line 164

#idString

Returns the Wallet ID.

Returns:

  • (String)

    The Wallet ID



275
276
277
# File 'lib/coinbase/wallet.rb', line 275

def id
  @model.id
end

#inspectString

Same as to_s.

Returns:

  • (String)

    a String representation of the Wallet



514
515
516
# File 'lib/coinbase/wallet.rb', line 514

def inspect
  to_s
end

#invoke_contractObject

Invokes a contract with the given ABI, method, and arguments.

Parameters:

  • abi (Array<Hash>)

    The ABI of the contract



# File 'lib/coinbase/wallet.rb', line 234

#load_seed(file_path) ⇒ String

Loads the seed of the Wallet from the given file.

Parameters:

  • file_path (String)

    The path of the file to load the seed from

Returns:

  • (String)

    A string indicating the success of the operation

Raises:

  • (ArgumentError)


449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/coinbase/wallet.rb', line 449

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

#networkCoinbase::Network

Returns the Network of the Wallet.

Returns:



281
282
283
# File 'lib/coinbase/wallet.rb', line 281

def network
  @network ||= Coinbase::Network.from_id(@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.

Parameters:

  • file_path (String)

    The path of the file to save the seed to

  • encrypt (bool) (defaults to: false)

    (Optional) Whether the seed information persisted to the local file system should be

Returns:

  • (String)

    A string indicating the success of the operation



414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/coinbase/wallet.rb', line 414

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.write(file_path, JSON.pretty_generate(existing_seeds_in_store))

  "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.

Parameters:

  • seed (String)

    The seed to set. Expects a 32-byte hexadecimal with no 0x prefix.

Raises:

  • (ArgumentError)


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/coinbase/wallet.rb', line 293

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_statusSymbol

Returns the ServerSigner Status of the Wallet.

Returns:

  • (Symbol)

    The ServerSigner Status



287
288
289
# File 'lib/coinbase/wallet.rb', line 287

def server_signer_status
  Coinbase.to_sym(@model.server_signer_status)
end

#sign_payloadCoinbase::PayloadSignature

Signs the given unsigned payload.

Parameters:

  • unsigned_payload (String)

    The hex-encoded hashed unsigned payload for the Address to sign.

Returns:



# File 'lib/coinbase/wallet.rb', line 229

#stakeCoinbase::StakingOperation

Stakes the given amount of the given Asset for the default address.

Parameters:

  • amount (Integer, Float, BigDecimal)

    The amount of the Asset to stake.

  • asset_id (Symbol)

    The ID of the Asset to stake.

  • mode (Symbol)

    (Optional) The staking mode. Defaults to :default.

  • options (Hash)

    (Optional) Additional options for the staking operation.

Returns:



# File 'lib/coinbase/wallet.rb', line 172

#stakeable_balanceBigDecimal

Retrieves the stakeable balance of the supplied asset for the default address.

Parameters:

  • asset_id (Symbol)

    The asset to retrieve the stakeable balance for

  • mode (Symbol)

    The staking mode. Defaults to :default.

  • options (Hash)

    Additional options for the staking operation

Returns:

  • (BigDecimal)

    The stakeable balance



# File 'lib/coinbase/wallet.rb', line 206

#staking_balancesHash, BigDecimal

Retrieves the balances used for staking for the supplied asset for the default address.

Parameters:

  • asset_id (Symbol)

    The asset to retrieve staking balances for

  • mode (Symbol)

    The staking mode. Defaults to :default.

  • options (Hash)

    Additional options for the staking operation

Returns:

  • (Hash)

    The staking balances

  • (BigDecimal)

    :stakeable_balance The amount of the asset that can be staked

  • (BigDecimal)

    :unstakeable_balance The amount of the asset that is currently staked and cannot be unstaked

  • (BigDecimal)

    :claimable_balance The amount of the asset that can be claimed



# File 'lib/coinbase/wallet.rb', line 196

#to_sString

Returns a String representation of the Wallet.

Returns:

  • (String)

    a String representation of the Wallet



503
504
505
506
507
508
509
510
# File 'lib/coinbase/wallet.rb', line 503

def to_s
  Coinbase.pretty_print_object(
    self.class,
    id: id,
    network_id: network.id,
    default_address: @model.default_address&.address_id
  )
end

#tradeCoinbase::Trade

Trades the specified amount from one asset to another using the default address.

Parameters:

  • amount (Integer, Float, BigDecimal)

    The amount of the Asset to send.

  • from_asset_id (Symbol)

    The ID of the Asset to trade from.

  • to_asset_id (Symbol)

    The ID of the Asset to trade to. default address. If a String, interprets it as the address ID.

Returns:



# File 'lib/coinbase/wallet.rb', line 156

#transferCoinbase::Transfer

Transfers the amount of the Asset from the default address to the specified destination. (see Coinbase::Address::WalletAddress#transfer)

Parameters:

  • amount (Integer, Float, BigDecimal)

    The amount of the Asset to send

  • asset_id (Symbol)

    The ID of the Asset to send

  • destination (Wallet | Address | String)

    The destination of the transfer. If a Wallet, sends to the Wallet's default address. If a String, interprets it as the address ID.

  • gasless (Boolean)

    Whether the transfer should be gasless. Defaults to false.

Returns:



# File 'lib/coinbase/wallet.rb', line 145

#unstakeCoinbase::StakingOperation

Unstakes the given amount of the given Asset on the default address.

Parameters:

  • amount (Integer, Float, BigDecimal)

    The amount of the Asset to unstake.

  • asset_id (Symbol)

    The ID of the Asset to unstake.

  • mode (Symbol)

    (Optional) The staking mode. Defaults to :default.

  • options (Hash)

    (Optional) Additional options for the unstaking operation.

Returns:



# File 'lib/coinbase/wallet.rb', line 180

#unstakeable_balanceBigDecimal

Retrieves the unstakeable balance for the supplied asset. Currently only the default_address is used to source the unstakeable balance.

Parameters:

  • asset_id (Symbol)

    The asset to retrieve the unstakeable balance for

  • mode (Symbol)

    The staking mode. Defaults to :default.

  • options (Hash)

    Additional options for the staking operation

Returns:

  • (BigDecimal)

    The unstakeable balance



# File 'lib/coinbase/wallet.rb', line 213