Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit c769bcc

Browse files
mergify[bot]CriesofCarrotsTyera Eulberg
authored
Add getTokenLargestAccounts endpoint (bp #11322) (#11338)
* Add getTokenLargestAccounts endpoint (#11322) (cherry picked from commit d1b2e6c) * Rebase on v1.2 Co-authored-by: Tyera Eulberg <teulberg@gmail.com> Co-authored-by: Tyera Eulberg <tyera@solana.com>
1 parent f06a4c7 commit c769bcc

File tree

2 files changed

+137
-2
lines changed

2 files changed

+137
-2
lines changed

client/src/rpc_response.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,10 @@ pub struct RpcStakeActivation {
219219
pub active: u64,
220220
pub inactive: u64,
221221
}
222+
223+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
224+
#[serde(rename_all = "camelCase")]
225+
pub struct RpcTokenAccountBalance {
226+
pub address: String,
227+
pub amount: u64,
228+
}

core/src/rpc.rs

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,48 @@ impl JsonRpcRequestProcessor {
843843
new_response(&bank, supply)
844844
}
845845

846+
pub fn get_token_largest_accounts(
847+
&self,
848+
mint: &Pubkey,
849+
commitment: Option<CommitmentConfig>,
850+
) -> Result<RpcResponse<Vec<RpcTokenAccountBalance>>> {
851+
let bank = self.bank(commitment)?;
852+
let mint_account = bank.get_account(mint).ok_or_else(|| {
853+
Error::invalid_params("Invalid param: could not find mint".to_string())
854+
})?;
855+
if mint_account.owner != spl_token_id_v1_0() {
856+
return Err(Error::invalid_params(
857+
"Invalid param: not a v1.0 Token mint".to_string(),
858+
));
859+
}
860+
let filters = vec![
861+
// Filter on Mint address
862+
RpcFilterType::Memcmp(Memcmp {
863+
offset: 0,
864+
bytes: MemcmpEncodedBytes::Binary(mint.to_string()),
865+
encoding: None,
866+
}),
867+
// Filter on Token Account state
868+
RpcFilterType::DataSize(size_of::<TokenAccount>() as u64),
869+
];
870+
let mut token_balances: Vec<RpcTokenAccountBalance> =
871+
get_filtered_program_accounts(&bank, &mint_account.owner, filters)
872+
.map(|(address, account)| {
873+
let mut data = account.data.to_vec();
874+
let amount = spl_token_v1_0::state::unpack(&mut data)
875+
.map(|account: &mut TokenAccount| account.amount)
876+
.unwrap_or(0);
877+
RpcTokenAccountBalance {
878+
address: address.to_string(),
879+
amount,
880+
}
881+
})
882+
.collect();
883+
token_balances.sort_by(|a, b| a.amount.cmp(&b.amount).reverse());
884+
token_balances.truncate(NUM_LARGEST_ACCOUNTS);
885+
new_response(&bank, token_balances)
886+
}
887+
846888
pub fn get_token_accounts_by_owner(
847889
&self,
848890
owner: &Pubkey,
@@ -1315,6 +1357,14 @@ pub trait RpcSol {
13151357
commitment: Option<CommitmentConfig>,
13161358
) -> Result<RpcResponse<u64>>;
13171359

1360+
#[rpc(meta, name = "getTokenLargestAccounts")]
1361+
fn get_token_largest_accounts(
1362+
&self,
1363+
meta: Self::Metadata,
1364+
mint_str: String,
1365+
commitment: Option<CommitmentConfig>,
1366+
) -> Result<RpcResponse<Vec<RpcTokenAccountBalance>>>;
1367+
13181368
#[rpc(meta, name = "getTokenAccountsByOwner")]
13191369
fn get_token_accounts_by_owner(
13201370
&self,
@@ -1948,6 +1998,20 @@ impl RpcSol for RpcSolImpl {
19481998
meta.get_token_supply(&mint, commitment)
19491999
}
19502000

2001+
fn get_token_largest_accounts(
2002+
&self,
2003+
meta: Self::Metadata,
2004+
mint_str: String,
2005+
commitment: Option<CommitmentConfig>,
2006+
) -> Result<RpcResponse<Vec<RpcTokenAccountBalance>>> {
2007+
debug!(
2008+
"get_token_largest_accounts rpc request received: {:?}",
2009+
mint_str
2010+
);
2011+
let mint = verify_pubkey(mint_str)?;
2012+
meta.get_token_largest_accounts(&mint, commitment)
2013+
}
2014+
19512015
fn get_token_accounts_by_owner(
19522016
&self,
19532017
meta: Self::Metadata,
@@ -4433,7 +4497,7 @@ pub mod tests {
44334497
.expect("actual response deserialization");
44344498
assert!(result.get("error").is_some());
44354499

4436-
// Test non-existent Owner
4500+
// Test non-existent Delegate
44374501
let req = format!(
44384502
r#"{{
44394503
"jsonrpc":"2.0",
@@ -4444,11 +4508,75 @@ pub mod tests {
44444508
Pubkey::new_rand(),
44454509
spl_token_id_v1_0(),
44464510
);
4447-
let res = io.handle_request_sync(&req, meta);
4511+
let res = io.handle_request_sync(&req, meta.clone());
44484512
let result: Value = serde_json::from_str(&res.expect("actual response"))
44494513
.expect("actual response deserialization");
44504514
let accounts: Vec<RpcKeyedAccount> =
44514515
serde_json::from_value(result["result"]["value"].clone()).unwrap();
44524516
assert!(accounts.is_empty());
4517+
4518+
// Add new_mint, and another token account on new_mint with different balance
4519+
let mut mint_data = [0; size_of::<Mint>()];
4520+
let mint_state: &mut Mint =
4521+
spl_token_v1_0::state::unpack_unchecked(&mut mint_data).unwrap();
4522+
*mint_state = Mint {
4523+
owner: COption::Some(owner),
4524+
decimals: 2,
4525+
is_initialized: true,
4526+
};
4527+
let mint_account = Account {
4528+
lamports: 111,
4529+
data: mint_data.to_vec(),
4530+
owner: spl_token_id_v1_0(),
4531+
..Account::default()
4532+
};
4533+
bank.store_account(
4534+
&Pubkey::from_str(&new_mint.to_string()).unwrap(),
4535+
&mint_account,
4536+
);
4537+
let mut account_data = [0; size_of::<TokenAccount>()];
4538+
let account: &mut TokenAccount =
4539+
spl_token_v1_0::state::unpack_unchecked(&mut account_data).unwrap();
4540+
*account = TokenAccount {
4541+
mint: new_mint,
4542+
owner,
4543+
delegate: COption::Some(delegate),
4544+
amount: 10,
4545+
is_initialized: true,
4546+
is_native: false,
4547+
delegated_amount: 30,
4548+
};
4549+
let token_account = Account {
4550+
lamports: 111,
4551+
data: account_data.to_vec(),
4552+
owner: spl_token_id_v1_0(),
4553+
..Account::default()
4554+
};
4555+
let token_with_smaller_balance = Pubkey::new_rand();
4556+
bank.store_account(&token_with_smaller_balance, &token_account);
4557+
4558+
// Test largest token accounts
4559+
let req = format!(
4560+
r#"{{"jsonrpc":"2.0","id":1,"method":"getTokenLargestAccounts","params":["{}"]}}"#,
4561+
new_mint,
4562+
);
4563+
let res = io.handle_request_sync(&req, meta);
4564+
let result: Value = serde_json::from_str(&res.expect("actual response"))
4565+
.expect("actual response deserialization");
4566+
let largest_accounts: Vec<RpcTokenAccountBalance> =
4567+
serde_json::from_value(result["result"]["value"].clone()).unwrap();
4568+
assert_eq!(
4569+
largest_accounts,
4570+
vec![
4571+
RpcTokenAccountBalance {
4572+
address: token_with_different_mint_pubkey.to_string(),
4573+
amount: 42,
4574+
},
4575+
RpcTokenAccountBalance {
4576+
address: token_with_smaller_balance.to_string(),
4577+
amount: 10,
4578+
}
4579+
]
4580+
);
44534581
}
44544582
}

0 commit comments

Comments
 (0)