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

# Async Deposits

> When a deposit queues instead of settling, claim it to finalize or cancel it to recover funds

<Note>
  Action endpoints require the user to be a registered [Turtle member](/sdk/authentication/register-wallet). {/* VERIFY: canonical auth header for claim/cancel. The original action pages carried no API-key note on these two endpoints; the public spec defines no security scheme. Confirm whether an API key is required and which header. */}
</Note>

Some vaults do not settle a deposit in one transaction. They queue it, process it on their own schedule, and only then is the position active. For those vaults the user has a follow-up decision once funds are pending: claim to finalize, or cancel to take the funds back. Both branches start from the same pending state and live on this page.

This applies only to opportunities with `complex` settlement (for example Mellow and Lagoon). Standard instant vaults never reach a pending state. See [Deposit modes](/sdk/concepts/deposit-modes) for how to detect which kind you are dealing with.

## Overview

`POST /v2/actions/claim-deposit/{opportunityId}`

`POST /v2/actions/cancel-deposit/{opportunityId}`

A pending deposit resolves exactly once. After the vault processes the queued funds, call claim-deposit to finalize the position; before processing completes, call cancel-deposit to return the funds to the user. Both endpoints take the same path parameter and body, and each returns an `actionId` and an ordered `transactions` array to sign and submit.

## The pending-deposit state machine

| State     | How it got here                                                        | What you can do                                                |
| --------- | ---------------------------------------------------------------------- | -------------------------------------------------------------- |
| Pending   | User deposited into a `complex` vault; the vault has queued the funds. | Wait for the vault to process, or cancel to recover funds now. |
| Processed | The vault finished processing the queued deposit.                      | Claim to finalize the position.                                |
| Claimed   | The user claimed the processed deposit.                                | Done. The position is active.                                  |
| Cancelled | The user cancelled before processing completed.                        | Done. Funds returned to the user.                              |

Claim and cancel are mutually exclusive resolutions of the same pending deposit. Both endpoints take only the user's address and your distributor ID, because the vault already holds the pending-deposit details keyed to that wallet.

## Detect a pending deposit

A deposit is pending only when the opportunity is `complex`. Read the deposit-steps type off the opportunity object before assuming a claim is needed.

{/* VERIFY: is there a read endpoint that reports a wallet's pending-deposit status for a complex opportunity, or does the integrator track it from the deposit they submitted? The public spec exposes no such status endpoint; do not invent one. */}

## Claim to finalize

Once the vault has processed the queued deposit, generate the claim transaction to finalize the position.

`POST /v2/actions/claim-deposit/{opportunityId}`

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://earn.turtle.xyz/v2/actions/claim-deposit/{opportunityId}" \
    -H "Content-Type: application/json" \
    -d '{
      "userAddress": "0x1234...",
      "distributorId": "your-distributor-id"
    }'
  ```

  ```typescript TypeScript theme={null}
  const opportunityId = '550e8400-e29b-41d4-a716-446655440000';

  const response = await fetch(
    `https://earn.turtle.xyz/v2/actions/claim-deposit/${opportunityId}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userAddress: '0x1234...',
        distributorId: 'your-distributor-id',
      }),
    }
  );
  const data = await response.json();
  ```
</CodeGroup>

## Response Example

```json theme={null}
{
  "actionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "transactions": [
    {
      "type": "claimDeposit",
      "transaction": {
        "to": "0x...",
        "data": "0x...",
        "value": "0",
        "gasLimit": "200000",
        "chainId": 1
      },
      "description": "Claim pending deposit"
    }
  ]
}
```

## Cancel to abort

If the user would rather recover funds than wait for the vault to process the deposit, generate the cancel transaction. Funds are returned to the user.

`POST /v2/actions/cancel-deposit/{opportunityId}`

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://earn.turtle.xyz/v2/actions/cancel-deposit/{opportunityId}" \
    -H "Content-Type: application/json" \
    -d '{
      "userAddress": "0x1234...",
      "distributorId": "your-distributor-id"
    }'
  ```

  ```typescript TypeScript theme={null}
  const opportunityId = '550e8400-e29b-41d4-a716-446655440000';

  const response = await fetch(
    `https://earn.turtle.xyz/v2/actions/cancel-deposit/${opportunityId}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userAddress: '0x1234...',
        distributorId: 'your-distributor-id',
      }),
    }
  );
  const data = await response.json();
  ```
</CodeGroup>

## Response Example

```json theme={null}
{
  "actionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "transactions": [
    {
      "type": "cancelDeposit",
      "transaction": {
        "to": "0x...",
        "data": "0x...",
        "value": "0",
        "gasLimit": "200000",
        "chainId": 1
      },
      "description": "Cancel pending deposit"
    }
  ]
}
```

## Shared parameters

Both endpoints take the same path parameter and body.

**Path Parameters**

<ParamField path="opportunityId" type="uuid" required>
  The opportunity holding the pending deposit. Get it from [Get Opportunities](/sdk/opportunities/get-opportunities).
</ParamField>

**Body Parameters**

<ParamField body="userAddress" type="string" required>
  The user's EVM wallet address. Must belong to a registered Turtle member with a pending deposit in this opportunity.
</ParamField>

<ParamField body="distributorId" type="string" required>
  Your distributor ID for attribution tracking.
</ParamField>

## Response Fields

Both endpoints return the same shape.

<ResponseField name="actionId" type="string" required>
  Unique identifier for this action, returned for both claim and cancel.
</ResponseField>

<ResponseField name="transactions" type="array" required>
  Ordered array of transactions to sign and submit. Each element follows the [Transaction Object](#transaction-object) shape rendered below; claim returns a single `claimDeposit` transaction and cancel returns a single `cancelDeposit` transaction.
</ResponseField>

## Broadcast

Each endpoint returns a single transaction. Sign and submit it.

```typescript theme={null}
const { transactions } = await response.json();
const tx = transactions[0];
const txResponse = await wallet.sendTransaction(tx.transaction);
await txResponse.wait();
```

### Transaction Object

Each transaction in the `transactions` array contains:

<ResponseField name="type" type="string" required>
  Transaction type, e.g. `approve`, `deposit`, `withdraw`, `claimDeposit`, `cancelDeposit`.
</ResponseField>

<ResponseField name="transaction" type="object" required>
  The raw transaction data to sign and submit.
</ResponseField>

<ResponseField name="transaction.to" type="string" required>
  Target contract address.
</ResponseField>

<ResponseField name="transaction.data" type="string" required>
  Encoded calldata (hex string with `0x` prefix).
</ResponseField>

<ResponseField name="transaction.value" type="string" required>
  Value in wei. Usually `"0"` for token interactions; non-zero for native token deposits.
</ResponseField>

<ResponseField name="transaction.gasLimit" type="string" required>
  Estimated gas limit.
</ResponseField>

<ResponseField name="transaction.chainId" type="integer" required>
  Chain ID for the transaction.
</ResponseField>

<ResponseField name="description" type="string" required>
  Human-readable description of what this transaction does.
</ResponseField>

<ResponseField name="metadata" type="object">
  Optional metadata for swap transactions, including provider info, amount out, gas estimate, and route details.
</ResponseField>

## Operational Notes

<AccordionGroup>
  <Accordion title="Only for complex vaults">
    Claim and cancel apply only to opportunities with `complex` settlement. Instant vaults complete on deposit and have nothing to claim or cancel.
  </Accordion>

  <Accordion title="No amount needed">
    Neither endpoint takes a token or amount. The vault already holds the pending-deposit details keyed to the user's address, so the request only needs `userAddress` and `distributorId`.
  </Accordion>

  <Accordion title="Claim and cancel are exclusive">
    A pending deposit resolves once, either to claimed or cancelled. After the vault processes the deposit, claim finalizes it; before processing completes, cancel returns the funds.
  </Accordion>
</AccordionGroup>

## Error Handling

<AccordionGroup>
  <Accordion title="User is not a Turtle member">
    **Status:** 403 Forbidden

    ```json theme={null}
    {
      "error": "not_a_member",
      "message": "User is not a Turtle member. Please complete the membership flow first.",
      "docsUrl": "https://docs.turtle.xyz/sdk/authentication/register-wallet"
    }
    ```

    **Solution:** Complete the [membership flow](/sdk/authentication/register-wallet) first. {/* VERIFY: 403 vs 404 for a non-member wallet, consistent with the deposit page. */}
  </Accordion>

  <Accordion title="Distributor not found">
    **Status:** 404 Not Found

    ```json theme={null}
    {
      "error": "distributor_not_found",
      "message": "Distributor not found with the provided ID"
    }
    ```

    **Solution:** Verify your distributor ID is correct and active.
  </Accordion>

  <Accordion title="Opportunity not found">
    **Status:** 404 Not Found

    ```json theme={null}
    {
      "error": "opportunity_not_found",
      "message": "Opportunity not found"
    }
    ```

    **Solution:** Check the opportunity ID with [Get Opportunities](/sdk/opportunities/get-opportunities).
  </Accordion>
</AccordionGroup>
