Skip to content

LangChain Integration Guide

This guide shows you how to integrate PQSafe AgentPay into a LangChain agent as a DynamicTool. The agent gains the ability to make post-quantum-authorized payments to any configured recipient — within the strict bounds of its spend envelope.

Prerequisites

  • Node.js ≥ 18
  • @pqsafe/agent-pay installed (installation)
  • LangChain packages installed:
    Terminal window
    npm install langchain @langchain/anthropic @langchain/core
  • A PQSafe issuer key (PQSAFE_ISSUER_KEY) in your .env
  • At least one configured payment rail (e.g. AIRWALLEX_CLIENT_ID + AIRWALLEX_API_KEY)

Architecture

LangChain Agent
└── AgentExecutor
└── tools: [pqsafePayTool]
createSignedEnvelope() ← ML-DSA-65 signature
executeAgentPayment() ← rail dispatch (Airwallex, Wise, etc.)
submitToLedger() ← immutable audit record

The agent decides when to pay; PQSafe enforces how much, to whom, and via which rail.

Step-by-step

  1. Generate a key pair and create a spend envelope

    Envelope creation happens at agent setup time — not inside the tool function. This ensures the cryptographic bounds are fixed before the agent starts running.

    import {
    generateKeyPair,
    createSpendEnvelope,
    createSignedEnvelope,
    } from '@pqsafe/agent-pay'
    const { publicKey, secretKey } = await generateKeyPair()
    const envelope = createSpendEnvelope({
    agentId: 'langchain-procurement-agent',
    maxAmount: 500, // Hard cap: $500 per envelope
    currency: 'USD',
    allowedRails: ['airwallex', 'wise'],
    allowedRecipients: [
    'anthropic.com',
    'openai.com',
    'cloudflare.com',
    ],
    validUntil: new Date(Date.now() + 24 * 3_600_000), // 24h
    requireApproval: true,
    approvalThreshold: 100, // Require approval for payments > $100
    memo: 'Procurement agent — AI infrastructure costs',
    })
    // Sign once; reuse for all payments within this session
    const signedEnvelope = createSignedEnvelope(envelope, secretKey)
  2. Build the PQSafe payment tool

    import { DynamicStructuredTool } from 'langchain/tools'
    import { z } from 'zod'
    import {
    executeAgentPayment,
    buildLedgerRecord,
    submitToLedger,
    } from '@pqsafe/agent-pay'
    const pqsafePayTool = new DynamicStructuredTool({
    name: 'pqsafe_pay',
    description: `Execute a cryptographically authorized payment via PQSafe AgentPay.
    Use this to pay vendors, top up API credits, or settle invoices.
    recipient must be one of: anthropic.com, openai.com, cloudflare.com
    amount must be ≤ 500 USD`,
    schema: z.object({
    recipient: z.string().describe('Payment recipient domain or address'),
    amount: z.number().positive().describe('Amount in USD'),
    memo: z.string().describe('Payment memo — appears in ledger and bank reference'),
    }),
    func: async ({ recipient, amount, memo }) => {
    try {
    const result = await executeAgentPayment(signedEnvelope, {
    recipient,
    amount,
    memo,
    })
    // Append to immutable ledger
    const record = buildLedgerRecord(signedEnvelope, result)
    const ledgerEntry = await submitToLedger(record)
    return JSON.stringify({
    status: result.status,
    txId: result.txId,
    rail: result.rail,
    ledgerHash: ledgerEntry.hash,
    amount,
    recipient,
    })
    } catch (error: unknown) {
    const message = error instanceof Error ? error.message : String(error)
    return `Payment failed: ${message}`
    }
    },
    })
  3. Wire the agent

    import { ChatAnthropic } from '@langchain/anthropic'
    import { AgentExecutor, createToolCallingAgent } from 'langchain/agents'
    import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts'
    import { HumanMessage } from '@langchain/core/messages'
    const llm = new ChatAnthropic({
    model: 'claude-opus-4-5',
    apiKey: process.env.ANTHROPIC_API_KEY,
    })
    const prompt = ChatPromptTemplate.fromMessages([
    [
    'system',
    `You are an autonomous procurement agent with payment capabilities.
    You can make payments on behalf of the engineering team using PQSafe's post-quantum-authorized payment system.
    All payments are cryptographically bounded — you cannot exceed the envelope limits.
    Always include a clear memo explaining the business purpose of each payment.`,
    ],
    new MessagesPlaceholder('chat_history'),
    ['human', '{input}'],
    new MessagesPlaceholder('agent_scratchpad'),
    ])
    const agent = await createToolCallingAgent({
    llm,
    tools: [pqsafePayTool],
    prompt,
    })
    const executor = new AgentExecutor({
    agent,
    tools: [pqsafePayTool],
    verbose: process.env.NODE_ENV === 'development',
    })
  4. Run the agent

    const result = await executor.invoke({
    input: 'Top up our Anthropic API credits by $50. Our balance is running low.',
    chat_history: [],
    })
    console.log(result.output)

Complete runnable example

import {
generateKeyPair,
createSpendEnvelope,
createSignedEnvelope,
executeAgentPayment,
buildLedgerRecord,
submitToLedger,
} from '@pqsafe/agent-pay'
import { DynamicStructuredTool } from 'langchain/tools'
import { z } from 'zod'
import { ChatAnthropic } from '@langchain/anthropic'
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents'
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts'
async function main() {
// 1. Setup: key pair + envelope (done once at startup)
const { secretKey } = await generateKeyPair()
const signedEnvelope = createSignedEnvelope(
createSpendEnvelope({
agentId: 'langchain-agent',
maxAmount: 500,
currency: 'USD',
allowedRails: ['airwallex'],
allowedRecipients: ['anthropic.com', 'openai.com'],
validUntil: new Date(Date.now() + 3_600_000),
}),
secretKey
)
// 2. Tool
const payTool = new DynamicStructuredTool({
name: 'pqsafe_pay',
description: 'Make a PQSafe-authorized payment. recipient: anthropic.com | openai.com. amount: USD.',
schema: z.object({
recipient: z.string(),
amount: z.number(),
memo: z.string(),
}),
func: async ({ recipient, amount, memo }) => {
const result = await executeAgentPayment(signedEnvelope, { recipient, amount, memo })
await submitToLedger(buildLedgerRecord(signedEnvelope, result))
return `Payment ${result.status}. TX: ${result.txId}. Rail: ${result.rail}.`
},
})
// 3. Agent
const llm = new ChatAnthropic({ model: 'claude-opus-4-5' })
const prompt = ChatPromptTemplate.fromMessages([
['system', 'You are an autonomous procurement agent. Make payments as instructed.'],
['human', '{input}'],
new MessagesPlaceholder('agent_scratchpad'),
])
const agent = await createToolCallingAgent({ llm, tools: [payTool], prompt })
const executor = new AgentExecutor({ agent, tools: [payTool] })
// 4. Run
const result = await executor.invoke({
input: 'Pay $25 to Anthropic for API credits.',
})
console.log(result.output)
}
main().catch(console.error)

Approval gates with LangChain

If your envelope has requireApproval: true, payments above approvalThreshold will pause and wait for human approval. The executeAgentPayment() call blocks until the approval is resolved (or the envelope expires).

// Envelope with Telegram approval for payments > $100
const envelope = createSpendEnvelope({
agentId: 'langchain-agent',
maxAmount: 500,
currency: 'USD',
allowedRails: ['airwallex'],
allowedRecipients: ['anthropic.com'],
validUntil: new Date(Date.now() + 3_600_000),
requireApproval: true,
approvalThreshold: 100, // Auto-approve ≤ $100; require approval > $100
})

See the Telegram Approval Gate recipe for full wiring instructions.

Error handling reference

PQSafe errorCauseResolution
ENVELOPE_EXPIREDvalidUntil has passedRe-issue envelope with new expiry
AMOUNT_EXCEEDS_ENVELOPEamount > maxAmountReduce payment amount or re-issue with higher cap
RECIPIENT_NOT_ALLOWEDrecipient not in allowedRecipientsUpdate allowlist in envelope definition
RAIL_NOT_ALLOWEDRequested rail not in allowedRailsAdd rail to envelope or switch rail
APPROVAL_REQUIREDPayment above threshold, no approvalWire Telegram bot or reduce amount
ENVELOPE_SIGNATURE_INVALIDEnvelope was modified after signingRe-sign with createSignedEnvelope()

Next steps