agentic-payments
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAgentic Payments: x402 + MPP
智能体支付:x402 + MPP
Two complementary protocols for AI-agent and machine-to-machine payments on Stellar. Pick based on who depends on whom and how often the agent pays.
这是两种互补的协议,用于在Stellar网络上实现AI智能体及机器间的支付。可根据依赖关系以及智能体的支付频率选择合适的协议。
Quick decision
快速选择指南
| x402 | MPP Charge | MPP Channel | |
|---|---|---|---|
| Per-request on-chain tx? | Yes (via facilitator) | Yes (Soroban SAC) | No (off-chain commits) |
| Needs facilitator? | Yes (OZ Channels) | No | No |
| Client needs XLM? | No (fees sponsored) | Optional ( | Yes |
| Setup complexity | Low | Low | Medium (deploy contract first) |
| Best for | Quickest setup, fee-free clients | No third-party dep | High-frequency agents |
- Selling an API, want zero-XLM clients → see x402 Seller below
- Calling an x402 API from an agent → see x402 Buyer below
- Selling an API, no facilitator dependency → see MPP Charge below
- Agent making many requests per session → see MPP Channel below
- Unsure → x402 (lowest friction to get started)
All protocols use USDC (SEP-41 SAC) by default; / CAIP-2 network IDs.
stellar:testnetstellar:pubnet| x402 | MPP Charge模式 | MPP Channel模式 | |
|---|---|---|---|
| 是否为单次请求触发链上交易? | 是(通过服务商) | 是(Soroban SAC) | 否(链下提交) |
| 是否需要服务商? | 是(OZ Channels) | 否 | 否 |
| 客户端是否需要XLM? | 否(费用由服务商代付) | 可选( | 是 |
| 部署复杂度 | 低 | 低 | 中等(需先部署合约) |
| 最佳适用场景 | 最快部署,客户端无需支付费用 | 无第三方依赖 | 高频智能体流量 |
- 售卖API,希望客户端无需持有XLM → 查看下方的x402 服务端
- 通过智能体调用x402 API → 查看下方的x402 客户端
- 售卖API,无需依赖第三方服务商 → 查看下方的MPP Charge模式
- 智能体在单次会话中发起大量请求 → 查看下方的MPP Channel模式
- 不确定选哪种 → x402(启动门槛最低)
所有协议默认使用USDC(SEP-41 SAC);支持 / CAIP-2网络ID。
stellar:testnetstellar:pubnetRelated skills
相关技能文档
- The Soroban SACs the protocols call →
../soroban/SKILL.md - USDC and other classic assets →
../assets/SKILL.md - Wallets and signing in the buyer client →
../dapp/SKILL.md - RPC simulation / submission patterns →
../data/SKILL.md - SEP-41 (token interface) and related standards →
../standards/SKILL.md
- 协议调用的Soroban SAC相关内容 →
../soroban/SKILL.md - USDC及其他传统资产相关内容 →
../assets/SKILL.md - 客户端钱包与签名相关内容 →
../dapp/SKILL.md - RPC模拟/提交模式相关内容 →
../data/SKILL.md - SEP-41(代币接口)及相关标准 →
../standards/SKILL.md
Part 1: x402 — Paid APIs + Agent Buyer Clients
第一部分:x402 — 付费API + 智能体客户端
When to use x402
何时使用x402
x402 is the right choice when:
- You want the fastest path to a paid API — minimal code, no contract deployment
- You want clients (including AI agents) to pay with zero XLM — the OZ Channels facilitator sponsors all network fees
- You're building on top of an existing x402 ecosystem (Coinbase, other chains)
Trade-off: you depend on OZ Channels (or a self-hosted relayer) for verification and settlement. If you need zero third-party dependency, use MPP Charge (Part 2 below) instead.
当满足以下条件时,x402是最佳选择:
- 你希望最快上线付费API — 代码量最少,无需部署合约
- 你希望客户端(包括AI智能体)无需持有XLM即可支付 — OZ Channels服务商承担所有网络费用
- 你基于已有的x402生态(如Coinbase或其他链)进行开发
权衡点:你需要依赖OZ Channels(或自托管的中继器)进行验证和结算。如果需要完全无第三方依赖,请使用第二部分的MPP Charge模式。
How x402 works on Stellar
x402在Stellar上的工作流程
Client → GET /resource → Server
Client ← 402 Payment Required (payment requirements) ← Server
Client builds Soroban SAC USDC transfer
Client signs auth entries only (not the full tx envelope)
Client → GET /resource + X-PAYMENT header → Server
Server → OZ Channels /verify + /settle → Stellar (~5s)
Client ← 200 OK + resourceThe key Stellar difference: clients sign auth entries, not full transaction envelopes. The facilitator assembles the transaction, pays fees, and submits. Clients need zero XLM.
Client → GET /resource → Server
Client ← 402 Payment Required (payment requirements) ← Server
Client builds Soroban SAC USDC transfer
Client signs auth entries only (not the full tx envelope)
Client → GET /resource + X-PAYMENT header → Server
Server → OZ Channels /verify + /settle → Stellar (~5s)
Client ← 200 OK + resourceStellar上的核心差异:客户端仅签署授权条目(auth entries),而非完整的交易信封。服务商负责组装交易、支付费用并提交。客户端无需持有任何XLM。
Seller: monetize an Express API
服务端:将Express API变现
bash
npm install @x402/express @x402/core @x402/stellar express dotenv
npm pkg set type=modulejs
// server.js
import "dotenv/config";
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { ExactStellarScheme } from "@x402/stellar/exact/server";
// Drive the CAIP-2 network ID from one place. Switching to mainnet means
// flipping STELLAR_NETWORK and FACILITATOR_URL in .env, nothing in code.
const NETWORK = process.env.STELLAR_NETWORK || "stellar:testnet";
if (!process.env.OZ_API_KEY) {
throw new Error(
"OZ_API_KEY is required. Generate one at https://channels.openzeppelin.com/testnet/gen (testnet) or https://channels.openzeppelin.com/gen (mainnet)."
);
}
const facilitator = new HTTPFacilitatorClient({
url: process.env.FACILITATOR_URL ?? "https://channels.openzeppelin.com/x402/testnet",
// OZ Channels requires Bearer auth on both testnet and mainnet
createAuthHeaders: async () => {
const h = { Authorization: `Bearer ${process.env.OZ_API_KEY}` };
return { verify: h, settle: h, supported: h };
},
});
const resourceServer = new x402ResourceServer(facilitator)
.register(NETWORK, new ExactStellarScheme());
const app = express();
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: {
scheme: "exact",
price: "$0.001", // human-readable, auto-converts to 7-decimal USDC units
network: NETWORK,
payTo: process.env.STELLAR_RECIPIENT, // recipient G... account
},
description: "Current weather data",
},
},
resourceServer
)
);
app.get("/weather", (_req, res) => {
res.json({ city: "San Francisco", temp: 18, conditions: "Foggy" });
});
app.listen(3001, () => console.log(`x402 server on http://localhost:3001 (${NETWORK})`));Env vars:
- — CAIP-2 network ID; defaults to
STELLAR_NETWORK. Set tostellar:testnetfor mainnet.stellar:pubnet - — your G... address (receives USDC, needs a USDC trustline)
STELLAR_RECIPIENT - — OZ Channels API key (required on both testnet and mainnet; generate at the link in the runbook below)
OZ_API_KEY - — defaults to testnet URL above; set to
FACILITATOR_URLfor mainnethttps://channels.openzeppelin.com/x402
Price format options:
- — human-readable, auto-converts to 7-decimal USDC units
"$0.001" - — explicit base units for non-USDC assets
{ amount: "1000", asset: "ASSET_SAC_CONTRACT_ID" }
payToG...payTotransferbash
npm install @x402/express @x402/core @x402/stellar express dotenv
npm pkg set type=modulejs
// server.js
import "dotenv/config";
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { ExactStellarScheme } from "@x402/stellar/exact/server";
// Drive the CAIP-2 network ID from one place. Switching to mainnet means
// flipping STELLAR_NETWORK and FACILITATOR_URL in .env, nothing in code.
const NETWORK = process.env.STELLAR_NETWORK || "stellar:testnet";
if (!process.env.OZ_API_KEY) {
throw new Error(
"OZ_API_KEY is required. Generate one at https://channels.openzeppelin.com/testnet/gen (testnet) or https://channels.openzeppelin.com/gen (mainnet)."
);
}
const facilitator = new HTTPFacilitatorClient({
url: process.env.FACILITATOR_URL ?? "https://channels.openzeppelin.com/x402/testnet",
// OZ Channels requires Bearer auth on both testnet and mainnet
createAuthHeaders: async () => {
const h = { Authorization: `Bearer ${process.env.OZ_API_KEY}` };
return { verify: h, settle: h, supported: h };
},
});
const resourceServer = new x402ResourceServer(facilitator)
.register(NETWORK, new ExactStellarScheme());
const app = express();
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: {
scheme: "exact",
price: "$0.001", // human-readable, auto-converts to 7-decimal USDC units
network: NETWORK,
payTo: process.env.STELLAR_RECIPIENT, // recipient G... account
},
description: "Current weather data",
},
},
resourceServer
)
);
app.get("/weather", (_req, res) => {
res.json({ city: "San Francisco", temp: 18, conditions: "Foggy" });
});
app.listen(3001, () => console.log(`x402 server on http://localhost:3001 (${NETWORK})`));环境变量:
- — CAIP-2网络ID;默认值为
STELLAR_NETWORK。主网请设置为stellar:testnet。stellar:pubnet - — 你的G...格式地址(接收USDC,需提前添加USDC信任线)
STELLAR_RECIPIENT - — OZ Channels API密钥(测试网和主网均需配置;可通过下方运行指南中的链接生成)
OZ_API_KEY - — 默认使用上述测试网地址;主网请设置为
FACILITATOR_URLhttps://channels.openzeppelin.com/x402
价格格式选项:
- — 人类可读格式,自动转换为7位小数的USDC基础单位
"$0.001" - — 针对非USDC资产的显式基础单位配置
{ amount: "1000", asset: "ASSET_SAC_CONTRACT_ID" }
payToG...payTotransferBuyer: agent client
客户端:智能体客户端
bash
npm install @x402/fetch @x402/stellar dotenv
npm pkg set type=modulejs
// client.js
import "dotenv/config";
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { createEd25519Signer } from "@x402/stellar";
import { ExactStellarScheme } from "@x402/stellar/exact/client";
const NETWORK = process.env.STELLAR_NETWORK || "stellar:testnet";
// createEd25519Signer takes the raw S... secret string and the CAIP-2 network ID.
// Do NOT pre-wrap with Keypair.fromSecret or call getNetworkPassphrase yourself —
// the signer does both internally.
const signer = createEd25519Signer(process.env.STELLAR_SECRET_KEY, NETWORK);
// wrapFetchWithPaymentFromConfig returns a fetch that handles 402 negotiation
// and auth-entry signing transparently.
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: NETWORK, client: new ExactStellarScheme(signer) }],
});
const res = await fetchWithPayment("http://localhost:3001/weather");
console.log(await res.json());
// Paid automatically: 402 negotiation + auth-entry signing under the hoodEnv vars:
- — CAIP-2 network ID; defaults to
STELLAR_NETWORK. Must match the server's network.stellar:testnet - — your S... secret key (needs USDC trustline + balance)
STELLAR_SECRET_KEY
Browser frontends: this client uses Node and , both of which run in Node. A vanilla browser cannot sign Soroban auth entries through a typical wallet extension without additional glue. For a browser payer, run the x402 client server-side and expose a thin proxy endpoint to the page, or wire up Wallets-Kit / Freighter with custom auth-entry signing.
fetchcreateEd25519Signerbash
npm install @x402/fetch @x402/stellar dotenv
npm pkg set type=modulejs
// client.js
import "dotenv/config";
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { createEd25519Signer } from "@x402/stellar";
import { ExactStellarScheme } from "@x402/stellar/exact/client";
const NETWORK = process.env.STELLAR_NETWORK || "stellar:testnet";
// createEd25519Signer takes the raw S... secret string and the CAIP-2 network ID.
// Do NOT pre-wrap with Keypair.fromSecret or call getNetworkPassphrase yourself —
// the signer does both internally.
const signer = createEd25519Signer(process.env.STELLAR_SECRET_KEY, NETWORK);
// wrapFetchWithPaymentFromConfig returns a fetch that handles 402 negotiation
// and auth-entry signing transparently.
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: NETWORK, client: new ExactStellarScheme(signer) }],
});
const res = await fetchWithPayment("http://localhost:3001/weather");
console.log(await res.json());
// Paid automatically: 402 negotiation + auth-entry signing under the hood环境变量:
- — CAIP-2网络ID;默认值为
STELLAR_NETWORK。必须与服务端网络一致。stellar:testnet - — 你的S...格式密钥(需提前添加USDC信任线并持有余额)
STELLAR_SECRET_KEY
浏览器前端注意事项: 该客户端使用Node.js的和,仅支持Node环境。普通浏览器无法通过常规钱包扩展直接签署Soroban授权条目,需额外适配。如需浏览器端支付,可将x402客户端部署在服务端,为页面提供轻量代理接口,或通过Wallets-Kit / Freighter实现自定义授权条目签署逻辑。
fetchcreateEd25519SignerTestnet runbook
测试网运行指南
You need two Stellar testnet accounts: a client/payer (signs and pays from a USDC balance) and a server/recipient (the in your route config). Both need a USDC trustline.
payToTwo steps are web-only (Captcha or auth form) and cannot be scripted: the Circle USDC faucet and the OZ Channels key generator. Everything else can be automated. A complete sketch lives at the end of this section.
setup.js-
Generate two keypairsbash
node -e "const { Keypair } = require('@stellar/stellar-sdk'); for (const n of ['RECIPIENT','PAYER']) { const k = Keypair.random(); console.log(n, k.publicKey(), k.secret()); }" -
Fund both with testnet XLM (friendbot)bash
curl "https://friendbot.stellar.org?addr=RECIPIENT_G..." curl "https://friendbot.stellar.org?addr=PAYER_G..." -
Add a USDC trustline to BOTH accounts — open Stellar Lab and add a USDC trustline to each, or run via SDK for each keypair:
G...jsimport * as StellarSdk from "@stellar/stellar-sdk"; const horizon = new StellarSdk.Horizon.Server("https://horizon-testnet.stellar.org"); // Circle's classic USDC issuer on Stellar testnet const USDC_ISSUER = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"; async function addTrustline(secret) { const kp = StellarSdk.Keypair.fromSecret(secret); const acc = await horizon.loadAccount(kp.publicKey()); const tx = new StellarSdk.TransactionBuilder(acc, { fee: StellarSdk.BASE_FEE, networkPassphrase: StellarSdk.Networks.TESTNET, }) .addOperation(StellarSdk.Operation.changeTrust({ asset: new StellarSdk.Asset("USDC", USDC_ISSUER), })) .setTimeout(60) .build(); tx.sign(kp); return horizon.submitTransaction(tx); } // Repeat for both the recipient secret and the payer secret. await addTrustline(process.env.RECIPIENT_SECRET); await addTrustline(process.env.PAYER_SECRET);Without a trustline on the recipient, the SACsettles into nothing and the request fails withtransfer.op_no_trust -
Fund the PAYER with testnet USDC — open the Circle testnet faucet, select Stellar testnet, paste the payer's. Web Captcha; no API.
G... -
Generate an OZ Channels testnet API key (channels.openzeppelin.com/testnet/gen). Required, not optional. Without it the server crashes at startup with.
Failed to initialize: no supported payment kinds loaded from any facilitator -
Fill in
.envSTELLAR_NETWORK=stellar:testnet STELLAR_RECIPIENT=G... (recipient public key) STELLAR_SECRET_KEY=S... (payer secret key) OZ_API_KEY=... -
Run itbash
node server.js # in another terminal node client.js
你需要两个Stellar测试网账户:客户端/付款方(签署并从USDC余额支付)和服务端/收款方(路由配置中的账户)。两个账户均需添加USDC信任线。
payTo其中两个步骤仅支持网页操作(需验证码或授权表单),无法脚本化:Circle USDC水龙头和OZ Channels密钥生成。其余步骤均可自动化。本节末尾提供完整的示例。
setup.js-
生成两个密钥对bash
node -e "const { Keypair } = require('@stellar/stellar-sdk'); for (const n of ['RECIPIENT','PAYER']) { const k = Keypair.random(); console.log(n, k.publicKey(), k.secret()); }" -
为两个账户充值测试网XLM(Friendbot)bash
curl "https://friendbot.stellar.org?addr=RECIPIENT_G..." curl "https://friendbot.stellar.org?addr=PAYER_G..." -
为两个账户添加USDC信任线 — 打开Stellar Lab,为每个账户添加USDC信任线,或通过SDK为每个密钥对执行以下操作:
G...jsimport * as StellarSdk from "@stellar/stellar-sdk"; const horizon = new StellarSdk.Horizon.Server("https://horizon-testnet.stellar.org"); // Circle's classic USDC issuer on Stellar testnet const USDC_ISSUER = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"; async function addTrustline(secret) { const kp = StellarSdk.Keypair.fromSecret(secret); const acc = await horizon.loadAccount(kp.publicKey()); const tx = new StellarSdk.TransactionBuilder(acc, { fee: StellarSdk.BASE_FEE, networkPassphrase: StellarSdk.Networks.TESTNET, }) .addOperation(StellarSdk.Operation.changeTrust({ asset: new StellarSdk.Asset("USDC", USDC_ISSUER), })) .setTimeout(60) .build(); tx.sign(kp); return horizon.submitTransaction(tx); } // Repeat for both the recipient secret and the payer secret. await addTrustline(process.env.RECIPIENT_SECRET); await addTrustline(process.env.PAYER_SECRET);如果收款方未添加信任线,SAC的操作将无法完成,请求会因transfer错误失败。op_no_trust -
为付款方充值测试网USDC — 打开Circle测试网水龙头,选择Stellar testnet,粘贴付款方的地址。需完成网页验证码,无API接口。
G... -
生成OZ Channels测试网API密钥(channels.openzeppelin.com/testnet/gen)。必填项,不可省略。未配置时服务端启动会崩溃,提示。
Failed to initialize: no supported payment kinds loaded from any facilitator -
填写文件
.envSTELLAR_NETWORK=stellar:testnet STELLAR_RECIPIENT=G... (recipient public key) STELLAR_SECRET_KEY=S... (payer secret key) OZ_API_KEY=... -
运行服务bash
node server.js # 在另一个终端运行 node client.js
Optional: setup.js to automate steps 1–3
可选:setup.js自动化步骤1–3
Drop this in your project and run once. It generates keys, friendbots, and adds USDC trustlines, then writes a starter so you only need to do the two manual web steps afterward.
.envjs
// setup.js
import fs from "fs/promises";
import {
Keypair, Horizon, Networks, TransactionBuilder, Operation, Asset, BASE_FEE,
} from "@stellar/stellar-sdk";
const USDC_ISSUER = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const horizon = new Horizon.Server("https://horizon-testnet.stellar.org");
const friendbot = (addr) => fetch(`https://friendbot.stellar.org?addr=${addr}`);
async function addTrustline(kp) {
const acc = await horizon.loadAccount(kp.publicKey());
const tx = new TransactionBuilder(acc, { fee: BASE_FEE, networkPassphrase: Networks.TESTNET })
.addOperation(Operation.changeTrust({ asset: new Asset("USDC", USDC_ISSUER) }))
.setTimeout(60).build();
tx.sign(kp);
return horizon.submitTransaction(tx);
}
const recipient = Keypair.random();
const payer = Keypair.random();
await Promise.all([friendbot(recipient.publicKey()), friendbot(payer.publicKey())]);
await new Promise(r => setTimeout(r, 2000));
await Promise.all([addTrustline(recipient), addTrustline(payer)]);
await fs.writeFile(".env", `STELLAR_RECIPIENT=${recipient.publicKey()}
STELLAR_SECRET_KEY=${payer.secret()}
OZ_API_KEY=
`);
console.log(`Fund payer with USDC: https://faucet.circle.com → ${payer.publicKey()}`);
console.log(`Get OZ key: https://channels.openzeppelin.com/testnet/gen → paste into OZ_API_KEY`);将以下代码放入项目中运行一次,即可生成密钥、通过Friendbot充值并添加USDC信任线,然后生成初始文件,你只需完成后续两个手动网页步骤即可。
.envjs
// setup.js
import fs from "fs/promises";
import {
Keypair, Horizon, Networks, TransactionBuilder, Operation, Asset, BASE_FEE,
} from "@stellar/stellar-sdk";
const USDC_ISSUER = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const horizon = new Horizon.Server("https://horizon-testnet.stellar.org");
const friendbot = (addr) => fetch(`https://friendbot.stellar.org?addr=${addr}`);
async function addTrustline(kp) {
const acc = await horizon.loadAccount(kp.publicKey());
const tx = new TransactionBuilder(acc, { fee: BASE_FEE, networkPassphrase: Networks.TESTNET })
.addOperation(Operation.changeTrust({ asset: new Asset("USDC", USDC_ISSUER) }))
.setTimeout(60).build();
tx.sign(kp);
return horizon.submitTransaction(tx);
}
const recipient = Keypair.random();
const payer = Keypair.random();
await Promise.all([friendbot(recipient.publicKey()), friendbot(payer.publicKey())]);
await new Promise(r => setTimeout(r, 2000));
await Promise.all([addTrustline(recipient), addTrustline(payer)]);
await fs.writeFile(".env", `STELLAR_RECIPIENT=${recipient.publicKey()}
STELLAR_SECRET_KEY=${payer.secret()}
OZ_API_KEY=
`);
console.log(`Fund payer with USDC: https://faucet.circle.com → ${payer.publicKey()}`);
console.log(`Get OZ key: https://channels.openzeppelin.com/testnet/gen → paste into OZ_API_KEY`);Two USDC addresses (don't confuse them)
两个USDC地址(请勿混淆)
USDC on Stellar has two addresses, used in different places. Mixing them up is a common stumble.
| Address | Format | Used for |
|---|---|---|
| Classic asset issuer | | The |
| SAC (Soroban Asset Contract) | | The Soroban contract the protocol invokes |
Use the exported constants instead of hard-coding when possible:
js
import { USDC_TESTNET_ADDRESS, USDC_PUBNET_ADDRESS } from "@x402/stellar";
// USDC_TESTNET_ADDRESS = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"
// USDC_PUBNET_ADDRESS = "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75"payToG...assetStellar上的USDC有两个地址,分别用于不同场景。混淆两者是常见错误。
| 地址类型 | 格式 | 用途 |
|---|---|---|
| 传统资产发行方 | | 传统USDC资产的发行方;添加信任线时使用( |
| SAC(Stellar资产合约) | | 协议调用 |
尽可能使用导出的常量而非硬编码:
js
import { USDC_TESTNET_ADDRESS, USDC_PUBNET_ADDRESS } from "@x402/stellar";
// USDC_TESTNET_ADDRESS = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"
// USDC_PUBNET_ADDRESS = "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75"路由配置中的始终是传统接收方账户(格式)。仅当为非USDC代币配置自定义价格时,才会出现SAC地址。
payToG...assetMainnet checklist
主网配置清单
| Config | Value |
|---|---|
| Network ID | |
| RPC URL | Provider-specific endpoint (see Stellar RPC providers directory) |
| Facilitator URL | |
| USDC SAC | |
| OZ Channels API key | Required (channels.openzeppelin.com/gen) |
| Funding | Real USDC on mainnet (CEX, DEX, or bridge) |
Always test on testnet first. To switch a working setup to mainnet, change only the (, , mainnet , and a mainnet ); the samples derive their network from , so no code changes are needed. Both networks require an OZ Channels API key in the header.
.envSTELLAR_NETWORK=stellar:pubnetFACILITATOR_URL=https://channels.openzeppelin.com/x402OZ_API_KEYSTELLAR_RECIPIENTSTELLAR_NETWORKAuthorization: Bearer| 配置项 | 值 |
|---|---|
| 网络ID | |
| RPC URL | 服务商提供的端点(详见Stellar RPC服务商目录) |
| 服务商URL | |
| USDC SAC | |
| OZ Channels API密钥 | 必填(channels.openzeppelin.com/gen) |
| 资金来源 | 主网真实USDC(通过中心化交易所、去中心化交易所或跨链桥获取) |
请务必先在测试网完成测试。如需将已验证的测试网配置切换到主网,只需修改文件(、、主网以及主网);示例代码均从获取网络配置,无需修改代码。测试网和主网均需在头中携带OZ Channels API密钥。
.envSTELLAR_NETWORK=stellar:pubnetFACILITATOR_URL=https://channels.openzeppelin.com/x402OZ_API_KEYSTELLAR_RECIPIENTSTELLAR_NETWORKAuthorization: BearerKey concepts
核心概念
Auth entry signing — On Stellar, x402 clients sign Soroban authorization entries, not full transaction envelopes. The facilitator assembles the complete transaction. This is lighter than EVM/Solana signing, and means clients never need to manage sequence numbers or pay fees.
Fee sponsorship — OZ Channels pays all Stellar network fees (~$0.00001/tx). Clients need a funded wallet with USDC but zero XLM.
exact-v2scheme: "exact"x402Version: 2SAC (Stellar Asset Contract) — USDC on Stellar is a classic asset wrapped in a Soroban contract. x402 payments invoke on the SAC. Any SEP-41 token works; USDC is the default.
transferLedger expiration — Auth entries include a bound. Use (~1 minute at 5s/ledger). Expired entries fail at settlement.
max_ledgerlatestLedger + 12CAIP-2 network IDs — and . These are the exact strings the protocol expects.
stellar:testnetstellar:pubnet授权条目签署 — 在Stellar上,x402客户端仅签署Soroban授权条目,而非完整的交易信封。服务商负责组装完整交易。这比EVM/Solana的签署方式更轻量,且客户端无需管理序列号或支付费用。
费用代付 — OZ Channels承担所有Stellar网络费用(约$0.00001/笔交易)。客户端只需持有USDC,无需持有XLM。
exact-v2scheme: "exact"x402Version: 2SAC(Stellar资产合约) — Stellar上的USDC是封装在Soroban合约中的传统资产。x402支付会调用SAC的方法。任何符合SEP-41标准的代币均可使用;USDC为默认选项。
transfer账本过期 — 授权条目包含上限。建议使用(约1分钟,基于5秒/账本的速度)。过期的授权条目在结算时会失败。
max_ledgerlatestLedger + 12CAIP-2网络ID — 和。协议要求使用这些精确字符串。
stellar:testnetstellar:pubnetCommon pitfalls
常见问题
Auth entry expired on settle
- Symptom: facilitator returns , error mentions ledger expiration
isValid: false - Fix: ensure client uses (or higher) as expiration; don't cache auth entries across requests
latestLedger + 12
Wrong USDC decimal precision
- Symptom: payment amount off by 10x or 100x
- Fix: Stellar USDC uses 7 decimal places (not 6 like EVM USDC). =
$0.001in base units.10000
V1/V2 package mismatch
- Symptom: TypeScript errors or silent payment failures
- Fix: use all packages at the same major version. V2 is multi-chain; don't import V1
@x402/*alongside V2@x402/core.@x402/stellar
Missing USDC trustline
- Symptom: error during settlement
op_no_trust - Fix: add a USDC operation before attempting any x402 payment (see testnet runbook above)
changeTrust
OZ Channels 401 on testnet or mainnet
- Symptom: facilitator rejects with 401, server logs
Failed to initialize: no supported payment kinds loaded from any facilitator - Fix: an API key is required on both networks (this is a recent change). Generate one at channels.openzeppelin.com/testnet/gen (testnet) or channels.openzeppelin.com/gen (mainnet), then set and pass it via
OZ_API_KEY(see the Seller example).createAuthHeaders
Trustline missing on the recipient
- Symptom: during settlement, even though the client has USDC
op_no_trust - Fix: the account needs a USDC trustline too. The SAC
payTosettles the underlying classic asset, which the recipient cannot hold without a trustline. Addtransferto both accounts during setup.changeTrust
Trying to sign auth entries from a browser
- Symptom: bundling errors, or a browser wallet that has no API to sign Soroban auth entries
- Fix: run the x402 client server-side (e.g. an Express route the browser calls), or use Wallets-Kit / Freighter with custom auth-entry signing. +
@x402/fetchtarget Node and assume a raw secret key.createEd25519Signer
Passing a (or a network passphrase) to
KeypaircreateEd25519Signer- Symptom: , or
TypeError: encoded argument must be of type StringError: Unknown Stellar network: Test SDF Network ; September 2015 - Fix: the signer takes the raw secret string and a CAIP-2 network ID. Do not wrap with
S...first, and do not pre-convert withKeypair.fromSecret— both are done internally.getNetworkPassphrasejs// wrong const signer = createEd25519Signer(Keypair.fromSecret(s), getNetworkPassphrase("stellar:testnet")); // right const signer = createEd25519Signer(s, "stellar:testnet");
结算时授权条目过期
- 症状:服务商返回,错误信息提及账本过期
isValid: false - 解决方法:确保客户端使用(或更高值)作为过期时间;请勿跨请求缓存授权条目
latestLedger + 12
USDC小数精度错误
- 症状:支付金额偏差10倍或100倍
- 解决方法:Stellar USDC使用7位小数(不同于EVM USDC的6位小数)。等于
$0.001基础单位。10000
V1/V2包版本不匹配
- 症状:TypeScript错误或静默支付失败
- 解决方法:所有包使用相同的主版本号。V2支持多链;请勿同时导入V1的
@x402/*和V2的@x402/core。@x402/stellar
缺少USDC信任线
- 症状:结算时出现错误
op_no_trust - 解决方法:在尝试任何x402支付前,执行USDC操作(详见上述测试网运行指南)
changeTrust
测试网或主网OZ Channels返回401
- 症状:服务商返回401,服务端日志提示
Failed to initialize: no supported payment kinds loaded from any facilitator - 解决方法:两个网络均需配置API密钥(这是近期更新的要求)。在channels.openzeppelin.com/testnet/gen(测试网)或channels.openzeppelin.com/gen(主网)生成密钥,然后设置并通过
OZ_API_KEY传递(详见服务端示例)。createAuthHeaders
收款方缺少信任线
- 症状:结算时出现错误,即使客户端持有USDC
op_no_trust - 解决方法:账户也需要添加USDC信任线。SAC的
payTo操作会结算底层传统资产,而接收方若无信任线则无法持有该资产。请在设置阶段为两个账户均添加transfer操作。changeTrust
尝试在浏览器中签署授权条目
- 症状:打包错误,或浏览器钱包无签署Soroban授权条目的API
- 解决方法:将x402客户端部署在服务端(例如为浏览器提供Express路由),或通过Wallets-Kit / Freighter实现自定义授权条目签署逻辑。+
@x402/fetch针对Node环境,假设使用原始密钥。createEd25519Signer
向传递(或网络密码)
createEd25519SignerKeypair- 症状:,或
TypeError: encoded argument must be of type StringError: Unknown Stellar network: Test SDF Network ; September 2015 - 解决方法:签名器接收原始格式密钥字符串和CAIP-2网络ID。请勿先用
S...包装,也请勿提前调用Keypair.fromSecret转换 — 这些操作均由签名器内部完成。getNetworkPassphrasejs// 错误写法 const signer = createEd25519Signer(Keypair.fromSecret(s), getNetworkPassphrase("stellar:testnet")); // 正确写法 const signer = createEd25519Signer(s, "stellar:testnet");
Part 2: MPP — Machine Payments Protocol (Charge + Channel)
第二部分:MPP — 机器支付协议(Charge + Channel模式)
When to use MPP
何时使用MPP
MPP is the right choice when:
- You want no facilitator dependency — payments settle directly on Stellar via Soroban SAC transfers
- Your AI agent makes many requests per session — use channel mode to pay off-chain and settle once
- You're building a Stellar-native payment stack without relying on third-party infrastructure
Two modes:
| Mode | On-chain txs | Best for |
|---|---|---|
| Charge | One per request | Per-request payments, no pre-funding required |
| Channel | One deposit + one close | High-frequency agents (100s of requests/session) |
If you need zero-XLM clients or the simplest possible setup, use x402 (Part 1 above) instead.
当满足以下条件时,MPP是最佳选择:
- 你希望无服务商依赖 — 支付通过Soroban SAC转账直接在Stellar上结算
- 你的AI智能体在单次会话中发起大量请求 — 使用Channel模式实现链下支付,仅需一次结算
- 你正在构建Stellar原生支付栈,无需依赖第三方基础设施
两种模式:
| 模式 | 链上交易次数 | 最佳适用场景 |
|---|---|---|
| Charge模式 | 单次请求对应一次链上交易 | 单次请求支付,无需预充值 |
| Channel模式 | 一次存入 + 一次关闭 | 高频智能体(单次会话数百次请求) |
如果需要客户端无需持有XLM或最简部署,请使用第一部分的x402协议。
Charge mode: per-request payments
Charge模式:单次请求支付
Each request triggers a Soroban SAC token transfer settled on-chain. No facilitator. Server can optionally sponsor fees so clients don't need XLM.
bash
npm install express @stellar/mpp mppx @stellar/stellar-sdk dotenv
npm pkg set type=moduleServer:
js
// charge-server.js
import express from "express";
import { Mppx } from "mppx";
import * as stellar from "@stellar/mpp/charge/server";
import * as StellarSdk from "@stellar/stellar-sdk";
const USDC_SAC_TESTNET = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA";
const RECIPIENT = process.env.STELLAR_RECIPIENT; // G... address
const mppx = Mppx.create({
secretKey: process.env.MPP_SECRET_KEY, // shared secret for credential verification
methods: [
stellar.charge({
recipient: RECIPIENT,
currency: USDC_SAC_TESTNET,
network: "stellar:testnet",
// optional: server pays network fees so clients don't need XLM
feePayer: process.env.FEE_PAYER_SECRET
? { envelopeSigner: StellarSdk.Keypair.fromSecret(process.env.FEE_PAYER_SECRET) }
: undefined,
}),
],
});
const app = express();
app.use(express.json());
// mppx middleware: returns 402 with challenge, then validates payment on retry
app.use(mppx.middleware());
app.get("/data", (req, res) => {
res.json({ result: "paid content", price: "$0.001 USDC" });
});
app.listen(3002, () => console.log("MPP charge server on http://localhost:3002"));Client:
js
// charge-client.js
import { Mppx } from "mppx";
import * as stellar from "@stellar/mpp/charge/client";
import * as StellarSdk from "@stellar/stellar-sdk";
const keypair = StellarSdk.Keypair.fromSecret(process.env.STELLAR_SECRET_KEY);
const mppx = Mppx.create({
methods: [
stellar.charge({
keypair,
mode: "pull", // server assembles and broadcasts the transaction
onProgress(event) {
// event.type: "challenge" | "signed" | "settled"
if (event.type === "settled") console.log("Settled:", event.txHash);
},
}),
],
});
// mppx wraps fetch — 402 handling is transparent
const res = await mppx.fetch("http://localhost:3002/data");
console.log(await res.json());Env vars (server): , , (optional)
Env vars (client):
STELLAR_RECIPIENTMPP_SECRET_KEYFEE_PAYER_SECRETSTELLAR_SECRET_KEYmode: "pull""push"- — client signs auth entries, server assembles + broadcasts (default; use with
"pull")feePayer - — client builds and broadcasts the transaction directly (client must have XLM for fees)
"push"
每个请求触发一次Soroban SAC代币转账,直接在链上结算。无需服务商。服务端可选择代付费用,使客户端无需持有XLM。
bash
npm install express @stellar/mpp mppx @stellar/stellar-sdk dotenv
npm pkg set type=module服务端:
js
// charge-server.js
import express from "express";
import { Mppx } from "mppx";
import * as stellar from "@stellar/mpp/charge/server";
import * as StellarSdk from "@stellar/stellar-sdk";
const USDC_SAC_TESTNET = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA";
const RECIPIENT = process.env.STELLAR_RECIPIENT; // G... address
const mppx = Mppx.create({
secretKey: process.env.MPP_SECRET_KEY, // shared secret for credential verification
methods: [
stellar.charge({
recipient: RECIPIENT,
currency: USDC_SAC_TESTNET,
network: "stellar:testnet",
// optional: server pays network fees so clients don't need XLM
feePayer: process.env.FEE_PAYER_SECRET
? { envelopeSigner: StellarSdk.Keypair.fromSecret(process.env.FEE_PAYER_SECRET) }
: undefined,
}),
],
});
const app = express();
app.use(express.json());
// mppx middleware: returns 402 with challenge, then validates payment on retry
app.use(mppx.middleware());
app.get("/data", (req, res) => {
res.json({ result: "paid content", price: "$0.001 USDC" });
});
app.listen(3002, () => console.log("MPP charge server on http://localhost:3002"));客户端:
js
// charge-client.js
import { Mppx } from "mppx";
import * as stellar from "@stellar/mpp/charge/client";
import * as StellarSdk from "@stellar/stellar-sdk";
const keypair = StellarSdk.Keypair.fromSecret(process.env.STELLAR_SECRET_KEY);
const mppx = Mppx.create({
methods: [
stellar.charge({
keypair,
mode: "pull", // server assembles and broadcasts the transaction
onProgress(event) {
// event.type: "challenge" | "signed" | "settled"
if (event.type === "settled") console.log("Settled:", event.txHash);
},
}),
],
});
// mppx wraps fetch — 402 handling is transparent
const res = await mppx.fetch("http://localhost:3002/data");
console.log(await res.json());服务端环境变量: , , (可选)
客户端环境变量:
STELLAR_RECIPIENTMPP_SECRET_KEYFEE_PAYER_SECRETSTELLAR_SECRET_KEYmode: "pull""push"- — 客户端签署授权条目,服务端组装并广播交易(默认选项;需配合
"pull"使用)feePayer - — 客户端自行构建并广播交易(客户端必须持有XLM以支付费用)
"push"
Channel mode: high-frequency off-chain payments
Channel模式:高频链下支付
The client deploys a one-way payment channel contract, deposits USDC once, then signs cumulative commitments off-chain for each request. No transaction per request — only two on-chain txs total (deposit + close). Ideal for AI agents making hundreds of calls in a session.
客户端部署单向支付通道合约,存入USDC一次,然后为每个请求签署累积承诺(链下操作)。无需为每个请求发起交易 — 全程仅需两次链上交易(存入 + 关闭)。非常适合单次会话中发起数百次调用的AI智能体。
Channel lifecycle
通道生命周期
1. Deploy channel contract (one-time) → C... contract address
2. Client deposits USDC into channel → on-chain tx
3. Per request: client signs commitment → off-chain (just a signature)
Amount is cumulative: each sig covers all previous payments + this one
4. Server closes channel when done → on-chain tx, settles total1. 部署通道合约(一次性操作) → C...合约地址
2. 客户端向通道存入USDC → 链上交易
3. 每次请求:客户端签署承诺 → 链下操作(仅需签名)
金额为累积值:每个签名涵盖之前所有支付 + 当前请求的金额
4. 会话结束时服务端关闭通道 → 链上交易,结算总金额Prerequisites
前置条件
- Deploy a one-way-channel Soroban contract to get a contract address
C... - Generate an ed25519 keypair for commitment signing (see stellar-mpp SDK)
- Fund the channel with USDC before making requests
- 部署单向支付通道Soroban合约以获取格式的合约地址
C... - 生成用于承诺签署的ed25519密钥对(详见stellar-mpp SDK)
- 在发起请求前为通道充值USDC
Server:
服务端:
js
// channel-server.js
import express from "express";
import { Mppx, Store } from "mppx";
import * as stellar from "@stellar/mpp/channel/server";
const mppx = Mppx.create({
secretKey: process.env.MPP_SECRET_KEY,
methods: [
stellar.channel({
channel: process.env.CHANNEL_CONTRACT, // C... contract address
commitmentKey: process.env.COMMITMENT_PUBKEY, // 64-char hex ed25519 public key
store: Store.memory(), // dev only — use persistent store in production
network: "stellar:testnet",
}),
],
});
const app = express();
app.use(express.json());
app.use(mppx.middleware());
app.get("/data", (req, res) => {
res.json({ result: "paid content" });
});
app.listen(3003);js
// channel-server.js
import express from "express";
import { Mppx, Store } from "mppx";
import * as stellar from "@stellar/mpp/channel/server";
const mppx = Mppx.create({
secretKey: process.env.MPP_SECRET_KEY,
methods: [
stellar.channel({
channel: process.env.CHANNEL_CONTRACT, // C... contract address
commitmentKey: process.env.COMMITMENT_PUBKEY, // 64-char hex ed25519 public key
store: Store.memory(), // dev only — use persistent store in production
network: "stellar:testnet",
}),
],
});
const app = express();
app.use(express.json());
app.use(mppx.middleware());
app.get("/data", (req, res) => {
res.json({ result: "paid content" });
});
app.listen(3003);Client:
客户端:
js
// channel-client.js
import { Mppx } from "mppx";
import * as stellar from "@stellar/mpp/channel/client";
import * as StellarSdk from "@stellar/stellar-sdk";
// commitment key must be a raw ed25519 seed — NOT a standard Stellar secret key
const commitmentKey = StellarSdk.Keypair.fromRawEd25519Seed(
Buffer.from(process.env.COMMITMENT_SECRET, "hex") // 64-char hex secret
);
const mppx = Mppx.create({
methods: [
stellar.channel({
commitmentKey,
onProgress(event) {
// event.type: "challenge" | "signed"
},
}),
],
});
// Make many requests — each signs a cumulative off-chain commitment
for (let i = 0; i < 100; i++) {
const res = await mppx.fetch("http://localhost:3003/data");
console.log(i, await res.json());
}js
// channel-client.js
import { Mppx } from "mppx";
import * as stellar from "@stellar/mpp/channel/client";
import * as StellarSdk from "@stellar/stellar-sdk";
// commitment key must be a raw ed25519 seed — NOT a standard Stellar secret key
const commitmentKey = StellarSdk.Keypair.fromRawEd25519Seed(
Buffer.from(process.env.COMMITMENT_SECRET, "hex") // 64-char hex secret
);
const mppx = Mppx.create({
methods: [
stellar.channel({
commitmentKey,
onProgress(event) {
// event.type: "challenge" | "signed"
},
}),
],
});
// Make many requests — each signs a cumulative off-chain commitment
for (let i = 0; i < 100; i++) {
const res = await mppx.fetch("http://localhost:3003/data");
console.log(i, await res.json());
}Closing the channel (server-initiated):
关闭通道(服务端发起):
js
import { close } from "@stellar/mpp/channel/server";
import * as StellarSdk from "@stellar/stellar-sdk";
const txHash = await close({
channel: process.env.CHANNEL_CONTRACT,
amount: lastCumulativeAmount, // bigint, total USDC owed in base units
signature: lastCommitmentSignature, // hex string from final commitment
feePayer: { envelopeSigner: StellarSdk.Keypair.fromSecret(process.env.FEE_PAYER_SECRET) },
network: "stellar:testnet",
});
// Single on-chain transaction settles the full session
console.log("Channel closed:", txHash);Env vars (server): , , ,
Env vars (client):
CHANNEL_CONTRACTCOMMITMENT_PUBKEYMPP_SECRET_KEYFEE_PAYER_SECRETCOMMITMENT_SECRETjs
import { close } from "@stellar/mpp/channel/server";
import * as StellarSdk from "@stellar/stellar-sdk";
const txHash = await close({
channel: process.env.CHANNEL_CONTRACT,
amount: lastCumulativeAmount, // bigint, total USDC owed in base units
signature: lastCommitmentSignature, // hex string from final commitment
feePayer: { envelopeSigner: StellarSdk.Keypair.fromSecret(process.env.FEE_PAYER_SECRET) },
network: "stellar:testnet",
});
// Single on-chain transaction settles the full session
console.log("Channel closed:", txHash);服务端环境变量: , , ,
客户端环境变量:
CHANNEL_CONTRACTCOMMITMENT_PUBKEYMPP_SECRET_KEYFEE_PAYER_SECRETCOMMITMENT_SECRETPackages and subpath imports
包及子路径导入
bash
npm install @stellar/mpp mppx @stellar/stellar-sdk| Import path | Recommended import pattern |
|---|---|
| |
| |
| |
| |
| Zod schema definitions for channel types |
| |
bash
npm install @stellar/mpp mppx @stellar/stellar-sdk| 导入路径 | 推荐导入方式 |
|---|---|
| |
| |
| |
| |
| 通道类型的Zod schema定义 |
| |
Testnet runbook
测试网运行指南
Steps shared with all protocols:
- Generate keypair + fund with Friendbot (see x402 testnet runbook in Part 1 above)
- Add USDC trustline
- Get testnet USDC from Circle faucet
Channel mode only:
4. Deploy the one-way-channel contract (see stellar-mpp-sdk for deploy script)
5. Generate a 64-char hex ed25519 seed for the commitment key:
bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"- Derive the public key and fund the channel with USDC before making requests
所有协议通用步骤:
- 生成密钥对并通过Friendbot充值(详见第一部分x402测试网运行指南)
- 添加USDC信任线
- 从Circle水龙头获取测试网USDC
仅Channel模式需额外执行:
4. 部署单向支付通道合约(详见stellar-mpp-sdk中的部署脚本)
5. 生成64字符十六进制ed25519种子作为承诺密钥:
bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"- 导出公钥并在发起请求前为通道充值USDC
Common pitfalls
常见问题
Channel: wrong commitment key format
- Symptom: throws or signatures fail to verify
Keypair.fromRawEd25519Seed - Fix: the commitment key is a raw ed25519 seed as a 64-char hex string — not a Stellar secret key. Generate with
S....crypto.randomBytes(32).toString('hex')
Channel: non-cumulative amounts
- Symptom: server rejects commitments after the first request
- Fix: each commitment's must be the running total of all payments so far, not just the price of the current request. The server tracks the highest-seen commitment.
amount
Channel: deposit TTL expired
- Symptom: fails or channel appears drained
close() - Fix: Soroban contract storage has a TTL. Close the channel before it expires, or extend storage TTL via . Don't leave channels open indefinitely.
bumpContractInstance
Charge: client has no XLM for fees
- Symptom: or fee errors on client-submitted transactions
op_insufficient_balance - Fix: set on the client and configure
mode: "pull"on the server so the server pays fees. The client only signs auth entries.feePayer
Store.memory()- Symptom: server loses track of channel state on restart, enables double-spend
- Fix: replace with a persistent store (database-backed) before going to production.
Store.memory()
Channel模式:承诺密钥格式错误
- 症状:抛出错误或签名验证失败
Keypair.fromRawEd25519Seed - 解决方法:承诺密钥是64字符十六进制格式的原始ed25519种子 — 不是Stellar的格式密钥。使用
S...生成。crypto.randomBytes(32).toString('hex')
Channel模式:金额未累积
- 症状:服务端在首次请求后拒绝后续承诺
- 解决方法:每个承诺的必须是截至当前的所有支付总额,而非仅当前请求的价格。服务端会跟踪已确认的最高金额承诺。
amount
Channel模式:存入TTL过期
- 症状:失败或通道显示已耗尽
close() - 解决方法:Soroban合约存储有TTL。请在过期前关闭通道,或通过延长存储TTL。请勿长期保持通道开启状态。
bumpContractInstance
Charge模式:客户端无XLM支付费用
- 症状:客户端提交交易时出现或费用错误
op_insufficient_balance - 解决方法:在客户端设置,并在服务端配置
mode: "pull",由服务端支付费用。客户端仅需签署授权条目。feePayer
生产环境使用
Store.memory()- 症状:服务端重启后丢失通道状态,可能导致双重支付
- 解决方法:在生产环境中,将替换为持久化存储(如数据库驱动的存储)。
Store.memory()