# Move money in and out

> Deposit from a wallet, from a one-time address, or from another chain, and withdraw anywhere.

Source: https://www.senddy.com/docs/guides/deposits
Last modified: 2026-10-07T18:10:55.000Z

---

Money enters a private balance with a deposit and leaves with a withdrawal. Both are gasless for the user. Deposits and withdrawals are visible onchain, because money crosses the pool's edge. Everything that happens inside stays private.

## From the user's wallet

If the user already holds the asset in the wallet connected to the client, it's one call and one signature:

```typescript
await senddy.deposit(toUSDC('100'));

// on chains with more than one asset
await senddy.deposit(amount, { token: HOODS });
```

There's no approval step and no gas. The signature authorizes exactly this deposit into exactly these private notes, so it can't be replayed or redirected.

## From anywhere, with a one-time address

New users often have money somewhere else: an exchange, another wallet, another app. Give them a one-time address to send to. When funds arrive, your server deposits them into the user's private balance.

The pieces are:

1. **In the browser**, create a deposit intent. It holds a fresh one-time key derived for this user. Save it durably before you show the address.
2. **The user sends** the asset to that address, from anywhere.
3. **Your server** watches the address and, when funds land, calls `depositWithEphemeralAccount()` with a relay submitter, which shields them.

```typescript
import { createEphemeralDepositIntent, deriveEphemeralDepositAccount } from '@senddy/client';
```

[Build a web app](/docs/guides/web-app) covers the server route this needs and the check it must keep.

## From Hyperliquid

Users can fund their private balance straight from Hyperliquid. Ask the client for an inbound address, save its intent, and show the address:

```typescript
const inbound = await senddy.hyperliquidInboundAddress({
  saveIntent: async (intent) => db.saveDepositIntent(user.id, intent),
});

inbound.depositAddress; // the user sends here from Hyperliquid
```

Funds are bridged and land in the user's private balance. Keep the intent and keep watching while the address is shown in your app, because every arrival lands at it.

## From other chains

Deposits from Solana and from other EVM chains work the same way as Hyperliquid: a reusable address on that chain, bridged through Relay into the user's private balance.

```typescript
import { EVM_BRIDGE_CHAINS } from '@senddy/client';

// Solana: USDC
const sol = await senddy.solanaInboundAddress({
  saveIntent: async (intent) => db.saveDepositIntent(user.id, intent),
});

// Ethereum, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche: USDC. Robinhood Chain: USDG.
const arb = await senddy.chainInboundAddress({
  chainId: EVM_BRIDGE_CHAINS.arbitrum,
  saveIntent: async (intent) => db.saveDepositIntent(user.id, intent),
});
```

Show a minimum amount: small transfers can cost more to bridge than they're worth. Lighter can't send to a deposit address, so a user coming from Lighter withdraws to their own wallet first.

## Withdraw to any address

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

Some assets carry a withdrawal fee, set by the asset's listing or by your app. Read it with `getExitFeeBps(token)` and show it before the user deposits. See [Set your fee](/docs/guides/fees).

## Withdraw to Hyperliquid

```typescript
const out = await senddy.bridgeToHyperliquid({
  recipient: '0xTheirHyperliquidAccount…',
  amount: toUSDC('250'),
  saveRefundIntent: async (intent) => db.saveRefundIntent(user.id, intent),
});

const status = await senddy.bridgeStatus({ txHash: out.txHash });
```

If the bridge can't fill, the money comes back to a one-time refund address. That's why the refund intent is saved first, before anything moves. The minimum is 5 USDC.

## Withdraw to other chains

```typescript
// Any EVM chain in EVM_BRIDGE_CHAINS, arriving as that chain's dollar token
await senddy.bridgeToChain({
  chainId: EVM_BRIDGE_CHAINS.ethereum,
  recipient: '0xRecipient…',
  amount: toUSDC('100'),
  saveRefundIntent: async (intent) => db.saveRefundIntent(user.id, intent),
});

// Solana
await senddy.bridgeToSolana({ recipient: 'SolanaAddress…', amount: toUSDC('100'), saveRefundIntent });

// Lighter: the wallet that owns the account, or its account index
await senddy.bridgeToLighter({ recipient: '0xTheirWallet…', amount: toUSDC('100'), saveRefundIntent });
```

Minimums: 1 USDC to an EVM chain or Solana, 5 USDC to Hyperliquid or Lighter. A Lighter withdrawal is refused before anything moves if the wallet has no Lighter account.

## Screening

Every deposit is checked against sanctions oracles before it's accepted. A flagged address can't deposit. See [Compliance](/docs/technical/compliance).
