# @rhinestone/deposit-modal

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

## Install

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

## Example

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

export function DepositButton({ walletClient, publicClient, address }) {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Deposit</button>
      <DepositModal
        isOpen={open}
        onClose={() => setOpen(false)}
        dappWalletClient={walletClient}
        dappPublicClient={publicClient}
        dappAddress={address}
        targetChain={8453}
        targetToken="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
        onLifecycle={(event) => {
          switch (event.type) {
            case "connected":
              console.log("smart account ready", event.smartAccount);
              break;
            case "submitted":
              console.log("source tx", event.txHash);
              break;
            case "complete":
              console.log("done", event.destinationTxHash);
              break;
            case "failed":
              console.error("failed", event.error);
              break;
          }
        }}
      />
    </>
  );
}
```

`WithdrawModal` lives at `@rhinestone/deposit-modal/withdraw` and takes the same shape (`onLifecycle` + `onError`).

## Fiat on-ramp (Swapped)

Pass `enableFiatOnramp` to expose Swapped's iframe as a funding option alongside Transfer Crypto:

```tsx
<DepositModal
  enableFiatOnramp
  // Optionally render one row per Swapped payment method instead of a generic
  // "Pay with Card" row. `method` must be the `payment_group` value from
  // Swapped's GET /api/v1/merchant/get_payment_methods — that exact string is
  // what the widget prepopulates from. Note Apple Pay is `apple-pay` (hyphen),
  // not `applepay`.
  fiatOnrampMethods={[
    { method: "creditcard", label: "Pay with Card", icon: "card" },
    { method: "apple-pay", label: "Apple Pay", icon: "apple" },
    { method: "bank-transfer", label: "Bank Transfer", icon: "bank" },
  ]}
  // Hide the Transfer Crypto QR row if you only want fiat options:
  // enableQrTransfer={false}
  // ...
/>
```

The flow: user picks a fiat option → modal registers a Rhinestone smart account → backend mints a server-signed Swapped iframe URL → user completes purchase → Swapped sends crypto on-chain to the smart account → the existing on-chain detection fires `onLifecycle({ type: "complete" })`.

**Destination guarantees.** The user picks the fiat amount inside Swapped's widget, but the destination is fixed server-side: every order receives **USDC on Base** delivered to the **registered Rhinestone smart account**. The frontend cannot override these; the backend signs the Swapped URL with `currencyCode=USDC_BASE` and `walletAddress=<smartAccount>`.

**Fees.** Rhinestone does not charge a markup by default. The client (dapp) controls the user-facing experience via the orchestrator's sponsorship config:
- **Sponsored** (client has a funded sponsor pool for the source chain): user receives 1:1 — the on-ramped amount lands on the target chain exactly.
- **Unsponsored**: the bridging cost is deducted from the on-ramped amount and shown as a "Bridging cost" line on the success screen.

Host CSP must allow Swapped's domains:

```
frame-src https://widget.swapped.com https://sandbox.swapped.com https://connect.swapped.com;
```

**Submerchant attribution.** Swapped orders are tagged with a `submerchant` for multi-tenant reporting. This is derived automatically server-side from the embedding page's domain (the request `Origin` header) — no configuration is required. In local development the domain resolves to `localhost`.

| Prop                 | Type                          | Default | Description |
| -------------------- | ----------------------------- | ------- | ----------- |
| `enableFiatOnramp`   | `boolean`                     | `false` | Show fiat on-ramp option(s). Requires `dappAddress` and backend Swapped keys. |
| `enableQrTransfer`   | `boolean`                     | `true`  | Show the Transfer Crypto / QR row. Set false for fiat-only. |
| `fiatOnrampMethods`  | `FiatPaymentMethodOption[]`   | —       | One row per method, each preselects Swapped's payment method via the `method` URL param. |

## Fund from Exchange (Swapped Connect)

Pass `enableExchangeConnect` to expose a "Fund from Exchange" row alongside the existing options. Clicking it opens an exchange picker backed by `/onramp/swapped/connect-exchanges`; after the user picks an exchange, the modal embeds Swapped's Connect iframe for that selected `connection`, signs in, and Swapped pulls the crypto on-chain to the smart account.

```tsx
<DepositModal
  enableExchangeConnect
  // ...
/>
```

Flow: user picks Fund from Exchange → backend returns supported exchanges + logos from Swapped using a 24-hour cache → user picks an exchange → modal registers a Rhinestone smart account → backend mints a server-signed Swapped Connect URL with the selected `connection` → user authenticates with their CEX inside the iframe → Swapped pushes crypto on-chain → existing on-chain detection fires `onLifecycle({ type: "complete" })`.

| Prop                     | Type      | Default | Description |
| ------------------------ | --------- | ------- | ----------- |
| `enableExchangeConnect`  | `boolean` | `false` | Show the "Fund from Exchange" row. Requires `dappAddress` and backend Swapped keys. Can be combined with `enableFiatOnramp`. |

## Releasing

Releases are driven by [changesets](https://github.com/changesets/changesets) and two GitHub Actions workflows (`.github/workflows/`).

- **Every PR** that changes published behaviour should include a changeset: run `bun run changeset`, pick the bump, and commit the generated `.changeset/*.md`.
- **Merge to `main` → `@dev` snapshot.** The `Release` workflow publishes a snapshot to npm under the `dev` tag (`npm install @rhinestone/deposit-modal@dev`). Snapshots are only published when there are pending changesets.
- **Stable release → `release` branch.** Merge `main` into `release`. The `changesets/action` opens a "Release" PR that bumps the version, writes the changelog, and consumes the changeset files. Merging that PR publishes `@latest` to npm and creates the git tag.

Publishing uses npm [OIDC trusted publishing](https://docs.npmjs.com/trusted-publishers) (no stored token). Prerequisites in the `rhinestonewtf/deposit-modal` repo / npm package:

- `@rhinestone/deposit-modal` configured as a trusted publisher on npmjs.com (GitHub Actions, repo `rhinestonewtf/deposit-modal`, workflow `release.yaml`).
- `APP_ID` / `APP_PRIVATE_KEY` secrets for the `rhinestone-automations` GitHub App (opens the Release PR).
- A `release` branch and a `ship` label.

## License

MIT
