> ## Documentation Index
> Fetch the complete documentation index at: https://rain-sandbox-trial.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Withdraw Collateral

> Fetch a user's collateral contract and admin withdrawal signature, then sign and submit the withdrawal on-chain.

Withdraw collateral from your app in two steps: get an admin signature from the Rain API, then sign and submit on-device. The SDK runs the entire on-chain portion for you: it builds the EIP-712 message, encodes the calldata, signs through the resolved wallet, and submits the transaction.

The SDK's [built-in Rain API client](/sdks/embedded-wallets-authentication#built-in-rain-api-client) can fetch the signature itself, so the whole flow can run in-app. Alternatively, your backend fetches the signature and forwards it to the app.

For background on how collateral contracts work, see [Withdraw Collateral](/docs/withdraw-collateral) and [Managing Collateral](/docs/managing-collateral).

## Withdrawal flow overview

The full round trip looks like this:

<div className="wf-diagram">
  <div className="diagram-shell">
    <svg id="withdraw-flow" role="img" aria-label="The withdrawal flow: the app gets an admin withdrawal signature from the Rain API, calls withdrawCollateral on the resolved client, which checks admin authorization, signs, and submits the transaction on-chain, returning the transaction hash to the app." viewBox="0 0 1080 624" width="1080" height="624" style={{width: "100%", height: "auto"}}><defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" className="marker-fill-default" /></marker></defs><line x1="150" y1="70" x2="150" y2="612" className="lane-line" stroke-width="1.5" stroke-dasharray="2 6" /><line x1="400" y1="70" x2="400" y2="612" className="lane-line" stroke-width="1.5" stroke-dasharray="2 6" /><line x1="650" y1="70" x2="650" y2="612" className="lane-line" stroke-width="1.5" stroke-dasharray="2 6" /><line x1="900" y1="70" x2="900" y2="612" className="lane-line" stroke-width="1.5" stroke-dasharray="2 6" /><path d="M 150 168 C 150 184, 400 184, 400 200" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow)" opacity="0.92" /><path d="M 400 268 C 400 284, 150 284, 150 300" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow)" opacity="0.92" /><path d="M 150 368 C 150 384, 650 384, 650 400" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow)" opacity="0.92" /><path d="M 650 468 C 650 484, 900 484, 900 500" fill="none" className="conn-default" stroke-width="1.7" marker-end="url(#arrow)" opacity="0.92" /><foreignObject x="56" y="16" width="188" height="54"><div className="lane-head"><span className="ico">📱</span><span className="nm">Mobile App</span></div></foreignObject><foreignObject x="306" y="16" width="188" height="54"><div className="lane-head rain"><span className="ico">🌧️</span><span className="nm">Rain API</span></div></foreignObject><foreignObject x="556" y="16" width="188" height="54"><div className="lane-head"><span className="ico">⚙️</span><span className="nm">RainClient</span></div></foreignObject><foreignObject x="806" y="16" width="188" height="54"><div className="lane-head"><span className="ico">⛓️</span><span className="nm">Blockchain</span></div></foreignObject><foreignObject x="56" y="100" width="188" height="68"><div className="card action"><span className="bn">1</span><span className="ct"><span className="tag">App → Rain API</span><span className="lab">Get withdrawal signature</span></span></div></foreignObject><foreignObject x="306" y="200" width="188" height="68"><div className="card action"><span className="bn">2</span><span className="ct"><span className="tag">Rain API → App</span><span className="lab">Admin signature, salt, expiresAt</span></span></div></foreignObject><foreignObject x="56" y="300" width="188" height="68"><div className="card action"><span className="bn">3</span><span className="ct"><span className="tag">App → RainClient</span><span className="lab mono">withdrawCollateral(...)</span></span></div></foreignObject><foreignObject x="556" y="400" width="188" height="68"><div className="card action"><span className="bn">4</span><span className="ct"><span className="tag">RainClient → Chain</span><span className="lab">Check admin authorization, sign & submit</span></span></div></foreignObject><foreignObject x="806" y="500" width="188" height="68"><div className="card action"><span className="bn">5</span><span className="ct"><span className="tag">Chain → App</span><span className="lab">Transaction hash</span></span></div></foreignObject></svg>
  </div>
</div>

The steps above, in text:

| # | From → To          | Message                                                         |
| - | ------------------ | --------------------------------------------------------------- |
| 1 | App → Rain API     | Get withdrawal signature (built-in client, or via your backend) |
| 2 | Rain API → App     | Admin signature, salt, expiresAt                                |
| 3 | App → RainClient   | `withdrawCollateral(...)`                                       |
| 4 | RainClient → Chain | Check admin authorization, sign via wallet, and submit          |
| 5 | Chain → App        | Transaction hash                                                |

<Warning>
  Getting the admin signature requires Rain API credentials. Use the [built-in Rain API client](/sdks/embedded-wallets-authentication#built-in-rain-api-client) with a runtime-delivered, scoped key, or keep the call server-side with your primary `Api-Key` and forward the result. Never ship your primary `Api-Key` in a mobile app.
</Warning>

<Steps>
  <Step title="Get the contract and admin signature">
    With the built-in Rain API client [configured](/sdks/embedded-wallets-authentication#built-in-rain-api-client), fetch the user's collateral contract and the admin signature directly in the app:

    <CodeGroup>
      ```swift iOS theme={null}
      let contract = try await rain.fetchCollateralContract()

      let adminSignature = try await rain.fetchAdminSignature(
          chainId: contract.chainId,
          tokenAddress: tokenAddress,
          amountBaseUnits: BigUInt(1_000_000),   // token base units
          adminAddress: contract.adminAddresses[0],
          recipientAddress: recipientAddress
      )
      ```

      ```kotlin Android theme={null}
      val contract = rain.fetchCollateralContract()

      val adminSignature = rain.fetchAdminSignature(
          chainId = contract.chainId,
          tokenAddress = tokenAddress,
          amountBaseUnits = BigInteger("1000000"),   // token base units
          adminAddress = contract.adminAddresses.first(),
          recipientAddress = recipientAddress,
      )
      ```
    </CodeGroup>

    If Rain hasn't produced the signature yet, the SDK throws [`RAIN_303`](/sdks/embedded-wallets-error-reference) carrying the reported `status` and an optional `retryAfter` hint: retry after that interval.

    Alternatively, fetch the signature from your backend with the server-side SDK and pass the `salt` / `signature` / `expiresAt` values into the app; the on-chain step is identical:

    ```ts TypeScript theme={null}
    const signature = await client.users.signatures.retrieveWithdrawalSignature(
      userId,
      {
        token: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
        adminAddress: "0x1234...5678",
        amount: "1000000", // in token base units
        recipientAddress: "0xabcd...ef01",
      },
    );
    // Pass signature.salt, signature.signature, and signature.expiresAt to your app.
    ```
  </Step>

  <Step title="Withdraw">
    Pass the admin signature to `withdrawCollateral`. The SDK builds the transaction, signs it through the resolved wallet (the provider must support typed-data signing), submits it, and returns the transaction hash.

    <CodeGroup>
      ```swift iOS theme={null}
      let addresses = WithdrawAssetAddresses(
          contractAddress: contract.controllerAddress,
          proxyAddress: contract.proxyAddress,
          recipientAddress: recipientAddress,
          tokenAddress: tokenAddress
      )

      let txHash = try await client.withdrawCollateral(
          chainId: contract.chainId,
          assetAddresses: addresses,
          amount: 1,
          decimals: 6,
          salt: adminSignature.salt,             // base64
          signature: adminSignature.signature,   // hex, 65 bytes
          expiresAt: adminSignature.expiresAt,
          nonce: nil                             // the latest nonce is always read on-chain
      )
      print(txHash)
      ```

      ```kotlin Android theme={null}
      import com.rain.sdk.models.RainWithdrawAddresses

      val addresses = RainWithdrawAddresses(
          proxyAddress = contract.proxyAddress,
          controllerAddress = contract.controllerAddress,
          tokenAddress = tokenAddress,
          recipientAddress = recipientAddress,
      )

      val result = client.withdrawCollateral(
          chainId = contract.chainId,
          addresses = addresses,
          amount = BigDecimal("1"),
          decimals = 6,
          adminSignature = adminSignature,   // RainAdminSignature(salt, signature, expiresAt)
          nonce = null,                      // omit to read the latest nonce on-chain
          autoSend = true,
      )
      println(result.transactionHash)
      ```
    </CodeGroup>

    Platform differences:

    | Behavior        | iOS                                                                                                                                               | Android                                                                                              |
    | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
    | Admin signature | `salt`, `signature`, `expiresAt` parameters                                                                                                       | `RainAdminSignature(salt, signature, expiresAt)`                                                     |
    | Address model   | `WithdrawAssetAddresses` (contract, proxy, recipient, token)                                                                                      | `RainWithdrawAddresses` (proxy, controller, token, recipient)                                        |
    | Nonce           | A caller-supplied `nonce` is currently ignored: the latest nonce is always read on-chain (`rain.buildEIP712Message` does honor an explicit nonce) | `nonce` parameter; `null` reads the latest on-chain                                                  |
    | Auto-send       | Always sends (returns the tx hash `String`)                                                                                                       | `autoSend` parameter (default `false`); leave `false` to get `transactionData` for manual submission |
    | Return type     | `String` (transaction hash)                                                                                                                       | `RainWithdrawResult` with `transactionHash` or `transactionData`                                     |

    On Android, addresses are checksummed and validated up front: malformed input throws [`RAIN_102`](/sdks/embedded-wallets-error-reference).
  </Step>
</Steps>

<Note>
  Before signing, the SDK checks on-chain that the resolved wallet is one of the collateral's admin signers. A wallet that is definitively not authorized throws [`RAIN_407`](/sdks/embedded-wallets-error-reference) (naming the wallet and proxy) before any signing round-trip, instead of an opaque on-chain `InvalidSignature()` revert. If the check cannot run (RPC failure), the withdrawal proceeds.
</Note>

## Estimate the withdrawal fee

Estimate the network fee before submitting.

<CodeGroup>
  ```swift iOS theme={null}
  let fee: Decimal = try await client.estimateWithdrawalFee(
      chainId: contract.chainId,
      addresses: addresses,
      amount: 1,
      decimals: 6,
      salt: adminSignature.salt,
      signature: adminSignature.signature,
      expiresAt: adminSignature.expiresAt
  )
  // estimated cost in the chain's native token
  ```

  ```kotlin Android theme={null}
  val fee: Double = client.estimateWithdrawalFee(
      chainId = contract.chainId,
      addresses = addresses,
      amount = 1.0,
      decimals = 6,
      adminSignature = adminSignature,
  )
  // estimated cost in the chain's native token
  ```
</CodeGroup>

<Note>
  Fee estimation builds the full transaction, including an EIP-712 `signTypedData` call through the provider. That means estimate-then-withdraw signs twice, and estimation can surface a signing prompt (for example biometrics on Turnkey). The collateral-admin check also runs during estimation, so `RAIN_407` is possible here too. iOS returns `Decimal`; Android returns `Double`.
</Note>

## Manual building (bring your own submission)

To build the withdrawal calldata yourself and submit with your own wallet, use the wallet-agnostic builders: they never resolve a provider (though Android's `build()` still requires at least one registered).

<CodeGroup>
  ```swift iOS theme={null}
  let (messageJson, saltHex) = try await rain.buildEIP712Message(
      chainId: chainId,
      walletAddress: walletAddress,
      assetAddresses: EIP712AssetAddresses(proxyAddress: proxy, recipientAddress: recipient, tokenAddress: token),
      amount: 1,
      decimals: 6,
      nonce: nil
  )
  // Sign messageJson with your wallet → signatureData (65-byte Data).
  // Decode to Data: saltHex (hex), adminSignature.salt (base64), adminSignature.signature (hex).
  let calldata = try await rain.buildWithdrawTransactionData(
      chainId: chainId,
      assetAddresses: addresses,
      amount: 1,
      decimals: 6,
      expiresAt: adminSignature.expiresAt,
      salt: saltData,
      signatureData: signatureData,
      adminSalt: adminSaltData,
      adminSignature: adminSignatureData
  )
  ```

  ```kotlin Android theme={null}
  val builder = rain.transactionBuilder

  val nonce = builder.getLatestNonce(rpcUrl, proxyAddress)
  val isAdmin = builder.isCollateralAdmin(rpcUrl, proxyAddress, walletAddress)  // null = unknown
  val (messageJson, salt) = builder.buildEIP712Message(chainId, addresses, walletAddress, amount, decimals, nonce)
  // sign messageJson with your wallet, then:
  val calldata = builder.buildWithdrawTransactionData(addresses, amount, decimals, salt, signatureData, adminSignature)
  ```
</CodeGroup>

## Error handling

Withdrawal methods can throw:

| Code      | Meaning                                                            |
| --------- | ------------------------------------------------------------------ |
| RAIN\_101 | SDK not built, or no wallet provider resolved                      |
| RAIN\_303 | Admin signature not ready yet (retry after `retryAfter`)           |
| RAIN\_401 | User cancelled the signing request                                 |
| RAIN\_402 | Insufficient funds for gas                                         |
| RAIN\_403 | Transaction simulation reverted before submission                  |
| RAIN\_405 | Withdrawal reverted on-chain (duplicate or already-used signature) |
| RAIN\_407 | Signing wallet is not a collateral admin                           |
| RAIN\_502 | EIP-712 / internal encoding error                                  |

See the full [Error Reference](/sdks/embedded-wallets-error-reference) for handling patterns.

## What's next

<Columns cols={3}>
  <Card title="Withdraw Collateral (Guide)" icon="book-open" href="/docs/withdraw-collateral">
    Contract architecture and server-side withdrawal examples.
  </Card>

  <Card title="Authentication" icon="key" href="/sdks/embedded-wallets-authentication">
    Configure the built-in Rain API client and session tokens.
  </Card>

  <Card title="Error Reference" icon="circle-exclamation" href="/sdks/embedded-wallets-error-reference">
    Handle SDK errors with standardized error codes.
  </Card>
</Columns>
