Skip to content

refactor(sdk): throwing getters for signer/credentials, drop operation args#324

Draft
ghermet wants to merge 17 commits intoprereleasefrom
refactor/throwing-getters-signer-credentials
Draft

refactor(sdk): throwing getters for signer/credentials, drop operation args#324
ghermet wants to merge 17 commits intoprereleasefrom
refactor/throwing-getters-signer-credentials

Conversation

@ghermet
Copy link
Copy Markdown
Contributor

@ghermet ghermet commented May 6, 2026

Summary

  • Replace ZamaSDK.requireSigner(op) with a throwing signer getter (backed by #signer) plus a non-throwing hasSigner peek. The throwing path goes through assertNonNullable and wraps the TypeError as the cause of SignerNotConfiguredError.
  • Convert #requireCredentialService(op) into a private throwing getter #credentials over the renamed backing field #credentialService.
  • Collapse requireChainAlignment(op) and requireAlignedWalletAccount(op) into a single async sdk.getWalletAccount() — no production code consumed the chain-id return value, and every call site already needed the chain-alignment assertion.
  • Drop the operation: string argument from SignerNotConfiguredError, WalletNotConnectedError, WalletAccountNotReadyError, and ChainMismatchError. Flatten the hierarchy: SignerRequiredError is removed; the three errors now extend ZamaError directly.
  • Drop the unused operation parameter from GenericSigner.requireWalletAccount() and the viem/ethers/base/wagmi implementations + tests.
  • Regenerate API reports and update gitbook references (errors.md, handle-errors.md).

Breaking changes

  • sdk.signer no longer typed GenericSigner | undefined. Reading it without a configured signer throws SignerNotConfiguredError. Use sdk.hasSigner to peek.
  • sdk.requireSigner(...), sdk.requireChainAlignment(...), sdk.requireAlignedWalletAccount(...) are removed. Use sdk.signer and sdk.getWalletAccount().
  • SignerRequiredError removed.
  • error.operation no longer set on SignerNotConfiguredError, WalletNotConnectedError, WalletAccountNotReadyError, ChainMismatchError.
  • GenericSigner.requireWalletAccount() no longer takes an operation argument.

Test plan

  • pnpm typecheck clean across all 6 typechecking packages
  • pnpm -w run test:run — 1580 passed / 5 skipped
  • pnpm lint — 0 warnings, 0 errors
  • pnpm api-report:check clean
  • Manual smoke against a downstream consumer to confirm error-handling flows that previously matched on error.operation are migrated

🤖 Generated with Claude Code

ghermet and others added 12 commits May 5, 2026 17:22
Design for replacing requireSigner/requireCredentialService with throwing
getters and a hasSigner peek. Async require methods stay as-is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the assertNonNullable TypeError in SignerNotConfiguredError via the
options.cause field, preserving the underlying assertion for diagnostics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec corrections (operation stays on SignerRequiredError as optional metadata
for the wallet errors) plus a task-by-task plan covering error refactor,
signer getter, credentialService getter, call site migration, API regen,
and docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
operation becomes optional on SignerRequiredError (passed via the options
bag). WalletNotConnectedError and WalletAccountNotReadyError continue to
forward operation. SignerNotConfiguredError no longer interpolates the
operation name in its message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SignerNotConfiguredError, WalletNotConnectedError, and WalletAccountNotReadyError
now extend ZamaError directly. SignerRequiredError is removed. None of these
errors take an operation argument anymore — messages are generic.

Drop the unused operation parameter from GenericSigner.requireWalletAccount
and from the viem/ethers/base signer implementations and call sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sSigner

The signer field is now a public throwing getter backed by #signer; reading
sdk.signer when no signer is configured throws SignerNotConfiguredError whose
cause is the underlying TypeError from assertNonNullable. hasSigner provides
a non-throwing peek for callers that want to test for configuration without
throwing (disposal path, useWalletAccount snapshot read).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…getter

The backing field is now #credentialServiceField; the throwing accessor
#credentialService is a private getter that asserts non-null and rethrows
the TypeError as the cause of SignerNotConfiguredError. Internal peek call
sites use #credentialServiceField directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflects the new ZamaSDK.signer getter + hasSigner peek, the removed
requireSigner method, and the flattened SignerRequiredError hierarchy
(SignerNotConfiguredError / WalletNotConnectedError / WalletAccountNotReadyError
now extend ZamaError directly, no operation field). GenericSigner /
BaseSigner.requireWalletAccount() lost its operation parameter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The base SignerRequiredError class is gone — SignerNotConfiguredError now
extends ZamaError directly. Replace stale entries in error tables with
SignerNotConfiguredError / SIGNER_NOT_CONFIGURED, and remove the line
about the error carrying the operation name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dentialService for the field

Swap the names so the backing field gets the natural #credentialService
identifier and the throwing private getter becomes #credentials. Local
variables follow: callers receive the service as `credentials`, and the
bundle returned from credentials.allow() is named `bundle` to disambiguate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…unt with getAccount

Drop the two require* methods and the operation argument they only fed to
ChainMismatchError. Expose a single getAccount() that returns the wallet
account after asserting signer/provider chain alignment. Group-B callers
that only wanted the side-effect now await getAccount() and discard the
return; group-A callers (the majority) already wanted the account address.

ChainMismatchError loses its operation field too — generic message now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
More descriptive name, matches the property being returned. Updates all
call sites in Token, ReadonlyToken, ZamaSDK internals, tests, and the API
reports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cla-bot cla-bot Bot added the cla-signed label May 6, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Public API Changes

sdk-ethers.api.md
--- a/sdk-ethers.api.md
+++ b/sdk-ethers.api.md
@@ -80,7 +80,7 @@
     // Warning: (ae-forgotten-export) The symbol "WalletAccount" needs to be exported by the entry point index.d.ts
     //
     // (undocumented)
-    requireWalletAccount(operation: string): WalletAccount;
+    requireWalletAccount(): WalletAccount;
     // Warning: (ae-forgotten-export) The symbol "EIP712TypedData" needs to be exported by the entry point index.d.ts
     //
     // (undocumented)
sdk-query.api.md
--- a/sdk-query.api.md
+++ b/sdk-query.api.md
@@ -416,7 +416,7 @@
 export interface GenericSigner {
     dispose?(): void;
     refreshWalletAccount?(): Promise<WalletAccount | undefined>;
-    requireWalletAccount(operation: string): WalletAccount;
+    requireWalletAccount(): WalletAccount;
     signTypedData(typedData: EIP712TypedData): Promise<Hex>;
     // Warning: (ae-forgotten-export) The symbol "WalletAccountStore" needs to be exported by the entry point index.d.ts
     readonly walletAccount: WalletAccountStore;
@@ -1307,6 +1307,8 @@
         delegatorAddress: Address;
         delegateAddress: Address;
     }): Promise<bigint>;
+    getWalletAccount(): Promise<WalletAccount>;
+    get hasSigner(): boolean;
     isAllowed(contracts: Address[]): Promise<boolean>;
     isAllowedAs(delegator: Address, contracts: Address[]): Promise<boolean>;
     isDelegated(params: {
@@ -1326,17 +1328,12 @@
     //
     // (undocumented)
     readonly relayer: RelayerDispatcher;
-    requireAlignedWalletAccount(operation: string): Promise<WalletAccount>;
-    // (undocumented)
-    requireChainAlignment(operation: string): Promise<number>;
-    requireSigner(operation: string): GenericSigner;
     revokeDelegation(input: {
         contractAddress: Address;
         delegateAddress: Address;
     }): Promise<TransactionResult>;
     revokePermits(contracts?: Address[]): Promise<void>;
-    // (undocumented)
-    readonly signer: GenericSigner | undefined;
+    get signer(): GenericSigner;
     // (undocumented)
     readonly storage: GenericStorage;
     terminate(): void;
sdk.api.md
--- a/sdk.api.md
+++ b/sdk.api.md
@@ -543,7 +543,7 @@
     // (undocumented)
     protected onDispose(): void;
     // (undocumented)
-    requireWalletAccount(operation: string): WalletAccount;
+    requireWalletAccount(): WalletAccount;
     // (undocumented)
     abstract signTypedData(typedData: EIP712TypedData): Promise<Hex>;
     // (undocumented)
@@ -575,13 +575,10 @@
 // @public
 export class ChainMismatchError extends ZamaError {
     constructor(input: {
-        operation: string;
         signerChainId: number;
         providerChainId: number;
     }, options?: ErrorOptions);
     // (undocumented)
-    readonly operation: string;
-    // (undocumented)
     readonly providerChainId: number;
     // (undocumented)
     readonly signerChainId: number;
@@ -7521,7 +7518,7 @@
 export interface GenericSigner {
     dispose?(): void;
     refreshWalletAccount?(): Promise<WalletAccount | undefined>;
-    requireWalletAccount(operation: string): WalletAccount;
+    requireWalletAccount(): WalletAccount;
     signTypedData(typedData: EIP712TypedData): Promise<Hex>;
     readonly walletAccount: WalletAccountStore;
     writeContract<const TAbi extends ContractAbi, TFunctionName extends WriteFunctionName<TAbi>, const TArgs extends WriteContractArgs<TAbi, TFunctionName>>(config: WriteContractConfig<TAbi, TFunctionName, TArgs>): Promise<Hex>;
@@ -14669,15 +14666,8 @@
 }
 
 // @public
-export class SignerNotConfiguredError extends SignerRequiredError {
-    constructor(operation: string, options?: ErrorOptions);
-}
-
-// @public
-export class SignerRequiredError extends ZamaError {
-    constructor(code: ZamaErrorCode, operation: string, message: string, options?: ErrorOptions);
-    // (undocumented)
-    readonly operation: string;
+export class SignerNotConfiguredError extends ZamaError {
+    constructor(options?: ErrorOptions);
 }
 
 // @public
@@ -18836,8 +18826,8 @@
 export type WalletAccountListener = (change: WalletAccountChange) => void;
 
 // @public
-export class WalletAccountNotReadyError extends SignerRequiredError {
-    constructor(operation: string, options?: ErrorOptions);
+export class WalletAccountNotReadyError extends ZamaError {
+    constructor(options?: ErrorOptions);
 }
 
 // @public
@@ -18848,8 +18838,8 @@
 }
 
 // @public
-export class WalletNotConnectedError extends SignerRequiredError {
-    constructor(operation: string, options?: ErrorOptions);
+export class WalletNotConnectedError extends ZamaError {
+    constructor(options?: ErrorOptions);
 }
 
 // @public
@@ -20083,6 +20073,8 @@
         delegatorAddress: Address;
         delegateAddress: Address;
     }): Promise<bigint>;
+    getWalletAccount(): Promise<WalletAccount>;
+    get hasSigner(): boolean;
     isAllowed(contracts: Address[]): Promise<boolean>;
     isAllowedAs(delegator: Address, contracts: Address[]): Promise<boolean>;
     isDelegated(params: {
@@ -20098,17 +20090,12 @@
     readonly registry: WrappersRegistry;
     // (undocumented)
     readonly relayer: RelayerDispatcher;
-    requireAlignedWalletAccount(operation: string): Promise<WalletAccount>;
-    // (undocumented)
-    requireChainAlignment(operation: string): Promise<number>;
-    requireSigner(operation: string): GenericSigner;
     revokeDelegation(input: {
         contractAddress: Address;
         delegateAddress: Address;
     }): Promise<TransactionResult>;
     revokePermits(contracts?: Address[]): Promise<void>;
-    // (undocumented)
-    readonly signer: GenericSigner | undefined;
+    get signer(): GenericSigner;
     // (undocumented)
     readonly storage: GenericStorage;
     terminate(): void;

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 92.45% (🎯 80%) 3136 / 3392
🔵 Statements 92.56% 3238 / 3498
🔵 Functions 92.61% (🎯 80%) 991 / 1070
🔵 Branches 86.12% (🎯 80%) 1198 / 1391
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/react-sdk/src/authorization/use-is-allowed.ts 100% 100% 100% 100%
packages/react-sdk/src/utils/wallet-account.ts 80% 100% 75% 80% 10
packages/sdk/src/zama-sdk.ts 94.15% 88.17% 95.91% 93.97% 148-149, 178, 246, 332, 389, 393, 416-419, 493-494, 510, 560-561, 577, 885
packages/sdk/src/config/types.ts 100% 100% 100% 100%
packages/sdk/src/credentials/credential-service.ts 95.94% 87.5% 100% 95.83% 118, 153, 243
packages/sdk/src/errors/base.ts 100% 100% 100% 100%
packages/sdk/src/errors/chain.ts 100% 100% 100% 100%
packages/sdk/src/errors/signer.ts 100% 100% 100% 100%
packages/sdk/src/ethers/ethers-signer.ts 80.55% 69.44% 88.88% 80.55% 78, 88, 93, 104-107, 128, 145, 168-170, 176, 183, 187
packages/sdk/src/signer/base-signer.ts 81.81% 75% 60% 81.81% 36, 56
packages/sdk/src/token/readonly-token.ts 96.71% 85% 100% 96.55% 217, 346-348, 356, 369, 633-635
packages/sdk/src/token/token.ts 97.46% 92.72% 100% 97.35% 367, 421, 847, 913, 990-995, 1050-1055
packages/sdk/src/types/signer.ts 100% 100% 100% 100%
packages/sdk/src/viem/viem-signer.ts 100% 100% 90% 100%
Generated in workflow #2197 for commit 985d67b by the Vitest Coverage Report Action

ghermet and others added 5 commits May 6, 2026 10:04
The throwing-getter refactor has shipped (PR #324). Drop the working
documents from the repo — the PR body and commit history are the
durable record.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ng-getters-signer-credentials

# Conflicts:
#	packages/sdk/src/token/token.ts
…reAccount to throwing getter

Two changes on the throwing-getter branch:

1. Revert 2418fe1 ("rename getAccount to getWalletAccount on ZamaSDK"):
   the shorter name reads better in the call sites and matches the existing
   convention. Updates ZamaSDK, Token, ReadonlyToken, the three SDK test
   files, and the api reports.

2. Convert ViemSigner.#requireAccount() into a private throwing getter
   (#account), matching the pattern this branch adopted for ZamaSDK.signer
   and ZamaSDK.#credentials. The returned walletClient field was redundant
   (always this.#walletClient); call sites now grab the field directly.
   Uses assertNonNullable + WalletNotConnectedError({ cause }) for parity
   with the other throwing getters.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores the shorter `SignerRequiredError` name (last used before
5aeeaa3 flattened the signer error hierarchy). Renames the error class,
the matching `ZamaErrorCode.SignerNotConfigured` enum key, and its
"SIGNER_NOT_CONFIGURED" string value to keep the code aligned with the
class name as the rest of the enum does.

Touches the class definition, error code enum, top-level export, every
JSDoc @throws reference in ZamaSDK / ReadonlyToken / config types, the
EthersSigner throw site, the SDK and React-SDK optional-signer tests,
the api report, and the gitbook errors / handle-errors / ZamaSDK pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant