Ana içeriğe geç

Solana Programında CPI Nasıl Yapılır

Bu kılavuz, SOL transferini Cross Program Invocation (CPI) kullanarak göstermek için Anchor framework'ünü kullanmaktadır. Aşağıda, Solana programları okurken veya yazarken karşılaşabileceğiniz üç farklı, ancak işlevsel olarak eşdeğer uygulama bulunmaktadır. İşte Solana Playground üzerinde son bir referans programı.

Başlangıç Kodu

Burada, Solana Playground üzerinde bir başlangıç programı yer almaktadır. lib.rs dosyası, tek bir sol_transfer talimatını içeren aşağıdaki programı içermektedir.

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

declare_id!("9AvUNHjxscdkiKQ8tUn12QCMXtcnbR9BVGq3ULNzFMRi");

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

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

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

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

#[derive(Accounts)]
pub struct SolTransfer<'info> {
#[account(mut)]
sender: Signer<'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'deki işlem detaylarına bir bağlantı kaydeder.

it("SOL Transfer Anchor", async () => {
const transactionSignature = await program.methods
.solTransfer(new BN(transferAmount))
.accounts({
sender: sender.publicKey,
recipient: recipient.publicKey,
})
.rpc();

console.log(
`\nTransaction Signature:` +
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
);
});

İşlem detaylarında, özel programın öncelikle çağrıldığını (talimat 1), ardından Sistem Programı'nın (talimat 1.1) çağrıldığını gösterir ve sonuçta başarılı bir SOL transferi gerçekleştirilir.

İşlem Detayları

Bu örneği Playground üzerinde derleyip, dağıtarak test edebilir ve SolanaFM keşif aracı üzerinden işlem detaylarını görüntüleyebilirsiniz.

Anchor ile CPI Nasıl Yapılır

Başlangıç kodunda, SolTransfer yapısı, transfer talimatı için gerekli olan hesapları belirtmektedir.

#[derive(Accounts)]
pub struct SolTransfer<'info> {
#[account(mut)]
sender: Signer<'info>,
#[account(mut)]
recipient: SystemAccount<'info>,
system_program: Program<'info, System>,
}

Anchor CpiContext

Başlangıç kodunda yer alan sol_transfer talimatı, CPI oluşturmanın tipik bir yaklaşımını göstermektedir ve Anchor framework'ü kullanılmaktadır.

bilgi

Bu yaklaşım, çağrılan talimata gereken program_id ve hesapları içeren bir CpiContext oluşturmayı ve ardından belirli bir talimatı çağırmak için (transfer) bir yardımcı fonksiyon kullanmayı içerir.

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

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

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

cpi_context değişkeni, transfer talimatı için gereken program ID'sini (Sistem Programı) ve hesapları (gönderen ve alıcı) belirtir.

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

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

transfer(cpi_context, amount)?;

Yardımcı Kütüphane ile Çağırma

Arka planda, yukarıdaki CpiContext örneği, solana_program kütüphanesinin invoke fonksiyonu etrafında bir sarmalayıcıdır ve system_instruction::transfer kullanarak talimatı oluşturur.

Aşağıdaki örnek, system_instruction::transfer yöntemini kullanarak Sistem Programı'nın transfer talimatına CPI yapmak için invoke() fonksiyonunu nasıl kullanacağınızı gösterir.

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

use anchor_lang::solana_program::{program::invoke, 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.sender.to_account_info();
let to_pubkey = ctx.accounts.recipient.to_account_info();
let program_id = ctx.accounts.system_program.to_account_info();

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

invoke(instruction, &[from_pubkey, to_pubkey, program_id])?;
Ok(())
}
ipucu

Bu uygulama, önceki örnekle işlevsel olarak eşdeğerdir.

Talimat ile Çağırma

Ayrıca, invoke() fonksiyonuna geçmek için talimatı elle oluşturabilirsiniz. Bu, çağırmak istediğiniz talimatı oluşturmak için bir kütüphane mevcut olmadıkça faydalıdır.

Bu yaklaşım, talimatın gerektirdiği AccountMeta'ları manuel olarak belirtmenizi ve talimat verileri tamponunu doğru bir şekilde oluşturmanızı gerektirir.

Aşağıdaki sol_transfer talimatı, önceki iki örneğin tamamen genişletilmiş eşdeğeridir.

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

// Talimat AccountMeta'ları hazırlama
let account_metas = vec![
AccountMeta::new(from_pubkey.key(), true),
AccountMeta::new(to_pubkey.key(), false),
];

// SOL transfer talimatı ayırtıcısı
let instruction_discriminator: u32 = 2;

// Talimat verilerini hazırlama
let mut instruction_data = Vec::with_capacity(4 + 8);
instruction_data.extend_from_slice(&instruction_discriminator.to_le_bytes());
instruction_data.extend_from_slice(&amount.to_le_bytes());

// Talimat oluşturma
let instruction = Instruction {
program_id: program_id.key(),
accounts: account_metas,
data: instruction_data,
};

// Talimatı çağırma
invoke(&instruction, &[from_pubkey, to_pubkey, program_id])?;
Ok(())
}

Yukarıdaki sol_transfer talimatı, bu örneği SOL transfer talimatını elle oluşturma konusunda yinelemektedir. Talimat oluşturma sürecinde aşağıdaki biçimi kullanmalısınız:

AccountMeta::new(account1_pubkey, true),           // yazılabilir, imzacı
AccountMeta::new(account2_pubkey, false), // yazılabilir, imzacı değil
AccountMeta::new_readonly(account3_pubkey, false), // yazılamaz, imzacı değil
AccountMeta::new_readonly(account4_pubkey, true), // yazılabilir, imzacı