Skip to content

Commit 40404b9

Browse files
authored
Merge pull request #3 from BlockheaderWeb3-Community/auto_ekubo_swap
feat: create ekubo_auto_swap()
2 parents 07ef3fb + 25ca30f commit 40404b9

File tree

3 files changed

+94
-40
lines changed

3 files changed

+94
-40
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ thiserror = "2.0.16"
2323
serde = {version="1.0.219", features=["derive"]}
2424
tokio = { version = "1.0", features = ["full"] }
2525
reqwest = { version = "0.12", features = ["json"] }
26+
serde_json = "1.0"
2627
url = "2.5"
2728
hex = "0.4"
2829
starknet = "0.17.0"

src/swappr.rs

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use crate::{
1515
constant::{TokenAddress, u128_to_uint256},
1616
types::connector::AutoSwappr,
1717
};
18+
use reqwest::Client;
19+
use serde_json::json;
1820
#[allow(dead_code)]
1921
type EkuboResponse = Result<
2022
starknet::core::types::InvokeTransactionResult,
@@ -110,46 +112,76 @@ impl AutoSwappr {
110112
}
111113

112114
// pub async fn ekubo_auto_swap(){
113-
// // todo steph
114-
// // approve contract to spend
115-
// // sent a post request to auto swapper backend
116-
// // post request arg will look like this
117-
// // ====== wallet_address, user address
118-
// // ======= to_token,
119-
// // ======= from_token,
120-
// // ======= swap_amount,
121-
122-
// // below is how the approval fall will look
123-
// // handle error
124-
125-
// // let account = approver_signer_account();
126-
127-
// // if !is_valid_address(token) {
128-
// // return Err("INVALID TOKEN ADDRESS".to_string());
129-
// // }
130-
131-
// // let spender = contract_address_felt();
132-
133-
// // let token = Felt::from_hex(token).expect("TOKEN ADDRESS NOT PROVIDED");
134-
135-
// // // Convert amount to uint256 (split into low and high parts)
136-
// // let (amount_low, amount_high) = u128_to_uint256(amount);
137-
138-
// // // Prepare the calldata: [spender, amount_low, amount_high]
139-
// // let calldata = vec![spender, amount_low, amount_high];
140-
141-
// // let call = Call {
142-
// // to: token,
143-
// // selector: get_selector_from_name("approve").unwrap(),
144-
// // calldata,
145-
// // };
146-
// // let execution = account
147-
// // .execute_v3(vec![call])
148-
// // .send()
149-
// // .await
150-
// // .map_err(|e| e.to_string())?;
151-
// // Ok(execution.transaction_hash)
152-
// }
115+
// Implemented: approve token and notify backend for auto-swap
116+
pub async fn ekubo_auto_swap(
117+
&mut self,
118+
token_from: Felt,
119+
token_to: Felt,
120+
amount: u128,
121+
backend_url: &str,
122+
) -> Result<String, String> {
123+
if amount == 0 {
124+
return Err("ZERO SWAP AMOUNT".to_string());
125+
}
126+
127+
// ensure token is supported to derive decimals
128+
let token_decimal = TokenAddress::new()
129+
.get_token_info_by_address(token_from)
130+
.map_err(|e| e.to_string())?
131+
.decimals;
132+
133+
let actual_amount = amount * 10_u128.pow(token_decimal as u32);
134+
let (amount_low, amount_high) = u128_to_uint256(actual_amount);
135+
136+
// Prepare approve call to allow contract to spend `token_from`
137+
let approve_call = Call {
138+
to: token_from,
139+
selector: selector!("approve"),
140+
calldata: vec![self.contract_address, amount_low, amount_high],
141+
};
142+
143+
// set preconfirmed block for querying
144+
self.account
145+
.set_block_id(BlockId::Tag(BlockTag::PreConfirmed));
146+
147+
// send approve transaction
148+
let approve_result = self
149+
.account
150+
.execute_v3(vec![approve_call])
151+
.send()
152+
.await
153+
.map_err(|e| format!("approve failed: {}", e))?;
154+
155+
// Prepare payload for backend
156+
let payload = json!({
157+
"wallet_address": format!("0x{:x}", self.account.address()),
158+
"user_address": format!("0x{:x}", self.account.address()),
159+
"to_token": format!("0x{:x}", token_to),
160+
"from_token": format!("0x{:x}", token_from),
161+
"swap_amount": actual_amount.to_string(),
162+
"approve_tx_hash": format!("0x{:x}", approve_result.transaction_hash),
163+
});
164+
165+
let client = Client::new();
166+
let resp = client
167+
.post(backend_url)
168+
.json(&payload)
169+
.send()
170+
.await
171+
.map_err(|e| format!("network error: {}", e))?;
172+
173+
let status = resp.status();
174+
let text = resp
175+
.text()
176+
.await
177+
.map_err(|e| format!("response read error: {}", e))?;
178+
179+
if status.is_success() {
180+
Ok(text)
181+
} else {
182+
Err(format!("backend error: {} - {}", status, text))
183+
}
184+
}
153185
}
154186

155187
#[cfg(test)]
@@ -184,4 +216,24 @@ mod tests {
184216
// assert!(result.await.clone().is_ok());
185217
println!("test complete {:?}", result.await.ok());
186218
}
219+
220+
#[tokio::test]
221+
#[ignore = "owner address, private key and backend required to run the test"]
222+
async fn it_works_auto() {
223+
// This test exercises `ekubo_auto_swap` flow: approve + notify backend.
224+
// It is ignored by default because it requires a funded wallet and a reachable backend.
225+
let rpc_url = "YOUR MAINNET RPC".to_string();
226+
let account_address = "YOUR WALLET ADDRESS".to_string();
227+
let private_key = "YOUR WALLET PRIVATE KEY".to_string();
228+
let mut swapper = AutoSwappr::config(rpc_url, account_address, private_key);
229+
230+
// Use STRK -> USDC for a tiny amount (1 unit). Backend URL is a placeholder and
231+
// should be replaced with a real auto-swapper endpoint when running the test.
232+
let backend_url = "https://example.com/api/auto-swap";
233+
let result = swapper.ekubo_auto_swap(*STRK, *USDC, 1, backend_url);
234+
235+
// Print the result (Ok response body or Err description). The test is ignored
236+
// so it won't run in CI unless explicitly enabled.
237+
println!("auto swap test result: {:?}", result.await);
238+
}
187239
}

0 commit comments

Comments
 (0)