Developer guide (full text) · in Chinese for now. The developer guide is bilingual, and AI assistants can follow this document as is (Markdown).

开发者指南

写给基于 TapeRelay 做产品(网盘、视频站、网页应用)的开发者。规范的原稿在 spec/TapeRelay.md(英文)和 spec/TapeRelay.zh.md(中文),暂且作废,等重写。运营方看 docs/OPERATORS.md;给 AI 的版本见 DEVELOPERS-FOR-AI.md。

一句话

中继网关替你存文件、传文件;权限靠电路(持有人的签名):后台、命令行用持有人签发的开发者密钥(像 API key,最长 366 天、随时撤销);每个文件同时存 3 家不同运营方。 TapeRelay 不替你运行程序;公开文件谁都能读,而且收不回来;密钥、密码不要放进来,要保密的内容用加密文件。

1. 做法

现在只有模版 A:文件记在你(开发者)的电路名下,适合网盘、内容平台,用户不用钱包,用你自己的账号体系登录。你要有一个后台(Node.js),它持有一把开发者密钥和一把加密私钥。用户的文件在浏览器里加密,直接传到节点;浏览器把要发的请求交给后台,后台核对后逐个签名,文件内容不经过后台。每个文件的钥匙另锁一份给后台,用户换了设备也能向后台要回来。删除时后台先隐藏(能恢复),到期后批量释放。示例在 examples/template-a/,详细做法见那里的 README。

原来还有模版 B(文件记在用户自己的电路名下,用户用自己的钱包),已经删掉。

三条链(2026-10-30,D-521~D-523):电路可以在 BNB Smart Chain、X Layer、Base 上,地址写成 eip155:<链号>:0x…(56、196、8453)。签名(EIP-712 的 domain 写着电路的链)、写发现文件(那条链上的 SiteRegistry,手续费付那条链的币,X Layer 是 OKB)都在电路所在的链上;读链时名字按里面的区号认链(X Layer 区号 2、Base 区号 3、BNB 不带),只给 0x 地址时在三条链上同时找。付款合约每条链各一份(还没部署),用户只在自己电路所在的链上买。

示例代码在 GitHub 仓库 Yiqsen/TapeRelay 的 examples/ 里。克隆后在仓库根目录先 npm ci --ignore-scripts(要 Node.js 20.19 以上),本文的路径都相对仓库根目录。

模版 A 的两把密钥:开发者密钥在控制台的「密钥」页生成,能撤销,最长一年到期。加密私钥是后台自己生成的随机数,专门保管用户文件的钥匙,丢了就打不开用户的文件,要另外备份。两把都只放在后台的密钥文件里(示例从仓库根目录的 .env 读)。后台被攻破,攻击者能用开发者密钥在你的节点上传、删除对象,也能打开所有用户的文件,发现了要马上在控制台撤销开发者密钥。电路持有人的钱包私钥不放在后台。

2. 存 3 家:怎么挑、怎么传

3. 权限:靠电路

服务器只认两种签名:电路持有人的钱包,和持有人授权的会话密钥(TapeRelay §8)。没有账号;像 API key 一样用的开发者密钥也是持有人授权的会话密钥,只是有效期长(见本节最后)。

范围 能做什么
objects 上传、认领、列出、查用量
send 以这枚电路的名义交出文件通知
inbox 拉取收件箱、标记已读
pointers 写签名指针
release 释放(删除)对象;用它删掉的进回收站,至少留 7 天(TapeStore §4.3、TapeStore §4.5,2026-10-29 起,服务器 features 有 recycle-bin 的才认)
admit 给你的用户签「准入」,让这台服务器替他们收发消息;也能收回。要这台的运营方把你的电路列为准入人

只要你用得到的范围;钱包签名时用户看得到应用和范围。释放(删除)只认钱包和带 release 范围的开发者密钥(TapeStore §4.3);网页的 24 小时授权根本不能有 release(有了整份不合格),开发者密钥也默认不勾。

4. 播放和下载

5. 改和删

6. 费用

7. 做不到的

8. 客户端库速查

client/src/index.mjs,普通 JavaScript,浏览器和 Node.js 都能用(只依赖 @noble)。浏览器里的网址布局照 examples/static.mjs。

开发包(预览,2026-10-29 起,D-511、D-514;还没发布到 npm):同一份代码分成两个包。

本机可以用 node tools/pack-sdk.mjs 出包(dist/sdk/,.tgz)。最简单的用法:

import { createTapeStore } from '@tapestore/client';
const store = createTapeStore({ account, servers, privateKey });   // 私钥只在自己的后台;浏览器里用 wallet 或会话密钥的 auth
// 后台、脚本也可以:createTapeStore({ developerKey: process.env.TAPESTORE_KEY })   电路和服务器从钥匙里取
const f = await store.upload(bytes, { name: 'a.mp4', contentType: 'video/mp4' });   // f.manifest 记进自己的数据库
await store.read(f.manifest);  await store.check(f.manifest);  await store.repair(f.manifest);
await store.remove(f.manifest, { keep: [还要留着的文件的清单哈希] });   // 钱包或带 release 范围的开发者密钥;内容相同的块只记一份,keep 里的不释放
await store.recycleBin();  await store.restore({ sessionKey: '0x…' });   // 回收站:开发者密钥删掉的,7 天之内找回

命令行 tapestore(@tapestore/cli,同样还没发布):tapestore init 4246.0.tape 从链上的发现文件读服务器写进 tapestore.json,之后 tapestore upload 文件、download、check、repair、rm --keep …、usage;私钥只从环境变量 TAPESTORE_PRIVATE_KEY 或 --key-file 读,不写在命令里。开发者密钥:tapestore key create -o 文件(要持有人的私钥)、之后设 TAPESTORE_KEY 就不要配置文件;key show、key revoke、recycle、restore。

两个包里,@taperelay/client 的 createClient 不带释放、列出、用量,要用 @tapestore/client 的 createStoreClient。本仓库的 client/src/index.mjs 照旧两样都带。

做什么 用什么
连服务器 createClient({ fetch })(开发包里要释放、列出、用量的用 createStoreClient);client.info(名字)、client.usage(名字, 授权)
挑运营方 parseOperatorList、checkOperator、pickOperators({ count: 3, features, exclude })
发现文件 discoveryJson、serverEntryOf、parseDiscovery、writeSiteFile(链上,交易)、DISCOVERY_PATH
授权 keyWallet(Node 里用私钥)、eip1193Wallet(浏览器钱包);authorizeSession({ app, scopes })、sessionAuth、walletAuth;开发者密钥 createDeveloperKey、developerKeyAuth、describeDeveloperKey;撤销 signRevocation + client.revoke
模版 A 的签名 浏览器 remoteAuth({ account, endpoint });后台 createRemoteSigner({ auth: developerKeyAuth(开发者密钥), servers }) 的 check、sign
上传 planPublicFile(文件, { withManifest: true })、uploadToServers(client, 服务器, 授权, 文件, { plan, onProgress }) → { stored, ok, pending, settled }
读 readFile、readObject(核对)、fileObjects(一个文件的全部对象)
副本 checkReplicas、repairReplicas
删除 client.release(名字, 授权, 对象)(钱包,或带 release 范围的);回收站 client.released,找回用 client.claim
指针 makePointer、signPointer、publishPointer、readPointer

中继网关的接口

每台中继网关都提供这些接口(规范 TapeRelay §14)。一般不用直接调:客户端库会签名、核对。权限一栏:公开不要签名;写范围的,认持有人的钱包或有这个范围的会话密钥;仅钱包只认持有人的钱包。

接口 做什么 客户端库 权限
GET /v1/info 服务器信息:配额、价格、支持的功能 client.info 公开
GET /v1/usage/{account} 用量和配额 client.usage objects
POST /v1/objects 上传一个对象 client.upload objects
POST /v1/objects/claim 认领服务器上已有的对象 client.claim objects
GET /v1/objects/released 回收站:7 天之内用开发者密钥删掉的(TapeStore §4.5) client.released objects
GET /v1/objects 列出记在名下的对象 client.list objects
GET /v1/objects/{sha256} 读一个对象,支持 Range client.getObject 公开
HEAD /v1/objects/{sha256} 查是否存在、多大 client.has 公开
POST /v1/objects/release 释放(删除),一次最多 1,000 个 client.release 钱包,或带 release 的开发者密钥
PUT /v1/pointers/{account}/{name} 写签名指针 client.putPointer pointers
GET /v1/pointers/{account}/{name} 读签名指针 client.getPointer 公开
POST /v1/outbox 发通知,由自己的服务器转交 client.submitNotice send
GET /v1/outbox/{id} 查投递状态 client.outboxStatus 公开
GET /v1/inbox/{account} 拉取收到的通知 client.inbox inbox
POST /v1/inbox/{account}/read 标记已读 client.markRead inbox
POST /v1/self-admissions 给用户签准入、收回准入 updateAdmissions admit
POST /v1/revocations 撤销会话密钥(持有人的签名在请求体里) client.revoke 仅钱包

{account} 是电路的链上名字(例如 4246.0.tape、Base 上的 1.3.1.tape;D-634 起协议里只认这种写法,客户端库的 normalizeCircuitInput 把短名、#4246@0、tape:// 网址换成它,0x 容器地址要经内核反查)。按范围授权只在 /v1/info 的 features 列出 scopes 的服务器上能用。另有服务器之间转交通知的 POST /v1/inbox/{account}、给网关排行汇总用的 GET /v1/stats/gateway(TapeRelay §7.7,每台服务器每天的请求数、访客数、流量),和运营方自己用的 /v1/admin/*,开发者用不到。

9. 能放什么、不能放什么

能放 不能放
网页(HTML、JS、CSS、WebAssembly)、图片、视频、文档、数据文件 后台程序、数据库服务、定时任务(TapeRelay 不运行它们)
只给自己或指定的人看的内容(用加密文件) 任何密钥、密码、API Key、私钥、助记词(放进公开文件就等于公开)

10. 公开文件谁都能读

11. 密钥放在哪里

  1. 放在你自己的后台或云函数里:网页去调你的后台,由后台带着密钥去调第三方服务。
  2. 网页里只放本来就能公开的 Key:限定了域名、只读、额度很小的那种。
  3. 节点网址里带着密钥的(例如某些区块链节点服务的网址),同样只能放在你的后台。
  4. 本协议的开发者密钥(trk1_ 开头)同样只放在后台:scanForSecrets 认得出它。

12. 要保密的内容:加密文件

13. 谁能写、怎样自动部署

14. 运营方能看到什么

能看到 看不到
谁(IP 地址)、什么时候读了哪个文件、文件多大 加密文件的内容
文件是哪枚电路存的、什么时候存的 你的钱包私钥、会话密钥的私钥
消息:谁发给谁、什么时候发的、多大 消息的内容(加密)

运营方可能因法律原因下架某个文件(读取回 451,客户端换下一台,TapeRelay §6.2):这也是要多存几家的原因之一(第 2 节、第 17 节)。

15. TapeRelay 能不能运行程序

服务器上不能,也不应该。 原因:

  1. 核对不了结果:TapeRelay 的一切都能按哈希、签名核对,所以你不必信任运营方。程序的运行结果没法这样核对,只能信运营方,就丢掉了最根本的保证。
  2. 安全和滥用:在运营方的机器上跑别人的代码,要隔离、要限资源,还要防挖矿和攻击,第三方运营方负担很重。
  3. 定位:TapeRelay 学的是 SMTP/IMAP 的分工,只管「存」和「送」;现在只卖存储和消息两种套餐,算力没法计费。

浏览器里能。 存进去的网页、脚本,会在访问者的浏览器里运行,跟静态网站托管一样。推荐的分工:

放在哪里 放什么
TapeRelay 界面(静态网页)、图片视频、公开的数据文件;要保密的数据用加密文件
链上合约 需要大家都信得过的逻辑和状态(转账、记账、权限)
你自己的服务器 密钥、要调第三方服务的后台、需要保密的计算

16. 在浏览器里运行的隐患

  1. 钓鱼网页:谁都能存一个冒充别人的页面。教用户认准你的电路名字(tape:// 地址),不要只看页面长得像不像。
  2. 网站之间要互相隔离:否则一个网站的脚本可能读到另一个网站存在浏览器里的东西。这由 TapeKit 外壳和网关负责;本协议的网关送出文件时带 TapeKit 给链上网站的那组安全设置(内容安全策略、nosniff 等,TapeRelay §12.2)。收到别人发来的文件,你的应用应当只给下载,不要在自己的页面里打开(TapeRelay §12.5)。
  3. 网页更新的权限:按哈希存的网页别人改不了。用「签名指针」(TapeRelay §7.3,第 312 条起有)让一个名字换成新的版本时,谁能签指针谁就能推送更新:
    • 网站的指针只用钱包签,不要交给会话密钥。会话密钥最长 24 小时(开发者密钥最长 366 天)、可以撤销,但读的人查不到撤销:会话密钥被偷,小偷签的指针在那份授权到期之前都算数;撤销之后等授权到期(最长 24 小时,带 pointers 的开发者密钥最长 366 天),再用钱包发布一次就改回来了(D-283:版号不得大于签名时间,谁都签不出能长期压住你的版号)。
    • 要「谁读到的都一定是最新的」,用链上的那种放法(TapeRelay §7.1 第 1 种):指针存在运营方的服务器上,读的人问遍你的每台服务器、取最新的,但一台服务器能少给(只给旧的);你的服务器都被拔掉时,读的人就读不到。
    • 电路转手之后,原来的持有人签的指针一律作废;新持有人发布时版号取当时的秒数,最多等 5 分钟。
    • 名字的位子删不掉:每枚电路在每台服务器上最多 100 个名字,规范里还没有删名字的办法(I-232 第 3 点、I-234 第 2 点,以后再定)。会话密钥被偷(授权到期之前,最长 24 小时,开发者密钥 366 天)、或者电路转手之前的原持有人,都能把 100 个位子写满;之后你只能改指这 100 个名字,起不了新名字,撤销、转手、等到期都腾不出位子。所以要紧的名字(例如 www)先用钱包发布一次占住,会话密钥只交给你信得过的设备。
  4. 流量:读取目前不计费,热门网页可能把免费运营方的流量拖垮,运营方会限流。热门内容请选能承受流量的运营方,或者多存几家。
  5. 直接网页托管:节点原来能把上传的网站直接给普通浏览器打开,已经删掉。网站放在 TapeOut 的容器网站里。

17. 让文件一直在

18. 上线前的检查清单