Skip to main content
The withdraw modal currently only supports 1/1 Safe wallets.
The WithdrawModal component handles outbound transfers from a Safe. The user selects a destination chain and token, enters an amount, and the modal constructs and signs the withdrawal transaction.

Basic usage

import { WithdrawModal } from "@rhinestone/deposit-modal";
import "@rhinestone/deposit-modal/styles.css";

<WithdrawModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  safeAddress="0xYOUR_SAFE_ADDRESS"
  sourceChain={8453}
  sourceToken="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
  targetChain={10}
  targetToken="0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"
  reownAppId="YOUR_REOWN_PROJECT_ID"
  onWithdrawComplete={(data) => {
    console.log("Withdrawal complete:", data.destinationTxHash);
  }}
/>

Transaction signing

By default, the modal uses Reown for wallet connection and signing. If your app manages wallets directly (e.g. Privy embedded wallets), pass onSignTransaction to handle Safe transaction signing yourself. The callback receives a SafeTransactionRequest containing the safeTxHash and EIP-712 typed data. Return a signature.
<WithdrawModal
  // ...required props
  dappAddress={embeddedWalletAddress}
  onSignTransaction={async (request) => {
    const provider = await getEmbeddedWalletProvider();

    // Sign the Safe transaction hash
    const signature = await provider.request({
      method: "personal_sign",
      params: [request.safeTxHash, embeddedWalletAddress],
    });

    // Adjust v value for Safe's eth_sign verification
    const v = parseInt(signature.slice(-2), 16);
    const adjusted = signature.slice(0, -2) + (v + 4).toString(16);

    return { signature: adjusted };
  }}
/>
Safe’s eth_sign path requires adding 4 to the v value of a personal_sign signature. This adjustment is specific to Safe’s signature verification — see the Safe docs for details.

Props reference

Required

PropTypeDescription
isOpenbooleanControls modal visibility
onClose() => voidCalled when the user closes the modal
safeAddressAddressThe 1/1 Safe contract address
sourceChainChain | numberChain where the Safe holds funds
sourceTokenAddressToken to withdraw from the Safe
targetChainChain | numberDestination chain
targetTokenAddressToken to receive on the destination chain

Wallet

PropTypeDefaultDescription
reownAppIdstringReown project ID for wallet connection
dappAddressAddressOwner address for embedded wallet flows
dappWalletClientWalletClientHost-provided viem wallet client
dappPublicClientPublicClientHost-provided viem public client
onSignTransaction(request: SafeTransactionRequest) => Promise<{ signature: Hex }>Custom Safe transaction signer
onRequestConnect() => voidCalled when wallet connection needed
connectButtonLabelstringCustom connect button label

Transfer

PropTypeDefaultDescription
recipientAddressSmart account addressDelivery address on target chain
defaultAmountstringPre-filled withdrawal amount
allowedRoutesRouteConfigRestrict available routes

Session

PropTypeDefaultDescription
sessionChainIdsnumber[]All supportedChain IDs for session key creation
forceRegisterbooleanfalseForce session re-creation
waitForFinalTxbooleantrueWait for destination chain confirmation
signerAddressAddressDefault signerSession signer address
rhinestoneApiKeystringAPI key for account setup

Backend

PropTypeDefaultDescription
backendUrlstringRhinestone production URLURL of your deposit-widget-backend instance

Display

PropTypeDefaultDescription
inlinebooleanfalseRender without modal overlay
closeOnOverlayClickbooleantrueClose modal on backdrop click
classNamestringCSS class for the modal container
themeDepositModalThemeTheme configuration
brandingDepositModalBrandingBranding configuration
uiConfigDepositModalUIConfigUI configuration
debugbooleanfalseEnable debug logging

Callbacks

PropTypeDescription
onReady() => voidModal initialized
onConnected(data: ConnectedEventData) => voidSmart account created
onWithdrawSubmitted(data: WithdrawSubmittedEventData) => voidWithdrawal transaction submitted
onWithdrawComplete(data: WithdrawCompleteEventData) => voidTokens arrived on target chain
onWithdrawFailed(data: WithdrawFailedEventData) => voidWithdrawal failed
onError(data: ErrorEventData) => voidError at any stage
onEvent(event: WithdrawEvent) => voidAnalytics event