> ## Documentation Index
> Fetch the complete documentation index at: https://docs.limitless.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Curated Packs

> Quote, buy, track, and claim curated Packs with the Rust SDK.

Requires `limitless-exchange-rust-sdk` **1.3.0 or later**. Read the [Partner Packs workflow](/developers/partner-packs.md) for account ownership, durable retries, amounts, and recovery rules.

## Configure a server-side client

```rust theme={null}
use std::time::{SystemTime, UNIX_EPOCH};
use limitless_exchange_rust_sdk::{
    Client, HmacCredentials, PackBuyParams, PackQuoteParams, PackPosition,
    PackClaimResponse, PackPollOptions, pack_min_payout,
};

type ExampleResult<T> = Result<T, Box<dyn std::error::Error>>;

fn create_client() -> ExampleResult<Client> {
    let http = Client::builder()
        .hmac_credentials(HmacCredentials {
            token_id: std::env::var("LMTS_TOKEN_ID")?,
            secret: std::env::var("LMTS_TOKEN_SECRET")?,
        })
        .build()?;
    Ok(Client::from_http_client(http)?)
}
```

Use `trading` and `delegated_signing` scopes, plus `account_creation` for allowances. Reuse the client and do not configure a global `x-on-behalf-of` header.

## Browse and check approval

```rust theme={null}
async fn check_vault_approval(client: &Client, profile_id: i32) -> ExampleResult<()> {
    let state = client.partner_accounts.check_allowances(profile_id).await?;
    let confirmed = state.targets.iter().any(|target| {
        target.target_type == "USDC_ALLOWANCE"
            && target.label == "parlay-treasury-vault"
            && target.confirmed && target.status == "confirmed"
    });
    if !confirmed {
        return Err("Confirm the Pack vault allowance before preparing a buy".into());
    }
    Ok(())
}
```

Use `client.packs.list().await?`, `search("football").await?`, or `get(pack_id).await?` to browse. If approval is missing, call `client.partner_accounts.retry_allowances(profile_id).await?` and poll `check_allowances` with a bounded wait until the vault is confirmed. Repair uses the [unlimited-approval policy](/developers/partner-packs.md#2-confirm-the-pack-vault-allowance).

## Prepare once, then submit the saved request

```rust theme={null}
async fn prepare_purchase(
    client: &Client, profile_id: i32, pack_id: &str, purchase_key: &str,
) -> ExampleResult<PackBuyParams> {
    check_vault_approval(client, profile_id).await?;
    let quote = client.packs.quote(&PackQuoteParams {
        pack_id: pack_id.into(),
        stake_amount: "10000000".into(),
    }).await?;
    if !quote.eligible {
        return Err(format!("Ineligible: {:?}", quote.ineligible_reasons).into());
    }
    let payout = quote.potential_payout.as_deref().ok_or("Missing quoted payout")?;
    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    Ok(PackBuyParams {
        on_behalf_of: profile_id,
        pack_id: pack_id.into(),
        stake_amount: "10000000".into(), // 10 USDC
        min_payout_amount: pack_min_payout(payout, 100)?,
        deadline: (now + 120).to_string(),
        idempotency_key: purchase_key.into(),
    })
}

async fn submit_saved_purchase(
    client: &Client, saved: &PackBuyParams,
) -> ExampleResult<PackPosition> {
    let buy = client.packs.buy(saved).await?;
    Ok(client.packs.wait_for_position(
        saved.on_behalf_of, &buy.position.id, PackPollOptions::default(),
    ).await?)
}
```

Durably save the complete `PackBuyParams` before submission, using `serde_json` and your application store. Associate the record with the environment, partner, and managed account. On restart, reload it; do not call preparation again for the same purchase.

Polling continues through position `FAILED` and stops on `VAULT_REVERTED`. Defaults are 60 attempts and a 5-second interval. On timeout or an ambiguous result, retain and replay the request unchanged, including its deadline. See [explicit renewal signals](/developers/partner-packs.md#recover-buy-errors).

## Read positions and claim

```rust theme={null}
async fn claim_winning_position(
    client: &Client, profile_id: i32, position_id: &str,
) -> ExampleResult<PackClaimResponse> {
    let position = client.packs.get_position(profile_id, position_id).await?;
    if position.status != "WON" {
        return Err("Position is not WON".into());
    }
    Ok(client.packs.claim(
        profile_id, position_id, PackPollOptions::default(),
    ).await?)
}
```

Use `list_positions(profile_id, 20, 0)` to list purchases, `claim_once(profile_id, position_id)` for one workflow step, and `confirm_claim(profile_id, position_id, redemption_tx_hash)` to recover a lost submission response with the mined redemption receipt. All are async.

After claiming, the position remains `WON`; verify `claim_tx_hash` or the claim response's `CLAIMED` status. `position.wallet` identifies the managed recipient.

## Classify errors

`limitless_exchange_rust_sdk::classify_pack_error(&error)` returns typed details. Its `kind` is a `PackErrorKind`: `AllowanceRequired`, `NotSubmitted`, `PriceDrifted`, `IntentExpired`, `WalletBusy`, `RateLimited`, `SubmissionUnknown`, `TransactionReverted`, or `Other`.

The optional fields include `token_address`, `spender`, `required_allowance`, `submission_state`, `parlay_id`, and `recovery`. Classification does not retry.

The claim helper surfaces API errors. Back off on rate limits and wallet-busy conflicts, then resume the same position. For `SubmissionUnknown`, reconcile the mined redemption transaction and use `confirm_claim`; do not blindly resubmit.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.