ykong 0.1.4

Rust client for Yearn's Kong GraphQL API
Documentation
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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
//! Type definitions for Kong API responses

use serde::{Deserialize, Deserializer, Serialize};

/// Helper module for deserializing values that can be either strings or integers
mod string_or_int {
    use serde::{Deserialize, Deserializer};

    /// Deserialize a value that might be a string or an integer into a String
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(untagged)]
        enum StringOrInt {
            String(String),
            Int(i64),
            UInt(u64),
            Float(f64),
        }

        Ok(
            Option::<StringOrInt>::deserialize(deserializer)?.map(|v| match v {
                StringOrInt::String(s) => s,
                StringOrInt::Int(i) => i.to_string(),
                StringOrInt::UInt(u) => u.to_string(),
                StringOrInt::Float(f) => f.to_string(),
            }),
        )
    }
}

/// Helper to deserialize flexible numeric fields
fn deserialize_flexible_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
    D: Deserializer<'de>,
{
    string_or_int::deserialize(deserializer)
}

/// Deserialize a value that can be either a string or u64 into u64
fn deserialize_string_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum StringOrU64 {
        String(String),
        U64(u64),
    }

    match StringOrU64::deserialize(deserializer)? {
        StringOrU64::String(s) => s.parse::<u64>().map_err(serde::de::Error::custom),
        StringOrU64::U64(n) => Ok(n),
    }
}

/// Deserialize a value that can be either a string or u64 into Option<u64>
fn deserialize_optional_string_or_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum StringOrU64 {
        String(String),
        U64(u64),
        Null,
    }

    match Option::<StringOrU64>::deserialize(deserializer)? {
        Some(StringOrU64::String(s)) => {
            if s.is_empty() {
                Ok(None)
            } else {
                s.parse::<u64>().map(Some).map_err(serde::de::Error::custom)
            }
        }
        Some(StringOrU64::U64(n)) => Ok(Some(n)),
        Some(StringOrU64::Null) | None => Ok(None),
    }
}

/// A Yearn vault
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Vault {
    /// Vault contract address
    pub address: String,
    /// Vault name
    pub name: Option<String>,
    /// Vault symbol
    pub symbol: Option<String>,
    /// Chain ID (1 = Ethereum, 137 = Polygon, etc.)
    pub chain_id: u64,
    /// API version (e.g., "3.0.4" for v3 vaults)
    pub api_version: Option<String>,
    /// Vault decimals (can be string or int from API)
    #[serde(default, deserialize_with = "deserialize_flexible_string")]
    pub decimals: Option<String>,
    /// Whether this is a v3 vault
    pub v3: Option<bool>,
    /// Whether this is a Yearn vault
    pub yearn: Option<bool>,
    /// Whether the vault is ERC4626 compliant
    pub erc4626: Option<bool>,
    /// Whether the vault is shutdown
    pub is_shutdown: Option<bool>,
    /// Emergency shutdown status
    pub emergency_shutdown: Option<bool>,
    /// Vault type (0 = default, 1 = automated, 2 = multi-strategy)
    pub vault_type: Option<i32>,
    /// Underlying asset token address
    pub token: Option<String>,
    /// Total assets in the vault (raw)
    #[serde(default)]
    pub total_assets: Option<String>,
    /// Total supply of vault shares (raw)
    #[serde(default)]
    pub total_supply: Option<String>,
    /// Price per share (raw)
    #[serde(default)]
    pub price_per_share: Option<String>,
    /// Deposit limit (raw)
    #[serde(default)]
    pub deposit_limit: Option<String>,
    /// Available deposit limit (raw)
    #[serde(default)]
    pub available_deposit_limit: Option<String>,
    /// Management fee (basis points)
    #[serde(default)]
    pub management_fee: Option<String>,
    /// Performance fee (basis points)
    #[serde(default)]
    pub performance_fee: Option<String>,
    /// Governance address
    pub governance: Option<String>,
    /// Guardian address
    pub guardian: Option<String>,
    /// Management address
    pub management: Option<String>,
    /// Rewards address
    pub rewards: Option<String>,
    /// Registry address
    pub registry: Option<String>,
    /// Inception timestamp (returned as string from API)
    #[serde(default)]
    pub incept_time: Option<String>,
    /// Inception block (returned as string from API)
    #[serde(default)]
    pub incept_block: Option<String>,
    /// Last report timestamp (returned as string from API)
    #[serde(default)]
    pub last_report: Option<String>,
    /// Activation timestamp (returned as string from API)
    #[serde(default)]
    pub activation: Option<String>,
    /// Project ID
    pub project_id: Option<String>,
    /// Project name
    pub project_name: Option<String>,
    /// Withdrawal queue
    #[serde(default)]
    pub withdrawal_queue: Option<Vec<String>>,
    /// Strategy addresses
    #[serde(default)]
    pub strategies: Option<Vec<String>>,
    /// TVL data
    pub tvl: Option<SparklinePoint>,
    /// APY data
    pub apy: Option<Apy>,
    /// Fee structure
    pub fees: Option<Fees>,
    /// Risk score
    pub risk: Option<RiskScore>,
    /// Vault metadata
    pub meta: Option<VaultMeta>,
    /// Underlying asset info
    pub asset: Option<Erc20>,
}

/// A Yearn strategy
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Strategy {
    /// Strategy contract address
    pub address: String,
    /// Strategy name
    pub name: Option<String>,
    /// Chain ID
    pub chain_id: u64,
    /// API version
    pub api_version: Option<String>,
    /// Vault address this strategy belongs to
    pub vault: Option<String>,
    /// Whether this is a v3 strategy
    pub v3: Option<bool>,
    /// Activation timestamp
    #[serde(default)]
    pub activation: Option<u64>,
    /// Inception timestamp
    #[serde(default)]
    pub incept_time: Option<u64>,
    /// Inception block
    #[serde(default)]
    pub incept_block: Option<u64>,
    /// Last report timestamp
    #[serde(default)]
    pub last_report: Option<u64>,
    /// Total debt
    #[serde(default)]
    pub total_debt: Option<String>,
    /// Total gain
    #[serde(default)]
    pub total_gain: Option<String>,
    /// Total loss
    #[serde(default)]
    pub total_loss: Option<String>,
    /// Performance fee
    #[serde(default)]
    pub performance_fee: Option<String>,
    /// Debt ratio
    #[serde(default)]
    pub debt_ratio: Option<String>,
    /// Estimated total assets
    #[serde(default)]
    pub estimated_total_assets: Option<String>,
    /// Whether the strategy is active
    pub is_active: Option<bool>,
    /// Whether the strategy is shutdown
    pub is_shutdown: Option<bool>,
    /// Keeper address
    pub keeper: Option<String>,
    /// Strategist address
    pub strategist: Option<String>,
    /// Risk score
    pub risk: Option<RiskScore>,
    /// APY data
    pub apy: Option<Apy>,
    /// TVL data
    pub tvl: Option<SparklinePoint>,
}

/// APY (Annual Percentage Yield) data
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Apy {
    /// Net APY (annualized)
    pub net: Option<f64>,
    /// Weekly net APY
    pub weekly_net: Option<f64>,
    /// Monthly net APY
    pub monthly_net: Option<f64>,
    /// Inception net APY
    pub inception_net: Option<f64>,
    /// Gross APR
    pub gross_apr: Option<f64>,
    /// Price per share at measurement
    #[serde(default)]
    pub price_per_share: Option<String>,
    /// Block number of measurement (returned as string from API)
    pub block_number: Option<String>,
    /// Block timestamp (returned as string from API)
    pub block_time: Option<String>,
}

/// TVL sparkline point
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SparklinePoint {
    /// Value in USD
    pub close: Option<f64>,
    /// Block number (returned as string from API)
    pub block_number: Option<String>,
    /// Block timestamp (returned as string from API)
    pub block_time: Option<String>,
}

/// Fee structure
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Fees {
    /// Management fee (annual, basis points)
    pub management_fee: Option<f64>,
    /// Performance fee (basis points)
    pub performance_fee: Option<f64>,
}

/// Risk score
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RiskScore {
    /// Overall risk level (1-5, lower is safer)
    pub risk_level: Option<i32>,
}

/// Vault metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VaultMeta {
    /// Display name
    pub display_name: Option<String>,
    /// Description
    pub description: Option<String>,
    /// Category
    pub category: Option<String>,
    /// Whether the vault is hidden
    pub is_hidden: Option<bool>,
    /// Whether the vault is boosted
    pub is_boosted: Option<bool>,
}

/// ERC20 token info
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Erc20 {
    /// Token address
    pub address: String,
    /// Token name
    pub name: Option<String>,
    /// Token symbol
    pub symbol: Option<String>,
    /// Token decimals (can be string or int from API)
    #[serde(default, deserialize_with = "deserialize_flexible_string")]
    pub decimals: Option<String>,
}

/// Token price info from Kong API
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Price {
    /// Token address
    pub address: String,
    /// Chain ID
    pub chain_id: u64,
    /// Price in USD
    pub price_usd: f64,
    /// Price source (e.g., "defillama", "coingecko")
    pub price_source: String,
    /// Block number
    #[serde(deserialize_with = "deserialize_string_or_u64")]
    pub block_number: u64,
    /// Timestamp
    #[serde(deserialize_with = "deserialize_string_or_u64")]
    pub timestamp: u64,
}

/// TVL data point
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Tvl {
    /// Chain ID
    pub chain_id: u64,
    /// Address (vault or strategy)
    pub address: String,
    /// TVL value in USD
    pub value: f64,
    /// Price in USD at the time
    pub price_usd: Option<f64>,
    /// Price source
    pub price_source: String,
    /// Period (day, week, month)
    pub period: String,
    /// Block number
    #[serde(deserialize_with = "deserialize_string_or_u64")]
    pub block_number: u64,
    /// Timestamp
    #[serde(default, deserialize_with = "deserialize_optional_string_or_u64")]
    pub time: Option<u64>,
}

/// Vault report (harvest event)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VaultReport {
    /// Chain ID
    pub chain_id: u64,
    /// Vault address
    pub address: String,
    /// Event name
    pub event_name: String,
    /// Strategy address
    pub strategy: String,
    /// Gain amount (raw)
    pub gain: String,
    /// Loss amount (raw)
    pub loss: String,
    /// Debt paid (raw)
    pub debt_paid: Option<String>,
    /// Total gain (raw)
    pub total_gain: Option<String>,
    /// Total loss (raw)
    pub total_loss: Option<String>,
    /// Total debt (raw)
    pub total_debt: Option<String>,
    /// Debt added (raw)
    pub debt_added: Option<String>,
    /// Debt ratio
    pub debt_ratio: Option<String>,
    /// Current debt (raw)
    pub current_debt: Option<String>,
    /// Protocol fees (raw)
    pub protocol_fees: Option<String>,
    /// Total fees (raw)
    pub total_fees: Option<String>,
    /// Total refunds (raw)
    pub total_refunds: Option<String>,
    /// Gain in USD
    pub gain_usd: Option<f64>,
    /// Loss in USD
    pub loss_usd: Option<f64>,
    /// Debt paid in USD
    pub debt_paid_usd: Option<f64>,
    /// Total gain in USD
    pub total_gain_usd: Option<f64>,
    /// Total loss in USD
    pub total_loss_usd: Option<f64>,
    /// Total debt in USD
    pub total_debt_usd: Option<f64>,
    /// Debt added in USD
    pub debt_added_usd: Option<f64>,
    /// Current debt in USD
    pub current_debt_usd: Option<f64>,
    /// Protocol fees in USD
    pub protocol_fees_usd: Option<f64>,
    /// Total fees in USD
    pub total_fees_usd: Option<f64>,
    /// Total refunds in USD
    pub total_refunds_usd: Option<f64>,
    /// Price in USD at report time
    pub price_usd: Option<f64>,
    /// Price source
    pub price_source: Option<String>,
    /// APR data
    pub apr: Option<ReportApr>,
    /// Block number
    pub block_number: u64,
    /// Block timestamp
    pub block_time: u64,
    /// Log index
    pub log_index: u64,
    /// Transaction hash
    pub transaction_hash: String,
}

/// Strategy report (harvest event)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StrategyReport {
    /// Chain ID
    pub chain_id: u64,
    /// Strategy address
    pub address: String,
    /// Event name
    pub event_name: String,
    /// Profit amount (raw)
    pub profit: String,
    /// Loss amount (raw)
    pub loss: String,
    /// Debt payment (raw)
    pub debt_payment: Option<String>,
    /// Debt outstanding (raw)
    pub debt_outstanding: Option<String>,
    /// Protocol fees (raw)
    pub protocol_fees: Option<String>,
    /// Performance fees (raw)
    pub performance_fees: Option<String>,
    /// APR data
    pub apr: Option<ReportApr>,
    /// Profit in USD
    pub profit_usd: Option<f64>,
    /// Loss in USD
    pub loss_usd: Option<f64>,
    /// Debt payment in USD
    pub debt_payment_usd: Option<f64>,
    /// Debt outstanding in USD
    pub debt_outstanding_usd: Option<f64>,
    /// Protocol fees in USD
    pub protocol_fees_usd: Option<f64>,
    /// Performance fees in USD
    pub performance_fees_usd: Option<f64>,
    /// Price in USD at report time
    pub price_usd: Option<f64>,
    /// Price source
    pub price_source: Option<String>,
    /// Block number
    pub block_number: u64,
    /// Block timestamp
    pub block_time: u64,
    /// Log index
    pub log_index: u64,
    /// Transaction hash
    pub transaction_hash: String,
}

/// APR data from a report
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReportApr {
    /// Gross APR
    pub gross: Option<f64>,
    /// Net APR
    pub net: Option<f64>,
    /// Forward APR
    pub forward: Option<f64>,
}

/// TVL timeseries entry (legacy format)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TvlEntry {
    /// Address (vault or strategy)
    pub address: Option<String>,
    /// Chain ID
    pub chain_id: Option<u64>,
    /// TVL in USD
    pub tvl: Option<f64>,
    /// Block number
    pub block_number: Option<u64>,
    /// Block timestamp
    pub block_time: Option<u64>,
    /// Period type (day, week, month)
    pub period: Option<String>,
}

/// Vault account (user position)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VaultAccount {
    /// Account address
    pub address: String,
    /// Vault address
    pub vault: String,
    /// Chain ID
    pub chain_id: u64,
    /// Share balance (raw)
    #[serde(default)]
    pub balance: Option<String>,
    /// Deposit amount (raw)
    #[serde(default)]
    pub deposits: Option<String>,
    /// Withdrawal amount (raw)
    #[serde(default)]
    pub withdrawals: Option<String>,
    /// Profit/loss (raw)
    #[serde(default)]
    pub profit: Option<String>,
}

/// GraphQL response wrapper
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphQLResponse<T> {
    /// Response data
    pub data: Option<T>,
    /// GraphQL errors
    pub errors: Option<Vec<GraphQLError>>,
}

/// GraphQL error
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphQLError {
    /// Error message
    pub message: String,
    /// Error locations
    pub locations: Option<Vec<GraphQLLocation>>,
    /// Error extensions
    pub extensions: Option<serde_json::Value>,
}

/// GraphQL error location
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphQLLocation {
    /// Line number
    pub line: u32,
    /// Column number
    pub column: u32,
}