Token Accounts: return ui_amount, decimals with decoded account (#11407)
* Return ui_amount, decimals from token client methods * Return ui_amount, decimals in RPC jsonParsed token accounts * Fixup docs * Return ui_amount, decimals in pubsub jsonParsed token accounts * Remove unnecessary duplicate struct * StringAmount rename
This commit is contained in:
parent
67fdf593a2
commit
b7c2681903
|
@ -8,10 +8,12 @@ pub mod parse_nonce;
|
|||
pub mod parse_token;
|
||||
pub mod parse_vote;
|
||||
|
||||
use crate::parse_account_data::{parse_account_data, ParsedAccount};
|
||||
use crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount};
|
||||
use solana_sdk::{account::Account, clock::Epoch, pubkey::Pubkey};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub type StringAmount = String;
|
||||
|
||||
/// A duplicate representation of an Account for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -44,11 +46,17 @@ pub enum UiAccountEncoding {
|
|||
}
|
||||
|
||||
impl UiAccount {
|
||||
pub fn encode(account: Account, encoding: UiAccountEncoding) -> Self {
|
||||
pub fn encode(
|
||||
account: Account,
|
||||
encoding: UiAccountEncoding,
|
||||
additional_data: Option<AccountAdditionalData>,
|
||||
) -> Self {
|
||||
let data = match encoding {
|
||||
UiAccountEncoding::Binary => account.data.into(),
|
||||
UiAccountEncoding::JsonParsed => {
|
||||
if let Ok(parsed_data) = parse_account_data(&account.owner, &account.data) {
|
||||
if let Ok(parsed_data) =
|
||||
parse_account_data(&account.owner, &account.data, additional_data)
|
||||
{
|
||||
UiAccountData::Json(parsed_data)
|
||||
} else {
|
||||
account.data.into()
|
||||
|
|
|
@ -30,6 +30,9 @@ pub enum ParseAccountError {
|
|||
#[error("Program not parsable")]
|
||||
ProgramNotParsable,
|
||||
|
||||
#[error("Additional data required to parse: {0}")]
|
||||
AdditionalDataMissing(String),
|
||||
|
||||
#[error("Instruction error")]
|
||||
InstructionError(#[from] InstructionError),
|
||||
|
||||
|
@ -52,16 +55,25 @@ pub enum ParsableAccount {
|
|||
Vote,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AccountAdditionalData {
|
||||
pub spl_token_decimals: Option<u8>,
|
||||
}
|
||||
|
||||
pub fn parse_account_data(
|
||||
program_id: &Pubkey,
|
||||
data: &[u8],
|
||||
additional_data: Option<AccountAdditionalData>,
|
||||
) -> Result<ParsedAccount, ParseAccountError> {
|
||||
let program_name = PARSABLE_PROGRAM_IDS
|
||||
.get(program_id)
|
||||
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
|
||||
let additional_data = additional_data.unwrap_or_default();
|
||||
let parsed_json = match program_name {
|
||||
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
|
||||
ParsableAccount::SplToken => serde_json::to_value(parse_token(data)?)?,
|
||||
ParsableAccount::SplToken => {
|
||||
serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)?
|
||||
}
|
||||
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
|
||||
};
|
||||
Ok(ParsedAccount {
|
||||
|
@ -83,18 +95,19 @@ mod test {
|
|||
fn test_parse_account_data() {
|
||||
let other_program = Pubkey::new_rand();
|
||||
let data = vec![0; 4];
|
||||
assert!(parse_account_data(&other_program, &data).is_err());
|
||||
assert!(parse_account_data(&other_program, &data, None).is_err());
|
||||
|
||||
let vote_state = VoteState::default();
|
||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state));
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let parsed = parse_account_data(&solana_vote_program::id(), &vote_account_data).unwrap();
|
||||
let parsed =
|
||||
parse_account_data(&solana_vote_program::id(), &vote_account_data, None).unwrap();
|
||||
assert_eq!(parsed.program, "vote".to_string());
|
||||
|
||||
let nonce_data = Versions::new_current(State::Initialized(Data::default()));
|
||||
let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
|
||||
let parsed = parse_account_data(&system_program::id(), &nonce_account_data).unwrap();
|
||||
let parsed = parse_account_data(&system_program::id(), &nonce_account_data, None).unwrap();
|
||||
assert_eq!(parsed.program, "nonce".to_string());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::parse_account_data::{ParsableAccount, ParseAccountError};
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_token_v1_0::{
|
||||
option::COption,
|
||||
|
@ -19,15 +22,24 @@ pub fn spl_token_v1_0_native_mint() -> Pubkey {
|
|||
Pubkey::from_str(&spl_token_v1_0::native_mint::id().to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
||||
pub fn parse_token(
|
||||
data: &[u8],
|
||||
mint_decimals: Option<u8>,
|
||||
) -> Result<TokenAccountType, ParseAccountError> {
|
||||
let mut data = data.to_vec();
|
||||
if data.len() == size_of::<Account>() {
|
||||
let account: Account = *unpack(&mut data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
let decimals = mint_decimals.ok_or_else(|| {
|
||||
ParseAccountError::AdditionalDataMissing(
|
||||
"no mint_decimals provided to parse spl-token account".to_string(),
|
||||
)
|
||||
})?;
|
||||
let ui_token_amount = token_amount_to_ui_amount(account.amount, decimals);
|
||||
Ok(TokenAccountType::Account(UiTokenAccount {
|
||||
mint: account.mint.to_string(),
|
||||
owner: account.owner.to_string(),
|
||||
amount: account.amount,
|
||||
token_amount: ui_token_amount,
|
||||
delegate: match account.delegate {
|
||||
COption::Some(pubkey) => Some(pubkey.to_string()),
|
||||
COption::None => None,
|
||||
|
@ -86,13 +98,31 @@ pub enum TokenAccountType {
|
|||
pub struct UiTokenAccount {
|
||||
pub mint: String,
|
||||
pub owner: String,
|
||||
pub amount: u64,
|
||||
pub token_amount: UiTokenAmount,
|
||||
pub delegate: Option<String>,
|
||||
pub is_initialized: bool,
|
||||
pub is_native: bool,
|
||||
pub delegated_amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiTokenAmount {
|
||||
pub ui_amount: f64,
|
||||
pub decimals: u8,
|
||||
pub amount: StringAmount,
|
||||
}
|
||||
|
||||
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
|
||||
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
|
||||
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
|
||||
UiTokenAmount {
|
||||
ui_amount: amount_decimals,
|
||||
decimals,
|
||||
amount: amount.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiMint {
|
||||
|
@ -110,6 +140,14 @@ pub struct UiMultisig {
|
|||
pub signers: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
|
||||
if data.len() == size_of::<Account>() {
|
||||
Some(Pubkey::new(&data[0..32]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -125,12 +163,17 @@ mod test {
|
|||
account.owner = owner_pubkey;
|
||||
account.amount = 42;
|
||||
account.is_initialized = true;
|
||||
assert!(parse_token(&account_data, None).is_err());
|
||||
assert_eq!(
|
||||
parse_token(&account_data).unwrap(),
|
||||
parse_token(&account_data, Some(2)).unwrap(),
|
||||
TokenAccountType::Account(UiTokenAccount {
|
||||
mint: mint_pubkey.to_string(),
|
||||
owner: owner_pubkey.to_string(),
|
||||
amount: 42,
|
||||
token_amount: UiTokenAmount {
|
||||
ui_amount: 0.42,
|
||||
decimals: 2,
|
||||
amount: "42".to_string()
|
||||
},
|
||||
delegate: None,
|
||||
is_initialized: true,
|
||||
is_native: false,
|
||||
|
@ -144,7 +187,7 @@ mod test {
|
|||
mint.decimals = 3;
|
||||
mint.is_initialized = true;
|
||||
assert_eq!(
|
||||
parse_token(&mint_data).unwrap(),
|
||||
parse_token(&mint_data, None).unwrap(),
|
||||
TokenAccountType::Mint(UiMint {
|
||||
owner: Some(owner_pubkey.to_string()),
|
||||
decimals: 3,
|
||||
|
@ -166,7 +209,7 @@ mod test {
|
|||
multisig.is_initialized = true;
|
||||
multisig.signers = signers;
|
||||
assert_eq!(
|
||||
parse_token(&multisig_data).unwrap(),
|
||||
parse_token(&multisig_data, None).unwrap(),
|
||||
TokenAccountType::Multisig(UiMultisig {
|
||||
num_required_signers: 2,
|
||||
num_valid_signers: 3,
|
||||
|
@ -180,6 +223,20 @@ mod test {
|
|||
);
|
||||
|
||||
let bad_data = vec![0; 4];
|
||||
assert!(parse_token(&bad_data).is_err());
|
||||
assert!(parse_token(&bad_data, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_token_account_mint() {
|
||||
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
|
||||
let mut account_data = [0; size_of::<Account>()];
|
||||
let mut account: &mut Account = unpack_unchecked(&mut account_data).unwrap();
|
||||
account.mint = mint_pubkey;
|
||||
|
||||
let expected_mint_pubkey = Pubkey::new(&[2; 32]);
|
||||
assert_eq!(
|
||||
get_token_account_mint(&account_data),
|
||||
Some(expected_mint_pubkey)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1235,7 +1235,7 @@ fn process_show_account(
|
|||
let cli_account = CliAccount {
|
||||
keyed_account: RpcKeyedAccount {
|
||||
pubkey: account_pubkey.to_string(),
|
||||
account: UiAccount::encode(account, UiAccountEncoding::Binary),
|
||||
account: UiAccount::encode(account, UiAccountEncoding::Binary, None),
|
||||
},
|
||||
use_lamports_unit,
|
||||
};
|
||||
|
|
|
@ -350,7 +350,7 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
let nonce_pubkey = Pubkey::new(&[4u8; 32]);
|
||||
let rpc_nonce_account = UiAccount::encode(nonce_account, UiAccountEncoding::Binary);
|
||||
let rpc_nonce_account = UiAccount::encode(nonce_account, UiAccountEncoding::Binary, None);
|
||||
let get_account_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(Some(rpc_nonce_account)),
|
||||
|
|
|
@ -15,7 +15,10 @@ use indicatif::{ProgressBar, ProgressStyle};
|
|||
use log::*;
|
||||
use serde_json::{json, Value};
|
||||
use solana_account_decoder::{
|
||||
parse_token::{parse_token, TokenAccountType, UiMint, UiMultisig, UiTokenAccount},
|
||||
parse_token::{
|
||||
get_token_account_mint, parse_token, TokenAccountType, UiMint, UiMultisig, UiTokenAccount,
|
||||
UiTokenAmount,
|
||||
},
|
||||
UiAccount,
|
||||
};
|
||||
use solana_sdk::{
|
||||
|
@ -38,6 +41,7 @@ use solana_transaction_status::{
|
|||
};
|
||||
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
|
@ -698,14 +702,30 @@ impl RpcClient {
|
|||
value: account,
|
||||
} = self.get_account_with_commitment(pubkey, commitment_config)?;
|
||||
|
||||
if account.is_none() {
|
||||
return Err(RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into());
|
||||
}
|
||||
let account = account.unwrap();
|
||||
let mint = get_token_account_mint(&account.data)
|
||||
.and_then(|mint_pubkey| {
|
||||
self.get_token_mint_with_commitment(&mint_pubkey, commitment_config)
|
||||
.ok()
|
||||
.map(|response| response.value)
|
||||
.flatten()
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
Into::<ClientError>::into(RpcError::ForUser(format!(
|
||||
"AccountNotFound: mint for token acccount pubkey={}",
|
||||
pubkey
|
||||
)))
|
||||
})?;
|
||||
|
||||
Ok(Response {
|
||||
context,
|
||||
value: account
|
||||
.map(|account| match parse_token(&account.data) {
|
||||
value: match parse_token(&account.data, Some(mint.decimals)) {
|
||||
Ok(TokenAccountType::Account(ui_token_account)) => Some(ui_token_account),
|
||||
_ => None,
|
||||
})
|
||||
.flatten(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -728,7 +748,7 @@ impl RpcClient {
|
|||
Ok(Response {
|
||||
context,
|
||||
value: account
|
||||
.map(|account| match parse_token(&account.data) {
|
||||
.map(|account| match parse_token(&account.data, None) {
|
||||
Ok(TokenAccountType::Mint(ui_token_mint)) => Some(ui_token_mint),
|
||||
_ => None,
|
||||
})
|
||||
|
@ -755,7 +775,7 @@ impl RpcClient {
|
|||
Ok(Response {
|
||||
context,
|
||||
value: account
|
||||
.map(|account| match parse_token(&account.data) {
|
||||
.map(|account| match parse_token(&account.data, None) {
|
||||
Ok(TokenAccountType::Multisig(ui_token_multisig)) => Some(ui_token_multisig),
|
||||
_ => None,
|
||||
})
|
||||
|
@ -763,7 +783,7 @@ impl RpcClient {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult<RpcTokenAmount> {
|
||||
pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult<UiTokenAmount> {
|
||||
Ok(self
|
||||
.get_token_account_balance_with_commitment(pubkey, CommitmentConfig::default())?
|
||||
.value)
|
||||
|
@ -773,7 +793,7 @@ impl RpcClient {
|
|||
&self,
|
||||
pubkey: &Pubkey,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<RpcTokenAmount> {
|
||||
) -> RpcResult<UiTokenAmount> {
|
||||
self.send(
|
||||
RpcRequest::GetTokenAccountBalance,
|
||||
json!([pubkey.to_string(), commitment_config]),
|
||||
|
@ -817,10 +837,10 @@ impl RpcClient {
|
|||
commitment_config
|
||||
]),
|
||||
)?;
|
||||
let pubkey_accounts = accounts_to_token_accounts(parse_keyed_accounts(
|
||||
accounts,
|
||||
RpcRequest::GetTokenAccountsByDelegate,
|
||||
)?);
|
||||
let pubkey_accounts = self.accounts_to_token_accounts(
|
||||
commitment_config,
|
||||
parse_keyed_accounts(accounts, RpcRequest::GetTokenAccountsByDelegate)?,
|
||||
);
|
||||
Ok(Response {
|
||||
context,
|
||||
value: pubkey_accounts,
|
||||
|
@ -860,17 +880,17 @@ impl RpcClient {
|
|||
RpcRequest::GetTokenAccountsByOwner,
|
||||
json!([owner.to_string(), token_account_filter, commitment_config]),
|
||||
)?;
|
||||
let pubkey_accounts = accounts_to_token_accounts(parse_keyed_accounts(
|
||||
accounts,
|
||||
RpcRequest::GetTokenAccountsByDelegate,
|
||||
)?);
|
||||
let pubkey_accounts = self.accounts_to_token_accounts(
|
||||
commitment_config,
|
||||
parse_keyed_accounts(accounts, RpcRequest::GetTokenAccountsByDelegate)?,
|
||||
);
|
||||
Ok(Response {
|
||||
context,
|
||||
value: pubkey_accounts,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_token_supply(&self, mint: &Pubkey) -> ClientResult<RpcTokenAmount> {
|
||||
pub fn get_token_supply(&self, mint: &Pubkey) -> ClientResult<UiTokenAmount> {
|
||||
Ok(self
|
||||
.get_token_supply_with_commitment(mint, CommitmentConfig::default())?
|
||||
.value)
|
||||
|
@ -880,13 +900,42 @@ impl RpcClient {
|
|||
&self,
|
||||
mint: &Pubkey,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<RpcTokenAmount> {
|
||||
) -> RpcResult<UiTokenAmount> {
|
||||
self.send(
|
||||
RpcRequest::GetTokenSupply,
|
||||
json!([mint.to_string(), commitment_config]),
|
||||
)
|
||||
}
|
||||
|
||||
fn accounts_to_token_accounts(
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
pubkey_accounts: Vec<(Pubkey, Account)>,
|
||||
) -> Vec<(Pubkey, UiTokenAccount)> {
|
||||
let mut mint_decimals: HashMap<Pubkey, u8> = HashMap::new();
|
||||
pubkey_accounts
|
||||
.into_iter()
|
||||
.filter_map(|(pubkey, account)| {
|
||||
let mint_pubkey = get_token_account_mint(&account.data)?;
|
||||
let decimals = mint_decimals.get(&mint_pubkey).cloned().or_else(|| {
|
||||
let mint = self
|
||||
.get_token_mint_with_commitment(&mint_pubkey, commitment_config)
|
||||
.ok()
|
||||
.map(|response| response.value)
|
||||
.flatten()?;
|
||||
mint_decimals.insert(mint_pubkey, mint.decimals);
|
||||
Some(mint.decimals)
|
||||
})?;
|
||||
match parse_token(&account.data, Some(decimals)) {
|
||||
Ok(TokenAccountType::Account(ui_token_account)) => {
|
||||
Some((pubkey, ui_token_account))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn poll_balance_with_timeout_and_commitment(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
|
@ -1251,18 +1300,6 @@ fn parse_keyed_accounts(
|
|||
Ok(pubkey_accounts)
|
||||
}
|
||||
|
||||
fn accounts_to_token_accounts(
|
||||
pubkey_accounts: Vec<(Pubkey, Account)>,
|
||||
) -> Vec<(Pubkey, UiTokenAccount)> {
|
||||
pubkey_accounts
|
||||
.into_iter()
|
||||
.filter_map(|(pubkey, account)| match parse_token(&account.data) {
|
||||
Ok(TokenAccountType::Account(ui_token_account)) => Some((pubkey, ui_token_account)),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::client_error;
|
||||
use solana_account_decoder::UiAccount;
|
||||
use solana_account_decoder::{parse_token::UiTokenAmount, UiAccount};
|
||||
use solana_sdk::{
|
||||
clock::{Epoch, Slot},
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
|
@ -10,7 +10,6 @@ use solana_transaction_status::ConfirmedTransactionStatusWithSignature;
|
|||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
pub type RpcResult<T> = client_error::Result<Response<T>>;
|
||||
pub type RpcAmount = String;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RpcResponseContext {
|
||||
|
@ -222,20 +221,12 @@ pub struct RpcStakeActivation {
|
|||
pub inactive: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTokenAmount {
|
||||
pub ui_amount: f64,
|
||||
pub decimals: u8,
|
||||
pub amount: RpcAmount,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTokenAccountBalance {
|
||||
pub address: String,
|
||||
#[serde(flatten)]
|
||||
pub amount: RpcTokenAmount,
|
||||
pub amount: UiTokenAmount,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
|
118
core/src/rpc.rs
118
core/src/rpc.rs
|
@ -9,7 +9,11 @@ use bincode::{config::Options, serialize};
|
|||
use jsonrpc_core::{Error, Metadata, Result};
|
||||
use jsonrpc_derive::rpc;
|
||||
use solana_account_decoder::{
|
||||
parse_token::{spl_token_id_v1_0, spl_token_v1_0_native_mint},
|
||||
parse_account_data::AccountAdditionalData,
|
||||
parse_token::{
|
||||
get_token_account_mint, spl_token_id_v1_0, spl_token_v1_0_native_mint,
|
||||
token_amount_to_ui_amount, UiTokenAmount,
|
||||
},
|
||||
UiAccount, UiAccountEncoding,
|
||||
};
|
||||
use solana_client::{
|
||||
|
@ -242,8 +246,14 @@ impl JsonRpcRequestProcessor {
|
|||
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
|
||||
new_response(
|
||||
&bank,
|
||||
bank.get_account(pubkey)
|
||||
.map(|account| UiAccount::encode(account, encoding)),
|
||||
bank.get_account(pubkey).and_then(|account| {
|
||||
if account.owner == spl_token_id_v1_0() && encoding == UiAccountEncoding::JsonParsed
|
||||
{
|
||||
get_parsed_token_account(bank.clone(), account)
|
||||
} else {
|
||||
Some(UiAccount::encode(account, encoding, None))
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -265,13 +275,18 @@ impl JsonRpcRequestProcessor {
|
|||
let config = config.unwrap_or_default();
|
||||
let bank = self.bank(config.commitment);
|
||||
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
|
||||
get_filtered_program_accounts(&bank, program_id, filters)
|
||||
let keyed_accounts = get_filtered_program_accounts(&bank, program_id, filters);
|
||||
if program_id == &spl_token_id_v1_0() && encoding == UiAccountEncoding::JsonParsed {
|
||||
get_parsed_token_accounts(bank, keyed_accounts).collect()
|
||||
} else {
|
||||
keyed_accounts
|
||||
.map(|(pubkey, account)| RpcKeyedAccount {
|
||||
pubkey: pubkey.to_string(),
|
||||
account: UiAccount::encode(account, encoding.clone()),
|
||||
account: UiAccount::encode(account, encoding.clone(), None),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_inflation_governor(
|
||||
&self,
|
||||
|
@ -971,7 +986,7 @@ impl JsonRpcRequestProcessor {
|
|||
&self,
|
||||
pubkey: &Pubkey,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<RpcResponse<RpcTokenAmount>> {
|
||||
) -> Result<RpcResponse<UiTokenAmount>> {
|
||||
let bank = self.bank(commitment);
|
||||
let account = bank.get_account(pubkey).ok_or_else(|| {
|
||||
Error::invalid_params("Invalid param: could not find account".to_string())
|
||||
|
@ -998,7 +1013,7 @@ impl JsonRpcRequestProcessor {
|
|||
&self,
|
||||
mint: &Pubkey,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<RpcResponse<RpcTokenAmount>> {
|
||||
) -> Result<RpcResponse<UiTokenAmount>> {
|
||||
let bank = self.bank(commitment);
|
||||
let (mint_owner, decimals) = get_mint_owner_and_decimals(&bank, mint)?;
|
||||
if mint_owner != spl_token_id_v1_0() {
|
||||
|
@ -1106,12 +1121,17 @@ impl JsonRpcRequestProcessor {
|
|||
encoding: None,
|
||||
}));
|
||||
}
|
||||
let accounts = get_filtered_program_accounts(&bank, &token_program_id, filters)
|
||||
let keyed_accounts = get_filtered_program_accounts(&bank, &token_program_id, filters);
|
||||
let accounts = if encoding == UiAccountEncoding::JsonParsed {
|
||||
get_parsed_token_accounts(bank.clone(), keyed_accounts).collect()
|
||||
} else {
|
||||
keyed_accounts
|
||||
.map(|(pubkey, account)| RpcKeyedAccount {
|
||||
pubkey: pubkey.to_string(),
|
||||
account: UiAccount::encode(account, encoding.clone()),
|
||||
account: UiAccount::encode(account, encoding.clone(), None),
|
||||
})
|
||||
.collect();
|
||||
.collect()
|
||||
};
|
||||
Ok(new_response(&bank, accounts))
|
||||
}
|
||||
|
||||
|
@ -1152,12 +1172,17 @@ impl JsonRpcRequestProcessor {
|
|||
encoding: None,
|
||||
}));
|
||||
}
|
||||
let accounts = get_filtered_program_accounts(&bank, &token_program_id, filters)
|
||||
let keyed_accounts = get_filtered_program_accounts(&bank, &token_program_id, filters);
|
||||
let accounts = if encoding == UiAccountEncoding::JsonParsed {
|
||||
get_parsed_token_accounts(bank.clone(), keyed_accounts).collect()
|
||||
} else {
|
||||
keyed_accounts
|
||||
.map(|(pubkey, account)| RpcKeyedAccount {
|
||||
pubkey: pubkey.to_string(),
|
||||
account: UiAccount::encode(account, encoding.clone()),
|
||||
account: UiAccount::encode(account, encoding.clone(), None),
|
||||
})
|
||||
.collect();
|
||||
.collect()
|
||||
};
|
||||
Ok(new_response(&bank, accounts))
|
||||
}
|
||||
}
|
||||
|
@ -1211,6 +1236,47 @@ fn get_filtered_program_accounts(
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_parsed_token_account(bank: Arc<Bank>, account: Account) -> Option<UiAccount> {
|
||||
get_token_account_mint(&account.data)
|
||||
.and_then(|mint_pubkey| get_mint_owner_and_decimals(&bank, &mint_pubkey).ok())
|
||||
.map(|(_, decimals)| {
|
||||
UiAccount::encode(
|
||||
account,
|
||||
UiAccountEncoding::JsonParsed,
|
||||
Some(AccountAdditionalData {
|
||||
spl_token_decimals: Some(decimals),
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_parsed_token_accounts<I>(
|
||||
bank: Arc<Bank>,
|
||||
keyed_accounts: I,
|
||||
) -> impl Iterator<Item = RpcKeyedAccount>
|
||||
where
|
||||
I: Iterator<Item = (Pubkey, Account)>,
|
||||
{
|
||||
let mut mint_decimals: HashMap<Pubkey, u8> = HashMap::new();
|
||||
keyed_accounts.filter_map(move |(pubkey, account)| {
|
||||
get_token_account_mint(&account.data).map(|mint_pubkey| {
|
||||
let spl_token_decimals = mint_decimals.get(&mint_pubkey).cloned().or_else(|| {
|
||||
let (_, decimals) = get_mint_owner_and_decimals(&bank, &mint_pubkey).ok()?;
|
||||
mint_decimals.insert(mint_pubkey, decimals);
|
||||
Some(decimals)
|
||||
});
|
||||
RpcKeyedAccount {
|
||||
pubkey: pubkey.to_string(),
|
||||
account: UiAccount::encode(
|
||||
account,
|
||||
UiAccountEncoding::JsonParsed,
|
||||
Some(AccountAdditionalData { spl_token_decimals }),
|
||||
),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Analyze a passed Pubkey that may be a Token program id or Mint address to determine the program
|
||||
/// id and optional Mint
|
||||
fn get_token_program_id_and_mint(
|
||||
|
@ -1264,16 +1330,6 @@ fn get_mint_decimals(data: &[u8]) -> Result<u8> {
|
|||
.map(|mint: &mut Mint| mint.decimals)
|
||||
}
|
||||
|
||||
fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> RpcTokenAmount {
|
||||
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
|
||||
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
|
||||
RpcTokenAmount {
|
||||
ui_amount: amount_decimals,
|
||||
decimals,
|
||||
amount: amount.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[rpc]
|
||||
pub trait RpcSol {
|
||||
type Metadata;
|
||||
|
@ -1565,7 +1621,7 @@ pub trait RpcSol {
|
|||
meta: Self::Metadata,
|
||||
pubkey_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<RpcResponse<RpcTokenAmount>>;
|
||||
) -> Result<RpcResponse<UiTokenAmount>>;
|
||||
|
||||
#[rpc(meta, name = "getTokenSupply")]
|
||||
fn get_token_supply(
|
||||
|
@ -1573,7 +1629,7 @@ pub trait RpcSol {
|
|||
meta: Self::Metadata,
|
||||
mint_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<RpcResponse<RpcTokenAmount>>;
|
||||
) -> Result<RpcResponse<UiTokenAmount>>;
|
||||
|
||||
#[rpc(meta, name = "getTokenLargestAccounts")]
|
||||
fn get_token_largest_accounts(
|
||||
|
@ -2226,7 +2282,7 @@ impl RpcSol for RpcSolImpl {
|
|||
meta: Self::Metadata,
|
||||
pubkey_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<RpcResponse<RpcTokenAmount>> {
|
||||
) -> Result<RpcResponse<UiTokenAmount>> {
|
||||
debug!(
|
||||
"get_token_account_balance rpc request received: {:?}",
|
||||
pubkey_str
|
||||
|
@ -2240,7 +2296,7 @@ impl RpcSol for RpcSolImpl {
|
|||
meta: Self::Metadata,
|
||||
mint_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<RpcResponse<RpcTokenAmount>> {
|
||||
) -> Result<RpcResponse<UiTokenAmount>> {
|
||||
debug!("get_token_supply rpc request received: {:?}", mint_str);
|
||||
let mint = verify_pubkey(mint_str)?;
|
||||
meta.get_token_supply(&mint, commitment)
|
||||
|
@ -4614,7 +4670,7 @@ pub mod tests {
|
|||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
let balance: RpcTokenAmount =
|
||||
let balance: UiTokenAmount =
|
||||
serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||
let error = f64::EPSILON;
|
||||
assert!((balance.ui_amount - 4.2).abs() < error);
|
||||
|
@ -4642,7 +4698,7 @@ pub mod tests {
|
|||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
let supply: RpcTokenAmount =
|
||||
let supply: UiTokenAmount =
|
||||
serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||
let error = f64::EPSILON;
|
||||
assert!((supply.ui_amount - 2.0 * 4.2).abs() < error);
|
||||
|
@ -4902,7 +4958,7 @@ pub mod tests {
|
|||
vec![
|
||||
RpcTokenAccountBalance {
|
||||
address: token_with_different_mint_pubkey.to_string(),
|
||||
amount: RpcTokenAmount {
|
||||
amount: UiTokenAmount {
|
||||
ui_amount: 0.42,
|
||||
decimals: 2,
|
||||
amount: "42".to_string(),
|
||||
|
@ -4910,7 +4966,7 @@ pub mod tests {
|
|||
},
|
||||
RpcTokenAccountBalance {
|
||||
address: token_with_smaller_balance.to_string(),
|
||||
amount: RpcTokenAmount {
|
||||
amount: UiTokenAmount {
|
||||
ui_amount: 0.1,
|
||||
decimals: 2,
|
||||
amount: "10".to_string(),
|
||||
|
|
|
@ -676,7 +676,8 @@ mod tests {
|
|||
.get_account(&nonce_account.pubkey())
|
||||
.unwrap()
|
||||
.data;
|
||||
let expected_data = parse_account_data(&system_program::id(), &expected_data).unwrap();
|
||||
let expected_data =
|
||||
parse_account_data(&system_program::id(), &expected_data, None).unwrap();
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "accountNotification",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! The `pubsub` module implements a threaded subscription service on client RPC request
|
||||
|
||||
use crate::rpc::{get_parsed_token_account, get_parsed_token_accounts};
|
||||
use core::hash::Hash;
|
||||
use jsonrpc_core::futures::Future;
|
||||
use jsonrpc_pubsub::{
|
||||
|
@ -7,7 +8,7 @@ use jsonrpc_pubsub::{
|
|||
SubscriptionId,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use solana_account_decoder::{UiAccount, UiAccountEncoding};
|
||||
use solana_account_decoder::{parse_token::spl_token_id_v1_0, UiAccount, UiAccountEncoding};
|
||||
use solana_client::{
|
||||
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
|
||||
rpc_filter::RpcFilterType,
|
||||
|
@ -178,7 +179,7 @@ where
|
|||
K: Eq + Hash + Clone + Copy,
|
||||
S: Clone + Serialize,
|
||||
B: Fn(&Bank, &K) -> X,
|
||||
F: Fn(X, Slot, Option<T>) -> (Box<dyn Iterator<Item = S>>, Slot),
|
||||
F: Fn(X, Slot, Option<T>, Option<Arc<Bank>>) -> (Box<dyn Iterator<Item = S>>, Slot),
|
||||
X: Clone + Serialize + Default,
|
||||
T: Clone,
|
||||
{
|
||||
|
@ -202,16 +203,18 @@ where
|
|||
commitment_slots.highest_confirmed_slot
|
||||
}
|
||||
};
|
||||
let results = {
|
||||
let bank_forks = bank_forks.read().unwrap();
|
||||
bank_forks
|
||||
.get(slot)
|
||||
let bank = bank_forks.read().unwrap().get(slot).cloned();
|
||||
let results = bank
|
||||
.clone()
|
||||
.map(|desired_bank| bank_method(&desired_bank, hashmap_key))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
.unwrap_or_default();
|
||||
let mut w_last_notified_slot = last_notified_slot.write().unwrap();
|
||||
let (filter_results, result_slot) =
|
||||
filter_results(results, *w_last_notified_slot, config.as_ref().cloned());
|
||||
let (filter_results, result_slot) = filter_results(
|
||||
results,
|
||||
*w_last_notified_slot,
|
||||
config.as_ref().cloned(),
|
||||
bank,
|
||||
);
|
||||
for result in filter_results {
|
||||
notifier.notify(
|
||||
Response {
|
||||
|
@ -244,18 +247,26 @@ fn filter_account_result(
|
|||
result: Option<(Account, Slot)>,
|
||||
last_notified_slot: Slot,
|
||||
encoding: Option<UiAccountEncoding>,
|
||||
bank: Option<Arc<Bank>>,
|
||||
) -> (Box<dyn Iterator<Item = UiAccount>>, Slot) {
|
||||
if let Some((account, fork)) = result {
|
||||
// If fork < last_notified_slot this means that we last notified for a fork
|
||||
// and should notify that the account state has been reverted.
|
||||
if fork != last_notified_slot {
|
||||
let encoding = encoding.unwrap_or(UiAccountEncoding::Binary);
|
||||
if account.owner == spl_token_id_v1_0() && encoding == UiAccountEncoding::JsonParsed {
|
||||
let bank = bank.unwrap(); // If result.is_some(), bank must also be Some
|
||||
if let Some(ui_account) = get_parsed_token_account(bank, account) {
|
||||
return (Box::new(iter::once(ui_account)), fork);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
Box::new(iter::once(UiAccount::encode(account, encoding))),
|
||||
Box::new(iter::once(UiAccount::encode(account, encoding, None))),
|
||||
fork,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
(Box::new(iter::empty()), last_notified_slot)
|
||||
}
|
||||
|
||||
|
@ -263,6 +274,7 @@ fn filter_signature_result(
|
|||
result: Option<transaction::Result<()>>,
|
||||
last_notified_slot: Slot,
|
||||
_config: Option<()>,
|
||||
_bank: Option<Arc<Bank>>,
|
||||
) -> (Box<dyn Iterator<Item = RpcSignatureResult>>, Slot) {
|
||||
(
|
||||
Box::new(
|
||||
|
@ -278,27 +290,30 @@ fn filter_program_results(
|
|||
accounts: Vec<(Pubkey, Account)>,
|
||||
last_notified_slot: Slot,
|
||||
config: Option<ProgramConfig>,
|
||||
bank: Option<Arc<Bank>>,
|
||||
) -> (Box<dyn Iterator<Item = RpcKeyedAccount>>, Slot) {
|
||||
let config = config.unwrap_or_default();
|
||||
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
|
||||
let filters = config.filters;
|
||||
(
|
||||
Box::new(
|
||||
accounts
|
||||
.into_iter()
|
||||
.filter(move |(_, account)| {
|
||||
let keyed_accounts = accounts.into_iter().filter(move |(_, account)| {
|
||||
filters.iter().all(|filter_type| match filter_type {
|
||||
RpcFilterType::DataSize(size) => account.data.len() as u64 == *size,
|
||||
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data),
|
||||
})
|
||||
})
|
||||
.map(move |(pubkey, account)| RpcKeyedAccount {
|
||||
});
|
||||
let accounts: Box<dyn Iterator<Item = RpcKeyedAccount>> =
|
||||
if encoding == UiAccountEncoding::JsonParsed {
|
||||
let bank = bank.unwrap(); // If !accounts.is_empty(), bank must be Some
|
||||
Box::new(get_parsed_token_accounts(bank, keyed_accounts))
|
||||
} else {
|
||||
Box::new(
|
||||
keyed_accounts.map(move |(pubkey, account)| RpcKeyedAccount {
|
||||
pubkey: pubkey.to_string(),
|
||||
account: UiAccount::encode(account, encoding.clone()),
|
||||
account: UiAccount::encode(account, encoding.clone(), None),
|
||||
}),
|
||||
),
|
||||
last_notified_slot,
|
||||
)
|
||||
};
|
||||
(accounts, last_notified_slot)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -860,7 +875,7 @@ impl RpcSubscriptions {
|
|||
&subscriptions.gossip_account_subscriptions,
|
||||
&subscriptions.gossip_program_subscriptions,
|
||||
&subscriptions.gossip_signature_subscriptions,
|
||||
&bank_forks,
|
||||
bank_forks,
|
||||
&commitment_slots,
|
||||
¬ifier,
|
||||
);
|
||||
|
@ -881,7 +896,7 @@ impl RpcSubscriptions {
|
|||
for pubkey in &pubkeys {
|
||||
Self::check_account(
|
||||
pubkey,
|
||||
&bank_forks,
|
||||
bank_forks,
|
||||
account_subscriptions.clone(),
|
||||
¬ifier,
|
||||
&commitment_slots,
|
||||
|
@ -895,7 +910,7 @@ impl RpcSubscriptions {
|
|||
for program_id in &programs {
|
||||
Self::check_program(
|
||||
program_id,
|
||||
&bank_forks,
|
||||
bank_forks,
|
||||
program_subscriptions.clone(),
|
||||
¬ifier,
|
||||
&commitment_slots,
|
||||
|
@ -909,7 +924,7 @@ impl RpcSubscriptions {
|
|||
for signature in &signatures {
|
||||
Self::check_signature(
|
||||
signature,
|
||||
&bank_forks,
|
||||
bank_forks,
|
||||
signature_subscriptions.clone(),
|
||||
¬ifier,
|
||||
&commitment_slots,
|
||||
|
|
|
@ -1118,7 +1118,7 @@ The result will be an RpcResponse JSON object with `value` equal to an array of
|
|||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenAccountsByDelegate", "params": ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"programId": "TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"}, {"encoding": "jsonParsed"}]}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":[{"data":{"program":"spl-token","parsed":{"accountType":"account","info":{"amount":1,"delegate":"4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T","delegatedAmount":1,"isInitialized":true,"isNative":false,"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E","owner":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}}},"executable":false,"lamports":1726080,"owner":"TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o","rentEpoch":4},"pubkey":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}],"id":1}
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":[{"data":{"program":"spl-token","parsed":{"accountType":"account","info":{"tokenAmount":{"amount":"1","uiAmount":0.1,"decimals":1},"delegate":"4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T","delegatedAmount":1,"isInitialized":true,"isNative":false,"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E","owner":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}}},"executable":false,"lamports":1726080,"owner":"TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o","rentEpoch":4},"pubkey":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}],"id":1}
|
||||
```
|
||||
|
||||
### getTokenAccountsByOwner
|
||||
|
@ -1154,7 +1154,7 @@ The result will be an RpcResponse JSON object with `value` equal to an array of
|
|||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenAccountsByOwner", "params": ["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F", {"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E"}, {"encoding": "jsonParsed"}]}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":[{"data":{"program":"spl-token","parsed":{"accountType":"account","info":{"amount":1,"delegate":null,"delegatedAmount":1,"isInitialized":true,"isNative":false,"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E","owner":"4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"}}},"executable":false,"lamports":1726080,"owner":"TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o","rentEpoch":4},"pubkey":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}],"id":1}
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":[{"data":{"program":"spl-token","parsed":{"accountType":"account","info":{"tokenAmount":{"amount":"1","uiAmount":0.1,"decimals":1},"delegate":null,"delegatedAmount":1,"isInitialized":true,"isNative":false,"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E","owner":"4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"}}},"executable":false,"lamports":1726080,"owner":"TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o","rentEpoch":4},"pubkey":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}],"id":1}
|
||||
```
|
||||
|
||||
### getTokenSupply
|
||||
|
@ -1481,8 +1481,10 @@ Subscribe to an account to receive notifications when the lamports or data for a
|
|||
},
|
||||
"value": {
|
||||
"data": {
|
||||
"nonce": {
|
||||
"initialized": {
|
||||
"program": "nonce"
|
||||
"parsed": {
|
||||
"type": "initialized",
|
||||
"info": {
|
||||
"authority": "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX",
|
||||
"blockhash": "LUaQTmM7WbMRiATdMMHaRGakPtCkc2GHtH57STKXs6k",
|
||||
"feeCalculator": {
|
||||
|
@ -1597,8 +1599,10 @@ Subscribe to a program to receive notifications when the lamports or data for a
|
|||
"pubkey": "H4vnBqifaSACnKa7acsxstsY1iV1bvJNxsCY7enrd1hq"
|
||||
"account": {
|
||||
"data": {
|
||||
"nonce": {
|
||||
"initialized": {
|
||||
"program": "nonce"
|
||||
"parsed": {
|
||||
"type": "initialized",
|
||||
"info": {
|
||||
"authority": "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX",
|
||||
"blockhash": "LUaQTmM7WbMRiATdMMHaRGakPtCkc2GHtH57STKXs6k",
|
||||
"feeCalculator": {
|
||||
|
|
Loading…
Reference in New Issue