Интеграция платежей LangGraph.
Пакет LangGraph не нужен. Обычный узел вызывает реальный клиент blockchain0x, interrupt() приостанавливается до тех пор, пока вебхук платежа не поступит, и граф возобновляется - устойчивый между запусками.
Специального пакета для LangGraph нет, и он вам не нужен. Обычный node в вашем вызывает реальный client , приостанавливает graph, пока не придет payment webhook, а обработчик возобновляет его с помощью собственных + LangGraph. Платежи завершаются в Base.
Надежное выполнение делает паттерн возобновления после платежа естественным.
Сложная часть платежей, управляемых агентами, - это асинхронный разрыв: вы запрашиваете платеж, покупатель платит через несколько минут (или никогда), и вам нужно продолжить работу, не теряя состояние того, где вы были. Большинство фреймов агентов полагаются на очередь задач + ручное хранилище состояния, чтобы преодолеть этот разрыв. LangGraph уже имеет встроенный примитив: interrupt() приостанавливает граф на узле, а контрольная точка сохраняет состояние на диск. Возобновление - это один вызов.
Рецепт использует это без какой-либо специальной механики. Обычный pay node вызывает blockchain0x.payments.create. Узел await_settlement вызывает interrupt(), чтобы приостановиться, а checkpointer сохраняет состояние. Обработчик webhook проверяет подпись, устанавливает settled в соответствующем thread через graph.updateState и вызывает graph.invoke, чтобы продолжить с места прерывания. Graph продолжает ровно с того места, где остановился, включая любой LLM context из предыдущих узлов - все через собственные APIs LangGraph.
Установите LangGraph и основной SDK. Без дополнительных пакетов.
Node 18+ и @langchain/langgraph 0.2+ плюс реальный клиент @blockchain0x/node. Нет адаптера LangGraph для добавления в любом из языков - пользователи LangGraph на Python устанавливают langgraph и клиент blockchain0x на Python и пишут тот же рецепт, с interrupt() и Command(resume=...) на стороне Python.
npm install @langchain/langgraph @blockchain0x/node
export BLOCKCHAIN0X_API_KEY=sk_test_... # sk_test_ = Base Sepolia, sk_live_ = Base mainnet export BLOCKCHAIN0X_WEBHOOK_SECRET=... # for the webhook handler
BLOCKCHAIN0X_API_KEY - это ключ sk_test_ для testnet или sk_live_ для mainnet из вашего dashboard; client читает его из environment. BLOCKCHAIN0X_WEBHOOK_SECRET (выдается один раз при создании или ротации webhook) нужен в процессе, который обрабатывает webhook. Если ваш graph работает в одном процессе, а handler - в другом, обоим нужен один и тот же checkpointer (например, общий Postgres), чтобы handler мог найти приостановленный thread.
Граф из трех узлов: оплатить, приостановить, доставить.
Ниже приведен полный LangGraph workflow с типизированным State, pay node, который вызывает реальный blockchain0x.payments.create, interrupt-based await_settlement node и deliver node, который запускает зависимую работу после того, как chain подтверждает перевод payment.
import { StateGraph, START, END, MemorySaver, interrupt, Annotation } from "@langchain/langgraph"; import { createClient } from "@blockchain0x/node"; const blockchain0x = createClient({ apiKey: process.env.BLOCKCHAIN0X_API_KEY! }); const State = Annotation.Root({ agentId: Annotation<string>(), to: Annotation<string>(), amountWei: Annotation<string>(), // USDC base units: "10000" = 0.01 USDC settled: Annotation<boolean>(), }); // A plain node that calls the real SDK. No dedicated package needed. async function pay(state: typeof State.State) { await blockchain0x.payments.create({ agentId: state.agentId, to: state.to, amountWei: state.amountWei, }); return {}; } // Suspend the graph until the payment.sent webhook resumes this thread. function awaitSettlement() { interrupt("awaiting on-chain settlement"); return {}; } async function deliver() { // Funds have moved - do the work that depended on the payment. return {}; } const graph = new StateGraph(State) .addNode("pay", pay) .addNode("await_settlement", awaitSettlement) .addNode("deliver", deliver) .addEdge(START, "pay") .addEdge("pay", "await_settlement") .addConditionalEdges("await_settlement", (s) => (s.settled ? "deliver" : END)) .addEdge("deliver", END) .compile({ checkpointer: new MemorySaver() });
When the graph runs, the pay node submits the USDC transfer through the SDK. The await_settlement node calls interrupt(), which suspends the graph and persists state via the checkpointer. The conditional edge waits to be told settled=true before routing to deliver; if settlement never lands, a timeout sweep routes it to END. amountWei is base units, so 0.01 USDC is "10000".
Проверьте вебхук, затем продолжите поток.
Обработчик проверяет подпись с помощью webhooks.verify из Node SDK, ищет приостановленный поток LangGraph в вашем собственном отображении, устанавливает settled с помощью graph.updateState и вызывает graph.invoke(null, config), чтобы продолжить с места прерывания. Никаких специальных помощников - это собственные API LangGraph.
import express from "express"; import { webhooks } from "@blockchain0x/node"; import { graph } from "./graph"; const app = express(); // Capture the RAW body. The HMAC is over the exact bytes on the wire. app.use(express.raw({ type: "application/json" })); app.post("/webhooks/payment", async (req, res) => { const result = webhooks.verify({ headers: req.headers, rawBody: req.body, // Buffer, raw bytes secret: process.env.BLOCKCHAIN0X_WEBHOOK_SECRET!, }); if (!result.ok) return res.status(400).json({ code: result.code }); if (result.eventType === "payment.sent") { // You map the event to the suspended graph thread (your own store). const config = { configurable: { thread_id: threadFor(result.eventId) } }; await graph.updateState(config, { settled: true }); await graph.invoke(null, config); // resume from the interrupt } res.status(200).send("ok"); });
webhooks.verify выполняет HMAC-SHA256 за постоянное время над сырым телом и возвращает дискриминированный союз - ветвитесь по result.ok, без try/catch. Соответствуйте result.eventId потоку, который вы приостановили, затем graph.updateState + graph.invoke(null, config) возобновляет с сохраненной контрольной точки, и граф выполняет доставку. Отправленные события: payment.received, payment.sent, wallet.deployed и webhook.test.
Клиент, который вы оборачиваете, открыт. Читайте его.
Нет starter package для LangGraph, который можно было бы клонировать - рецепт выше и есть интеграция. SDK Blockchain0x открыты в GitHub; этот рецепт оборачивает Python SDK (blockchain0x-python), а полный набор методов приведен в docs. Используйте его как reference для body nodes и проверки webhook.
github.com/tosh-labs/blockchain0x-pythonПолный набор методов SDK и схема подписи webhook описаны в the docs. Начните с ключа sk_test_ в Base Sepolia, затем переключитесь на sk_live_, когда graph возобновит работу так, как вы ожидаете.
Пять ловушек, специфичных для LangGraph, которых следует избегать.
Модель долговременного выполнения LangGraph мощная, но имеет свои собственные подводные камни вокруг контрольных точек, прерываний и обновлений состояния.
Нет пакета LangGraph - простой узел вызывает SDK.
Blockchain0x поставляет адаптеры для LangChain и CrewAI, а также сервер MCP; нет отдельного пакета LangGraph в npm или pip. Рецепт выше - это путь: обычная графовая нода вызывает реальный клиент @blockchain0x/node, и вы продолжаете с помощью graph.updateState + graph.invoke от LangGraph. Нет специального класса payment-node и нет помощника для импорта - их никогда не существовало.
Отсутствует контрольный пункт
Функция interrupt() LangGraph работает только в том случае, если граф имеет контрольную точку. Без нее состояние исчезает, когда ваш процесс завершается, и обработчик вебхука не имеет потока для возобновления. Используйте MemorySaver для разработки и SqliteSaver или PostgresSaver в производстве. Если ваш граф и обработчик вебхука работают в разных процессах, они должны делить одну и ту же контрольную точку (например, один Postgres), чтобы обработчик мог найти приостановленный поток.
Сопоставление вебхука с правильным потоком - это ваша работа
Вебхук сообщает вам, что платеж был завершен; он не знает ваш LangGraph thread_id. Сохраняйте свое собственное отображение - когда вы начинаете граф, храните thread_id, ключом которого является агент, получатель или ключ идемпотентности, который вы установили, затем ищите его в обработчике. Вызов threadFor() выше - это тот самый поиск. Если вы ошибетесь, graph.invoke возобновит неправильный поток или вообще не возобновит.
Обработайте путь без расчетов или приостановите навсегда
Условный переход от await_settlement маршрутизирует доставку только когда settled истинно. Если платеж никогда не поступает, поток остается приостановленным на неопределенный срок. Добавьте тайм-аут: запланированную очистку, которая возобновляет устаревшие потоки с settled=false, чтобы переход маршрутизировался к END, или проверку payment.sent-versus-deadline. Приостановленный поток ничего не стоит, но он также никогда не завершится самостоятельно.
Суммы представлены в базовых единицах USDC, как строки
payments.create takes amountWei: a string of USDC base units (6 decimals), so 0.01 USDC is "10000" and 5 USDC is "5000000". It also does not retry by default and can answer 503 until the chain adapter is wired for your network. Handle that in the pay node - route to END or a retry node on error rather than letting the graph wedge.