Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.crossmint.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide covers every breaking change in the Wallets SDK V1 release. It is organized by audience — client-side (React / React Native, Swift, Kotlin), 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" }],
  }}
>
  {/* ... */}
</CrossmintWalletProvider>
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().

Client-side migration (Swift)

1. Update the SDK version

In Xcode, go to your package dependency for crossmint-swift-sdk and update the version requirement:
Up to Next Major Version: 0.10.2

2. Replace getOrCreateWallet with getWallet + createWallet

getOrCreateWallet has been removed. Use getWallet to retrieve an existing wallet. If it returns nil, call createWallet. The signer parameter is now named recovery.
let wallet = try await sdk.crossmintWallets.getOrCreateWallet(
    chain: EVMChain.baseSepolia,
    signer: EVMSigners.email(email)
)

3. Update OTP authentication

The single otpAuthentication() method has been replaced with two explicit steps: sendEmailOtp and confirmEmailOtp.
// Send OTP (code: nil triggers the send)
try await crossmintAuthManager.otpAuthentication(email: email, code: nil)

// Verify OTP
try await crossmintAuthManager.otpAuthentication(email: email, code: otpCode)

4. Understand device signers

Device signers are a new signer type backed by the device’s hardware security module (Secure Enclave). 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.
  • Opt in explicitly via WalletOptions(deviceSigner: true) if you need to control it.
  • Call wallet.needsRecovery() to check if the device signer needs to be re-established (e.g. after the user reinstalls the app or 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.

5. Add and manage signers

Signer management is available on Wallet.
// Add a signer
try await wallet.addSigner(SignerConfig.passkey)

// Switch to a specific signer for subsequent operations
try await wallet.useSigner(SignerConfig.passkey)

// Check and trigger recovery if needed
let needsRecovery = await wallet.needsRecovery()
if needsRecovery {
    try await wallet.recover()
}

6. Update ApiKeySigner initialization

ApiKeySigner no longer provides a default ApiKeySignerData — you must pass one explicitly.
let signer = ApiKeySigner()

7. Replace the deprecated Signers enum

The Signers enum is deprecated. Use EVMSigners or SolanaSigners for type-safe chain compatibility.
let signer = Signers.email(email)

Client-side migration (Kotlin)

1. Update the SDK version

Update the version in your libs.versions.toml or build.gradle.kts:
[versions]
crossmint-sdk = "0.0.16" # or your current pre-1.0.0 version

2. Replace getOrCreateWallet with getWallet + createWallet

getOrCreateWallet has been removed. Use getWallet to retrieve an existing wallet. If it returns a failure, call createWallet. The signer parameter is now named recovery.
when (val result = crossmintWallets.getOrCreateWallet(
    chain = EVMChain.BaseSepolia,
    signer = SignerType.Email(email)
)) {
    is Result.Success -> { val wallet = result.value }
    is Result.Failure -> { /* handle error */ }
}

3. Understand device signers

Device signers are a new signer type backed by the device’s hardware security module (Android Keystore). 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 reinstalls the app or 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.

4. Add and manage signers

Signer management is a first-class API on Wallet.
// Add a signer
wallet.addSigner(DelegatedSigner.Passkey)

// Switch to a specific signer for subsequent operations
wallet.useSigner(DelegatedSigner.Passkey)

// Remove a signer
wallet.removeSigner(DelegatedSigner.Passkey)

// Check and trigger recovery if needed
if (wallet.needsRecovery()) {
    wallet.recover()
}

5. Use SignerSelection in send and approve

send() and approve() now accept an optional signer: SignerSelection parameter. It defaults to SignerSelection.Admin (the recovery signer), so existing call sites without the parameter continue to work. Pass SignerSelection.Delegated(...) to sign with an operational signer.
when (val sendResult = wallet.send(
    recipient,
    tokenLocator,
    amount,
    signer = SignerSelection.Delegated(delegatedSignerData)
)) {
    is Result.Success -> { /* proceed to approve */ }
    is Result.Failure -> { /* handle error */ }
}

6. Check balances and list transfers

balances(), fund(), and listTransfers() are available directly on Wallet.
// Check balances
when (val result = wallet.balances()) {
    is Result.Success -> {
        println("ETH: ${result.value.nativeToken.amount}")
        println("USDC: ${result.value.usdc.amount}")
    }
    is Result.Failure -> { /* handle */ }
}

// List transfers
when (val result = wallet.listTransfers(emptyList())) {
    is Result.Success -> { val transfers = result.value.transfers }
    is Result.Failure -> { /* handle */ }
}

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: YOUR_SERVER_API_KEY" \
  -d '{
    "type": "evm-smart-wallet",
    "config": {
      "adminSigner": {
        "type": "evm-keypair",
        "address": "ADMIN_WALLET_ADDRESS"
      },
      "delegatedSigners": [
        { "type": "external-wallet", "address": "EXTERNAL_WALLET_ADDRESS" }
      ]
    }
  }'
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_LOCATOR>/transactions/<TRANSACTION_ID>/approvals \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_SERVER_API_KEY" \
  -d '{
    "approvals": [{
      "signer": "external-wallet:EXTERNAL_WALLET_ADDRESS",
      "signature": "TX_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
  • (Android) Updated SDK to 1.0.0
  • (Android/iOS) Replaced getOrCreateWallet with createWallet / getWallet, renamed signerrecovery
  • (iOS) Replaced otpAuthentication with sendEmailOtp + confirmEmailOtp
  • (iOS) Updated ApiKeySigner initialization — no longer has a default parameter
  • (iOS) Replaced deprecated Signers enum with EVMSigners / SolanaSigners
  • 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