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:

  1. Users never need gas. Deposits are a signature that Senddy relays. Sends and swaps are proofs that Senddy relays.
  2. The browser holds no API key. It holds a short-lived session token your server mints per user.
  3. Private keys come from a wallet signature and the pool address. They're derived on the device and never leave it.

1. Packages#

Terminal
npm install @senddy/client @senddy/core viem

Plus 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#

TypeScript
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.com

plus your RPC provider and your login provider.

Production checklist#

CheckWhy
Show proof progressProving takes 10 to 30 seconds in a browser. A real progress bar keeps users from leaving
Show withdrawal fees before the first depositgetExitFeeBps(token). Users should never discover a fee on the way out
Show amounts in the asset firstShow the dollar value beside it, never a rate alone
Seed a new pool with more than one tokenVery small first deposits can fall below the pool's minimum share count
Use one login provider app per productKeeps your users, settings and allowed origins separate from anyone else's
Give the site its own API keySo your traffic and rate limits are yours alone