3

In order to delegate funds they must be moved into a staking account. The staking account address can be anything, even random bits as long as it doesn’t collide with any other staking account address. The official docs for solana staking https://docs.solana.com/cli/delegate-stake just get some random bits by generating a key pair and using the public key and throwing the rest away.

How would a wallet identify the staking accounts that belong to a user (can be withdrawn from) if the staking address has no relationship to any other address? What are the best practices here to generate a staking account address in wallet development? Do wallets in practice use bip32 key derivations to deterministically generate the key pair or is it really random?

1 Answer 1

1

You have a few options, but yes, typically you just use new random keypairs for your stake accounts. When you want to fetch all of your stake accounts, you can use get_program_accounts with a memcmp filter with your pubkey, ie:

  use {
      base64::{engine::general_purpose, Engine as _},
      solana_account_decoder::UiAccountEncoding,
      solana_client::{
          rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
          rpc_filter::{MemcmpEncodedBytes, RpcFilterType, Memcmp},
          nonblocking::rpc_client::RpcClient,
      },
      solana_sdk::{
          commitment_config::CommitmentConfig,
          pubkey::Pubkey,
          stake::{self, state::StakeState},
      },
  };
// your prep code here to create `rpc_client`
      let staker = Pubkey::try_from("your_key_here_as_base_58").unwrap();
      let staker_base64 = general_purpose::STANDARD.encode(&staker.as_ref());
      let memcmp = RpcFilterType::Memcmp(Memcmp {
          offset: 4 + 8,
          bytes: MemcmpEncodedBytes::Base64(staker_base64),
          encoding: None,
      });
      let config = RpcProgramAccountsConfig {
          filters: Some(vec![memcmp]),
          account_config: RpcAccountInfoConfig {
              encoding: Some(UiAccountEncoding::Base64),
              data_slice: None,
              commitment: Some(CommitmentConfig::confirmed()),
              min_context_slot: None,
          },
          with_context: Some(false),
      };
      let program_accounts = rpc_client.get_program_accounts_with_config(
              &stake::id(),
              config,
      ).await?;
      for (_, account) in program_accounts {
          println!("{account:?}");
          let stake = bincode::deserialize::<StakeState>(&account.data)?;
          println!("{stake:?}");
      }

The stake program contains a CreateAccountWithSeed instruction, which derives a new address from the signing keypair and a seed. If you wanted, you could use numerical seeds and just increment each time. When fetching your accounts, you start with seed 1, derive the address, and try to fetch an account there.

$ solana create-stake-account .config/solana/id.json 10 --seed 1
...
$ solana create-stake-account .config/solana/id.json 10 --seed 2

Note that this is a bit different from a program-derived address.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.