LangGraph Integration

The @shade402/langgraph package provides payment nodes for LangGraph workflows, enabling agents to make X402 payments as part of their execution graph.

Installation

pnpm add @shade402/langgraph @shade402/client @shade402/core @langchain/langgraph

Quick Start

import { StateGraph } from '@langchain/langgraph';
import { fetchWithPaymentNode } from '@shade402/langgraph';
import { Keypair } from '@solana/web3.js';
import { PaymentState } from '@shade402/langgraph';

const workflow = new StateGraph<PaymentState>({
  channels: {
    api_url: { reducer: (x, y) => y ?? x },
    api_response: { reducer: (x, y) => y ?? x },
    wallet_keypair: { reducer: (x, y) => y ?? x },
  },
});

workflow.addNode('fetch_api', fetchWithPaymentNode);
workflow.setEntryPoint('fetch_api');

const app = workflow.compile();

const result = await app.invoke({
  api_url: 'https://api.example.com/premium-data',
  wallet_keypair: Keypair.generate(),
});

Payment Nodes

fetchWithPaymentNode

Combined node that fetches API and handles payment automatically:

import { fetchWithPaymentNode } from '@shade402/langgraph';

workflow.addNode('fetch_api', fetchWithPaymentNode);

This node:

  1. Makes HTTP request to state.api_url

  2. If 402 received, automatically creates payment

  3. Retries request with payment

  4. Returns API response in state.api_response

paymentNode

Separate payment node for more control:

import { paymentNode } from '@shade402/langgraph';

workflow.addNode('make_payment', paymentNode);

This node:

  1. Expects state.payment_required to be true

  2. Makes payment for state.api_url

  3. Updates state.api_response and state.payment_completed

Payment State

The PaymentState interface defines the state structure:

interface PaymentState {
  wallet_keypair?: Keypair;        // Wallet for payments
  api_url?: string;                // URL to fetch
  api_response?: string;           // API response (JSON string)
  payment_completed?: boolean;     // Payment completion status
  payment_error?: string | null;   // Payment error message
  payment_required?: boolean;      // Whether payment is required
  max_payment_amount?: string;     // Maximum payment amount
  http_method?: string;            // HTTP method (default: 'GET')
  allow_local?: boolean;           // Allow localhost URLs
  rpc_url?: string;                // Solana RPC URL
  [key: string]: any;              // Additional state fields
}

Workflow Patterns

Simple Payment Flow

import { StateGraph } from '@langchain/langgraph';
import { fetchWithPaymentNode } from '@shade402/langgraph';
import { PaymentState } from '@shade402/langgraph';

const workflow = new StateGraph<PaymentState>({
  channels: {
    api_url: { reducer: (x, y) => y ?? x },
    api_response: { reducer: (x, y) => y ?? x },
    wallet_keypair: { reducer: (x, y) => y ?? x },
    max_payment_amount: { reducer: (x, y) => y ?? x },
  },
});

workflow.addNode('fetch_api', fetchWithPaymentNode);
workflow.addNode('process', async (state: PaymentState) => {
  const data = JSON.parse(state.api_response || '{}');
  return { ...state, processed: data };
});

workflow.setEntryPoint('fetch_api');
workflow.addEdge('fetch_api', 'process');

const app = workflow.compile();

Conditional Payment Flow

import { StateGraph } from '@langchain/langgraph';
import { paymentNode, checkPaymentRequired, checkPaymentCompleted } from '@shade402/langgraph';
import { PaymentState } from '@shade402/langgraph';

const workflow = new StateGraph<PaymentState>({
  channels: {
    api_url: { reducer: (x, y) => y ?? x },
    api_response: { reducer: (x, y) => y ?? x },
    wallet_keypair: { reducer: (x, y) => y ?? x },
    payment_required: { reducer: (x, y) => y ?? x },
    payment_completed: { reducer: (x, y) => y ?? x },
  },
});

// Initial fetch node
workflow.addNode('fetch_api', async (state: PaymentState) => {
  // Make initial request
  const response = await fetch(state.api_url!);
  
  if (response.status === 402) {
    return { ...state, payment_required: true };
  }
  
  const data = await response.json();
  return { ...state, api_response: JSON.stringify(data) };
});

// Payment node
workflow.addNode('make_payment', paymentNode);

// Process node
workflow.addNode('process', async (state: PaymentState) => {
  const data = JSON.parse(state.api_response || '{}');
  return { ...state, processed: data };
});

// Error handler
workflow.addNode('error_handler', async (state: PaymentState) => {
  return { ...state, error: state.payment_error };
});

// Edges
workflow.setEntryPoint('fetch_api');

workflow.addConditionalEdges(
  'fetch_api',
  checkPaymentRequired,
  {
    payment_required: 'make_payment',
    success: 'process',
    error: 'error_handler',
  }
);

workflow.addConditionalEdges(
  'make_payment',
  checkPaymentCompleted,
  {
    completed: 'process',
    failed: 'error_handler',
  }
);

const app = workflow.compile();

Multiple API Calls

const workflow = new StateGraph<PaymentState>({
  channels: {
    api_urls: { reducer: (x, y) => y ?? x },
    api_responses: { reducer: (x, y) => y ?? x },
    wallet_keypair: { reducer: (x, y) => y ?? x },
  },
});

// Fetch multiple APIs
workflow.addNode('fetch_apis', async (state: PaymentState) => {
  const urls = state.api_urls || [];
  const responses: string[] = [];
  
  for (const url of urls) {
    const nodeState = {
      ...state,
      api_url: url,
    };
    
    const result = await fetchWithPaymentNode(nodeState);
    responses.push(result.api_response || '');
  }
  
  return { ...state, api_responses: responses };
});

workflow.setEntryPoint('fetch_apis');

Conditional Functions

checkPaymentRequired

Routes based on whether payment is required:

import { checkPaymentRequired } from '@shade402/langgraph';

workflow.addConditionalEdges(
  'fetch_api',
  checkPaymentRequired,
  {
    payment_required: 'make_payment',
    success: 'process',
    error: 'error_handler',
  }
);

Returns:

  • 'payment_required': If state.payment_required is true

  • 'success': If state.api_response exists

  • 'error': Otherwise

checkPaymentCompleted

Routes based on payment completion:

import { checkPaymentCompleted } from '@shade402/langgraph';

workflow.addConditionalEdges(
  'make_payment',
  checkPaymentCompleted,
  {
    completed: 'process',
    failed: 'error_handler',
  }
);

Returns:

  • 'completed': If state.payment_completed is true

  • 'failed': Otherwise

Custom Conditional Functions

function shouldRetryPayment(state: PaymentState): string {
  if (state.payment_error && state.payment_error.includes('INSUFFICIENT_FUNDS')) {
    return 'error'; // Don't retry insufficient funds
  }
  
  if (state.payment_error) {
    return 'retry'; // Retry other errors
  }
  
  return 'continue';
}

workflow.addConditionalEdges(
  'make_payment',
  shouldRetryPayment,
  {
    continue: 'process',
    retry: 'make_payment',
    error: 'error_handler',
  }
);

Complete Example

import { StateGraph } from '@langchain/langgraph';
import { fetchWithPaymentNode, PaymentState } from '@shade402/langgraph';
import { Keypair } from '@solana/web3.js';

interface MyState extends PaymentState {
  query: string;
  result?: any;
}

const workflow = new StateGraph<MyState>({
  channels: {
    query: { reducer: (x, y) => y ?? x },
    api_url: { reducer: (x, y) => y ?? x },
    api_response: { reducer: (x, y) => y ?? x },
    wallet_keypair: { reducer: (x, y) => y ?? x },
    max_payment_amount: { reducer: (x, y) => y ?? x },
    result: { reducer: (x, y) => y ?? x },
  },
});

// Fetch API
workflow.addNode('fetch_api', fetchWithPaymentNode);

// Process response
workflow.addNode('process', async (state: MyState) => {
  const data = JSON.parse(state.api_response || '{}');
  return {
    ...state,
    result: {
      query: state.query,
      data: data,
      paymentId: data.paymentId,
    },
  };
});

// Error handler
workflow.addNode('error_handler', async (state: MyState) => {
  return {
    ...state,
    result: {
      error: state.payment_error,
      query: state.query,
    },
  };
});

workflow.setEntryPoint('fetch_api');

workflow.addConditionalEdges(
  'fetch_api',
  (state: MyState) => {
    if (state.payment_error) return 'error';
    if (state.api_response) return 'success';
    return 'error';
  },
  {
    success: 'process',
    error: 'error_handler',
  }
);

const app = workflow.compile();

// Run workflow
const wallet = Keypair.generate();
const result = await app.invoke({
  query: 'Analyze this data',
  api_url: 'https://api.example.com/analyze',
  wallet_keypair: wallet,
  max_payment_amount: '1.0',
  rpc_url: process.env.SOLANA_RPC_URL,
});

console.log(result.result);

Best Practices

  1. Define clear state structure

  2. Handle errors appropriately

  3. Set maximum payment amounts

  4. Use conditional edges for flow control

  5. Process API responses in separate nodes

  6. Monitor payment usage

  7. Log payment transactions

  8. Test with devnet before production

  9. Use secure wallet storage

  10. Consider rate limiting

Next Steps

Last updated