diff --git a/docs.json b/docs.json index a5807fe..a991289 100644 --- a/docs.json +++ b/docs.json @@ -152,7 +152,26 @@ } ] }, - + { + "tab": "Simple Deposit", + "groups": [ + { + "group": "OVERVIEW", + "pages": [ + "simple-deposit/overview", + "simple-deposit/quickstart" + ] + }, + { + "group": "SDK Reference", + "pages": [ + "simple-deposit/react-sdk", + "simple-deposit/core-sdk", + "simple-deposit/reference" + ] + } + ] + }, { "tab": "Social Logins", "groups": [ diff --git a/simple-deposit/core-sdk.mdx b/simple-deposit/core-sdk.mdx index 0ebef07..bf09c1f 100644 --- a/simple-deposit/core-sdk.mdx +++ b/simple-deposit/core-sdk.mdx @@ -29,7 +29,7 @@ client.startWatching(); | `ownerAddress` | `string` | Yes | — | User's wallet address. | | `intermediaryAddress` | `string` | Yes | — | JWT wallet from Auth Core. | | `authCoreProvider` | `AuthCoreProvider` | No* | — | Provider for signing. *Required for sweeps. | -| `destination` | `DestinationConfig` | Yes | — | Where swept funds go (`chainId` required). | +| `destination` | `DestinationConfig` | Yes | — | Where swept funds go (`chainId` required; optional `token` defaults to USDC — see [Destination tokens](/simple-deposit/reference#destination-tokens)). | | `supportedTokens` | `TokenType[]` | No | All | Tokens to watch. | | `supportedChains` | `number[]` | No | All 17 | Chains to watch. | | `autoSweep` | `boolean` | No | `true` | Auto-sweep on detection. | @@ -38,6 +38,7 @@ client.startWatching(); | `recovery` | `RecoveryConfig` | No | — | Recovery behavior. | | `refund` | `RefundConfig` | No | `{ enabled: false }` | Auto-refund (experimental). | | `funding` | `FundingConfig` | No | `{ enabled: false }` | Fund the deposit address from a connected browser wallet (zero-config). | +| `onramp` | `OnrampConfig` | No | `{ enabled: false }` | Buy crypto with fiat via RampNow, delivered straight to the destination (zero-config). | | `onDepositEvent` | `(event: DepositLifecycleEvent) => void` | No | — | Deposit-lifecycle callback. Best-effort and client-side (not a server webhook). | | `uaProjectId` | `string` | No | SDK default | Particle project ID for UA operations only. | @@ -67,6 +68,8 @@ client.startWatching(); | `getRefundConfig()` | — | `RefundConfig` | Current refund config. | | `getWalletBalances(wallet)` | `FundingWallet` | `Promise` | Discover a connected wallet's cross-chain balances. | | `fundFromWallet(wallet, balance, opts?)` | `FundingWallet, FundingBalance, FundOptions?` | `Promise` | Transfer a discovered balance to the deposit address. | +| `openOnramp(opts?)` | `{ amount?: number }` | `Promise` | Open the RampNow onramp to buy crypto with fiat. | +| `isOnrampSupported()` | — | `boolean` | Whether the current destination chain supports onramp. | | `getTransactions(page, pageSize)` | `number, number` | `Promise` | Page-based transaction history. | | `getTokenTransactions(filter, cursor?)` | `TokenTransactionFilter, string?` | `Promise` | Cursor-based filtered transactions. | | `getTransaction(id)` | `string` | `Promise` | Single transaction lookup. | @@ -168,6 +171,34 @@ import { --- +## Onramp — Buy with Fiat (Headless) + +Let a user **buy crypto with fiat** (card / Apple Pay / bank) via [RampNow](https://rampnow.io), delivered **straight to the configured destination**. Unlike Fund from Wallet, the purchase bypasses the deposit-address → watcher → sweeper pipeline — RampNow's order events are the source of truth, forwarded by the SDK as `onramp:*` events. The onramp buys the **destination token** (USDC by default), matching the sweep — note RampNow supports a narrower token set than the sweep, so gate the "Buy" UI on `isOnrampSupported()` / `isOnrampTokenSupported(token, chainId)` (see [Destination tokens](/simple-deposit/reference#destination-tokens)). + +Onramp is a single flag — zero-config. The SDK ships with a default RampNow key, so there's nothing to source; pass your own `apiKey` only to route purchases through your own RampNow partner account. The underlying `@rampnow/sdk` is loaded lazily (dynamic `import()`) only when the onramp is opened, so it never bundles into apps that don't use it. + +```typescript +const client = new DepositClient({ + ownerAddress: '0x...', + intermediaryAddress: '0x...', + authCoreProvider: provider, + destination: { chainId: CHAIN.BASE }, + onramp: { enabled: true }, // ← that's it +}); + +// Events report progress. +client.on('onramp:complete', (order) => console.log('delivered', order.amount, order.token)); + +// Open the overlay (100 USD by default fiat currency). +await client.openOnramp({ amount: 100 }); +``` + + + Onramp delivery is supported for **Ethereum, BNB Chain, Base, Arbitrum, and Solana**. X Layer is **not** supported — `isOnrampSupported()` returns `false` for it. Order events only flow in `mode: 'overlay'`; a `redirect` tab has no channel back to report progress. + + +--- + ## Transaction History Query the Universal Account's transaction history. Results are cached (30s TTL, LRU) to avoid redundant API calls when paginating. @@ -251,6 +282,7 @@ client.off('deposit:detected', handler); | `funding:started` | `FundingBalance` | Wallet-funding transfer started. | | `funding:complete` | `FundingTransferResult` | Wallet-funding transfer submitted. | | `funding:error` | `Error, FundingBalance?` | Wallet-funding transfer failed. | +| `onramp:complete` | `OnrampOrder` | Fiat purchase delivered to the destination. | | `status:change` | `ClientStatus` | Status changed. | --- diff --git a/simple-deposit/react-sdk.mdx b/simple-deposit/react-sdk.mdx index b9e5c90..bf31feb 100644 --- a/simple-deposit/react-sdk.mdx +++ b/simple-deposit/react-sdk.mdx @@ -12,7 +12,7 @@ Wraps your app and manages the deposit lifecycle. Handles Auth Core context inte import { DepositProvider, CHAIN } from '@particle-network/simple-deposit/react'; @@ -26,6 +26,7 @@ import { DepositProvider, CHAIN } from '@particle-network/simple-deposit/react'; |---|---|---|---| | `destination.chainId` | `number` | — | **Required.** Destination chain (use `CHAIN` constant). | | `destination.address` | `string` | Owner's EOA | Custom sweep destination address. | +| `destination.token` | `TokenType` | `'USDC'` | Token delivered to the destination (sweep converts to it; onramp buys it). See [Destination tokens](/simple-deposit/reference#destination-tokens). | | `supportedTokens` | `TokenType[]` | All | Tokens to watch. | | `supportedChains` | `number[]` | All 17 chains | Chains to watch. | | `autoSweep` | `boolean` | `true` | Auto-sweep detected deposits. | @@ -35,6 +36,7 @@ import { DepositProvider, CHAIN } from '@particle-network/simple-deposit/react'; | `notifications` | `boolean \| DepositNotificationsProps` | `false` | Show in-app deposit notifications. Set `true` to auto-mount the toaster, or pass props to configure it. | | `funding` | `FundingConfig` | `{ enabled: false }` | Let users fund the deposit address from a connected browser wallet. When enabled, `DepositWidget` shows a "Fund from wallet" toggle. | | `fundingWallet` | `{ evm?: { provider, address } }` | — | Reuse your app's already-connected wallet as the funding source (skips the connect step). | +| `onramp` | `OnrampConfig` | `{ enabled: false }` | Let users buy crypto with fiat (card / Apple Pay / bank) via RampNow, delivered straight to the destination. When enabled, `DepositWidget` shows a "Buy" toggle. | | `uaProjectId` | `string` | SDK default | Particle project ID for UA operations only. | --- @@ -108,6 +110,36 @@ const { client, stuckFunds, recoverFunds, refundDeposit } = useDepositContext(); --- +## useOnramp + +Drive a fiat purchase (buy crypto with fiat via RampNow) from a fully custom UI. Returns nothing useful until `onramp.enabled` is set on the provider. + +```tsx +import { useOnramp } from '@particle-network/simple-deposit/react'; + +const { open, status, error, enabled, supported } = useOnramp(); + +// Render your own button: + +``` + +### Return Value + +| Property | Type | Description | +|---|---|---| +| `open` | `(opts?: { amount?: number }) => Promise` | Open the RampNow overlay/redirect. | +| `close` | `() => void` | Close the overlay. | +| `isOpen` | `boolean` | Overlay is open. | +| `status` | `'idle' \| 'opening' \| 'open' \| 'processing' \| 'success' \| 'error'` | Current purchase status. | +| `lastOrder` | `OnrampOrder \| null` | Most recent completed order. | +| `error` | `Error \| null` | Last error. | +| `enabled` | `boolean` | Onramp is enabled in config. | +| `supported` | `boolean` | Destination chain supports onramp (see [supported chains](/simple-deposit/reference#onramp)). | + +--- + ## Components ### DepositWidget @@ -133,7 +165,7 @@ import { DepositWidget } from '@particle-network/simple-deposit/react'; | `onClose` | `() => void` | — | Close handler. | - When `funding.enabled` is set on the provider, `DepositWidget` automatically gains a **"Receive | Fund from wallet"** toggle — no extra props needed. + When `funding.enabled` and/or `onramp.enabled` are set on the provider, `DepositWidget` automatically gains a **"Receive | Fund | Buy"** toggle — segments appear only for the features you enable, no extra props needed. Pass `showBuy={false}` to suppress the Buy tab. ### DepositModal @@ -152,6 +184,44 @@ import { DepositModal } from '@particle-network/simple-deposit/react'; | `onClose` | `() => void` | Close handler (required). | | `overlayClassName` | `string` | Custom overlay CSS class. | +### OnrampButton + +Standalone "Buy" button for custom placement. Renders nothing when `onramp` is disabled. Must live inside a `DepositProvider` with `onramp: { enabled: true }`. + +```tsx +import { OnrampButton } from '@particle-network/simple-deposit/react'; + + console.log('bought', order)} /> +``` + +| Prop | Type | Default | Description | +|---|---|---|---| +| `client` | `DepositClient` | Context | Optional client (uses context if omitted). | +| `theme` | `'dark' \| 'light'` | `'dark'` | Color theme. | +| `amount` | `number` | — | Pre-fill the fiat amount. | +| `onPurchased` | `(order: OnrampOrder) => void` | — | Called when a purchase completes. | + +### OnrampStatusToast + +A small floating "purchase in progress / complete / failed" popup that persists even after the user switches tabs or closes the RampNow overlay. Mount it once near your app root — it portals to `document.body` and is driven by `useOnramp` status. + +```tsx +import { OnrampStatusToast } from '@particle-network/simple-deposit/react'; + + +``` + +| Prop | Type | Default | Description | +|---|---|---|---| +| `client` | `DepositClient` | Context | Optional client (uses context if omitted). | +| `theme` | `'dark' \| 'light'` | `'dark'` | Color theme. | +| `position` | `'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left' \| 'top-center' \| 'bottom-center'` | `'bottom-right'` | Where the toast anchors. | +| `successDurationMs` | `number` | `6000` | Auto-dismiss delay for the success toast. `0` keeps it until dismissed. | + + + Order events only flow in `mode: 'overlay'`. A `redirect` tab has no postMessage channel back, so there's nothing to track and the toast stays hidden. + + ### RecoveryWidget UI for scanning and recovering stuck funds. diff --git a/simple-deposit/reference.mdx b/simple-deposit/reference.mdx index 7ff05f8..ff76de3 100644 --- a/simple-deposit/reference.mdx +++ b/simple-deposit/reference.mdx @@ -10,11 +10,45 @@ description: "Type definitions, constants, chain utilities, and advanced configu ```typescript interface DestinationConfig { - address?: string; // Defaults to ownerAddress - chainId: number; // Required — use CHAIN constant + address?: string; // Defaults to ownerAddress + chainId: number; // Required — use CHAIN constant + token?: TokenType; // Defaults to 'USDC' — see "Destination tokens" } ``` +#### Destination tokens + +By default every deposit is delivered as **USDC** on the destination chain. Set +`destination.token` to deliver a different asset; the sweep converts each deposit +into that token and the fiat onramp buys it directly. The token must be +deliverable on the destination chain or the client throws a `ConfigurationError`. + +```typescript +// Deliver native ETH on Base instead of USDC +destination: { chainId: CHAIN.BASE, token: 'ETH' } +``` + +Deliverable token × chain matrix: + +| Token | Ethereum | Base | Arbitrum | BNB | Solana | +|-------|:--------:|:----:|:--------:|:---:|:------:| +| USDC | ✓ | ✓ | ✓ | ✓ | ✓ | +| USDT | ✓ | — | ✓ | ✓ | ✓ | +| ETH | ✓ (native) | ✓ (native) | ✓ (native) | ✓ (bridged) | — | +| SOL | — | — | — | — | ✓ (native) | + +Deliverability mirrors the Universal Account's routable primary assets (Base has +no USDT route, for example). Use `isSupportedDestinationToken(token, chainId)` to +check a combo at runtime. + +> **X Layer** is a deposit *source* only — the Universal Account has no X Layer +> primary tokens, so it cannot be used as a sweep destination. + +> **Onramp note:** RampNow's fiat "Buy" supports a narrower set than the sweep — +> stablecoins on all chains, but `ETH` only on Ethereum/Base, `BNB` on BNB, and +> `SOL` on Solana. `isOnrampSupported()` / `isOnrampTokenSupported(token, chainId)` +> reflect this; gate "Buy" UI on them. + ### DetectedDeposit ```typescript @@ -135,6 +169,42 @@ interface FundingTransferResult { // straight back into fundFromWallet(). Treat it as opaque; don't construct it by hand. ``` +### OnrampConfig + +```typescript +interface OnrampConfig { + enabled: boolean; // Master switch (default: false) + apiKey?: string; // Your RampNow partner key (pk_live_…); omit to use the SDK default + mode?: 'overlay' | 'redirect'; // 'overlay' = in-page iframe (emits order events); 'redirect' = new tab (no events). Default 'overlay' + fiatCurrency?: string; // Fiat the user spends, e.g. 'EUR' (default 'USD') + defaultFiatAmount?: number; // Pre-fill the fiat amount + paymentMode?: string; // 'card' | 'apple_pay' | 'google_pay' | 'sepa' | … + widgetUrl?: string; // Override the RampNow widget base URL (e.g. a sandbox host) +} +``` + +| Property | Type | Default | Description | +|---|---|---|---| +| `enabled` | `boolean` | `false` | Master switch for the feature. | +| `apiKey` | `string` | _(SDK default)_ | **Optional.** Your RampNow partner key (`pk_live_…`). Omit to use the SDK's built-in key; set it to route through your own partner account (your fees / KYC / limits). | +| `mode` | `'overlay' \| 'redirect'` | `'overlay'` | `'overlay'` = in-page iframe modal (emits order events → activity feed updates). `'redirect'` = open in a new tab (no close button to manage, but **no order events**). | +| `fiatCurrency` | `string` | `'USD'` | Fiat currency the user spends (e.g. `'EUR'`). | +| `defaultFiatAmount` | `number` | — | Pre-fill the fiat amount. | +| `paymentMode` | `string` | — | Preferred method: `'card'`, `'apple_pay'`, `'google_pay'`, `'sepa'`, … | +| `widgetUrl` | `string` | `https://app.rampnow.io` | Override the RampNow widget base URL (e.g. a sandbox host). For testing you normally just use a sandbox `apiKey`. | + +### OnrampOrder + +The completed-order payload from RampNow, delivered by the `onramp:complete` event and exposed as `lastOrder` from `useOnramp`. + +```typescript +interface OnrampOrder { + amount: string; // Delivered amount + token: TokenType; // Delivered token (USDC today) + // …plus RampNow order metadata (status, id, destination) +} +``` + ### UATransaction ```typescript @@ -267,12 +337,14 @@ import { ```typescript import { - getChainName, // getChainName(42161) → "Arbitrum" - isValidDestinationChain, // isValidDestinationChain(42161) → true - getAddressType, // getAddressType(101) → 'solana' - isValidEvmAddress, // Validates 0x + 40 hex chars - isValidSolanaAddress, // Validates base58 format - validateAddressForChain, // Validates address format for chain type + getChainName, // getChainName(42161) → "Arbitrum" + isValidDestinationChain, // isValidDestinationChain(42161) → true + isSupportedDestinationToken, // isSupportedDestinationToken('ETH', CHAIN.BASE) → true + isOnrampTokenSupported, // isOnrampTokenSupported('ETH', CHAIN.BASE) → true + getAddressType, // getAddressType(101) → 'solana' + isValidEvmAddress, // Validates 0x + 40 hex chars + isValidSolanaAddress, // Validates base58 format + validateAddressForChain, // Validates address format for chain type } from '@particle-network/simple-deposit'; ``` @@ -349,12 +421,16 @@ setDestination({ chainId: CHAIN.ETHEREUM }); ```typescript Headless client.setDestination({ chainId: CHAIN.BASE, address: '0xTreasury...' }); const dest = client.getDestination(); // { address, chainId } + +// Deliver a non-default token (see "Destination tokens") +client.setDestination({ chainId: CHAIN.BASE, token: 'ETH' }); ``` - Throws `ConfigurationError` if the chain ID or address is invalid. + Throws `ConfigurationError` if the chain ID, address, or token is invalid — see the + [deliverable token × chain matrix](#destination-tokens). ### Notifications @@ -430,6 +506,34 @@ When `funding.enabled` is set, `DepositWidget` automatically shows a "Receive | Balance-source precedence: `balanceProvider` > `apiKey` (direct) > `proxyUrl` > hosted default. Balance discovery covers **Ethereum, BNB Chain, Base, Arbitrum, and Solana**; X Layer is not supported and is skipped during discovery. +### Onramp + +Let users buy crypto with fiat (card / Apple Pay / bank) via [RampNow](https://rampnow.io), delivered straight to the destination. Onramp bypasses the watcher/sweeper pipeline — RampNow's order events are the source of truth and are forwarded as `onramp:*` events. Zero-config: the SDK ships a default RampNow key, and `@rampnow/sdk` loads lazily only when the onramp opens. + +```typescript +const client = new DepositClient({ + ownerAddress: '0x...', + intermediaryAddress: '0x...', + authCoreProvider: provider, + destination: { chainId: CHAIN.BASE }, + onramp: { enabled: true }, // ← that's it +}); +``` + +When `onramp.enabled` is set, `DepositWidget` automatically adds a "Buy" tab. For custom placement use `OnrampButton`, the `useOnramp()` hook, or the `OnrampStatusToast` popup — see the [React SDK](/simple-deposit/react-sdk). Headless apps call `client.openOnramp({ amount })` directly — see the [Core SDK](/simple-deposit/core-sdk#onramp-buy-with-fiat-headless). + + + **Supported chains:** Ethereum, BNB Chain, Base, Arbitrum, and Solana. X Layer is **not** supported — `isOnrampSupported()` returns `false` and the "Buy" UI is disabled with an explanation. + + + + **Delivered token:** Onramp always buys **USDC**, matching the token the sweep delivers to the destination. + + + + Order events only flow in `mode: 'overlay'`. A `redirect` tab has no postMessage channel back, so there's nothing to track (no activity-feed updates, no status toast). + + ### Recovery Recover funds that are stuck in the intermediary wallet (e.g., due to failed sweeps).