metaflux_client/mip3/
auction.rs1use std::time::Duration;
19
20use serde::{Deserialize, Serialize};
21use serde_json::{Value, json};
22use tokio::time::{Instant, sleep};
23
24use crate::Client;
25use crate::error::ClientError;
26use crate::wallet::{Address, Wallet};
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum AuctionKind {
35 TokenRegister,
37 PerpDeploy,
40 SpotPairDeploy,
43}
44
45impl AuctionKind {
46 #[must_use]
48 pub fn type_id(&self) -> &'static str {
49 match self {
50 Self::TokenRegister => "token_register",
51 Self::PerpDeploy => "perp_deploy",
52 Self::SpotPairDeploy => "spot_pair_deploy",
53 }
54 }
55}
56
57#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "snake_case")]
60pub struct AuctionBid {
61 pub kind: AuctionKind,
63 pub bid_amount_usdc_cents: u128,
66}
67
68#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "snake_case")]
71pub struct BidReceipt {
72 pub round_id: u64,
74 pub bidder: Address,
76 pub accepted_amount_usdc_cents: u128,
78 pub status: String,
80}
81
82impl Client {
83 pub async fn submit_gas_auction_bid(
93 &self,
94 wallet: &Wallet,
95 bid: AuctionBid,
96 ) -> Result<BidReceipt, ClientError> {
97 let action = json!({
98 "type": "submit_gas_auction_bid",
99 "bidder": wallet.address(),
100 "kind": bid.kind,
101 "bid_amount_usdc_cents": bid.bid_amount_usdc_cents,
102 });
103 self.rest().exchange().post_signed(wallet, action).await
104 }
105
106 pub async fn check_deploy_credit(&self, address: Address) -> Result<u32, ClientError> {
114 #[derive(Deserialize)]
116 struct CreditResp {
117 credit_count: u32,
118 }
119 let body = json!({
120 "type": "deploy_credit",
121 "address": address,
122 });
123 let resp: CreditResp = self.rest().info().raw(body).await.and_then(|v: Value| {
124 serde_json::from_value::<CreditResp>(v).map_err(ClientError::from)
125 })?;
126 Ok(resp.credit_count)
127 }
128
129 pub async fn await_deploy_credit(
141 &self,
142 wallet: &Wallet,
143 max_wait: Duration,
144 ) -> Result<(), ClientError> {
145 if max_wait.is_zero() {
146 return Err(ClientError::Validation(
147 "await_deploy_credit: max_wait must be > 0".into(),
148 ));
149 }
150 let deadline = Instant::now() + max_wait;
151 let mut delay = Duration::from_millis(500);
152 let cap = Duration::from_secs(5);
153
154 loop {
155 let credits = self.check_deploy_credit(wallet.address()).await?;
156 if credits > 0 {
157 return Ok(());
158 }
159 let now = Instant::now();
160 if now >= deadline {
161 return Err(ClientError::Validation(format!(
162 "await_deploy_credit: timed out after {:?} without seeing credit",
163 max_wait
164 )));
165 }
166 let remaining = deadline - now;
167 let sleep_for = delay.min(remaining);
168 sleep(sleep_for).await;
169 delay = (delay * 2).min(cap);
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn auction_kind_serializes_snake_case() {
180 assert_eq!(
181 serde_json::to_string(&AuctionKind::TokenRegister).unwrap(),
182 "\"token_register\""
183 );
184 assert_eq!(
185 serde_json::to_string(&AuctionKind::PerpDeploy).unwrap(),
186 "\"perp_deploy\""
187 );
188 assert_eq!(
189 serde_json::to_string(&AuctionKind::SpotPairDeploy).unwrap(),
190 "\"spot_pair_deploy\""
191 );
192 }
193
194 #[test]
195 fn auction_bid_round_trips() {
196 let b = AuctionBid {
197 kind: AuctionKind::PerpDeploy,
198 bid_amount_usdc_cents: 150_000_000, };
200 let j = serde_json::to_string(&b).unwrap();
201 let dec: AuctionBid = serde_json::from_str(&j).unwrap();
202 assert_eq!(b, dec);
203 }
204
205 #[test]
206 fn bid_receipt_round_trips() {
207 let r = BidReceipt {
208 round_id: 42,
209 bidder: Address::ZERO,
210 accepted_amount_usdc_cents: 100_000_000, status: "accepted".into(),
212 };
213 let j = serde_json::to_string(&r).unwrap();
214 let dec: BidReceipt = serde_json::from_str(&j).unwrap();
215 assert_eq!(r, dec);
216 }
217
218 #[tokio::test]
219 async fn await_deploy_credit_rejects_zero_wait() {
220 let c = Client::new("https://devnet-gateway.mtf.exchange").unwrap();
221 let w = Wallet::random_for_testing();
222 let err = c.await_deploy_credit(&w, Duration::ZERO).await.unwrap_err();
223 assert!(matches!(err, ClientError::Validation(_)));
224 }
225}