关于 Patina
Patina 将账户活动日期转化为可携带的证据,用于说明一个人持有在线账户的时长。它面向需要评估账户年限的服务;分数反映历史时长,不代表一个人是否独一无二。
主要功能
- 01汇总八个来源的账户历史日期
- 02根据年限、连续性和相互印证等因素评分
- 03丢弃账户内容,仅保存按月归整的日期
- 04生成可离线验证的签名证明
- 05通过 MCP 服务提供分数
适用场景
- 01在注册或资格审核流程中评估账户年限
- 02将历史证据与其他 Sybil 抵抗检查结合
- 03无需联系 Patina 即可验证签名分数
- 04通过句柄让 AI Agent 查询账户年限
MCP 相关
MCP 服务向对话中的 AI Agent 提供账户年限分数。句柄解析器可在不披露身份的情况下返回账户年限。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://patinadata.xyz/api/mcpclaude mcp add --transport http ramakrishnanhulk20-patina https://patinadata.xyz/api/mcp{"mcpServers":{"ramakrishnanhulk20-patina":{"type":"http","url":"https://patinadata.xyz/api/mcp"}}}在 Cursor 中打开{"servers":{"ramakrishnanhulk20-patina":{"type":"http","url":"https://patinadata.xyz/api/mcp"}}}在 VS Code 中打开{"mcpServers":{"ramakrishnanhulk20-patina":{"type":"http","url":"https://patinadata.xyz/api/mcp"}}}{"mcpServers":{"ramakrishnanhulk20-patina":{"serverUrl":"https://patinadata.xyz/api/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
4| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| username | string | 是 | 此人的 Patina 用户名,与 patinadata.xyz/u/<username> 中显示的一致。 |
详细介绍
Patina
证明用户对自己已有账户的持有时长
任何人都能创建新账户,但没人能创建旧账户。
概览 • 评分 • 隐私 • 架构 • 快速开始 • 集成 • 测试结果
在线部署
| 界面 | URL |
|---|---|
| 应用 | patinadata.xyz |
| 文档 | patinadata.xyz/docs |
| MCP | patinadata.xyz/api/mcp |
| 验证器 | patinadata.xyz/verify |
| 注册表 | io.github.ramakrishnanhulk20/patina |
| 签名地址 | 0x3989bdFaf3BA242d27B4D0cEed98F446d0c52DAD |
概览
Patina 会读取用户已有账户中的历史记录,并将其转化为可移植、可验证的证据,证明一个真实的人多年来一直在使用这些账户。它基于数据可移植协议 Vana 构建:数据存放在用户自己的个人服务器中,在用户可撤销的授权下读取一次,并且绝不会经由会保留数据的 OAuth 授权流程传递。
女巫攻击防护问题尚未解决,成本也很高。空投、二次方资助轮次、DAO 投票或免费试用,都需要知道面对的是一千个人,还是一个拥有一千个钱包的人。常见的解决方案效果不佳:生物识别会侵犯隐私,KYC 会破坏隐私,而大多数行为信号都能直接买到。
运营账户农场的人可以购买关注者、批量上传帖子,还能填出一份令人信服的个人资料。但他们买不到十年历史。
为什么选择 Patina?
| KYC | World ID / 人格证明 | Patina | |
|---|---|---|---|
| 证明多年历史 | 否 | 否 | 是 |
| 证明你是独一无二的人 | 部分 | 是 | 否 |
| 无需钱包即可使用 | 是 | 否 | 是 |
| 无需生物识别即可使用 | 是 | 否 | 是 |
| 用户之后可以撤销访问权限 | 否 | 否 | 是 |
| 其他应用可以复用该证明 | 否 | 是 | 是 |
Patina 证明的是持有时长,而非独特性。今天就能验证一个刚出现的独一无二的人;但无法凭空产生十六年的佐证活动记录。两者相辅相成,工具描述也会明确说明这一点,而不会暗示相反的结论。
功能
所有权有证据,而非凭空假定
数据采集通过 Vana Desktop 进行:它会在用户自己的设备上打开浏览器,并要求用户登录。凭据不会离开设备,Patina 也不会看到密码。
但 Vana 有两种数据采集路径,且不会报告使用了哪一种。另一种路径会通过个人资料 URL 读取公开页面,这只能证明账号存在,无法证明账号由谁持有。因此,不能只要求“仅限桌面端”,而必须强制执行。
所以,每个数据来源都必须要求一个公开页面无法提供的范围;如果没有返回该范围,就会拒绝读取。LinkedIn 必须返回建立联系的日期,Spotify 必须返回已保存的曲目,YouTube 必须返回“稍后观看”列表。公开个人资料无法提供私密信息,因此用户最终会使用 Desktop 并登录。如果添加数据来源时没有提供一个此类范围,sources.test.ts 就会报错。
Steam 因同样的原因被移除。它的连接器不会让任何人登录:它只需 Steam Web API 密钥和 Steam ID,而 Steam ID 是公开的。任何人都可以把陌生人的账号年龄和好友日期当作自己的信息交给 Patina;与 YouTube 不同,Steam 没有可要求提供的私密范围。
Instagram 因相关原因已暂停。它的证明范围 instagram.posts 是 Vana 的服务器从公开页面采集的,因此读取只能证明账号存在,无法证明账号由谁持有;Instagram 也没有 Patina 可以要求提供的私密范围。我们不会提供 Instagram;Vana 的两条路径都会在花费任何费用前拒绝它;句柄解析器不会查询它;已存储的 Instagram 数据也会从所有评分中排除。等手机应用的已登录证明功能上线后,它就会恢复。
八个数据来源,十七个范围
GitHub、LinkedIn、Spotify、YouTube、Amazon、Uber、DoorDash 和 Shop。每个数据来源的所有范围都会放在同一次授权中,因此整个清单需要进行八次授权,而不是十七次。
只保留时间戳,不保留内容
每个范围都会请求日期信息。说明文字、地址、电子邮件、曲目名称、游戏名称以及其他人的姓名都会在存储任何内容前被丢弃;如果其中任何内容进入存储,测试就会报错。
已签名、可移植且有时效
每个非临时评分都附带一份 EIP-191 证明,任何人都可以使用 Patina 公布的地址离线验证。无需密钥,无需 OAuth,CORS 开放。
证明会在三十天后过期;过期时间包含在签名消息中,而非单独存放,因此验证时效性也可以离线完成。撤销列表也能解决同样的问题,但这会要求每个验证者都调用 Patina,而这恰恰是人们集成它时想要避免的依赖。评分使用与持有托管余额的密钥分开的密钥签名,因此一个密钥泄露不会导致另一个也泄露。
能说明自身状态
/api/health 会对数据库进行真实的写入和读取,并实时读取托管余额;如果部署无法为下一位用户提供服务,就会返回 503。这样,常规运行时间监控无需了解具体检查内容,也能发现故障。受密码保护的 /admin 会显示转化漏斗、用户数量,以及余额还能支持多少次连接。转化漏斗统计在服务器端完成:没有第三方脚本,没有 Cookie 横幅,也没有任何与隐私页面相矛盾的内容。
便于 Agent 读取
MCP 服务可在对话过程中向 AI Agent 提供评分,并通过句柄解析器返回账号使用时长,而不泄露身份。
评分
六个组成部分,权重依据各项在时间而非金钱上的成本。
| 组成部分 | 最高分 | 衡量内容 |
|---|---|---|
| 资历 | 30 | 所有已连接来源中能够证实的最早日期。满分对应十二年。 |
| 连续性 | 25 | 此人实际活跃的不同月份数,按绝对数量乘以覆盖率计算。 |
| 佐证 | 15 | 独立来源对日期的认同,每个来源按其自身资历加权。 |
| 背书 | 12 | 其他人选择建立连接的时间,而非人数。按距今时间加权。 |
| 深度 | 10 | 实际创作的内容;如果内容量集中在一阵子内产生,则会折减。 |
| 广度 | 8 | 相互佐证的独立账号。 |
资历和两个时间组成部分直接计分。深度、背书和广度则受它们限制:攻击者可以在一个下午里伪造内容量、朋友和广度,因此只有在真实经历提供支撑的范围内,这些项目才计分。15% 的最低分可避免真正年轻的人仅仅因为十九岁就被压到零分。
根据参考档案进行评分,断言见 score.test.ts:
| 档案 | 得分 |
|---|---|
| 跨四个账号活跃十一年 | 95 |
| 普通情况:十一年、两个账号、确有空档期 | 71 |
| 真正年轻:三年、两个账号、真实情况 | 26 |
| 账号农场:3,900 个购买的关注者,一周内发布 120 条内容 | 2 |
普通人与账号农场之间相差 69 分。这一差距正是本产品的价值所在。
诚实说明局限
这些说明会直接展示在网站上,而不是藏在这里:
- 无法证明真人身份。 它报告的是能够看到多少真实经历。这是证据,不是证明书。
- 低分不代表被指控。 年轻、私密或不活跃的账号得分较低,但这并不意味着他们做错了什么。
- 老账号可以被购买。 这会把免费攻击变成昂贵攻击,且成本会随账号资历增长。这并不意味着攻击不可能。
- 桌面应用能证明会话控制权,而非所有权。 买来的账号附带密码。门槛从“知道用户名”提升到“持有凭据”,这是很大的进步,但两者并非一回事。
- 证明范围是底线,不是保证。 要求提供某些私密信息,能迫使用户真正登录;但真正登录仍然只是一次登录。这项要求排除的是有人给陌生人的公开档案打分,而这正是会让整个分数失去意义的问题。
读取和保留的内容
下面列出的每项权限范围都会因其时间戳而被请求。此表体现了我们的信任依据,因此隐私页面上也会以大致相同的形式呈现。
| 权限范围 | 保留内容 | 收到后立即丢弃 |
|---|---|---|
linkedin.connections | dateConnected[]、数量 | 姓名、标题、个人档案 URL |
instagram.posts | Instagram 暂停期间不接收新数据;已持有的数据不参与评分 | 说明文字、图片、点赞、整个 who_liked[] 数组 |
spotify.savedTracks | added_at[]、总数 | 曲目名称、艺人、专辑 |
github.history | createdAt[]、哈希处理后的仓库、汇总后的互动量 | PR 和 issue 的标题及正文 |
linkedin.experience / education | 解析后的日期范围 | 公司、职位名称、学校、成绩 |
uber.trips | requestTime[]、数量 | 上车和下车地址、费用、城市 |
amazon / doordash / shop 订单 | 日期、数量 | 商品、商家、总金额、配送地址 |
youtube.profile | joinedDate、数量 | 电子邮箱地址 |
youtube.watchLater | 不保留任何内容 | 所有内容;仅将其作为已登录会话的证明进行请求 |
时间戳在存储前会折叠为月份区间。评分器只会查询月份,因此保留更精细的数据毫无必要。
架构
连接流程
graph LR
U[User] -->|1 Connect| P[Patina]
P -->|2 createAccessRequest<br/>all scopes for one source| V[Vana Account API]
V -->|3 approval URL| U
U -->|4 Open in Vana Desktop| D[Vana Desktop]
D -->|5 Playwright signs in<br/>on the user's machine| S[Source]
S -->|6 import| PS[Personal Server]
P -->|7 read every scope<br/>one grant| PS
P -->|8 settle 402| E[Escrow Gateway]
P -->|9 normalise, discard, score| R[(Redis)]
读取并结算
sequenceDiagram
participant App as Patina
participant PS as Personal Server
participant GW as Escrow Gateway
App->>PS: GET /v1/data/{scope}
PS-->>App: 402 with accessRecord
App->>GW: authorizeGrantPayment(accessRecord)
GW-->>App: receipt
App->>PS: GET /v1/data/{scope}
PS-->>App: 200 payload
Note over App: repeat per scope, same grant
App->>PS: acknowledgeRead (once, at the end)
acknowledgeRead 会将请求移至 completed,这是终态,不再处于可读取状态。如果在第一个范围读取后就触发它,用户付费后,剩余内容就会因 Personal Server 不再提供服务而无法读取。
评分流程
graph LR
RAW[Raw payload] -->|readScope| F[Fragment<br/>months + counts]
F -->|stored by scope| DB[(Redis)]
DB -->|evidenceFrom| EV[Evidence]
EV -->|scorePatina| SC[Score]
EV -->|buildStory| ST[Story]
SC -->|buildAttestation| AT[Signed attestation]
片段按范围作为键存储,并在评分时合并。重试读取时会覆盖对应片段,而不是再次累加其中的月份,因此连接不稳定也不会虚增分数。
快速开始
前置条件
- Node 20+
- 从 account.vana.org/developers 获取的 Vana 应用身份
- Upstash Redis 凭据(本地使用时可选;未提供时会回退到内存存储)
安装
git clone https://github.com/ramakrishnanhulk20/Patina.git
cd Patina
npm install
cp .env.example .env.local
填写 VANA_APP_PRIVATE_KEY 和 VANA_APP_URL,然后注册应用并为其托管账户注资:
npm run register:moksha
开发
npm run dev # development server
npm test # 118 tests
npm run typecheck # types, ignoring generated route types
npm run verify # typecheck + tests + production build
集成
REST
查询任何公开评分,无需密钥或 OAuth:
curl https://patinadata.xyz/api/verify/ramkumar
{
"username": "ramkumar",
"score": 71,
"verdict": "Well established",
"oldestYear": 2012,
"yearsOfHistory": 13.6,
"sourcesConnected": ["github", "linkedin", "spotify"],
"provisional": false,
"issuedAt": "2026-08-26T12:00:00.000Z",
"expiresAt": "2026-09-25T12:00:00.000Z",
"attestation": {
"app": "0x...",
"message": "Patina score attestation
username: ramkumar
score: 71/100
...
expiresAt: 2026-09-25T12:00:00.000Z
app: 0x...",
"signature": "0x...",
"expiresAt": "2026-09-25T12:00:00.000Z"
}
}
离线验证证明
import { verifyMessage } from "viem";
const ok = await verifyMessage({
address: "0x3989bdFaf3BA242d27B4D0cEed98F446d0c52DAD",
message: attestation.message,
signature: attestation.signature,
});
MCP
{
"mcpServers": {
"patina": {
"type": "http",
"url": "https://patinadata.xyz/api/mcp"
}
}
}
| 工具 | 用途 |
|---|---|
get_patina_score | 获取 Patina 用户名的完整评分明细 |
check_threshold | 根据年数、评分或来源数量进行是/否信任判断 |
resolve_identity | 将 GitHub 或 LinkedIn 用户名映射到评分,同时不泄露身份。Instagram 查询已暂停 |
verify_attestation | 验证签名是否确实来自 Patina |
测试结果
174 passing
| 测试套件 | 测试数 | 覆盖范围 |
|---|---|---|
normalize.test.ts | 30 | 信封结构、丢弃保证、幂等性 |
score.test.ts | 27 | 每个组成部分、门槛、参考档案 |
store.test.ts | 26 | 跨设备身份、用户名、移除单个来源、删除 |
mcp-lookup.test.ts | 20 | 解析器的隐私规则、签名、判定偏移 |
story.test.ts | 19 | 时间线、活动、佐证材料、历史记录不足时的文案 |
routes.test.ts | 15 | 真实 HTTP 请求:会话、签名下限、管理员门槛 |
device.test.ts | 12 | 哪些设备可以完成连接 |
attest.test.ts | 10 | 签名、过期、密钥隔离、已发布地址偏移 |
sources.test.ts | 9 | 所有权不变量:不得通过公开页面查询任何来源 |
ratelimit.test.ts | 4 | 托管账户滥用限制 |
altcha.test.ts | 2 | 工作量证明机器人检查 |
最值得了解的一项测试是:nothing sensitive survives normalisation。它会输入一份包含看似真实的电子邮件地址、两个家庭住址、一段说明文字、一位同事的全名和 LinkedIn URL、一款游戏的名称、一家雇主、一首歌、一件产品和一家餐厅的载荷,然后断言这 13 项内容均不会出现在任何保存的数据中。
项目结构
src/
├── app/
│ ├── api/
│ │ ├── vana/ request + data: the connect spine
│ │ ├── patina/ me, username, data, restore, delete
│ │ ├── admin/ session, index rebuild
│ │ ├── verify/ public score lookup
│ │ ├── badge/ embeddable SVG badge
│ │ └── mcp/ MCP server
│ ├── connect/ the evidence board, and the phone handoff
│ ├── my-data/ see it, download it, remove any of it
│ ├── admin/ the funnel, the numbers, the money left
│ ├── u/[username]/ public page and story
│ ├── verify/ verifier, including an offline checker
│ └── docs/ integration reference
└── lib/
├── score.ts the whole argument, weight by weight
├── normalize.ts 18 scopes in, timestamps out
├── sources.ts the manifest, and the proof scope each source must return
├── device.ts whether this device can finish a connection at all
├── vana.ts app identity, one controller per source
├── vana-settle-read.ts paid reads, escrow settlement
├── escrow-balance.ts how many more connections the balance will fund
├── metrics.ts the funnel, counted server-side and nowhere else
├── store.ts profiles keyed on Personal Server identity
├── admin.ts one operator, one password, fails closed
├── story.ts evidence as a narrative, and exhibits
├── attest.ts signed attestations, expiring, on their own key
└── mcp-lookup.ts what an agent is allowed to learn
src/lib/score.ts 是整套论证的核心。每个权重都有注释解释其设定依据。
技术栈
| 层 | 选择 |
|---|---|
| 框架 | Next.js 16、React 19、App Router |
| 语言 | TypeScript |
| 样式 | Tailwind CSS 4、自定义令牌层 |
| 数据 | Vana Data Portability Protocol(@opendatalabs/vana-sdk) |
| 存储 | Upstash Redis、内存回退方案 |
| 签名 | viem、EIP-191 |
| Agent | Model Context Protocol(mcp-handler) |
| 滥用防护 | ALTCHA 工作量证明、按会话限流 |
| 测试 | node:test,无测试框架 |
许可证
MIT,详见 LICENSE。
致谢
本项目基于 Vana 及其 Data Portability Protocol 构建。连接器来自 PDP-Connect/data-connectors。品牌图标来自 Simple Icons 和 Font Awesome 的免费品牌图标集,均以内联方式提供,因此应用不会发出外部请求。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。