use std::sync::Arc;
use alloy_signer_local::PrivateKeySigner;
use cow_bridging::{
BridgeError, SigningStepManager,
provider::HookBridgeProvider,
sdk::{BridgeQuoteAndPost, GetBridgeSignedHookContext, get_bridge_signed_hook},
types::QuoteBridgeRequest,
utils::determine_intermediate_token,
};
use cow_chains::SupportedChainId;
use cow_errors::CowError;
use cow_trading::{OrderPostingResult, SwapAdvancedSettings, TradeParameters, TradingSdk};
use cow_types::OrderKind;
#[allow(
missing_debug_implementations,
reason = "carries `&dyn HookBridgeProvider`; manual impl is noisy"
)]
pub struct PostCrossChainOrderContext<'a> {
pub request: &'a QuoteBridgeRequest,
pub hook_provider: &'a dyn HookBridgeProvider,
pub quote_and_post: &'a BridgeQuoteAndPost,
pub trading_sdk: &'a TradingSdk,
pub hook_signer: &'a Arc<PrivateKeySigner>,
pub hook_deadline: Option<u64>,
pub advanced_settings: Option<&'a SwapAdvancedSettings>,
pub signing_step_manager: Option<&'a SigningStepManager>,
}
pub async fn post_cross_chain_order(
ctx: PostCrossChainOrderContext<'_>,
) -> Result<OrderPostingResult, CowError> {
if let Some(mgr) = ctx.signing_step_manager {
mgr.fire_before_bridging_sign().await?;
}
let chain_id = SupportedChainId::try_from(ctx.request.sell_chain_id).map_err(|e| {
CowError::Config(format!(
"unsupported sell_chain_id {} for cross-chain post: {e}",
ctx.request.sell_chain_id,
))
})?;
let deadline = ctx.hook_deadline.unwrap_or_else(|| u64::from(u32::MAX));
let hook_gas_limit = ctx
.quote_and_post
.bridge
.bridge_call_details
.as_ref()
.and_then(|d| d.pre_authorized_bridging_hook.post_hook.gas_limit.parse::<u64>().ok())
.unwrap_or_else(|| cow_bridging::DEFAULT_GAS_COST_FOR_HOOK_ESTIMATION);
let sign_result = get_bridge_signed_hook(
ctx.hook_provider,
ctx.request,
GetBridgeSignedHookContext {
signer: ctx.hook_signer.as_ref(),
hook_gas_limit,
chain_id,
deadline,
},
)
.await
.map_err(bridge_to_cow_err);
let sign_output = match sign_result {
Ok(out) => out,
Err(e) => {
if let Some(mgr) = ctx.signing_step_manager {
mgr.fire_on_bridging_sign_error(&e);
}
return Err(e);
}
};
if let Some(mgr) = ctx.signing_step_manager {
mgr.fire_after_bridging_sign().await?;
}
let intermediate = resolve_intermediate_token(ctx.request, ctx.hook_provider).await?;
let app_data_value = build_app_data_value(&ctx, &sign_output.hook)?;
let settings_for_quote = if let Some(user_settings) = ctx.advanced_settings {
SwapAdvancedSettings {
app_data: Some(app_data_value),
slippage_bps: user_settings.slippage_bps,
partner_fee: user_settings.partner_fee.clone(),
}
} else {
SwapAdvancedSettings::default().with_app_data(app_data_value)
};
let intermediate_evm =
intermediate.address.to_evm().ok_or_else(post_flow_non_evm_intermediate)?;
let trade_params = TradeParameters {
kind: OrderKind::Sell,
sell_token: ctx.request.sell_token,
sell_token_decimals: ctx.request.sell_token_decimals,
buy_token: intermediate_evm,
buy_token_decimals: intermediate.decimals,
amount: ctx.request.sell_amount,
slippage_bps: Some(ctx.request.slippage_bps),
receiver: parse_receiver(&sign_output.hook.recipient).ok(),
valid_for: None,
valid_to: Some(if deadline > u64::from(u32::MAX) { u32::MAX } else { deadline as u32 }),
partially_fillable: None,
partner_fee: None,
};
let quote_results = ctx
.trading_sdk
.get_quote_only_with_settings(ctx.request.account, trade_params, &settings_for_quote)
.await?;
if let Some(mgr) = ctx.signing_step_manager {
mgr.fire_before_order_sign().await?;
}
let post_result = ctx.trading_sdk.post_swap_order_from_quote("e_results, None).await;
let posted = match post_result {
Ok(r) => r,
Err(e) => {
if let Some(mgr) = ctx.signing_step_manager {
mgr.fire_on_order_sign_error(&e);
}
return Err(e);
}
};
if let Some(mgr) = ctx.signing_step_manager {
mgr.fire_after_order_sign().await?;
}
Ok(posted)
}
async fn resolve_intermediate_token(
request: &QuoteBridgeRequest,
provider: &dyn HookBridgeProvider,
) -> Result<cow_bridging::types::IntermediateTokenInfo, CowError> {
let candidates = provider
.get_intermediate_tokens(request)
.await
.map_err(|e| CowError::Config(format!("failed to list intermediate tokens: {e}")))?;
if candidates.is_empty() {
return Err(CowError::Config("no intermediate tokens available at post time".into()));
}
let candidate_addrs: Vec<alloy_primitives::Address> =
candidates.iter().filter_map(|t| t.address.to_evm()).collect();
let chosen = determine_intermediate_token(
request.sell_chain_id,
request.sell_token,
&candidate_addrs,
&foldhash::HashSet::default(),
false,
)
.map_err(|e: BridgeError| CowError::Config(format!("intermediate selection failed: {e}")))?;
candidates
.iter()
.find(|t| t.address == chosen)
.cloned()
.ok_or_else(|| CowError::Config("chosen intermediate missing from candidate list".into()))
}
fn build_app_data_value(
ctx: &PostCrossChainOrderContext<'_>,
hook: &cow_bridging::types::BridgeHook,
) -> Result<serde_json::Value, CowError> {
let mut metadata = ctx
.advanced_settings
.and_then(|s| s.app_data.as_ref())
.and_then(|v| v.as_object().cloned())
.unwrap_or_default();
metadata.insert(
"bridging".to_owned(),
serde_json::json!({ "providerId": ctx.hook_provider.info().dapp_id }),
);
metadata.insert("hooks".to_owned(), serde_json::json!({ "post": [&hook.post_hook] }));
Ok(serde_json::json!({
"version": "1.4.0",
"appCode": "CoW Bridging",
"metadata": metadata,
}))
}
fn parse_receiver(recipient: &str) -> Result<alloy_primitives::Address, CowError> {
recipient
.parse::<alloy_primitives::Address>()
.map_err(|e| CowError::Parse { field: "bridge_hook.recipient", reason: e.to_string() })
}
fn bridge_to_cow_err(e: BridgeError) -> CowError {
if let BridgeError::Cow(inner) = e { inner } else { CowError::Config(e.to_string()) }
}
fn post_flow_non_evm_intermediate() -> CowError {
CowError::Config("intermediate token must be an EVM address for post flow".into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn post_flow_non_evm_intermediate_returns_config_error() {
let err = post_flow_non_evm_intermediate();
assert!(matches!(&err, CowError::Config(m) if m.contains("EVM address")), "got {err:?}");
}
}