Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/basic_solana/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ solana-transaction = { workspace = true, features = ["bincode"] }
[dev-dependencies]
candid = { workspace = true }
candid_parser = { workspace = true }
ic-management-canister-types = {workspace = true}
ic-management-canister-types = { workspace = true }
ic-test-utilities-load-wasm = { workspace = true }
pocket-ic = { workspace = true }
solana-client = { workspace = true }
Expand Down
45 changes: 38 additions & 7 deletions examples/basic_solana/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ pub async fn nonce_account(owner: Option<Principal>) -> sol_rpc_types::Pubkey {
#[update]
pub async fn associated_token_account(owner: Option<Principal>, mint_account: String) -> String {
let owner = owner.unwrap_or_else(validate_caller_not_anonymous);
let mint = Pubkey::from_str(&mint_account).unwrap();
let wallet = SolanaWallet::new(owner).await;
spl::get_associated_token_address(wallet.solana_account().as_ref(), &mint).to_string()
let mint = Pubkey::from_str(&mint_account).unwrap();
spl::get_associated_token_address(
wallet.solana_account().as_ref(),
&mint,
&get_account_owner(&mint).await,
)
.to_string()
}

#[update]
Expand Down Expand Up @@ -188,11 +193,16 @@ pub async fn create_associated_token_account(
let payer = wallet.solana_account();
let mint = Pubkey::from_str(&mint_account).unwrap();

let (associated_token_account, instruction) =
spl::create_associated_token_account_instruction(payer.as_ref(), payer.as_ref(), &mint);
let (associated_token_account, instruction) = spl::create_associated_token_account_instruction(
payer.as_ref(),
payer.as_ref(),
&mint,
&get_account_owner(&mint).await,
);

if let Some(_account) = client
.get_account_info(associated_token_account)
.with_encoding(GetAccountInfoEncoding::Base64)
.send()
.await
.expect_consistent()
Expand Down Expand Up @@ -323,10 +333,18 @@ pub async fn send_spl_token(
let mint = Pubkey::from_str(&mint_account).unwrap();
let amount = amount.0.to_u64().unwrap();

let from = spl::get_associated_token_address(payer.as_ref(), &mint);
let to = spl::get_associated_token_address(&recipient, &mint);
let token_program = get_account_owner(&mint).await;

let instruction = spl::transfer_instruction(&from, &to, payer.as_ref(), amount);
let from = spl::get_associated_token_address(payer.as_ref(), &mint, &token_program);
let to = spl::get_associated_token_address(&recipient, &mint, &token_program);

let instruction = spl::transfer_instruction_with_program_id(
&from,
&to,
payer.as_ref(),
amount,
&token_program,
);

let message = Message::new_with_blockhash(
&[instruction],
Expand All @@ -348,6 +366,19 @@ pub async fn send_spl_token(
.to_string()
}

async fn get_account_owner(account: &Pubkey) -> Pubkey {
let owner = client()
.get_account_info(*account)
.with_encoding(GetAccountInfoEncoding::Base64)
.send()
.await
.expect_consistent()
.expect("Call to `getAccountInfo` failed")
.unwrap_or_else(|| panic!("Account not found for pubkey `{account}`"))
.owner;
Pubkey::from_str(&owner).unwrap()
}

fn main() {}

#[test]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
use solana_instruction::{AccountMeta, Instruction};
use solana_pubkey::Pubkey;

#[cfg(test)]
mod tests;

mod associated_token_account_program {
solana_pubkey::declare_id!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
}
mod system_program {
solana_pubkey::declare_id!("11111111111111111111111111111111");
}
mod token_2022_program {
solana_pubkey::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
pub mod token_program {
solana_pubkey::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
}
mod associated_token_account_program {
solana_pubkey::declare_id!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
pub mod token_2022_program {
solana_pubkey::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
}

/// Derives the Associated Token Account address for the given mint address.
/// This implementation was taken from [the associated token account repository](https://github.com/solana-program/associated-token-account/blob/main/interface/src/address.rs).
pub fn get_associated_token_address(
wallet_address: &Pubkey,
token_mint_address: &Pubkey,
token_program_id: &Pubkey,
) -> Pubkey {
let (program_derived_address, _bump) = Pubkey::find_program_address(
&[
&wallet_address.to_bytes(),
&token_2022_program::id().to_bytes(),
&token_program_id.to_bytes(),
&token_mint_address.to_bytes(),
],
&associated_token_account_program::id(),
Expand All @@ -34,9 +41,10 @@ pub fn create_associated_token_account_instruction(
funding_address: &Pubkey,
wallet_address: &Pubkey,
token_mint_address: &Pubkey,
token_program_id: &Pubkey,
) -> (Pubkey, Instruction) {
let associated_account_address =
get_associated_token_address(wallet_address, token_mint_address);
get_associated_token_address(wallet_address, token_mint_address, token_program_id);
let instruction = Instruction {
program_id: associated_token_account_program::id(),
accounts: vec![
Expand All @@ -45,7 +53,7 @@ pub fn create_associated_token_account_instruction(
AccountMeta::new_readonly(*wallet_address, false),
AccountMeta::new_readonly(*token_mint_address, false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(token_2022_program::id(), false),
AccountMeta::new_readonly(*token_program_id, false),
],
data: vec![
0, // SPL Associated Token Account program "create" instruction
Expand All @@ -56,14 +64,15 @@ pub fn create_associated_token_account_instruction(

/// Creates an instruction to run the [`Transfer` instruction](https://github.com/solana-program/token/blob/main/interface/src/instruction.rs)
/// in the SPL Token program.
pub fn transfer_instruction(
pub fn transfer_instruction_with_program_id(
source_address: &Pubkey,
destination_address: &Pubkey,
authority_address: &Pubkey,
amount: u64,
token_program_id: &Pubkey,
) -> Instruction {
Instruction {
program_id: token_2022_program::id(),
program_id: *token_program_id,
accounts: vec![
AccountMeta::new(*source_address, false),
AccountMeta::new(*destination_address, false),
Expand Down
42 changes: 42 additions & 0 deletions examples/basic_solana/src/spl/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::spl::{get_associated_token_address, token_2022_program, token_program};
use solana_pubkey::{pubkey, Pubkey};
use std::str::FromStr;

// USDC token which uses the legacy Token Program: https://solscan.io/token/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
const USDC_MINT_ADDRESS: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
// BonkEarn token which uses the Token 2022 Program: https://solscan.io/token/CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo
const BERN_MINT_ADDRESS: &str = "CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo";

const WALLET_ADDRESS: &str = "AAAGuCgkmxYDTiBvzx1QT5XEjqXPRtQaiEXQo4gatD2o";

#[test]
fn should_compute_ata_with_legacy_token_program() {
let associated_token_address = get_associated_token_address(
&Pubkey::from_str(WALLET_ADDRESS).unwrap(),
&Pubkey::from_str(USDC_MINT_ADDRESS).unwrap(),
&token_program::id(),
);

// The associated token address was obtained with the following command:
// spl-token address --owner $WALLET_ADDRESS --token $MINT_ADDRESS --verbose --url mainnet-beta
assert_eq!(
associated_token_address,
pubkey!("Cra8woRQhnHsGAmFWcCN1m7A9J44ykNfGpehi6dMBuKR")
)
}

#[test]
fn should_compute_ata_with_token_2022_program() {
let associated_token_address = get_associated_token_address(
&Pubkey::from_str(WALLET_ADDRESS).unwrap(),
&Pubkey::from_str(BERN_MINT_ADDRESS).unwrap(),
&token_2022_program::id(),
);

// The associated token address was obtained with the following command:
// spl-token address --owner $WALLET_ADDRESS --token $MINT_ADDRESS --verbose --url mainnet-beta
assert_eq!(
associated_token_address,
pubkey!("GPtCoaz35vdCrFbyhxcRrkYvECrUkrBX6CoRZEv8EQDw")
)
}
18 changes: 6 additions & 12 deletions libs/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ pub mod fixtures;
mod request;

use crate::request::{
GetAccountInfoRequest, GetBalanceRequest, GetBlockRequest, GetRecentPrioritizationFeesRequest,
GetRecentPrioritizationFeesRequestBuilder, GetSignatureStatusesRequest,
GetSignatureStatusesRequestBuilder, GetSignaturesForAddressRequest,
GetSignaturesForAddressRequestBuilder, GetSlotRequest, GetTokenAccountBalanceRequest,
GetTransactionRequest, JsonRequest, SendTransactionRequest,
GetAccountInfoRequest, GetAccountInfoRequestBuilder, GetBalanceRequest, GetBlockRequest,
GetRecentPrioritizationFeesRequest, GetRecentPrioritizationFeesRequestBuilder,
GetSignatureStatusesRequest, GetSignatureStatusesRequestBuilder,
GetSignaturesForAddressRequest, GetSignaturesForAddressRequestBuilder, GetSlotRequest,
GetTokenAccountBalanceRequest, GetTransactionRequest, JsonRequest, SendTransactionRequest,
};
use async_trait::async_trait;
use candid::{utils::ArgumentEncoder, CandidType, Principal};
Expand Down Expand Up @@ -328,13 +328,7 @@ impl<R> SolRpcClient<R> {
pub fn get_account_info(
&self,
params: impl Into<GetAccountInfoParams>,
) -> RequestBuilder<
R,
RpcConfig,
GetAccountInfoParams,
sol_rpc_types::MultiRpcResult<Option<sol_rpc_types::AccountInfo>>,
sol_rpc_types::MultiRpcResult<Option<solana_account_decoder_client_types::UiAccount>>,
> {
) -> GetAccountInfoRequestBuilder<R> {
RequestBuilder::new(
self.clone(),
GetAccountInfoRequest::new(params.into()),
Expand Down
20 changes: 18 additions & 2 deletions libs/client/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use derive_more::From;
use serde::de::DeserializeOwned;
use sol_rpc_types::{
AccountInfo, CommitmentLevel, ConfirmedBlock, ConfirmedTransactionStatusWithSignature,
GetAccountInfoParams, GetBalanceParams, GetBlockCommitmentLevel, GetBlockParams,
GetRecentPrioritizationFeesParams, GetRecentPrioritizationFeesRpcConfig,
GetAccountInfoEncoding, GetAccountInfoParams, GetBalanceParams, GetBlockCommitmentLevel,
GetBlockParams, GetRecentPrioritizationFeesParams, GetRecentPrioritizationFeesRpcConfig,
GetSignatureStatusesParams, GetSignaturesForAddressLimit, GetSignaturesForAddressParams,
GetSlotParams, GetSlotRpcConfig, GetTokenAccountBalanceParams, GetTransactionParams, Lamport,
NonZeroU8, PrioritizationFee, RoundingError, RpcConfig, RpcResult, RpcSources,
Expand Down Expand Up @@ -127,6 +127,22 @@ impl SolRpcRequest for GetAccountInfoRequest {
}
}

pub type GetAccountInfoRequestBuilder<R> = RequestBuilder<
R,
RpcConfig,
GetAccountInfoParams,
sol_rpc_types::MultiRpcResult<Option<AccountInfo>>,
sol_rpc_types::MultiRpcResult<Option<solana_account_decoder_client_types::UiAccount>>,
>;

impl<R> GetAccountInfoRequestBuilder<R> {
/// Change the `encoding` parameter for a `getAccountInfo` request.
pub fn with_encoding(mut self, encoding: GetAccountInfoEncoding) -> Self {
self.request.params.encoding = Some(encoding);
self
}
}

#[derive(Debug, Clone)]
pub struct GetBalanceRequest(GetBalanceParams);

Expand Down