1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
//! Vault-related GraphQL queries
use crate::client::Client;
use crate::error::Result;
use crate::types::{Vault, VaultAccount};
use serde::Deserialize;
/// Vault query builder for filtering vaults
#[derive(Debug, Default, Clone)]
pub struct VaultFilter {
chain_id: Option<u64>,
api_version: Option<String>,
v3: Option<bool>,
yearn: Option<bool>,
erc4626: Option<bool>,
vault_type: Option<i32>,
risk_level: Option<i32>,
addresses: Option<Vec<String>>,
}
impl VaultFilter {
/// Create a new filter
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Filter by chain ID
#[must_use]
pub fn chain_id(mut self, chain_id: u64) -> Self {
self.chain_id = Some(chain_id);
self
}
/// Filter by API version
#[must_use]
pub fn api_version(mut self, version: impl Into<String>) -> Self {
self.api_version = Some(version.into());
self
}
/// Filter v3 vaults only
#[must_use]
pub fn v3(mut self, v3: bool) -> Self {
self.v3 = Some(v3);
self
}
/// Filter Yearn vaults only
#[must_use]
pub fn yearn(mut self, yearn: bool) -> Self {
self.yearn = Some(yearn);
self
}
/// Filter ERC4626 compliant vaults
#[must_use]
pub fn erc4626(mut self, erc4626: bool) -> Self {
self.erc4626 = Some(erc4626);
self
}
/// Filter by vault type (0 = default, 1 = automated, 2 = multi-strategy)
#[must_use]
pub fn vault_type(mut self, vault_type: i32) -> Self {
self.vault_type = Some(vault_type);
self
}
/// Filter by risk level (1-5)
#[must_use]
pub fn risk_level(mut self, level: i32) -> Self {
self.risk_level = Some(level);
self
}
/// Filter by specific addresses
#[must_use]
pub fn addresses(mut self, addresses: Vec<String>) -> Self {
self.addresses = Some(addresses);
self
}
/// Build the GraphQL arguments string
fn build_args(&self) -> String {
let mut args = Vec::new();
if let Some(chain_id) = self.chain_id {
args.push(format!("chainId: {chain_id}"));
}
if let Some(ref version) = self.api_version {
args.push(format!("apiVersion: \"{version}\""));
}
if let Some(v3) = self.v3 {
args.push(format!("v3: {v3}"));
}
if let Some(yearn) = self.yearn {
args.push(format!("yearn: {yearn}"));
}
if let Some(erc4626) = self.erc4626 {
args.push(format!("erc4626: {erc4626}"));
}
if let Some(vault_type) = self.vault_type {
args.push(format!("vaultType: {vault_type}"));
}
if let Some(risk_level) = self.risk_level {
args.push(format!("riskLevel: {risk_level}"));
}
if let Some(ref addresses) = self.addresses {
let addr_str: Vec<String> = addresses.iter().map(|a| format!("\"{a}\"")).collect();
args.push(format!("addresses: [{}]", addr_str.join(", ")));
}
if args.is_empty() {
String::new()
} else {
format!("({})", args.join(", "))
}
}
}
/// Vaults API
pub struct VaultsApi<'a> {
client: &'a Client,
}
impl<'a> VaultsApi<'a> {
/// Create a new vaults API instance
#[must_use]
pub fn new(client: &'a Client) -> Self {
Self { client }
}
/// Get all vaults (with optional filter)
///
/// # Example
///
/// ```no_run
/// # async fn example() -> ykong::error::Result<()> {
/// use ykong::Client;
///
/// let client = Client::new()?;
/// let vaults = client.vaults().list(None).await?;
/// println!("Found {} vaults", vaults.len());
/// # Ok(())
/// # }
/// ```
pub async fn list(&self, filter: Option<VaultFilter>) -> Result<Vec<Vault>> {
let args = filter.unwrap_or_default().build_args();
let query = format!(
r"{{
vaults{args} {{
address
name
symbol
chainId
apiVersion
decimals
v3
yearn
erc4626
isShutdown
emergencyShutdown
vaultType
token
totalAssets
totalSupply
pricePerShare
depositLimit
availableDepositLimit
managementFee
performanceFee
governance
guardian
management
rewards
registry
inceptTime
inceptBlock
lastReport
activation
projectId
projectName
withdrawalQueue
strategies
tvl {{ close blockTime }}
apy {{ net weeklyNet monthlyNet inceptionNet grossApr blockTime }}
fees {{ managementFee performanceFee }}
risk {{ riskLevel }}
meta {{ displayName description category isHidden isBoosted }}
asset {{ address name symbol decimals }}
}}
}}"
);
#[derive(Deserialize)]
struct Response {
vaults: Vec<Vault>,
}
let response: Response = self.client.query(&query).await?;
Ok(response.vaults)
}
/// Get vaults for a specific chain
///
/// # Example
///
/// ```no_run
/// # async fn example() -> ykong::error::Result<()> {
/// use ykong::Client;
///
/// let client = Client::new()?;
/// // Get Ethereum mainnet vaults
/// let vaults = client.vaults().by_chain(1).await?;
/// # Ok(())
/// # }
/// ```
pub async fn by_chain(&self, chain_id: u64) -> Result<Vec<Vault>> {
self.list(Some(VaultFilter::new().chain_id(chain_id))).await
}
/// Get only v3 vaults
pub async fn v3_vaults(&self) -> Result<Vec<Vault>> {
self.list(Some(VaultFilter::new().v3(true))).await
}
/// Get only Yearn official vaults
pub async fn yearn_vaults(&self) -> Result<Vec<Vault>> {
self.list(Some(VaultFilter::new().yearn(true))).await
}
/// Get a single vault by address and chain
///
/// # Example
///
/// ```no_run
/// # async fn example() -> ykong::error::Result<()> {
/// use ykong::Client;
///
/// let client = Client::new()?;
/// let vault = client.vaults().get(1, "0x...").await?;
/// # Ok(())
/// # }
/// ```
pub async fn get(&self, chain_id: u64, address: &str) -> Result<Option<Vault>> {
let query = format!(
r#"{{
vault(chainId: {chain_id}, address: "{address}") {{
address
name
symbol
chainId
apiVersion
decimals
v3
yearn
erc4626
isShutdown
emergencyShutdown
vaultType
token
totalAssets
totalSupply
pricePerShare
depositLimit
availableDepositLimit
managementFee
performanceFee
governance
guardian
management
rewards
registry
inceptTime
inceptBlock
lastReport
activation
projectId
projectName
withdrawalQueue
strategies
tvl {{ close blockTime }}
apy {{ net weeklyNet monthlyNet inceptionNet grossApr blockTime }}
fees {{ managementFee performanceFee }}
risk {{ riskLevel }}
meta {{ displayName description category isHidden isBoosted }}
asset {{ address name symbol decimals }}
}}
}}"#
);
#[derive(Deserialize)]
struct Response {
vault: Option<Vault>,
}
let response: Response = self.client.query(&query).await?;
Ok(response.vault)
}
/// Get vault accounts (user positions) for an address
///
/// **DEPRECATED:** The Kong API removed user position queries in 2024.
/// The old `vaultAccounts(chainId, address)` endpoint no longer exists.
///
/// To get user vault balances, you must query on-chain via vault `balanceOf(user)` calls.
/// Consider using Alchemy, Moralis, or direct RPC calls instead.
#[deprecated(
since = "0.1.2",
note = "Kong API removed user position queries. Use on-chain balanceOf() calls instead."
)]
pub async fn accounts(&self, _chain_id: u64, _address: &str) -> Result<Vec<VaultAccount>> {
// The Kong API changed in 2024:
// - Old: vaultAccounts(chainId, address) returned user positions with balances
// - New: vaultAccounts(chainId, vault) returns AccountRole (role holders, not depositors)
// - accountVaults(chainId, account) returns vaults where user has ROLES, not deposits
//
// User position/balance data must now be fetched on-chain via balanceOf() calls.
Err(crate::Error::Domain(
crate::error::DomainError::ApiEndpointRemoved {
endpoint: "vaultAccounts".to_string(),
alternative: "Use on-chain balanceOf() calls or ethcli portfolio command"
.to_string(),
},
))
}
}