
SSP 中的 SPL 代币与代币账户
如果你在 Ethereum 上用过 ERC-20 代币,Solana 的代币模型大约会让你觉得眼熟三十秒,然后就不再说得通了。在 Ethereum 上,代币合约维护着一份「谁拥有多少」的账本,而你的地址是那份账本里的一行。在 Solana 上,你的地址根本不会出现在该代币的记录中。取而代之的是,系统会另建一个账户来保管你在那个特定代币上的余额——归你所有,但与你的主地址彼此分离。
仅仅这一个设计决定,就解释了人们在 SPL 代币上遇到的几乎所有意外:为什么把代币发给一个新的人比发给已经持有它的人更贵;为什么钱包会对一个从未接触过该代币的地址显示零余额;以及为什么小数位数在这里比在别处都更要紧。本文按 SSP 的实现方式走一遍这个模型。

为什么 Solana 需要为每种代币单开一个账户
Solana 的核心规则是:一切皆账户,而每个账户都有所有者、大小,以及与该大小成比例的租金押金。这里不存在那种「随着用户增多而在内部悄悄扩张映射表」的合约——存储必须落在某个账户里,而且总得有人为它付钱。
于是 SPL Token 程序把工作拆开。mint 账户保存关于代币本身的事实:总供应量、小数位数,以及谁可以增发。每个持有者则获得自己的代币账户——一个固定大小的小账户,记录着某一个 mint 的一笔余额,并指明一位所有者。你的 SOL 余额住在你的地址上;你的 USDC 余额住在一个由你的地址掌控的代币账户里。
这种设计的好处是可预测性——每一笔代币余额形状相同,存储成本也相同。代价则是:持有一种新代币,就需要把一个新账户带到世上。若你想要规范本身而非摘要,Solana 的代币文档是第一手来源。
关联代币账户,以及谁为它买单
如果每个持有者都需要一个代币账户,而任何人都能创建账户,那么每人每个 mint 就会散落着好几个代币账户,且无从判断该发往哪一个。关联代币账户(ATA)正是解决此问题的约定:对于给定的所有者与给定的 mint,存在唯一一个规范地址,由二者确定性地派生而来。钱包可以自行计算,因此谁都不必公布它。
麻烦在于租金。一个代币账户约 165 字节,折合约 0.002 SOL 作为租金押金被占用,只要该账户存在就一直如此。这笔押金在账户关闭时可以取回,但必须预先支付,且必须由手里有 SOL 的人支付——而一个人生中第一次收到代币转账的收款方,按定义可能恰恰没有。
SSP 处理这件事的方式,与它处理其他所有 Solana 手续费的方式相同。当你把某个 SPL 代币发给一个尚无 ATA 的人时,交易中会包含一条幂等指令来创建它,租金由 SSP 的 paymaster 支付。你的金库会在同一笔交易内偿还 paymaster,这正是首次发给新收款人比再次发给同一个人多花约 0.0025 SOL 的原因。「幂等」是这里的关键词:若该账户其实已经存在,这条指令会成功且什么也不做,而不是让整笔转账失败。
请注意,这条创建指令位于多签提案之外。创建别人的代币账户并不会动用你的资金,也不需要你金库的授权,因此它不属于你两台设备所签名的那部分。而确实会动用你资金的偿还指令,则在提案之内。
SSP 开箱支持什么
SSP 内置原生 SOL、Circle 官方的 USDC mint,以及 Solana 上的 FLUX。任何出现在你金库中的其他 SPL 代币也会被解析并一同显示。
有一条刻意为之的限制值得明说:SSP 的 Solana 转账是基于经典的 SPL Token 程序构建的。以 Token-2022 发行的代币——那个带有 transfer hook、机密转账及其他扩展的较新程序——目前不在发送路径之内。这是范围上的取舍而非疏漏:Token-2022 的扩展可能改变一笔转账实际做了什么,而支持它们意味着要针对下文所述的设备端解码保证逐一审计每个扩展。
一笔代币转账实际是怎么组装的
当你从 SSP 发送一个 SPL 代币时,被签名的交易按顺序包含:
- 如有需要,创建收款人的 ATA——幂等,由 paymaster 支付,位于提案之外。
- TransferChecked——把余额从你金库的代币账户转到收款人的账户,由金库授权。
- 偿还 paymaster——一笔来自你金库的普通 SOL 转账,位于提案之内。
这一切作为单一的原子交易落地。没有单独的批准步骤,没有挂在链上的待处理提案,也不存在「收款人账户已创建但转账没有发生」这种中间状态。要么全部执行,要么全都不执行。
来源账户是你金库自己的 ATA——以同样的确定性方式派生,所有者为金库的程序地址。由于金库是程序派生地址而非密钥对,该派生明确允许所有者落在 ed25519 曲线之外。若你想了解金库地址为何如此运作的底层细节,自我启动的 Solana 多签有所覆盖。
小数位数是签名的一部分
这一段值得放慢速度,因为这正是一个代币可能对你撒谎的地方。
mint 会声明其代币使用多少位小数。USDC 用六位;许多代币用九位;并无定规。如果钱包显示「100 USDC」,但按错误的小数位数去构造一笔以原始单位计的转账,你可能会多发一千倍,而屏幕上看不出任何异样。
SSP 使用 TransferChecked,而非普通的转账指令。区别在于,TransferChecked 会把 mint 地址与小数位数字节直接嵌入被签名的指令数据中。由此产生三重彼此独立的校验:
- 你的扩展解码这些字节,并与它正在显示的内容比对。
- SSP Key 在你的手机上独立解码同样的字节,并与扩展提供的代币元数据比对。
- 链上的 SPL Token 程序把指令中的小数位数与该 mint 的实际小数位数比对,不一致就拒绝该交易。
第三重校验是无法用任何说辞绕过的。一个声称与自身不符的小数位数的 mint,不会产出一笔错误的转账——它产出的是一笔失败的交易。

接收代币
接收更简单,但有一处细节。请把你的钱包地址给发送方——也就是 SSP 在接收界面上展示的那个——而不是某个代币账户地址。无论对方发送什么,正确的 ATA 都会由你的地址与该 mint 派生出来,需要时由发送方的钱包创建。
如果你的金库从未持有过该代币,那么在第一笔转账到达之前,这个账户并不存在,它的租金由向你发送的人支付。这是正常的,你无需做任何事。但这也意味着,对于一个别人答应给你、却尚未到账的代币,区块浏览器不会显示任何代币账户——那是账户还不存在,而不是转账丢了。
常见陷阱
发往代币账户地址而非钱包地址。 有些浏览器会把 ATA 放在显眼位置。把代币发给某个代币账户而不是它的所有者,是 Solana 上众所周知的一种丢币方式。SSP 期待的是所有者的钱包地址,其余部分由它自行派生。
因为某个陌生代币出现在你的钱包里就认定它安全。 任何人都可以创建一个 mint 并把它发给你,而 mint 可以取一个模仿真实代币的名字。代币出现在你的金库中并不构成背书。在把某笔余额当作真实价值之前,请对照官方来源核实其 mint 地址。
期待 Ethereum 式的授权。 SPL 代币确有委托机制,但那种让 ERC-20 授权成为长期风险的常设额度模式,并不是大多数 Solana 应用的构建方式。若你从 Ethereum 而来,代币授权:你一再授出的许可讲的是你正在戒掉的习惯,而 SSP 中的 Ethereum提供了对照。
忘记首次发送的附加成本。 首次转给新收款人时多出的约 0.0025 SOL 是租金,而不是 SSP 收取的费用。它留在收款人的代币账户里,若对方日后关闭该账户便可取回。
关于一笔真实转账的分步操作,请见用 SSP 发送 Solana。关于 SSP 如何以一个没有创建者的 2-of-2 多签来保管底层金库,请从 SSP 中的 Solana开始。


