Skip to main content

User Wallets API

User-wallet routes operate on the authenticated user’s embedded wallet. Routes that create, list, or revoke additional signer credentials require a personal user session with recent MFA. Use walletIndex to target a non-default wallet. When omitted, Steward uses walletIndex: 0.

Encrypted Private Key Import

Encrypted import lets an authenticated user import an existing EVM or Solana private key into an embedded user wallet without sending a top-level plaintext privateKey field. The flow is exposed through the API and SDK, and @stwd/react includes a hosted import component that encrypts the key client-side before submit. Sessions are bound to the selected wallet index so one indexed wallet cannot consume another wallet’s import.
Auth: Personal user session with recent MFA Feature flags: STEWARD_ALLOW_PRIVATE_KEY_IMPORT=true and STEWARD_ALLOW_USER_PRIVATE_KEY_IMPORT=true Request headers: Authorization: Bearer <user-session-jwt> and Content-Type: application/json. Responses that carry import-session material are returned with Cache-Control: no-store.

Initialize

The init response returns a one-time X25519 public key, a 10-minute expiry, and AAD fields bound to the personal tenant, user, wallet agent, chain, wallet index, and app client when the session contains one.

Submit

Encrypt the private key client-side with X25519-HKDF-SHA256-AES-256-GCM using the init public key. The AES-GCM AAD is the importSessionId; the HKDF info string is:
Use an empty string for appClientId when the returned AAD value is null. Submit only the encrypted envelope:
walletIndex is optional only when importing the default wallet. If supplied, it must match the walletIndex returned by init. Wrong-index submits return 400 and do not consume the original session; a later submit with the original index can still succeed. Once the tenant, user, app client, agent, chain, wallet index, envelope shape, and decrypted key validate, Steward atomically consumes the session before writing wallet storage. Replay after a successful submit returns 400 with "Encrypted import session is invalid or expired". Session records are serialized through Steward’s KeyStore envelope, then stored through the shared auth StoreBackend (RedisBackend when Redis is configured, otherwise Postgres auth_kv_store when migrations are available, otherwise memory for local/dev fallback). This is not an HSM-backed import-session store. Replay, expiry, tenant/user/app/wallet mismatch, malformed envelopes, and top-level privateKey fields fail closed. Successful import writes encrypted vault storage, reapplies user-wallet default policies, emits redacted audit/webhook metadata, and returns:
SDK
cURL
Production deployments should initialize Redis or run migrations so the import-session backend is durable across API replicas. Production startup fails before accepting traffic if any auth store falls back to memory. The Bun /ready probe reports checks.authStores; only a guaranteed single-instance deployment may acknowledge restart-unsafe memory storage with STEWARD_ALLOW_MEMORY_AUTH_STORES=true.

Additional Signer Credentials

Additional signers let a user create bounded delegated credentials for one wallet index without exporting the wallet private key. User-wallet signers are always HMAC delegated signers and are limited to signing permissions such as sign_message and sign_transaction. Private-key export, recovery, owner, policy, and quorum permissions are rejected by the API. The credential secret is generated by Steward and returned only in the create response. It is never returned by list or revoke responses, and signer metadata redacts credential hashes.

List Signers

Auth: Personal user session with recent MFA Query Parameters:
Response:

Create Signer

Auth: Personal user session with recent MFA Request Body:
Do not send credentialSecret, policyIds, publicKey, or non-HMAC key settings. Steward generates the signer credential and stores only a server-side hash. Response:
Store credentialSecret immediately. Steward displays it once and cannot retrieve it later.

Revoke Signer

Auth: Personal user session with recent MFA The signer must belong to the selected wallet index. Revocation returns the redacted signer object with status: "revoked" and does not include credentialSecret.