Accept and send private payments

Let users pay and get paid without showing amounts, balances or names on the public ledger.

A private payment moves money between two Senddy balances. Onchain, nobody can see the amount, the memo, the sender or the recipient. The recipient's app decrypts it and shows it like any other payment.

This guide assumes you've done the Quickstart.

Receive#

Every user has a private receive address. Show it, or encode it in a QR code:

TypeScript
const address = senddy.getReceiveAddress(); // "senddy1…"

It's safe to share. It lets people pay the user and nothing else: it can't be used to read their balance or move their money.

To react when money arrives, listen for balance changes:

TypeScript
senddy.on('balanceChange', (balance) => {
  showToast(`Balance: ${formatUSDC(balance.estimatedUSDC)}`);
});

senddy.startAutoSync();

startAutoSync() keeps the balance fresh in the background. Call stopAutoSync() when the screen closes, and destroy() when the user signs out.

Send#

TypeScript
import { toUSDC } from '@senddy/client';

const result = await senddy.transfer('senddy1…', toUSDC('42.50'), {
  memo: 'March rent',
});
OptionWhat it does
memoA note only the recipient can read. Up to 31 characters
anonymousLeave the sender out of the encrypted memo, so even the recipient doesn't see who paid
tokenSend an asset other than the default, on chains with more than one

Gas is sponsored, so the sender doesn't need ETH.

Show a review screen#

transfer() proves and submits in one call. To show a confirmation step first, split it. The proof is made while the user reads the screen, so "Confirm" feels instant.

TypeScript
const prepared = await senddy.prepareTransfer('senddy1…', toUSDC('42.50'), { memo: 'March rent' });

// show the review screen…

const result = await senddy.submitTransfer(prepared);

Proving takes a few seconds in a browser. Show real progress, not a spinner.

Pay by username#

Senddy app users have usernames. Your server can resolve one to a receive address with your API key:

TypeScript
const res = await fetch(`https://www.senddy.com/api/v1/username/lookup/${username}`, {
  headers: { 'x-api-key': process.env.SENDDY_API_KEY! },
});
const { senddyAddress } = await res.json();

Usernames are shared with the Senddy app. If your app wants its own namespace, keep a mapping from your user IDs to receive addresses on your side.

Cash out to a regular wallet#

To pay someone outside Senddy, or let a user cash out, withdraw to any address:

TypeScript
await senddy.withdraw('0xRecipient…', toUSDC('100'));

A withdrawal is visible onchain, because the money leaves the pool. The user's remaining balance and history stay private. More in Move money in and out.

Good to know#

  • No dust management. The client picks and merges notes for you, and consolidates in the background when a balance gets fragmented.
  • One asset per payment. On chains with more than one asset, pass token and the client only spends notes of that asset.
  • Compliance. Deposits into the pool are screened before money arrives, so every balance users pay from has been checked. See Compliance.