Skip to main content
This guide covers every breaking change in the Wallets SDK V1 release. It is organized by audience — client-side (React / React Native), server-side (Node.js), and REST API — so you can jump to the section that applies to you.
V1 is a major release with breaking changes. Read through the relevant sections before upgrading your packages.

Terminology changes

V1 renames several concepts. Use this table as a find-and-replace checklist across your codebase.
Old termNew termNotes
adminSignerRecovery signerRecovery signers are high-friction (OTP-based). They can also sign transactions as a fallback when no operational signer is available.
delegatedSignerSigner (operational)Day-to-day signers: device, passkey, server, external-wallet.
getOrCreateWallet()createWallet() + getWallet()Separate calls. getWallet() throws WalletNotAvailableError if no wallet exists.
addDelegatedSigner()addSigner()Signature also changed — accepts config objects, not just locator strings.
experimental_activity()transfers(params)Name and signature changed. tokens and status params are optional.
experimental_ prefixRemovedexperimental_prepareOnlyprepareOnly, experimental_signersigner, experimental_approvalapproval, experimental_nfts()nfts(), experimental_transactions()transactions(), experimental_transaction(id)transaction(id).
customAuth / experimental_setCustomAuthsetJwt()setJwt(jwt) replaces the old experimental_setCustomAuth({ jwt, email, externalWalletSigner }).
useWalletEmailSigneruseWalletOtpSignerNow works for both email and phone OTP signers.
createOnLogin.signercreateOnLogin.recoveryThe signer field in createOnLogin is now recovery.
createOnLogin.delegatedSignerscreateOnLogin.signersArray of operational signers to register at wallet creation.
Activity typeTransfers typeFind-and-replace import.

Client-side migration (React / React Native)

1. Update packages

Update all @crossmint packages to V1 versions:
npm install @crossmint/client-sdk-react-ui@latest @crossmint/wallets-sdk@latest

2. Update createOnLogin in your provider

The signer field is now recovery, and delegatedSigners is now signers.
<CrossmintWalletProvider
  createOnLogin={{
    chain: "base-sepolia",
    signer: { type: "email" },
    delegatedSigners: [{ type: "passkey" }],
  }}
>
Device signers are now the default operational signer. If you do not specify signers, a device signer is created automatically on EVM chains. Solana does not support device signers yet — the recovery signer is used as a fallback for signing.

3. Replace getOrCreateWallet with getWallet + createWallet

getOrCreateWallet has been removed. Use getWallet to retrieve an existing wallet. If it throws WalletNotAvailableError, call createWallet.
import { useWallet } from "@crossmint/client-sdk-react-ui";

const { getOrCreateWallet } = useWallet();

const wallet = await getOrCreateWallet({
    signer: { type: "email", email: "user@example.com" },
});

4. Replace customAuth with setJwt

If you use a third-party auth provider (BYOA), replace experimental_setCustomAuth with setJwt.
const { experimental_setCustomAuth } = useCrossmint();

useEffect(() => {
    experimental_setCustomAuth({ jwt, email, externalWalletSigner });
}, [jwt]);
See the Bring Your Own Auth guide for complete setup instructions.

5. Remove experimental_ prefixes

All experimental_ prefixes have been removed from wallet methods and options.
// Methods
const activity = await wallet.experimental_activity();
const nfts = await wallet.experimental_nfts();
const txs = await wallet.experimental_transactions();
const tx = await wallet.experimental_transaction(txId);

// Options
await wallet.send(to, token, amount, {
    experimental_prepareOnly: true,
    experimental_signer: `external-wallet:${address}`,
});

await wallet.approve({
    transactionId,
    options: {
        experimental_approval: { signature, signer: locator },
    },
});

6. Replace useWalletEmailSigner with useWalletOtpSigner

The useWalletEmailSigner hook has been replaced by useWalletOtpSigner, which works for both email and phone OTP flows.
import { useWalletEmailSigner } from "@crossmint/client-sdk-react-native-ui";

const { needsAuth, sendOtp, verifyOtp, reject } = useWalletEmailSigner();

7. Replace addDelegatedSigner with addSigner

The method name and signature have changed. addSigner now accepts signer config objects in addition to locator strings.
await wallet.addDelegatedSigner({
    signer: "passkey:abc123",
});

8. Remove Farcaster and Dynamic login references

Farcaster sign-in and Dynamic wallet login have been removed entirely. If you used either, migrate to an alternative auth method.
  • Remove "farcaster" from loginMethods in CrossmintAuthProvider.
  • Remove "web3" from loginMethods.
  • Remove any FarcasterSignIn, Web3AuthFlow, or DynamicWalletProvider imports.

9. Understand device signers

Device signers are a new signer type backed by the device’s hardware security module (Secure Enclave on mobile, IndexedDB on web). They are the default operational signer for EVM wallets — no OTP is required for day-to-day signing. Key points:
  • No code change is needed if you want the default device signer behavior.
  • Call wallet.needsRecovery() to check if the device signer needs to be re-established (e.g. after the user switches devices).
  • Call wallet.recover() to trigger recovery using the recovery signer.
  • Solana does not support device signers yet. The recovery signer is used as a fallback.

10. Use useSigner() for non-device signers

If you need to sign with a non-device signer (e.g. passkey, external wallet, server), call wallet.useSigner() first.
// Select a passkey signer for subsequent operations
wallet.useSigner({ type: "passkey" });

// Now send will use the passkey signer
await wallet.send(to, token, amount);
useSigner() only accepts signer config objects — locator strings (e.g. "external-wallet:0x...") are no longer accepted.

11. Update external wallet signer to use onSign

V1 replaces chain-specific signing props (provider, viemAccount, onSignTransaction, onSignStellarTransaction) with a single unified onSign callback. When using an external-wallet signer (via useSigner), both address and onSign are required.
const wallet = await wallets.getOrCreateWallet("<user-email>", {
    chain: "base-sepolia",
    signer: { type: "email", email: "user@example.com" },
    delegatedSigners: [{
        type: "external-wallet",
        address: "0xABC",
        provider: window.ethereum,
        // or: viemAccount, onSignTransaction (Solana), onSignStellarTransaction (Stellar)
    }],
});
Registration vs. usage: When registering an external-wallet signer (in createWallet({ signers: [...] }) or addSigner()), only address is needed — no onSign. The onSign callback is only required when actively signing via useSigner().

Server-side migration (Node.js)

owner field behavior:
  • Client-side: owner must NOT be provided — it is automatically derived from the JWT. The client-side types (ClientSideWalletArgsFor, ClientSideWalletCreateArgs) omit it entirely.
  • Server-side with email/phone signers: owner is required — must be manually set.
  • Server-side with any other signer (device, passkey, API key, external wallet, server signer): owner is optional.

1. Update packages

npm install @crossmint/wallets-sdk@latest

2. Replace getOrCreateWallet with createWallet / getWallet

import { CrossmintWallets, createCrossmint } from "@crossmint/wallets-sdk";

const crossmint = createCrossmint({ apiKey: "<server-api-key>" });
const wallets = CrossmintWallets.from(crossmint);

const wallet = await wallets.getOrCreateWallet("<user-email>", {
    chain: "base-sepolia",
    signer: { type: "email", email: "user@example.com" },
});

3. Update signer configuration

The old signer field (admin signer) is now recovery, and delegatedSigners is now signers.
const wallet = await wallets.getOrCreateWallet("<user-email>", {
    chain: "base-sepolia",
    signer: { type: "email", email: "user@example.com" },
    delegatedSigners: [
        { type: "external-wallet", address: "0x..." },
    ],
});

4. Migrate from API key signer to server signer

If you were using the API key as a signer for server-side operations, migrate to the new server signer. Server signers use deterministic key derivation from a secret you control.
// API key signer was implicit — the server API key was used to sign
const wallet = await wallets.getOrCreateWallet("<user-email>", {
    chain: "base-sepolia",
    signer: { type: "email", email: "user@example.com" },
});

// Transactions were signed with the API key automatically
await wallet.send(to, token, amount);
Store your server signer secret securely. The same secret always derives the same key, so losing it means losing signing access. Crossmint never has access to your secret.

5. Remove experimental_ prefixes

Same as client-side — remove all experimental_ prefixes from method names and option fields.

REST API migration

The REST API changes in V1 are additive — there are no breaking changes to existing endpoints. The main addition is server signer support.

Switch to the 2025-06-09 API version

Server signers require the 2025-06-09 API version. Update your base URL:
https://staging.crossmint.com/api/2025-06-09/wallets/...

Create a wallet with a server signer

Previously, server-managed signing used the external-wallet type. V1 introduces a dedicated server signer type.
curl -X POST https://staging.crossmint.com/api/2025-06-09/wallets \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: <x-api-key>" \
  -d '{
    "type": "evm-smart-wallet",
    "config": {
      "adminSigner": {
        "type": "evm-keypair",
        "address": "<adminAddress>"
      },
      "delegatedSigners": [
        { "type": "external-wallet", "address": "<externalAddress>" }
      ]
    }
  }'
The REST API accepts address (not secret) for server signers. Derive the address from your secret in your backend before passing it to the API.

Submit approvals with a server signer

Use the server:<address> locator format when submitting approvals.
curl -X POST https://staging.crossmint.com/api/2025-06-09/wallets/<wallet>/transactions/<txId>/approvals \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: <x-api-key>" \
  -d '{
    "approvals": [{
      "signer": "external-wallet:<address>",
      "signature": "<signature>"
    }]
  }'

REST ↔ SDK signer type mapping

Use this table to map between REST API signer types and SDK signer config objects.
REST API signer typeREST locator formatSDK signer config
evm-keypair / solana-keypairexternal-wallet:<address>{ type: "external-wallet", address, provider }
serverserver:<address>{ type: "server", secret: "..." }
evm-passkeypasskey:<credentialId>{ type: "passkey" }
N/A (client-only)device:<publicKey>{ type: "device" }
evm-email-otpemail:<email>{ type: "email", email: "..." }
evm-phone-otpphone:<number>{ type: "phone", phone: "..." }

Checklist

Use this checklist to verify your migration is complete:
  • Updated all @crossmint packages to V1 versions
  • Replaced getOrCreateWallet with createWallet / getWallet
  • Updated createOnLogin: signerrecovery, delegatedSignerssigners
  • Replaced experimental_setCustomAuth with setJwt (if using BYOA)
  • Removed all experimental_ prefixes from methods and options
  • Replaced useWalletEmailSigner with useWalletOtpSigner
  • Replaced addDelegatedSigner with addSigner
  • Removed Farcaster / Dynamic login references (if applicable)
  • Updated REST API calls to use server signer type (if applicable)
  • Searched codebase for old terms: adminSigner, getOrCreateWallet, delegatedSigner, experimental_

Next steps

Error handling

Handle WalletNotAvailableError and other common errors

Add a signer

Register operational signers on your wallet