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-payinstalled (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 recordThe agent decides when to pay; PQSafe enforces how much, to whom, and via which rail.
Step-by-step
-
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 envelopecurrency: 'USD',allowedRails: ['airwallex', 'wise'],allowedRecipients: ['anthropic.com','openai.com','cloudflare.com',],validUntil: new Date(Date.now() + 24 * 3_600_000), // 24hrequireApproval: true,approvalThreshold: 100, // Require approval for payments > $100memo: 'Procurement agent — AI infrastructure costs',})// Sign once; reuse for all payments within this sessionconst signedEnvelope = createSignedEnvelope(envelope, secretKey) -
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.comamount 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 ledgerconst 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}`}},}) -
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',}) -
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 > $100const 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 error | Cause | Resolution |
|---|---|---|
ENVELOPE_EXPIRED | validUntil has passed | Re-issue envelope with new expiry |
AMOUNT_EXCEEDS_ENVELOPE | amount > maxAmount | Reduce payment amount or re-issue with higher cap |
RECIPIENT_NOT_ALLOWED | recipient not in allowedRecipients | Update allowlist in envelope definition |
RAIL_NOT_ALLOWED | Requested rail not in allowedRails | Add rail to envelope or switch rail |
APPROVAL_REQUIRED | Payment above threshold, no approval | Wire Telegram bot or reduce amount |
ENVELOPE_SIGNATURE_INVALID | Envelope was modified after signing | Re-sign with createSignedEnvelope() |
Next steps
- CrewAI Integration Guide — multi-agent procurement workflows
- Telegram Approval Gate recipe — add human oversight
- Pay Anthropic Credits recipe — production pattern for this use case
- SpendEnvelope concept — understand envelope fields and invariants