Skip to main content

SDK Examples

Complete examples for common integration patterns.

Basic Payment

import { Conto } from '@conto/sdk';

const conto = new Conto({
  apiKey: process.env.CONTO_API_KEY!
});

// Simple one-step payment
const result = await conto.payments.pay({
  amount: 50.00,
  recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f...',
  recipientName: 'OpenAI',
  purpose: 'GPT-4 API credits',
  category: 'AI_SERVICES'
});

console.log('TX Hash:', result.txHash);

Two-Step Payment with Approval Handling

import { Conto, ContoError } from '@conto/sdk';

async function makePayment(params: {
  amount: number;
  recipient: string;
  purpose: string;
}) {
  const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });

  // Step 1: Request authorization
  const request = await conto.payments.request({
    amount: params.amount,
    recipientAddress: params.recipient,
    purpose: params.purpose
  });

  // Step 2: Handle different statuses
  switch (request.status) {
    case 'APPROVED':
      const result = await conto.payments.execute(request.requestId);
      return { success: true, txHash: result.txHash };

    case 'DENIED':
      return {
        success: false,
        error: 'Payment denied',
        reasons: request.reasons,
        violations: request.violations
      };

    case 'REQUIRES_APPROVAL':
      return {
        success: false,
        pending: true,
        requestId: request.requestId,
        message: 'Awaiting manual approval'
      };
  }
}

LangChain Tool Integration

import { DynamicTool } from 'langchain/tools';
import { Conto, ContoError } from '@conto/sdk';

const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });

export const paymentTool = new DynamicTool({
  name: 'make_payment',
  description: `Make a stablecoin payment to a recipient.
    Input: JSON with amount, recipientAddress, recipientName, purpose.
    Returns: Transaction details or error message.`,

  func: async (input: string) => {
    try {
      const params = JSON.parse(input);

      const result = await conto.payments.pay({
        amount: params.amount,
        recipientAddress: params.recipientAddress,
        recipientName: params.recipientName,
        purpose: params.purpose,
        category: params.category || 'OPERATIONS'
      });

      return JSON.stringify({
        success: true,
        transactionHash: result.txHash,
        amount: result.amount,
        explorerUrl: result.explorerUrl
      });

    } catch (error) {
      if (error instanceof ContoError) {
        return JSON.stringify({
          success: false,
          error: error.message,
          code: error.code
        });
      }
      return JSON.stringify({
        success: false,
        error: 'Unexpected error'
      });
    }
  }
});

OpenAI Function Calling

import OpenAI from 'openai';
import { Conto } from '@conto/sdk';

const openai = new OpenAI();
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });

// Define the function
const paymentFunction = {
  name: 'make_payment',
  description: 'Make a stablecoin payment to a vendor or service',
  parameters: {
    type: 'object',
    properties: {
      amount: { type: 'number', description: 'Amount in USDC' },
      recipientAddress: { type: 'string', description: 'Ethereum address' },
      recipientName: { type: 'string', description: 'Recipient name' },
      purpose: { type: 'string', description: 'Payment purpose' }
    },
    required: ['amount', 'recipientAddress', 'purpose']
  }
};

// Handle function calls
async function handleFunctionCall(name: string, args: any) {
  if (name === 'make_payment') {
    const result = await conto.payments.pay({
      amount: args.amount,
      recipientAddress: args.recipientAddress,
      recipientName: args.recipientName,
      purpose: args.purpose
    });
    return { success: true, txHash: result.txHash };
  }
}

Express API Endpoint

import express from 'express';
import { Conto, ContoError } from '@conto/sdk';

const app = express();
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });

app.post('/api/payments', async (req, res) => {
  try {
    const { amount, recipientAddress, purpose } = req.body;

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

    // Make payment
    const result = await conto.payments.pay({
      amount,
      recipientAddress,
      purpose
    });

    res.json({
      success: true,
      transactionId: result.transactionId,
      txHash: result.txHash,
      explorerUrl: result.explorerUrl
    });

  } catch (error) {
    if (error instanceof ContoError) {
      res.status(error.status).json({
        success: false,
        error: error.message,
        code: error.code
      });
    } else {
      res.status(500).json({
        success: false,
        error: 'Internal server error'
      });
    }
  }
});

Python SDK Wrapper

import requests
from typing import Optional, Dict, Any

class ContoSDK:
    def __init__(self, api_key: str, base_url: str = "https://conto.finance"):
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }

    def request_payment(
        self,
        amount: float,
        recipient_address: str,
        recipient_name: Optional[str] = None,
        purpose: Optional[str] = None,
        category: Optional[str] = None
    ) -> Dict[str, Any]:
        payload = {
            "amount": amount,
            "recipientAddress": recipient_address
        }
        if recipient_name:
            payload["recipientName"] = recipient_name
        if purpose:
            payload["purpose"] = purpose
        if category:
            payload["category"] = category

        response = requests.post(
            f"{self.base_url}/api/sdk/payments/request",
            headers=self.headers,
            json=payload
        )
        response.raise_for_status()
        return response.json()

    def execute_payment(self, request_id: str) -> Dict[str, Any]:
        response = requests.post(
            f"{self.base_url}/api/sdk/payments/{request_id}/execute",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def pay(self, amount: float, recipient_address: str, **kwargs) -> Dict[str, Any]:
        request = self.request_payment(amount, recipient_address, **kwargs)

        if request["status"] != "APPROVED":
            raise Exception(f"Payment denied: {request['reasons']}")

        return self.execute_payment(request["requestId"])


# Usage
conto = ContoSDK("conto_agent_xxx")

result = conto.pay(
    amount=50.00,
    recipient_address="0x1234...",
    recipient_name="OpenAI",
    purpose="API credits"
)

print(f"Transaction hash: {result['txHash']}")

Single-Call Payment with autoExecute

Skip the two-step flow entirely by using autoExecute: true — request authorization and execute the payment in one API call.
const response = await fetch('https://conto.finance/api/sdk/payments/request', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.CONTO_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    amount: 50.00,
    recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f...',
    recipientName: 'OpenAI',
    purpose: 'GPT-4 API credits',
    category: 'AI_SERVICES',
    autoExecute: true
  })
});

const result = await response.json();

switch (result.status) {
  case 'EXECUTED':
    console.log('Payment complete in one call!');
    console.log('TX Hash:', result.execution.txHash);
    console.log('Explorer:', result.execution.explorerUrl);
    console.log('Currency:', result.currency);
    console.log('Chain:', result.chain.chainName);
    break;

  case 'APPROVED':
    if (result.autoExecuteError) {
      // Auto-execute failed, fall back to manual execute
      console.log('Auto-execute failed:', result.autoExecuteError);
      const execResult = await fetch(result.executeUrl, {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${process.env.CONTO_API_KEY}` }
      }).then(r => r.json());
      console.log('Manual execute TX:', execResult.txHash);
    } else {
      // autoExecute was silently ignored (missing payments:execute scope)
      // Execute manually
      const execResult = await fetch(result.executeUrl, {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${process.env.CONTO_API_KEY}` }
      }).then(r => r.json());
    }
    break;

  case 'DENIED':
    console.log('Payment denied:', result.reasons);
    if (result.context?.nextSteps) {
      console.log('Suggested actions:', result.context.nextSteps);
    }
    break;

  case 'REQUIRES_APPROVAL':
    console.log('Awaiting manual approval');
    break;
}

Agent Bootstrap with Setup Endpoint

Call GET /api/sdk/setup on startup to understand your agent’s full configuration — wallets, policies, counterparties, and capabilities.
async function bootstrapAgent() {
  const config = await fetch('https://conto.finance/api/sdk/setup', {
    headers: { 'Authorization': `Bearer ${process.env.CONTO_API_KEY}` }
  }).then(r => r.json());

  console.log('Agent:', config.agent.name, `(${config.agent.status})`);
  console.log('Scopes:', config.scopes.join(', '));

  // Check capabilities
  if (config.capabilities.canAutoExecute) {
    console.log('Can auto-execute payments (single-call)');
  }
  console.log('Max single payment:', config.capabilities.maxSinglePayment);
  console.log('Remaining daily budget:', config.capabilities.remainingDailyBudget);

  // List wallets
  for (const wallet of config.wallets) {
    console.log(`Wallet ${wallet.address} (${wallet.custodyType})`);
    console.log(`  Chain: ${wallet.chainName} | Balance: ${wallet.balance} ${wallet.currency}`);
    console.log(`  Executable: ${wallet.isExecutable}`);
    console.log(`  Limits: $${wallet.limits.perTransaction}/tx, $${wallet.limits.daily}/day`);
  }

  // List known counterparties
  for (const cp of config.counterparties) {
    console.log(`Counterparty: ${cp.name} (${cp.trustLevel}) - ${cp.transactionCount} txns`);
  }

  return config;
}

Programmatic Counterparty Creation

Agents can create counterparties before making payments to new recipients. This avoids “unknown counterparty” policy denials.
async function ensureCounterparty(address: string, name: string, category?: string) {
  const response = await fetch('https://conto.finance/api/sdk/counterparties', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CONTO_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name,
      address,
      type: 'VENDOR',
      category: category || 'OPERATIONS'
    })
  });

  const counterparty = await response.json();
  console.log(`Counterparty ${counterparty.created ? 'created' : 'updated'}: ${counterparty.name}`);
  console.log(`Trust: ${counterparty.trustLevel} (${counterparty.trustScore})`);
  return counterparty;
}

// Usage: create counterparty then pay
await ensureCounterparty('0x1234...', 'Anthropic', 'AI_SERVICES');
const result = await conto.payments.pay({
  amount: 100,
  recipientAddress: '0x1234...',
  purpose: 'Claude API credits'
});

Requesting Policy Exceptions

When a payment is denied by policy, agents can request an exception for human review instead of failing silently.
async function requestException(type: string, reason: string, details?: object) {
  const response = await fetch('https://conto.finance/api/sdk/policies/exceptions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CONTO_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type,       // ADD_TO_WHITELIST, INCREASE_SPEND_LIMIT, etc.
      reason,
      urgency: 'NORMAL',
      details
    })
  });

  const exception = await response.json();
  console.log(`Exception ${exception.exceptionId} submitted (${exception.status})`);
  return exception;
}

// Example: payment denied, request whitelist addition
const request = await conto.payments.request({
  amount: 100,
  recipientAddress: '0xNewVendor...',
  purpose: 'New service subscription'
});

if (request.status === 'DENIED') {
  const hasWhitelistViolation = request.violations?.some(
    v => v.type === 'WHITELIST_VIOLATION'
  );

  if (hasWhitelistViolation) {
    await requestException('ADD_TO_WHITELIST', 'Need to pay new vendor for service subscription', {
      counterpartyAddress: '0xNewVendor...',
      counterpartyName: 'New Service Inc.',
      amount: 100
    });
  }
}

Batch Payments

import { Conto } from '@conto/sdk';

interface PaymentItem {
  recipient: string;
  amount: number;
  purpose: string;
}

async function batchPayments(items: PaymentItem[]) {
  const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
  const results = [];

  for (const item of items) {
    try {
      const result = await conto.payments.pay({
        amount: item.amount,
        recipientAddress: item.recipient,
        purpose: item.purpose
      });

      results.push({
        recipient: item.recipient,
        success: true,
        txHash: result.txHash
      });

    } catch (error) {
      results.push({
        recipient: item.recipient,
        success: false,
        error: error.message
      });
    }

    // Rate limit: wait between requests
    await new Promise(r => setTimeout(r, 500));
  }

  return results;
}

Webhook Handler (Approval Notifications)

import express from 'express';
import { Conto } from '@conto/sdk';

const app = express();
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });

// Store pending payments
const pendingPayments = new Map<string, {
  amount: number;
  recipient: string;
  resolve: (value: any) => void;
}>();

// Request payment and wait for approval
async function requestPaymentWithApproval(params: {
  amount: number;
  recipient: string;
  purpose: string;
}) {
  const request = await conto.payments.request({
    amount: params.amount,
    recipientAddress: params.recipient,
    purpose: params.purpose
  });

  if (request.status === 'APPROVED') {
    return conto.payments.execute(request.requestId);
  }

  if (request.status === 'REQUIRES_APPROVAL') {
    // Wait for webhook callback
    return new Promise((resolve) => {
      pendingPayments.set(request.requestId, {
        amount: params.amount,
        recipient: params.recipient,
        resolve
      });
    });
  }

  throw new Error(`Payment denied: ${request.reasons.join(', ')}`);
}

// Webhook endpoint (called when payment is approved)
app.post('/webhooks/conto', async (req, res) => {
  const { event, requestId } = req.body;

  if (event === 'payment.approved') {
    const pending = pendingPayments.get(requestId);

    if (pending) {
      const result = await conto.payments.execute(requestId);
      pending.resolve(result);
      pendingPayments.delete(requestId);
    }
  }

  res.json({ received: true });
});

MCP Server Configuration

For Claude Desktop integration:
{
  "mcpServers": {
    "conto": {
      "command": "npx",
      "args": ["conto-mcp-server"],
      "env": {
        "CONTO_API_KEY": "conto_agent_xxx..."
      }
    }
  }
}
Then Claude can make payments naturally:
“Pay $50 to 0x123… for API credits”

Next Steps