Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Crossmint/crossmint-agentic-finance/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The x402Adapter creates a viem LocalAccount-compatible interface from Crossmint wallets, enabling seamless integration with the x402 payment protocol. The adapter handles signature processing for ERC-6492 (pre-deployed), EIP-1271 (deployed), and standard ECDSA signatures.
Installation
The adapter is typically implemented as a local module in your project:
Core Functions
createX402Signer
Converts a Crossmint wallet to an x402-compatible signer.
Basic Usage
import { createX402Signer } from './x402Adapter';
import { EVMWallet } from '@crossmint/wallets-sdk';
import type { Wallet } from '@crossmint/wallets-sdk';
const wallet: Wallet<any> = /* your Crossmint wallet */;
const x402Signer = createX402Signer(wallet);
console.log("Signer address:", x402Signer.account.address);
Implementation (events-concierge)
From events-concierge/src/x402Adapter.ts:14-73:
import type { Wallet } from "@crossmint/wallets-sdk";
import { EVMWallet } from "@crossmint/wallets-sdk";
import type { Hex } from "viem";
export function createX402Signer(wallet: Wallet<any>) {
const evm = EVMWallet.from(wallet);
console.log("Creating x402 account:", {
walletAddress: wallet.address,
evmAddress: evm.address,
addressType: typeof evm.address
});
// Create viem Account-compatible object
// x402 expects an Account with address, type, and signTypedData method
const account: any = {
address: evm.address as `0x${string}`,
type: "local",
source: "custom",
// signTypedData method required by x402 for payment signatures
signTypedData: async (params: any) => {
console.log("signTypedData called for address:", evm.address);
const { domain, message, primaryType, types } = params;
console.log("Full EIP-712 payload being signed:");
console.log(" Domain:", JSON.stringify(domain, null, 2));
console.log(" Message:", JSON.stringify(message, null, 2));
console.log(" PrimaryType:", primaryType);
console.log(" Types:", JSON.stringify(types, null, 2));
console.log("Key fields:");
console.log(" - Payer (from):", evm.address);
console.log(" - Recipient (to):", message?.to || message?.recipient || message?.payTo || 'MISSING');
console.log(" - Verifying contract:", domain?.verifyingContract);
// Sign with Crossmint wallet
console.log("Calling Crossmint signTypedData...");
const sig = await evm.signTypedData({
domain,
message,
primaryType,
types,
chain: evm.chain as any
} as any);
console.log("Signature received from Crossmint");
console.log("Signature details:", {
signatureLength: sig.signature.length,
signatureStart: sig.signature.substring(0, 66),
isERC6492: sig.signature.endsWith("6492649264926492649264926492649264926492649264926492649264926492")
});
const processed = processSignature(sig.signature as string);
console.log("Processed signature ready for x402 facilitator");
return processed;
}
};
console.log("x402 account created with address:", account.address);
return account;
}
Implementation (cloudflare-agents)
From cloudflare-agents/src/x402Adapter.ts:14-68:
import type { Wallet } from "@crossmint/wallets-sdk";
import { EVMWallet } from "@crossmint/wallets-sdk";
import type { LocalAccount, Hex } from "viem";
export function createX402Signer(wallet: Wallet<any>) {
const evm = EVMWallet.from(wallet);
// Create x402-compatible signer with viem wallet client structure
// Must have account, chain, and transport properties for x402 to recognize it as SignerWallet
const signer: any = {
account: {
address: evm.address,
type: "local",
source: "custom"
},
chain: {
id: 84532, // Base Sepolia chain ID
name: "Base Sepolia",
network: "base-sepolia",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: { http: ["https://base-sepolia.g.alchemy.com/v2/m8uZ16oNz2KOgSqu-9Pv6E1fkc69n8Xf"] },
public: { http: ["https://base-sepolia.g.alchemy.com/v2/m8uZ16oNz2KOgSqu-9Pv6E1fkc69n8Xf"] }
}
},
transport: {
type: "http",
url: "https://sepolia.base.org"
},
async signTypedData(params: any) {
const { domain, message, primaryType, types } = params;
console.log("Signing x402 payment:", {
from: evm.address,
to: domain.verifyingContract,
primaryType
});
// Sign with Crossmint wallet
const sig = await evm.signTypedData({
domain,
message,
primaryType,
types,
chain: evm.chain as any
} as any);
console.log("Raw signature from Crossmint:", {
signatureLength: sig.signature.length,
signatureStart: sig.signature.substring(0, 66)
});
return processSignature(sig.signature as string);
}
};
return signer;
}
Signature Processing
processSignature
Normalizes signatures for x402 compatibility, handling multiple signature formats.
From events-concierge/src/x402Adapter.ts:75-111:
function processSignature(rawSignature: string): Hex {
const signature = ensureHexPrefix(rawSignature);
console.log(`Processing signature: ${signature.substring(0, 20)}... (${signature.length} chars)`);
// Handle ERC-6492 wrapped signatures (for pre-deployed wallets)
if (isERC6492Signature(signature)) {
console.log("ERC-6492 signature detected - keeping for facilitator");
return signature;
}
// Handle EIP-1271 signatures (for deployed smart contract wallets)
if (signature.length === 174) {
console.log("EIP-1271 signature detected");
return signature;
}
// Handle standard ECDSA signatures (65 bytes / 132 hex chars)
if (signature.length === 132) {
console.log("Standard ECDSA signature");
return signature;
}
// Handle non-standard lengths - try to extract standard signature
if (signature.length > 132) {
const extracted = '0x' + signature.slice(-130);
console.log(`Extracted standard signature from longer format`);
return extracted as Hex;
}
console.log("Using signature as-is");
return signature;
}
ERC-6492 Signatures
Pre-deployed smart contract wallets use ERC-6492 wrapped signatures:
function isERC6492Signature(signature: string): boolean {
return signature.endsWith("6492649264926492649264926492649264926492649264926492649264926492");
}
Hex Prefix Normalization
function ensureHexPrefix(signature: string): Hex {
return (signature.startsWith('0x') ? signature : `0x${signature}`) as Hex;
}
Wallet Deployment
checkWalletDeployment
Checks if a wallet contract is deployed on-chain.
From events-concierge/src/x402Adapter.ts:131-154:
export async function checkWalletDeployment(
walletAddress: string,
chain: string
): Promise<boolean> {
try {
const { createPublicClient, http } = await import("viem");
const { baseSepolia } = await import("viem/chains");
const publicClient = createPublicClient({
chain: baseSepolia,
transport: http("https://sepolia.base.org")
});
const code = await publicClient.getCode({
address: walletAddress as `0x${string}`
});
// If bytecode exists and is not just "0x", the wallet is deployed
return code !== undefined && code !== '0x' && code.length > 2;
} catch (error) {
console.error('Failed to check wallet deployment:', error);
return false;
}
}
deployWallet
Deploys a pre-deployed wallet with a minimal self-transfer.
From events-concierge/src/x402Adapter.ts:159-185:
export async function deployWallet(wallet: Wallet<any>): Promise<string> {
console.log("Deploying wallet on-chain...");
try {
const { EVMWallet } = await import("@crossmint/wallets-sdk");
const evmWallet = EVMWallet.from(wallet);
// Deploy wallet with a minimal self-transfer (1 wei)
const deploymentTx = await evmWallet.sendTransaction({
to: wallet.address,
value: 1n, // 1 wei
data: "0x"
});
console.log(`Wallet deployed! Transaction: ${deploymentTx.hash}`);
return deploymentTx.hash || `deployment_tx_${Date.now()}`;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.error("Deployment error:", error);
if (errorMsg.includes('insufficient') || errorMsg.includes('balance')) {
throw new Error("Insufficient ETH balance for deployment gas fees");
}
throw new Error(`Wallet deployment failed: ${errorMsg}`);
}
}
Integration with x402 Client
Guest Agent Example
From events-concierge/src/agents/guest.ts:269-285:
import { withX402Client } from "agents/x402";
import { createX402Signer } from "../x402Adapter";
class Guest {
async connectToMCP(mcpUrl: string) {
// Create x402 signer from Crossmint wallet
const x402Signer = createX402Signer(this.wallet);
// Build x402 client
this.x402Client = withX402Client(this.mcp.mcpConnections[id].client, {
network: "base-sepolia",
account: x402Signer
});
console.log("x402 client ready with wallet:", this.wallet.address);
}
}
Payment Workflow
// 1. Check wallet deployment status
const isDeployed = await checkWalletDeployment(wallet.address, "base-sepolia");
if (!isDeployed) {
console.log("Wallet is pre-deployed (ERC-6492 mode)");
console.log("Deploying wallet for settlement...");
// 2. Deploy wallet before payment
const deploymentTxHash = await deployWallet(wallet);
console.log("Wallet deployed:", deploymentTxHash);
}
// 3. Create x402 signer
const x402Signer = createX402Signer(wallet);
// 4. Make payment with x402 client
const result = await x402Client.callTool(
onPaymentRequired,
{
name: "paidTool",
arguments: {}
}
);
console.log("Payment successful:", result);
Complete Example
Full x402Adapter Implementation
import type { Wallet } from "@crossmint/wallets-sdk";
import { EVMWallet } from "@crossmint/wallets-sdk";
import type { Hex } from "viem";
/**
* Create an x402-compatible Account from a Crossmint wallet
* Returns a viem Account-compatible object that x402 can use
*/
export function createX402Signer(wallet: Wallet<any>) {
const evm = EVMWallet.from(wallet);
const account: any = {
address: evm.address as `0x${string}`,
type: "local",
source: "custom",
signTypedData: async (params: any) => {
const { domain, message, primaryType, types } = params;
// Sign with Crossmint wallet
const sig = await evm.signTypedData({
domain,
message,
primaryType,
types,
chain: evm.chain as any
} as any);
return processSignature(sig.signature as string);
}
};
return account;
}
function processSignature(rawSignature: string): Hex {
const signature = ensureHexPrefix(rawSignature);
// Handle ERC-6492 wrapped signatures
if (isERC6492Signature(signature)) {
return signature;
}
// Handle EIP-1271 signatures (174 chars)
if (signature.length === 174) {
return signature;
}
// Handle standard ECDSA signatures (132 chars)
if (signature.length === 132) {
return signature;
}
// Extract standard signature from longer format
if (signature.length > 132) {
return ('0x' + signature.slice(-130)) as Hex;
}
return signature;
}
function ensureHexPrefix(signature: string): Hex {
return (signature.startsWith('0x') ? signature : `0x${signature}`) as Hex;
}
function isERC6492Signature(signature: string): boolean {
return signature.endsWith("6492649264926492649264926492649264926492649264926492649264926492");
}
export async function checkWalletDeployment(
walletAddress: string,
chain: string
): Promise<boolean> {
const { createPublicClient, http } = await import("viem");
const { baseSepolia } = await import("viem/chains");
const publicClient = createPublicClient({
chain: baseSepolia,
transport: http("https://sepolia.base.org")
});
const code = await publicClient.getCode({
address: walletAddress as `0x${string}`
});
return code !== undefined && code !== '0x' && code.length > 2;
}
export async function deployWallet(wallet: Wallet<any>): Promise<string> {
const evmWallet = EVMWallet.from(wallet);
const deploymentTx = await evmWallet.sendTransaction({
to: wallet.address,
value: 1n,
data: "0x"
});
return deploymentTx.hash || `deployment_tx_${Date.now()}`;
}
ERC-6492 (Pre-deployed Wallets)
- Length: Variable (typically >132 chars)
- Magic Suffix:
6492649264926492649264926492649264926492649264926492649264926492
- Use Case: Smart contract wallets that haven’t been deployed yet
- Processing: Keep signature as-is for facilitator
EIP-1271 (Deployed Smart Contracts)
- Length: 174 characters (including 0x prefix)
- Use Case: Deployed smart contract wallets
- Processing: Keep signature as-is
Standard ECDSA
- Length: 132 characters (including 0x prefix)
- Format: 65 bytes (r: 32 bytes, s: 32 bytes, v: 1 byte)
- Use Case: EOA wallets and some deployed contracts
- Processing: Keep signature as-is
Best Practices
Always Check Deployment Status
Before making x402 payments, verify wallet deployment:
const isDeployed = await checkWalletDeployment(wallet.address, "base-sepolia");
if (!isDeployed) {
await deployWallet(wallet);
}
Handle Signature Errors Gracefully
try {
const signature = await account.signTypedData(params);
} catch (error) {
console.error("Signature error:", error);
throw new Error(`Failed to sign payment: ${error instanceof Error ? error.message : String(error)}`);
}
Log Payment Details
For debugging, log EIP-712 payload details:
console.log("Signing payment:", {
from: evm.address,
to: message.to,
amount: message.maxAmount,
verifyingContract: domain.verifyingContract
});
Type Definitions
import type { Wallet } from "@crossmint/wallets-sdk";
import type { Hex } from "viem";
interface X402Account {
address: `0x${string}`;
type: "local";
source: "custom";
signTypedData: (params: {
domain: any;
message: any;
primaryType: string;
types: any;
}) => Promise<Hex>;
}
function createX402Signer(wallet: Wallet<any>): X402Account;
function checkWalletDeployment(
walletAddress: string,
chain: string
): Promise<boolean>;
function deployWallet(wallet: Wallet<any>): Promise<string>;
Troubleshooting
Signature Verification Fails
Problem: x402 facilitator rejects signature
Solution: Ensure wallet is deployed before settlement
if (!await checkWalletDeployment(wallet.address, "base-sepolia")) {
await deployWallet(wallet);
}
Insufficient Gas
Problem: Wallet deployment fails with “insufficient balance”
Solution: Fund wallet with ETH for gas fees
// Fund wallet before deployment
await fundWallet(wallet.address, "0.01"); // 0.01 ETH
await deployWallet(wallet);
Invalid Signature Length
Problem: Signature has unexpected length
Solution: processSignature handles this automatically by extracting standard signature
// Automatically handles non-standard lengths
if (signature.length > 132) {
const extracted = '0x' + signature.slice(-130);
return extracted as Hex;
}
See Also