From 9edf8fd74db372bdf76dc872f0dbe4684b18ff6a Mon Sep 17 00:00:00 2001 From: Guille Date: Fri, 10 Apr 2026 15:48:06 +0000 Subject: [PATCH] fix: use recovery address directly in isRecoverySigner for server signers The API-sourced recovery config has shape {type: 'server', address: '...'} without a secret field. Calling deriveServerSignerDetails on it caused TypeError because stripAndValidateSecret received undefined. Now checks for address field first (API path) and falls back to secret derivation (createWallet path). --- .../fix-server-signer-recovery-check.md | 9 +++++++ packages/wallets/src/wallets/wallet.ts | 26 ++++++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 .changeset/fix-server-signer-recovery-check.md diff --git a/.changeset/fix-server-signer-recovery-check.md b/.changeset/fix-server-signer-recovery-check.md new file mode 100644 index 000000000..44cff138d --- /dev/null +++ b/.changeset/fix-server-signer-recovery-check.md @@ -0,0 +1,9 @@ +--- +"@crossmint/wallets-sdk": patch +--- + +Fix TypeError in `isRecoverySigner` when recovery config from API lacks `secret` field + +When calling `wallet.useSigner({ type: "server", secret: "xmsk1_..." })` on a wallet fetched via `getWallet`, the `isRecoverySigner()` method would call `deriveServerSignerDetails()` on the API-sourced recovery config which has shape `{type: "server", address: "..."}` without a `secret` field. This caused `stripAndValidateSecret(undefined)` to throw `TypeError: Cannot read properties of undefined (reading 'startsWith')`. + +The fix uses the `address` field from the API recovery config directly instead of attempting to re-derive it from a non-existent secret. diff --git a/packages/wallets/src/wallets/wallet.ts b/packages/wallets/src/wallets/wallet.ts index 7bc8eae55..c32d58b11 100644 --- a/packages/wallets/src/wallets/wallet.ts +++ b/packages/wallets/src/wallets/wallet.ts @@ -1351,13 +1351,25 @@ export class Wallet { this.#apiClient.projectId, this.#apiClient.environment ).derivedAddress; - const recoveryDerived = deriveServerSignerDetails( - recovery, - this.chain, - this.#apiClient.projectId, - this.#apiClient.environment - ).derivedAddress; - return inputDerived === recoveryDerived; + + // When the recovery config comes from the API (via getWallet), it has + // {type: "server", address: "..."} without the secret — use it directly. + // When it comes from the user (via createWallet), it has a secret so we can derive. + if ("address" in recovery && typeof recovery.address === "string") { + return inputDerived === recovery.address; + } + if ("secret" in recovery && typeof recovery.secret === "string") { + return ( + inputDerived === + deriveServerSignerDetails( + recovery, + this.chain, + this.#apiClient.projectId, + this.#apiClient.environment + ).derivedAddress + ); + } + return false; } // For other types, compare locators