本文介绍了 Quicknode 的 Machine Payments Protocol(MPP)会话机制,说明如何通过一次打开支付通道、后续使用离线签名凭证来高效访问 API。文中以 TypeScript 多链余额查询脚本为例,展示了跨 15 条 EVM 链的会话创建、RPC 调用、费用结算与退款流程。
Machine Payments Protocol(MPP)让你可以在 HTTP 请求中内联支付 API 访问费用。无需账户,无需 API key。只需要一个带有稳定币余额的钱包。
Session 更进一步。你只需打开一次 payment channel,然后在每次请求中复用它。每次调用都通过一个小型签名凭证支付,因此不需要每次都进行链上交易,请求也能保持快速。
在本指南中,你将构建一个 TypeScript 脚本:打开一个单独的 MPP session,使用 Quicknode 的 MPP endpoints 查询 15 条 EVM 链上的原生代币余额,并输出一份带有成本拆分的格式化报告。
这非常适合 session,因为这个脚本会在一次运行中跨不同网络发起多次 RPC 调用。与其为每次调用分别付费,不如让所有请求都通过同一个 payment channel 流转。你只需支付一次初始化成本,然后在其余请求中复用它。
Quicknode 支持覆盖所有受支持链的 MPP session。同一个查询 Ethereum 的 session,也可以在无需额外配置的情况下查询 Base、Arbitrum、Polygon 等。
MPP 是 Quicknode 上两种 agentic payment protocol 之一
Quicknode 支持两种基于钱包的无账户 RPC 访问支付协议:MPP(本指南使用)和 x402(Coinbase 推出的开放标准)。两者都允许脚本和 AI agents 以内联方式为 API 调用付费,无需账户或 API key,但它们在 wire format、session 机制和支持的支付方式上有所不同。有关并排比较,请参阅 Agentic Payments overview;如需协议细节,可直接查看 x402 docs 和 MPP docs。
mppx SDK 创建和管理 payment sessionMachine Payments Protocol 是一个 向 IETF 提议的开放标准,它为任何 HTTP endpoint 添加内联支付。它旨在解决一个特定问题:程序化客户端(agents、脚本、bots)在没有人工先行设置计费的情况下,没有很好的方式为 API 访问付费。
传统支付流程依赖结账表单、浏览器会话和可视化 CAPTCHA。MPP 用基于 HTTP 402 Payment Required 的机器可读支付协商取代了这些。
该协议遵循 challenge-response 模式:
402 Payment Required 和一个 WWW-Authenticate: Payment header,描述价格、接受的货币以及可用的支付方式。Authorization: Payment header 重新发送原始请求。Payment-Receipt header 的响应。像 mppx 这样的客户端库会自动处理第 2 到第 4 步。从你的代码视角看,请求就是直接生效了。
MPP 定义了两种控制计费方式的 payment intent:
| Charge | Session | |
|---|---|---|
| 模式 | 每个请求一次性支付 | 通过 payment channel 按使用付费 |
| 结算 | 每个请求一笔链上交易 | 链下 voucher,链上仅在打开/关闭时结算 |
| 验证延迟 | 数百毫秒(链上) | 微秒级(受 CPU 约束的 ecrecover) |
| 最适合 | 偶尔查询、简单集成 | 高频请求、批量工作流、agents |
关键区别在于支付验证的方式。对于 charge,每个请求都会触发一次链上交易。对于 session,服务器通过一次 ecrecover 调用验证签名 voucher,在关键路径中不需要 RPC 调用或数据库写入。这意味着服务器在 session 期间完全不接触链,因此吞吐量受服务器 CPU 而不是区块链共识限制。
在 Quicknode 的 MPP endpoints 上,这也带来了具体的成本节省:charge 请求每次 $0.001,而 session 请求每次 $0.00001。对于一个在一次运行中跨不同网络发起 15+ 次 RPC 调用的脚本来说,session 是自然选择:
| 查询的链数 | Charge($0.001/req) | Session($0.00001/req) |
|---|---|---|
| 10 | $0.010 | $0.0001 |
| 25 | $0.025 | $0.00025 |
| 50 | $0.050 | $0.0005 |
在进入代码之前,理解 session 在底层如何工作很重要。下图展示了我们的多链余额检查器的完整流程:
Tempo Chainmpp.quicknode.comScriptTempo Chainmpp.quicknode.comScript后续请求仅使用链下 vouchersSession closePOST /session/ethereum-mainnet (eth_getBalance)402 Payment Required (challenge)将 PathUSD 存入托管合约(链上)Channel openedRetry with voucher(1)200 OK (balance result)POST /session/base-mainnet + voucher(2)200 OKPOST /session/arbitrum-mainnet + voucher(3)200 OK...(其余链)Close session在链上结算最终 voucher退还未使用的 deposit
Payment channel:一个链上托管合约,客户端预先存入资金。这一步只在 session 开始时发生一次。
累积 vouchers:每个请求都包含一个签名消息,表示“我目前已消费到 X 总额”。Voucher 是累积式的,而不是增量式的。服务器只需要最新的 voucher 就能领取全部应得资金。例如,在三次各 $0.00001 的请求之后:voucher(1) = 10,voucher(2) = 20,voucher(3) = 30 个原子单位。
多网络 session:单个 session 实例可处理跨多个 /session/:network endpoint 的请求。session 负责管理 payment channel;网络 slug 决定请求被代理到哪个 Quicknode RPC backend。
结算:当 session 关闭时,服务器会在链上结算最终 voucher。客户端会收到任何未使用 deposit 的退款。
完整脚本位于 qn-guide-examples 仓库中(直接查看源文件)。克隆它并安装依赖:
git clone https://github.com/quiknode-labs/qn-guide-examples.git
cd qn-guide-examples/mpp/multichain-balance-checker
npm install
你也可以将 index.ts 文件复制到你自己的项目中,并安装所需依赖。
该项目使用两个运行时依赖:
| Package | 用途 |
|---|---|
mppx |
MPP 协议客户端(session 管理、voucher 签名) |
viem |
钱包操作和余额格式化 |
以及两个开发依赖:用于直接运行 TypeScript 的 tsx,和用于类型检查的 typescript。
脚本会查询多个 EVM 链上的原生余额。每条链都定义了其 MPP network slug、显示名称、原生代币符号和小数精度:
const CHAINS = [\
{ slug: 'ethereum-mainnet', name: 'Ethereum', symbol: 'ETH', decimals: 18 },\
{ slug: 'base-mainnet', name: 'Base', symbol: 'ETH', decimals: 18 },\
{ slug: 'arbitrum-mainnet', name: 'Arbitrum', symbol: 'ETH', decimals: 18 },\
{ slug: 'optimism-mainnet', name: 'Optimism', symbol: 'ETH', decimals: 18 },\
{ slug: 'matic-mainnet', name: 'Polygon', symbol: 'POL', decimals: 18 },\
{ slug: 'worldchain-mainnet', name: 'World Chain', symbol: 'ETH', decimals: 18 },\
{ slug: 'bsc-mainnet', name: 'BNB Chain', symbol: 'BNB', decimals: 18 },\
{ slug: 'fantom-mainnet', name: 'Fantom', symbol: 'FTM', decimals: 18 },\
{ slug: 'celo-mainnet', name: 'Celo', symbol: 'CELO', decimals: 18 },\
{ slug: 'xdai-mainnet', name: 'Gnosis', symbol: 'xDAI', decimals: 18 },\
{ slug: 'zksync-mainnet', name: 'zkSync Era', symbol: 'ETH', decimals: 18 },\
{ slug: 'scroll-mainnet', name: 'Scroll', symbol: 'ETH', decimals: 18 },\
{ slug: 'linea-mainnet', name: 'Linea', symbol: 'ETH', decimals: 18 },\
{ slug: 'mantle-mainnet', name: 'Mantle', symbol: 'MNT', decimals: 18 },\
{ slug: 'blast-mainnet', name: 'Blast', symbol: 'ETH', decimals: 18 },\
]
这里的 network slug 是关键。它映射到 https://mpp.quicknode.com/session/:slug 的 MPP session endpoint。URL 中的 /session/ 前缀就是告诉服务器使用 session intent 而不是 charge intent。
你可以按需添加或移除链。Quicknode 的 MPP endpoints 支持 所有受支持网络。你也可以动态获取完整的受支持 slug 列表:
curl https://mpp.quicknode.com/networks
注意,你查询的链与支付网络是解耦的。你的 session 运行在 Tempo 区块链上(testnet 或 mainnet),但它可以访问任何受支持链上的 RPC endpoints。
脚本需要一个钱包来为 payment channel 签名 vouchers。它可以使用环境变量中的现有私钥,也可以自动生成一个新的:
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
const privateKey = (process.env.MPPX_PRIVATE_KEY as `0x${string}`) || generatePrivateKey()
const account = privateKeyToAccount(privateKey)
当密钥自动生成时,脚本会将其保存到 .env 文件中(带有 chmod 600 权限),以便后续运行复用同一个钱包:
import { existsSync, writeFileSync } from 'fs'
if (isGenerated && !existsSync(envPath)) {
writeFileSync(
envPath,
[\
'# 由 multichain balance checker 自动生成',\
'# 此文件已加入 gitignore —— 切勿提交私钥。',\
`MPPX_PRIVATE_KEY=${privateKey}`,\
'',\
].join('\n'),
{ mode: 0o600 },
)
}
在 testnet 使用时,脚本会自动使用 Tempo testnet faucet 为钱包注资。这会给钱包提供 PathUSD(testnet 稳定币)以及 Tempo Moderato(chain ID 42431)上的 gas:
async function fundWallet(address: string): Promise<void> {
const res = await fetch('https://docs.tempo.xyz/api/faucet', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address }),
})
if (res.ok) {
console.log('Faucet: Request accepted — waiting for onchain confirmation...')
await waitForFunding(address)
}
}
waitForFunding 函数会轮询 Tempo Moderato 上的 PathUSD 合约,以在继续之前确认余额已经到账:
const TEMPO_RPC = 'https://rpc.moderato.tempo.xyz'
const PATHUSD_ADDRESS = '0x20c0000000000000000000000000000000000000'
async function getPathUSDBalance(walletAddress: string): Promise<bigint> {
const data = '0x70a08231' + walletAddress.slice(2).padStart(64, '0')
const res = await fetch(TEMPO_RPC, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0', id: 1,
method: 'eth_call',
params: [{ to: PATHUSD_ADDRESS, data }, 'latest'],
}),
})
const json = (await res.json()) as { result?: string }
return BigInt(json.result ?? '0x0')
}
这种零摩擦设置意味着任何人都可以克隆仓库并运行脚本,而无需注册任何东西或手动为钱包注资。
info
Tempo testnet 钱包的生命周期上限为每个 intent 10,000 次请求(charge 和 session 分开计数)。如需无限制使用,请切换到由 mainnet 资助的钱包。请参阅 Going to Production。
核心 session 逻辑就在这里。脚本使用 tempo.session() 创建一个 MPP session,然后遍历每条链,通过 session 发送 eth_getBalance 请求:
import { tempo } from 'mppx/client'
const session = tempo.session({
account,
maxDeposit: '1', // 1 PathUSD — 可覆盖 100,000 次每次 $0.00001 的请求
})
maxDeposit 参数设置 payment channel 的总预算。session 关闭时,你没花完的部分会返还给你。
info
如果 channel deposit 在 session 中途耗尽,mppx 客户端可以通过额外的链上 deposit 为 channel 补充资金,而无需关闭并重新打开 session。对于我们的 15 链脚本,即使很小的 deposit 也足够了,但如果你在构建需要发起成千上万次请求的长运行应用,这会很有用。
对于每条链,脚本都会使用标准 JSON-RPC eth_getBalance payload 调用 session.fetch()。session 会自动处理所有 MPP 机制:
const MPP_BASE_URL = 'https://mpp.quicknode.com/session'
for (const chain of CHAINS) {
const response = await session.fetch(`${MPP_BASE_URL}/${chain.slug}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'eth_getBalance',
params: [WALLET_ADDRESS, 'latest'],
}),
})
const data = (await response.json()) as { result?: string; error?: { message: string } }
// 将十六进制余额解析为人类可读格式
const balance = formatBalance(data.result!, chain.decimals)
}
在第一次请求中,session.fetch() 会处理完整的 402 challenge-response 流程:接收 challenge、将 PathUSD 存入链上托管合约,并使用签名 voucher 重试。这大约需要 500ms。
在每一次后续请求中,session 只会递增 cumulative voucher,并将其包含在 Authorization header 中。服务器使用单次 ecrecover 操作验证 voucher(无需链上交易),因此响应几乎没有支付开销。
脚本还包含了对 404 响应的重试,以处理临时的 MPP 路由问题:
if (response.status === 404) {
response = await session.fetch(`${MPP_BASE_URL}/${chain.slug}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: rpcBody,
})
}
eth_getBalance 响应返回的是一个十六进制字符串,表示以 wei(或该链对应的最小单位)计的余额。脚本会将其转换为人类可读的小数:
import { formatUnits } from 'viem'
function formatBalance(hexBalance: string, decimals: number): string {
const value = BigInt(hexBalance)
return parseFloat(formatUnits(value, decimals)).toFixed(4)
}
所有查询完成后,脚本会关闭 session 以触发链上结算。服务器会结算最终的 cumulative voucher,并将未使用的 deposit 退回到钱包:
// 在关闭前捕获 session 数据
const channelId = session.channelId
const cumulativeSpend = session.cumulative
console.log('Closing session and settling onchain...')
const receipt = await session.close()
然后脚本会打印一份摘要,显示总成本、退款金额和结算详情:
printSummary(
results,
totalRequests,
channelId,
cumulativeSpend,
receipt?.txHash,
);
脚本还将整个查询循环包裹在 try/catch 中,并尝试即使在出错时也关闭 session,以避免 payment channel 一直保持打开状态:
try {
// ... query loop ...
await session.close()
} catch (err) {
console.error('Fatal error:', err instanceof Error ? err.message : err)
try {
await session.close()
console.log('Session closed after error.')
} catch {
console.log('Could not close session.')
}
process.exit(1)
}
一切就绪后,运行脚本:
## 使用自动生成的 testnet 钱包运行(零设置)
npx tsx index.ts
## 或通过 .env 文件提供你自己的私钥
echo "MPPX_PRIVATE_KEY=0x..." > .env
npx tsx --env-file=.env index.ts
你应该会看到如下输出:
Multichain Balance Checker (via MPP Session)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Wallet: 0x1234...abcd (auto-generated)
Target: 0xd8dA...6045
Chain | Balance | Status
──────────────────┼───────────────────────┼──────────
Ethereum | 1.2345 ETH | ok
Base | 0.5000 ETH | ok
Arbitrum | 0.0000 ETH | ok
Optimism | 2.1000 ETH | ok
Polygon | 150.0000 POL | ok
BNB Chain | 0.0312 BNB | ok
... | ... | ...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Session Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Chains queried: 15 (15 ok, 0 errors)
Total RPC calls: 15
Session cost: 0.000150 PathUSD ($0.00015)
Channel deposit: 1.000000 PathUSD
Refunded: 0.999850 PathUSD
Channel ID: 0xdef456...abc123
Settlement tx: 0xabc123...def456
一旦 session 关闭,你就可以在区块浏览器上验证结算。输出中的 Settlement tx hash 链接到最终的链上交易。Tempo Moderato 请使用 testnet explorer,生产运行请使用 mainnet explorer。
下方截图展示了结算交易的样子:

红框显示与 payment channel 相关的两笔余额转移:session 打开时向托管合约发送的 1 PathUSD,以及结算时退回的 0.99985 PathUSD 退款。橙框显示作为结算交易一部分支付给服务器的实际 session 成本(0.00015 PathUSD)。
你可以通过编辑 index.ts 顶部的 WALLET_ADDRESS 常量来更改要检查的钱包地址:
const WALLET_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' // 任意地址
脚本默认使用 testnet,以便实现无摩擦上手。生产使用时会有如下变化:
| Testnet(默认) | Mainnet | |
|---|---|---|
| 支付网络 | Tempo Moderato(chain ID 42431) | Tempo mainnet(chain ID 4217) |
| 货币 | PathUSD | PathUSD 或 USDC.e |
| 请求上限 | 每个钱包生命周期 10,000 次 | 无限 |
| 钱包注资 | 通过 faucet 自动注资 | 需要手动注资 |
要切换到 mainnet,请提供一个在 Tempo mainnet 上持有 PathUSD 或 USDC.e 的钱包密钥。mppx 客户端会自动检测可用余额,并从 402 challenge 中选择正确的支付方式。
这个余额检查器刻意保持简单,以便将重点放在 session 机制上。关键要点是这个模式:打开一个 session,在不同链上发起多次 RPC 调用,然后关闭并结算。由于单个 MPP session 可以处理所有 network slug 的请求,你可以在不更改 session 设置的情况下,将脚本扩展为在任何受支持链上调用任意 RPC 方法。例如,你可以通过将 eth_getBalance 替换为对代币合约 balanceOf 函数的 eth_call,来增加 ERC-20 token 余额检查。
你构建了一个多链余额检查器,通过单个 MPP session 查询 15 条 EVM 链。需要记住的关键模式是:打开一个 payment channel,使用链下 vouchers 在任意受支持链组合上发起所需数量的 RPC 调用,然后关闭并结算。session 自动处理了所有支付机制,从最初的 402 challenge 到 voucher 签名,再到最终结算和退款。
这种模式适用于任何需要发起多次 RPC 调用的工作流:portfolio trackers、监控脚本、数据管道,或需要跨网络读取链上状态的 AI agents。Session 让这些工具能够以程序化方式为访问付费,无需账户或 API key,同时将支付开销保持在关键路径之外。
要了解更多关于 MPP、Tempo 和 Quicknode 的 MPP 集成,请查看以下资源:
什么是 MPP,它与传统 API key 身份验证有何不同?
Machine Payments Protocol(MPP)是一个开放标准,它通过 402 Payment Required 状态码为 HTTP endpoints 添加内联支付。你无需注册账户和管理 API key,而是使用钱包中的 stablecoin 按请求付费。这使得脚本、agents 和 bots 能够无需人工介入的计费设置,立即开始使用 API。
我应该什么时候使用 MPP session,而不是按请求的 charge 支付?
当你的脚本或应用在单次运行中发起多次 RPC 调用时,请使用 session。Session 会在链上打开一个 payment channel,并对后续请求使用链下 vouchers。Voucher 验证是一个受 CPU 约束的签名检查,增加的开销只有微秒级,而链上 charge 支付则需要数百毫秒。在 Quicknode 的 MPP endpoints 上,session 的单次请求成本也显著更低。如果你少于 10 次请求,charge 会更简单。
使用 MPP endpoints 需要 Quicknode 账户吗?
不需要。MPP endpoints 不需要账户、API key 或注册。你只需要一个持有 stablecoin(或 testnet token)的钱包。支付会随着每个 HTTP 请求内联完成。
我可以通过 MPP session 查询像 Solana 这样的非 EVM 链吗?
Quicknode 的 MPP endpoints 支持 Solana 和其他非 EVM 链。你可以通过更改 URL 中的 network slug,使用同一个 session 实例查询任何受支持链。例如,使用 /session/solana-mainnet 通过同一个 payment channel 查询 Solana RPC endpoints。
如果我的脚本在关闭 session 之前崩溃了,会发生什么?
如果 session 没有被显式关闭,payment channel 会一直保持打开,直到超时。服务器仍然可以结算它收到的最后一个 voucher。为避免这种情况,脚本会将查询循环包裹在 try/catch 中,并尝试即使在出错时也关闭 session。
testnet 的请求限制是多少,如何切换到 mainnet?
Quicknode 的 MPP endpoints 使用 Tempo Moderato 上的 PathUSD,允许每个 intent 最多 10,000 次免费 testnet 请求(charge 和 session 分开计数)。如需无限制使用,请提供一个在 Tempo mainnet(chain ID 4217)上持有 PathUSD 或 USDC.e 的钱包。
如果你有任何反馈或新的主题请求,请告诉我们。我们很乐意听到你的声音。
- 原文链接: quicknode.com/guides/age...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!