# Server-side user wallets

Give every user their own on-chain wallet without asking them to install anything or manage keys. Your backend creates a custodial [backend wallet](/docs/products/server) per user, maps it to your own user record, and signs on their behalf when your app needs to act.

Use this when the wallet should feel invisible: fintech balances, in-game assets, reward accounts, or any product where the user shouldn't see a wallet at all.

:::info
This pattern is fully custodial — your backend controls the key. If you want the user to co-own the key and approve actions themselves, use an [embedded wallet](/docs/products/embedded-wallet), and see [User + server signers](/docs/products/server/workflows/user-and-server-signers) to let the server act within a scope the user grants.
:::

## Prerequisites

Complete the [backend setup](/docs/products/server/setup) so `OPENFORT_SECRET_KEY` and `OPENFORT_WALLET_SECRET` are available, then initialize the SDK once and reuse it:

```ts [openfort.ts]
import Openfort from '@openfort/openfort-node'

export const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY!, {
  walletSecret: process.env.OPENFORT_WALLET_SECRET!,
})
```

:::steps
## Create a wallet when the user signs up

Create the wallet once and store its `id` alongside your user. Openfort holds the private key; you only persist the reference.

```ts
import { openfort } from './openfort'

async function createWalletForUser(userId: string) {
  const account = await openfort.accounts.evm.backend.create()

  // Persist the mapping in your own database.
  await db.users.update(userId, {
    walletId: account.id,
    walletAddress: account.address,
  })

  return account
}
```

## Look the wallet up later

Fetch it by the `id` you stored (or by address). Reads don't need the wallet secret; signing does.

```ts
async function getUserWallet(userId: string) {
  const { walletId } = await db.users.get(userId)
  return openfort.accounts.evm.backend.get({ id: walletId })
}
```

## Act on the user's behalf

Sign and submit a transaction from the user's wallet. With a [gas sponsorship](/docs/configuration/gas-sponsorship) configured, the user never needs to hold the native token.

```ts
async function sendForUser(userId: string, to: string, data: string) {
  const account = await getUserWallet(userId)

  const result = await openfort.accounts.evm.backend.sendTransaction({
    account,
    chainId: 84532, // Base Sepolia
    interactions: [{ to, data }],
    // Omit `policy` to use your project's default gas sponsorship.
  })

  return result.response?.transactionHash
}
```
:::

## Keep it safe

* Store only the wallet `id`/address in your database — never the private key.
* Guard the operations above behind your own authentication so a user can only ever move funds from their own wallet.
* Add [signing policies](/docs/configuration/policies) to cap what these wallets can do (contracts, methods, spend limits) as defense-in-depth.
* If a user needs to leave with their key, [export it](/docs/products/server/accounts#exporting-accounts) — after that, its security is their responsibility.

## Next steps

* [Manage accounts](/docs/products/server/accounts) — list, retrieve, import, and export wallets.
* [Signing policies](/docs/configuration/policies) — restrict what each wallet can sign.
* [Agentic wallets](/docs/products/server/workflows/agentic-wallets) — the same pattern for an AI agent instead of a user.
