Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Crossmint/crossmint-sdk/llms.txt

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

Go beyond simple transfers and build custom transactions for smart contracts, DeFi protocols, and complex blockchain interactions.

Overview

Crossmint’s Wallets SDK supports:
  • Smart contract interactions - Call any contract method
  • Token transfers - ERC-20, ERC-721, ERC-1155, SPL tokens
  • Message signing - For authentication and verification
  • Typed data signing - EIP-712 support
  • Raw transactions - Full control over transaction parameters

Prerequisites

npm install @crossmint/wallets-sdk @crossmint/common-sdk-base
For Solana transactions:
npm install @solana/web3.js

EVM Smart Contract Interactions

Calling Contract Methods

Interact with smart contracts using the ABI:
1

Get an EVM wallet

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

const crossmint = createCrossmint({ apiKey: "YOUR_API_KEY" });
const wallets = CrossmintWallets.from(crossmint);

const wallet = await wallets.getOrCreateWallet({
  chain: "ethereum-sepolia",
  signer: { type: "api-key", locator: "user-123" },
});

const evmWallet = EVMWallet.from(wallet);
2

Define the contract ABI

const nftAbi = [
  {
    inputs: [
      { name: "to", type: "address" },
      { name: "tokenId", type: "uint256" },
    ],
    name: "mint",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
] as const;
3

Execute the transaction

const result = await evmWallet.sendTransaction({
  to: "0xContractAddress123456789",
  abi: nftAbi,
  functionName: "mint",
  args: ["0xRecipientAddress", 1],
});

console.log("Transaction hash:", result.hash);
console.log("Explorer link:", result.explorerLink);
console.log("Transaction ID:", result.transactionId);

ERC-20 Token Transfer

Transfer ERC-20 tokens:
const erc20Abi = [
  {
    inputs: [
      { name: "to", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    name: "transfer",
    outputs: [{ name: "", type: "bool" }],
    stateMutability: "nonpayable",
    type: "function",
  },
] as const;

const result = await evmWallet.sendTransaction({
  to: "0xTokenContractAddress",
  abi: erc20Abi,
  functionName: "transfer",
  args: [
    "0xRecipient",
    "1000000000000000000", // 1 token with 18 decimals
  ],
});

ERC-721 NFT Transfer

const erc721Abi = [
  {
    inputs: [
      { name: "from", type: "address" },
      { name: "to", type: "address" },
      { name: "tokenId", type: "uint256" },
    ],
    name: "transferFrom",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
] as const;

const result = await evmWallet.sendTransaction({
  to: "0xNFTContractAddress",
  abi: erc721Abi,
  functionName: "transferFrom",
  args: [
    evmWallet.address,
    "0xRecipient",
    42, // Token ID
  ],
});

Sending Native Currency

Send ETH, MATIC, or other native tokens:
const result = await evmWallet.sendTransaction({
  to: "0xRecipientAddress",
  value: "0.1", // Amount in ETH/MATIC/etc.
});

console.log("Sent 0.1 ETH to", "0xRecipientAddress");
console.log("Transaction:", result.explorerLink);

Message Signing

Sign a Message

Sign arbitrary messages for authentication:
const signature = await evmWallet.signMessage({
  message: "Sign this message to authenticate",
});

console.log("Signature:", signature.signature);
console.log("Signature ID:", signature.signatureId);

Verify Message Signature

import { verifyMessage } from "viem";

const isValid = await verifyMessage({
  address: evmWallet.address,
  message: "Sign this message to authenticate",
  signature: signature.signature,
});

console.log("Signature valid:", isValid);

EIP-712 Typed Data Signing

Sign structured data following EIP-712:
const domain = {
  name: "MyDApp",
  version: "1",
  chainId: 11155111, // Sepolia
  verifyingContract: "0xContractAddress",
};

const types = {
  Person: [
    { name: "name", type: "string" },
    { name: "wallet", type: "address" },
  ],
};

const message = {
  name: "Alice",
  wallet: "0xAliceAddress",
};

const signature = await evmWallet.signTypedData({
  domain,
  types,
  primaryType: "Person",
  message,
  chain: "ethereum-sepolia",
});

console.log("Typed data signature:", signature.signature);
EIP-712 signatures are commonly used by DeFi protocols for gasless approvals and permit functions.

Solana Transactions

Basic SOL Transfer

import { SolanaWallet } from "@crossmint/wallets-sdk";
import { Transaction, SystemProgram, PublicKey } from "@solana/web3.js";

const wallet = await wallets.getOrCreateWallet({
  chain: "solana-devnet",
  signer: { type: "api-key", locator: "user-123" },
});

const solanaWallet = SolanaWallet.from(wallet);

// Create transfer transaction
const transaction = new Transaction().add(
  SystemProgram.transfer({
    fromPubkey: new PublicKey(solanaWallet.address),
    toPubkey: new PublicKey("recipient-address"),
    lamports: 1000000, // 0.001 SOL
  })
);

// Serialize and send
const serialized = transaction.serialize().toString("base64");
const result = await solanaWallet.sendTransaction({
  serializedTransaction: serialized,
});

console.log("Transaction hash:", result.hash);

SPL Token Transfer

import {
  createTransferInstruction,
  getAssociatedTokenAddress,
} from "@solana/spl-token";

const tokenMint = new PublicKey("TokenMintAddress");
const recipientAddress = new PublicKey("RecipientAddress");

// Get token accounts
const sourceAccount = await getAssociatedTokenAddress(
  tokenMint,
  new PublicKey(solanaWallet.address)
);

const destinationAccount = await getAssociatedTokenAddress(
  tokenMint,
  recipientAddress
);

// Create transfer instruction
const transaction = new Transaction().add(
  createTransferInstruction(
    sourceAccount,
    destinationAccount,
    new PublicKey(solanaWallet.address),
    1000000, // Amount with token decimals
  )
);

const serialized = transaction.serialize().toString("base64");
const result = await solanaWallet.sendTransaction({
  serializedTransaction: serialized,
});

Solana Program Interaction

Interact with Solana programs (smart contracts):
import { TransactionInstruction } from "@solana/web3.js";

const programId = new PublicKey("ProgramAddress");

const instruction = new TransactionInstruction({
  keys: [
    { pubkey: new PublicKey(solanaWallet.address), isSigner: true, isWritable: true },
    { pubkey: new PublicKey("AccountAddress"), isSigner: false, isWritable: true },
  ],
  programId,
  data: Buffer.from([/* instruction data */]),
});

const transaction = new Transaction().add(instruction);
const serialized = transaction.serialize().toString("base64");

const result = await solanaWallet.sendTransaction({
  serializedTransaction: serialized,
});

Advanced Transaction Options

Prepare-Only Mode

Create transactions without executing them:
const prepared = await evmWallet.sendTransaction({
  to: "0xRecipient",
  value: "0.1",
  options: {
    experimental_prepareOnly: true,
  },
});

console.log("Transaction ID:", prepared.transactionId);
console.log("Hash:", prepared.hash); // undefined - not executed yet

// Execute later
const result = await evmWallet.approveTransactionAndWait(prepared.transactionId);
console.log("Executed hash:", result.hash);
Prepare-only mode is experimental and may change in future versions.

Custom Gas Settings

Control gas prices for EVM transactions:
const result = await evmWallet.sendTransaction({
  to: "0xRecipient",
  value: "0.1",
  gasLimit: "21000",
  maxFeePerGas: "50000000000", // 50 gwei
  maxPriorityFeePerGas: "2000000000", // 2 gwei
});

Transaction Status Tracking

Monitor transaction status:
// Send transaction
const result = await evmWallet.sendTransaction({
  to: "0xRecipient",
  value: "0.1",
});

// Check status later
const transaction = await wallet.getTransaction(result.transactionId);

console.log("Status:", transaction.status);
console.log("Hash:", transaction.onChain?.txId);
console.log("Explorer:", transaction.onChain?.explorerLink);
Possible status values:
  • pending - Transaction created but not submitted
  • submitted - Transaction sent to blockchain
  • success - Transaction confirmed
  • failed - Transaction failed

Transaction History

Retrieve past transactions:
const transactions = await wallet.getTransactions();

for (const tx of transactions) {
  console.log("ID:", tx.id);
  console.log("Status:", tx.status);
  console.log("Hash:", tx.onChain?.txId);
  console.log("Created:", new Date(tx.createdAt));
  console.log("---");
}

Batch Transactions

Execute multiple transactions efficiently:
async function batchMint(recipients: string[]) {
  const promises = recipients.map((recipient) =>
    evmWallet.sendTransaction({
      to: "0xNFTContract",
      abi: nftAbi,
      functionName: "mint",
      args: [recipient, 1],
    })
  );

  const results = await Promise.all(promises);
  return results.map((r) => r.hash);
}

const txHashes = await batchMint([
  "0xRecipient1",
  "0xRecipient2",
  "0xRecipient3",
]);

console.log("Minted to", txHashes.length, "recipients");

Error Handling

Handle transaction errors gracefully:
import { 
  TransactionNotCreatedError,
  TransactionFailedError,
  InsufficientFundsError 
} from "@crossmint/wallets-sdk";

try {
  const result = await evmWallet.sendTransaction({
    to: "0xRecipient",
    value: "100", // Too much!
  });
} catch (error) {
  if (error instanceof InsufficientFundsError) {
    console.error("Not enough funds");
    // Prompt user to add funds
  } else if (error instanceof TransactionFailedError) {
    console.error("Transaction failed on-chain");
    // Show error to user
  } else if (error instanceof TransactionNotCreatedError) {
    console.error("Failed to create transaction");
    // Retry or show error
  } else {
    console.error("Unknown error:", error);
  }
}

Best Practices

1

Validate inputs before sending

import { isValidEvmAddress } from "@crossmint/common-sdk-base";

function validateRecipient(address: string) {
  if (!isValidEvmAddress(address)) {
    throw new Error("Invalid recipient address");
  }
}

validateRecipient(recipientAddress);
await evmWallet.sendTransaction({ to: recipientAddress, value: "0.1" });
2

Check balances before transactions

const balance = await wallet.getBalance();
const requiredAmount = parseFloat("0.1");

if (parseFloat(balance.native.raw) < requiredAmount) {
  throw new Error("Insufficient balance");
}

await evmWallet.sendTransaction({
  to: "0xRecipient",
  value: requiredAmount.toString(),
});
3

Use explorer links for transparency

const result = await evmWallet.sendTransaction({...});

console.log("View transaction:", result.explorerLink);
// Show link to user so they can track progress
4

Implement retry logic

async function sendWithRetry(tx: any, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await evmWallet.sendTransaction(tx);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Testing Transactions

Always test on testnets first:
// Use testnet chains
const testWallet = await wallets.getOrCreateWallet({
  chain: "ethereum-sepolia", // Testnet
  signer: { type: "api-key", locator: "test-user" },
});

const testEvmWallet = EVMWallet.from(testWallet);

// Test your transaction
const result = await testEvmWallet.sendTransaction({
  to: "0xTestRecipient",
  value: "0.001",
});

console.log("Test transaction:", result.explorerLink);
Get free testnet tokens from faucets:

Next Steps

Multi-Chain Support

Execute transactions across multiple chains

Embedded Wallets

Manage wallets for your users

Build docs developers (and LLMs) love