Complete Workflow Examples
Production-ready examples for building with Cilantro Smart Wallets
Complete Workflow Examples
This guide provides production-ready examples for common wallet workflows, from creation to transaction execution.
Example 1: Complete User-Controlled Wallet Flow
From wallet creation to sending a transaction with a non-custodial email signer.
import { configure, setAuth } from 'cilantro-sdk';
import { login } from 'cilantro-sdk/auth';
import { create, sendSOL } from 'cilantro-sdk/wallet';
import {
createEmailSignerHelper,
getEmailSignerKeypair,
createIndexedDBAdapter
} from 'cilantro-sdk/helpers';
async function completeWalletFlow() {
configure({ apiKey: process.env.PLATFORM_API_KEY });
// Step 1: Login and set JWT
const authResult = await login({
usernameOrEmail: 'user@example.com',
password: 'SecurePass123!'
});
setAuth({ jwt: authResult.data.accessToken });
// Step 2: Create storage for device keys
const storage = createIndexedDBAdapter();
// Step 3: Create user-controlled wallet
const { data: walletData } = await create({ name: 'My Personal Wallet', userId: 'user-id' });
const walletId = walletData.id;
console.log('✅ Wallet created:', walletId);
console.log('Address:', walletData.address);
// Step 4: Add email signer
const signer = await createEmailSignerHelper(walletId, {
email: 'user@example.com',
deviceKeyManager: storage
});
console.log('✅ Email signer created:', signer.signerId);
// Step 5: Get keypair for signing
const keypair = await getEmailSignerKeypair(
walletId,
signer.signerId,
{ deviceKeyManager: storage }
);
console.log('✅ Keypair derived and cached');
// Step 6: Send SOL (assuming wallet has balance)
const result = await sendSOL(walletId, {
recipientAddress: 'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
amountLamports: 1000000 // 0.001 SOL
});
const { data: txData } = result;
console.log('✅ Transaction sent:', txData?.signature);
return {
walletId,
signerId: signer.signerId,
address: walletData.address,
transactionSignature: txData?.signature
};
}
completeWalletFlow().catch(console.error);
Example 2: Platform-Controlled Gaming Wallet
Create a custodial wallet for instant gaming transactions.
import { setAuth } from 'cilantro-sdk';
import { create, sendSOL, getAssets } from 'cilantro-sdk/wallet';
async function createGamingWallet(userId: string) {
setAuth({ platformApiKey: process.env.PLATFORM_API_KEY });
// Create platform-controlled wallet (no admin signer)
const { data } = await create({
name: `Gaming Wallet - User ${userId}`,
userId,
});
console.log('Gaming wallet created:', data.id);
console.log('Address:', data.address);
return data;
}
async function rewardPlayer(walletId: string, rewardAmount: number) {
// Platform can instantly send rewards
const result = await sendSOL(walletId, {
recipientAddress: 'player-wallet-address',
amountLamports: rewardAmount,
gasless: true // Platform pays gas fees
});
const { data } = result;
console.log('Reward sent:', data?.signature);
return data;
}
async function getPlayerAssets(walletId: string) {
const { data: assets } = await getAssets(walletId);
console.log('Player assets:', assets?.balance, assets?.nfts, assets?.tokens);
return assets;
}
// Usage
const wallet = await createGamingWallet('user-123');
await rewardPlayer(wallet.id, 100000000); // 0.1 SOL reward
const assets = await getPlayerAssets(wallet.id);
Example 3: Multi-Device Support
Managing the same signer across multiple devices.
import {
generateDeviceKeyPair,
addNewDeviceToSigner,
getEmailSignerKeypair,
createLocalStorageAdapter
} from 'cilantro-sdk/helpers';
import { createEmailSignerHelper } from 'cilantro-sdk/helpers';
async function setupMultiDevice() {
// Device 1: Create signer with device key
const storage1 = createLocalStorageAdapter();
const signer = await createEmailSignerHelper('wallet-id', {
email: 'user@example.com',
deviceKeyManager: storage1
});
console.log('✅ Signer created on Device 1');
// Device 2: Add new device to existing signer
const storage2 = createLocalStorageAdapter();
const newDeviceKey = await generateDeviceKeyPair();
await addNewDeviceToSigner(
'wallet-id',
signer.signerId,
newDeviceKey.publicKey
);
// Store the device key on Device 2
await storage2.saveDeviceKey(newDeviceKey);
console.log('✅ Device 2 added to signer');
// Now both devices can sign
const keypair1 = await getEmailSignerKeypair(
'wallet-id',
signer.signerId,
{ deviceKeyManager: storage1 }
);
const keypair2 = await getEmailSignerKeypair(
'wallet-id',
signer.signerId,
{ deviceKeyManager: storage2 }
);
console.log('✅ Both devices can now sign');
return { signer, keypair1, keypair2 };
}
setupMultiDevice().catch(console.error);
Example 4: Client-Side Transaction Signing
Complete flow for user-controlled transaction signing.
import {
prepareTransaction,
submitTransaction
} from 'cilantro-sdk/wallet';
import {
getEmailSignerKeypair,
createLocalStorageAdapter
} from 'cilantro-sdk/helpers';
import { Transaction } from '@solana/web3.js';
import { sign as ed25519Sign } from '@noble/ed25519';
async function sendSOLClientSide(
walletId: string,
signerId: string,
recipientAddress: string,
amountLamports: number
) {
// Step 1: Get signer keypair
const storage = createLocalStorageAdapter();
const keypair = await getEmailSignerKeypair(walletId, signerId, {
deviceKeyManager: storage
});
// Step 2: Prepare transaction (unsigned)
const prepared = await prepareTransaction(walletId, {
type: 'SEND_SOL',
signerPubkey: Buffer.from(keypair.publicKey).toString('base64'),
sendSolParams: {
recipientAddress,
amountLamports
}
});
console.log('Transaction prepared');
// Step 3: Deserialize transaction
const tx = Transaction.from(
Buffer.from(prepared.data.serializedTransaction, 'base64')
);
// Step 4: Sign transaction locally
const message = tx.serializeMessage();
const privateKey = keypair.secretKey.slice(0, 32);
const signature = await ed25519Sign(message, privateKey);
tx.addSignature(
{ toBuffer: () => Buffer.from(keypair.publicKey) } as any,
Buffer.from(signature)
);
const signedTx = tx.serialize().toString('base64');
console.log('Transaction signed locally');
// Step 5: Submit signed transaction
const result = await submitTransaction(walletId, {
signedTransaction: signedTx
});
console.log('✅ Transaction submitted:', result.data.signature);
return result.data;
}
// Usage
const txResult = await sendSOLClientSide(
'wallet-123',
'signer-456',
'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
1000000000 // 1 SOL
);
console.log('View on explorer:', `https://solscan.io/tx/${txResult.signature}`);
Example 5: Transaction with Error Handling
Robust transaction handling with retries and confirmation.
import { sendSOL } from 'cilantro-sdk/wallet';
import { getTransactionStatus } from 'cilantro-sdk/transactions';
async function waitForConfirmation(signature: string, maxAttempts = 30) {
for (let i = 0; i < maxAttempts; i++) {
await new Promise(resolve => setTimeout(resolve, 2000)); // 2s delay
const status = await getTransactionStatus(signature);
if (status.data.status === 'confirmed') {
console.log('✅ Transaction confirmed');
return status.data;
} else if (status.data.status === 'failed') {
throw new Error(`Transaction failed: ${status.data.error}`);
}
console.log(`Waiting for confirmation... (${i + 1}/${maxAttempts})`);
}
throw new Error('Transaction timeout');
}
async function sendSOLWithRetry(
walletId: string,
recipientAddress: string,
amountLamports: number,
maxRetries: number = 3
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`\nAttempt ${attempt}/${maxRetries}`);
// Send transaction
const result = await sendSOL(walletId, {
recipientAddress,
amountLamports
});
const signature = result.data.signature;
console.log('Transaction sent:', signature);
// Wait for confirmation
const confirmed = await waitForConfirmation(signature);
return {
signature,
status: 'confirmed',
...confirmed
};
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error.message);
if (attempt === maxRetries) {
throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
}
// Wait before retry (exponential backoff)
const delay = 2000 * attempt;
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage
try {
const result = await sendSOLWithRetry(
'wallet-id',
'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
1000000000 // 1 SOL
);
console.log('\n✅ Success:', result);
console.log('Explorer:', `https://solscan.io/tx/${result.signature}`);
} catch (error) {
console.error('\n❌ Transaction failed:', error.message);
}
Example 6: Multi-Signer Wallet with Recovery
Create a wallet with multiple authentication methods.
import { create } from 'cilantro-sdk/wallet';
import {
createEmailSignerHelper,
createPhoneSignerHelper,
createIndexedDBAdapter
} from 'cilantro-sdk/helpers';
async function createSecureWallet(
email: string,
phone: string
) {
const storage = createIndexedDBAdapter();
// Step 1: Create wallet
const { data } = await create({ name: 'Secure Multi-Signer Wallet', userId: 'user-id' });
const walletId = data.id;
console.log('✅ Wallet created:', walletId);
// Step 2: Add email signer (primary)
const emailSigner = await createEmailSignerHelper(walletId, { email, deviceKeyManager: storage });
console.log('✅ Email signer added (primary)');
// Step 3: Add phone signer (recovery)
const phoneSigner = await createPhoneSignerHelper(walletId, { phone, deviceKeyManager: storage });
console.log('✅ Phone signer added (recovery)');
// Step 4: Add passkey signer (high-security) — use usePasskey or SDK passkey helpers
// passkeyOptions = await startPasskeyRegistration(walletId); ...
return {
walletId,
address: data.address,
signers: { email: emailSigner.signerId, phone: phoneSigner.signerId, passkey: 'registered' }
};
}
// Usage
const secureWallet = await createSecureWallet(
'user@example.com',
'+1234567890'
);
console.log('\n✅ Secure wallet setup complete:');
console.log('- Primary authentication: Email');
console.log('- Recovery method: Phone');
console.log('- High-value transactions: Passkey');
Example 7: Batch Operations
Send to multiple recipients efficiently.
import { batchSendSOL } from 'cilantro-sdk/wallet';
async function airdropTokens(
walletId: string,
recipients: Array<{ address: string; amount: number }>
) {
const transactions = recipients.map(recipient => ({
recipientAddress: recipient.address,
amountLamports: recipient.amount
}));
const result = await batchSendSOL({
walletId,
transactions
});
console.log('✅ Batch airdrop complete');
console.log('Transactions:', result.data.signatures);
// Wait for all confirmations
for (const signature of result.data.signatures) {
console.log(`Confirming: ${signature}`);
await waitForConfirmation(signature);
}
console.log('✅ All transactions confirmed');
return result.data;
}
// Usage
const recipients = [
{ address: 'Address1...', amount: 1000000000 }, // 1 SOL
{ address: 'Address2...', amount: 500000000 }, // 0.5 SOL
{ address: 'Address3...', amount: 250000000 } // 0.25 SOL
];
await airdropTokens('wallet-id', recipients);
Example 8: DeFi Application Pattern
User-controlled wallet for DeFi operations.
import { configure } from 'cilantro-sdk';
import { loginAndSetAuth } from 'cilantro-sdk/auth';
import { create } from 'cilantro-sdk/wallet';
import {
createEmailSignerHelper,
getEmailSignerKeypair,
createLocalStorageAdapter
} from 'cilantro-sdk/helpers';
import { generateDeviceKeyPair } from 'cilantro-sdk/helpers';
async function createDeFiWallet(
userEmail: string,
userPassword: string
) {
// Step 1: Configure and authenticate
configure({ apiKey: process.env.CILANTRO_API_KEY });
await loginAndSetAuth({
usernameOrEmail: userEmail,
password: userPassword
});
// Step 2: Create device key for non-custodial signing
const deviceKey = await generateDeviceKeyPair();
const storage = createLocalStorageAdapter();
await storage.saveDeviceKey(deviceKey);
// Step 3: Create user-controlled wallet with email admin signer (API may vary)
const { data: wallet } = await create({
name: 'DeFi Wallet',
userId: 'user-id',
adminSigner: {
type: 'email',
authId: userEmail,
devicePublicKey: deviceKey.publicKey
}
});
console.log('✅ DeFi wallet created (non-custodial)');
console.log('Wallet ID:', wallet.id);
console.log('Address:', wallet.address);
// Step 4: Verify we can derive signing keys
const keypair = await getEmailSignerKeypair(
wallet.id,
wallet.adminSigner?.signerId ?? '',
{ deviceKeyManager: storage }
);
console.log('✅ Signing keys verified');
return { walletId: wallet.id, address: wallet.address, adminSigner: wallet.adminSigner };
}
// Usage
const defiWallet = await createDeFiWallet(
'defi-user@example.com',
'SecurePassword123!'
);
console.log('\n✅ User has full control of their DeFi wallet');
console.log('Platform never sees private keys!');
Example 9: Hybrid Wallet System
Start with custodial, upgrade to non-custodial as user gains experience.
import { create } from 'cilantro-sdk/wallet';
import { createEmailSignerHelper, createLocalStorageAdapter } from 'cilantro-sdk/helpers';
async function hybridWalletSystem(userId: string, userEmail: string) {
// Phase 1: New user - create custodial wallet for easy onboarding
const quickWallet = await create({
walletName: `Quick Wallet - User ${userId}`
// No adminSigner = platform controlled
});
console.log('✅ Phase 1: Custodial wallet created');
console.log('User can start immediately!');
// User can immediately transact with zero friction
// ... user gains experience with crypto ...
// Phase 2: User wants more control - add email signer
const storage = createLocalStorageAdapter();
const signer = await createEmailSignerHelper(quickWallet.data.walletId, {
email: userEmail,
deviceKeyManager: storage
});
console.log('✅ Phase 2: Email signer added');
console.log('User can now choose signing method!');
return {
walletId: quickWallet.data.walletId,
mode: 'hybrid',
options: {
platformSigning: 'Available (fast, for small amounts)',
userSigning: 'Available (secure, for large amounts)'
}
};
}
// Usage
const wallet = await hybridWalletSystem('user-123', 'user@example.com');
console.log('\n✅ Hybrid system gives users choice:');
console.log('- Platform signing: Fast, frictionless');
console.log('- User signing: Secure, non-custodial');
Example 10: NFT Minting Workflow
Complete NFT minting with metadata.
import { mintNFTSimple } from 'cilantro-sdk/wallet';
async function mintGameNFT(
walletId: string,
playerAddress: string,
nftMetadata: {
name: string;
symbol: string;
description: string;
imageUrl: string;
attributes?: Array<{ trait_type: string; value: string }>;
}
) {
const result = await mintNFTSimple(walletId, {
recipientAddress: playerAddress,
name: nftMetadata.name,
symbol: nftMetadata.symbol,
uri: `https://api.yourgame.com/nft/${nftMetadata.name}`,
metadata: {
name: nftMetadata.name,
description: nftMetadata.description,
image: nftMetadata.imageUrl,
attributes: nftMetadata.attributes || []
}
});
console.log('✅ NFT minted:', result.data.mintAddress);
console.log('Transaction:', result.data.signature);
return result.data;
}
// Usage
const nft = await mintGameNFT(
'wallet-id',
'player-address',
{
name: 'Legendary Sword',
symbol: 'SWORD',
description: 'A legendary weapon forged in dragon fire',
imageUrl: 'https://cdn.yourgame.com/nft/legendary-sword.png',
attributes: [
{ trait_type: 'Rarity', value: 'Legendary' },
{ trait_type: 'Attack', value: '150' },
{ trait_type: 'Durability', value: '100' }
]
}
);
console.log('\n✅ NFT minted and sent to player!');
Example 11: Address Book Management
Manage frequently used addresses for quick access.
import {
create,
findAll,
update,
remove
} from 'cilantro-sdk/address-book';
// Add addresses with tags
await create({
address: 'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
label: 'Exchange Wallet',
tags: ['exchange', 'trading']
});
await create({
address: 'AnotherAddress...',
label: 'DeFi Wallet',
tags: ['defi', 'yield-farming']
});
// Search addresses
const exchangeAddresses = await findAll({
tag: 'exchange',
search: 'wallet'
});
// Use address for transaction
if (exchangeAddresses.data.items.length > 0) {
const address = exchangeAddresses.data.items[0];
await sendSOL('wallet-id', {
recipientAddress: address.address,
amountLamports: 1000000000
});
}
Example 12: Scheduled Transactions
Set up automated recurring payments.
import { create, findAll, cancel } from 'cilantro-sdk/scheduled-transactions';
// Create monthly subscription
const subscription = await create('wallet-id', {
transactionType: 'SEND_SOL',
recurrenceType: 'MONTHLY',
scheduledAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
toAddress: 'subscription-recipient-address',
amount: 2000000000, // 2 SOL per month
metadata: {
subscriptionId: 'sub-123',
plan: 'premium'
}
});
// Monitor scheduled transactions
const scheduled = await findAll('wallet-id');
const pending = scheduled.data.filter(tx => tx.status === 'pending');
console.log(`Pending transactions: ${pending.length}`);
// Cancel if needed
await cancel('wallet-id', subscription.data.id);
Example 13: Spending Limits
Set up budget controls and monitoring.
import {
create,
getBudgetStatus,
update
} from 'cilantro-sdk/spending-limits';
// Set up comprehensive limits
const dailyLimit = await create('wallet-id', {
period: 'DAILY',
limitAmount: 1000000000, // 1 SOL per day
alertThreshold: 80
});
const monthlyLimit = await create('wallet-id', {
period: 'MONTHLY',
limitAmount: 20000000000, // 20 SOL per month
autoPause: true,
alertThreshold: 90
});
// Monitor budget
const budget = await getBudgetStatus('wallet-id');
console.log('Daily spent:', budget.data.daily?.spent);
console.log('Monthly remaining:', budget.data.monthly?.remaining);
// Check if limit approaching
if (budget.data.daily?.percentage >= 80) {
console.warn('Daily limit at 80%!');
}
Example 14: Analytics and Reporting
Get wallet analytics and export transaction data.
import { getWalletAnalytics } from 'cilantro-sdk/analytics';
import { exportTransactions } from 'cilantro-sdk/transactions';
// Get wallet analytics
const analytics = await getWalletAnalytics('wallet-id', {
days: 30,
includeHistory: true
});
console.log('Total balance:', analytics.data.totalBalance);
console.log('Active wallets:', analytics.data.activeWallets);
console.log('NFT count:', analytics.data.nftCount);
// Export transactions for accounting
const csv = await exportTransactions('wallet-id', {
format: 'CSV',
fromDate: '2024-01-01T00:00:00Z',
toDate: '2024-12-31T23:59:59Z',
status: 'confirmed'
});
// Download CSV file
const blob = new Blob([csv.data], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'transactions.csv';
a.click();
Example 15: Passkey Operations
Complete passkey registration and transaction signing.
import {
startPasskeyRegistration,
verifyPasskeyRegistration,
startPasskeyAuthentication,
verifyPasskeyAuthentication
} from 'cilantro-sdk/wallet';
// Register passkey
async function registerPasskey(walletId: string) {
const options = await startPasskeyRegistration(walletId);
const credential = await navigator.credentials.create({
publicKey: options.data
}) as PublicKeyCredential;
const signer = await verifyPasskeyRegistration(walletId, {
response: credential.response,
credentialId: credential.id
});
return signer.data;
}
// Authenticate with passkey
async function authenticateWithPasskey(walletId: string) {
const options = await startPasskeyAuthentication(walletId);
const assertion = await navigator.credentials.get({
publicKey: options.data
}) as PublicKeyCredential;
const result = await verifyPasskeyAuthentication(walletId, {
response: assertion.response,
credentialId: assertion.id
});
return result.data;
}
// Use passkey for transaction
const authResult = await authenticateWithPasskey('wallet-id');
// ... proceed with transaction signing
Example 16: Complete Non-Custodial Flow
End-to-end non-custodial transaction with email signer.
import {
prepareTransaction,
submitTransaction
} from 'cilantro-sdk/wallet';
import {
getEmailSignerKeypair,
createLocalStorageAdapter
} from 'cilantro-sdk/helpers';
import { Transaction } from '@solana/web3.js';
import { sign as ed25519Sign } from '@noble/ed25519';
async function sendSOLNonCustodial(
walletId: string,
signerId: string,
recipientAddress: string,
amountLamports: number
) {
// Get signer keypair
const storage = createLocalStorageAdapter();
const keypair = await getEmailSignerKeypair(walletId, signerId, {
deviceKeyManager: storage
});
// Prepare transaction
const prepared = await prepareTransaction(walletId, {
type: 'SEND_SOL',
signerPubkey: Buffer.from(keypair.publicKey).toString('base64'),
sendSolParams: {
recipientAddress,
amountLamports
}
});
// Sign transaction
const tx = Transaction.from(
Buffer.from(prepared.data.serializedTransaction, 'base64')
);
const message = tx.serializeMessage();
const privateKey = keypair.secretKey.slice(0, 32);
const signature = await ed25519Sign(message, privateKey);
tx.addSignature(
{ toBuffer: () => Buffer.from(keypair.publicKey) } as any,
Buffer.from(signature)
);
// Submit
const result = await submitTransaction(walletId, {
signedTransaction: tx.serialize().toString('base64')
});
return result.data;
}