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

# withdrawFunds

Initiate a withdrawal from active positions to user's EOA. Supports both full and partial withdrawals. Processed asynchronously by backend.

The SDK calls `GET /api/v1/users/withdraw/v2` (full) or `POST /api/v1/users/partial-withdraw/v2` (partial). When the accept response includes both `id` and `statusUrl`, [`withdrawFunds`](/docs/sdk/api/withdraw-funds) attaches a `lifecycle` object with status `accepted`. Poll that id with [`getWithdrawStatus`](/docs/sdk/api/get-withdraw-status), [`waitForWithdrawSettlement`](/docs/sdk/api/wait-for-withdraw-settlement), or [`waitForWithdrawComplete`](/docs/sdk/api/wait-for-withdraw-complete). Do **not** call `withdrawFunds` again for the same request; poll the lifecycle id instead.

Funds are always withdrawn to the Safe owner's address (the `userAddress` parameter).

**Supported assets:** USDC (default), WETH, EURC, USDT, PYUSD, and NVDAc. Chains and decimals: [Supported Assets](/docs/sdk/supported-assets). NVDAc withdrawals are always delayed — see below.

## Delayed withdrawals (`yieldmaxxing` strategy)

Positions held in protocols with asynchronous withdrawals (Ipor, Superform)
cannot be exited on demand. **Every NVDAc withdrawal falls in this case**,
since those two protocols are the only ones holding it. For those the backend sends the immediately
available portion straight away, then queues a redemption for the rest and
forwards the funds to the EOA once the protocol releases them (roughly a day on Ipor, three on Superform).

The user has nothing else to call, but `withdrawFunds` returns as soon as the
immediate portion is sent, so its `txHash` does not cover the whole amount. When every source is an async pool, nothing settles on-chain at that point
and `txHash` is **`undefined`** on a successful call. Treat it as optional and
track the remainder through lifecycle polling (above) and
[getPortfolio](/docs/sdk/api/get-portfolio#computing-the-total-balance):

```typescript theme={null}
await sdk.withdrawFunds("0xUser...", 8453);

const { portfolio } = await sdk.getPortfolio("0xUser...");
portfolio.pendingAsyncWithdrawals
  ?.filter((w) => w.status === "REQUESTED" || w.status === "CLAIMABLE")
  .forEach((w) => {
    console.log(`${w.amount} ${w.token?.symbol} from ${w.protocol?.name}`);
    console.log(`Expected to be claimable around ${w.estimatedClaimAt}`);
  });
```

Once requested, that amount can no longer be withdrawn: it is gone from both
the position snapshot and the Safe balance.

<Warning title="You cannot submit two redemptions on the same pool">
  While an entry for a pool is `REQUESTED` or `CLAIMABLE`, a second
  `withdrawFunds` aimed at that **same pool** is not allowed. The protocols
  behind it (ERC-7540) hold a single request slot per user; the backend drops
  the duplicate and still answers `success: true`. The SDK throws instead of
  returning that no-op:

  ```text theme={null}
  Withdrawal failed: A redemption is already in flight for NVDAc on NVDAC
  (status REQUESTED), and async pools allow only one at a time. Wait for it to
  reach CLAIMED before withdrawing the rest. Estimated settlement: 2026-09-24T13:59:05.543Z.
  ```

  It only throws when nothing the call could reach is withdrawable. Another pool,
  another asset or an idle Safe balance still goes through, partially.

  To disable the action before the user tries, read `pendingAsyncWithdrawals`:

  ```typescript theme={null}
  const { portfolio } = await sdk.getPortfolio("0xUser...");

  const redeeming = (portfolio.pendingAsyncWithdrawals ?? []).some(
    (w) =>
      w.token?.symbol === "NVDAc" &&
      (w.status === "REQUESTED" || w.status === "CLAIMABLE")
  );
  ```
</Warning>

It is also missing from `portfolioByAssetType` while in flight, so validate
user-entered amounts against that field rather than the total balance. Read
[Computing the total balance](/docs/sdk/api/get-portfolio#computing-the-total-balance)
before showing any balance to the user.

## Signature

```typescript theme={null}
withdrawFunds(userAddress: string, chainId: SupportedChainId, amount?: string, tokenSymbol?: SupportedAsset): Promise<WithdrawResponse>
```

## Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `userAddress` | `string` | ✅ | User's EOA address (owner of the Safe) |
| `chainId` | `SupportedChainId` | ✅ | Chain to withdraw from |
| `amount` | `string` | ❌ | Amount to withdraw (omit for full withdrawal) |
| `tokenSymbol` | `SupportedAsset` | ❌ | `"USDC"`, `"WETH"`, `"EURC"`, `"USDT"`, `"PYUSD"`, or `"NVDAc"` (default: `"USDC"`). See [Supported Assets](/docs/sdk/supported-assets) |

## Returns

Withdraw response with status message

## Return Type

```typescript theme={null}
interface WithdrawResponse {
  success: boolean;
  message: string;
  txHash?: string;
  type: 'full' | 'partial';
  amount: string;
  /** Present when the v2 accept response includes id and statusUrl */
  lifecycle?: WithdrawLifecycleResponse;
}
```

The `lifecycle` field on accept is a local stub (`status: "accepted"`). Call [`getWithdrawStatus`](/docs/sdk/api/get-withdraw-status) for the live state.

## Example

### Withdraw USDC (default)

```typescript theme={null}
// Full USDC withdrawal (withdrawn to user's EOA)
const result = await sdk.withdrawFunds("0xUser...", 42161);
console.log(result.message); // "Withdrawal request sent"

if (result.lifecycle?.id) {
  const settled = await sdk.waitForWithdrawSettlement(
    result.lifecycle.id,
    42161,
  );
  if (settled.status !== "completed") {
    await sdk.waitForWithdrawComplete(result.lifecycle.id, 42161);
  }
}

// Partial withdrawal of 50 USDC (6 decimals)
const partial = await sdk.withdrawFunds(
  "0xUser...",
  42161,
  "50000000" // 50 USDC = 50 * 10^6
);
console.log(partial.type); // "partial"
```

### Withdraw WETH

```typescript theme={null}
// Full WETH withdrawal
const result = await sdk.withdrawFunds("0xUser...", 8453, undefined, "WETH");
console.log(result.message); // "Withdrawal request sent"

// Partial withdrawal of 0.25 WETH (18 decimals)
const partial = await sdk.withdrawFunds(
  "0xUser...",
  8453,
  "250000000000000000", // 0.25 WETH = 0.25 * 10^18
  "WETH"
);
console.log(partial.type); // "partial"
```

### Withdraw EURC

```typescript theme={null}
// Full EURC withdrawal (Mainnet / Base)
const result = await sdk.withdrawFunds("0xUser...", 8453, undefined, "EURC");
console.log(result.message); // "Withdrawal request sent"
```

### Withdraw NVDAc

Always delayed, and only one redemption can be in flight per pool. If
`pendingAsyncWithdrawals` already has a `REQUESTED` or `CLAIMABLE` entry for
NVDAc, do not call `withdrawFunds` again — it throws.

```typescript theme={null}
const { portfolio } = await sdk.getPortfolio("0xUser...");
const redeeming = (portfolio.pendingAsyncWithdrawals ?? []).some(
  (w) =>
    w.token?.symbol === "NVDAc" &&
    (w.status === "REQUESTED" || w.status === "CLAIMABLE")
);

if (redeeming) {
  // Wait for CLAIMED. estimatedClaimAt is an upper bound, not a schedule.
} else {
  await sdk.withdrawFunds("0xUser...", 8453, undefined, "NVDAc");
}
```


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