Skip to content

Commit b043bc2

Browse files
juntaoclaude
andcommitted
fix: account for HL Bridge2 fee in chained withdrawal
HL Bridge2 charges ~$1 fee, so $15 becomes $14 on Arbitrum. The code was polling until the full requested amount arrived, which never happened, causing a 10-minute timeout. Now tracks initial balance and detects any increase. Uses the actual post-fee amount for the Across bridge quote instead of the original amount. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6e3b444 commit b043bc2

1 file changed

Lines changed: 27 additions & 12 deletions

File tree

src/commands/withdraw.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,22 @@ async fn withdraw_usdc_hl_bridged(
271271
ethers::types::Bytes::from(data)
272272
};
273273

274+
// Record initial Arbitrum USDC balance before HL withdrawal
274275
let mut arb_usdc_balance = U256::zero();
275-
let required = ethers::utils::parse_units(amount, 6)
276-
.context("Invalid amount")?
277-
.into();
276+
{
277+
let call_tx = ethers::types::TransactionRequest::new()
278+
.to(usdc_addr)
279+
.data(balance_calldata.clone());
280+
if let Ok(result) = arb_client.call(&call_tx.into(), None).await {
281+
if result.len() >= 32 {
282+
arb_usdc_balance = U256::from_big_endian(&result[..32]);
283+
}
284+
}
285+
}
286+
let initial_arb_balance = arb_usdc_balance;
278287

288+
// HL Bridge2 charges a ~$1 fee, so we check for any increase over
289+
// the initial balance rather than the full requested amount.
279290
for attempt in 1..=20 {
280291
tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;
281292
let call_tx = ethers::types::TransactionRequest::new()
@@ -291,32 +302,36 @@ async fn withdraw_usdc_hl_bridged(
291302
" Checking Arbitrum USDC... ${:.2} (attempt {}/20)",
292303
bal_f, attempt
293304
);
294-
if arb_usdc_balance >= required {
305+
if arb_usdc_balance > initial_arb_balance {
295306
break;
296307
}
297308
}
298309

299-
if arb_usdc_balance < required {
310+
if arb_usdc_balance <= initial_arb_balance {
300311
bail!(
301-
"USDC did not arrive on Arbitrum after 10 minutes. Balance: {}, needed: {}",
312+
"USDC did not arrive on Arbitrum after 10 minutes. Balance: {}",
302313
arb_usdc_balance,
303-
required
304314
);
305315
}
306316

307-
eprintln!(" ✅ USDC arrived on Arbitrum!");
317+
let arrived_amount = arb_usdc_balance - initial_arb_balance;
318+
let arrived_usdc = format!("{}.{:06}", arrived_amount / 1_000_000, arrived_amount % 1_000_000);
319+
eprintln!(
320+
" ✅ USDC arrived on Arbitrum! (${} after Bridge2 fee)",
321+
arrived_amount.as_u128() as f64 / 1e6
322+
);
308323

309324
// Step 3: Bridge USDC from Arbitrum → destination via Across
310325
eprintln!(
311326
"Step 2: Bridging USDC from Arbitrum → {} via Across...",
312327
dest_chain.name()
313328
);
314329

315-
// Re-fetch Across quote now that USDC is on Arbitrum.
316-
// The original quote (fetched before HL withdrawal) has stale calldata
317-
// because the HL Bridge2 step takes ~4 minutes.
330+
// Re-fetch Across quote using the actual arrived amount (after Bridge2 fee).
331+
// The original quote used the pre-fee amount and has stale calldata.
318332
eprintln!(" Refreshing Across bridge quote...");
319-
let quote = bridge::get_across_quote_reverse(dest_chain, amount, &cfg.address).await?;
333+
let quote =
334+
bridge::get_across_quote_reverse(dest_chain, &arrived_usdc, &cfg.address).await?;
320335
let output_amount = quote
321336
.expected_output_amount
322337
.as_deref()

0 commit comments

Comments
 (0)