第 27 章 · Crypto Product 为什么不同于 Web2 Product
为什么 Crypto 产品不能照抄 Web2 的流程?
- 练习的能力
- BuilderSystem ThinkingAI Literacy
- 动手
- 把一个 DApp 的首次使用流程逐步截图,标出每一次签名到底授权了什么。
- AI Lab
- 让 AI 生成一份功能的 PRD 草稿,人工补上它漏掉的失败路径:交易失败、卡住、被抢跑。
一个现实问题
一个做了八年 Web2 产品的人,加入了一个链上团队。
他接到的第一个需求很简单:做一个「一键买入」按钮。用户点一下,把稳定币换成另一种资产。他照着自己最熟的流程做了一遍——加载态、进度条、成功页、失败重试、埋点、A/B 测试入口,一应俱全。
上线两周,客服和数据里冒出来六件事:
- 有人点了按钮完全没反应。查下来是钱包里没有用来付手续费的原生代币——他有钱买东西,但没钱发起这笔交易。
- 有人点了三次。第一次点完等了四十秒没动静,以为卡了,又点了两次。三笔交易全部上链,全部成交。
- 有人在成功页出来之后来问:「我什么时候能取消?」
- 有人转错了地址,来找客服退款。
- 有人在钱包里看到一个弹窗,上面是一串十六进制,他点了「确认」。三周后钱没了。查下来,他那次点的是一个无限额度的授权。
- 有人抱怨:「我看到的价格是 1.02,成交价是 0.97。」 中间发生了什么,界面上什么都没写。
六件事在 Web2 里都有标准解法:预检余额、按钮防抖、撤单接口、客服退款、权限弹窗写人话、价格锁定。这里一个都不成立。
不是因为这个人不够好,而是因为他熟悉的那套流程建立在三个前提上,而这三个前提在链上全部不成立。
这一章要解决的问题是:这三个前提分别是什么,它们不成立之后,产品设计要改哪些地方。
思想实验
不要一次性想 Crypto。一次拿掉一个前提,看产品会变成什么样。
第一步:把「撤销」从你的产品里删掉。
不是隐藏这个按钮,是让它在物理上不存在。用户点了确认,事情就发生了,任何人都无法逆转——包括你、包括你的数据库、包括法院。
你会发现很多设计开始塌方:
- 确认页从一个过场变成了主界面。 在可撤销的世界里,确认页是礼貌;在不可撤销的世界里,它是最后一道防线。
- 客服从「解决问题」变成「解释问题」。 你的客服话术库里有一半在讲怎么帮用户补救,这一半全部作废。
- 你不能用线上流量做试错。 Web2 产品经理最依赖的工具之一是小流量灰度:出问题就回滚。这里回滚不了,出问题的那部分用户的钱是真的没了。
- 新手引导必须前置。 不能让用户「先试试看,不对再改」。
第二步:让每一个动作都需要用户掏出一把钥匙,当场签名。
这把钥匙不在你这里,你无法代替他,也无法替他保管。每一次签名,钱包会弹出一个窗口,显示一段他多半看不懂的内容,让他自己决定按不按。
于是又塌了几处:
- 步骤数变成了一个必须被压缩的成本。 在 Web2 里多一步是体验问题,在这里多一次签名是流失点加风险点。
- 你写的文案不一定是用户看到的文案。 弹窗是钱包渲染的,不是你的界面。你只能影响它,不能控制它。
- 「记住我的选择」这个设计变得极度危险。 它在链上对应的是「一次授权,永久有效,额度无限」。
第三步:允许任何人在你不知情的情况下,把别人的产品接到你的产品上。
不需要申请,不需要签合同,不需要你同意。他读你的合约接口,写一段代码调用它,就接上了。
这一步塌得最有意思,因为它同时带来最大的好处和最大的麻烦:
- 分发可以在你不做任何事的情况下发生。 别人把你接进他的产品,他的用户就成了你的用户。
- 你的使用假设会被打破。 你以为用户是一个人,实际来的是一个合约;你以为调用是一笔一笔来的,实际是一次交易里连续来一百笔。
- 你无法单方面改接口。 改了,接在你上面的那些东西会一起坏,而你不知道有谁接了。
三步做完,你手上的已经不是一个「加了区块链的 Web2 产品」,而是一个约束完全不同的产品。
你来决定
现在做一个具体功能:一键复投。用户在你的产品里有一笔收益,点一下,把收益重新投进去。链上要做三件事:领取、授权、存入。
观察结果
四个选项指向同一句话:
链上产品的难点不在功能,在状态与信任的边界被重新划过一次。
Web2 产品的很多默认设计,其实是建立在三个隐含前提上的。把它们和链上现实并排放:
| Web2 的隐含前提 | 链上的现实 | 产品必须补什么 |
|---|---|---|
| 出错了可以改 | 一旦确认,无人能改 | 确认前的信息完整度、模拟预演、金额与地址的二次核对 |
| 我的服务器代表用户行动 | 每一步都要用户本人签名 | 压缩签名次数、翻译签名内容、限死授权边界 |
| 我控制我的接口和我的用户 | 任何人可以接入,任何调用都可能来自合约 | 接口稳定性承诺、异常调用的处理、对组合场景的假设检查 |
| 请求成功等于事情完成 | 交易有多个中间状态,还可能被重排 | 一套完整的状态机与等待期文案 |
| 用户免费使用 | 每一次写操作都要付费 | 费用预检、失败退款的解释、代付机制的取舍 |
第四行和第五行是两个额外的落差,很容易被漏掉。
第四行:Web2 的请求要么成功要么失败,中间态只是「加载中」。链上的一笔交易可以处于「已签名未广播」「在内存池里等」「被打包了但还没最终确定」「被重排掉了」几种状态,每一种对用户意味着不同的事。
第五行:链上没有免费操作。这意味着「随便试试」这个行为在你的产品里是有价格的,而定价会改变用户的行为:他们会犹豫、会攒着一起做、会在手续费高的时候不来。
建立模型
三个不可协商的约束
- 不可逆
- 要签名
- 可组合
| 约束 | 它删掉了什么 | 它逼出什么设计 |
|---|---|---|
| 不可逆 | 撤销、退款、回滚、灰度试错 | 确认前置、模拟预演、限额、白名单 |
| 要签名 | 服务端代用户行动、静默续期 | 压缩步数、翻译内容、限死额度与期限 |
| 可组合 | 对调用方的控制、接口的自由变更 | 稳定接口、防御性假设、对批量与合约调用的处理 |
第三个约束最容易被当成纯技术问题,它其实是一个产品定位问题。可组合意味着你的产品既可能是别人的入口,也可能是别人的零件。 这两种定位需要完全不同的东西:做入口要好用,做零件要接口稳定、文档清楚、行为可预测。多数团队没想清楚自己要做哪一个,结果两头都不够好。
一笔交易的六种状态
这是产品侧必须内化的状态机。每一格都要有对应的界面和文案,漏掉任何一格,用户就会自己脑补,而他脑补的通常是最坏的那种。
- 构造
- 待签名
- 已广播
- 待确认
- 已确认
- 最终
| 状态 | 用户此刻在经历什么 | 这一格最容易出的产品错 |
|---|---|---|
| 构造 | 界面在算金额、路径、费用 | 显示的报价和最终成交价没有说明差异来源 |
| 待签名 | 钱包弹窗打开了 | 自己界面上没有解释这次签名是干什么的 |
| 已广播 | 交易进了网络,还没被打包 | 界面没给交易凭据,用户无法自己去查 |
| 待确认 | 被打包了,确认数还不够 | 显示「成功」——这是最贵的一处错 |
| 已确认 | 确认数够了 | 没说明「够了」的标准是什么 |
| 最终 | 实际上无法再被改变 | 和上一格混为一谈 |
第四格要单独说。在确认数不足时显示「成功」,等于向用户做了一个你无法保证的承诺。 第 1 章讲结算时说过:「显示到账」和「完成结算」经常不是同一个时间点,而这个差值在链上是产品要负责表达的。
失败路径清单
这是这一章最该被抄走的一张表。 任何一个涉及交易的功能,做 PRD 时逐条过一遍:
| 失败方式 | 用户看到什么 | 产品该做什么 |
|---|---|---|
| 没有原生代币付手续费 | 点了没反应 | 发起前预检,明确告诉他缺什么、去哪补 |
| 资产余额不足 | 弹窗报一串错误 | 发起前预检,把可用上限直接填进输入框 |
| 授权额度不够 | 交易失败,手续费照扣 | 预检额度,把授权和执行的关系说清楚 |
| 价格滑动超过容忍值 | 交易被回滚 | 解释为什么会这样,给出调整入口,不要默认调大 |
| 被抢先执行 | 成交价比看到的差 | 说明价格保护机制,给出报价有效期 |
| 卡在内存池里 | 界面一直转圈 | 给出交易凭据和查询入口,说明可以怎么处理 |
| 合约执行中途失败 | 失败且手续费不退 | 提前模拟,把必然失败的交易拦在签名之前 |
| 用户在钱包里点了取消 | 回到原界面 | 区分「取消」和「失败」,不要都报错 |
| 网络拥堵、费用飙升 | 迟迟不被打包 | 展示当前费用水平,允许用户等 |
| 链重组 | 已显示成功的交易消失了 | 这就是为什么第四格不能写「成功」 |
十条里有七条可以在用户签名之前被拦下来。而「在签名前多做一次检查」是链上产品投入产出比最高的一件事——它同时降低流失、降低损失、降低客服量。
签名的四种类型
用户在钱包里点「确认」时,他可能在做四件完全不同的事。产品要在自己的界面上把它们区分开,因为钱包弹窗未必会区分。
| 类型 | 它做了什么 | 能不能撤销 | 产品要说清楚的 |
|---|---|---|---|
| 转账签名 | 把资产转出去 | 不能 | 收款地址、金额、这一步之后就结束了 |
| 授权签名 | 允许某合约动用你的某种资产 | 可以,但要主动去撤 | 额度多少、有效期多久、指向的合约能不能被升级 |
| 消息签名 | 证明你控制这个地址,不上链、不花钱 | 不适用 | 它不会动你的钱——这句话要明说,否则用户会怕 |
| 离线订单签名 | 签一份可以被别人拿去上链执行的授权 | 取决于设计 | 有效期、可以被谁执行、怎么作废 |
第二类是第 24 章那条「你自己签了名」的风险路径的入口。产品侧有三个具体动作可以把它压到最小:按需授权而不是无限授权、给授权设有效期、在产品里提供一个能看到并撤销历史授权的地方。
第四类最容易被误解,因为它「不花钱、不上链」,看起来像消息签名。但它是一份可以被执行的授权,而且执行的时机不由用户决定。任何让用户签这类内容的功能,都必须在自己的界面上写清楚有效期和作废方式。
它叫什么
用户通过一个你无法控制的应用,来批准你的产品发起的每一个操作。
这是链上产品最特别的一处结构:你的关键转化步骤发生在别人的界面里。 你能做的只有两件事——在自己这一侧把内容解释清楚,以及尽可能减少需要跳转过去的次数。
它还意味着你的产品要面对一群行为差异极大的钱包:显示方式不同、对限额和有效期的支持不同、对同一段调用数据的翻译不同。在设计阶段就要问「这句话在三个主流钱包里分别显示成什么样」,实现层面的差异见 T13。
用私钥对一段内容的授权,它是链上世界里唯一的「我同意」。
关键性质有三条:它不可伪造(只有持有私钥的人能产生)、它不可否认(事后无法说「不是我签的」)、它的效力取决于内容而不是场景(同一份签名在你的产品里和在别的地方效力相同)。
第三条是产品风险的来源:用户在你这里学会的「看到弹窗就点确认」,会被他带到任何一个钓鱼网站。所以「教用户读签名内容」不只是安全教育,它是产品的一部分。
用户为让网络执行他的操作而支付的费用。
三件事改写产品设计:它必须用原生代币支付(所以一个只有稳定币的新用户寸步难行)、它会随网络繁忙程度大幅波动、失败的交易同样扣费。
第三条最需要被产品处理。一笔注定失败的交易,如果在签名之前就被模拟拦下来,用户省下的不只是手续费,还有一次挫败。
有一类设计可以让别人替用户付这笔费用,它能显著降低第一次使用的门槛,但它把成本挪到了产品方身上,而且会引来专门薅这个成本的地址——第 28 章讲的正是这类问题。
一笔交易到达「实际上无法再被改变」的那个点。
它不是一个开关,是一条曲线:确认数越多,被改变的可能性越低。不同的链在这件事上的设计差异很大(第 8 章比较过),产品要为自己所在的链选一个门槛,并且把这个门槛告诉用户。
界面上那个「成功」的绿勾,应该在你选定的门槛之后出现,不是在交易被打包的那一刻。
确认之后,没有任何人可以撤销它。
这是第 1 章讲结算时那句「更像现金」的产品含义。现金交易也是这样:给出去就是给出去了,没有客服。
它对产品的要求可以压成一句话:把 Web2 里花在「出错之后」的所有精力,全部前移到「出错之前」。 确认页、预检、模拟、限额、地址核对——这些在 Web2 里是可选的打磨,在这里是主体功能。
任何人都可以在不经你同意的情况下调用你的合约,把你的产品接进他的产品。
好处是分发可以自己发生:别人把你接进去,他的用户就成了你的用户,而你一分钱都没花。第 9 章讲过这是智能合约最特别的性质之一。
代价是你失去了三样东西:对调用方的控制、对接口的自由变更权、对使用场景的假设。第三样最容易伤人——你以为用户是人,来的是合约;你以为一次来一笔,来的是一次交易里一百笔。这些假设写进产品逻辑之前,要先问一句「如果调用我的是一段代码,还成立吗」。
动手
这个 Lab 的目的不是用这个产品,是把它的流程拆成帧。 所以慢比快重要,每一步都要停下来截图。
先准备:新建一个全新的、干净的钱包地址,不要用你平时用的那个。到测试网水龙头领一点原生代币。选一个你没用过的 DApp。
从打开首页开始截图,一直截到你完成第一次有意义的操作。
每一屏都截,包括加载中、错误提示、钱包弹窗。钱包弹窗一定要截全,包括需要展开才能看到的那部分。
截完之后数一数总共几屏、其中几屏需要你做决定、几次要签名。这三个数字就是这个产品的首次使用成本。
逐个解析签名弹窗,填这张表。
| 第几次签名 | 钱包上显示什么 | 属于四类中的哪一类 | 额度 | 有效期 | 产品界面上有没有解释 |
|---|---|---|---|---|---|
| 1 | |||||
| 2 |
「额度」这一列是重点。 如果是授权签名,去看它请求的额度是你操作所需的数量,还是一个巨大的数字。后者就是无限授权。
看不懂弹窗内容时,把它复制下来,在区块浏览器上查这个合约,看它的接口名。这一步做几次之后,你会对常见的签名类型形成直觉。
故意触发三种失败,各截一次图。
| 怎么触发 | 观察什么 |
|---|---|
| 把原生代币花光,再发起一次操作 | 产品有没有预检,提示是人话还是一串错误码 |
| 输入一个超过余额的数字 | 是在输入时就拦下,还是等到签名后才失败 |
| 在钱包弹窗里点「拒绝」 | 产品区不区分「用户取消」和「交易失败」 |
第三种最能看出一个产品的成熟度。 把用户主动取消报成「交易失败,请重试」,是一个很常见也很伤人的错误。
记录状态变化的每一个时刻。
从你点确认开始计时,记下:界面第一次变化的时间、显示交易凭据的时间、显示「成功」的时间。
然后拿着交易凭据去区块浏览器上核对:产品显示「成功」的那一刻,这笔交易有几个确认?
这是整个 Lab 最有价值的一个数字。把它和产品界面上的措辞对照一下——它写的是「成功」「已提交」还是「等待确认」?
做一次授权体检,然后撤销。
用一个授权查询工具(或者直接在区块浏览器上读合约)查这个新地址目前给出去了哪些授权、额度各是多少。
刚才那一次操作,可能已经留下了一个无限期、无限额的授权。把它撤销掉,并记下撤销要花几步、几笔手续费。
这一步是第 24 章那条「你自己签了名」的路径的关闭动作。做完之后,回去给你自己的日常钱包也做一遍。
写一份三段式的结论。
这个产品做得好的三件事:
这个产品漏掉的三条失败路径:
如果我来改,第一个要改的是:第二段对照前面那张失败路径清单逐条打勾。十条里通常有三到五条没有被处理,而这几条就是这个产品下一个季度的工作量。
AI Lab
这个任务的分工非常清晰,也非常适合用来理解 AI 在产品工作里的边界:
模型很擅长的:把一个功能描述展开成结构完整的文档——目标、角色、主流程、界面元素、埋点、验收标准。这部分它写得又快又规范,能省掉大量体力活。
模型系统性地不擅长的:失败路径。原因不神秘——它的训练材料里,Web2 的产品文档远多于链上的,而 Web2 的失败路径本来就少、而且大多可以补救。所以它不是偶尔漏,是结构性地漏。
请为下面这个功能写一份 PRD 草稿:
<功能描述,例如:用户把持有的 A 资产换成 B 资产,一次完成>
目标链:<链名>。目标用户:<第一次用这类产品的人 / 老手>。
第一部分,主流程。逐步写,每一步标明:
- 用户看到什么
- 这一步是否需要签名,如果需要,属于哪一类(转账 / 授权 / 消息 / 离线订单)
- 如果是授权,额度和有效期分别设成什么,为什么
第二部分,交易状态。按「构造 / 待签名 / 已广播 / 待确认 / 已确认 / 最终」
六个状态,分别写界面显示什么文案、用户可以做什么。
明确写出:在第几个状态才显示「成功」,判断标准是什么。
第三部分,失败路径。逐条写,每条包含触发条件、用户看到什么、产品怎么处理:
没有原生代币 / 余额不足 / 授权额度不足 / 滑点超限 / 被抢先执行 /
卡在内存池 / 合约执行失败 / 用户主动取消 / 网络拥堵 / 链重组
第四部分,单独列出你不确定的地方。
规则:
- 不要写任何「撤销」「回滚」「退款」逻辑,除非你能说清它在链上怎么实现。
- 每一条失败路径都要有具体的界面文案,不要写「给出友好提示」。注意这份提示词把失败路径十条直接列了出来。 这是故意的——不列,它就不会写;列了,它会写得不错。这本身是一个很有用的发现:模型缺的不是能力,是这张清单。而这张清单必须来自人。
拿到草稿之后,你要做的三件事:
- 逐条检查第三部分的十条。 它写全了吗?每一条的处理方式在链上真的可行吗?
- 把第二部分和自己 Lab 里的观察对照。 你实测的那个产品在第几个状态显示「成功」?模型写的和它一样吗?
- 找它编出来的功能。 最常见的两种是「一键撤销上一笔交易」和「失败自动重试并退还手续费」。前者不存在,后者的手续费退不回来。
这类任务上模型有四个高频错误,按危险程度排序:
- 把 Web2 的可撤销假设带进来。 写出「用户可在 15 分钟内取消该笔交易」这种需求,而它在链上没有实现路径。
- 在「已广播」就显示成功。 状态机压缩成两格,这是最常见的一个,也是第一节里那六个客服问题的直接来源。
- 默认无限授权。 它会写「首次使用时请求授权」,但不写额度,而不写额度在实现时通常就变成了无限。
- 完全不提被抢先执行。 这件事在 Web2 里没有对应物,所以它几乎不会主动想到。完整的机制在 T24。
补完之后,把你补的那部分单独存起来。连续做三个功能之后,你会发现自己补的内容高度重复——那就是你自己的链上产品检查清单,比任何模板都好用。
AI 说完之后,你必须自己验证
- 它写的成功路径里,有没有把「交易已广播」当成「操作已完成」
- 十条失败路径它覆盖了几条?没有原生代币、授权不足、滑点超限这三条最常被漏
- 它有没有处理「用户在钱包里点了取消」——这和交易失败是两件事
- 它写的签名步骤,有没有说明每一次签名属于哪一类、额度多少、有效期多久
- 授权那一步,它默认用的是无限额度还是按需额度
- 它有没有写交易卡在内存池时的界面状态和用户可做的操作
- 被抢先执行这一条,它是完全没提,还是给了一个具体的产品应对
- 最关键的一条:它给出的任何「重试」「撤销」「回滚」逻辑,在链上真的成立吗
真实案例
早期的链上产品为了减少用户操作,普遍在第一次授权时请求一个极大的额度:一次授权,以后再也不用弹窗。这个做法当时解决了真实的体验问题,然后变成了行业惯例,被后来的产品一路抄了下去。
代价在几年后以另一种形式出现:用户的钱包里积累了大量他早就忘记的、永久有效的、额度无限的授权,而这些授权指向的合约有一部分是可以被升级的。第 24 章讲的第五条资金流出路径,绝大部分损失就发生在这里。
这个案例的价值不在于批评当初的选择,而在于它展示了产品默认值的寿命:一个为了少点一次而做的决定,会在产品死掉很久之后,还在别人的钱包里生效。
一个产品在交易被打包的那一刻就显示绿色对勾和「交易成功」。绝大多数时候这没问题。
出问题的那一次,网络发生了一次小范围重组,那个区块被替换掉了,交易没有被重新打包。用户已经看到了成功,已经关掉了页面,已经按「我收到了」去做了下一件事。
问题不在于重组,重组是这个系统的正常行为。问题在于产品做了一个它无权做的承诺。
正确的做法很简单,只是要多写几行文案:在确认数不足时显示「已提交,等待确认(当前 2/12)」,到达门槛后再显示成功。多一行状态,少一类事故。
用户在界面上看到一个报价,点了确认。交易进入公开的内存池,任何人都能看到它还没执行。
有程序注意到这是一笔足以推动价格的大单,于是在它前面插入一笔自己的交易,在它后面再插一笔,赚走中间的差价。用户最终的成交价比看到的报价差了一截。
链上一切正常,没有任何合约被攻破。这是公开内存池的自然结果,不是事故。
产品侧能做的有几件:给报价加有效期并明示、把价格容忍度的含义解释清楚而不是让用户盲调、在大额时提示风险、或者接入不经过公开内存池的提交方式。但第一件事是在界面上承认这件事存在——最差的做法是让用户在成交之后自己去猜为什么价格不一样。完整机制见 T24。
一个协议上线时假设:调用它的是人,通过它自己的界面,一次一笔。所有的限流、风控和数据统计都建立在这个假设上。
几个月后,另一个团队把它接进了自己的产品,用户在那边操作,调用在这边发生。于是这个协议看到的是:调用方是一个合约地址、一次交易里连续来几十笔、没有任何界面侧的前置检查。
它的日活统计立刻失真(第 23 章讲的聚合器问题),它的限流逻辑失效,而它的某些业务假设可能直接不成立。
这不是谁的错,这是可组合性的代价。它要求产品在设计时就回答一个问题:如果调用我的是一段代码而不是一个人,我的每一条假设还成不成立?
改一个变量
第一次使用的门槛会明显下降:用户不再需要先去别处弄一点原生代币才能开始,失败路径里最常见的那一条直接消失。
但三件事会跟着出现。成本挪到了你身上,而且它随网络拥堵波动,你无法控制。你需要一套防滥用机制,否则会有专门为了消耗你的代付额度而来的地址——第 28 章讲的正是这类行为的经济学。用户对成本失去了感知,于是他不会理解「为什么在别的产品里要付钱」,你实际上改变了他对这个环境的认知。
更微妙的一点:代付通常需要用户先签一份离线授权,由你去上链执行。 这把签名类型从第一类换成了第四类,风险结构也跟着变了。用户仍然要签名,只是签的东西换了——这件事必须在界面上说清楚,不能因为「体验更顺」就略过。
六格状态机看起来会被压扁:几乎不需要等待,费用低到用户不在意,失败重试的代价很小。很多设计确实可以简化。
但三个约束一个都没消失。不可逆没有变:确认快只是意味着不可逆来得更快。要签名没有变:签名次数的成本从「等待」变成了「打断」,而打断同样会流失。可组合没有变。
还有一个反方向的效应值得注意:费用低会让「随便试试」变得便宜,于是刷量和薅激励的成本也变低了。 你在产品体验上省下来的,可能要在增长侧还回去。
不要把「这条链更快更便宜」当作「可以按 Web2 的方式做产品」。 它改变的是参数,不是约束。
这不是一个需要更好新手引导的问题,这是一个产品形态选择的问题,而且只有三条路。
第一条,教育:把钱包、签名、手续费全部教一遍。转化率会很低,但留下来的人理解自己在做什么。
第二条,隐藏:用某种方式让用户不用面对钱包。这条路上要极其小心地回答一个问题——私钥在谁手里? 如果在你手里,你是托管方;如果在用户手里只是被封装了,那要说清楚他怎么在你消失之后拿回控制权。
第三条,换用户:承认自己这个功能不适合完全的新手,先服务已经有钱包的人。
三条都是合法的选择,但它们必须被明确地选一条。最常见的失败是既想隐藏复杂度、又不想承担托管责任,结果做出一个「看起来像托管但实际上没有托管方的保障」的东西。
你从「做入口」变成了「做零件」,评价标准整个换了一套。
变得重要的:接口的稳定性和文档质量、行为的可预测性、对批量调用和合约调用方的友好程度、以及别人接你的成本有多低。
变得不重要的:视觉、新手引导、转化漏斗——那些是接你的人要操心的。
变得非常困难的:知道你的用户是谁。所有调用都来自别的合约,你看到的只有地址。第 26 章那张损益表你还算得出来,但第 28 章那套留存分析会变得很难做。
这条路的好处是分发可以自己发生,坏处是你的增长取决于别人的产品决策。第 29 章讲的 BD,很大一部分工作就是在这条路上推动集成。
带走的问题
这一章给这一问加了一个链上特有的追问:来的是人,还是合约?
一个被别的产品接进去的协议,它看到的调用方是代码。这不只是统计口径问题,它会让你所有关于「用户会怎么做」的产品假设失效。做任何功能之前问一句:如果调用我的是一段代码,这条假设还成立吗。
在产品这一层,这一问有一个很具体的形式:用户签下这个名之后,最坏的情况是什么,损失落在谁头上。
授权签名的最坏情况是额度内的资产全部被转走;离线订单签名的最坏情况是它在一个对用户不利的时刻被执行。这两句话写不出来的功能,不该上线。
这一问在这一章是它的人类版本:你的产品让用户签的那个名,给了对方什么权限,多大额度,有效到什么时候。
三个答案都写不出来的授权,就是第 24 章那条风险路径的入口。而产品侧能做的事很明确:按需授权、加有效期、提供一个能看到并撤销的地方。把这三件事做好的产品,比做了安全教育的产品更安全。
这一问在链上产品里有一个不需要讨论的答案:任何不可逆的资金动作,都必须由人来按下最后那一步。
有意思的是,这个结论不是因为自动化不可靠,而是因为不可逆意味着错误无法被第二次尝试修正。所以设计的重点不是「要不要保留人的确认」,而是「怎样让人在确认前看到足够的信息」——这正是这一章那张失败路径清单的全部意义。
本章自测
不可逆:删掉了撤销、退款、回滚和灰度试错。产品必须把所有精力从「出错之后」前移到「出错之前」——确认页、预检、模拟、限额、地址核对。
要签名:删掉了服务端代用户行动。每一步都要用户本人批准,而批准发生在一个你无法控制的界面里。所以签名次数是成本,签名内容的翻译是你的责任。
可组合:删掉了对调用方的控制和对接口的自由变更权。任何人可以把你接进去,你以为的用户可能是一段代码。
三者合起来的意思是:这不是一个加了区块链的 Web2 产品,这是一个约束完全不同的产品。 照抄流程之所以不成立,是因为那些流程的前提不在了。
因为它做了一个产品无权做的承诺。
一笔交易被打包,只是进了一个区块。在确认数不足时,这个区块可能被替换掉,交易会消失。用户此刻已经看到了绿色对勾、已经关掉页面、已经按「我收到了」去做下一件事。
正确的做法是把状态机拆开:已广播、待确认(显示当前确认数和门槛)、已确认、最终。多一行状态文案,少一类事故。
这背后是第 1 章那个区分:「显示到账」和「完成结算」不是同一个时间点。在 Web2 里这个差值由后台吸收,在链上它必须由产品表达出来。
转账签名:把资产转出去,不可撤销,一步到位。
授权签名:允许某个合约动用你的某种资产。一次签名,效力持续到你主动撤销为止,而且默认额度常常是无限。这是四类里风险最高的。
消息签名:证明你控制这个地址,不上链、不花钱、不动资产。产品要主动说明这一点,否则用户会以为自己在批准一笔交易。
离线订单签名:签一份可以被别人拿去上链执行的授权。它「不花钱、不上链」,看起来像消息签名,但它是可执行的,而且执行时机不由用户决定。
产品的责任是在自己的界面上区分这四类,因为钱包弹窗未必区分。授权那一类还要加三个动作:按需额度、设有效期、提供撤销入口。
因为它见过的产品文档里,Web2 的远多于链上的,而 Web2 的失败路径本来就少、而且大多可以事后补救。它学到的「完整的 PRD」这个模板,里面就没有这一节。
具体表现是四类错误:把可撤销假设带进来(写出根本无法实现的取消功能)、在已广播就显示成功、默认无限授权、完全不提被抢先执行。
应对办法不是换模型,是在提示词里直接给出那十条失败路径清单。给了它就写得不错,不给它就不写——这说明缺的不是能力,是清单,而清单必须来自人。
这也是这一阶段反复出现的分工:模型负责把结构铺满,人负责补上那些只有在这个环境里待过才知道的东西。
没有标准答案,检查这几件事:
- 你是实际触发了这些失败,还是只看界面猜的?至少要实测三条。
- 「没有原生代币」这一条,它是发起前预检,还是等到签名之后才报错?
- 它区分「用户主动取消」和「交易失败」吗?
- 它在第几个状态显示「成功」?你去区块浏览器上核对过当时的确认数吗?
- 它的授权请求是按需额度还是无限额度?有没有有效期?
- 它有没有在产品里提供一个查看和撤销历史授权的地方?
- 被抢先执行这一条,它在界面上承认存在吗?
一个经验值:十条里漏三到五条是很普遍的,而且漏的通常集中在后半段——卡在内存池、被抢先执行、链重组这三条。这三条的共同点是它们在 Web2 里没有对应物,所以最容易被从别的行业带过来的产品直觉整个跳过。
做完之后再做一件事:把你的结论和这个产品的实际留存数据对照一下。失败路径处理得差的产品,流失通常集中在第一次使用,而这正是第 28 章要量化的东西。
一句话带走
不可逆、要签名、可组合,这三件事改写了几乎所有产品决策。