> ## 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.

# Strict authentication

> When every signing action needs the wallet password again, and how to pass it

Strict authentication is a security mode the account owner turns on in the Trustodian apps, for
themselves or for an organization. When it is on, **every action that releases a key needs the
wallet password typed again, once per action**. The API key alone is no longer enough.

The SDK can read the mode but cannot change it.

> [!NOTE]
>
> When strict authentication is off, nothing extra is asked. You still pass `walletPassword` where
> you did before: seed wallets need it on every signature (see
> [Seed wallets](#seed-wallets-need-the-password-on-every-signature)).

## Who decides

The server decides per request, and the SDK does not predict it. Strict applies when the platform
or the user has it on, or when the **organization that owns the target** has it on. The target is
the wallet, seed, vault or order the request acts on. A personal wallet ignores organization
policies, so the same API key can be strict for one vault and not for another.

## Passing the password

In strict mode, pass `walletPassword` on every method that signs:

| Method | Needs `walletPassword` in strict mode |
| :- | :- |
| `sendTransaction`, `submitRecovery`, `activateXrpTrustLine` | Yes |
| `createOrder`, `createSendOrder`, `createSettingsOrder` (Solana), `signOrder`, `rejectOrder` | Yes |
| `executeOrder` | Yes |
| `activateMultisig` | Yes |
| `revokeAllowance`, `reduceAllowance` | Yes, see [Token approvals](https://sdk-guide.mintlify.site/sdk-guide/token-approvals.md) |
| Hardware wallet methods | No, the device signs. The exception is executing or activating a Vault, see [below](#hardware-wallets) |
| Reads (balances, history, orders, `listAllowances`) | No |

```typescript theme={null}
await sdk.runWithUserRef(userRef, () =>
  sdk.sendTransaction({ ...input, userRef, walletPassword }),
);
```

Methods that already take the password (create and import, `changeWalletPassword`,
`rotateOprfKeyVersion`, `recoverWalletsBatch`, `topUpCosignerKeys`, employee provisioning) keep
taking it. In strict mode they also use it to read the shares they need.

The SDK checks the password you pass against the server with a proof derived from the typed value.
It never uses the unlocked session for this, so an unlocked session does not replace the prompt.

### One password per action

The SDK sends each request without a proof first. If the server answers that the password is
required, the SDK proves it against a one-time challenge and repeats the request once. The refused
request released nothing, so repeating it is safe. The server answers with a short-lived grant (two
minutes), and the rest of the same call reuses it:

* further key releases of the call (a vault order that signs with several keys);
* the order or vault submission that follows (`order/sign`, `order/execute`, `order/activate`).

The grant is dropped when the method returns. The next method call is a new action and needs the
password again. `signOrder` followed by `executeOrder` is two actions, so the user enters the
password twice.

If the call has no `walletPassword` and the server requires it, the SDK throws
`StrictAuthPasswordRequiredError`. Ask the user for the password and call the method again.

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

try {
  await sdk.signOrder({ ...input, userRef });
} catch (error) {
  if (!(error instanceof StrictAuthPasswordRequiredError)) throw error;
  const walletPassword = await askUserForWalletPassword();
  await sdk.signOrder({ ...input, userRef, walletPassword });
}
```

## Reading the mode

```typescript theme={null}
const status = await sdk.getStrictAuthStatus();
const { organizationId } = await sdk.getOrgMe();
const inOrg = await sdk.getStrictAuthStatus({ organizationId });
```

```typescript theme={null}
type StrictAuthStatus = {
  effective: boolean;
  sources: { platform: boolean; organization: boolean; user: boolean; device: boolean };
  factor: 'passkey' | 'wallet_password' | null;
  passkeyEnabled: boolean;
  walletPasswordSet: boolean;
};
```

Use it to decide whether to show a password field up front. Without `organizationId` the answer
covers the platform and the user. With it, that organization's policy is added; `getOrgMe()` returns the id. It cannot tell you
what the server will decide for a particular wallet or vault, so treat `effective: false` as
"probably not", never as a guarantee. The answer is cached for 30 seconds per organization.

## Hardware wallets

The device signs, so hardware methods take no password, with one exception. Executing or activating
a Vault (`prepareHardwareOrderExecute`, `prepareHardwareVaultActivation`) needs the password in
strict mode. Pass it to the prepare call and again to the submit:

```typescript theme={null}
const request = await sdk.prepareHardwareOrderExecute({ ...input, walletPassword });
// run request.calls on the device
const outcome = await sdk.submitHardwareStep(request, result, { walletPassword });
```

The SDK does not keep the password between steps. Without it on the submit, the server's refusal
reaches you as `StrictAuthPasswordRequiredError` after the device has already signed, and the user
has to start again. Read `getStrictAuthStatus()` before the session if you want to ask first.

## Errors

| Error | `code` | When | What to do |
| :- | :- | :- | :- |
| `StrictAuthPasswordRequiredError` | `STRICT_AUTH_PASSWORD_REQUIRED` | The server requires the password and the call had none | Ask for the password and call again |
| `WalletPasswordAttemptError` | | The password was wrong. `lockInfo` carries `failedAttempts`, `lockedUntil`, `retryAfterMs`, `forceLogout` | Ask again. It counts toward the same lockout as unlocking |
| `WalletPasswordLockedError` | `SESSION_WALLET_PASSWORD_LOCKED` | Too many wrong attempts | Wait until `lockedUntil` |
| `StepUpGrantRejectedError` | `STEP_UP_GRANT_REJECTED` | The grant of the running action was refused. `reason` is `spent` or `invalid` | Start the action again with the password. The SDK never retries this itself |

An account whose password was never proven to the server answers the challenge with HTTP 409
`WALLET_PASSWORD_MIGRATION_REQUIRED`. Call `unlockWalletSession` once with the password, then retry.

## Seed wallets need the password on every signature

Signing with a wallet derived from the recovery phrase rebuilds the account seed for that one
signature and wipes it afterwards. The seed is never kept between calls, so pass `walletPassword` on
every signing call for a seed wallet, in either mode. Without it the call fails with
`SESSION_LOCKED`.

Each signature is a separate request to a rate-limited route, so signing many transactions in a row
with a seed wallet is throttled per signature.

> [!WARNING]
>
> `SDKConfig.seedEntropyCache` is deprecated and ignored. Setting it logs a one-time warning and has
> no other effect. Remove it from your configuration.


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