-
Notifications
You must be signed in to change notification settings - Fork 186
fix: auto-refresh balance cache on stale-balance order failures #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -485,6 +485,11 @@ fn parse_date(s: &str) -> Result<NaiveDate> { | |
| .map_err(|_| anyhow::anyhow!("Invalid date: expected YYYY-MM-DD format")) | ||
| } | ||
|
|
||
| /// detect the CLOB "not enough balance / allowance" error so we can refresh and retry | ||
| fn is_balance_error(e: &polymarket_client_sdk::error::Error) -> bool { | ||
| e.to_string().contains("not enough balance") | ||
| } | ||
|
|
||
| #[allow(clippy::too_many_lines)] | ||
| pub async fn execute( | ||
| args: ClobArgs, | ||
|
|
@@ -676,18 +681,44 @@ pub async fn execute( | |
| let size_dec = | ||
| Decimal::from_str(&size).map_err(|_| anyhow::anyhow!("Invalid size: {size}"))?; | ||
|
|
||
| let sdk_side = Side::from(side); | ||
| let sdk_order_type = OrderType::from(order_type); | ||
| let token_id = parse_token_id(&token)?; | ||
|
|
||
| let order = client | ||
| .limit_order() | ||
| .token_id(parse_token_id(&token)?) | ||
| .side(Side::from(side)) | ||
| .token_id(token_id) | ||
| .side(sdk_side) | ||
| .price(price_dec) | ||
| .size(size_dec) | ||
| .order_type(OrderType::from(order_type)) | ||
| .order_type(sdk_order_type.clone()) | ||
| .post_only(post_only) | ||
| .build() | ||
| .await?; | ||
| let order = client.sign(&signer, order).await?; | ||
| let result = client.post_order(order).await?; | ||
| let result = match client.post_order(order).await { | ||
| Ok(r) => r, | ||
| Err(e) if is_balance_error(&e) => { | ||
| eprintln!("Balance cache may be stale, refreshing and retrying..."); | ||
| let req = BalanceAllowanceRequest::builder() | ||
| .asset_type(AssetType::Collateral) | ||
| .build(); | ||
| let _ = client.update_balance_allowance(req).await; | ||
| let order = client | ||
| .limit_order() | ||
| .token_id(token_id) | ||
| .side(sdk_side) | ||
| .price(price_dec) | ||
| .size(size_dec) | ||
| .order_type(sdk_order_type) | ||
| .post_only(post_only) | ||
| .build() | ||
| .await?; | ||
| let order = client.sign(&signer, order).await?; | ||
| client.post_order(order).await? | ||
| } | ||
| Err(e) => return Err(e.into()), | ||
| }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicated retry logic across order command pathsLow Severity The balance-refresh-and-retry logic is duplicated nearly identically between the Additional Locations (1) |
||
| print_post_order_result(&result, output)?; | ||
| } | ||
|
|
||
|
|
@@ -757,16 +788,39 @@ pub async fn execute( | |
| Amount::usdc(amount_dec)? | ||
| }; | ||
|
|
||
| let sdk_order_type = OrderType::from(order_type); | ||
| let token_id = parse_token_id(&token)?; | ||
|
|
||
| let order = client | ||
| .market_order() | ||
| .token_id(parse_token_id(&token)?) | ||
| .token_id(token_id) | ||
| .side(sdk_side) | ||
| .amount(parsed_amount) | ||
| .order_type(OrderType::from(order_type)) | ||
| .order_type(sdk_order_type.clone()) | ||
| .build() | ||
| .await?; | ||
| let order = client.sign(&signer, order).await?; | ||
| let result = client.post_order(order).await?; | ||
| let result = match client.post_order(order).await { | ||
| Ok(r) => r, | ||
| Err(e) if is_balance_error(&e) => { | ||
| eprintln!("Balance cache may be stale, refreshing and retrying..."); | ||
| let req = BalanceAllowanceRequest::builder() | ||
| .asset_type(AssetType::Collateral) | ||
| .build(); | ||
| let _ = client.update_balance_allowance(req).await; | ||
| let order = client | ||
| .market_order() | ||
| .token_id(token_id) | ||
| .side(sdk_side) | ||
| .amount(parsed_amount) | ||
| .order_type(sdk_order_type) | ||
| .build() | ||
| .await?; | ||
| let order = client.sign(&signer, order).await?; | ||
| client.post_order(order).await? | ||
| } | ||
| Err(e) => return Err(e.into()), | ||
| }; | ||
| print_post_order_result(&result, output)?; | ||
| } | ||
|
|
||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retry always refreshes collateral, ignoring sell-side conditional tokens
Medium Severity
The retry logic always refreshes
AssetType::Collateralregardless of order side. For sell orders, a "not enough balance" error indicates stale conditional token (shares) balance, not collateral. Refreshing collateral won't fix that, so the retry will always fail for sell-side stale-balance errors. Additionally, conditional balance updates require atoken_id(as seen in the manualUpdateBalancecommand), which the retry code doesn't provide.Additional Locations (1)
src/commands/clob.rs#L806-L810