Skip to content

feat(wallets): createServerSigner returns a sign(message) function - #2276

Open
albertoelias-crossmint wants to merge 3 commits into
mainfrom
albertoe/server-signer-sign
Open

albertoelias-crossmint wants to merge 3 commits into
mainfrom
albertoe/server-signer-sign

Conversation

@albertoelias-crossmint

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

Copy link
Copy Markdown
Collaborator

Description

Problem

A backend that holds a server signer secret can derive its address with createServerSigner(), but cannot sign with it. The function returns { type, address, keyBytes } and nothing else. When the API asks that signer for an approval (a transaction or signature approval, or a quorum member's approval message), the developer has to rebuild the signing per chain from keyBytes: viem signMessage with { raw } on EVM, ed25519 over the base58 message on Solana, ed25519 over the base64-decoded message with base64 out on Stellar. Calling signer.sign(message) fails with TypeError: signer.sign is not a function, and a wrong rebuild is only caught when the API rejects the approval.

What this PR does

  • createServerSigner() also returns sign(message). It delegates to the same EVMServerSigner, SolanaServerSigner and StellarServerSigner the SDK uses when a server signer approves on its own, so the output is what the API already accepts.
  • keyBytes is left intact: the adapter wipes the bytes it is given, so it gets a copy.
  • Message and signature encodings follow the chain: EVM 0x-hex in and out, Solana base58, Stellar base64.
flowchart LR
    subgraph Before
    A1["createServerSigner()"] --> B1["address + keyBytes"] --> C1["caller re-implements per-chain signing"]
    end
    subgraph After
    A2["createServerSigner()"] --> B2["address + keyBytes + sign"] --> C2["sign(pending.message)"] --> D2["POST under server:address"]
    end
Loading

Result

Before After
signer.sign(message) TypeError: signer.sign is not a function signature in the chain's encoding
Lines a developer writes per chain ~10 (curves, encodings) 1
Bad message whatever the rebuild does EVM and Stellar throw the SDK's own "expects 0x-prefixed hex" / "expected a base64-encoded string"; Solana throws on non-base58

Unchanged: address, keyBytes, CreateServerSignerParams. ServerSigner gains a required sign field; code that only reads the existing fields is unaffected, code that constructs a ServerSigner literal by hand would need to add it.

What to check when reviewing

Test plan

  • pnpm --filter @crossmint/wallets-sdk test:vitest: 770 tests pass. src/api/client.test.ts needs PREVIEW_CK_KEY and was not run locally; unrelated.
  • New tests in createServerSigner.test.ts (8): EVM signature recovers to the address; Solana and Stellar signatures verify against the address's key; EVM, Solana and Stellar reject malformed messages; repeated signing works; keyBytes unchanged. All 8 fail against the old source.
  • pnpm biome check and the package build pass.
  • Not yet run: signing a real pending approval on staging.

Package updates

  • @crossmint/wallets-sdk minor changeset (.changeset/server-signer-sign.md)

Checklist

  • Tests fail on the old code
  • Changeset added
  • No existing field changed
  • Staging run recorded

Rollback

Revert the commit; sign is additive and nothing else reads it.

Closes WAL-13399

🤖 Generated with Claude Code


Devin Review

A server signer made with createServerSigner returned only its address and
key bytes, so a backend that had to sign an approval itself (for example a
quorum member's message, which the SDK does not sign yet) rebuilt each chain's
signing from keyBytes. sign(message) reuses the SDK's own EVM, Solana and
Stellar server signers, so the signature matches what the SDK posts when the
same server signer approves on its own. keyBytes stays intact: the adapter
gets a copy, since it wipes its input.
@changeset-bot

changeset-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7206f94

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 Minor
@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[bot]

This comment was marked as resolved.

@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 issues were found.

Reviews (1) · Last reviewed commit: "test(wallets): cover a non-base58 Solana..." · 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