> ## Documentation Index
> Fetch the complete documentation index at: https://sdk-guide.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> API keys, the wallet-password session, and userRef

Trustodian uses two independent credentials, and both are needed for anything that touches key material:

| Credential | Authenticates | Scope |
| - | - | - |
| API key | Your backend to the Trustodian API | Every request |
| Wallet password | The end user to their own key material | Every crypto operation |

An API key alone is enough for read-only calls (wallet lists, portfolio, orders, history). It can never sign: without the user's wallet password the SDK cannot derive the key that decrypts a share.

> [!WARNING]
>
> **Server-side only.** Neither credential may reach a browser or mobile client.

## API key

**1. Create the key**

Sign in to the Trustodian app, open Settings → API Keys, and create a key with the permissions
your integration needs. A key can also carry an IP whitelist and a per-minute rate limit.

This is the one step the SDK cannot do for you. An API key cannot issue another API key, and
there is no registration or login call in the SDK.

**2. Store it as a secret**

Keep it in your secret manager or environment, never in source control.

**3. Pass it to the constructor**

```typescript theme={null}
import { TrustodianSDK } from 'vaults-multichain-sdk';

const sdk = new TrustodianSDK({
  apiKey: process.env.TRUSTODIAN_API_KEY!,
  network: 'testnet',
  shareStore: myShareStore,
});
```

The key is sent as the `x-api-key` header on every request. The backend resolves the key to a user account, so `userRef` for that account comes from the key itself. See [`userRef`](#userref) below.

### Passkey / 2FA step-up

`changeWalletPassword` accepts an optional `stepUp` object carrying a passkey assertion and a 2FA code.

```typescript theme={null}
await sdk.changeWalletPassword(userRef, {
  currentPassword,
  newPassword,
  stepUp: { assertion: passkeyAssertionB64, twoFactorCode: '123456' },
});
```

For API-key transport the operation is gated by the key's own permissions instead, so `stepUp` is optional for SDK integrations.

## The wallet-password session

Before `createWallet`, `importWallet`, `sendTransaction`, `signOrder`, or any other operation that reconstructs a key, the user's wallet password must have unlocked a session.

The password itself is never sent. Unlocking derives an in-memory session key, and the session must be unlocked before any operation that reconstructs a key. See [Key Management](https://sdk-guide.mintlify.site/sdk-guide/key-management.md) for what the derived keys encrypt and why some calls still take a `password` argument even when the session is open.

### Lifecycle methods

| Method | Signature | Purpose |
| - | - | - |
| `unlockWalletSession` | `(userRef, password)` | Authenticate an existing password; fails if none is registered |
| `lockWalletSession` | `(userRef)` | Synchronously wipe the session key and any cached seed |
| `getEncTokenStatus` | `()` | Whether a password is configured, plus lockout state |
| `getWalletPasswordHint` | `()` | The user-set hint, or `null` |
| `changeWalletPassword` | `(userRef, { currentPassword, newPassword, passwordHint?, stepUp? })` | Rotate the password and re-encrypt every share |

```typescript theme={null}
const userRef = await sdk.getSdkUserId();

await sdk.unlockWalletSession(userRef, userPassword);

// … crypto work …

sdk.lockWalletSession(userRef);
```

> [!NOTE]
>
> The password itself was set during app onboarding, since an account cannot be issued an API key
> before that. `getEncTokenStatus()` and `getWalletPasswordHint()` are there if you need to check
> state or help a user who has forgotten theirs.

### Session TTL

A session lives for `keyTtlMs` milliseconds, 5 minutes by default, after which the session key is wiped. Configure it on the constructor:

```typescript theme={null}
const sdk = new TrustodianSDK({
  apiKey: process.env.TRUSTODIAN_API_KEY!,
  network: 'mainnet',
  shareStore: myShareStore,
  keyTtlMs: 600_000, // 10 minutes
});
```

Sessions are keyed by `userRef`, so one SDK instance can hold several users unlocked at once. Do not lean on the TTL as a security control. Call `lockWalletSession(userRef)` as soon as the request that needed it is finished.

### Password status and lockout

```typescript theme={null}
const status = await sdk.getEncTokenStatus();
// { exists, failedAttempts?, lockedUntil?, retryAfterMs? }

const hint = await sdk.getWalletPasswordHint(); // string | null
```

Repeated wrong passwords lock the account: the API answers `423 Locked` with `lockedUntil` and `retryAfterMs`, and the SDK raises a wallet-password lock error carrying both. Surface the wait rather than retrying in a loop.

### Changing the password

```typescript theme={null}
await sdk.changeWalletPassword(userRef, {
  currentPassword,
  newPassword,
  passwordHint: 'New hint',
});
```

`changeWalletPassword` is a full re-encryption pass over every share the account has. See [Key Management](https://sdk-guide.mintlify.site/sdk-guide/key-management.md#changing-the-wallet-password) for the result shape and the step-up rule.

## `userRef`

`userRef` is the stable Trustodian account id the API key resolves to. It scopes session keys, `shareStore` rows, and every crypto call.

```typescript theme={null}
const userRef = await sdk.getSdkUserId();
```

Use the same value everywhere for a given account. Two ways to supply it:

```typescript Explicit theme={null}
await sdk.sendTransaction({
  userRef,
  chain: EChainType.EVM,
  from,
  to,
  amount: '0.1',
  chainId: 1,
  tokenAddress: '',
  decimals: 18,
  isNativeToken: true,
});
```

```typescript Ambient theme={null}
await sdk.runWithUserRef(userRef, async () => {
  await sdk.sendTransaction({
    chain: EChainType.EVM,
    from,
    to,
    amount: '0.1',
    chainId: 1,
    tokenAddress: '',
    decimals: 18,
    isNativeToken: true,
  });
});
```

`runWithUserRef` keeps concurrent requests isolated from one another on Node.js, so you can set the `userRef` once per request instead of threading it through every call.

## Account and platform info

| Method | Returns |
| - | - |
| `getSdkUser()` | The authenticated profile |
| `getSdkUserId()` | The account's `userRef` |

```typescript theme={null}
const user = await sdk.getSdkUser();
// { uid, id, email, name?, isTwoFA, force2FA, hasRecoveryPhrase?, recoveryPhraseVerified? }
```

`hasRecoveryPhrase` and `recoveryPhraseVerified` drive the seed flow, described in [Recovery Phrase](https://sdk-guide.mintlify.site/sdk-guide/recovery-phrase.md). `getRecoveryPhraseStatus()` reads them fresh; `getSdkUser()` may serve a cached profile.

## Authentication failures

Auth failures arrive as `UnauthorizedError`, `PermissionDeniedError` or `RateLimitError`. For the HTTP status mapping, the full class list and recovery guidance, see [Errors](https://sdk-guide.mintlify.site/sdk-guide/errors.md).

> [!WARNING]
>
> An invalid API key returns `403`, not `401`. A `401` means the header was absent, or the account
> behind a valid key could not be resolved. Branch on both when handling auth failures.

## Security checklist

> **Keep the API key in a secret manager**
>
> `apiKey: process.env.TRUSTODIAN_API_KEY!`, never in source control and never in a client bundle.
> Use the key's IP whitelist so a leaked key is still unusable from elsewhere.

> **Never store or log the wallet password**
>
> Take it from the authenticated user per request, use it, and drop it. It does not belong in env
> vars, config files, your database, or `shareStore`. Never write it to a log, not even at debug
> level, and the same goes for a device share or a reconstructed key.

> **Implement IntegratorShareStore for production**
>
> `InMemoryShareStore` loses every device share on restart. Persist device shares per
> `(userRef, walletId)` in encrypted storage. See [Key Management](https://sdk-guide.mintlify.site/sdk-guide/key-management.md).

> **Lock sessions eagerly**
>
> `lockWalletSession(userRef)` in a `finally` block beats waiting out `keyTtlMs`.

> **Give each integration its own key**
>
> Separate keys per environment and per service keep permissions tight and make revocation cheap.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.