# User Onboarding

Take a new user from sign-up to a first wallet. Which wallet flow you run is set by your tenant's wallet mode — it is not a per-user choice.

## Overview

Every tenant has a `walletMode` in its configuration: `custodial` (the default when none is set), `non_custodial` or `mpc`. Registration has no custody parameter, so every user of a tenant follows that tenant's mode, and each mode creates wallets through different endpoints:

- **custodial** — the server generates and stores the key: `POST /v1/wallets/`.
- **non_custodial** — the key is generated on the device and only the address is registered: `POST /v1/wallets/client`. `POST /v1/wallets/` returns `400`.
- **mpc** — a 2-of-3 threshold wallet created through the MPC key-generation ceremony. `POST /v1/wallets/` returns `400`.

## Step 1: Read the Wallet Mode

Public Config (`GET /v1/tenant/public`) returns `walletMode` before any user is logged in. Like registration, it is authenticated with HMAC headers instead of a Bearer token: `x-tenant-api-key`, `x-timestamp` (Unix seconds, accepted within ±300 s of server time) and `x-signature` — a hex HMAC-SHA256, keyed with the tenant API secret, of `method`, request path (including any query string), timestamp and the hex SHA-256 of the raw body, joined by newlines. A given signature is accepted only once.

```typescript
import { createHash, createHmac } from 'node:crypto';

const API = 'https://api.aureahub.com';

// HMAC headers for tenant-signed routes such as /v1/tenant/public and /v1/auth/register
function tenantHeaders(method: string, pathWithQuery: string, rawBody = '') {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const bodyHash = createHash('sha256').update(rawBody).digest('hex'); // SHA-256 of '' for no body
  const signature = createHmac('sha256', process.env.AUREA_TENANT_API_SECRET!)
    .update(`${method}\n${pathWithQuery}\n${timestamp}\n${bodyHash}`)
    .digest('hex');
  return {
    'x-tenant-api-key': process.env.AUREA_TENANT_API_KEY!,
    'x-timestamp': timestamp,
    'x-signature': signature,
  };
}

const { walletMode } = await fetch(`${API}/v1/tenant/public`, {
  headers: tenantHeaders('GET', '/v1/tenant/public')
}).then(r => r.json());
// 'custodial' | 'non_custodial' | 'mpc'
```

After login you can also use Available Features (`GET /v1/tenant/available-features`), whose `nonCustodialWallets` flag is `true` exactly when `walletMode` is `non_custodial`, and [MPC Config](https://docs.aureahub.com/docs/mpc-config.md) for MPC tenants.

## Step 2: Register

`POST /v1/auth/register` takes `username` (3–100 letters, digits, `_` or `-`), `email`, `password` (at least 8 characters with upper-case, lower-case and a digit) and an optional `wallet` object whose `option` is `create`, `import` or `skip`. It requires the same HMAC headers, computed over the exact body you send. The response contains `accessToken`, `refreshToken`, `expiresIn`, `user` and `wallet` (`null` when no wallet was created). Full reference: [Register](https://docs.aureahub.com/docs/auth-register.md).

```typescript
const body = JSON.stringify({
  username: 'alice',
  email: 'alice@example.com',
  password: 'SecurePass123',
  // custodial tenants only — see below
  ...(walletMode === 'custodial' ? { wallet: { option: 'create', chain: 'gnosis' } } : {})
});

const res = await fetch(`${API}/v1/auth/register`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', ...tenantHeaders('POST', '/v1/auth/register', body) },
  body
});
const { accessToken, refreshToken, user, wallet } = await res.json();
const bearer = { Authorization: `Bearer ${accessToken}` };
```

## Custodial Tenants

Create the first wallet at registration with `wallet: { option: "create", chain }`, or afterwards with [Create Wallet](https://docs.aureahub.com/docs/wallets-create.md). If the tenant is configured with a default registration chain, a wallet is also created at sign-up when `wallet` is omitted. The new wallet is made primary when the user has no primary wallet on that chain and network type.

The server generates the key and stores it encrypted. When a key vault is configured, the key is split into two Shamir shares — one encrypted in the database, one in the key vault — and the wallet reports `keyManagementScheme: "sss_2of2_akv"`; otherwise it reports `aes_single`.

```typescript
const wallet = await fetch(`${API}/v1/wallets/`, {
  method: 'POST',
  headers: { ...bearer, 'Content-Type': 'application/json' },
  body: JSON.stringify({ chain: 'gnosis', name: 'Main' })
}).then(r => r.json());
```

## Non-Custodial Tenants

Do not send `wallet.option: "create"` at registration and do not call `POST /v1/wallets/` — both return `400` on these tenants. Generate the key on the device, prove ownership with a challenge, and register the address. Aurea stores no key material; transfers and swaps are prepared by the server and signed on the device.

```typescript
import { Wallet, getBytes } from 'ethers';

const device = Wallet.createRandom();            // keep the key in secure device storage

const q = new URLSearchParams({ chain: 'evm', address: device.address });
const { challenge } = await fetch(`${API}/v1/wallets/client/challenge?${q}`, { headers: bearer })
  .then(r => r.json());

const wallet = await fetch(`${API}/v1/wallets/client`, {
  method: 'POST',
  headers: { ...bearer, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    chain: 'evm',                                  // one wallet per supported EVM chain; returns the gnosis one
    address: device.address,
    signedChallenge: await device.signMessage(getBytes('0x' + challenge))  // sign the 32 challenge bytes
  })
}).then(r => r.json());
// wallet.keyManagementScheme === 'client_side'
```

Details: [Non-Custodial Challenge](https://docs.aureahub.com/docs/wallets-client-challenge.md), [Register Client Wallet](https://docs.aureahub.com/docs/wallets-client-register.md) and the [Non-Custodial Key Lifecycle](https://docs.aureahub.com/docs/guide-non-custodial-keys.md) guide.

## MPC Tenants

On an MPC tenant, `wallet.option: "create"` at registration creates no wallet (the response's `wallet` is `null`), and `POST /v1/wallets/` returns `400`. Create the wallet with the MPC flow: [MPC Config](https://docs.aureahub.com/docs/mpc-config.md) → [Start DKG Ceremony](https://docs.aureahub.com/docs/mpc-dkg-start.md) → [Register MPC Wallet](https://docs.aureahub.com/docs/mpc-wallet-register.md). MPC wallets report `keyManagementScheme: "mpc_tss"`.

## Step 3: Primary Wallet and Balance

The first wallet is usually primary already (see above). To choose another, use [Set Primary Wallet](https://docs.aureahub.com/docs/wallets-primary.md) — the body `{ "isPrimary": true }` is required — or [Set Primary (atomic)](https://docs.aureahub.com/docs/wallets-set-primary.md). Then read the balance with [Get Balance](https://docs.aureahub.com/docs/wallets-balance.md).

```typescript
if (!wallet.isPrimary) {
  await fetch(`${API}/v1/wallets/${wallet.id}/primary`, {
    method: 'PATCH',
    headers: { ...bearer, 'Content-Type': 'application/json' },
    body: JSON.stringify({ isPrimary: true })
  });
}

const balance = await fetch(`${API}/v1/wallets/${wallet.id}/balance`, { headers: bearer })
  .then(r => r.json());
// { walletId, address, chain, nativeBalance: { balance, symbol, decimals }, tokens, lastUpdated }
```

## Custody Modes at a Glance

|  | custodial | non_custodial | mpc |
| --- | --- | --- | --- |
| Wallet created with | `POST /v1/wallets/` or `wallet.option: "create"` at registration | `GET /v1/wallets/client/challenge` + `POST /v1/wallets/client` | MPC DKG ceremony + `POST /v1/mpc/wallets` |
| Private key | Held by the server, encrypted (Shamir 2-of-2 across database and key vault when a key vault is configured) | Only on the user's device; the server stores the address | Threshold key shares — see MPC Wallets |
| keyManagementScheme | `sss_2of2_akv` or `aes_single` | `client_side` | `mpc_tss` |
| Transfers and swaps | Signed server-side | Prepared by the server, signed on the client | Prepared by the server, signed on the client |
| dApp personal_sign | Server returns `signature` | Client signs the returned `messageHash` | Client signs the returned `messageHash` |
| Key export | Through key migration, if enabled for the tenant | Not applicable — no server-held key | Rejected (`409`) — no server-held key |

---

Web version: https://docs.aureahub.com/#guide-user-onboarding
