agentic-payments

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Agentic 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

快速选择指南

x402MPP ChargeMPP Channel
Per-request on-chain tx?Yes (via facilitator)Yes (Soroban SAC)No (off-chain commits)
Needs facilitator?Yes (OZ Channels)NoNo
Client needs XLM?No (fees sponsored)Optional (
feePayer
)
Yes
Setup complexityLowLowMedium (deploy contract first)
Best forQuickest setup, fee-free clientsNo third-party depHigh-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;
stellar:testnet
/
stellar:pubnet
CAIP-2 network IDs.
x402MPP Charge模式MPP Channel模式
是否为单次请求触发链上交易?是(通过服务商)是(Soroban SAC)否(链下提交)
是否需要服务商?是(OZ Channels)
客户端是否需要XLM?否(费用由服务商代付)可选(
feePayer
配置)
部署复杂度中等(需先部署合约)
最佳适用场景最快部署,客户端无需支付费用无第三方依赖高频智能体流量
  • 售卖API,希望客户端无需持有XLM → 查看下方的x402 服务端
  • 通过智能体调用x402 API → 查看下方的x402 客户端
  • 售卖API,无需依赖第三方服务商 → 查看下方的MPP Charge模式
  • 智能体在单次会话中发起大量请求 → 查看下方的MPP Channel模式
  • 不确定选哪种 → x402(启动门槛最低)
所有协议默认使用USDC(SEP-41 SAC);支持
stellar:testnet
/
stellar:pubnet
CAIP-2网络ID。

Related 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 + resource
The 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 + resource
Stellar上的核心差异:客户端仅签署授权条目(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=module
js
// 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:
  • STELLAR_NETWORK
    — CAIP-2 network ID; defaults to
    stellar:testnet
    . Set to
    stellar:pubnet
    for mainnet.
  • STELLAR_RECIPIENT
    — your G... address (receives USDC, needs a USDC trustline)
  • OZ_API_KEY
    — OZ Channels API key (required on both testnet and mainnet; generate at the link in the runbook below)
  • FACILITATOR_URL
    — defaults to testnet URL above; set to
    https://channels.openzeppelin.com/x402
    for mainnet
Price format options:
  • "$0.001"
    — human-readable, auto-converts to 7-decimal USDC units
  • { amount: "1000", asset: "ASSET_SAC_CONTRACT_ID" }
    — explicit base units for non-USDC assets
payTo
is the recipient's classic Stellar account (
G...
), not the USDC SAC contract address.
Sending USDC lands in the classic balance of the
payTo
account, which is why that account also needs a USDC trustline. The SAC contract address is what the protocol invokes
transfer
on; see "Two USDC addresses" below.
bash
npm install @x402/express @x402/core @x402/stellar express dotenv
npm pkg set type=module
js
// 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})`));
环境变量:
  • STELLAR_NETWORK
    — CAIP-2网络ID;默认值为
    stellar:testnet
    。主网请设置为
    stellar:pubnet
  • STELLAR_RECIPIENT
    — 你的G...格式地址(接收USDC,需提前添加USDC信任线)
  • OZ_API_KEY
    — OZ Channels API密钥(测试网和主网均需配置;可通过下方运行指南中的链接生成)
  • FACILITATOR_URL
    — 默认使用上述测试网地址;主网请设置为
    https://channels.openzeppelin.com/x402
价格格式选项:
  • "$0.001"
    — 人类可读格式,自动转换为7位小数的USDC基础单位
  • { amount: "1000", asset: "ASSET_SAC_CONTRACT_ID" }
    — 针对非USDC资产的显式基础单位配置
payTo
为接收方的传统Stellar账户(
G...
格式),而非USDC SAC合约地址。
USDC会转入
payTo
账户的传统余额,因此该账户必须提前添加USDC信任线。SAC合约地址是协议调用
transfer
方法的目标;详见下方「两个USDC地址」说明。

Buyer: agent client

客户端:智能体客户端

bash
npm install @x402/fetch @x402/stellar dotenv
npm pkg set type=module
js
// 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
Env vars:
  • STELLAR_NETWORK
    — CAIP-2 network ID; defaults to
    stellar:testnet
    . Must match the server's network.
  • STELLAR_SECRET_KEY
    — your S... secret key (needs USDC trustline + balance)
Browser frontends: this client uses Node
fetch
and
createEd25519Signer
, 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.
bash
npm install @x402/fetch @x402/stellar dotenv
npm pkg set type=module
js
// 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
环境变量:
  • STELLAR_NETWORK
    — CAIP-2网络ID;默认值为
    stellar:testnet
    。必须与服务端网络一致。
  • STELLAR_SECRET_KEY
    — 你的S...格式密钥(需提前添加USDC信任线并持有余额)
浏览器前端注意事项: 该客户端使用Node.js的
fetch
createEd25519Signer
,仅支持Node环境。普通浏览器无法通过常规钱包扩展直接签署Soroban授权条目,需额外适配。如需浏览器端支付,可将x402客户端部署在服务端,为页面提供轻量代理接口,或通过Wallets-Kit / Freighter实现自定义授权条目签署逻辑。

Testnet runbook

测试网运行指南

You need two Stellar testnet accounts: a client/payer (signs and pays from a USDC balance) and a server/recipient (the
payTo
in your route config). Both need a USDC trustline.
Two 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
setup.js
sketch lives at the end of this section.
  1. Generate two keypairs
    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()); }"
  2. Fund both with testnet XLM (friendbot)
    bash
    curl "https://friendbot.stellar.org?addr=RECIPIENT_G..."
    curl "https://friendbot.stellar.org?addr=PAYER_G..."
  3. Add a USDC trustline to BOTH accounts — open Stellar Lab and add a USDC trustline to each
    G...
    , or run via SDK for each keypair:
    js
    import * 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 SAC
    transfer
    settles into nothing and the request fails with
    op_no_trust
    .
  4. Fund the PAYER with testnet USDC — open the Circle testnet faucet, select Stellar testnet, paste the payer's
    G...
    . Web Captcha; no API.
  5. 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
    .
  6. Fill in
    .env
    STELLAR_NETWORK=stellar:testnet
    STELLAR_RECIPIENT=G... (recipient public key)
    STELLAR_SECRET_KEY=S... (payer secret key)
    OZ_API_KEY=...
  7. Run it
    bash
    node server.js
    # in another terminal
    node client.js
你需要两个Stellar测试网账户:客户端/付款方(签署并从USDC余额支付)和服务端/收款方(路由配置中的
payTo
账户)。两个账户均需添加USDC信任线。
其中两个步骤仅支持网页操作(需验证码或授权表单),无法脚本化:Circle USDC水龙头和OZ Channels密钥生成。其余步骤均可自动化。本节末尾提供完整的
setup.js
示例。
  1. 生成两个密钥对
    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()); }"
  2. 为两个账户充值测试网XLM(Friendbot)
    bash
    curl "https://friendbot.stellar.org?addr=RECIPIENT_G..."
    curl "https://friendbot.stellar.org?addr=PAYER_G..."
  3. 为两个账户添加USDC信任线 — 打开Stellar Lab,为每个
    G...
    账户添加USDC信任线,或通过SDK为每个密钥对执行以下操作:
    js
    import * 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
    错误失败。
  4. 为付款方充值测试网USDC — 打开Circle测试网水龙头,选择Stellar testnet,粘贴付款方的
    G...
    地址。需完成网页验证码,无API接口。
  5. 生成OZ Channels测试网API密钥channels.openzeppelin.com/testnet/gen)。必填项,不可省略。未配置时服务端启动会崩溃,提示
    Failed to initialize: no supported payment kinds loaded from any facilitator
  6. 填写
    .env
    文件
    STELLAR_NETWORK=stellar:testnet
    STELLAR_RECIPIENT=G... (recipient public key)
    STELLAR_SECRET_KEY=S... (payer secret key)
    OZ_API_KEY=...
  7. 运行服务
    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
.env
so you only need to do the two manual web steps afterward.
js
// 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信任线,然后生成初始
.env
文件,你只需完成后续两个手动网页步骤即可。
js
// 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.
AddressFormatUsed for
Classic asset issuer
G...
(32-byte ed25519 public key)
The
issuer
of the classic USDC asset; used when adding a trustline (
new Asset("USDC", G...)
)
SAC (Soroban Asset Contract)
C...
(32-byte contract address)
The Soroban contract the protocol invokes
transfer
on; used in payment requirements
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"
payTo
in your route config is always a classic recipient account (
G...
). The SAC address only appears if you set a custom
asset
in the price config for a non-USDC token.
Stellar上的USDC有两个地址,分别用于不同场景。混淆两者是常见错误。
地址类型格式用途
传统资产发行方
G...
(32字节ed25519公钥)
传统USDC资产的发行方;添加信任线时使用(
new Asset("USDC", G...)
SAC(Stellar资产合约)
C...
(32字节合约地址)
协议调用
transfer
方法的Soroban合约;支付要求中使用
尽可能使用导出的常量而非硬编码:
js
import { USDC_TESTNET_ADDRESS, USDC_PUBNET_ADDRESS } from "@x402/stellar";
// USDC_TESTNET_ADDRESS = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"
// USDC_PUBNET_ADDRESS  = "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75"
路由配置中的
payTo
始终是传统接收方账户(
G...
格式)。仅当为非USDC代币配置自定义
asset
价格时,才会出现SAC地址。

Mainnet checklist

主网配置清单

ConfigValue
Network ID
stellar:pubnet
RPC URLProvider-specific endpoint (see Stellar RPC providers directory)
Facilitator URL
https://channels.openzeppelin.com/x402
USDC SAC
USDC_PUBNET_ADDRESS
from
@x402/stellar
(currently
CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75
)
OZ Channels API keyRequired (channels.openzeppelin.com/gen)
FundingReal USDC on mainnet (CEX, DEX, or bridge)
Always test on testnet first. To switch a working setup to mainnet, change only the
.env
(
STELLAR_NETWORK=stellar:pubnet
,
FACILITATOR_URL=https://channels.openzeppelin.com/x402
, mainnet
OZ_API_KEY
, and a mainnet
STELLAR_RECIPIENT
); the samples derive their network from
STELLAR_NETWORK
, so no code changes are needed. Both networks require an OZ Channels API key in the
Authorization: Bearer
header.
配置项
网络ID
stellar:pubnet
RPC URL服务商提供的端点(详见Stellar RPC服务商目录
服务商URL
https://channels.openzeppelin.com/x402
USDC SAC
@x402/stellar
导出的
USDC_PUBNET_ADDRESS
(当前值为
CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75
OZ Channels API密钥必填(channels.openzeppelin.com/gen
资金来源主网真实USDC(通过中心化交易所、去中心化交易所或跨链桥获取)
请务必先在测试网完成测试。如需将已验证的测试网配置切换到主网,只需修改
.env
文件(
STELLAR_NETWORK=stellar:pubnet
FACILITATOR_URL=https://channels.openzeppelin.com/x402
、主网
OZ_API_KEY
以及主网
STELLAR_RECIPIENT
);示例代码均从
STELLAR_NETWORK
获取网络配置,无需修改代码。测试网和主网均需在
Authorization: Bearer
头中携带OZ Channels API密钥。

Key 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-v2
scheme
— The Stellar x402 scheme version. Server advertises
scheme: "exact"
+
x402Version: 2
. Don't mix v1 and v2 packages.
SAC (Stellar Asset Contract) — USDC on Stellar is a classic asset wrapped in a Soroban contract. x402 payments invoke
transfer
on the SAC. Any SEP-41 token works; USDC is the default.
Ledger expiration — Auth entries include a
max_ledger
bound. Use
latestLedger + 12
(~1 minute at 5s/ledger). Expired entries fail at settlement.
CAIP-2 network IDs
stellar:testnet
and
stellar:pubnet
. These are the exact strings the protocol expects.
授权条目签署 — 在Stellar上,x402客户端仅签署Soroban授权条目,而非完整的交易信封。服务商负责组装完整交易。这比EVM/Solana的签署方式更轻量,且客户端无需管理序列号或支付费用。
费用代付 — OZ Channels承担所有Stellar网络费用(约$0.00001/笔交易)。客户端只需持有USDC,无需持有XLM。
exact-v2
方案
— Stellar x402方案的版本。服务端会声明
scheme: "exact"
+
x402Version: 2
。请勿混用v1和v2版本的包。
SAC(Stellar资产合约) — Stellar上的USDC是封装在Soroban合约中的传统资产。x402支付会调用SAC的
transfer
方法。任何符合SEP-41标准的代币均可使用;USDC为默认选项。
账本过期 — 授权条目包含
max_ledger
上限。建议使用
latestLedger + 12
(约1分钟,基于5秒/账本的速度)。过期的授权条目在结算时会失败。
CAIP-2网络ID
stellar:testnet
stellar:pubnet
。协议要求使用这些精确字符串。

Common pitfalls

常见问题

Auth entry expired on settle
  • Symptom: facilitator returns
    isValid: false
    , error mentions ledger expiration
  • Fix: ensure client uses
    latestLedger + 12
    (or higher) as expiration; don't cache auth entries across requests
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.001
    =
    10000
    in base units.
V1/V2 package mismatch
  • Symptom: TypeScript errors or silent payment failures
  • Fix: use all
    @x402/*
    packages at the same major version. V2 is multi-chain; don't import V1
    @x402/core
    alongside V2
    @x402/stellar
    .
Missing USDC trustline
  • Symptom:
    op_no_trust
    error during settlement
  • Fix: add a USDC
    changeTrust
    operation before attempting any x402 payment (see testnet runbook above)
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
    OZ_API_KEY
    and pass it via
    createAuthHeaders
    (see the Seller example).
Trustline missing on the recipient
  • Symptom:
    op_no_trust
    during settlement, even though the client has USDC
  • Fix: the
    payTo
    account needs a USDC trustline too. The SAC
    transfer
    settles the underlying classic asset, which the recipient cannot hold without a trustline. Add
    changeTrust
    to both accounts during setup.
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/fetch
    +
    createEd25519Signer
    target Node and assume a raw secret key.
Passing a
Keypair
(or a network passphrase) to
createEd25519Signer
  • Symptom:
    TypeError: encoded argument must be of type String
    , or
    Error: Unknown Stellar network: Test SDF Network ; September 2015
  • Fix: the signer takes the raw
    S...
    secret string and a CAIP-2 network ID. Do not wrap with
    Keypair.fromSecret
    first, and do not pre-convert with
    getNetworkPassphrase
    — both are done internally.
    js
    // 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错误或静默支付失败
  • 解决方法:所有
    @x402/*
    包使用相同的主版本号。V2支持多链;请勿同时导入V1的
    @x402/core
    和V2的
    @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
    传递(详见服务端示例)。
收款方缺少信任线
  • 症状:结算时出现
    op_no_trust
    错误,即使客户端持有USDC
  • 解决方法:
    payTo
    账户也需要添加USDC信任线。SAC的
    transfer
    操作会结算底层传统资产,而接收方若无信任线则无法持有该资产。请在设置阶段为两个账户均添加
    changeTrust
    操作。
尝试在浏览器中签署授权条目
  • 症状:打包错误,或浏览器钱包无签署Soroban授权条目的API
  • 解决方法:将x402客户端部署在服务端(例如为浏览器提供Express路由),或通过Wallets-Kit / Freighter实现自定义授权条目签署逻辑。
    @x402/fetch
    +
    createEd25519Signer
    针对Node环境,假设使用原始密钥。
createEd25519Signer
传递
Keypair
(或网络密码)
  • 症状:
    TypeError: encoded argument must be of type String
    ,或
    Error: Unknown Stellar network: Test SDF Network ; September 2015
  • 解决方法:签名器接收原始
    S...
    格式密钥字符串和CAIP-2网络ID。请勿先用
    Keypair.fromSecret
    包装,也请勿提前调用
    getNetworkPassphrase
    转换 — 这些操作均由签名器内部完成。
    js
    // 错误写法
    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:
ModeOn-chain txsBest for
ChargeOne per requestPer-request payments, no pre-funding required
ChannelOne deposit + one closeHigh-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=module
Server:
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):
STELLAR_RECIPIENT
,
MPP_SECRET_KEY
,
FEE_PAYER_SECRET
(optional) Env vars (client):
STELLAR_SECRET_KEY
mode: "pull"
vs
"push"
:
  • "pull"
    — client signs auth entries, server assembles + broadcasts (default; use with
    feePayer
    )
  • "push"
    — client builds and broadcasts the transaction directly (client must have XLM for fees)
每个请求触发一次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_RECIPIENT
,
MPP_SECRET_KEY
,
FEE_PAYER_SECRET
(可选) 客户端环境变量:
STELLAR_SECRET_KEY
mode: "pull"
vs
"push"
  • "pull"
    — 客户端签署授权条目,服务端组装并广播交易(默认选项;需配合
    feePayer
    使用)
  • "push"
    — 客户端自行构建并广播交易(客户端必须持有XLM以支付费用)

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 total
1. 部署通道合约(一次性操作)   → C...合约地址
2. 客户端向通道存入USDC    → 链上交易
3. 每次请求:客户端签署承诺 → 链下操作(仅需签名)
   金额为累积值:每个签名涵盖之前所有支付 + 当前请求的金额
4. 会话结束时服务端关闭通道      → 链上交易,结算总金额

Prerequisites

前置条件

  • Deploy a one-way-channel Soroban contract to get a
    C...
    contract address
  • 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):
CHANNEL_CONTRACT
,
COMMITMENT_PUBKEY
,
MPP_SECRET_KEY
,
FEE_PAYER_SECRET
Env vars (client):
COMMITMENT_SECRET
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);
服务端环境变量:
CHANNEL_CONTRACT
,
COMMITMENT_PUBKEY
,
MPP_SECRET_KEY
,
FEE_PAYER_SECRET
客户端环境变量:
COMMITMENT_SECRET

Packages and subpath imports

包及子路径导入

bash
npm install @stellar/mpp mppx @stellar/stellar-sdk
Import pathRecommended import pattern
@stellar/mpp/charge/server
import * as stellar from "@stellar/mpp/charge/server"
— use
stellar.charge(...)
@stellar/mpp/charge/client
import * as stellar from "@stellar/mpp/charge/client"
— use
stellar.charge(...)
@stellar/mpp/channel/server
import * as stellar from "@stellar/mpp/channel/server"
— use
stellar.channel(...)
,
stellar.close(...)
,
stellar.getChannelState(...)
,
stellar.watchChannel(...)
@stellar/mpp/channel/client
import * as stellar from "@stellar/mpp/channel/client"
— use
stellar.channel(...)
@stellar/mpp/channel
Zod schema definitions for channel types
mppx
import { Mppx, Store } from "mppx"
bash
npm install @stellar/mpp mppx @stellar/stellar-sdk
导入路径推荐导入方式
@stellar/mpp/charge/server
import * as stellar from "@stellar/mpp/charge/server"
— 使用
stellar.charge(...)
@stellar/mpp/charge/client
import * as stellar from "@stellar/mpp/charge/client"
— 使用
stellar.charge(...)
@stellar/mpp/channel/server
import * as stellar from "@stellar/mpp/channel/server"
— 使用
stellar.channel(...)
,
stellar.close(...)
,
stellar.getChannelState(...)
,
stellar.watchChannel(...)
@stellar/mpp/channel/client
import * as stellar from "@stellar/mpp/channel/client"
— 使用
stellar.channel(...)
@stellar/mpp/channel
通道类型的Zod schema定义
mppx
import { Mppx, Store } from "mppx"

Testnet runbook

测试网运行指南

Steps shared with all protocols:
  1. Generate keypair + fund with Friendbot (see x402 testnet runbook in Part 1 above)
  2. Add USDC trustline
  3. 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'))"
  1. Derive the public key and fund the channel with USDC before making requests
所有协议通用步骤:
  1. 生成密钥对并通过Friendbot充值(详见第一部分x402测试网运行指南)
  2. 添加USDC信任线
  3. Circle水龙头获取测试网USDC
仅Channel模式需额外执行: 4. 部署单向支付通道合约(详见stellar-mpp-sdk中的部署脚本) 5. 生成64字符十六进制ed25519种子作为承诺密钥:
bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
  1. 导出公钥并在发起请求前为通道充值USDC

Common pitfalls

常见问题

Channel: wrong commitment key format
  • Symptom:
    Keypair.fromRawEd25519Seed
    throws or signatures fail to verify
  • Fix: the commitment key is a raw ed25519 seed as a 64-char hex string — not a Stellar
    S...
    secret key. Generate with
    crypto.randomBytes(32).toString('hex')
    .
Channel: non-cumulative amounts
  • Symptom: server rejects commitments after the first request
  • Fix: each commitment's
    amount
    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.
Channel: deposit TTL expired
  • Symptom:
    close()
    fails or channel appears drained
  • Fix: Soroban contract storage has a TTL. Close the channel before it expires, or extend storage TTL via
    bumpContractInstance
    . Don't leave channels open indefinitely.
Charge: client has no XLM for fees
  • Symptom:
    op_insufficient_balance
    or fee errors on client-submitted transactions
  • Fix: set
    mode: "pull"
    on the client and configure
    feePayer
    on the server so the server pays fees. The client only signs auth entries.
Store.memory()
in production
  • Symptom: server loses track of channel state on restart, enables double-spend
  • Fix: replace
    Store.memory()
    with a persistent store (database-backed) before going to production.
Channel模式:承诺密钥格式错误
  • 症状:
    Keypair.fromRawEd25519Seed
    抛出错误或签名验证失败
  • 解决方法:承诺密钥是64字符十六进制格式的原始ed25519种子 — 不是Stellar的
    S...
    格式密钥。使用
    crypto.randomBytes(32).toString('hex')
    生成。
Channel模式:金额未累积
  • 症状:服务端在首次请求后拒绝后续承诺
  • 解决方法:每个承诺的
    amount
    必须是截至当前的所有支付总额,而非仅当前请求的价格。服务端会跟踪已确认的最高金额承诺。
Channel模式:存入TTL过期
  • 症状:
    close()
    失败或通道显示已耗尽
  • 解决方法:Soroban合约存储有TTL。请在过期前关闭通道,或通过
    bumpContractInstance
    延长存储TTL。请勿长期保持通道开启状态。
Charge模式:客户端无XLM支付费用
  • 症状:客户端提交交易时出现
    op_insufficient_balance
    或费用错误
  • 解决方法:在客户端设置
    mode: "pull"
    ,并在服务端配置
    feePayer
    ,由服务端支付费用。客户端仅需签署授权条目。
生产环境使用
Store.memory()
  • 症状:服务端重启后丢失通道状态,可能导致双重支付
  • 解决方法:在生产环境中,将
    Store.memory()
    替换为持久化存储(如数据库驱动的存储)。