Build a web app
Ship Senddy in a browser app with social login, session tokens and gasless deposits. The production checklist.
This is the shape of a production integration, taken from apps running on Senddy today, including HOODS on Robinhood Chain. Read it once before you build, and again before you launch.
The shape#
Embedded wallet ──signs──▶ private keys (derived on the device, never sent anywhere)
│
├──signs──▶ deposit authorization ──▶ Senddy relay ──▶ pool
│
└──user's login token──▶ your /api/session-token ──▶ Senddy /session ──▶ sst_ token
│
@senddy/client ◀───────────────────────┘Three facts shape everything else:
- Users never need gas. Deposits are a signature that Senddy relays. Sends and swaps are proofs that Senddy relays.
- The browser holds no API key. It holds a short-lived session token your server mints per user.
- Private keys come from a wallet signature and the pool address. They're derived on the device and never leave it.
1. Packages#
npm install @senddy/client @senddy/core viemPlus your login provider. Embedded-wallet providers such as Privy work well, because every user gets a wallet without a browser extension.
2. Login and wallet#
Any provider that gives you a viem WalletClient for the user works. Two settings matter:
- Prefer embedded wallets. Key derivation signs typed data at the start of each session. With an external wallet that's a popup every login.
- Allow your chain. Robinhood Chain (4663) must be enabled in your provider's dashboard.
3. Session tokens#
Add one server route that verifies the user's login and mints a Senddy session token. Authentication has the code. Pass the provider to the client as sessionToken.
4. The client#
const senddy = createSenddyClient({
publicClient,
walletClient, // the user's embedded wallet
chainId,
pool, // must be exactly right: see below
apiUrl,
subgraphUrl,
assets, // per-asset EIP-712 domains, for chains with more than one asset
defaultAsset,
sessionToken, // your provider from step 3
});
await senddy.init();The pool address is the most important line in the integration. Keys are derived from it. A wrong value doesn't throw: the user derives a different identity and sees an empty balance. Take it from Choose a chain or from @senddy/core, never by hand.
5. Deposits#
You'll usually want both flows from Move money in and out:
- Already in the wallet:
senddy.deposit(amount, { token }). One signature, no gas. - "Send funds to this address": a one-time address for new users. Your server needs a route that receives the deposit request, checks the user's signature against the token's EIP-712 domain, and relays it. Keep that check: it's what stops your route being used as an open relay.
If you adapt an existing deposit route to another chain, make sure the EIP-712 domain uses that chain's ID. A hardcoded chain ID rejects every valid deposit.
6. Content Security Policy#
connect-src needs your instance's origins. For Robinhood Chain:
https://api-rh.senddy.com wss://api-rh.senddy.com
https://api.goldsky.complus your RPC provider and your login provider.
Production checklist#
| Check | Why |
|---|---|
| Show proof progress | Proving takes 10 to 30 seconds in a browser. A real progress bar keeps users from leaving |
| Show withdrawal fees before the first deposit | getExitFeeBps(token). Users should never discover a fee on the way out |
| Show amounts in the asset first | Show the dollar value beside it, never a rate alone |
| Seed a new pool with more than one token | Very small first deposits can fall below the pool's minimum share count |
| Use one login provider app per product | Keeps your users, settings and allowed origins separate from anyone else's |
| Give the site its own API key | So your traffic and rate limits are yours alone |