1Do App

Session Pay

钱包签署会话边界,session key 只授权递增的累计付款总额;任何中继者都可提交,链上只支付差额。

6 个章节onedo-session-pay
01

授权结构

Session Pay 没有 createSession 交易。钱包在链下签署 SessionGrant,每次 settle 都重新提交 grant、钱包签名和 session key 签名;链上不保存完整授权。

EIP-712 domain 为 Session Pay on 1Do / 1,绑定当前 chainId 与钱包 verifyingContract。钱包签名通过 ERC-1271 验证。

  • sessionKey 与 payee 必须是非零地址,spendLimit 必须大于 0。
  • token 是固定支付资产;address(0) 表示原生币,其他地址表示 ERC-20。
  • spendLimit 是整个会话的累计上限,不是单笔额度。
  • sessionExpiresAt 是最后可提交时间;当前时间等于该值时仍有效,超过后失败。值为 0 会立即过期,并不表示无限期。
  • salt 用于区分其他字段完全相同的独立 grant。
Signed grant and settlement authorization
struct SessionGrant {
  address sessionKey;
  address payee;
  address token; // address(0) = native asset
  uint256 spendLimit;
  uint256 sessionExpiresAt;
  bytes32 salt;
}

struct SettlementAuthorization {
  bytes32 sessionId;
  uint256 newTotalPaid;
}
02

sessionId 与两层签名

sessionId 是 SessionGrant 的 EIP-712 struct hash,不是包含 domain 的完整 digest。由于它本身不包含 chainId 或钱包地址,索引与数据库键应至少组合 chainId + wallet + sessionId。

钱包 selfSig 授权 grant 的完整边界;sessionSig 只签 SettlementAuthorization(sessionId, newTotalPaid)。两份签名在每次结算时都会验证。

  • selfSig 通过执行钱包的 ERC-1271 验证,确保 grant 属于该钱包和链。
  • sessionSig 使用标准 ECDSA recover,签名者必须等于 grant.sessionKey;当前不支持把合约钱包直接作为 session key。
  • 任何地址都可以作为 relayer 提交 settle,但不能改变 payee、token、上限、期限或累计值。
03

累计差额结算

步骤 1

读取链上进度

客户端读取该钱包 sessionId 的 previousTotalPaid 与 revoked 状态。

步骤 2

签署新累计值

session key 签署严格大于 previousTotalPaid 且不超过 spendLimit 的 newTotalPaid。

步骤 3

提交完整证明

relayer 提交 grant、authorization、selfSig 与 sessionSig;应用重新检查期限、撤销状态和两层签名。

步骤 4

只支付差额

实际付款等于 newTotalPaid - previousTotalPaid。状态先更新,再从钱包直接向固定 payee 发送原生币或 ERC-20。

累计值代替普通 nonce

同一 sessionId 没有单独递增 nonce。旧值、相同值或更小值都会因为不严格递增而失败,因此重复提交旧授权不会再次付款。

04

撤销与生命周期

  • revokeSession(sessionId) 只能由钱包 self-call;撤销不可恢复,重复调用不会重复发出事件。
  • 禁用应用不会清除 totalPaid 或 revoked。重新启用同一应用 logic 后,未到期且未撤销的会话可从原累计值继续。
  • 到期、达到 spendLimit 和主动撤销都不会写入统一 status 枚举,客户端应结合 grant 与链上状态推导。
  • 未知 sessionId 与尚未支付的新会话都会返回 totalPaid = 0、revoked = false;仅凭这两个值无法证明链下 grant 存在。
05

状态读取、事件与 SDK

  • totalPaid(sessionId)、revoked(sessionId) 与 sessionState(sessionId) 都应在目标钱包 Runtime 上下文读取。
  • SessionSettled 记录 sessionId、payee、token、paymentAmount、newTotalPaid 与 sessionKey;SessionRevoked 记录 sessionId。事件 emitter 是钱包地址。
  • sessionIdOf(grant) 可计算 struct hash。当前 SDK 的 OneDoSessionPay 命名空间提供 grant/authorization 哈希、typed-data 与 session key 签名辅助。
06

资金与失败边界

  • 授权不会预留余额,也不会自动定时扣款。settle 是 nonpayable;每次结算时钱包必须已经持有足够的原生币或 ERC-20。
  • 付款从钱包直接发送到 grant.payee,不经过 relayer;Token 转账或原生币接收失败会回滚 totalPaid 更新。
  • ERC-20 使用 SafeERC20,但手续费型或 rebasing Token 没有净到账校验;paymentAmount 表示钱包请求转出的差额,不保证收款人净到账完全相同。
  • grant 无效、已过期、已撤销、sessionId 不匹配、累计值不递增、超过上限或任一签名错误都会失败。
  • 支付资产与收款人在 grant 生命周期内固定;更换其中任何字段会产生新的 sessionId,并需要新的钱包签名。