Skip to content

Commit

Permalink
feat: GetTokenAccounts added
Browse files Browse the repository at this point in the history
  • Loading branch information
anamansari062 committed Apr 30, 2024
1 parent 32a4aad commit 42f0013
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 2 deletions.
7 changes: 6 additions & 1 deletion src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::error::Result;
use crate::request_handler::RequestHandler;
use crate::types::types::{RpcRequest, RpcResponse};
use crate::types::{
Asset, AssetList, AssetProof, GetAsset, GetAssetBatch, GetAssetProof, GetAssetProofBatch, GetAssetSignatures, GetAssetsByAuthority, GetAssetsByCreator, GetAssetsByGroup, GetAssetsByOwner, SearchAssets, TransactionSignatureList
Asset, AssetList, AssetProof, GetAsset, GetAssetBatch, GetAssetProof, GetAssetProofBatch, GetAssetSignatures, GetAssetsByAuthority, GetAssetsByCreator, GetAssetsByGroup, GetAssetsByOwner, GetTokenAccounts, SearchAssets, TokenAccountsList, TransactionSignatureList
};

use reqwest::{Client, Method, Url};
Expand Down Expand Up @@ -98,4 +98,9 @@ impl RpcClient {
pub async fn get_asset_signatures(&self, request: GetAssetSignatures) -> Result<TransactionSignatureList> {
self.post_rpc_request("getSignaturesForAsset", request).await
}

/// Gets transaction signatures for a given asset
pub async fn get_token_accounts(&self, request: GetTokenAccounts) -> Result<TokenAccountsList> {
self.post_rpc_request("getTokenAccounts", request).await
}
}
49 changes: 49 additions & 0 deletions src/types/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,21 @@ pub struct GetAssetSignatures {
pub sort_direction: Option<AssetSortDirection>,
}

#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GetTokenAccounts {
pub owner: Option<String>,
pub mint: Option<String>,
pub limit: Option<u32>,
pub page: Option<u32>,
pub before: Option<String>,
pub after: Option<String>,
#[serde(default, alias = "displayOptions")]
pub options: Option<DisplayOptions>,
#[serde(default)]
pub cursor: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AssetSorting {
Expand Down Expand Up @@ -271,6 +286,22 @@ pub struct TransactionSignatureList {
pub items: Vec<(String, String)>,
}

#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(default)]
pub struct TokenAccountsList {
pub total: u32,
pub limit: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<String>,
pub token_accounts: Vec<TokenAccount>,
}

#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(default)]
pub struct AssetError {
Expand Down Expand Up @@ -658,3 +689,21 @@ pub struct NotFilter {
// #[serde(default)]
// pub before: Option<DateTime<Utc>>,
// }

#[derive(Serialize, Deserialize, Debug)]
pub struct TokenAccount {
pub address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delegate: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delegated_amount: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_extensions: Option<Value>,
pub frozen: bool,
}
2 changes: 1 addition & 1 deletion tests/rpc/test_get_asset_signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async fn test_get_asset_signatures_success() {
let mut server: Server = Server::new_with_opts_async(mockito::ServerOpts::default()).await;
let url: String = server.url();

let mock_response: ApiResponse<Option<Asset>> = ApiResponse {
let mock_response: ApiResponse<TransactionSignatureList> = ApiResponse {
jsonrpc: "2.0".to_string(),
result: Some(TransactionSignatureList {
total: 1,
Expand Down
134 changes: 134 additions & 0 deletions tests/rpc/test_get_token_accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use std::sync::Arc;

use helius_sdk::client::Helius;
use helius_sdk::config::Config;
use helius_sdk::error::HeliusError;
use helius_sdk::rpc_client::RpcClient;
use helius_sdk::types::*;

use mockito::{self, Server};
use reqwest::Client;
use serde_json::Value;

#[tokio::test]
async fn test_get_token_accounts_success() {
let mut server: Server = Server::new_with_opts_async(mockito::ServerOpts::default()).await;
let url: String = server.url();

let mock_response: ApiResponse<TokenAccountsList> = ApiResponse {
jsonrpc: "2.0".to_string(),
result: Some(TokenAccountsList {
total: 1,
limit: 1,
page: Some(
1,
),
cursor: None,
before: None,
after: None,
token_accounts: [
TokenAccount {
address: "FDxksmT4hRCpS2Wr5NF2i3uuYGeTz6pSyychb44gDzL".to_string(),
mint: Some(
"2v5byxxWVeAcrN39fznVrhuWZuoPkjpzGuqJHemyqP1x".to_string(),
),
owner: Some(
"GdNh12yVy5Lsew9WXVCV5ErgK5SpmsBJkcti5jVtPB7o".to_string(),
),
amount: Some(
1,
),
delegate: None,
delegated_amount: Some(
0,
),
token_extensions: None,
frozen: true,
},
],
}),
id: "1".to_string(),
};

server
.mock("POST", "/?api-key=fake_api_key")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&mock_response).unwrap())
.create();

let config: Arc<Config> = Arc::new(Config {
api_key: "fake_api_key".to_string(),
cluster: Cluster::Devnet,
endpoints: HeliusEndpoints {
api: url.to_string(),
rpc: url.to_string(),
},
});

let client: Client = Client::new();
let rpc_client: Arc<RpcClient> = Arc::new(RpcClient::new(Arc::new(client.clone()), Arc::clone(&config)).unwrap());
let helius: Helius = Helius {
config,
client,
rpc_client,
};

let request = GetTokenAccounts {
owner: Some("GdNh12yVy5Lsew9WXVCV5ErgK5SpmsBJkcti5jVtPB7o".to_string()),
page: Some(1),
limit: Some(1),
..Default::default()
};

let response: Result<TokenAccountsList, HeliusError> = helius.rpc().get_token_accounts(request).await;
assert!(response.is_ok(), "API call failed with error: {:?}", response.err());

let token_accounts: TokenAccountsList = response.unwrap();
assert!(token_accounts.items.is_some(), "No token account returned when one was expected");

assert_eq!(
token_accounts.token_accounts[0].address, "FDxksmT4hRCpS2Wr5NF2i3uuYGeTz6pSyychb44gDzL",
"signature does not match expected value"
);
}

#[tokio::test]
async fn test_get_token_accounts_failure() {
let mut server: Server = Server::new_with_opts_async(mockito::ServerOpts::default()).await;
let url: String = server.url();

server
.mock("POST", "/?api-key=fake_api_key")
.with_status(500)
.with_header("content-type", "application/json")
.with_body(r#"{"error": "Internal Server Error"}"#)
.create();

let config: Arc<Config> = Arc::new(Config {
api_key: "fake_api_key".to_string(),
cluster: Cluster::Devnet,
endpoints: HeliusEndpoints {
api: url.to_string(),
rpc: url.to_string(),
},
});

let client: Client = Client::new();
let rpc_client: Arc<RpcClient> = Arc::new(RpcClient::new(Arc::new(client.clone()), Arc::clone(&config)).unwrap());
let helius: Helius = Helius {
config,
client,
rpc_client,
};

let request = GetTokenAccounts {
owner: Some("GdNh12yVy5Lsew9WXVCV5ErgK5SpmsBJkcti5jVtPB7o".to_string()),
page: Some(1),
limit: Some(1),
..Default::default()
};

let response: Result<TokenAccountsList, HeliusError> = helius.rpc().get_token_accounts(request).await;
assert!(response.is_err(), "Expected an error but got success");
}

0 comments on commit 42f0013

Please sign in to comment.