Skip to content

fix(wallets): addSigner and addRecoveryMethod create the passkey when the config has none - #2275

Open
albertoelias-crossmint wants to merge 4 commits into
mainfrom
albertoe/wallets-passkey-add-signer
Open

albertoelias-crossmint wants to merge 4 commits into
mainfrom
albertoe/wallets-passkey-add-signer

Conversation

@albertoelias-crossmint

@albertoelias-crossmint albertoelias-crossmint commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Problem

A developer who follows the docs and calls wallet.addSigner({ type: "passkey" }), or wallet.addRecoveryMethod({ type: "passkey" }), never gets a passkey prompt. The SDK looks up a signer literally named passkey and posts the bare config. The API answers signer: Invalid input: expected string, received object, which says nothing about the real cause: the request carries no credential id and no public key.

The only way around it today is to know that createPasskeySigner has to run first, then pass its result back in by hand. wallet.create already creates the passkey for you, so the two entry points behave differently for the same config.

What this PR does

  • When a passkey config has no credential id (no id, no passkey:{id} locator), addSigner and addRecoveryMethod create the credential first, then register its id and public key.
  • The credential comes from the config's onCreatePasskey, else the wallet's passkeyProvider, else the browser WebAuthn API. The same code now backs wallet.create (moved, not changed).
  • The request body carries only the fields the API takes (type, id, name, publicKey), no callbacks or locator.
  • A passkey that is created in the call skips the "already registered" lookup, which cannot match yet.
flowchart LR
    subgraph Before
    A1["addSigner({ type: passkey })"] --> C1["POST bare config"] --> D1["signer: Invalid input"]
    end
    subgraph After
    A2["addSigner({ type: passkey })"] --> B2["create passkey (callback, provider or browser)"] --> C2["POST id + publicKey"] --> D2["pending approval"]
    end
Loading

Result

Before After
addSigner({ type: "passkey" }) signer: Invalid input: expected string, received object prompts, registers the new passkey
addRecoveryMethod({ type: "passkey" }) same error prompts, registers it as a recovery method
Calls a developer writes 2 (createPasskeySigner, then addSigner with its output) 1
User cancels the prompt n/a the call rejects with the prompt's error, nothing is registered

Unchanged: a config that already has id (or a passkey:{id} locator) registers as before; wallet.create behaviour; public types (PasskeySignerConfig gains an optional publicKey, SignerDescriptor an optional prepareRegistration).

What to check when reviewing

Test plan

  • pnpm --filter @crossmint/wallets-sdk test:vitest: 772 tests pass. src/api/client.test.ts needs PREVIEW_CK_KEY and was not run locally; it does not touch this code.
  • New tests: descriptor (prepareRegistration keeps configs with an id, creates via callback, provider and browser; addSignerPayload fields) and wallet (addSigner and addRecoveryMethod create and register, and register nothing when the prompt is cancelled). All 11 new tests fail when the source change is reverted and the tests are kept.
  • pnpm biome check on the changed files and pnpm --filter @crossmint/wallets-sdk build pass.
  • Not yet run: a live passkey add on base-sepolia in a browser (needs Face ID). To be attached before merge.

Package updates

  • @crossmint/wallets-sdk patch changeset (.changeset/passkey-add-signer-creates-credential.md)

Checklist

  • Tests fail on the old code
  • Changeset added
  • No public type removed or narrowed
  • Live run recorded

Rollback

Revert the commit; no state or schema is written. Passkeys already registered through this path stay valid.

Closes WAL-13395

🤖 Generated with Claude Code


Devin Review

…n the config has none

`wallet.addSigner({ type: "passkey" })`, as the add-signers guide shows, never created a
credential: the SDK looked up signer locator "passkey" (400, "Invalid address: passkey") and
then sent the bare config, which the API rejects (`signer: Invalid input`, wallet:signer-invalid).

A passkey config without a credential id is now created first, with the config's
onCreatePasskey, else the wallet's passkeyProvider, else WebAuthn (the same order wallet
creation uses, now shared through createPasskeyCredential). The pre-registration signer
lookup is skipped for a passkey created in the call. addRecoveryMethod does the same and keeps
the created credential in the wallet's recovery methods. Configs with an id are unchanged,
and the passkey request body now carries only type, id, name and publicKey.
@changeset-bot

changeset-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7f0dce0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@crossmint/wallets-sdk Patch
@crossmint/wallets-quickstart-devkit Patch
@crossmint/wallets-playground-react Patch
@crossmint/client-sdk-react-base Patch
@crossmint/client-sdk-react-native-ui Patch
@crossmint/client-sdk-react-ui Patch
@crossmint/integration-tests Patch
@crossmint/wallets-playground-expo Patch
@crossmint/auth-ssr-nextjs-demo Patch
@crossmint/client-sdk-nextjs-starter Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Devin Review

@greptile-apps

greptile-apps Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable defects were found.

Reviews (1) · Last reviewed commit: "docs(wallets): shorten passkey registrat..." · Reviewed by Greptile

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants