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;
}

Best Practices Summary

Next Steps

Complete Workflow Examples | Cilantro Smart Wallet Docs | Cilantro Smart Wallet