npm package: @rhinestone/deposit-modal
Description: React modal component for Rhinestone cross-chain deposits
Author: Rhinestone
Latest version: 0.1.66

# @rhinestone/deposit-modal

React components for cross-chain deposits and withdrawals via Rhinestone smart accounts.

## Install

```bash
npm install @rhinestone/deposit-modal viem
```

## Bundle Size Tips

Prefer subpath imports so apps only bundle what they use:

```tsx
import { DepositModal } from "@rhinestone/deposit-modal/deposit";
import { WithdrawModal } from "@rhinestone/deposit-modal/withdraw";
import { executeSafeEthTransfer } from "@rhinestone/deposit-modal/safe";
import { getChainName } from "@rhinestone/deposit-modal/constants";
```

## DepositModal

Deposits tokens from any supported chain into a target chain/token via a Rhinestone smart account.

```
User wallet → transfer → Rhinestone smart account → bridge → target chain/token
```

```tsx
import { DepositModal } from "@rhinestone/deposit-modal/deposit";
import "@rhinestone/deposit-modal/styles.css";

<DepositModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  dappWalletClient={walletClient}
  dappPublicClient={publicClient}
  dappAddress={address}
  targetChain={8453}
  targetToken="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
  onDepositComplete={(data) => console.log("Done!", data)}
/>;
```

### Deposit flow matrix

|                                                                     | Flow 1: Dapp signer                                                     | Flow 2: External wallet                                             | Flow 3: QR deposit                                                  |
| ------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Typical dapp input                                                  | `dappWalletClient` + `dappAddress`                                      | `dappAddress` (user selects/connects external wallet in modal)      | `dappAddress` only                                                  |
| Connect step options                                                | `Connected Wallet` (+ `External Wallet`/`Transfer Crypto` if available) | Usually `External Wallet` + `Transfer Crypto`                       | Same options, user picks `Transfer Crypto`                          |
| Smart-account owner (`ownerAddress`)                                | `dappWalletClient.account.address`                                      | Selected external wallet address                                    | `dappAddress`                                                       |
| `createSmartAccount(userSigner, sessionOwner.account)` `userSigner` | `walletClientToAccount(dappWalletClient)`                               | `walletClientToAccount(externalWalletClient)`                       | `createViewOnlyAccount(dappAddress)`                                |
| `createSmartAccount(..., sessionOwner.account)` co-owner            | Local browser session-owner keypair (`sessionOwner.account`)            | Local browser session-owner keypair (`sessionOwner.account`)        | Local browser session-owner keypair (`sessionOwner.account`)        |
| Smart account `S` owners passed to SDK                              | `[ownerAddress, sessionOwner.address]`                                  | `[ownerAddress, sessionOwner.address]`                              | `[ownerAddress, sessionOwner.address]`                              |
| Session-owner localStorage key                                      | `rhinestone:session-owner:<ownerAddress.lowercase()>`                   | `rhinestone:session-owner:<ownerAddress.lowercase()>`               | `rhinestone:session-owner:<ownerAddress.lowercase()>`               |
| Session policy signer in `sessionDetails`                           | `DEFAULT_SIGNER_ADDRESS` (or `signerAddress` prop override)             | `DEFAULT_SIGNER_ADDRESS` (or `signerAddress` prop override)         | `DEFAULT_SIGNER_ADDRESS` (or `signerAddress` prop override)         |
| Who signs source transfer                                           | `dappWalletClient`                                                      | External wallet client                                              | No modal signer (user sends funds manually)                         |
| How processing starts                                               | Confirm step submits transfer tx                                        | Confirm step submits transfer tx                                    | User sends to QR address; modal polls status                        |
| `POST /register` `address`                                          | Smart account address (`S`)                                             | Smart account address (`S`)                                         | Smart account address (`S`)                                         |
| `POST /register` `eoaAddress`                                       | `ownerAddress`                                                          | `ownerAddress`                                                      | `dappAddress`                                                       |
| `POST /register` `sessionOwner`                                     | `sessionOwner.address` (local co-owner key)                             | `sessionOwner.address` (local co-owner key)                         | `sessionOwner.address` (local co-owner key)                         |
| `POST /register` `accountParams.sessionDetails`                     | Built from session policy signer + `sessionOwner.account` signature     | Built from session policy signer + `sessionOwner.account` signature | Built from session policy signer + `sessionOwner.account` signature |
| `POST /register` `target`                                           | `{ chain: targetChain, token: targetToken, recipient? }`                | `{ chain: targetChain, token: targetToken, recipient? }`            | `{ chain: targetChain, token: targetToken, recipient? }`            |
| If `dappAddress` is missing                                         | Unsupported for this flow                                               | External-only flow can still run                                    | Not available (QR hidden)                                           |

Notes:

- External wallet option is available when Reown is enabled (`reownAppId`) and a wallet can be connected in the modal.
- `onConnected({ address, smartAccount })` reports the active owner address used for that flow.
- Smart-account owner config is passed to the SDK as two ECDSA owners (`ownerAddress` + local `sessionOwner`) with threshold `1`.
- `DEFAULT_SIGNER_ADDRESS` is the session policy signer inside `sessionDetails`; it is not the local co-owner key stored in browser.

### Props

| Prop                 | Type                   | Required | Description                                                                  |
| -------------------- | ---------------------- | -------- | ---------------------------------------------------------------------------- |
| `isOpen`             | `boolean`              | Yes      | Modal open state                                                             |
| `onClose`            | `() => void`           | Yes      | Close handler                                                                |
| `targetChain`        | `Chain \| number`      | Yes      | Destination chain                                                            |
| `targetToken`        | `Address`              | Yes      | Destination token address                                                    |
| `dappWalletClient`   | `WalletClient \| null` | No       | Host-provided signer wallet client                                           |
| `dappPublicClient`   | `PublicClient \| null` | No       | Host-provided public client (fallbacks are created internally if missing)    |
| `dappAddress`        | `Address \| null`      | No       | Dapp identity / owner anchor used by setup and QR flow                       |
| `inline`             | `boolean`              | No       | Render inline without overlay                                                |
| `sourceChain`        | `Chain \| number`      | No       | Pre-selected source chain                                                    |
| `sourceToken`        | `Address`              | No       | Pre-selected source token                                                    |
| `defaultAmount`      | `string`               | No       | Pre-filled amount                                                            |
| `recipient`          | `Address`              | No       | Custom recipient address                                                     |
| `backendUrl`         | `string`               | No       | Backend URL                                                                  |
| `solanaRpcUrl`       | `string`               | No       | Optional Solana RPC URL override (default: `https://api.mainnet.solana.com`) |
| `rhinestoneApiKey`   | `string`               | No       | Optional SDK api key forwarded to `RhinestoneSDK` during smart-account setup |
| `signerAddress`      | `Address`              | No       | Session signer address                                                       |
| `waitForFinalTx`     | `boolean`              | No       | Wait for destination tx (default: true)                                      |
| `enableSolana`       | `boolean`              | No       | Enable Solana wallet and QR deposit flows (default: `true`)                  |
| `onRequestConnect`   | `() => void`           | No       | Called when wallet connection needed                                         |
| `connectButtonLabel` | `string`               | No       | Custom connect button label                                                  |
| `theme`              | `DepositModalTheme`    | No       | Theme customization                                                          |
| `branding`           | `DepositModalBranding` | No       | Logo and title                                                               |
| `uiConfig`           | `DepositModalUIConfig` | No       | UI display options                                                           |
| `className`          | `string`               | No       | Additional CSS class                                                         |

### Callbacks

| Callback                   | Description                                         |
| -------------------------- | --------------------------------------------------- |
| `onReady`                  | Modal initialized                                   |
| `onConnected(data)`        | Smart account created (`{ address, smartAccount }`) |
| `onDepositSubmitted(data)` | Transfer signed (`{ txHash, sourceChain, amount }`) |
| `onDepositComplete(data)`  | Deposit arrived (`{ txHash, destinationTxHash? }`)  |
| `onDepositFailed(data)`    | Bridge failed (`{ txHash, error? }`)                |
| `onError(data)`            | Error payload (`{ message, code? }`)                |

## WithdrawModal

Withdraws tokens from a 1/1 Safe to a target chain/token. The connected wallet must be the sole Safe owner. User signs an EIP-712 SafeTx authorizing the ERC20 transfer, then funds are bridged to the destination.

```
Safe → ERC20 transfer → Rhinestone smart account → bridge → target chain/token
```

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

<WithdrawModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  dappWalletClient={walletClient}
  dappPublicClient={publicClient}
  dappAddress={address}
  safeAddress="0x..."
  sourceChain={8453}
  sourceToken="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
  targetChain={1}
  targetToken="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
  onWithdrawComplete={(data) => console.log("Done!", data)}
/>;
```

### Withdrawal flow

Prerequisites:

- A 1/1 Safe (single owner) with ERC20 token balance on a supported source chain
- The connected wallet must be the sole owner of the Safe

End-to-end steps:

1. User connects wallet (must be the Safe owner)
2. Modal creates a Rhinestone smart account and signs session authorization
3. Modal registers the smart account with the backend (`/register`)
4. User enters withdrawal amount
5. User signs an EIP-712 `execTransaction` authorizing the ERC20 transfer from the Safe to the smart account
6. Modal calls `/process/:address` to trigger the bridge intent
7. Modal polls `/status/:address` for webhook events until the bridge completes on the destination chain

To test with the demo configurator:

1. Switch flow to "Withdraw"
2. Enter the Safe address
3. Select the source chain/token matching the Safe's holdings
4. Select the destination chain/token
5. The connected wallet (Privy embedded or Reown) must be the Safe's sole owner

### Additional Props (beyond shared DepositModal props)

| Prop          | Type              | Required | Description                 |
| ------------- | ----------------- | -------- | --------------------------- |
| `safeAddress` | `Address`         | Yes      | Safe holding funds          |
| `sourceChain` | `Chain \| number` | Yes      | Chain where the Safe exists |
| `sourceToken` | `Address`         | Yes      | Token in the Safe           |

### Callbacks

| Callback                    | Description                                                           |
| --------------------------- | --------------------------------------------------------------------- |
| `onWithdrawSubmitted(data)` | Safe transfer signed (`{ txHash, sourceChain, amount, safeAddress }`) |
| `onWithdrawComplete(data)`  | Withdrawal arrived (`{ txHash, destinationTxHash? }`)                 |
| `onWithdrawFailed(data)`    | Bridge failed (`{ txHash, error? }`)                                  |

Also supports `onReady`, `onConnected`, and `onError` (same as DepositModal).

## Theming

```tsx
<DepositModal
  theme={{ mode: "dark", ctaColor: "#8b5cf6", radius: "lg" }}
  branding={{ logoUrl: "https://example.com/logo.png", title: "Fund Account" }}
  uiConfig={{ showLogo: true, showStepper: true, maxDepositUsd: 10000 }}
/>
```

**Theme**: `mode` (light/dark), `ctaColor`, `ctaHoverColor`, `fontColor`, `iconColor`, `borderColor`, `backgroundColor`, `radius` (none/sm/md/lg/full)

**Branding**: `logoUrl`, `title`

**UI Config**: `showLogo`, `showStepper`, `showBackButton`, `balanceTitle`, `maxDepositUsd`, `minDepositUsd`

Use `enableSolana={false}` to keep the deposit experience EVM-only even when `reownAppId` is provided.

## Utility Exports

```tsx
import {
  SOURCE_CHAINS,
  SUPPORTED_CHAINS,
  chainRegistry,
  getChainName,
  getChainIcon,
  getChainObject,
  getUsdcAddress,
  getTokenAddress,
  getTokenDecimals,
  getTokenSymbol,
  getTokenIcon,
  getExplorerTxUrl,
  DEFAULT_BACKEND_URL,
  NATIVE_TOKEN_ADDRESS,
} from "@rhinestone/deposit-modal/constants";
```

## Supported Chains

Mainnet (1), Base (8453), Arbitrum (42161), Optimism (10), Polygon (137), BSC (56)

## License

MIT