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

Commit d1b2e6c

Browse files
Add getTokenLargestAccounts endpoint (#11322)
1 parent 27a8879 commit d1b2e6c

File tree

2 files changed

+135
-2
lines changed

2 files changed

+135
-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: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,48 @@ impl JsonRpcRequestProcessor {
893893
Ok(new_response(&bank, supply))
894894
}
895895

896+
pub fn get_token_largest_accounts(
897+
&self,
898+
mint: &Pubkey,
899+
commitment: Option<CommitmentConfig>,
900+
) -> Result<RpcResponse<Vec<RpcTokenAccountBalance>>> {
901+
let bank = self.bank(commitment);
902+
let mint_account = bank.get_account(mint).ok_or_else(|| {
903+
Error::invalid_params("Invalid param: could not find mint".to_string())
904+
})?;
905+
if mint_account.owner != spl_token_id_v1_0() {
906+
return Err(Error::invalid_params(
907+
"Invalid param: not a v1.0 Token mint".to_string(),
908+
));
909+
}
910+
let filters = vec![
911+
// Filter on Mint address
912+
RpcFilterType::Memcmp(Memcmp {
913+
offset: 0,
914+
bytes: MemcmpEncodedBytes::Binary(mint.to_string()),
915+
encoding: None,
916+
}),
917+
// Filter on Token Account state
918+
RpcFilterType::DataSize(size_of::<TokenAccount>() as u64),
919+
];
920+
let mut token_balances: Vec<RpcTokenAccountBalance> =
921+
get_filtered_program_accounts(&bank, &mint_account.owner, filters)
922+
.map(|(address, account)| {
923+
let mut data = account.data.to_vec();
924+
let amount = TokenState::unpack(&mut data)
925+
.map(|account: &mut TokenAccount| account.amount)
926+
.unwrap_or(0);
927+
RpcTokenAccountBalance {
928+
address: address.to_string(),
929+
amount,
930+
}
931+
})
932+
.collect();
933+
token_balances.sort_by(|a, b| a.amount.cmp(&b.amount).reverse());
934+
token_balances.truncate(NUM_LARGEST_ACCOUNTS);
935+
Ok(new_response(&bank, token_balances))
936+
}
937+
896938
pub fn get_token_accounts_by_owner(
897939
&self,
898940
owner: &Pubkey,
@@ -1346,6 +1388,14 @@ pub trait RpcSol {
13461388
commitment: Option<CommitmentConfig>,
13471389
) -> Result<RpcResponse<u64>>;
13481390

1391+
#[rpc(meta, name = "getTokenLargestAccounts")]
1392+
fn get_token_largest_accounts(
1393+
&self,
1394+
meta: Self::Metadata,
1395+
mint_str: String,
1396+
commitment: Option<CommitmentConfig>,
1397+
) -> Result<RpcResponse<Vec<RpcTokenAccountBalance>>>;
1398+
13491399
#[rpc(meta, name = "getTokenAccountsByOwner")]
13501400
fn get_token_accounts_by_owner(
13511401
&self,
@@ -1981,6 +2031,20 @@ impl RpcSol for RpcSolImpl {
19812031
meta.get_token_supply(&mint, commitment)
19822032
}
19832033

2034+
fn get_token_largest_accounts(
2035+
&self,
2036+
meta: Self::Metadata,
2037+
mint_str: String,
2038+
commitment: Option<CommitmentConfig>,
2039+
) -> Result<RpcResponse<Vec<RpcTokenAccountBalance>>> {
2040+
debug!(
2041+
"get_token_largest_accounts rpc request received: {:?}",
2042+
mint_str
2043+
);
2044+
let mint = verify_pubkey(mint_str)?;
2045+
meta.get_token_largest_accounts(&mint, commitment)
2046+
}
2047+
19842048
fn get_token_accounts_by_owner(
19852049
&self,
19862050
meta: Self::Metadata,
@@ -4514,7 +4578,7 @@ pub mod tests {
45144578
.expect("actual response deserialization");
45154579
assert!(result.get("error").is_some());
45164580

4517-
// Test non-existent Owner
4581+
// Test non-existent Delegate
45184582
let req = format!(
45194583
r#"{{
45204584
"jsonrpc":"2.0",
@@ -4525,11 +4589,73 @@ pub mod tests {
45254589
Pubkey::new_rand(),
45264590
spl_token_id_v1_0(),
45274591
);
4528-
let res = io.handle_request_sync(&req, meta);
4592+
let res = io.handle_request_sync(&req, meta.clone());
45294593
let result: Value = serde_json::from_str(&res.expect("actual response"))
45304594
.expect("actual response deserialization");
45314595
let accounts: Vec<RpcKeyedAccount> =
45324596
serde_json::from_value(result["result"]["value"].clone()).unwrap();
45334597
assert!(accounts.is_empty());
4598+
4599+
// Add new_mint, and another token account on new_mint with different balance
4600+
let mut mint_data = [0; size_of::<Mint>()];
4601+
let mint_state: &mut Mint = TokenState::unpack_unchecked(&mut mint_data).unwrap();
4602+
*mint_state = Mint {
4603+
owner: COption::Some(owner),
4604+
decimals: 2,
4605+
is_initialized: true,
4606+
};
4607+
let mint_account = Account {
4608+
lamports: 111,
4609+
data: mint_data.to_vec(),
4610+
owner: spl_token_id_v1_0(),
4611+
..Account::default()
4612+
};
4613+
bank.store_account(
4614+
&Pubkey::from_str(&new_mint.to_string()).unwrap(),
4615+
&mint_account,
4616+
);
4617+
let mut account_data = [0; size_of::<TokenAccount>()];
4618+
let account: &mut TokenAccount = TokenState::unpack_unchecked(&mut account_data).unwrap();
4619+
*account = TokenAccount {
4620+
mint: new_mint,
4621+
owner,
4622+
delegate: COption::Some(delegate),
4623+
amount: 10,
4624+
is_initialized: true,
4625+
is_native: false,
4626+
delegated_amount: 30,
4627+
};
4628+
let token_account = Account {
4629+
lamports: 111,
4630+
data: account_data.to_vec(),
4631+
owner: spl_token_id_v1_0(),
4632+
..Account::default()
4633+
};
4634+
let token_with_smaller_balance = Pubkey::new_rand();
4635+
bank.store_account(&token_with_smaller_balance, &token_account);
4636+
4637+
// Test largest token accounts
4638+
let req = format!(
4639+
r#"{{"jsonrpc":"2.0","id":1,"method":"getTokenLargestAccounts","params":["{}"]}}"#,
4640+
new_mint,
4641+
);
4642+
let res = io.handle_request_sync(&req, meta);
4643+
let result: Value = serde_json::from_str(&res.expect("actual response"))
4644+
.expect("actual response deserialization");
4645+
let largest_accounts: Vec<RpcTokenAccountBalance> =
4646+
serde_json::from_value(result["result"]["value"].clone()).unwrap();
4647+
assert_eq!(
4648+
largest_accounts,
4649+
vec![
4650+
RpcTokenAccountBalance {
4651+
address: token_with_different_mint_pubkey.to_string(),
4652+
amount: 42,
4653+
},
4654+
RpcTokenAccountBalance {
4655+
address: token_with_smaller_balance.to_string(),
4656+
amount: 10,
4657+
}
4658+
]
4659+
);
45344660
}
45354661
}

0 commit comments

Comments
 (0)