不用绑卡,边聊边付:一个 Web3 版的支付实验

大家好,我是 Sonny,一个 Web3 新手。

在一个月前,我对区块链的理解还停留在“比特币很贵”“以太坊能发币”这种模糊印象,至于 UTXO、PoW、智能合约、状态通道……这些词听起来就像天书。但越是不懂,越觉得有趣。尤其是当我听说“未来的互联网服务可以边用边付,不用注册、不用绑卡、没有中间商”,我就忍不住想:这到底怎么做到的?

为了真正搞明白这些概念,我决定动手试试。正好看到 Nervos CKB 的文档对新手挺友好,又支持灵活的脚本和原生时间锁,于是就选它作为实验平台,从零开始实现了一套简易的 Spillman 支付通道(Spillman Payment Channel),并在大模型 AI 对话场景中得到了应用——**用户在与 AI 流式对话的过程中,每收到一段回复时(chunk),就自动完成一次链下微支付 ** 。

这个过程当然磕磕绊绊——搞不清 Cell 模型、被交易签名搞到头大、甚至一度以为时间锁失效了……但每解决一个问题,就离“理解 Web3 支付”更近一步。现在,我把这个小项目开源了 Github,也想和你分享这段从“完全不懂”到“勉强跑通”的探索旅程。

不过,在详细介绍这个项目之前,我想先聊聊:什么是 Spillman Channel?它到底解决了什么问题?

什么是 Spillman Channel?

Spillman Channel是一种单向的链下支付通道协议,最早由比特币社区提出。它的核心思想很简单:

买方先在区块链上锁定一笔资金,然后在链下多次向卖方付款,最后只需卖家一次上链操作完成最终结算。

整个过程大致分为三个阶段:

  1. 创建通道

买方首先和卖方协作,构建一个带时间锁的退款交易——这笔交易的作用是:如果通道长时间未结算,买方可以在未来某个时间点(比如 7 天后)独自把全部资金拿回来。

卖方先对这笔退款交易签名(表示同意这个“保险机制”),买方保存好这个已签名的交易作为“安全凭证”。

然后,买方才会真正向一个双方共管的多签地址(通常由一个 Cell 表示)转入保证金(比如 100 CKB)。

这样一来,资金虽然被“锁住”了,但买方始终握有“兜底退出”的能力,不怕卖方后续失联。

  1. 链下支付

在通道建立后,买方可以不断签发新的“支付承诺”,比如第一次说“我总共付你 5 CKB”,第二次说“我现在总共付你 5 + 3 = 8 CKB”(相当于在上次基础上再加 3)。每一份新承诺代表的是累计应付款总额,而不是单次增量。而且,新承诺会自动“覆盖”旧的——也就是说,卖方只需要保留最新的一份,因为只有它才代表当前双方认可的最终分配状态。

  1. 结算或退款:
  • 如果一切顺利,卖方拿着最新的支付承诺上链,拿走对应金额,剩余资金退还给买方。

  • 如果卖方消失,买方也可以等一段时间后,用预先签好的“退款交易”把全部资金拿回来(通过时间锁保障)。

这种方式把高频小额支付从链上转移到链下,极大降低了手续费和延迟,特别适合像 AI 回复、视频流、API 调用这类“按量计费”的场景。

多签合约

第一次读到 Spillman Channel 的设计时,我其实有点懵——“多签地址”“时间锁”“预签名交易”……这些词听起来很高大上,但具体怎么落地?尤其是作为 Web3 新手,连 UTXO 模型都还没完全吃透,更别说写智能合约了。

但我决定从最基础的一步开始:先实现一个支持“双方签名 + 时间锁”的脚本(Lock Script)。

我先是翻了 Nervos 官方的 system-scripts,看到里面有个现成的 secp256k1_blake160_multisig_all 多签合约。但转念一想:既然要真正理解,不如自己写一个!正好看到 CKB 的虚拟机(ckb-vm)支持用 JavaScript 写合约,我心里一亮——我可是前端出身啊!用 JS 写逻辑,简直如鱼得水。

这个合约只需要支持两件事:

  1. 验证两个参与方(A 和 B)的签名:

合约会用 script.args 中对应的公钥哈希和 witnessData 中的签名来验证。

const firstSigResult = verifySignature(sign1, scriptArgs[0]);
const secondSigResult = verifySignature(sign2, scriptArgs[1]);
if (firstSigResult && secondSigResult) {
  // 验证通过!
}

虽然底层用的是 secp256k1_blake160_sighash_all(单签脚本),但通过在 JS 里连续验证两个签名,可以模拟出了 2-of-2 多签的效果——既绕过了复杂的多签脚本,又达到了同样的安全目标 Github

  1. 支持时间锁:

本来以为要自己在合约里处理时间逻辑,结果发现 CKB 在交易层原生支持 since 字段! 只要在交易的 input 上设置 since = 7天后的时间戳,这笔钱就无法在设置的时间内被交易。 这意味着:时间锁根本不需要写进合约,协议层已经帮你搞定了。

写完这个合约后,我突然发现:那些曾经让我望而却步的词——加签、验签、公钥哈希、UTXO 模型、交易结构、Cell 状态……竟然不再那么陌生了。

虽然还谈不上精通,但至少我知道它们在哪儿起作用、怎么组合、为什么这样设计。

Spillman Channel 与大模型支付场景的结合

搞定了底层合约,下一步就是:找个真正需要“小额高频支付”的场景,把它用起来。

我一开始想过视频按秒付费、API 按调用次数计费,但总觉得不够那么“实时”——直到我想到大模型聊天。

现在的 AI 聊天,尤其是流式响应(streaming),本质上就是一段一段地吐出内容:第一段可能是“好的,我来帮你分析…”,第二段是具体观点,第三段是总结……每一段都消耗算力,这不正是天然的小额高频场景吗?

于是我想:能不能在用户每收到一个文本块(chunk)的同时,自动完成一次微支付?

不需要等整轮对话结束也不依赖中心化账户体系——用 Spillman 通道,边聊边付,用多少付多少。

更重要的是,这种模式把“价值交换”变得极其细粒度:

  • 如果 AI 回复卡住了,你只付了前两段的钱;

  • 如果回答特别长,你按实际接收的内容付费;

  • 甚至未来可以动态定价——简单问题便宜,复杂推理贵一点。
    token_111

如图,在 AI 回复的同时,Hello!How can i assist you today? 被大模型提供方分成了不同的 Chunk。

Chunk1: How

Chunk2: can

Chunk3: …

链下也在每一个 Chunk 到来时根据 Token 消耗量进行支付。

用户系统:以公钥为身份,构建去中心化的登录体验

在传统 Web2 应用中,用户体系通常依赖手机号或邮箱注册,背后是一套中心化的数据库管理账号信息。但当我们进入区块链世界时,身份本身就应该由用户掌控。

我思考:既然支付是基于签名的,那为什么不能直接用用户的公钥作为唯一标识?

于是,我设计了一个极简的“去中心化用户系统”——不依赖注册、不存储密码,只认公钥。

具体流程如下:

  1. 用户在登录时输入自己的 64 位十六进制私钥(比如 0x...);

  2. 前端使用该私钥生成对应的公钥,并计算出 blake160 哈希值(即 CKB 中常用的地址格式);

  3. 私钥仅保存在本地 localStorage,绝不会上传到服务器;

  4. 公钥哈希被发送至服务端,作为本次会话的“用户 ID”;

  5. 服务端根据这个公钥建立用户档案,记录其支付通道状态、历史交易等信息。

将私钥存入 localStorage 在生产环境中是非常危险的做法! 因为任何 XSS(跨站脚本)攻击都可能窃取它,导致资金被盗。 本项目这么做纯粹是为了简化演示——省去钱包集成(如 MetaMask、JoyID 等)的复杂度,让逻辑更聚焦于支付通道本身。 真实产品中,应使用安全的钱包插件或硬件签名,绝不应在前端持久化存储私钥。

如图所示,登录界面极其简洁:只需输入私钥,即可完成身份验证。

这种设计虽然简单,但已经实现了:

  • 用户真正拥有身份控制权;

  • 系统无需维护密码表;

  • 支付行为与身份天然绑定,便于链上验证。

更重要的是,它让整个 Demo 无需注册、开箱即用——只要你有 CKB 私钥,就能立刻开始“边聊边付”。

创建支付通道:利用 UTXO 特性实现“先签名、后上链”的安全机制

接下来,我们进入核心环节——创建 Spillman 支付通道。在界面中,可以选择充值金额(如 100,000 CKB)、通道过期时间(30s 到 7 天),系统会自动换算成可用 Token 数量(例如 1 CKB = 0.01 Token)。

点击「Create Payment Channel」按钮后,背后发生了一件看似简单却极其关键的事情:

前端构建一笔“未上链”的 funding 交易,并将其结构发送给服务端,由卖家(服务方)提前签名,用于未来退款。

这一步,正是整个支付通道安全性的基石。

为什么卖家需要“先签名”?

在传统思维中,如果买家要向某个地址打款,就必须先发起一笔交易并上链。但这就带来一个严重问题:买家的钱一旦打入多签地址,就被永久锁定;因为卖家可以永远不签名退款交易,导致买家无法取回资金;而且买家无法验证卖家是否真的愿意配合关闭通道。
那有没有一种方式,能让买家先承诺付款,但不真正锁币?答案是:有,而且正是 CKB 的 UTXO 模型赋予我们的能力。

利用 UTXO 的交易 Hash

在 CKB 中,每笔交易的 tx hash 是由其输入(Inputs)、输出(Outputs)、Witness 等字段共同决定的。 最关键的一点是:只要 Inputs 不变,哪怕 input 中的交易没上链,它的 Hash 也是确定的。

这意味着:

  • 我们可以在不上链的前提下,生成一个完整的交易结构;

  • 并将这个交易的 Hash 作为后续退款交易的 Input;

  • 从而让卖家提前对“退款路径”进行签名,而不影响买家的资金自由;

具体流程是怎样的?

  1. 前端先构建 funding 交易(但不上链)
  • 输入:用户自己的 UTXO

  • 输出:一个由买家和卖家公钥共同控制的多签 Cell

  • 金额 = 用户选择的充值额度

  • 计算出这笔交易的哈希值 fundingTxHash

  1. 前端基于 fundingTxHash 构建退款交易
  • Input:指向 fundingTxHash 的第 0 个输出(即未来的多签 Cell)

  • Output:将全部资金返回给买家自己的地址

  • 设置 since 字段为通道过期时间(例如 7 天后)

  • 在 Witness 中预留服务端签名的位置

  1. 前端将这笔“未签名的退款交易”发送给服务端
  • 服务端验证:

    • 退款金额是否等于产品选择的金额一致?

    • 时间锁是否和产品选择中的一致?

    • 多签脚本是否匹配当前协议?

  • 验证通过后,服务端用自己的私钥对交易签名,并将签名返回(如下图)

  1. 前端收到签名后,组装成完整的退款交易,并可以选择本地保存
  • 这笔交易现在可以随时在通道过期后广播,无需服务端再次参与

  • 即使服务端宕机或失联,用户也能拿回资金

  1. 最后,用户点击 PayNow 才广播 funding 交易上链,同时向服务端发送上链成功,服务端确认信息无误后,通道正式激活

链下支付:每收到一个 Chuck,就更新一次资金分配

token_111
一旦支付通道创建成功,真正的“边聊边付”就开始了。
假设用户创建通道时充值了 1000 CKB,并约定 1 CKB = 1 Token。

  • 初始状态:多签地址中有 1000 CKB。

为什么这样设计?

因为 Spillman 是单向支付通道,资金只能从买家流向卖家。

所以每次“支付”,本质上是买家在说:“我现在同意把 X CKB 给你,剩下的归我。这是最新的分配,请收好。”

只要卖家手握最新承诺,未来任何时候都可以拿着它上链结算,拿走应得的部分。而买家则保留了“如果服务中断,还能按最新状态退款”的能力(通过时间锁保障)。

  • 第一段回复到来,消耗 2 Token: 前端生成第一份支付承诺:“通道内资金应分配为:买家 998 CKB,卖家 2 CKB” 用户用私钥签署这份承诺,并发送给服务端。服务端验证后保存。

  • 第二段回复到来,又消耗 3 Token(累计 5): 前端生成第二份支付承诺:通道内资金应分配为:买家 995 CKB,卖家 5 CKB” 注意:这不是“再付 3”,而是重新再建立一条新的承诺,更新为最新累计总额。

  • 后续每一段都如此,不断签署新的分配状态。

每当用户向 AI 发送一个问题,AI 会以流式(streaming)方式逐块(chunk)返回回答。

而我们的系统会在每个 chunk 到达时,自动触发一次链下支付。

但请注意:这里的“支付”并不是真的发起一笔链上交易,而是在链下签署一份新的资金分配方案——也就是 Spillman 协议中的“支付承诺”。

在界面上,这一切是透明的

用户聊天时, 输入侧会实时显示:

  • 已消耗 Token 数

  • 剩余 Token 数

  • 最近几笔支付记录

Auto Pay 开启时,每当 AI 返回一个 chunk,前端会自动为该 chunk 生成一笔链下支付承诺,由用户私钥签名后立即发送给卖家。

每一条记录背后,都是一份经过签名的链下支付凭证——轻量、即时,无需等待区块确认,也没有手续费。

当 Auto Pay 关闭时,chunk 返回后不会自动支付。但当下次用户尝试发起新对话时,前端和服务端会共同检查:是否已为最新的 chunk 完成支付。

如果尚未支付,对话将被阻断,直到用户手动完成该笔支付为止。

结算时刻:在时间耗尽前,把钱拿回来

根据 Spillman 协议的设计,一旦通道过期(since 时间到达),买家就可以单方面发起退款交易,将全部资金取回,这对卖家显然不公平。所以,我们需要一个机制:在通道即将过期前,自动完成结算。

我设计了一个后台定时任务:每分钟运行一次,扫描所有有过链下支付的支付通道,如果某个通道的剩余有效期小于 15 分钟,就立即触发结算流程。

你可以通过访问 http://localhost:3000/admin/tasks 查看该任务的状态和日志:

结算的过程就是把这个支付通道最新的那笔链下交易上链,在之前的链下支付过程中,服务端会保存交易结构和买家对这笔交易的签名,所以在上链时只要加上卖家的签名就可以成功上链。

同时,我在用户界面也增加了手动结算的功能——用户可以随时终止通道,拿回剩余的保证金。这其实不属于 Spillman Channel 协议的一部分,但我觉得,从用户角度出发,能随时随地取回自己的钱,是一件很安心、也很开心的事。在 Web2 中,这种体验几乎不存在;而在 Web3 里,它却可以轻松实现。

取回保证金:没用过?直接退回来!

当用户从未与卖家发生过任何链下支付(即没有使用过服务),且支付通道已过期时,买家理应能取回全部保证金。

为了让这个场景更容易被真实体验到,我加了一个定时任务 check-expired-channels:服务端每 10 分钟检查一次所有通道,如果发现某个通道已过期且从未有过链下支付记录,就会将其状态标记为 expired

这样一来,用户在前端界面上就能直接看到“提现”按钮,一键操作,无需手动构造交易或处理复杂的签名流程。

需要说明的是,这个定时任务和前端交互并不属于 Spillman Channel 协议本身,纯粹是我为了让大家更方便、更直观地体验“未使用即退款”这个功能。

Web2 vs Web3

我之前在 Web2 领域做过一段时间的国际支付,对接过几十个国家的本地支付方式:信用卡、网银转账、电子钱包、先买后付……五花八门,规则各异。

但有一个共同痛点始终绕不开:用户体验割裂,信任成本高。

比如,很多用户因为安全顾虑,不愿意“保存信用卡”用于下次支付。结果每次都要重新输入卡号、CVV、短信验证码——流程繁琐,支付成功率自然就低。

而作为支付服务商,我们还得处理 PCI 合规、风控拦截、拒付纠纷……整个支付链条既不透明,也不高效。

有人可能会问:“你这个 Demo 不就是 Web2 充值模式换了个壳吗?现在平台不也支持先充钱再按 token 扣费?”

其实还是有很大差异的。

在 Web2 中,用户充值的钱进了平台的账本,平台说了算:能不能退、怎么退、有效期多久,全由它定。一旦平台跑路或冻结账户,用户的钱就没了。

而在 Web3 方案里:

  • 用户的钱始终在用户自己控制的多签地址里,平台无法动用;

  • 每一笔消费都需要用户明确签名,没有“后台悄悄扣款”;

  • 退款规则写在链上合约里,到期自动生效,不需要客服审批;

  • 前端 + 后端代码全部开源,逻辑透明,无法作弊。

做完这个 Demo 之后,我突然觉得:原来支付可以这么“干净”。

当然,这并不是说 Web3 支付已经完美——对普通用户来说,私钥管理、Gas 费、交易确认时间……仍是不小的门槛。

但它的方向让我觉得值得探索:把控制权还给用户,把规则写进代码,把信任建立在透明之上。

有意思的是,这个 Demo 本身其实更像是 Web2 与 Web3 的混合体:

  • 聊天界面、定时任务、用户会话管理……全是 Web2 的熟悉工程;

  • 而资金结算、支付通道、签名验证、时间锁……则完全跑在 CKB 的 Web3 基础上。

它没有否定 Web2 的开发效率和交互体验,也没有神化 Web3 的去中心化理想,而是尝试在两者之间找到一个实用的平衡点:

用 Web2 的体验,承载 Web3 的价值。

也许在很久很久之后的支付,不是 “Web2 vs Web3”,而是 “Web2 + Web3”

中心化服务提供便利,去中心化协议保障权利。

而这,正是我想通过这个小项目探索的可能。

写在最后

这个 Demo 肯定不完美,甚至有点“玩具”性质——毕竟这是我入门 Web3 的第一个实战项目。

很多代码其实是靠 AI 辅助生成的(是的,我一边学一边用 AI 当“编程搭档”),过程中踩了不少坑,也改了很多遍。

但正是通过这个项目,我真正理解了那些曾经听起来很遥远的概念:

加密与签名、验签流程、UTXO 模型、CKB 的 Cell 结构、时间锁、链下状态通道、甚至用 JavaScript 写链上合约……

它们不再是文档里的术语,而是我亲手调试过、验证过、甚至“被它折磨过”的真实逻辑。

如果你也对 AI、支付、或者区块链 感兴趣,欢迎来 GitHub 看看代码:

  • 可以提个 issue 一起讨论

  • 也可以直接 clone 下来跑一跑

  • 说不定你还能帮我发现几个 bug :sweat_smile:

毕竟,所有伟大的系统,都是从一个“不完美的 Demo”开始的。

15 Likes

Hi Sonny, thanks for sharing this intriguing demo with your exploration and thoughts!

如果有计划在现有基础上对这个demo进行迭代的话,欢迎来申请 Spark 星火计划!

2 Likes

大神,能做comfyui的插件吗,它是开源的,接入fiber的话,comfyui出图片和视频按单个付款。图片和视频的需求更大。

我还不太了解 Fiber,目前还处在一个学习的阶段,不过我看 Fiber 跟 Lightning Network 的概念有点类似,后续如果有机会了解的话,跟大家也会分享一下。

牛啊姐妹,我十年前就知道这个,但到现在也没啥进步,你一个月后就搓出来项目了。

下次还有这种机会的话请私信我 :laughing:

1 Like

请问怎么保证过期商家一定会签名,并把钱退回给用户。这个方案中没有太看懂。我觉得在AI场景中,比边聊边付精准的,流式炒币,丢1块钱,马上开始赚钱,亏了,继续投币 :grinning_face:

@president_tin

可以参考这个流程,买家会把退款交易结构发送给卖家,卖家在确认交易无误后签名,此时买家拥有了已签名的退款交易,在买家拿到签名后才会真正向多签地址打钱,这也是整个支付通道的核心。

不好意思,技术小白。我理解是前面先做了一个付款凭证,且只有双方确认,这笔钱才能收到。这里得意思是说,如果这笔钱付出去了,但是存在退款,这个是从一个服务端来验证得,这个服务端是怎么保证商家会愿意签名退款。或者这么说,是不是只要我签名付款了,发生最终要退款得环节,还是需要双方协商来处理。这里只能保证的是,我承诺付的一定会付过去,如果对方超时了,卖家也会收到钱。

@president_tin 哈哈,没关系,能把一件事给不了解技术的同学讲明白也代表我是真正理解了这个项目,下面我尽量用通俗的语言描述下大致的流程:

第一步:开通道(建立信任)

你想用 AI 服务,先跟商家说:“我准备交 100 块押金,最多用 7 天。”

商家说:“行,但得按规则来。”

于是你写一张 “退款保证书”

“7 天后,如果我没消费,我能拿回全部押金。”

你让商家在这张保证书上签字。

只有等他签完字,你才真的把 100 块放进共管账户(就像一个两人共管的保险箱)。

——这时候,你的钱才真正“锁进去”。

第二步:边用边更新“欠条”

你开始聊天,AI 回了第一段话,算 2 块钱。

你就写一张 “欠条”

“目前我用了 2 块,剩下 98 块归我,2 块归商家。”

你在这张欠条上签字,发给商家。

商家收到后,就保存这张最新的欠条

接着 AI 又回一段,花了 3 块(累计 5 块),你再写一张新欠条:

“现在我用了 5 块,95 块归我,5 块归商家。”

第三步:怎么拿钱?

  • 商家结算:商家随时可以用你签的最新欠条,去共管账户里拿走他应得的钱;这里需要说明的是,商家有强烈的动机在过期前去结算,否则过期后,买家可以自己取出全部保证金,因为之前的退款保证书已经有商家的签名了,买家只需要签他自己的名字就可以取出。

  • 如果买家 7 天内没有消费:7 天一到,你直接凭那张“退款保证书”把剩下的钱全拿回来;

  • 如果商家跑路:到期后你照样能拿回全部押金,因为之前已经有退款协议了。

所以,整个过程买家始终掌握主动权

  • 钱没保障不打;

  • 用多少付多少

“我理解是前面先做了一个付款凭证,且只有双方确认,这笔钱才能收到。这里得意思是说,如果这笔钱付出去了,但是存在退款,这个是从一个服务端来验证得”

这里的不是先做了付款凭证,而是先有一个退款凭证,

“这个服务端是怎么保证商家会愿意签名退款”

这里的服务端其实就是商家,商家这里没有不愿意的动机,因为这笔钱只有在用户没消费并且过期后才能取出。如果用户消费了,那么商家可以在通道过期前上链来获得商家应有的那部分(从多签地址里出钱)。

“是不是只要我签名付款了,发生最终要退款得环节,还是需要双方协商来处理。这里只能保证的是,我承诺付的一定会付过去,如果对方超时了,卖家也会收到钱。”

退款环节的流向是从保险箱(多签地址)到买家,是需要双方的签名,但是之前卖家已经签过名了,所以在退款时只需要再加上买家一个人的签名就可以生效了。

1 Like