Skip to main content

Overview

Real-world examples demonstrating common integration patterns with the KillB JavaScript SDK.

Complete On-Ramp Example

Full implementation of an on-ramp flow:
import { KillB } from '@killb/sdk';

async function completeOnRampFlow() {
  // Initialize SDK
  const killb = new KillB({
    email: process.env.KILLB_EMAIL!,
    password: process.env.KILLB_PASSWORD!,
    environment: 'sandbox'
  });

  await killb.initialize();

  // User data from your application
  const userData = {
    firstName: 'María',
    lastName: 'López',
    email: '[email protected]',
    phone: '+525512345678',
    walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
  };

  try {
    // Step 1: Create or get user
    let user;
    try {
      user = await killb.users.getByExternalId(`user-${userData.email}`);
      console.log('✅ Existing user found');
    } catch (error) {
      // User doesn't exist, create new
      user = await killb.users.create({
        type: 'PERSON',
        externalId: `user-${userData.email}`,
        data: {
          firstName: userData.firstName,
          lastName: userData.lastName,
          email: userData.email,
          phone: userData.phone,
          dateOfBirth: '1990-01-01',
          address: {
            street1: 'Calle Principal 123',
            city: 'Ciudad de México',
            state: 'CDMX',
            zipCode: '01000',
            countryCode: 'MX'
          },
          document: {
            type: 'RFC',
            number: 'LOMM900310ABC',
            issuedCountryCode: 'MX'
          }
        }
      });
      console.log('✅ User created:', user.id);
    }

    // Step 2: Create or get wallet account
    const walletExternalId = `wallet-${user.id}-polygon`;
    let wallet;
    
    const existingAccounts = await killb.accounts.getByUserId(user.id);
    wallet = existingAccounts.find(a => a.externalId === walletExternalId);
    
    if (!wallet) {
      wallet = await killb.accounts.create({
        type: 'WALLET',
        userId: user.id,
        externalId: walletExternalId,
        data: {
          firstName: userData.firstName,
          lastName: userData.lastName,
          email: userData.email,
          phone: userData.phone,
          currency: 'USDC',
          network: 'POLYGON',
          address: userData.walletAddress,
          countryCode: 'MX',
          document: {
            type: 'RFC',
            number: 'LOMM900310ABC',
            issuedCountryCode: 'MX'
          }
        }
      });
      console.log('✅ Wallet created:', wallet.id);
    } else {
      console.log('✅ Existing wallet found');
    }

    // Step 3: Get quotation
    const quote = await killb.quotations.create({
      fromCurrency: 'MXN',
      toCurrency: 'USDC',
      amount: 1000,
      amountIsToCurrency: false,
      cashInMethod: 'SPEI',
      cashOutMethod: 'POLYGON'
    });

    console.log('✅ Quote created');
    console.log('   Send:', quote.fromAmount, 'MXN');
    console.log('   Receive:', quote.toAmount, 'USDC');
    console.log('   Rate:', quote.rate);
    console.log('   Expires in:', Math.floor((quote.expiresAt - Date.now()) / 1000), 'seconds');

    // Step 4: Create ramp
    const ramp = await killb.ramps.create({
      quotationId: quote.id,
      userId: user.id,
      accountId: wallet.id,
      externalId: `onramp-${Date.now()}`
    });

    console.log('✅ Ramp created:', ramp.id);
    console.log('   Status:', ramp.status);
    
    // SPEI payment instructions
    const paymentInfo = ramp.paymentInfo[0];
    console.log('\n📋 Payment Instructions:');
    console.log('   CLABE:', paymentInfo.CLABE);
    console.log('   Bank:', paymentInfo.Bank);
    console.log('   Beneficiary:', paymentInfo.Beneficiary);
    console.log('   Reference:', paymentInfo.concepto);

    // Step 5: In sandbox, simulate payment
    if (killb.environment === 'sandbox') {
      console.log('\n⚡ Simulating payment...');
      await killb.faker.cashIn(ramp.id);
    }

    // Step 6: Wait for completion
    console.log('\n⏳ Waiting for ramp to complete...');
    const completedRamp = await ramp.waitForCompletion({
      pollInterval: 5000,
      timeout: 300000,
      onStatusChange: (status) => {
        console.log('   Status updated:', status);
      }
    });

    console.log('✅ Ramp completed!');
    console.log('   Transfer proof:', completedRamp.transferProof);
    
    return completedRamp;

  } catch (error) {
    console.error('❌ Error:', error);
    throw error;
  }
}

// Run the example
completeOnRampFlow()
  .then(() => console.log('\n🎉 All done!'))
  .catch(console.error);

Express.js Integration

Complete Express.js server with KillB SDK:
import express from 'express';
import { KillB, WebhookHandler } from '@killb/sdk';

const app = express();
app.use(express.json());

// Initialize KillB SDK
const killb = new KillB({
  email: process.env.KILLB_EMAIL!,
  password: process.env.KILLB_PASSWORD!,
  environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox'
});

// Initialize on server start
killb.initialize().then(() => {
  console.log('✅ KillB SDK initialized');
});

// Create on-ramp endpoint
app.post('/api/onramp/create', async (req, res) => {
  try {
    const { userId, walletAddress, amount } = req.body;

    // Get or create wallet
    let wallet = await killb.accounts.findOne({
      userId,
      type: 'WALLET',
      'data.address': walletAddress
    });

    if (!wallet) {
      wallet = await killb.accounts.create({
        type: 'WALLET',
        userId,
        data: { /* wallet data */ }
      });
    }

    // Create quotation
    const quote = await killb.quotations.create({
      fromCurrency: 'COP',
      toCurrency: 'USDC',
      amount,
      amountIsToCurrency: false,
      cashInMethod: 'PSE',
      cashOutMethod: 'POLYGON'
    });

    // Create ramp
    const ramp = await killb.ramps.create({
      quotationId: quote.id,
      userId,
      accountId: wallet.id,
      externalId: `order-${req.body.orderId}`
    });

    res.json({
      success: true,
      rampId: ramp.id,
      paymentUrl: ramp.paymentInfo[0].url,
      expectedAmount: quote.toAmount
    });

  } catch (error) {
    console.error('On-ramp creation error:', error);
    res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

// Get ramp status endpoint
app.get('/api/ramp/:id/status', async (req, res) => {
  try {
    const ramp = await killb.ramps.get(req.params.id);
    
    res.json({
      id: ramp.id,
      status: ramp.status,
      fromAmount: ramp.fromAmount,
      toAmount: ramp.toAmount,
      transferProof: ramp.transferProof
    });
  } catch (error) {
    res.status(404).json({ error: 'Ramp not found' });
  }
});

// Webhook endpoint
const webhookHandler = new WebhookHandler(process.env.WEBHOOK_SECRET!);

webhookHandler.on('ramp.completed', async (data) => {
  console.log('Ramp completed:', data.id);
  // Update your database
  await db.ramps.update(data.id, { status: 'COMPLETED' });
  // Notify user
  await sendEmail(data.userId, 'Transaction completed');
});

webhookHandler.on('ramp.failed', async (data) => {
  console.log('Ramp failed:', data.id);
  await handleRampFailure(data);
});

app.post('/webhooks/killb', async (req, res) => {
  try {
    await webhookHandler.handle(
      req.body,
      req.headers['x-signature-sha256'] as string
    );
    res.status(200).json({ received: true });
  } catch (error) {
    res.status(401).json({ error: 'Invalid signature' });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

React Integration

React hooks for KillB operations:
import { KillB } from '@killb/sdk';
import { useState, useEffect } from 'react';

// Initialize SDK (do this once at app level)
export const killb = new KillB({
  email: process.env.REACT_APP_KILLB_EMAIL!,
  password: process.env.REACT_APP_KILLB_PASSWORD!,
  environment: 'sandbox'
});

// Custom hook for on-ramp
function useOnRamp() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const createOnRamp = async (
    userId: string,
    walletAddress: string,
    amount: number
  ) => {
    setLoading(true);
    setError(null);

    try {
      // Get wallet or create
      const wallet = await killb.accounts.create({
        type: 'WALLET',
        userId,
        data: {
          /* wallet data */
          address: walletAddress,
          network: 'POLYGON',
          currency: 'USDC'
        }
      });

      // Get quote
      const quote = await killb.quotations.create({
        fromCurrency: 'COP',
        toCurrency: 'USDC',
        amount,
        amountIsToCurrency: false,
        cashInMethod: 'PSE',
        cashOutMethod: 'POLYGON'
      });

      // Create ramp
      const ramp = await killb.ramps.create({
        quotationId: quote.id,
        userId,
        accountId: wallet.id
      });

      setLoading(false);
      return ramp;
    } catch (err) {
      setError(err as Error);
      setLoading(false);
      throw err;
    }
  };

  return { createOnRamp, loading, error };
}

// Component usage
function OnRampWidget() {
  const { createOnRamp, loading, error } = useOnRamp();
  const [amount, setAmount] = useState('');
  const [paymentUrl, setPaymentUrl] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    try {
      const ramp = await createOnRamp(
        currentUser.killbUserId,
        currentUser.walletAddress,
        parseFloat(amount)
      );
      
      setPaymentUrl(ramp.paymentInfo[0].url);
    } catch (error) {
      console.error('Failed to create ramp:', error);
    }
  };

  if (paymentUrl) {
    return (
      <div>
        <h3>Complete Payment</h3>
        <a href={paymentUrl} target="_blank" rel="noopener">
          Pay with PSE
        </a>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="number"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
        placeholder="Amount in COP"
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Get Quote & Pay'}
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}

Next.js API Route

// pages/api/ramps/create.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { killb } from '@/lib/killb';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { userId, accountId, amount, fromCurrency, toCurrency } = req.body;

    // Validate inputs
    if (!userId || !accountId || !amount) {
      return res.status(400).json({ error: 'Missing required fields' });
    }

    // Create quotation
    const quote = await killb.quotations.create({
      fromCurrency,
      toCurrency,
      amount,
      amountIsToCurrency: false,
      cashInMethod: fromCurrency === 'COP' ? 'PSE' : 'SPEI',
      cashOutMethod: 'POLYGON'
    });

    // Create ramp
    const ramp = await killb.ramps.create({
      quotationId: quote.id,
      userId,
      accountId,
      externalId: `nextjs-${Date.now()}`
    });

    res.status(201).json({
      success: true,
      rampId: ramp.id,
      paymentUrl: ramp.paymentInfo[0].url,
      rate: quote.rate,
      expectedAmount: quote.toAmount
    });

  } catch (error: any) {
    console.error('Ramp creation failed:', error);
    res.status(500).json({
      success: false,
      error: error.message,
      code: error.code
    });
  }
}

Batch Operations

Process multiple operations efficiently:
import { KillB } from '@killb/sdk';

async function batchCreateUsers(usersData: any[]) {
  const killb = new KillB(config);
  await killb.initialize();

  const results = await Promise.allSettled(
    usersData.map(data => killb.users.create({
      type: 'PERSON',
      externalId: data.id,
      data: data
    }))
  );

  const successful = results.filter(r => r.status === 'fulfilled');
  const failed = results.filter(r => r.status === 'rejected');

  console.log(`✅ Created: ${successful.length}`);
  console.log(`❌ Failed: ${failed.length}`);

  return {
    successful: successful.map(r => (r as any).value),
    failed: failed.map(r => (r as any).reason)
  };
}

// Get balances for multiple accounts
async function getAllBalances(accountIds: string[]) {
  const balances = await Promise.all(
    accountIds.map(id => killb.savings.getBalance(id))
  );

  const total = balances.reduce((sum, b) => sum + parseFloat(b.amount), 0);
  
  return {
    accounts: balances,
    totalUSD: total
  };
}

Error Recovery

Implement robust error handling:
import { 
  KillB, 
  QuotationExpiredError, 
  InsufficientBalanceError,
  UserNotFoundError 
} from '@killb/sdk';

async function createRampWithRetry(
  quote: Quotation,
  userId: string,
  accountId: string,
  maxRetries = 3
) {
  let attempt = 0;

  while (attempt < maxRetries) {
    try {
      return await killb.ramps.create({
        quotationId: quote.id,
        userId,
        accountId
      });
    } catch (error) {
      attempt++;

      if (error instanceof QuotationExpiredError) {
        console.log('Quote expired, getting new quote...');
        // Get new quote and retry
        const newQuote = await killb.quotations.create({
          fromCurrency: quote.fromCurrency,
          toCurrency: quote.toCurrency,
          amount: quote.fromAmount,
          amountIsToCurrency: false,
          cashInMethod: quote.cashInMethod,
          cashOutMethod: quote.cashOutMethod
        });
        quote = newQuote;
        continue;
      }

      if (error instanceof InsufficientBalanceError) {
        console.error('Insufficient balance, cannot retry');
        throw error;
      }

      if (attempt >= maxRetries) {
        throw error;
      }

      // Exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Retry ${attempt}/${maxRetries} after ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Monitoring & Logging

Add comprehensive logging:
import { KillB } from '@killb/sdk';
import winston from 'winston';

// Setup logger
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'killb.log' })
  ]
});

// Initialize SDK with logging
const killb = new KillB({
  email: process.env.KILLB_EMAIL!,
  password: process.env.KILLB_PASSWORD!,
  debug: true,
  logger: {
    log: (message: string) => logger.info(message),
    error: (message: string) => logger.error(message)
  }
});

// Wrap SDK calls with metrics
async function createRampWithMetrics(data: any) {
  const startTime = Date.now();
  
  try {
    const ramp = await killb.ramps.create(data);
    
    const duration = Date.now() - startTime;
    logger.info('Ramp created', {
      rampId: ramp.id,
      duration,
      userId: data.userId
    });
    
    return ramp;
  } catch (error: any) {
    const duration = Date.now() - startTime;
    logger.error('Ramp creation failed', {
      error: error.message,
      code: error.code,
      duration,
      userId: data.userId
    });
    
    throw error;
  }
}

Testing

Unit tests with SDK:
import { KillB } from '@killb/sdk';
import { describe, it, expect, beforeAll } from '@jest/globals';

describe('KillB SDK Integration', () => {
  let killb: KillB;
  let testUser: any;

  beforeAll(async () => {
    killb = new KillB({
      email: process.env.KILLB_TEST_EMAIL!,
      password: process.env.KILLB_TEST_PASSWORD!,
      environment: 'sandbox'
    });
    
    await killb.initialize();
  });

  it('should create a user', async () => {
    const user = await killb.users.create({
      type: 'PERSON',
      externalId: `test-${Date.now()}`,
      data: {
        firstName: 'Test',
        lastName: 'User',
        email: '[email protected]',
        // ... other required fields
      }
    });

    expect(user.id).toBeDefined();
    expect(user.status).toBe('ACTIVE');
    
    testUser = user;
  });

  it('should create a quotation', async () => {
    const quote = await killb.quotations.create({
      fromCurrency: 'COP',
      toCurrency: 'USDC',
      amount: 100000,
      amountIsToCurrency: false,
      cashInMethod: 'PSE',
      cashOutMethod: 'POLYGON'
    });

    expect(quote.id).toBeDefined();
    expect(quote.rate).toBeGreaterThan(0);
    expect(quote.toAmount).toBeGreaterThan(0);
  });

  it('should complete full on-ramp flow', async () => {
    // ... full flow test
  });
});

Next Steps