Skip to main content

bybit_api/api/
earn.rs

1//! Earn API endpoints.
2
3use crate::client::BybitClient;
4use crate::error::Result;
5use crate::models::earn::*;
6use crate::models::Category;
7
8impl BybitClient {
9    pub async fn add_liquidity(&self, params: AddLiquidityParams) -> Result<AddLiquidityResponse> {
10        self.post("/v5/earn/liquidity-mining/add-liquidity", &params)
11            .await
12    }
13
14    // FIXME(gen-sdk-rust): plan agent referenced AddMarginParams without
15    // actually emitting the type to models/earn.rs. Falling back to
16    // serde_json::Value so the SDK compiles; the caller constructs the body
17    // manually. Re-run gen-sdk-rust after fixing plan output to recover the
18    // typed signature.
19    // FIXME(typed-signature): falls back to `serde_json::Value` because the
20    // OpenAPI spec referenced a response/request type that gen-sdk-rust could
21    // not auto-resolve. Replace with a typed struct in a follow-up PR.
22    pub async fn add_liquidity_mining_margin(&self, params: serde_json::Value) -> Result<serde_json::Value> {
23        self.post("/v5/earn/liquidity-mining/add-margin", &params)
24            .await
25    }
26
27    pub async fn claim_liquidity_interest(
28        &self,
29        params: ClaimLiquidityInterestParams,
30    ) -> Result<ClaimLiquidityInterestResponse> {
31        self.post("/v5/earn/liquidity-mining/claim-interest", &params)
32            .await
33    }
34
35    #[allow(clippy::too_many_arguments)] // TODO(api-ergonomics): convert positional args to a typed `*Params` struct
36    pub async fn get_advance_earn_order(
37        &self,
38        category: Category,
39        product_id: Option<i64>,
40        order_id: Option<&str>,
41        order_link_id: Option<&str>,
42        start_time: Option<i64>,
43        end_time: Option<i64>,
44        limit: Option<i64>,
45        cursor: Option<&str>,
46    ) -> Result<GetAdvanceEarnOrderResponse> {
47        let product_id_str = product_id.map(|v| v.to_string());
48        let start_time_str = start_time.map(|v| v.to_string());
49        let end_time_str = end_time.map(|v| v.to_string());
50        let limit_str = limit.map(|v| v.to_string());
51
52        let cat_str = category.to_string();
53        let mut params = vec![("category", cat_str.as_str())];
54        if let Some(ref v) = product_id_str {
55            params.push(("productId", v.as_str()));
56        }
57        if let Some(v) = order_id {
58            params.push(("orderId", v));
59        }
60        if let Some(v) = order_link_id {
61            params.push(("orderLinkId", v));
62        }
63        if let Some(ref v) = start_time_str {
64            params.push(("startTime", v.as_str()));
65        }
66        if let Some(ref v) = end_time_str {
67            params.push(("endTime", v.as_str()));
68        }
69        if let Some(ref v) = limit_str {
70            params.push(("limit", v.as_str()));
71        }
72        if let Some(v) = cursor {
73            params.push(("cursor", v));
74        }
75
76        self.get("/v5/earn/advance/order", &params).await
77    }
78
79    pub async fn get_advance_earn_position(
80        &self,
81        category: Category,
82        product_id: Option<i64>,
83        coin: Option<&str>,
84        limit: Option<i64>,
85        cursor: Option<&str>,
86    ) -> Result<GetAdvanceEarnPositionResponse> {
87        let product_id_str = product_id.map(|v| v.to_string());
88        let limit_str = limit.map(|v| v.to_string());
89
90        let cat_str = category.to_string();
91        let mut params = vec![("category", cat_str.as_str())];
92        if let Some(ref v) = product_id_str {
93            params.push(("productId", v.as_str()));
94        }
95        if let Some(v) = coin {
96            params.push(("coin", v));
97        }
98        if let Some(ref v) = limit_str {
99            params.push(("limit", v.as_str()));
100        }
101        if let Some(v) = cursor {
102            params.push(("cursor", v));
103        }
104
105        self.get("/v5/earn/advance/position", &params).await
106    }
107
108    pub async fn get_advance_earn_product(
109        &self,
110        category: Category,
111        coin: Option<&str>,
112        duration: Option<&str>,
113    ) -> Result<GetAdvanceEarnProductResponse> {
114        let cat_str = category.to_string();
115        let mut params = vec![("category", cat_str.as_str())];
116        if let Some(v) = coin {
117            params.push(("coin", v));
118        }
119        if let Some(v) = duration {
120            params.push(("duration", v));
121        }
122
123        self.get_public("/v5/earn/advance/product", &params).await
124    }
125
126    pub async fn get_advance_earn_product_extra_info(
127        &self,
128        category: Category,
129        product_id: Option<i64>,
130    ) -> Result<GetAdvanceEarnProductExtraInfoResponse> {
131        let product_id_str = product_id.map(|v| v.to_string());
132
133        let cat_str = category.to_string();
134        let mut params = vec![("category", cat_str.as_str())];
135        if let Some(ref v) = product_id_str {
136            params.push(("productId", v.as_str()));
137        }
138
139        self.get_public("/v5/earn/advance/product-extra-info", &params)
140            .await
141    }
142
143    pub async fn get_double_win_leverage(
144        &self,
145        product_id: i64,
146        initial_price: &str,
147        lower_price: &str,
148        upper_price: &str,
149    ) -> Result<GetDoubleWinLeverageResponse> {
150        let product_id_str = product_id.to_string();
151        let params = vec![
152            ("productId", product_id_str.as_str()),
153            ("initialPrice", initial_price),
154            ("lowerPrice", lower_price),
155            ("upperPrice", upper_price),
156        ];
157
158        self.get("/v5/earn/advance/double-win-leverage", &params)
159            .await
160    }
161
162    pub async fn get_earn_apr_history(
163        &self,
164        category: Category,
165        product_id: &str,
166        start_time: i64,
167        end_time: i64,
168    ) -> Result<GetEarnAprHistoryResponse> {
169        let cat_str = category.to_string();
170        let start_time_str = start_time.to_string();
171        let end_time_str = end_time.to_string();
172        let params = vec![
173            ("category", cat_str.as_str()),
174            ("productId", product_id),
175            ("startTime", start_time_str.as_str()),
176            ("endTime", end_time_str.as_str()),
177        ];
178
179        self.get_public("/v5/earn/apr-history", &params).await
180    }
181
182    pub async fn get_earn_hourly_yield_history(
183        &self,
184        category: Category,
185        product_id: Option<&str>,
186        start_time: Option<i64>,
187        end_time: Option<i64>,
188        limit: Option<i64>,
189        cursor: Option<&str>,
190    ) -> Result<GetEarnHourlyYieldHistoryResponse> {
191        let start_time_str = start_time.map(|v| v.to_string());
192        let end_time_str = end_time.map(|v| v.to_string());
193        let limit_str = limit.map(|v| v.to_string());
194
195        let cat_str = category.to_string();
196        let mut params = vec![("category", cat_str.as_str())];
197        if let Some(v) = product_id {
198            params.push(("productId", v));
199        }
200        if let Some(ref v) = start_time_str {
201            params.push(("startTime", v.as_str()));
202        }
203        if let Some(ref v) = end_time_str {
204            params.push(("endTime", v.as_str()));
205        }
206        if let Some(ref v) = limit_str {
207            params.push(("limit", v.as_str()));
208        }
209        if let Some(v) = cursor {
210            params.push(("cursor", v));
211        }
212
213        self.get("/v5/earn/hourly-yield", &params).await
214    }
215
216    #[allow(clippy::too_many_arguments)] // TODO(api-ergonomics): convert positional args to a typed `*Params` struct
217    pub async fn get_earn_order_history(
218        &self,
219        category: Category,
220        order_id: Option<&str>,
221        order_link_id: Option<&str>,
222        product_id: Option<&str>,
223        start_time: Option<i64>,
224        end_time: Option<i64>,
225        limit: Option<i64>,
226        cursor: Option<&str>,
227    ) -> Result<GetEarnOrderHistoryResponse> {
228        let start_time_str = start_time.map(|v| v.to_string());
229        let end_time_str = end_time.map(|v| v.to_string());
230        let limit_str = limit.map(|v| v.to_string());
231
232        let cat_str = category.to_string();
233        let mut params = vec![("category", cat_str.as_str())];
234        if let Some(v) = order_id {
235            params.push(("orderId", v));
236        }
237        if let Some(v) = order_link_id {
238            params.push(("orderLinkId", v));
239        }
240        if let Some(v) = product_id {
241            params.push(("productId", v));
242        }
243        if let Some(ref v) = start_time_str {
244            params.push(("startTime", v.as_str()));
245        }
246        if let Some(ref v) = end_time_str {
247            params.push(("endTime", v.as_str()));
248        }
249        if let Some(ref v) = limit_str {
250            params.push(("limit", v.as_str()));
251        }
252        if let Some(v) = cursor {
253            params.push(("cursor", v));
254        }
255
256        self.get("/v5/earn/order", &params).await
257    }
258
259    pub async fn get_earn_position(
260        &self,
261        category: Category,
262        product_id: Option<&str>,
263        coin: Option<&str>,
264    ) -> Result<GetEarnPositionResponse> {
265        let cat_str = category.to_string();
266        let mut params = vec![("category", cat_str.as_str())];
267        if let Some(v) = product_id {
268            params.push(("productId", v));
269        }
270        if let Some(v) = coin {
271            params.push(("coin", v));
272        }
273
274        self.get("/v5/earn/position", &params).await
275    }
276
277    pub async fn get_earn_product(
278        &self,
279        category: Category,
280        coin: Option<&str>,
281    ) -> Result<GetEarnProductResponse> {
282        let cat_str = category.to_string();
283        let mut params = vec![("category", cat_str.as_str())];
284        if let Some(v) = coin {
285            params.push(("coin", v));
286        }
287
288        self.get_public("/v5/earn/product", &params).await
289    }
290
291    pub async fn get_earn_yield_history(
292        &self,
293        category: Category,
294        product_id: Option<i64>,
295        start_time: Option<i64>,
296        end_time: Option<i64>,
297        limit: Option<i64>,
298        cursor: Option<&str>,
299    ) -> Result<GetEarnYieldHistoryResponse> {
300        let product_id_str = product_id.map(|v| v.to_string());
301        let start_time_str = start_time.map(|v| v.to_string());
302        let end_time_str = end_time.map(|v| v.to_string());
303        let limit_str = limit.map(|v| v.to_string());
304
305        let cat_str = category.to_string();
306        let mut params = vec![("category", cat_str.as_str())];
307        if let Some(ref v) = product_id_str {
308            params.push(("productId", v.as_str()));
309        }
310        if let Some(ref v) = start_time_str {
311            params.push(("startTime", v.as_str()));
312        }
313        if let Some(ref v) = end_time_str {
314            params.push(("endTime", v.as_str()));
315        }
316        if let Some(ref v) = limit_str {
317            params.push(("limit", v.as_str()));
318        }
319        if let Some(v) = cursor {
320            params.push(("cursor", v));
321        }
322
323        self.get("/v5/earn/yield", &params).await
324    }
325
326    #[allow(clippy::too_many_arguments)] // TODO(api-ergonomics): convert positional args to a typed `*Params` struct
327    pub async fn get_fixed_term_order(
328        &self,
329        order_type: Option<&str>,
330        product_id: Option<&str>,
331        category: Option<Category>,
332        order_id: Option<&str>,
333        start_time: Option<i64>,
334        end_time: Option<i64>,
335        limit: Option<i64>,
336        cursor: Option<&str>,
337    ) -> Result<GetFixedTermOrderResponse> {
338        let cat_str = category.map(|c| c.to_string());
339        let start_time_str = start_time.map(|v| v.to_string());
340        let end_time_str = end_time.map(|v| v.to_string());
341        let limit_str = limit.map(|v| v.to_string());
342
343        let mut params: Vec<(&str, &str)> = vec![];
344        if let Some(v) = order_type {
345            params.push(("orderType", v));
346        }
347        if let Some(v) = product_id {
348            params.push(("productId", v));
349        }
350        if let Some(ref v) = cat_str {
351            params.push(("category", v.as_str()));
352        }
353        if let Some(v) = order_id {
354            params.push(("orderId", v));
355        }
356        if let Some(ref v) = start_time_str {
357            params.push(("startTime", v.as_str()));
358        }
359        if let Some(ref v) = end_time_str {
360            params.push(("endTime", v.as_str()));
361        }
362        if let Some(ref v) = limit_str {
363            params.push(("limit", v.as_str()));
364        }
365        if let Some(v) = cursor {
366            params.push(("cursor", v));
367        }
368
369        self.get("/v5/earn/fixed-term/order", &params).await
370    }
371
372    pub async fn get_fixed_term_position(
373        &self,
374        product_id: Option<&str>,
375        category: Option<Category>,
376        coin: Option<&str>,
377    ) -> Result<GetFixedTermPositionResponse> {
378        let cat_str = category.map(|c| c.to_string());
379        let mut params: Vec<(&str, &str)> = vec![];
380        if let Some(v) = product_id {
381            params.push(("productId", v));
382        }
383        if let Some(ref v) = cat_str {
384            params.push(("category", v.as_str()));
385        }
386        if let Some(v) = coin {
387            params.push(("coin", v));
388        }
389
390        self.get("/v5/earn/fixed-term/position", &params).await
391    }
392
393    pub async fn get_fixed_term_product(
394        &self,
395        coin: Option<&str>,
396    ) -> Result<GetFixedTermProductResponse> {
397        let mut params: Vec<(&str, &str)> = vec![];
398        if let Some(v) = coin {
399            params.push(("coin", v));
400        }
401
402        self.get_public("/v5/earn/fixed-term/product", &params)
403            .await
404    }
405
406    pub async fn get_hold_to_earn_product(&self) -> Result<GetHoldToEarnProductResponse> {
407        self.get_public("/v5/earn/hold-to-earn/product", &[]).await
408    }
409
410    pub async fn get_hold_to_earn_yield_history(
411        &self,
412        time_start: Option<i64>,
413        time_end: Option<i64>,
414        limit: i64,
415        cursor: Option<&str>,
416    ) -> Result<GetHoldToEarnYieldHistoryResponse> {
417        let time_start_str = time_start.map(|v| v.to_string());
418        let time_end_str = time_end.map(|v| v.to_string());
419        let limit_str = limit.to_string();
420
421        let mut params = vec![("limit", limit_str.as_str())];
422        if let Some(ref v) = time_start_str {
423            params.push(("timeStart", v.as_str()));
424        }
425        if let Some(ref v) = time_end_str {
426            params.push(("timeEnd", v.as_str()));
427        }
428        if let Some(v) = cursor {
429            params.push(("cursor", v));
430        }
431
432        self.get("/v5/earn/hold-to-earn/yield-history", &params)
433            .await
434    }
435
436    pub async fn get_liquidity_mining_liquidation_records(
437        &self,
438        base_coin: Option<&str>,
439        quote_coin: Option<&str>,
440        start_time: Option<i64>,
441        end_time: Option<i64>,
442        limit: Option<i64>,
443        cursor: Option<&str>,
444    ) -> Result<GetLiquidityMiningLiquidationRecordsResponse> {
445        let start_time_str = start_time.map(|v| v.to_string());
446        let end_time_str = end_time.map(|v| v.to_string());
447        let limit_str = limit.map(|v| v.to_string());
448
449        let mut params: Vec<(&str, &str)> = vec![];
450        if let Some(v) = base_coin {
451            params.push(("baseCoin", v));
452        }
453        if let Some(v) = quote_coin {
454            params.push(("quoteCoin", v));
455        }
456        if let Some(ref v) = start_time_str {
457            params.push(("startTime", v.as_str()));
458        }
459        if let Some(ref v) = end_time_str {
460            params.push(("endTime", v.as_str()));
461        }
462        if let Some(ref v) = limit_str {
463            params.push(("limit", v.as_str()));
464        }
465        if let Some(v) = cursor {
466            params.push(("cursor", v));
467        }
468
469        self.get("/v5/earn/liquidity-mining/liquidation-records", &params)
470            .await
471    }
472
473    #[allow(clippy::too_many_arguments)] // TODO(api-ergonomics): convert positional args to a typed `*Params` struct
474    pub async fn get_liquidity_mining_orders(
475        &self,
476        order_id: Option<&str>,
477        order_link_id: Option<&str>,
478        product_id: Option<&str>,
479        order_type: Option<&str>,
480        status: Option<&str>,
481        start_time: Option<i64>,
482        end_time: Option<i64>,
483        limit: Option<i64>,
484        cursor: Option<&str>,
485    ) -> Result<GetLiquidityMiningOrdersResponse> {
486        let start_time_str = start_time.map(|v| v.to_string());
487        let end_time_str = end_time.map(|v| v.to_string());
488        let limit_str = limit.map(|v| v.to_string());
489
490        let mut params: Vec<(&str, &str)> = vec![];
491        if let Some(v) = order_id {
492            params.push(("orderId", v));
493        }
494        if let Some(v) = order_link_id {
495            params.push(("orderLinkId", v));
496        }
497        if let Some(v) = product_id {
498            params.push(("productId", v));
499        }
500        if let Some(v) = order_type {
501            params.push(("orderType", v));
502        }
503        if let Some(v) = status {
504            params.push(("status", v));
505        }
506        if let Some(ref v) = start_time_str {
507            params.push(("startTime", v.as_str()));
508        }
509        if let Some(ref v) = end_time_str {
510            params.push(("endTime", v.as_str()));
511        }
512        if let Some(ref v) = limit_str {
513            params.push(("limit", v.as_str()));
514        }
515        if let Some(v) = cursor {
516            params.push(("cursor", v));
517        }
518
519        self.get("/v5/earn/liquidity-mining/order", &params).await
520    }
521
522    pub async fn get_liquidity_mining_positions(
523        &self,
524        product_id: Option<&str>,
525        base_coin: Option<&str>,
526    ) -> Result<GetLiquidityMiningPositionsResponse> {
527        let mut params: Vec<(&str, &str)> = vec![];
528        if let Some(v) = product_id {
529            params.push(("productId", v));
530        }
531        if let Some(v) = base_coin {
532            params.push(("baseCoin", v));
533        }
534
535        self.get("/v5/earn/liquidity-mining/position", &params)
536            .await
537    }
538
539    pub async fn get_liquidity_mining_products(
540        &self,
541        base_coin: Option<&str>,
542        quote_coin: Option<&str>,
543    ) -> Result<GetLiquidityMiningProductsResponse> {
544        let mut params: Vec<(&str, &str)> = vec![];
545        if let Some(v) = base_coin {
546            params.push(("baseCoin", v));
547        }
548        if let Some(v) = quote_coin {
549            params.push(("quoteCoin", v));
550        }
551
552        self.get_public("/v5/earn/liquidity-mining/product", &params)
553            .await
554    }
555
556    pub async fn get_liquidity_mining_yield_records(
557        &self,
558        base_coin: Option<&str>,
559        quote_coin: Option<&str>,
560        start_time: Option<i64>,
561        end_time: Option<i64>,
562        limit: Option<i64>,
563        cursor: Option<&str>,
564    ) -> Result<GetLiquidityMiningYieldRecordsResponse> {
565        let start_time_str = start_time.map(|v| v.to_string());
566        let end_time_str = end_time.map(|v| v.to_string());
567        let limit_str = limit.map(|v| v.to_string());
568
569        let mut params: Vec<(&str, &str)> = vec![];
570        if let Some(v) = base_coin {
571            params.push(("baseCoin", v));
572        }
573        if let Some(v) = quote_coin {
574            params.push(("quoteCoin", v));
575        }
576        if let Some(ref v) = start_time_str {
577            params.push(("startTime", v.as_str()));
578        }
579        if let Some(ref v) = end_time_str {
580            params.push(("endTime", v.as_str()));
581        }
582        if let Some(ref v) = limit_str {
583            params.push(("limit", v.as_str()));
584        }
585        if let Some(v) = cursor {
586            params.push(("cursor", v));
587        }
588
589        self.get("/v5/earn/liquidity-mining/yield-records", &params)
590            .await
591    }
592
593    pub async fn get_rwa_nav_chart(
594        &self,
595        product_id: i64,
596        start_time: Option<i64>,
597        end_time: Option<i64>,
598    ) -> Result<GetRwaNavChartResponse> {
599        let product_id_str = product_id.to_string();
600        let start_time_str = start_time.map(|v| v.to_string());
601        let end_time_str = end_time.map(|v| v.to_string());
602
603        let mut params = vec![("productId", product_id_str.as_str())];
604        if let Some(ref v) = start_time_str {
605            params.push(("startTime", v.as_str()));
606        }
607        if let Some(ref v) = end_time_str {
608            params.push(("endTime", v.as_str()));
609        }
610
611        self.get_public("/v5/earn/rwa/nav-chart", &params).await
612    }
613
614    #[allow(clippy::too_many_arguments)] // TODO(api-ergonomics): convert positional args to a typed `*Params` struct
615    pub async fn get_rwa_order_list(
616        &self,
617        order_id: Option<&str>,
618        order_link_id: Option<&str>,
619        order_type: Option<&str>,
620        product_id: Option<i64>,
621        start_time: Option<i64>,
622        end_time: Option<i64>,
623        limit: Option<i64>,
624        cursor: Option<&str>,
625    ) -> Result<GetRwaOrderListResponse> {
626        let product_id_str = product_id.map(|v| v.to_string());
627        let start_time_str = start_time.map(|v| v.to_string());
628        let end_time_str = end_time.map(|v| v.to_string());
629        let limit_str = limit.map(|v| v.to_string());
630
631        let mut params: Vec<(&str, &str)> = vec![];
632        if let Some(v) = order_id {
633            params.push(("orderId", v));
634        }
635        if let Some(v) = order_link_id {
636            params.push(("orderLinkId", v));
637        }
638        if let Some(v) = order_type {
639            params.push(("orderType", v));
640        }
641        if let Some(ref v) = product_id_str {
642            params.push(("productId", v.as_str()));
643        }
644        if let Some(ref v) = start_time_str {
645            params.push(("startTime", v.as_str()));
646        }
647        if let Some(ref v) = end_time_str {
648            params.push(("endTime", v.as_str()));
649        }
650        if let Some(ref v) = limit_str {
651            params.push(("limit", v.as_str()));
652        }
653        if let Some(v) = cursor {
654            params.push(("cursor", v));
655        }
656
657        self.get("/v5/earn/rwa/order", &params).await
658    }
659
660    pub async fn get_rwa_position_list(&self) -> Result<GetRwaPositionListResponse> {
661        self.get("/v5/earn/rwa/position", &[]).await
662    }
663
664    pub async fn get_rwa_product_list(
665        &self,
666        coin: Option<&str>,
667    ) -> Result<GetRwaProductListResponse> {
668        let mut params: Vec<(&str, &str)> = vec![];
669        if let Some(v) = coin {
670            params.push(("coin", v));
671        }
672
673        self.get_public("/v5/earn/rwa/product", &params).await
674    }
675
676    pub async fn get_smart_leverage_redeem_est_amount_list(
677        &self,
678        category: Category,
679        position_ids: &str,
680    ) -> Result<GetSmartLeverageRedeemEstAmountListResponse> {
681        let cat_str = category.to_string();
682        let params = vec![("category", cat_str.as_str()), ("positionIds", position_ids)];
683
684        self.get("/v5/earn/advance/get-redeem-est-amount-list", &params)
685            .await
686    }
687
688    pub async fn get_token_daily_yield(
689        &self,
690        coin: &str,
691        start_time: Option<i64>,
692        end_time: Option<i64>,
693        cursor: Option<&str>,
694        limit: Option<i64>,
695    ) -> Result<GetTokenDailyYieldResponse> {
696        let start_time_str = start_time.map(|v| v.to_string());
697        let end_time_str = end_time.map(|v| v.to_string());
698        let limit_str = limit.map(|v| v.to_string());
699
700        let mut params = vec![("coin", coin)];
701        if let Some(ref v) = start_time_str {
702            params.push(("startTime", v.as_str()));
703        }
704        if let Some(ref v) = end_time_str {
705            params.push(("endTime", v.as_str()));
706        }
707        if let Some(v) = cursor {
708            params.push(("cursor", v));
709        }
710        if let Some(ref v) = limit_str {
711            params.push(("limit", v.as_str()));
712        }
713
714        self.get("/v5/earn/token/yield", &params).await
715    }
716
717    pub async fn get_token_historical_apr(
718        &self,
719        coin: &str,
720        range: i64,
721    ) -> Result<GetTokenHistoricalAprResponse> {
722        let range_str = range.to_string();
723        let params = vec![("coin", coin), ("range", range_str.as_str())];
724
725        self.get_public("/v5/earn/token/history-apr", &params).await
726    }
727
728    pub async fn get_token_hourly_yield(
729        &self,
730        coin: &str,
731        start_time: Option<i64>,
732        end_time: Option<i64>,
733        cursor: Option<&str>,
734        limit: Option<i64>,
735    ) -> Result<GetTokenHourlyYieldResponse> {
736        let start_time_str = start_time.map(|v| v.to_string());
737        let end_time_str = end_time.map(|v| v.to_string());
738        let limit_str = limit.map(|v| v.to_string());
739
740        let mut params = vec![("coin", coin)];
741        if let Some(ref v) = start_time_str {
742            params.push(("startTime", v.as_str()));
743        }
744        if let Some(ref v) = end_time_str {
745            params.push(("endTime", v.as_str()));
746        }
747        if let Some(v) = cursor {
748            params.push(("cursor", v));
749        }
750        if let Some(ref v) = limit_str {
751            params.push(("limit", v.as_str()));
752        }
753
754        self.get("/v5/earn/token/hourly-yield", &params).await
755    }
756
757    #[allow(clippy::too_many_arguments)] // TODO(api-ergonomics): convert positional args to a typed `*Params` struct
758    pub async fn get_token_order_list(
759        &self,
760        coin: &str,
761        order_link_id: Option<&str>,
762        order_id: Option<&str>,
763        order_type: Option<&str>,
764        start_time: Option<i64>,
765        end_time: Option<i64>,
766        cursor: Option<&str>,
767        limit: Option<i64>,
768    ) -> Result<GetTokenOrderListResponse> {
769        let start_time_str = start_time.map(|v| v.to_string());
770        let end_time_str = end_time.map(|v| v.to_string());
771        let limit_str = limit.map(|v| v.to_string());
772
773        let mut params = vec![("coin", coin)];
774        if let Some(v) = order_link_id {
775            params.push(("orderLinkId", v));
776        }
777        if let Some(v) = order_id {
778            params.push(("orderId", v));
779        }
780        if let Some(v) = order_type {
781            params.push(("orderType", v));
782        }
783        if let Some(ref v) = start_time_str {
784            params.push(("startTime", v.as_str()));
785        }
786        if let Some(ref v) = end_time_str {
787            params.push(("endTime", v.as_str()));
788        }
789        if let Some(v) = cursor {
790            params.push(("cursor", v));
791        }
792        if let Some(ref v) = limit_str {
793            params.push(("limit", v.as_str()));
794        }
795
796        self.get("/v5/earn/token/order", &params).await
797    }
798
799    pub async fn get_token_position(&self, coin: &str) -> Result<GetTokenPositionResponse> {
800        let params = vec![("coin", coin)];
801        self.get("/v5/earn/token/position", &params).await
802    }
803
804    pub async fn get_token_product(&self, coin: &str) -> Result<GetTokenProductResponse> {
805        let params = vec![("coin", coin)];
806        self.get_public("/v5/earn/token/product", &params).await
807    }
808
809    pub async fn list_earn_coupons(&self, category: Category) -> Result<ListEarnCouponsResponse> {
810        let cat_str = category.to_string();
811        let params = vec![("category", cat_str.as_str())];
812        self.get("/v5/earn/coupons", &params).await
813    }
814
815    // FIXME(typed-signature): falls back to `serde_json::Value` because the
816    // OpenAPI spec referenced a response/request type that gen-sdk-rust could
817    // not auto-resolve. Replace with a typed struct in a follow-up PR.
818    pub async fn modify_earn_position(
819        &self,
820        params: ModifyEarnPositionParams,
821    ) -> Result<serde_json::Value> {
822        self.post("/v5/earn/position/modify", &params).await
823    }
824
825    pub async fn place_advance_earn_order(
826        &self,
827        params: PlaceAdvanceEarnOrderParams,
828    ) -> Result<PlaceAdvanceEarnOrderResponse> {
829        self.post("/v5/earn/advance/place-order", &params).await
830    }
831
832    pub async fn place_earn_order(
833        &self,
834        params: PlaceEarnOrderParams,
835    ) -> Result<PlaceEarnOrderResponse> {
836        self.post("/v5/earn/place-order", &params).await
837    }
838
839    pub async fn place_fixed_term_order(
840        &self,
841        params: PlaceFixedTermOrderParams,
842    ) -> Result<PlaceFixedTermOrderResponse> {
843        self.post("/v5/earn/fixed-term/place-order", &params).await
844    }
845
846    pub async fn place_rwa_order(
847        &self,
848        params: PlaceRwaOrderParams,
849    ) -> Result<PlaceRwaOrderResponse> {
850        self.post("/v5/earn/rwa/place-order", &params).await
851    }
852
853    pub async fn place_token_order(
854        &self,
855        params: PlaceTokenOrderParams,
856    ) -> Result<PlaceTokenOrderResponse> {
857        self.post("/v5/earn/token/place-order", &params).await
858    }
859
860    pub async fn pwm_asset_trend(
861        &self,
862        plan_id: &str,
863        start_time: Option<i64>,
864        end_time: Option<i64>,
865    ) -> Result<PwmAssetTrendResponse> {
866        let start_time_str = start_time.map(|v| v.to_string());
867        let end_time_str = end_time.map(|v| v.to_string());
868
869        let mut params = vec![("planId", plan_id)];
870        if let Some(ref v) = start_time_str {
871            params.push(("startTime", v.as_str()));
872        }
873        if let Some(ref v) = end_time_str {
874            params.push(("endTime", v.as_str()));
875        }
876
877        self.get("/v5/earn/pwm/investment-plan/asset-trend", &params)
878            .await
879    }
880
881    pub async fn pwm_claim(&self, params: PwmClaimParams) -> Result<PwmClaimResponse> {
882        self.post("/v5/earn/pwm/investment-plan/claim", &params)
883            .await
884    }
885
886    pub async fn pwm_create_custom_plan(
887        &self,
888        params: PwmCreateCustomPlanParams,
889    ) -> Result<PwmCreateCustomPlanResponse> {
890        self.post("/v5/earn/pwm/customize-plan/create", &params)
891            .await
892    }
893
894    pub async fn pwm_fund_nav(
895        &self,
896        fund_id: &str,
897        start_time: Option<i64>,
898        end_time: Option<i64>,
899    ) -> Result<PwmFundNavResponse> {
900        let start_time_str = start_time.map(|v| v.to_string());
901        let end_time_str = end_time.map(|v| v.to_string());
902
903        let mut params = vec![("fundId", fund_id)];
904        if let Some(ref v) = start_time_str {
905            params.push(("startTime", v.as_str()));
906        }
907        if let Some(ref v) = end_time_str {
908            params.push(("endTime", v.as_str()));
909        }
910
911        self.get("/v5/earn/pwm/investment-plan/fund-nav", &params)
912            .await
913    }
914
915    pub async fn pwm_fund_transfer(
916        &self,
917        params: PwmFundTransferParams,
918    ) -> Result<PwmFundTransferResponse> {
919        self.post("/v5/earn/pwm/fund-transfer", &params).await
920    }
921
922    pub async fn pwm_get_new_plan_detail(
923        &self,
924        plan_id: &str,
925    ) -> Result<PwmGetNewPlanDetailResponse> {
926        let params = vec![("planId", plan_id)];
927        self.get("/v5/earn/pwm/investment-plan/new-plan", &params)
928            .await
929    }
930
931    pub async fn pwm_get_plan_detail(&self, plan_id: &str) -> Result<PwmGetPlanDetailResponse> {
932        let params = vec![("planId", plan_id)];
933        self.get("/v5/earn/pwm/investment-plan/detail", &params)
934            .await
935    }
936
937    pub async fn pwm_inst_create_fund(
938        &self,
939        params: PwmInstCreateFundParams,
940    ) -> Result<PwmInstCreateFundResponse> {
941        self.post("/v5/earn/pwm/asset-manager/create-fund", &params)
942            .await
943    }
944
945    pub async fn pwm_inst_create_investment_plan(
946        &self,
947        params: PwmInstCreateInvestmentPlanParams,
948    ) -> Result<PwmInstCreateInvestmentPlanResponse> {
949        self.post("/v5/earn/pwm/asset-manager/create-investment-plan", &params)
950            .await
951    }
952
953    pub async fn pwm_inst_create_sub_account(
954        &self,
955        params: PwmInstCreateSubAccountParams,
956    ) -> Result<PwmInstCreateSubAccountResponse> {
957        self.post("/v5/earn/pwm/asset-manager/create-sub-account", &params)
958            .await
959    }
960
961    pub async fn pwm_inst_get_investment_plans(
962        &self,
963        plan_id: Option<&str>,
964        status: Option<&str>,
965        subscription_uid: Option<&str>,
966        limit: Option<i64>,
967        cursor: Option<&str>,
968    ) -> Result<PwmInstGetInvestmentPlansResponse> {
969        let limit_str = limit.map(|v| v.to_string());
970
971        let mut params: Vec<(&str, &str)> = vec![];
972        if let Some(v) = plan_id {
973            params.push(("planId", v));
974        }
975        if let Some(v) = status {
976            params.push(("status", v));
977        }
978        if let Some(v) = subscription_uid {
979            params.push(("subscriptionUid", v));
980        }
981        if let Some(ref v) = limit_str {
982            params.push(("limit", v.as_str()));
983        }
984        if let Some(v) = cursor {
985            params.push(("cursor", v));
986        }
987
988        self.get("/v5/earn/pwm/asset-manager/get-investment-plan", &params)
989            .await
990    }
991
992    pub async fn pwm_inst_list_funds(
993        &self,
994        fund_id: Option<&str>,
995        coin: Option<&str>,
996        status: Option<&str>,
997        limit: Option<i64>,
998        cursor: Option<&str>,
999    ) -> Result<PwmInstListFundsResponse> {
1000        let limit_str = limit.map(|v| v.to_string());
1001
1002        let mut params: Vec<(&str, &str)> = vec![];
1003        if let Some(v) = fund_id {
1004            params.push(("fundId", v));
1005        }
1006        if let Some(v) = coin {
1007            params.push(("coin", v));
1008        }
1009        if let Some(v) = status {
1010            params.push(("status", v));
1011        }
1012        if let Some(ref v) = limit_str {
1013            params.push(("limit", v.as_str()));
1014        }
1015        if let Some(v) = cursor {
1016            params.push(("cursor", v));
1017        }
1018
1019        self.get("/v5/earn/pwm/asset-manager/all-funds", &params)
1020            .await
1021    }
1022
1023    #[allow(clippy::too_many_arguments)] // TODO(api-ergonomics): convert positional args to a typed `*Params` struct
1024    pub async fn pwm_inst_list_orders(
1025        &self,
1026        fund_id: Option<&str>,
1027        order_type: Option<&str>,
1028        status: Option<&str>,
1029        start_time: Option<i64>,
1030        end_time: Option<i64>,
1031        limit: Option<i64>,
1032        cursor: Option<&str>,
1033    ) -> Result<PwmInstListOrdersResponse> {
1034        let start_time_str = start_time.map(|v| v.to_string());
1035        let end_time_str = end_time.map(|v| v.to_string());
1036        let limit_str = limit.map(|v| v.to_string());
1037
1038        let mut params: Vec<(&str, &str)> = vec![];
1039        if let Some(v) = fund_id {
1040            params.push(("fundId", v));
1041        }
1042        if let Some(v) = order_type {
1043            params.push(("orderType", v));
1044        }
1045        if let Some(v) = status {
1046            params.push(("status", v));
1047        }
1048        if let Some(ref v) = start_time_str {
1049            params.push(("startTime", v.as_str()));
1050        }
1051        if let Some(ref v) = end_time_str {
1052            params.push(("endTime", v.as_str()));
1053        }
1054        if let Some(ref v) = limit_str {
1055            params.push(("limit", v.as_str()));
1056        }
1057        if let Some(v) = cursor {
1058            params.push(("cursor", v));
1059        }
1060
1061        self.get("/v5/earn/pwm/asset-manager/all-order", &params)
1062            .await
1063    }
1064
1065    pub async fn pwm_inst_manage_investment_plan(
1066        &self,
1067        params: PwmInstManageInvestmentPlanParams,
1068    ) -> Result<PwmInstManageInvestmentPlanResponse> {
1069        self.post("/v5/earn/pwm/asset-manager/manage-investment-plan", &params)
1070            .await
1071    }
1072
1073    pub async fn pwm_inst_manage_order(
1074        &self,
1075        params: PwmInstManageOrderParams,
1076    ) -> Result<PwmInstManageOrderResponse> {
1077        self.post("/v5/earn/pwm/asset-manager/manage-order", &params)
1078            .await
1079    }
1080
1081    pub async fn pwm_inst_settle_profit(
1082        &self,
1083        params: PwmInstSettleProfitParams,
1084    ) -> Result<PwmInstSettleProfitResponse> {
1085        self.post("/v5/earn/pwm/asset-manager/settle-profit", &params)
1086            .await
1087    }
1088
1089    pub async fn pwm_invest_more(
1090        &self,
1091        params: PwmInvestMoreParams,
1092    ) -> Result<PwmInvestMoreResponse> {
1093        self.post("/v5/earn/pwm/investment-plan/invest-more", &params)
1094            .await
1095    }
1096
1097    pub async fn pwm_list_investment_plans(
1098        &self,
1099        plan_id: Option<&str>,
1100        status: Option<&str>,
1101        limit: Option<i64>,
1102        cursor: Option<&str>,
1103    ) -> Result<PwmListInvestmentPlansResponse> {
1104        let limit_str = limit.map(|v| v.to_string());
1105
1106        let mut params: Vec<(&str, &str)> = vec![];
1107        if let Some(v) = plan_id {
1108            params.push(("planId", v));
1109        }
1110        if let Some(v) = status {
1111            params.push(("status", v));
1112        }
1113        if let Some(ref v) = limit_str {
1114            params.push(("limit", v.as_str()));
1115        }
1116        if let Some(v) = cursor {
1117            params.push(("cursor", v));
1118        }
1119
1120        self.get("/v5/earn/pwm/investment-plan/all", &params).await
1121    }
1122
1123    #[allow(clippy::too_many_arguments)] // TODO(api-ergonomics): convert positional args to a typed `*Params` struct
1124    pub async fn pwm_list_order(
1125        &self,
1126        plan_id: Option<&str>,
1127        category: Option<Category>,
1128        order_type: Option<&str>,
1129        status: Option<&str>,
1130        start_time: Option<i64>,
1131        end_time: Option<i64>,
1132        limit: Option<i64>,
1133        cursor: Option<&str>,
1134        order_link_id: Option<&str>,
1135    ) -> Result<PwmListOrderResponse> {
1136        let cat_str = category.map(|c| c.to_string());
1137        let start_time_str = start_time.map(|v| v.to_string());
1138        let end_time_str = end_time.map(|v| v.to_string());
1139        let limit_str = limit.map(|v| v.to_string());
1140
1141        let mut params: Vec<(&str, &str)> = vec![];
1142        if let Some(v) = plan_id {
1143            params.push(("planId", v));
1144        }
1145        if let Some(ref v) = cat_str {
1146            params.push(("category", v.as_str()));
1147        }
1148        if let Some(v) = order_type {
1149            params.push(("type", v));
1150        }
1151        if let Some(v) = status {
1152            params.push(("status", v));
1153        }
1154        if let Some(ref v) = start_time_str {
1155            params.push(("startTime", v.as_str()));
1156        }
1157        if let Some(ref v) = end_time_str {
1158            params.push(("endTime", v.as_str()));
1159        }
1160        if let Some(ref v) = limit_str {
1161            params.push(("limit", v.as_str()));
1162        }
1163        if let Some(v) = cursor {
1164            params.push(("cursor", v));
1165        }
1166        if let Some(v) = order_link_id {
1167            params.push(("orderLinkId", v));
1168        }
1169
1170        self.get("/v5/earn/pwm/investment-plan/order", &params)
1171            .await
1172    }
1173
1174    pub async fn pwm_list_product_cards(&self) -> Result<PwmListProductCardsResponse> {
1175        // Public endpoint per Bybit V5 docs:
1176        // https://bybit-exchange.github.io/docs/v5/finance/pwm/customize-plan/product
1177        self.get_public("/v5/earn/pwm/customize-plan/product", &[])
1178            .await
1179    }
1180
1181    pub async fn pwm_query_fund_transfer_result(
1182        &self,
1183        transfer_id: Option<&str>,
1184        from_user_id: Option<i64>,
1185    ) -> Result<PwmQueryFundTransferResultResponse> {
1186        let from_user_id_str = from_user_id.map(|v| v.to_string());
1187
1188        let mut params: Vec<(&str, &str)> = vec![];
1189        if let Some(v) = transfer_id {
1190            params.push(("transferId", v));
1191        }
1192        if let Some(ref v) = from_user_id_str {
1193            params.push(("fromUserId", v.as_str()));
1194        }
1195
1196        self.get("/v5/earn/pwm/query-fund-transfer-result", &params)
1197            .await
1198    }
1199
1200    pub async fn pwm_redeem(&self, params: PwmRedeemParams) -> Result<PwmRedeemResponse> {
1201        self.post("/v5/earn/pwm/investment-plan/redeem", &params)
1202            .await
1203    }
1204
1205    pub async fn pwm_subscribe(&self, params: PwmSubscribeParams) -> Result<PwmSubscribeResponse> {
1206        self.post("/v5/earn/pwm/investment-plan/subscribe", &params)
1207            .await
1208    }
1209
1210    pub async fn redeem_fixed_term(
1211        &self,
1212        params: RedeemFixedTermParams,
1213    ) -> Result<RedeemFixedTermResponse> {
1214        self.post("/v5/earn/fixed-term/redeem", &params).await
1215    }
1216
1217    // FIXME(typed-signature): falls back to `serde_json::Value` because the
1218    // OpenAPI spec referenced a response/request type that gen-sdk-rust could
1219    // not auto-resolve. Replace with a typed struct in a follow-up PR.
1220    pub async fn reinvest_liquidity(
1221        &self,
1222        params: ReinvestLiquidityParams,
1223    ) -> Result<serde_json::Value> {
1224        self.post("/v5/earn/liquidity-mining/reinvest", &params)
1225            .await
1226    }
1227
1228    // FIXME(typed-signature): falls back to `serde_json::Value` because the
1229    // OpenAPI spec referenced a response/request type that gen-sdk-rust could
1230    // not auto-resolve. Replace with a typed struct in a follow-up PR.
1231    pub async fn remove_liquidity(
1232        &self,
1233        params: RemoveLiquidityParams,
1234    ) -> Result<serde_json::Value> {
1235        self.post("/v5/earn/liquidity-mining/remove-liquidity", &params)
1236            .await
1237    }
1238
1239    // FIXME(typed-signature): falls back to `serde_json::Value` because the
1240    // OpenAPI spec referenced a response/request type that gen-sdk-rust could
1241    // not auto-resolve. Replace with a typed struct in a follow-up PR.
1242    pub async fn set_fixed_term_auto_invest(
1243        &self,
1244        params: SetFixedTermAutoInvestParams,
1245    ) -> Result<serde_json::Value> {
1246        self.post("/v5/earn/fixed-term/position/auto-invest", &params)
1247            .await
1248    }
1249}