Ana içeriğe geç

Solana programında PDA İmzacı ile CPI Nasıl Yapılır

Bu kılavuz, Anchor framework'ü kullanarak gönderenin programın imzalamak zorunda olduğu bir PDA olduğu Cross-Program Invocation (CPI) kullanarak SOL transferinin nasıl yapılacağını göstermektedir.

bilgi

Bu senaryonun tipik bir kullanım durumu, kullanıcılar adına t okent hesaplarını yöneten bir programdır.

Örneğin, bir DeFi protokolünün kullanıcı fonlarını tek bir hesaba havuzladığı bir senaryoyu düşünün. Protokolün, otomatik olarak çekim taleplerini yönetmek için güvenlik kontrolleri eklemesi gerekmektedir. Böyle durumlarda, bu havuzlanmış fonlar üzerindeki kontrol tek bir kullanıcının elinde değil, programın kendisinin elindedir. Bu, protokolün token hesaplarının sahibi olarak PDA'ların kullanılmasını gerektirir ve böylece çekimler için programatik olarak imza atılabilir.


Aşağıda, Solana programlarını okurken veya yazarken karşılaşabileceğiniz işlevsel olarak eşdeğer iki farklı uygulama bulunmaktadır. İşte, Solana Playground üzerinde son referans programı.

Başlangıç Kodu

İşte Solana Playground üzerinde bir başlangıç programı. lib.rs dosyasında tek bir sol_transfer talimatını içeren aşağıdaki program bulunmaktadır.

use anchor_lang::prelude::*;
use anchor_lang::system_program::{transfer, Transfer};

declare_id!("3455LkCS85a4aYmSeNbRrJsduNQfYRY82A7eCD3yQfyR");

#[program]
pub mod cpi {
use super::*;

pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
let from_pubkey = ctx.accounts.pda_account.to_account_info();
let to_pubkey = ctx.accounts.recipient.to_account_info();
let program_id = ctx.accounts.system_program.to_account_info();

let seed = to_pubkey.key();
let bump_seed = ctx.bumps.pda_account;
let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];

let cpi_context = CpiContext::new(
program_id,
Transfer {
from: from_pubkey,
to: to_pubkey,
},
)
.with_signer(signer_seeds);

transfer(cpi_context, amount)?;
Ok(())
}
}

#[derive(Accounts)]
pub struct SolTransfer<'info> {
#[account(
mut,
seeds = [b"pda", recipient.key().as_ref()],
bump,
)]
pda_account: SystemAccount<'info>,
#[account(mut)]
recipient: SystemAccount<'info>,
system_program: Program<'info, System>,
}
not

cpi.test.ts dosyası, özel sol_transfer talimatının nasıl çağrılacağını gösterir ve SolanaFM'de işlem detaylarına bir bağlantı kaydeder.

Programda belirtilen tohumları kullanarak PDA'nın nasıl türetileceğini göstermektedir:

const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("pda"), wallet.publicKey.toBuffer()],
program.programId,
);

Bu örnekteki ilk adım, PDA hesabını Playground cüzdanından temel bir SOL transferi ile finanse etmektir.

it("PDA'yı SOL ile finanse et", async () => {
const transferInstruction = SystemProgram.transfer({
fromPubkey: wallet.publicKey,
toPubkey: PDA,
lamports: transferAmount,
});

const transaction = new Transaction().add(transferInstruction);

const transactionSignature = await sendAndConfirmTransaction(
connection,
transaction,
[wallet.payer], // imzalayıcı
);

console.log(
`\nİşlem İmzası:` +
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
);
});

PDA SOL ile finanse edildikten sonra, sol_transfer talimatı çağrılır. Bu talimat, SOL'u PDA'dan wallet hesabına geri transfer eder ve bu işlem System Program'a bir CPI ile “imzalanır”.

it("PDA imzacı ile SOL Transferi", async () => {
const transactionSignature = await program.methods
.solTransfer(new BN(transferAmount))
.accounts({
pdaAccount: PDA,
recipient: wallet.publicKey,
})
.rpc();

console.log(
`\nİşlem İmzası: https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
);
});

İşlem detayları, öncelikle özel programın çağrıldığını (görev 1), ardından System Program'ı (görev 1.1) çağırdığını gösterir ve bu da başarılı bir SOL transferi ile sonuçlanır.

İşlem Detayları

İşlem detaylarını görüntülemek için testi derleyebilir, dağıtabilir ve çalıştırabilirsiniz. SolanaFM explorer üzerinde.


Anchor Kullanarak İmzacılarla CPI Nasıl Yapılır

Başlangıç kodunda, SolTransfer yapısı transfer talimatı için gereken hesapları belirler.

#[derive(Accounts)]
pub struct SolTransfer<'info> {
#[account(
mut,
seeds = [b"pda", recipient.key().as_ref()],
bump,
)]
pda_account: SystemAccount<'info>,
#[account(mut)]
recipient: SystemAccount<'info>,
system_program: Program<'info, System>,
}

pda_account için adres türetmek üzere kullanılan seeds, sabit kodlanmış "pda" dizesini ve recipient hesabının adresini içerir. Bu, pda_account için adresin her recipient için benzersiz olduğu anlamına gelir.

PDA'yı türetmek için Javascript karşılığı test dosyasında bulunmaktadır.

const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("pda"), wallet.publicKey.toBuffer()],
program.programId,
);

Anchor CpiContext

Başlangıç kodunda yer alan sol_transfer talimatı, Anchor framework'ü kullanarak CPI'ların nasıl oluşturulacağına dair tipik bir yaklaşımı göstermektedir.

Bu yaklaşım, çağrılan talimatlar için gereken program_id ve hesapları içeren bir CpiContext oluşturmayı, ardından belirli bir talimatı invoke etmek için bir yardımcı fonksiyonu (transfer) içermektedir.

pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
let from_pubkey = ctx.accounts.pda_account.to_account_info();
let to_pubkey = ctx.accounts.recipient.to_account_info();
let program_id = ctx.accounts.system_program.to_account_info();

let seed = to_pubkey.key();
let bump_seed = ctx.bumps.pda_account;
let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];

let cpi_context = CpiContext::new(
program_id,
Transfer {
from: from_pubkey,
to: to_pubkey,
},
)
.with_signer(signer_seeds);

transfer(cpi_context, amount)?;
Ok(())
}
ipucu

PDAlar ile imzalama yapıldığında, isteğe bağlı tohumlar ve bump tohumları cpi_context içinde signer_seeds olarak with_signer() kullanılarak dahil edilir.

let seed = to_pubkey.key();
let bump_seed = ctx.bumps.pda_account;
let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];

let cpi_context = CpiContext::new(
program_id,
Transfer {
from: from_pubkey,
to: to_pubkey,
},
)
.with_signer(signer_seeds);

cpi_context ve amount, CPI'yi gerçekleştirmek için transfer fonksiyonuna geçilir.

transfer(cpi_context, amount)?;

CPI işlenirken, Solana çalışma zamanı sağlanan tohumların ve çağrıları program ID'sinin geçerli bir PDA türettiğini doğrular. PDA, çağrıda bir imzacı olarak eklenir. Bu mekanizma, programların kendi program ID'sinden türetilen PDAlar için programatik olarak imza atmasına olanak tanır.

Crate Yardımcı ile Çağırma

Altta yatan kod, yukarıdaki örnek invoke_signed() fonksiyonunun bir sargısıdır ve system_instruction::transfer kullanarak talimatı oluşturur.

Aşağıdaki örnek, invoke_signed() fonksiyonunu kullanarak System Program'ın transfer talimatına bir CPI yapmak için system_instruction::transfer yöntemini kullanır ve PDA tarafından imzalanır.

Öncelikle, lib.rs dosyasının üst kısmına bu ithalatları ekleyin:

use anchor_lang::solana_program::{program::invoke_signed, system_instruction};

Sonra, sol_transfer talimatını aşağıdaki gibi değiştirin:

pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
let from_pubkey = ctx.accounts.pda_account.to_account_info();
let to_pubkey = ctx.accounts.recipient.to_account_info();
let program_id = ctx.accounts.system_program.to_account_info();

let seed = to_pubkey.key();
let bump_seed = ctx.bumps.pda_account;
let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];

let instruction =
&system_instruction::transfer(&from_pubkey.key(), &to_pubkey.key(), amount);

invoke_signed(instruction, &[from_pubkey, to_pubkey, program_id], signer_seeds)?;
Ok(())
}

Bu uygulama, önceki örnekle işlevsel olarak eşdeğerdir. signer_seeds, invoke_signed fonksiyonuna geçirilir.