Skip to main content
One runnable script per sign-in path, covering the whole flow: session keys → sign in → authenticateJwt with the session signer → create and sign with backUpToDynamic: true → refresh → re-authenticate at the refresh limit.
This script collects the OTP code on stdin; in production, collect it through your own channel.
import readline from 'node:readline/promises';
import { DynamicEvmWalletClient } from '@dynamic-labs-wallet/node-evm';
import {
  createAuthClient,
  generateSessionKeyPair,
  signSessionMessage,
} from '@dynamic-labs-wallet/node';
import { ThresholdSignatureScheme } from '@dynamic-labs-wallet/core';

const environmentId = process.env.DYNAMIC_ENVIRONMENT_ID!;

// 1. Session key pair — persist privateKeyJwk in your secrets vault.
const { publicKeyHex, privateKeyJwk } = await generateSessionKeyPair();

// 2. Sign the user in with email OTP, binding the session public key into the JWT.
const auth = createAuthClient({ environmentId });
const { verificationUUID } = await auth.email.sendOtp('user@example.com');

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const code = await rl.question('OTP code sent to the user: ');
rl.close();

const { jwt } = await auth.email.verifyOtp({
  verificationUUID,
  code,
  sessionPublicKey: publicKeyHex,
});

// 3. Authenticate the wallet client with the JWT and the session signer.
const client = new DynamicEvmWalletClient({
  environmentId,
  enableMPCAccelerator: false, // Set to true only on AWS Nitro Enclave-compatible infrastructure
});
await client.authenticateJwt(jwt, {
  getSessionSignature: (message) => signSessionMessage(message, privateKeyJwk),
});

// 4. Create a wallet for the user, backed up to Dynamic. First run only —
// on later runs, load the persisted walletMetadata and externalServerKeyShares
// instead of creating a new wallet.
const { walletMetadata, externalServerKeyShares } = await client.createWalletAccount({
  thresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
  password: process.env.WALLET_PASSWORD,
  backUpToDynamic: true,
});
console.log('Wallet created:', walletMetadata.accountAddress);
// Persist walletMetadata (durable store) and externalServerKeyShares (secrets vault).

// 5. Sign on the user's behalf.
const signature = await client.signMessage({
  message: 'Hello from your agent',
  walletMetadata,
  externalServerKeyShares,
  password: process.env.WALLET_PASSWORD,
});
console.log('Signed:', signature);

// 6. Long-running sessions: refresh and persist the replacement JWT.
try {
  const refreshedJwt = await client.refreshAuthToken();
  // Overwrite the stored JWT with refreshedJwt.
} catch {
  // Refresh limit (refreshExp) reached or session invalid: repeat step 2,
  // then authenticateJwt with the new token.
}
Run either with your environment variables set, for example:
npx tsx --env-file=.env agent.ts
If sign-in or wallet creation fails, check the dashboard configuration steps in the quickstart first: embedded wallets enabled, chains enabled, and the environment ID correct.
Last modified on July 9, 2026