Add SPL Token-specific rpc endpoints (#11231)
* Simplify account-decoder program ids + spl_token helper * Spl program namespace version * Add getTokenAccountBalance endpoint * Remove token program id from getTokenAccountBalance request * Add getTokenSupply endpoint * Remove token program id from getTokenSupply request * Add getTokenAccountsByOwner/Delegate endpoints * Remove token program id from getTokenAccountsByOwner/Delegate requests * Named parameter
This commit is contained in:
parent
e553a98d2f
commit
b45ac5d4db
|
@ -2931,7 +2931,6 @@ dependencies = [
|
||||||
"solana-sdk 1.2.13",
|
"solana-sdk 1.2.13",
|
||||||
"solana-sdk 1.3.0",
|
"solana-sdk 1.3.0",
|
||||||
"solana-vote-program",
|
"solana-vote-program",
|
||||||
"spl-memo",
|
|
||||||
"spl-token",
|
"spl-token",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
@ -3237,6 +3236,7 @@ dependencies = [
|
||||||
"solana-perf",
|
"solana-perf",
|
||||||
"solana-rayon-threadlimit",
|
"solana-rayon-threadlimit",
|
||||||
"solana-runtime",
|
"solana-runtime",
|
||||||
|
"solana-sdk 1.2.13",
|
||||||
"solana-sdk 1.3.0",
|
"solana-sdk 1.3.0",
|
||||||
"solana-sdk-macro-frozen-abi",
|
"solana-sdk-macro-frozen-abi",
|
||||||
"solana-stake-program",
|
"solana-stake-program",
|
||||||
|
@ -3246,6 +3246,7 @@ dependencies = [
|
||||||
"solana-version",
|
"solana-version",
|
||||||
"solana-vote-program",
|
"solana-vote-program",
|
||||||
"solana-vote-signer",
|
"solana-vote-signer",
|
||||||
|
"spl-token",
|
||||||
"systemstat",
|
"systemstat",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
@ -15,9 +15,8 @@ Inflector = "0.11.4"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||||
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
||||||
spl-memo = { version = "1.0.4", features = ["skip-no-mangle"] }
|
|
||||||
spl-sdk = { package = "solana-sdk", version = "=1.2.13", default-features = false }
|
spl-sdk = { package = "solana-sdk", version = "=1.2.13", default-features = false }
|
||||||
spl-token = { version = "1.0.2", features = ["skip-no-mangle"] }
|
spl-token-v1-0 = { package = "spl-token", version = "1.0.2", features = ["skip-no-mangle"] }
|
||||||
serde = "1.0.112"
|
serde = "1.0.112"
|
||||||
serde_derive = "1.0.103"
|
serde_derive = "1.0.103"
|
||||||
serde_json = "1.0.56"
|
serde_json = "1.0.56"
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
use crate::{parse_nonce::parse_nonce, parse_token::parse_token, parse_vote::parse_vote};
|
use crate::{
|
||||||
|
parse_nonce::parse_nonce,
|
||||||
|
parse_token::{parse_token, spl_token_id_v1_0},
|
||||||
|
parse_vote::parse_vote,
|
||||||
|
};
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
|
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::collections::HashMap;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref SYSTEM_PROGRAM_ID: Pubkey =
|
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
|
||||||
Pubkey::from_str(&system_program::id().to_string()).unwrap();
|
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v1_0();
|
||||||
static ref TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str(&spl_token::id().to_string()).unwrap();
|
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
|
||||||
static ref VOTE_PROGRAM_ID: Pubkey =
|
|
||||||
Pubkey::from_str(&solana_vote_program::id().to_string()).unwrap();
|
|
||||||
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
|
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
|
||||||
let mut m = HashMap::new();
|
let mut m = HashMap::new();
|
||||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
use crate::parse_account_data::{ParsableAccount, ParseAccountError};
|
use crate::parse_account_data::{ParsableAccount, ParseAccountError};
|
||||||
use spl_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use spl_token::{
|
use spl_sdk::pubkey::Pubkey as SplPubkey;
|
||||||
|
use spl_token_v1_0::{
|
||||||
option::COption,
|
option::COption,
|
||||||
state::{Account, Mint, Multisig, State},
|
state::{Account, Mint, Multisig, State},
|
||||||
};
|
};
|
||||||
use std::mem::size_of;
|
use std::{mem::size_of, str::FromStr};
|
||||||
|
|
||||||
|
// A helper function to convert spl_token_v1_0::id() as spl_sdk::pubkey::Pubkey to solana_sdk::pubkey::Pubkey
|
||||||
|
pub fn spl_token_id_v1_0() -> Pubkey {
|
||||||
|
Pubkey::from_str(&spl_token_v1_0::id().to_string()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
||||||
let mut data = data.to_vec();
|
let mut data = data.to_vec();
|
||||||
|
@ -45,7 +51,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
||||||
.signers
|
.signers
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|pubkey| {
|
.filter_map(|pubkey| {
|
||||||
if pubkey != &Pubkey::default() {
|
if pubkey != &SplPubkey::default() {
|
||||||
Some(pubkey.to_string())
|
Some(pubkey.to_string())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -103,8 +109,8 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_token() {
|
fn test_parse_token() {
|
||||||
let mint_pubkey = Pubkey::new(&[2; 32]);
|
let mint_pubkey = SplPubkey::new(&[2; 32]);
|
||||||
let owner_pubkey = Pubkey::new(&[3; 32]);
|
let owner_pubkey = SplPubkey::new(&[3; 32]);
|
||||||
let mut account_data = [0; size_of::<Account>()];
|
let mut account_data = [0; size_of::<Account>()];
|
||||||
let mut account: &mut Account = State::unpack_unchecked(&mut account_data).unwrap();
|
let mut account: &mut Account = State::unpack_unchecked(&mut account_data).unwrap();
|
||||||
account.mint = mint_pubkey;
|
account.mint = mint_pubkey;
|
||||||
|
@ -138,12 +144,12 @@ mod test {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let signer1 = Pubkey::new(&[1; 32]);
|
let signer1 = SplPubkey::new(&[1; 32]);
|
||||||
let signer2 = Pubkey::new(&[2; 32]);
|
let signer2 = SplPubkey::new(&[2; 32]);
|
||||||
let signer3 = Pubkey::new(&[3; 32]);
|
let signer3 = SplPubkey::new(&[3; 32]);
|
||||||
let mut multisig_data = [0; size_of::<Multisig>()];
|
let mut multisig_data = [0; size_of::<Multisig>()];
|
||||||
let mut multisig: &mut Multisig = State::unpack_unchecked(&mut multisig_data).unwrap();
|
let mut multisig: &mut Multisig = State::unpack_unchecked(&mut multisig_data).unwrap();
|
||||||
let mut signers = [Pubkey::default(); 11];
|
let mut signers = [SplPubkey::default(); 11];
|
||||||
signers[0] = signer1;
|
signers[0] = signer1;
|
||||||
signers[1] = signer2;
|
signers[1] = signer2;
|
||||||
signers[2] = signer3;
|
signers[2] = signer3;
|
||||||
|
|
|
@ -58,3 +58,10 @@ pub struct RpcProgramAccountsConfig {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub account_config: RpcAccountInfoConfig,
|
pub account_config: RpcAccountInfoConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum RpcTokenAccountsFilter {
|
||||||
|
Mint(String),
|
||||||
|
ProgramId(String),
|
||||||
|
}
|
||||||
|
|
|
@ -67,6 +67,8 @@ solana-transaction-status = { path = "../transaction-status", version = "1.3.0"
|
||||||
solana-version = { path = "../version", version = "1.3.0" }
|
solana-version = { path = "../version", version = "1.3.0" }
|
||||||
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
||||||
solana-vote-signer = { path = "../vote-signer", version = "1.3.0" }
|
solana-vote-signer = { path = "../vote-signer", version = "1.3.0" }
|
||||||
|
spl-sdk = { package = "solana-sdk", version = "=1.2.13", default-features = false }
|
||||||
|
spl-token-v1-0 = { package = "spl-token", version = "1.0.2", features = ["skip-no-mangle"] }
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = "0.1"
|
tokio = "0.1"
|
||||||
|
|
591
core/src/rpc.rs
591
core/src/rpc.rs
|
@ -8,10 +8,10 @@ use crate::{
|
||||||
use bincode::{config::Options, serialize};
|
use bincode::{config::Options, serialize};
|
||||||
use jsonrpc_core::{Error, Metadata, Result};
|
use jsonrpc_core::{Error, Metadata, Result};
|
||||||
use jsonrpc_derive::rpc;
|
use jsonrpc_derive::rpc;
|
||||||
use solana_account_decoder::{UiAccount, UiAccountEncoding};
|
use solana_account_decoder::{parse_token::spl_token_id_v1_0, UiAccount, UiAccountEncoding};
|
||||||
use solana_client::{
|
use solana_client::{
|
||||||
rpc_config::*,
|
rpc_config::*,
|
||||||
rpc_filter::RpcFilterType,
|
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType},
|
||||||
rpc_request::{
|
rpc_request::{
|
||||||
DELINQUENT_VALIDATOR_SLOT_DISTANCE, MAX_GET_CONFIRMED_BLOCKS_RANGE,
|
DELINQUENT_VALIDATOR_SLOT_DISTANCE, MAX_GET_CONFIRMED_BLOCKS_RANGE,
|
||||||
MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE,
|
MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE,
|
||||||
|
@ -32,6 +32,7 @@ use solana_runtime::{
|
||||||
send_transaction_service::{SendTransactionService, TransactionInfo},
|
send_transaction_service::{SendTransactionService, TransactionInfo},
|
||||||
};
|
};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
|
account::Account,
|
||||||
account_utils::StateMut,
|
account_utils::StateMut,
|
||||||
clock::{Slot, UnixTimestamp},
|
clock::{Slot, UnixTimestamp},
|
||||||
commitment_config::{CommitmentConfig, CommitmentLevel},
|
commitment_config::{CommitmentConfig, CommitmentLevel},
|
||||||
|
@ -50,9 +51,11 @@ use solana_transaction_status::{
|
||||||
ConfirmedBlock, ConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
|
ConfirmedBlock, ConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
|
||||||
};
|
};
|
||||||
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
|
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
|
||||||
|
use spl_token_v1_0::state::{Account as TokenAccount, State as TokenState};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{max, min},
|
cmp::{max, min},
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
mem::size_of,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
@ -249,14 +252,7 @@ impl JsonRpcRequestProcessor {
|
||||||
let config = config.unwrap_or_default();
|
let config = config.unwrap_or_default();
|
||||||
let bank = self.bank(config.commitment);
|
let bank = self.bank(config.commitment);
|
||||||
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
|
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
|
||||||
bank.get_program_accounts(Some(&program_id))
|
get_filtered_program_accounts(&bank, program_id, filters)
|
||||||
.into_iter()
|
|
||||||
.filter(|(_, 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(|(pubkey, account)| RpcKeyedAccount {
|
.map(|(pubkey, account)| RpcKeyedAccount {
|
||||||
pubkey: pubkey.to_string(),
|
pubkey: pubkey.to_string(),
|
||||||
account: UiAccount::encode(account, encoding.clone()),
|
account: UiAccount::encode(account, encoding.clone()),
|
||||||
|
@ -839,6 +835,145 @@ impl JsonRpcRequestProcessor {
|
||||||
inactive: inactive_stake,
|
inactive: inactive_stake,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_token_account_balance(
|
||||||
|
&self,
|
||||||
|
pubkey: &Pubkey,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<u64>> {
|
||||||
|
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())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if account.owner != spl_token_id_v1_0() {
|
||||||
|
return Err(Error::invalid_params(
|
||||||
|
"Invalid param: not a v1.0 Token account".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut data = account.data.to_vec();
|
||||||
|
let balance = TokenState::unpack(&mut data)
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::invalid_params("Invalid param: not a v1.0 Token account".to_string())
|
||||||
|
})
|
||||||
|
.map(|account: &mut TokenAccount| account.amount)?;
|
||||||
|
Ok(new_response(&bank, balance))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_token_supply(
|
||||||
|
&self,
|
||||||
|
mint: &Pubkey,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<u64>> {
|
||||||
|
let bank = self.bank(commitment);
|
||||||
|
let mint_account = bank.get_account(mint).ok_or_else(|| {
|
||||||
|
Error::invalid_params("Invalid param: could not find mint".to_string())
|
||||||
|
})?;
|
||||||
|
if mint_account.owner != spl_token_id_v1_0() {
|
||||||
|
return Err(Error::invalid_params(
|
||||||
|
"Invalid param: not a v1.0 Token mint".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let filters = vec![
|
||||||
|
// Filter on Mint address
|
||||||
|
RpcFilterType::Memcmp(Memcmp {
|
||||||
|
offset: 0,
|
||||||
|
bytes: MemcmpEncodedBytes::Binary(mint.to_string()),
|
||||||
|
encoding: None,
|
||||||
|
}),
|
||||||
|
// Filter on Token Account state
|
||||||
|
RpcFilterType::DataSize(size_of::<TokenAccount>() as u64),
|
||||||
|
];
|
||||||
|
let supply = get_filtered_program_accounts(&bank, &mint_account.owner, filters)
|
||||||
|
.map(|(_pubkey, account)| {
|
||||||
|
let mut data = account.data.to_vec();
|
||||||
|
TokenState::unpack(&mut data)
|
||||||
|
.map(|account: &mut TokenAccount| account.amount)
|
||||||
|
.unwrap_or(0)
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
Ok(new_response(&bank, supply))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_token_accounts_by_owner(
|
||||||
|
&self,
|
||||||
|
owner: &Pubkey,
|
||||||
|
token_account_filter: TokenAccountsFilter,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<Vec<RpcKeyedAccount>>> {
|
||||||
|
let bank = self.bank(commitment);
|
||||||
|
let (token_program_id, mint) = get_token_program_id_and_mint(&bank, token_account_filter)?;
|
||||||
|
|
||||||
|
let mut filters = vec![
|
||||||
|
// Filter on Owner address
|
||||||
|
RpcFilterType::Memcmp(Memcmp {
|
||||||
|
offset: 32,
|
||||||
|
bytes: MemcmpEncodedBytes::Binary(owner.to_string()),
|
||||||
|
encoding: None,
|
||||||
|
}),
|
||||||
|
// Filter on Token Account state
|
||||||
|
RpcFilterType::DataSize(size_of::<TokenAccount>() as u64),
|
||||||
|
];
|
||||||
|
if let Some(mint) = mint {
|
||||||
|
// Optional filter on Mint address
|
||||||
|
filters.push(RpcFilterType::Memcmp(Memcmp {
|
||||||
|
offset: 0,
|
||||||
|
bytes: MemcmpEncodedBytes::Binary(mint.to_string()),
|
||||||
|
encoding: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
let accounts = get_filtered_program_accounts(&bank, &token_program_id, filters)
|
||||||
|
.map(|(pubkey, account)| RpcKeyedAccount {
|
||||||
|
pubkey: pubkey.to_string(),
|
||||||
|
account: UiAccount::encode(account, UiAccountEncoding::JsonParsed),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(new_response(&bank, accounts))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_token_accounts_by_delegate(
|
||||||
|
&self,
|
||||||
|
delegate: &Pubkey,
|
||||||
|
token_account_filter: TokenAccountsFilter,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<Vec<RpcKeyedAccount>>> {
|
||||||
|
let bank = self.bank(commitment);
|
||||||
|
let (token_program_id, mint) = get_token_program_id_and_mint(&bank, token_account_filter)?;
|
||||||
|
|
||||||
|
let mut filters = vec![
|
||||||
|
// Filter on Delegate is_some()
|
||||||
|
RpcFilterType::Memcmp(Memcmp {
|
||||||
|
offset: 72,
|
||||||
|
bytes: MemcmpEncodedBytes::Binary(
|
||||||
|
bs58::encode(bincode::serialize(&1u32).unwrap()).into_string(),
|
||||||
|
),
|
||||||
|
encoding: None,
|
||||||
|
}),
|
||||||
|
// Filter on Delegate address
|
||||||
|
RpcFilterType::Memcmp(Memcmp {
|
||||||
|
offset: 76,
|
||||||
|
bytes: MemcmpEncodedBytes::Binary(delegate.to_string()),
|
||||||
|
encoding: None,
|
||||||
|
}),
|
||||||
|
// Filter on Token Account state
|
||||||
|
RpcFilterType::DataSize(size_of::<TokenAccount>() as u64),
|
||||||
|
];
|
||||||
|
if let Some(mint) = mint {
|
||||||
|
// Optional filter on Mint address
|
||||||
|
filters.push(RpcFilterType::Memcmp(Memcmp {
|
||||||
|
offset: 0,
|
||||||
|
bytes: MemcmpEncodedBytes::Binary(mint.to_string()),
|
||||||
|
encoding: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
let accounts = get_filtered_program_accounts(&bank, &token_program_id, filters)
|
||||||
|
.map(|(pubkey, account)| RpcKeyedAccount {
|
||||||
|
pubkey: pubkey.to_string(),
|
||||||
|
account: UiAccount::encode(account, UiAccountEncoding::JsonParsed),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(new_response(&bank, accounts))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_filter(input: &RpcFilterType) -> Result<()> {
|
fn verify_filter(input: &RpcFilterType) -> Result<()> {
|
||||||
|
@ -859,6 +994,26 @@ fn verify_signature(input: &str) -> Result<Signature> {
|
||||||
.map_err(|e| Error::invalid_params(format!("Invalid param: {:?}", e)))
|
.map_err(|e| Error::invalid_params(format!("Invalid param: {:?}", e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum TokenAccountsFilter {
|
||||||
|
Mint(Pubkey),
|
||||||
|
ProgramId(Pubkey),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_token_account_filter(
|
||||||
|
token_account_filter: RpcTokenAccountsFilter,
|
||||||
|
) -> Result<TokenAccountsFilter> {
|
||||||
|
match token_account_filter {
|
||||||
|
RpcTokenAccountsFilter::Mint(mint_str) => {
|
||||||
|
let mint = verify_pubkey(mint_str)?;
|
||||||
|
Ok(TokenAccountsFilter::Mint(mint))
|
||||||
|
}
|
||||||
|
RpcTokenAccountsFilter::ProgramId(program_id_str) => {
|
||||||
|
let program_id = verify_pubkey(program_id_str)?;
|
||||||
|
Ok(TokenAccountsFilter::ProgramId(program_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Run transactions against a frozen bank without committing the results
|
/// Run transactions against a frozen bank without committing the results
|
||||||
fn run_transaction_simulation(
|
fn run_transaction_simulation(
|
||||||
bank: &Bank,
|
bank: &Bank,
|
||||||
|
@ -882,6 +1037,52 @@ fn run_transaction_simulation(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use a set of filters to get an iterator of keyed program accounts from a bank
|
||||||
|
fn get_filtered_program_accounts(
|
||||||
|
bank: &Arc<Bank>,
|
||||||
|
program_id: &Pubkey,
|
||||||
|
filters: Vec<RpcFilterType>,
|
||||||
|
) -> impl Iterator<Item = (Pubkey, Account)> {
|
||||||
|
bank.get_program_accounts(Some(&program_id))
|
||||||
|
.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),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
bank: &Arc<Bank>,
|
||||||
|
token_account_filter: TokenAccountsFilter,
|
||||||
|
) -> Result<(Pubkey, Option<Pubkey>)> {
|
||||||
|
match token_account_filter {
|
||||||
|
TokenAccountsFilter::Mint(mint) => {
|
||||||
|
let mint_account = bank.get_account(&mint).ok_or_else(|| {
|
||||||
|
Error::invalid_params("Invalid param: could not find mint".to_string())
|
||||||
|
})?;
|
||||||
|
if mint_account.owner != spl_token_id_v1_0() {
|
||||||
|
return Err(Error::invalid_params(
|
||||||
|
"Invalid param: not a v1.0 Token mint".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok((mint_account.owner, Some(mint)))
|
||||||
|
}
|
||||||
|
TokenAccountsFilter::ProgramId(program_id) => {
|
||||||
|
if program_id == spl_token_id_v1_0() {
|
||||||
|
Ok((program_id, None))
|
||||||
|
} else {
|
||||||
|
Err(Error::invalid_params(
|
||||||
|
"Invalid param: unrecognized Token program id".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[rpc]
|
#[rpc]
|
||||||
pub trait RpcSol {
|
pub trait RpcSol {
|
||||||
type Metadata;
|
type Metadata;
|
||||||
|
@ -1154,6 +1355,44 @@ pub trait RpcSol {
|
||||||
pubkey_str: String,
|
pubkey_str: String,
|
||||||
config: Option<RpcStakeConfig>,
|
config: Option<RpcStakeConfig>,
|
||||||
) -> Result<RpcStakeActivation>;
|
) -> Result<RpcStakeActivation>;
|
||||||
|
|
||||||
|
// SPL Token-specific RPC endpoints
|
||||||
|
// See https://github.com/solana-labs/solana-program-library/releases/tag/token-v1.0.0 for
|
||||||
|
// program details
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getTokenAccountBalance")]
|
||||||
|
fn get_token_account_balance(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
pubkey_str: String,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<u64>>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getTokenSupply")]
|
||||||
|
fn get_token_supply(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
mint_str: String,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<u64>>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getTokenAccountsByOwner")]
|
||||||
|
fn get_token_accounts_by_owner(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
owner_str: String,
|
||||||
|
token_account_filter: RpcTokenAccountsFilter,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<Vec<RpcKeyedAccount>>>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getTokenAccountsByDelegate")]
|
||||||
|
fn get_token_accounts_by_delegate(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
delegate_str: String,
|
||||||
|
token_account_filter: RpcTokenAccountsFilter,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<Vec<RpcKeyedAccount>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RpcSolImpl;
|
pub struct RpcSolImpl;
|
||||||
|
@ -1741,6 +1980,63 @@ impl RpcSol for RpcSolImpl {
|
||||||
let pubkey = verify_pubkey(pubkey_str)?;
|
let pubkey = verify_pubkey(pubkey_str)?;
|
||||||
meta.get_stake_activation(&pubkey, config)
|
meta.get_stake_activation(&pubkey, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_token_account_balance(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
pubkey_str: String,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<u64>> {
|
||||||
|
debug!(
|
||||||
|
"get_token_account_balance rpc request received: {:?}",
|
||||||
|
pubkey_str
|
||||||
|
);
|
||||||
|
let pubkey = verify_pubkey(pubkey_str)?;
|
||||||
|
meta.get_token_account_balance(&pubkey, commitment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_token_supply(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
mint_str: String,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<u64>> {
|
||||||
|
debug!("get_token_supply rpc request received: {:?}", mint_str);
|
||||||
|
let mint = verify_pubkey(mint_str)?;
|
||||||
|
meta.get_token_supply(&mint, commitment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_token_accounts_by_owner(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
owner_str: String,
|
||||||
|
token_account_filter: RpcTokenAccountsFilter,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<Vec<RpcKeyedAccount>>> {
|
||||||
|
debug!(
|
||||||
|
"get_token_accounts_by_owner rpc request received: {:?}",
|
||||||
|
owner_str
|
||||||
|
);
|
||||||
|
let owner = verify_pubkey(owner_str)?;
|
||||||
|
let token_account_filter = verify_token_account_filter(token_account_filter)?;
|
||||||
|
meta.get_token_accounts_by_owner(&owner, token_account_filter, commitment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_token_accounts_by_delegate(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
delegate_str: String,
|
||||||
|
token_account_filter: RpcTokenAccountsFilter,
|
||||||
|
commitment: Option<CommitmentConfig>,
|
||||||
|
) -> Result<RpcResponse<Vec<RpcKeyedAccount>>> {
|
||||||
|
debug!(
|
||||||
|
"get_token_accounts_by_delegate rpc request received: {:?}",
|
||||||
|
delegate_str
|
||||||
|
);
|
||||||
|
let delegate = verify_pubkey(delegate_str)?;
|
||||||
|
let token_account_filter = verify_token_account_filter(token_account_filter)?;
|
||||||
|
meta.get_token_accounts_by_delegate(&delegate, token_account_filter, commitment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_bs58_transaction(bs58_transaction: String) -> Result<(Vec<u8>, Transaction)> {
|
fn deserialize_bs58_transaction(bs58_transaction: String) -> Result<(Vec<u8>, Transaction)> {
|
||||||
|
@ -1811,6 +2107,8 @@ pub mod tests {
|
||||||
vote_instruction,
|
vote_instruction,
|
||||||
vote_state::{Vote, VoteInit, MAX_LOCKOUT_HISTORY},
|
vote_state::{Vote, VoteInit, MAX_LOCKOUT_HISTORY},
|
||||||
};
|
};
|
||||||
|
use spl_sdk::pubkey::Pubkey as SplPubkey;
|
||||||
|
use spl_token_v1_0::{option::COption, state::Mint};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
const TEST_MINT_LAMPORTS: u64 = 1_000_000;
|
const TEST_MINT_LAMPORTS: u64 = 1_000_000;
|
||||||
|
@ -3963,4 +4261,277 @@ pub mod tests {
|
||||||
3
|
3
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_token_rpcs() {
|
||||||
|
let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&Pubkey::new_rand());
|
||||||
|
|
||||||
|
let mut account_data = [0; size_of::<TokenAccount>()];
|
||||||
|
let account: &mut TokenAccount = TokenState::unpack_unchecked(&mut account_data).unwrap();
|
||||||
|
let mint = SplPubkey::new(&[2; 32]);
|
||||||
|
let owner = SplPubkey::new(&[3; 32]);
|
||||||
|
let delegate = SplPubkey::new(&[4; 32]);
|
||||||
|
*account = TokenAccount {
|
||||||
|
mint,
|
||||||
|
owner,
|
||||||
|
delegate: COption::Some(delegate),
|
||||||
|
amount: 42,
|
||||||
|
is_initialized: true,
|
||||||
|
is_native: false,
|
||||||
|
delegated_amount: 30,
|
||||||
|
};
|
||||||
|
let token_account = Account {
|
||||||
|
lamports: 111,
|
||||||
|
data: account_data.to_vec(),
|
||||||
|
owner: spl_token_id_v1_0(),
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
let token_account_pubkey = Pubkey::new_rand();
|
||||||
|
bank.store_account(&token_account_pubkey, &token_account);
|
||||||
|
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getTokenAccountBalance","params":["{}"]}}"#,
|
||||||
|
token_account_pubkey,
|
||||||
|
);
|
||||||
|
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: u64 = serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||||
|
assert_eq!(balance, 42);
|
||||||
|
|
||||||
|
// Test non-existent token account
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getTokenAccountBalance","params":["{}"]}}"#,
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert!(result.get("error").is_some());
|
||||||
|
|
||||||
|
// Add the mint, plus another token account to ensure getTokenSupply sums all mint accounts
|
||||||
|
let mut mint_data = [0; size_of::<Mint>()];
|
||||||
|
let mint_state: &mut Mint = TokenState::unpack_unchecked(&mut mint_data).unwrap();
|
||||||
|
*mint_state = Mint {
|
||||||
|
owner: COption::Some(owner),
|
||||||
|
decimals: 2,
|
||||||
|
is_initialized: true,
|
||||||
|
};
|
||||||
|
let mint_account = Account {
|
||||||
|
lamports: 111,
|
||||||
|
data: mint_data.to_vec(),
|
||||||
|
owner: spl_token_id_v1_0(),
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
|
||||||
|
let other_token_account_pubkey = Pubkey::new_rand();
|
||||||
|
bank.store_account(&other_token_account_pubkey, &token_account);
|
||||||
|
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getTokenSupply","params":["{}"]}}"#,
|
||||||
|
mint,
|
||||||
|
);
|
||||||
|
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: u64 = serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||||
|
assert_eq!(supply, 2 * 42);
|
||||||
|
|
||||||
|
// Test non-existent mint address
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getTokenSupply","params":["{}"]}}"#,
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert!(result.get("error").is_some());
|
||||||
|
|
||||||
|
// Add another token account with the same owner and delegate but different mint
|
||||||
|
let mut account_data = [0; size_of::<TokenAccount>()];
|
||||||
|
let account: &mut TokenAccount = TokenState::unpack_unchecked(&mut account_data).unwrap();
|
||||||
|
let new_mint = SplPubkey::new(&[5; 32]);
|
||||||
|
*account = TokenAccount {
|
||||||
|
mint: new_mint,
|
||||||
|
owner,
|
||||||
|
delegate: COption::Some(delegate),
|
||||||
|
amount: 42,
|
||||||
|
is_initialized: true,
|
||||||
|
is_native: false,
|
||||||
|
delegated_amount: 30,
|
||||||
|
};
|
||||||
|
let token_account = Account {
|
||||||
|
lamports: 111,
|
||||||
|
data: account_data.to_vec(),
|
||||||
|
owner: spl_token_id_v1_0(),
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
let token_with_different_mint_pubkey = Pubkey::new_rand();
|
||||||
|
bank.store_account(&token_with_different_mint_pubkey, &token_account);
|
||||||
|
|
||||||
|
// Test getTokenAccountsByOwner with Token program id returns all accounts, regardless of Mint address
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"getTokenAccountsByOwner",
|
||||||
|
"params":["{}", {{"programId": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
owner,
|
||||||
|
spl_token_id_v1_0(),
|
||||||
|
);
|
||||||
|
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 accounts: Vec<RpcKeyedAccount> =
|
||||||
|
serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||||
|
assert_eq!(accounts.len(), 3);
|
||||||
|
|
||||||
|
// Test returns only mint accounts
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,"method":"getTokenAccountsByOwner",
|
||||||
|
"params":["{}", {{"mint": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
owner, mint,
|
||||||
|
);
|
||||||
|
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 accounts: Vec<RpcKeyedAccount> =
|
||||||
|
serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||||
|
assert_eq!(accounts.len(), 2);
|
||||||
|
|
||||||
|
// Test non-existent Mint/program id
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"getTokenAccountsByOwner",
|
||||||
|
"params":["{}", {{"programId": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
owner,
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert!(result.get("error").is_some());
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"getTokenAccountsByOwner",
|
||||||
|
"params":["{}", {{"mint": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
owner,
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert!(result.get("error").is_some());
|
||||||
|
|
||||||
|
// Test non-existent Owner
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"getTokenAccountsByOwner",
|
||||||
|
"params":["{}", {{"programId": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
spl_token_id_v1_0(),
|
||||||
|
);
|
||||||
|
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 accounts: Vec<RpcKeyedAccount> =
|
||||||
|
serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||||
|
assert!(accounts.is_empty());
|
||||||
|
|
||||||
|
// Test getTokenAccountsByDelegate with Token program id returns all accounts, regardless of Mint address
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"getTokenAccountsByDelegate",
|
||||||
|
"params":["{}", {{"programId": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
delegate,
|
||||||
|
spl_token_id_v1_0(),
|
||||||
|
);
|
||||||
|
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 accounts: Vec<RpcKeyedAccount> =
|
||||||
|
serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||||
|
assert_eq!(accounts.len(), 3);
|
||||||
|
|
||||||
|
// Test returns only mint accounts
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,"method":
|
||||||
|
"getTokenAccountsByDelegate",
|
||||||
|
"params":["{}", {{"mint": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
delegate, mint,
|
||||||
|
);
|
||||||
|
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 accounts: Vec<RpcKeyedAccount> =
|
||||||
|
serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||||
|
assert_eq!(accounts.len(), 2);
|
||||||
|
|
||||||
|
// Test non-existent Mint/program id
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"getTokenAccountsByDelegate",
|
||||||
|
"params":["{}", {{"programId": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
delegate,
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert!(result.get("error").is_some());
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"getTokenAccountsByDelegate",
|
||||||
|
"params":["{}", {{"mint": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
delegate,
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert!(result.get("error").is_some());
|
||||||
|
|
||||||
|
// Test non-existent Owner
|
||||||
|
let req = format!(
|
||||||
|
r#"{{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"id":1,
|
||||||
|
"method":"getTokenAccountsByDelegate",
|
||||||
|
"params":["{}", {{"programId": "{}"}}]
|
||||||
|
}}"#,
|
||||||
|
Pubkey::new_rand(),
|
||||||
|
spl_token_id_v1_0(),
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta);
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
let accounts: Vec<RpcKeyedAccount> =
|
||||||
|
serde_json::from_value(result["result"]["value"].clone()).unwrap();
|
||||||
|
assert!(accounts.is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
||||||
- [getSlotLeader](jsonrpc-api.md#getslotleader)
|
- [getSlotLeader](jsonrpc-api.md#getslotleader)
|
||||||
- [getStakeActivation](jsonrpc-api.md#getstakeactivation)
|
- [getStakeActivation](jsonrpc-api.md#getstakeactivation)
|
||||||
- [getSupply](jsonrpc-api.md#getsupply)
|
- [getSupply](jsonrpc-api.md#getsupply)
|
||||||
|
- [getTokenAccountBalance](jsonrpc-api.md#gettokenaccountbalance)
|
||||||
|
- [getTokenAccountsByDelegate](jsonrpc-api.md#gettokenaccountsbydelegate)
|
||||||
|
- [getTokenAccountsByOwner](jsonrpc-api.md#gettokenaccountsbyowner)
|
||||||
|
- [getTokenSupply](jsonrpc-api.md#gettokensupply)
|
||||||
- [getTransactionCount](jsonrpc-api.md#gettransactioncount)
|
- [getTransactionCount](jsonrpc-api.md#gettransactioncount)
|
||||||
- [getVersion](jsonrpc-api.md#getversion)
|
- [getVersion](jsonrpc-api.md#getversion)
|
||||||
- [getVoteAccounts](jsonrpc-api.md#getvoteaccounts)
|
- [getVoteAccounts](jsonrpc-api.md#getvoteaccounts)
|
||||||
|
@ -1016,6 +1020,116 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":{"circulating":16000,"nonCirculating":1000000,"nonCirculatingAccounts":["FEy8pTbP5fEoqMV1GdTz83byuA8EKByqYat1PKDgVAq5","9huDUZfxoJ7wGMTffUE7vh1xePqef7gyrLJu9NApncqA","3mi1GmwEE3zo2jmfDuzvjSX9ovRXsDUKHvsntpkhuLJ9","BYxEJTDerkaRWBem3XgnVcdhppktBXa2HbkHPKj2Ui4Z],total:1016000}},"id":1}
|
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":{"circulating":16000,"nonCirculating":1000000,"nonCirculatingAccounts":["FEy8pTbP5fEoqMV1GdTz83byuA8EKByqYat1PKDgVAq5","9huDUZfxoJ7wGMTffUE7vh1xePqef7gyrLJu9NApncqA","3mi1GmwEE3zo2jmfDuzvjSX9ovRXsDUKHvsntpkhuLJ9","BYxEJTDerkaRWBem3XgnVcdhppktBXa2HbkHPKj2Ui4Z],total:1016000}},"id":1}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### getTokenAccountBalance
|
||||||
|
|
||||||
|
Returns the token balance of an SPL Token account.
|
||||||
|
|
||||||
|
#### Parameters:
|
||||||
|
|
||||||
|
- `<string>` - Pubkey of Token account to query, as base-58 encoded string
|
||||||
|
- `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||||
|
|
||||||
|
#### Results:
|
||||||
|
|
||||||
|
- `RpcResponse<u64>` - RpcResponse JSON object with `value` field set to the balance
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
// Request
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenAccountBalance", "params": ["7fUAJdStEuGbc3sM84cKRL6yYaaSstyLSU4ve5oovLS7"]}' http://localhost:8899
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":9864,"id":1}
|
||||||
|
```
|
||||||
|
|
||||||
|
### getTokenAccountsByDelegate
|
||||||
|
|
||||||
|
Returns all SPL Token accounts by approved Delegate.
|
||||||
|
|
||||||
|
#### Parameters:
|
||||||
|
|
||||||
|
- `<string>` - Pubkey of account delegate to query, as base-58 encoded string
|
||||||
|
- `<object>` - Either:
|
||||||
|
* `mint: <string>` - Pubkey of the specific token Mint to limit accounts to, as base-58 encoded string; or
|
||||||
|
* `programId: <string>` - Pubkey of the Token program ID that owns the accounts, as base-58 encoded string
|
||||||
|
- `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||||
|
|
||||||
|
#### Results:
|
||||||
|
|
||||||
|
The result will be an RpcResponse JSON object with `value` equal to an array of JSON objects, which will contain:
|
||||||
|
|
||||||
|
- `pubkey: <string>` - the account Pubkey as base-58 encoded string
|
||||||
|
- `account: <object>` - a JSON object, with the following sub fields:
|
||||||
|
- `lamports: <u64>`, number of lamports assigned to this account, as a u64
|
||||||
|
- `owner: <string>`, base-58 encoded Pubkey of the program this account has been assigned to
|
||||||
|
`data: <object>`, Token state data associated with the account, in JSON format `{<program>: <state>}`
|
||||||
|
- `executable: <bool>`, boolean indicating if the account contains a program \(and is strictly read-only\)
|
||||||
|
- `rentEpoch: <u64>`, the epoch at which this account will next owe rent, as u64
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
// Request
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenAccountsByDelegate", "params": ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"programId": "TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"}]}' http://localhost:8899
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":[{"data":{"token":{"account":{"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}
|
||||||
|
```
|
||||||
|
|
||||||
|
### getTokenAccountsByOwner
|
||||||
|
|
||||||
|
Returns all SPL Token accounts by token owner.
|
||||||
|
|
||||||
|
#### Parameters:
|
||||||
|
|
||||||
|
- `<string>` - Pubkey of account owner to query, as base-58 encoded string
|
||||||
|
- `<object>` - Either:
|
||||||
|
* `mint: <string>` - Pubkey of the specific token Mint to limit accounts to, as base-58 encoded string; or
|
||||||
|
* `programId: <string>` - Pubkey of the Token program ID that owns the accounts, as base-58 encoded string
|
||||||
|
- `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||||
|
|
||||||
|
#### Results:
|
||||||
|
|
||||||
|
The result will be an RpcResponse JSON object with `value` equal to an array of JSON objects, which will contain:
|
||||||
|
|
||||||
|
- `pubkey: <string>` - the account Pubkey as base-58 encoded string
|
||||||
|
- `account: <object>` - a JSON object, with the following sub fields:
|
||||||
|
- `lamports: <u64>`, number of lamports assigned to this account, as a u64
|
||||||
|
- `owner: <string>`, base-58 encoded Pubkey of the program this account has been assigned to
|
||||||
|
`data: <object>`, Token state data associated with the account, in JSON format `{<program>: <state>}`
|
||||||
|
- `executable: <bool>`, boolean indicating if the account contains a program \(and is strictly read-only\)
|
||||||
|
- `rentEpoch: <u64>`, the epoch at which this account will next owe rent, as u64
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
// Request
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenAccountsByOwner", "params": ["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F", {"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E"}]}' http://localhost:8899
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":[{"data":{"token":{"account":{"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}
|
||||||
|
```
|
||||||
|
|
||||||
|
### getTokenSupply
|
||||||
|
|
||||||
|
Returns the total supply of an SPL Token type.
|
||||||
|
|
||||||
|
#### Parameters:
|
||||||
|
|
||||||
|
- `<string>` - Pubkey of token Mint to query, as base-58 encoded string
|
||||||
|
- `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||||
|
|
||||||
|
#### Results:
|
||||||
|
|
||||||
|
- `RpcResponse<u64>` - RpcResponse JSON object with `value` field set to the total token supply
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
// Request
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenSupply", "params": ["3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E"]}' http://localhost:8899
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":100000,"id":1}
|
||||||
|
```
|
||||||
|
|
||||||
### getTransactionCount
|
### getTransactionCount
|
||||||
|
|
||||||
Returns the current Transaction count from the ledger
|
Returns the current Transaction count from the ledger
|
||||||
|
|
|
@ -16,7 +16,7 @@ lazy_static = "1.4.0"
|
||||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||||
solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
|
solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
|
||||||
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
||||||
spl-memo = { version = "1.0.4", features = ["skip-no-mangle"] }
|
spl-memo-v1-0 = { package = "spl-memo", version = "1.0.4", features = ["skip-no-mangle"] }
|
||||||
serde = "1.0.112"
|
serde = "1.0.112"
|
||||||
serde_derive = "1.0.103"
|
serde_derive = "1.0.103"
|
||||||
serde_json = "1.0.56"
|
serde_json = "1.0.56"
|
||||||
|
|
|
@ -7,7 +7,8 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref MEMO_PROGRAM_ID: Pubkey = Pubkey::from_str(&spl_memo::id().to_string()).unwrap();
|
static ref MEMO_PROGRAM_ID: Pubkey =
|
||||||
|
Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap();
|
||||||
static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
|
static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
|
||||||
let mut m = HashMap::new();
|
let mut m = HashMap::new();
|
||||||
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
|
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
|
||||||
|
|
Loading…
Reference in New Issue