Quick Start
Build a passkey wallet and send your first transaction in under 5 minutes.
What is a passkey wallet? Instead of seed phrases or private keys, your wallet is secured by your device's biometrics (FaceID, fingerprint, Windows Hello). No passwords to remember, no seed phrases to write down. Your biometric = your wallet key.
Frontend Required: Passkey operations (register, authenticate) use WebAuthn browser APIs and must run in a client-side browser context (HTTPS or localhost). They cannot run in Node.js, server components, or terminal scripts. All code examples below must execute in a browser — either in a React/Next.js client component, a plain HTML page with a <script> tag, or any other browser environment.
1. Initialize the SDK
First, tell the SDK which blockchain you want to use. We'll use Base Sepolia (a free test network) so you won't spend any real money while learning.
import { createSDK } from '@veridex/sdk';
// Initialize with testnet (free test network — no real money involved)
const sdk = createSDK('base', {
network: 'testnet',
relayerUrl: 'https://relayer.veridex.network',
});
// That's it! The SDK is ready to use.2. Register a Passkey
This is the "sign up" step. The user's browser will show a biometric prompt (FaceID, fingerprint, etc.). After they verify, a passkey is created and a wallet address is generated automatically.
// This pops up a biometric prompt (FaceID / fingerprint / PIN)
const credential = await sdk.passkey.register(
'user@example.com', // Username shown in the passkey prompt
'My Veridex Wallet' // A friendly name for this passkey
);
// Done! The user now has a wallet.
console.log('Credential ID:', credential.credentialId);
console.log('Key Hash:', credential.keyHash);
console.log('Vault Address:', sdk.getVaultAddress());The vault address is deterministic — the same passkey always produces the same address on every EVM chain. Your user gets one address that works everywhere.
3. Fund the Vault
Your wallet needs some test tokens before it can send transactions. "Faucets" are free token dispensers for test networks — just paste your vault address and click a button.
For testnet, use these faucets:
| Chain | Faucet |
|---|---|
| Base Sepolia | base.org/faucet (opens in a new tab) |
| Optimism Sepolia | app.optimism.io/faucet (opens in a new tab) |
| Arbitrum Sepolia | faucet.quicknode.com/arbitrum (opens in a new tab) |
| Stacks Testnet | explorer.hiro.so/sandbox/faucet (opens in a new tab) |
4. Prepare and Execute a Transfer
Sending tokens is a two-step process: prepare (preview the cost) then execute (sign and send). This lets you show users exactly what will happen before they confirm.
// Step A: Prepare — calculates gas cost, doesn't send anything yet
const prepared = await sdk.prepareTransfer({
targetChain: 10004, // Base Sepolia (Wormhole chain ID)
token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // USDC on Base Sepolia
recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234',
amount: 1000000n, // 1 USDC (USDC has 6 decimals, so 1_000_000 = 1.0 USDC)
});
// Show the user a human-readable summary
const summary = await sdk.getTransactionSummary(prepared);
console.log(summary.title); // "Transfer"
console.log(summary.description); // "Send 1.0 USDC to 0x742d...5A234"
console.log('Gas cost:', prepared.formattedCost);
// Step B: Execute — triggers biometric prompt, signs, and sends
const result = await sdk.executeTransfer(prepared, signer);
console.log('Transaction hash:', result.transactionHash);
// Done! The tokens are on their way.5. Check Spending Limits
import { ethers } from 'ethers';
// Check current limits
const limits = await sdk.getSpendingLimits();
console.log('Daily remaining:', limits.dailyRemaining);
// Check if a specific amount is allowed
const check = await sdk.checkSpendingLimit(ethers.parseEther('1.0'));
if (check.allowed) {
console.log('Transfer allowed!');
} else {
console.log('Blocked:', check.message);
}
// Get formatted limits for UI
const formatted = await sdk.getFormattedSpendingLimits();
console.log(`${formatted.dailyUsedPercentage}% of daily limit used`);6. Check Balances
// Get all token balances on the current chain
const portfolio = await sdk.getVaultBalances();
for (const entry of portfolio.tokens) {
console.log(`${entry.token.symbol}: ${entry.formatted}`);
}
console.log('Total USD:', portfolio.totalUsdValue);7. Back Up Your Wallet (Optional but Recommended)
If you lose your device, you can recover your wallet using an encrypted backup. This takes 30 seconds:
import { WalletBackupManager } from '@veridex/sdk';
const backup = new WalletBackupManager({
passkey: sdk.passkey,
relayerUrl: 'https://relayer.veridex.network/api/v1',
});
// Encrypt and upload your credentials (triggers biometric prompt)
const cred = sdk.passkey.getCredential();
const result = await backup.backupCredentials(cred.keyHash);
console.log('Backup complete!', result.credentialCount, 'credentials saved');Your backup is encrypted with a key only your passkey can produce — the server cannot read it. See the full Wallet Backup & Recovery guide for recovery steps.
Complete Example
import { createSDK } from '@veridex/sdk';
import { ethers } from 'ethers';
async function main() {
// 1. Initialize
const sdk = createSDK('base', {
network: 'testnet',
relayerUrl: 'https://relayer.veridex.network',
});
// 2. Check if user has existing passkeys
const storedCredentials = sdk.passkey.getAllStoredCredentials();
if (storedCredentials.length === 0) {
// 3. Register new passkey (prompts biometric)
console.log('Registering passkey...');
await sdk.passkey.register('user@example.com', 'My Wallet');
} else {
// 4. Authenticate with existing passkey (shows passkey picker)
console.log('Logging in...');
await sdk.passkey.authenticate();
}
// 5. Get vault info
const vault = sdk.getVaultAddress();
console.log('Vault:', vault);
// 6. Check spending limits
const limits = await sdk.getSpendingLimits();
console.log('Daily limit remaining:', limits.dailyRemaining);
// 7. Prepare a transfer
const prepared = await sdk.prepareTransfer({
targetChain: 10004,
token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234',
amount: 100000n, // 0.1 USDC
});
// 8. Show summary to user
const summary = await sdk.getTransactionSummary(prepared);
console.log(`${summary.title}: ${summary.description}`);
console.log('Gas cost:', prepared.formattedCost);
if (summary.risks.length > 0) {
console.warn('Risks:', summary.risks.map(r => r.message));
}
}
main().catch(console.error);Multi-Chain Initialization (Advanced)
For apps that need multi-chain support, use the VeridexSDK class directly with an EVMClient:
import { VeridexSDK } from '@veridex/sdk';
import { EVMClient } from '@veridex/sdk/chains/evm';
// Create the EVM chain client (hub chain)
const evmClient = new EVMClient({
chainId: 84532,
wormholeChainId: 10004,
rpcUrl: 'https://sepolia.base.org',
hubContractAddress: '0x66D87dE68327f48A099c5B9bE97020Feab9a7c82',
wormholeCoreBridge: '0x79A1027a6A159502049F10906D333EC57E95F083',
name: 'Base Sepolia',
explorerUrl: 'https://sepolia.basescan.org',
vaultFactory: '0xCFaEb5652aa2Ee60b2229dC8895B4159749C7e53',
vaultImplementation: '0x0d13367C16c6f0B24eD275CC67C7D9f42878285c',
});
const sdk = new VeridexSDK({
chain: evmClient,
persistWallet: true,
testnet: true,
relayerUrl: '/api/relayer',
relayerApiKey: process.env.NEXT_PUBLIC_RELAYER_API_KEY,
});The createSDK shorthand is great for getting started. For production apps with multi-chain vaults, use VeridexSDK + EVMClient directly — see the React Integration guide.
Gasless Transfer (via Relayer)
Send tokens without the user needing gas:
// Transfer via relayer — user pays no gas
const result = await sdk.transferViaRelayer({
targetChain: 10004,
token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234',
amount: 1000000n, // 1 USDC
});
console.log('Transaction hash:', result.transactionHash);Agent SDK Quick Start
For AI agents that need to make autonomous payments:
import { createAgentWallet } from '@veridex/agentic-payments';
async function agentQuickStart() {
// 1. Create agent with spending limits and MCP tools
const agent = await createAgentWallet({
masterCredential: {
credentialId: process.env.CREDENTIAL_ID!,
publicKeyX: BigInt(process.env.PUBLIC_KEY_X!),
publicKeyY: BigInt(process.env.PUBLIC_KEY_Y!),
keyHash: process.env.KEY_HASH!,
},
session: {
dailyLimitUSD: 50,
perTransactionLimitUSD: 10,
expiryHours: 24,
allowedChains: [10004], // Base Sepolia
},
mcp: { enabled: true }, // Enable MCP tools for AI model integration
relayerUrl: 'https://relay.veridex.network',
});
// 2. Get MCP tools (for Gemini, Claude, GPT function calling)
const tools = agent.getMCPTools();
console.log('Available tools:', tools.map(t => t.name));
// 3. Fetch paid data (x402 handled automatically)
const response = await agent.fetch('https://paid-api.example.com/data');
const data = await response.json();
// 4. Direct payment
const receipt = await agent.pay({
chain: 10004,
token: 'USDC',
amount: '1000000',
recipient: '0x...',
});
// 5. Check spending
const status = agent.getSessionStatus();
console.log(`Spent: $${status.totalSpentUSD} / $${status.limits!.dailyLimitUSD}`);
// 6. Clean up
await agent.revokeSession();
}Next.js SSR-Safe Pattern
In Next.js, use dynamic imports to avoid SSR issues:
// lib/veridex-client.ts
let sdkInstance: any = null;
export async function getVeridexSDK() {
if (!sdkInstance) {
const { createSDK } = await import('@veridex/sdk');
sdkInstance = createSDK('base', {
network: 'testnet',
relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL,
});
}
return sdkInstance;
}The await import('@veridex/sdk') ensures the SDK is only loaded in the browser, never during server-side rendering. See the Next.js Integration guide for the full pattern.
React Integration
'use client';
import { useState } from 'react';
import { createSDK } from '@veridex/sdk';
const sdk = createSDK('base', { network: 'testnet' });
export function WalletButton() {
const [vault, setVault] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleConnect = async () => {
setLoading(true);
try {
// Check for existing passkey
const hasStored = sdk.passkey.hasStoredCredential();
if (hasStored) {
await sdk.passkey.authenticate();
} else {
await sdk.passkey.register('user@example.com', 'My Wallet');
sdk.passkey.saveToLocalStorage();
}
setVault(sdk.getVaultAddress());
} catch (error) {
console.error('Failed to connect:', error);
}
setLoading(false);
};
if (vault) {
return (
<div>
<p>Connected: {vault.slice(0, 6)}...{vault.slice(-4)}</p>
</div>
);
}
return (
<button onClick={handleConnect} disabled={loading}>
{loading ? 'Connecting...' : 'Connect with Passkey'}
</button>
);
}