A copyable frontend integration for private SOL transfers, using Vite + React + TypeScript and the public @invisible-labs/sdk. MIT licensed. Static hosting on Cloudflare Pages.
Devnet beta: the pinned SDK is 0.9.0-devnet. Use devnet SOL. A compatible, available coordinator is required for real transfers.
Node.js 24 recommended; Node.js 22.12+ supported.
git clone https://github.com/Invisible-Labs/invisible-sdk-react-example.git
cd invisible-sdk-react-example
npm ci
npm run devNo GitHub Packages authentication or API key is needed. The SDK's invisibleDevnet() preset supplies the default route. To use another compatible coordinator, copy .env.example to .env.local and set VITE_INVISIBLE_COORDINATOR_URL to its public wss:// URL. The route cannot override the SDK's embedded attestation policy.
- Open the app. The SDK automatically opens the session, completes Noise XX and verifies attestation. No wallet connection is required.
- Enter an amount, paste the destination address into the form and choose a payout window in Settings. Review the full address, then create the transfer.
- Save the Recovery Code. Send the exact displayed devnet SOL amount from your external Solana wallet to the deposit address before expiry.
- Follow authenticated status updates, confirmed payouts and refund outcomes in Activity.
- If eligible, request a refund to the original deposit wallet.
This example does not send the deposit transaction for you. Keep wallet funding in your application's existing wallet integration.
The interface follows the compact card layout of Uniswap, adapted for private SOL transfers. The destination address is visible directly in the form. Settings offer styled payout choices with keyboard support; the review dialog and settings/help popovers use native browser elements. No wallet provider is requested.
Copy the entire src/invisible/ folder, excluding invisible.test.tsx, and install the same public SDK version:
npm install @invisible-labs/[email protected]The folder depends only on React and the public SDK. It imports no app components, routing, design system, Vite environment variables or private workspace packages.
import { useState } from "react";
import { invisibleDevnet } from "@invisible-labs/sdk/presets";
import { usePrivateTransferController } from "./invisible";
import { PrivateTransferWidget, type TransferTab } from "./invisible/ui";
import "./invisible/styles.css";
function PrivateTransfer() {
const controller = usePrivateTransferController(invisibleDevnet());
const [tab, setTab] = useState<TransferTab>("transfer");
return <PrivateTransferWidget controller={controller} tab={tab} onTabChange={setTab} />;
}The controller opens one attested session and coordinates the existing hooks. The widget provides address input, review, deposit instructions, activity and refunds. It never opens another session. The parent controls the tab so navigation can be shared with its own header.
For your own design, import the controller or individual hooks from ./invisible; import only the display modules you need from ./invisible/ui. UI styles are scoped to .invisible-widget. Wrap standalone display modules in that class, and customize --invisible-* variables on the wrapper. Do not copy the demo's global src/style.css.
See the folder guide for module interfaces, lifecycle and safety rules. src/App.tsx only supplies the demo route, header and navigation.
- SDK: WebSocket transport, Noise, attestation, DKG/delegation, policy validation, recovery codes, typed errors and local transfer records.
- Hooks/controller: React cleanup, command concurrency, selected transfer, polling, refresh subscriptions, deposit/refund guards and readable errors.
- Application: form/UI, external wallet deposit and your storage/privacy policy.
SyncRequiredmessages carry refresh hints. Onlysyncprovides lifecycle truth.- Polling never overlaps; retries apply to reads. A failed attestation stops the session. Disconnect closes it; reconnect creates and attests a fresh one. Select a saved transfer to resume reads.
- Mutations are never automatically replayed after timeout, disconnect or lost response. A non-secret local marker and native Web Locks prevent repeating an uncertain request across reloads and cooperating tabs. No client idempotency key is sent to the coordinator.
- A refund receipt is acceptance, not settlement. Keep reading sync for
refund_status, landed amount and transaction signatures. - Browser storage is not an encrypted vault. SDK records may contain the sync secret and Recovery Code. Same-origin scripts/profile access can read them. Terminal SDK records remove bearer secrets.
- SDK persistence does not save pre-delegation resume secrets or signing shares. An
allocationrecord is not a fundable transfer. Do not recreate or resume it blindly. - No LP integration, protocol implementation, raw WebSocket messages, secret logs, analytics or backend is included.
For a blocked uncertain request, reconnect and inspect the saved transfer first. The marker deliberately stays blocked if no authoritative outcome can be established. The folder guide describes this boundary.
The live example uses a manual static upload. GitHub CI verifies the source; it does not automatically deploy the demo.
For automatic deployments of your own copy, connect your GitHub repository in Workers & Pages > Create application > Pages > Import an existing Git repository:
| Setting | Value |
|---|---|
| Framework | Vite |
| Build command | npm run build |
| Build output directory | dist |
| Node version | 24 (NODE_VERSION if needed) |
| Environment | Optional VITE_INVISIBLE_COORDINATOR_URL |
The build serves static HTML, CSS and JavaScript. The SDK connects from the browser directly to the coordinator. No SSR, Pages Function or server proxy is needed. public/_headers sets simple security headers. Keep WASM compilation enabled if you add a CSP.
Cloudflare Vite deployment guide.
bash scripts/local-ci.sh
npm run previewCI runs the same install, behavioral tests, typecheck, production build and dependency audit. Tests cover failed attestation, recovery records, unknown outcomes, double submits, transfer correlation, refresh hints, stale responses, polling cleanup and terminal states.
Mocked tests verify the React adapter. They do not prove live coordinator compatibility, wallet funding or on-chain settlement. Validate those against a compatible coordinator before treating a transfer as operationally verified.