LangGraph支払い統合。
LangGraphパッケージは必要ありません。プレーンノードが実際のblockchain0xクライアントを呼び出し、interrupt()が支払いWebhookが到着するまで停止し、グラフが再開します - 実行を通じて耐久性があります。
LangGraph 専用パッケージはありませんし、必要もありません。 内の通常の node が実際の client を呼び出し、 が payment webhook 到着まで graph を一時停止し、handler が LangGraph 独自の + で再開します。支払いは Base で決済されます。
耐久性のある実行により、支払い後の再開パターンが自然になります。
エージェント駆動の支払いの難しい部分は非同期のギャップです:支払いをリクエストし、購入者が数分後に支払う(または支払わない)と、あなたはどこにいたかの状態を失うことなく作業を再開しなければなりません。ほとんどのエージェントフレームワークは、このギャップを埋めるためにジョブキュー + 手動の状態ストアに依存しています。LangGraphには、すでに組み込まれたプリミティブがあります:interrupt()はノードでグラフをサスペンドし、チェックポインタは状態をディスクに保持します。再開は1回の呼び出しです。
このレシピは特別な仕組みなしでそれを活かします。通常の pay ノードが blockchain0x.payments.create を呼びます。await_settlement ノードは interrupt() を呼んで停止し、checkpointer が state を保持します。webhook handler は署名を検証し、graph.updateState で一致する thread を settled に設定し、graph.invoke を呼んで interrupt から続行します。graph は、前のノードの LLM context を含め、停止した位置から正確に再開します - すべて LangGraph の API だけで実現しています。
LangGraphとコアSDKをインストールします。追加のパッケージはありません。
Node 18+と@langchain/langgraph 0.2+、および実際の@blockchain0x/nodeクライアント。どちらの言語にも追加するLangGraphアダプターはありません - Python LangGraphユーザーはlanggraphとblockchain0x Pythonクライアントをインストールし、同じレシピを書きます。Python側ではinterrupt()とCommand(resume=...)を使用します。
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 は dashboard の sk_test_ testnet または sk_live_ mainnet キーで、client は環境変数から読み込みます。BLOCKCHAIN0X_WEBHOOK_SECRET (webhook を作成またはローテーションしたときに 1 回だけ返されます) は webhook を処理するプロセスで必要です。graph が 1 つのプロセスで動作し、handler が別のプロセスで動作する場合は、handler が中断された thread を見つけられるように、両者で同じ checkpointer (例: shared Postgres) が必要です。
3ノードのグラフ:支払い、一時停止、配信。
以下は完全な LangGraph workflow です。typed State、実際の blockchain0x.payments.create を呼び出す pay node、interrupt ベースの await_settlement node、そして chain が payment の移動を確認したら依存処理を実行する deliver node を含みます。
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".
Webhookを確認し、その後スレッドを再開します。
ハンドラーはNode SDKのwebhooks.verifyを使用して署名を検証し、あなた自身のマッピングからサスペンドされたLangGraphスレッドを検索し、graph.updateStateでsettledを設定し、interruptから続けるためにgraph.invoke(null, config)を呼び出します。特別なヘルパーはありません - それらはLangGraphの独自のAPIです。
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はRAWボディに対して定数時間でHMAC-SHA256を行い、識別されたユニオンを返します - result.okで分岐し、try/catchはありません。result.eventIdを一時停止したスレッドにマッピングし、次にgraph.updateState + graph.invoke(null, config)が永続化されたチェックポイントから再開し、グラフが配信を実行します。出荷されたイベントはpayment.received、payment.sent、wallet.deployed、webhook.testです。
ラップしているクライアントはオープンです。読んでください。
クローンする LangGraph の starter package はありません - 上の recipe がそのまま integration です。blockchain0x の SDK は GitHub で open source です。この recipe は Python SDK (blockchain0x-python) をラップしており、完全なメソッド一覧は docs にあります。node body と webhook verify の参考として読んでください。
github.com/tosh-labs/blockchain0x-pythonSDK のメソッド一覧と webhook の署名方式は the docs に記載されています。まずは Base Sepolia で sk_test_ key を使い、graph が想定どおり再開したら sk_live_ に切り替えてください。
LangGraphに特有の避けるべき5つの罠。
LangGraphの耐久実行モデルは強力ですが、チェックポイント、中断、および状態更新に関する独自のリスクがあります。
LangGraphパッケージはありません - プレーンノードがSDKを呼び出します。
Blockchain0xは、LangChainとCrewAI用のアダプタとMCPサーバーを出荷します。npmやpipには専用のLangGraphパッケージはありません。上記のレシピはパスです:通常のグラフノードが実際の@blockchain0x/nodeクライアントを呼び出し、LangGraphの独自のgraph.updateState + graph.invokeで再開します。特別なペイメントノードクラスやインポートするための再開ヘルパーはありません - それらは存在しませんでした。
チェックポインターがありません
LangGraphのinterrupt()は、グラフにチェックポインタがある場合にのみ機能します。チェックポインタがない場合、プロセスが終了すると状態が消失し、Webhookハンドラには再開するスレッドがありません。開発にはMemorySaverを使用し、本番ではSqliteSaverまたはPostgresSaverを使用します。グラフとWebhookハンドラが異なるプロセスで実行される場合、同じチェックポインタ(例: 1つのPostgres)を共有する必要がありますので、ハンドラがサスペンドされたスレッドを見つけることができます。
Webhookを正しいスレッドにマッピングするのはあなたの仕事です
Webhookは、支払いが決済されたことを知らせます; あなたのLangGraph thread_idは知りません。自分のマッピングを保持してください - グラフを開始するとき、エージェント、受取人、または設定したアイデンポテンシーキーによってキー付けされたthread_idを保存し、ハンドラーでそれを検索します。上記のthreadFor()呼び出しがその検索です。これを間違えると、graph.invokeは間違ったスレッドを再開するか、まったく再開しません。
決済なしのパスを処理するか、永遠に保留します
await_settlementからの条件付きエッジは、settledがtrueのときのみ配信するようにルーティングされます。支払いが決済されない場合、スレッドは無期限にサスペンドされたままになります。タイムアウトを追加します:settled=falseで古いスレッドを再開するスケジュールされたスイープを追加し、エッジがENDにルーティングされるか、payment.sentと締切のチェックを行います。サスペンドされたスレッドは何もコストがかかりませんが、自分自身で終了することもありません。
金額は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.