Skip to main content

vaea_flash_sdk/
types.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4// ═══════════════════════════════════════════════════════════
5//  Config
6// ═══════════════════════════════════════════════════════════
7
8#[derive(Debug, Clone)]
9pub struct VaeaConfig {
10    pub api_url: String,
11    pub source: Source,
12}
13
14impl Default for VaeaConfig {
15    fn default() -> Self {
16        Self {
17            api_url: "https://api.vaea.fi".to_string(),
18            source: Source::Sdk,
19        }
20    }
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24#[serde(rename_all = "lowercase")]
25pub enum Source {
26    Sdk,
27    Ui,
28    Protocol,
29}
30
31impl fmt::Display for Source {
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        match self {
34            Source::Sdk => write!(f, "sdk"),
35            Source::Ui => write!(f, "ui"),
36            Source::Protocol => write!(f, "protocol"),
37        }
38    }
39}
40
41// ═══════════════════════════════════════════════════════════
42//  Error types
43// ═══════════════════════════════════════════════════════════
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub enum VaeaErrorCode {
47    InsufficientLiquidity,
48    TokenNotSupported,
49    SlippageExceeded,
50    FeeTooHigh,
51    RepayFailed,
52    TxExpired,
53    SourceUnavailable,
54    ProgramPaused,
55    InvalidAmount,
56    InsufficientSolForFee,
57    ApiError,
58    NetworkError,
59}
60
61impl fmt::Display for VaeaErrorCode {
62    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63        match self {
64            VaeaErrorCode::InsufficientLiquidity => write!(f, "INSUFFICIENT_LIQUIDITY"),
65            VaeaErrorCode::TokenNotSupported => write!(f, "TOKEN_NOT_SUPPORTED"),
66            VaeaErrorCode::SlippageExceeded => write!(f, "SLIPPAGE_EXCEEDED"),
67            VaeaErrorCode::FeeTooHigh => write!(f, "FEE_TOO_HIGH"),
68            VaeaErrorCode::RepayFailed => write!(f, "REPAY_FAILED"),
69            VaeaErrorCode::TxExpired => write!(f, "TX_EXPIRED"),
70            VaeaErrorCode::SourceUnavailable => write!(f, "SOURCE_UNAVAILABLE"),
71            VaeaErrorCode::ProgramPaused => write!(f, "PROGRAM_PAUSED"),
72            VaeaErrorCode::InvalidAmount => write!(f, "INVALID_AMOUNT"),
73            VaeaErrorCode::InsufficientSolForFee => write!(f, "INSUFFICIENT_SOL_FOR_FEE"),
74            VaeaErrorCode::ApiError => write!(f, "API_ERROR"),
75            VaeaErrorCode::NetworkError => write!(f, "NETWORK_ERROR"),
76        }
77    }
78}
79
80// ═══════════════════════════════════════════════════════════
81//  Capacity types
82// ═══════════════════════════════════════════════════════════
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct CapacityResponse {
86    pub updated_at: u64,
87    pub tokens: Vec<TokenCapacity>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct TokenCapacity {
92    pub symbol: String,
93    pub mint: String,
94    pub name: String,
95    pub decimals: u8,
96    pub route_type: String,
97    pub source_protocol: String,
98    pub max_amount: f64,
99    pub max_amount_usd: f64,
100    pub fee_sdk: FeeInfo,
101    pub fee_ui: FeeInfo,
102    pub status: String,
103    pub updated_at: u64,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct FeeInfo {
108    pub bps: u16,
109    pub pct: f64,
110    pub total_pct: f64,
111}
112
113// ═══════════════════════════════════════════════════════════
114//  Quote types
115// ═══════════════════════════════════════════════════════════
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct QuoteResponse {
119    pub token: String,
120    pub mint: String,
121    pub amount_requested: f64,
122    pub source: String,
123    pub route: RouteQuote,
124    pub fee_breakdown: FeeBreakdown,
125    pub price_impact: f64,
126    pub valid_until: u64,
127    pub valid_for_slots: u64,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct RouteQuote {
132    #[serde(rename = "type")]
133    pub route_type: String,
134    pub steps: Vec<RouteStep>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct RouteStep {
139    pub action: String,
140    pub protocol: String,
141    pub token: String,
142    pub amount: f64,
143    pub expected_output: Option<f64>,
144    pub price_impact: Option<f64>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct FeeBreakdown {
149    pub source_fee: f64,
150    pub vaea_fee: f64,
151    pub total_fee_sol: f64,
152    pub total_fee_usd: f64,
153    pub total_fee_pct: f64,
154}
155
156// ═══════════════════════════════════════════════════════════
157//  Build types
158// ═══════════════════════════════════════════════════════════
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct BuildRequest {
162    pub token: String,
163    pub amount: f64,
164    pub user_pubkey: String,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub source: Option<String>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub slippage_bps: Option<u16>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub max_fee_bps: Option<u16>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct ApiInstructionData {
175    pub program_id: String,
176    pub data: String,
177    pub accounts: Vec<ApiAccountMeta>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ApiAccountMeta {
182    pub pubkey: String,
183    pub is_signer: bool,
184    pub is_writable: bool,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct BuildResponse {
189    pub prefix_instructions: Vec<ApiInstructionData>,
190    pub suffix_instructions: Vec<ApiInstructionData>,
191    pub lookup_tables: Vec<String>,
192    pub estimated_fee_lamports: u64,
193    pub valid_for_slots: u64,
194}
195
196// ═══════════════════════════════════════════════════════════
197//  Health types
198// ═══════════════════════════════════════════════════════════
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct HealthResponse {
202    pub status: String,
203    pub timestamp: u64,
204    pub components: serde_json::Value,
205    pub sources: serde_json::Value,
206}
207
208// ═══════════════════════════════════════════════════════════
209//  Borrow params
210// ═══════════════════════════════════════════════════════════
211
212pub struct BorrowParams {
213    pub token: String,
214    pub amount: f64,
215    pub instructions: Vec<solana_sdk::instruction::Instruction>,
216    pub slippage_bps: Option<u16>,
217    pub max_fee_bps: Option<u16>,
218}
219
220// ═══════════════════════════════════════════════════════════
221//  Constants
222// ═══════════════════════════════════════════════════════════
223
224pub const VAEA_API_URL: &str = "https://api.vaea.fi";
225pub const VAEA_PROGRAM_ID: &str = "VAEAmcjQ5RB9yonyrCRSkRT8womX6uqm5PS7PXr528b";
226
227/// Pre-loaded Address Lookup Table with VAEA fixed accounts.
228/// Saves ~124 bytes per transaction by compressing 4 account addresses.
229pub const VAEA_LOOKUP_TABLE: solana_sdk::pubkey::Pubkey =
230    solana_sdk::pubkey!("CsfmCd4gBs2VNsBQu37kjKttKJTMzePrQwYCDdLnuLDs");
231
232pub const SUPPORTED_TOKENS: &[&str] = &[
233    // Direct routes
234    "SOL", "USDC", "USDT", "JitoSOL", "JupSOL", "JUP", "JLP", "cbBTC",
235    // Direct LSTs
236    "mSOL", "bSOL",
237    // Sanctum LSTs
238    "INF", "laineSOL",
239];
240
241/// Fee constant — flat 0.02% for all tiers
242pub const FEE_BPS_SDK: u16 = 2;
243pub const FEE_BPS_UI: u16 = 2;
244pub const FEE_BPS_CPI: u16 = 2;
245
246// ═══════════════════════════════════════════════════════════
247//  Extended types
248// ═══════════════════════════════════════════════════════════
249
250/// Result of a transaction simulation.
251#[derive(Debug, Clone)]
252pub struct SimulateResult {
253    /// Whether the TX would succeed
254    pub success: bool,
255    /// Error details if simulation failed
256    pub error: Option<String>,
257    /// Exact compute units consumed
258    pub compute_units: u64,
259    /// Full program logs
260    pub logs: Vec<String>,
261}
262
263/// A single loan request in a multi-borrow.
264pub struct MultiBorrowRequest {
265    /// Token symbol or mint
266    pub token: String,
267    /// Borrow amount in human units
268    pub amount: f64,
269}
270
271/// Params for multi-token atomic flash loans.
272pub struct BorrowMultiParams {
273    /// Array of loans to execute atomically
274    pub loans: Vec<MultiBorrowRequest>,
275    /// User instructions to insert between all borrows and all repays
276    pub instructions: Vec<solana_sdk::instruction::Instruction>,
277    /// Max slippage in bps
278    pub slippage_bps: Option<u16>,
279    /// Max fee guard in bps
280    pub max_fee_bps: Option<u16>,
281}
282
283// ═══════════════════════════════════════════════════════════
284//  v2: Zero-CPI Types
285// ═══════════════════════════════════════════════════════════
286
287/// Source tier for fee routing
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub enum FlashTier {
290    Sdk = 0,
291    Ui = 1,
292    Protocol = 2,
293}
294
295impl FlashTier {
296    pub fn from_u8(v: u8) -> Option<Self> {
297        match v {
298            0 => Some(Self::Sdk),
299            1 => Some(Self::Ui),
300            2 => Some(Self::Protocol),
301            _ => None,
302        }
303    }
304
305    pub fn fee_bps(&self) -> u16 {
306        match self {
307            Self::Sdk => FEE_BPS_SDK,
308            Self::Ui => FEE_BPS_UI,
309            Self::Protocol => FEE_BPS_CPI,
310        }
311    }
312}
313
314/// On-chain FlashState v2 (102 bytes)
315#[derive(Debug, Clone)]
316pub struct FlashStateInfo {
317    /// Borrower pubkey
318    pub payer: solana_sdk::pubkey::Pubkey,
319    /// Token mint borrowed
320    pub token_mint: solana_sdk::pubkey::Pubkey,
321    /// Amount borrowed in native units
322    pub amount: u64,
323    /// Fee in native units
324    pub fee: u64,
325    /// Source tier (0=SDK, 1=UI, 2=Protocol)
326    pub source_tier: u8,
327    /// Human-readable tier
328    pub tier: FlashTier,
329    /// Slot when created
330    pub slot_created: u64,
331    /// PDA bump
332    pub bump: u8,
333}
334
335/// Per-source capacity detail
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct SourceCapacity {
338    pub protocol: String,
339    pub pool: String,
340    pub available: f64,
341    pub available_usd: f64,
342    pub utilization_pct: f64,
343    pub status: String,
344}
345
346/// Aggregated capacity for a token across all sources
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct AggregatedTokenCapacity {
349    #[serde(flatten)]
350    pub base: TokenCapacity,
351    pub sources: Vec<SourceCapacity>,
352    pub total_available: f64,
353    pub total_available_usd: f64,
354}
355
356// ═══════════════════════════════════════════════════════════
357//  Liquidity Matrix types (Phase 2)
358// ═══════════════════════════════════════════════════════════
359
360/// Per-token liquidity breakdown across all lending protocols.
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct MatrixTokenEntry {
363    pub mint: String,
364    pub symbol: String,
365    pub name: String,
366    pub decimals: u8,
367    pub is_direct: bool,
368    /// Per-protocol available liquidity (human-readable units)
369    pub liquidity: std::collections::HashMap<String, f64>,
370    pub total_liquidity: f64,
371    pub total_liquidity_usd: f64,
372    pub cheapest_source_fee_bps: u16,
373    pub best_protocol: Option<String>,
374}
375
376/// Response from GET /v1/matrix.
377#[derive(Debug, Clone, Serialize, Deserialize)]
378pub struct MatrixResponse {
379    pub updated_at: u64,
380    pub total_tokens: u32,
381    pub total_direct: u32,
382    pub protocols: Vec<String>,
383    pub tokens: Vec<MatrixTokenEntry>,
384}
385
386// ═══════════════════════════════════════════════════════════
387//  Discovery types (Phase 1)
388// ═══════════════════════════════════════════════════════════
389
390/// Response from GET /v1/discovery.
391#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct DiscoverySummary {
393    pub direct_count: Option<u32>,
394    pub total_count: Option<u32>,
395    pub scan_duration_ms: Option<u64>,
396    #[serde(default)]
397    pub protocols: Vec<String>,
398    pub updated_at: Option<u64>,
399}
400
401// ═══════════════════════════════════════════════════════════
402//  Smart Router types (Phase 3)
403// ═══════════════════════════════════════════════════════════
404
405/// A single route candidate evaluated by the Smart Router.
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct RouteCandidate {
408    pub strategy_type: String,
409    pub protocol: String,
410    pub available_liquidity: f64,
411    pub protocol_fee_bps: u16,
412    pub vaea_fee_bps: u16,
413    pub total_cost_bps: u16,
414    pub total_cost_usd: f64,
415    pub sufficient_liquidity: bool,
416    pub feasible: bool,
417}
418
419/// Response from GET /v1/route — the optimal flash loan route.
420#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct ResolvedRoute {
422    pub token_mint: String,
423    pub token_symbol: String,
424    pub amount: f64,
425    pub amount_usd: f64,
426    pub strategy: serde_json::Value,  // {"direct": {...}}
427    pub candidates: Vec<RouteCandidate>,
428    pub reasoning: String,
429}
430
431
432// ═══════════════════════════════════════════════════════════
433//  Sources types
434// ═══════════════════════════════════════════════════════════
435
436/// A lending protocol in the sources response.
437#[derive(Debug, Clone, Serialize, Deserialize)]
438pub struct ProtocolInfo {
439    pub id: String,
440    pub name: String,
441    pub program_id: String,
442    pub token_count: u32,
443    pub tokens: Vec<String>,
444}
445
446/// Fee tier detail.
447#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct FeeTierInfo {
449    pub bps: u16,
450}
451
452/// Response from GET /v1/sources.
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct SourcesResponse {
455    pub discovery: String,
456    pub protocols: Vec<ProtocolInfo>,
457    pub fee_tiers: std::collections::HashMap<String, FeeTierInfo>,
458}
459
460// ═══════════════════════════════════════════════════════════
461//  Aggregated Capacity Response
462// ═══════════════════════════════════════════════════════════
463
464/// Response from GET /v1/capacity/aggregated.
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct AggregatedCapacityResponse {
467    pub updated_at: u64,
468    pub tokens: Vec<serde_json::Value>,
469}
470