Comptes

Comment créer un compte de système

Créez un compte que le Programme du Systèmeopen in new window possède. Le runtime de Solana donnera au propriétaire du compte l'accès à l'écriture des données et au transfert des lamports. Lors de la création d'un compte, nous devons pré-allouer un espace de stockage fixe en bytes (space) et suffisamment de lamports pour couvrir la rente. La Renteopen in new window est un coût encouru pour maintenir les comptes en vie sur Solana.

Press </> button to view full source
const createAccountParams = {
  fromPubkey: fromPubkey.publicKey,
  newAccountPubkey: newAccountPubkey.publicKey,
  lamports: rentExemptionAmount,
  space,
  programId: SystemProgram.programId,
};

const createAccountTransaction = new Transaction().add(
  SystemProgram.createAccount(createAccountParams)
);

await sendAndConfirmTransaction(connection, createAccountTransaction, [
  fromPubkey,
  newAccountPubkey,
]);
let create_account_ix = system_instruction::create_account(
    &from_pubkey,
    &new_account_pubkey,
    rent_exemption_amount,
    space as u64,
    &from_pubkey,
);

let (recent_blockhash, _) = connection.get_recent_blockhash().unwrap();

let create_account_tx = solana_sdk::transaction::Transaction::new_signed_with_payer(
    &[create_account_ix],
    Some(&from_pubkey),
    &[&from_keypair, &new_account_keypair],
    recent_blockhash,
);

match connection.send_and_confirm_transaction(&create_account_tx) {
    Ok(sig) => loop {
        if let Ok(confirmed) = connection.confirm_transaction(&sig) {
            if confirmed {
                println!("Transaction: {} Status: {}", sig, confirmed);
                break;
            }
        }
    },
    Err(_) => println!("Error creating system account"),
};

Comment calculer les frais de compte

Garder les comptes en vie sur Solana engendre un coût de stockage appelé renteopen in new window. Il est possible de rendre un compte entièrement exempt du paiement de la rente en déposant au moins deux ans de rente. Pour le calcul, vous devez prendre en compte la quantité de données que vous avez l'intention de stocker dans le compte.

import { Connection, clusterApiUrl } from "@solana/web3.js";

(async () => {
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

  // length of data in the account to calculate rent for
  const dataLength = 1500;
  const rentExemptionAmount =
    await connection.getMinimumBalanceForRentExemption(dataLength);
  console.log({
    rentExemptionAmount,
  });
})();
use solana_client::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;

fn main() {
    let rpc_url = String::from("https://api.devnet.solana.com");
    let connection = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
    let data_length = 1500;

    let rent_exemption_amount = connection
        .get_minimum_balance_for_rent_exemption(data_length)
        .unwrap();

    println!("rent exemption amount: {}", rent_exemption_amount);
}
solana rent 1500

Comment créer des comptes avec des seeds

Il est possible d'utiliser createAccountWithSeed pour gérer vos comptes au lieu de créer un tas de paires de clés (Keypair) différentes.

Générer

Press </> button to view full source
PublicKey.createWithSeed(basePubkey, seed, programId);
use solana_program::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, Signer};

fn main() {
    let base_pubkey = Keypair::new().pubkey();
    let seed = "robot001";
    let program_id = solana_program::system_program::id();

    let derived_pubkey = Pubkey::create_with_seed(&base_pubkey, seed, &program_id).unwrap();

    println!("account pubkey: {:?}", derived_pubkey);
}

Créer

Press </> button to view full source
const tx = new Transaction().add(
  SystemProgram.createAccountWithSeed({
    fromPubkey: feePayer.publicKey, // funder
    newAccountPubkey: derived,
    basePubkey: basePubkey,
    seed: seed,
    lamports: 1e8, // 0.1 SOL
    space: 0,
    programId: owner,
  })
);

console.log(
  `txhash: ${await sendAndConfirmTransaction(connection, tx, [feePayer, base])}`
);
let derived_pubkey = Pubkey::create_with_seed(&base_pubkey, seed, &program_id).unwrap();

let ix = system_instruction::create_account_with_seed(
  &fee_payer_pubkey,
  &derived_pubkey,
  &base_pubkey,
  seed,
  LAMPORTS_PER_SOL / 10,
  0,
  &program_id,
);

let tx = solana_sdk::transaction::Transaction::new_signed_with_payer(
  &[ix],
  Some(&fee_payer_pubkey),
  &[&fee_payer_keypair, &base_keypair],
  recent_blockhash,
);

Transférer

Press </> button to view full source
const tx = new Transaction().add(
  SystemProgram.transfer({
    fromPubkey: derived,
    basePubkey: basePubkey,
    toPubkey: Keypair.generate().publicKey, // create a random receiver
    lamports: 0.01 * LAMPORTS_PER_SOL,
    seed: seed,
    programId: programId,
  })
);
console.log(
  `txhash: ${await sendAndConfirmTransaction(connection, tx, [feePayer, base])}`
);

TIP

Seul un compte appartenant au programme du système peut transférer via le programme du système.

Comment créer des PDAs

Les Adresse Dérivées du Programme (PDAs)open in new window sont comme des adresses normales mais avec les différences suivantes :

  1. Elles sont en dehors de la courbe ed25519
  2. Il faut utiliser un programme pour signer au lieu d'une clé privée

Remarque: Les comptes PDAs ne peuvent être créés que dans les programmes. L'adresse peut être créée côté client.

TIP

Bien que le PDA soit dérivé par l'identifiant d'un programme, cela ne signifie pas que le PDA est la propriété de ce programme. (Par exemple, vous pouvez initialiser votre PDA en tant que compte de jetons, qui est un compte appartenant au programme de jetons).

Générer un PDA

findProgramAddress ajoutera un byte supplémentaire à la fin de votre seed. Celui-ci part de 255 jusqu'à 0 et renvoie la première clé publique hors-courbe. Vous obtiendrez toujours le même résultat si vous passez le même identifiant de programme et la même seed.

import { PublicKey } from "@solana/web3.js";

(async () => {
  const programId = new PublicKey(
    "G1DCNUQTSGHehwdLCAmRyAG8hf51eCHrLNUqkgGKYASj"
  );

  let [pda, bump] = await PublicKey.findProgramAddress(
    [Buffer.from("test")],
    programId
  );
  console.log(`bump: ${bump}, pubkey: ${pda.toBase58()}`);
  // you will find the result is different from `createProgramAddress`.
  // It is expected because the real seed we used to calculate is ["test" + bump]
})();
use solana_program::pubkey::Pubkey;
use std::str::FromStr;

fn main() {
    let program_id = Pubkey::from_str("G1DCNUQTSGHehwdLCAmRyAG8hf51eCHrLNUqkgGKYASj").unwrap();

    let (pda, bump_seed) = Pubkey::find_program_address(&[b"test"], &program_id);
    println!("pda: {}, bump: {}", pda, bump_seed);
}

Créer un PDA

Vous trouverez ci-dessous un exemple de programme pour créer un compte PDA appartenant au programme et un exemple pour appeler le programme avec le client.

Programme

L'exemple ci-dessous montre une instruction unique system_instruction::create_account qui crée un compte avec une taille de données allouées space, un montant de lamports rent_lamports pour le PDA dérivé. Ceci est signé avec le PDA en utilisant invoke_signed de façon similaire à ce qui a été dit plus haut.

Press </> button to view full source
invoke_signed(
    &system_instruction::create_account(
        &payer_account_info.key,
        &pda_account_info.key,
        rent_lamports,
        space.into(),
        program_id
    ),
    &[
        payer_account_info.clone(),
        pda_account_info.clone()
    ],
    &[&[&payer_account_info.key.as_ref(), &[bump]]]
)?;

Client

Press </> button to view full source
let tx = new Transaction().add(
  new TransactionInstruction({
    keys: [
      {
        pubkey: feePayer.publicKey,
        isSigner: true,
        isWritable: true,
      },
      {
        pubkey: pda,
        isSigner: false,
        isWritable: true,
      },
      {
        pubkey: SYSVAR_RENT_PUBKEY,
        isSigner: false,
        isWritable: false,
      },
      {
        pubkey: SystemProgram.programId,
        isSigner: false,
        isWritable: false,
      },
    ],
    data: Buffer.from(new Uint8Array([data_size, bump])),
    programId: programId,
  })
);

console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer])}`);

Comment signer avec un PDA

Les PDAs ne peuvent être signés que dans un programme. Vous trouverez ci-dessous un exemple de programme permettant de signer avec un PDA et d'appeler le programme avec le client.

Programme

L'exemple ci-dessous montre une instruction unique qui transfère les SOL d'un PDA qui a été dérivé par la graine escrow à un compte. invoke_signed est utilisé pour signer avec le PDA.

Press </> button to view full source
invoke_signed(
    &system_instruction::transfer(
        &pda_account_info.key,
        &to_account_info.key,
        100_000_000, // 0.1 SOL
    ),
    &[
        pda_account_info.clone(),
        to_account_info.clone(),
        system_program_account_info.clone(),
    ],
    &[&[b"escrow", &[bump_seed]]],
)?;

Client

Press </> button to view full source
let tx = new Transaction().add(
  new TransactionInstruction({
    keys: [
      {
        pubkey: pda,
        // Leave `false` here although we need a pda as a signer.
        // It will be escalated on program if we use invoke_signed.
        isSigner: false,
        isWritable: true,
      },
      {
        pubkey: to.publicKey,
        isSigner: false,
        isWritable: true,
      },
      {
        pubkey: SystemProgram.programId,
        isSigner: false,
        isWritable: false,
      },
    ],
    data: Buffer.from(new Uint8Array([bump])),
    programId: programId,
  })
);

console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer])}`);

Comment obtenir les comptes du programme

Retourne tous les comptes appartenant à un programme. Reportez-vous à la section guides pour plus d'informations sur getProgramAccounts et sa configuration.

import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";

(async () => {
  const MY_PROGRAM_ID = new PublicKey(
    "6a2GdmttJdanBkoHt7f4Kon4hfadx4UTUgJeRkCaiL3U"
  );
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

  const accounts = await connection.getProgramAccounts(MY_PROGRAM_ID);

  console.log(`Accounts for program ${MY_PROGRAM_ID}: `);
  console.log(accounts);

  /*
  // Output

  Accounts for program 6a2GdmttJdanBkoHt7f4Kon4hfadx4UTUgJeRkCaiL3U:  
  [
    {
      account: {
        data: <Buffer 60 06 66 ca 2c 1d c7 85 04 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 fc>,
        executable: false,
        lamports: 1064880,
        owner: [PublicKey],
        rentEpoch: 228
      },
      pubkey: PublicKey {
        _bn: <BN: 82fc5b91154dc5c840cb464ba6a89212d0fd789367c0a1488fb1941d78f9727a>
      }
    },
    {
      account: {
        data: <Buffer 60 06 66 ca 2c 1d c7 85 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 fd>,
        executable: false,
        lamports: 1064880,
        owner: [PublicKey],
        rentEpoch: 229
      },
      pubkey: PublicKey {
        _bn: <BN: 404dc1fe368cf194f20cf3c681a071c61893ced98f65cda12ba5a147e984e669>
      }
    }
  ]
  */
})();
use solana_client::rpc_client::RpcClient;
use solana_program::pubkey::Pubkey;
use solana_sdk::commitment_config::CommitmentConfig;
use std::str::FromStr;

fn main() {
    let rpc_url = String::from("https://api.devnet.solana.com");
    let connection = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());

    let program_id = Pubkey::from_str("6a2GdmttJdanBkoHt7f4Kon4hfadx4UTUgJeRkCaiL3U").unwrap();
    let accounts = connection.get_program_accounts(&program_id).unwrap();

    println!("accounts for {}, {:?}", program_id, accounts);
}
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '
 {"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["6a2GdmttJdanBkoHt7f4Kon4hfadx4UTUgJeRkCaiL3U"]}
'

# Output
# {"jsonrpc":"2.0","result":[{"account":{"data":"fe2kiXpgfrjWQjCPX3n5MB339Ayqav75ej","executable":false,"lamports":1064880,"owner":"6a2GdmttJdanBkoHt7f4Kon4hfadx4UTUgJeRkCaiL3U","rentEpoch":228},"pubkey":"9pKBrUtJU9GNmct6T2BQtiKqvubtjS9D2if2bm1P8TQd"},{"account":{"data":"fe2kiXpgfrjVs7hiZJNVFsbJUuhXhFx3pQ","executable":false,"lamports":1064880,"owner":"6a2GdmttJdanBkoHt7f4Kon4hfadx4UTUgJeRkCaiL3U","rentEpoch":229},"pubkey":"5L1rztbopmgGMWPKb2efoGyhGnrBJm6K53Hf9S4nxdHr"}],"id":1}

Comment fermer des comptes

Vous pouvez fermer un compte (effacer toutes les données stockées) en retirant tous les SOL détenus par ce compte. (vous pouvez vous référer à renteopen in new window pour plus d'informations)

Programme

Press </> button to view full source
let dest_starting_lamports = dest_account_info.lamports();
**dest_account_info.lamports.borrow_mut() = dest_starting_lamports
    .checked_add(source_account_info.lamports())
    .unwrap();
**source_account_info.lamports.borrow_mut() = 0;

let mut source_data = source_account_info.data.borrow_mut();
source_data.fill(0);

Client

Press </> button to view full source
// 1. create an account to your program
let createNewAccountTx = new Transaction().add(
  SystemProgram.createAccount({
    fromPubkey: feePayer.publicKey,
    newAccountPubkey: newAccount.publicKey,
    lamports: 1e8, // 0.1 SOL
    space: 10, // a random value
    programId: programId,
  })
);

// 2. close your account
let closeAccountTx = new Transaction().add(
  new TransactionInstruction({
    keys: [
      {
        pubkey: newAccount.publicKey,
        isSigner: false,
        isWritable: true,
      },
      {
        pubkey: feePayer.publicKey,
        isSigner: false,
        isWritable: true,
      },
    ],
    programId: programId,
  })
);

Comment obtenir le solde d'un compte

Press </> button to view full source
console.log(`${(await connection.getBalance(wallet)) / LAMPORTS_PER_SOL} SOL`);
connection.get_balance(&wallet).unwrap();
client = Client("https://api.devnet.solana.com")

key_pair = Keypair()
public_key = key_pair.public_key

client.get_balance(public_key)

TIP

Si vous voulez obtenir un solde de jetons, vous devez connaître l'adresse du compte de jetons. Pour plus d'informations, voir Références des Jetons

Last Updated: 10/5/2022, 2:09:02 PM