> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Use Agent Wallets

> Authenticate the wallet client with the user JWT and session signer, create wallets and sign with backUpToDynamic, and refresh long-running sessions.

With a JWT in hand, authenticate the wallet client and operate wallets as the user: create, sign, and keep the session alive.

<Note>
  Before this: a Dynamic user JWT minted with your session public key bound in — see [Sign In with Email OTP](/node/agents/sign-in-with-email-otp) or [Sign In with a Private Key](/node/agents/sign-in-with-a-private-key) — and access to the session private key.
</Note>

## Authenticate the wallet client

```typescript theme={"system"}
import { DynamicEvmWalletClient } from '@dynamic-labs-wallet/node-evm';
import { signSessionMessage } from '@dynamic-labs-wallet/node';

const client = new DynamicEvmWalletClient({
  environmentId: process.env.DYNAMIC_ENVIRONMENT_ID!,
  enableMPCAccelerator: false, // Set to true only on AWS Nitro Enclave-compatible infrastructure
});

await client.authenticateJwt(jwt, {
  getSessionSignature: (message) => signSessionMessage(message, privateKeyJwk),
});
```

`authenticateJwt` installs the JWT as the bearer token for all subsequent calls. There is no token exchange: the client only checks the token's structure (a compact JWS with three segments); the server validates it on every request.

`getSessionSignature` keeps key custody with you. The session private key never enters the SDK — the SDK sends messages to your callback and uses the signatures it returns. `signSessionMessage` implements the callback for a stored JWK; a KMS- or HSM-backed signer works the same way as long as it returns the same encoding.

<Warning>
  `getSessionSignature` must return the signature as lowercase hex: the raw 64-byte ECDSA P-256 `r‖s`, exactly as `signSessionMessage` produces. Base64 output corrupts the proof — base64 can contain `/`, which breaks the `/`-delimited composite the SDK sends to the keyshares relay.
</Warning>

## Create wallets and sign

With the signer configured, use the client as usual. When you pass `backUpToDynamic: true`, the SDK automatically attaches a signed-session proof to the backup and recovery calls — it signs the JWT's session ID and a single-use server nonce with your callback. Without a signer, those calls fail with a 400 from the keyshares relay for agent JWT sessions.

```typescript theme={"system"}
import { ThresholdSignatureScheme } from '@dynamic-labs-wallet/core';

const { walletMetadata, externalServerKeyShares } = await client.createWalletAccount({
  thresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
  password: process.env.WALLET_PASSWORD,
  backUpToDynamic: true,
});
// Persist walletMetadata (durable store) and externalServerKeyShares (secrets vault) —
// see Storage & Security for the full persistence matrix.

const signature = await client.signMessage({
  message: 'Hello from your agent',
  walletMetadata,
  externalServerKeyShares,
  password: process.env.WALLET_PASSWORD,
});
```

## Reuse an existing wallet

Creation is a one-time step. On later runs, load the persisted `walletMetadata` and `externalServerKeyShares` from your stores instead of creating a new wallet, and sign directly:

```typescript theme={"system"}
const walletMetadata = await loadWalletMetadata(userId); // your durable store
const externalServerKeyShares = await loadKeyShares(userId); // your secrets vault

const signature = await client.signMessage({
  message: 'Hello again from your agent',
  walletMetadata,
  externalServerKeyShares,
  password: process.env.WALLET_PASSWORD,
});
```

<Warning>
  Calling `createWalletAccount` on every run creates a new wallet each time. Create once, persist the returned artifacts, and reuse them for the life of the wallet.
</Warning>

## Refresh long-running sessions

User JWTs are short-lived. `refreshAuthToken` refreshes the JWT using the current session, installs the replacement on the client, and returns it so you can persist it. The session signer you passed to `authenticateJwt` is preserved across refreshes.

```typescript theme={"system"}
const refreshedJwt = await client.refreshAuthToken();
// Overwrite the stored JWT — the previous token is superseded.
```

Sign-in returns `expiresAt` — schedule refreshes ahead of it instead of waiting for a 401.

The server caps how long a session can be extended via the JWT's `refreshExp` claim. Past that limit, `refreshAuthToken` fails with a 401 and a new sign-in is required — refresh cannot extend a session indefinitely. For a human user, that means re-approving (a new OTP code or SIWE signature). For an autonomous agent with an agent signing token, re-authentication is just another programmatic sign-in.

```typescript theme={"system"}
try {
  await client.refreshAuthToken();
} catch {
  // Refresh limit reached or session invalid: run the sign-in flow again,
  // then call authenticateJwt with the new token.
}
```

`refreshAuthToken` also throws a descriptive error when the environment is configured for cookie-based auth: refresh responses then carry no JWT in the body, and agent flows require header-based JWTs.

## Next steps

* [Example Usage](/node/agents/example-usage) — complete runnable scripts for both sign-in paths.
* [Storage & Security](/node/agents/storage-and-security) — what to persist, and the security model.

## API reference

* [authenticateJwt](/node/reference/auth/authenticate-jwt)
* [refreshAuthToken](/node/reference/auth/refresh-auth-token)
* [signSessionMessage](/node/reference/auth/sign-session-message)
