内容
# 无需托管的Solana托管解决方案 — 一个无私钥的保险箱,管理员可以释放资金,但永远不能窃取它们
**`solana-marketplace-escrow`** 是一个无需托管、由仲裁者中介的 **Solana托管解决方案**。该托管保险箱具有 **无可用私钥**,因此点对点市场管理员可以 **释放** 资金给参与者,但永远不能 **窃取** 它们。
> 一个管理员无法带走资金的托管解决方案。
TypeScript 软件开发工具包 · Anchor 程序 · MCP 服务器 · x402 按使用付费 API · 现场演示。
**▸ 现场演示:** https://nirholas.github.io/solana-marketplace-escrow/ — 无私钥托管解决方案,原子交换和管理员控制台,在浏览器中运行真实的开发网络交易。
---
## 问题
运行具有人类管理员的市场天真方法是,将资金存放在托管钱包中,并将管理员的私钥交给他们,以便他们可以支付纠纷赢家。该私钥可以发送资金到 **任何地方** — 一旦泄露,一个受损的笔记本电脑,一个流氓管理员,所有的托管资金都会立即被清空。一个`Keypair.fromSecretKey(...)`托管解决方案是一个对每个用户的钱都有危险的加载枪。
这不是假设的。这是大多数“托管”机器人和许多市场后端今天的实际运作方式:一个热键在环境变量中,对池资金具有无限权限。
## 洞察
你不能泄露一个不存在的私钥。
将资金存放在 **没有可排空私钥** 的地方,并将管理员的权限从 **“将钱转移到任何地方”** 减少到 **“选择买家预先固定的几个结果之一”**。管理员成为 **裁判,而不是托管人**:他们可以宣布获胜者,但目的地在资金时已经设定,无法更改。
安全的授权模型,通过构造强制执行:
| 结果 | 支付给 | 授权人 | 何时 |
| --- | --- | --- | --- |
| `release:by-buyer` | 卖家 | 买家 | 买家确认交付(顺利路径) |
| `release:by-arbiter` | 卖家 | 仲裁者 | 为卖家解决纠纷 |
| `refund:by-seller` | 买家 | 卖家 | 卖家取消(顺利路径) |
| `refund:by-arbiter` | 买家 | 仲裁者 | 为买家解决纠纷 |
- **买家** 只能将资金推送到 **卖家**。从不自我退款。
- **卖家** 只能将资金推送到 **买家**。从不自我支付。
- **仲裁者** 可以选择任一方 — 但 **永远不能** 将资金重定向到第三方地址,包括他们自己的地址。没有任何指令,在任何结果中,都可以支付给其他人,也没有私钥可以伪造新的指令。
## 三个后端,一个API
| 后端 | 信任模型 | 需要部署程序? | 最适合 |
| --- | --- | --- | --- |
| **`presign`** | 无私钥通过 **约定**(短暂私钥被销毁) | 否 — 今天就可以运行,在任何地方 | 快速集成,零部署 |
| **`program`** | 无私钥通过 **构造**(PDA没有私钥) | 是 — Anchor PDA程序 | 生产/最大保障 |
| **`custodial`** | **平台托管**(后端持有私钥) | 否 | 管理员/管理员控制台;主资金原子释放(`nirholas/atomic`模式) |
前两个是 **无私钥** — 没有人可以窃取,甚至平台。第三个,**`custodial`**,是您可能真正想要用于托管的市场模式:平台持有主钱包(SOL)+ 每个托管私钥,并且管理员从Web控制台释放 — 后端发出 **一个原子交易**,其中主支付费用,托管私钥签名转账(主代表托管钱包操作)。管理员不持有私钥。参见[`apps/dashboard`](apps/dashboard)和[docs/custodial.md](docs/custodial.md)。验证端到端:管理员创建托管解决方案 → 管理员点击释放 → 卖家付款。
**`presign`** — 一个短暂的保险箱代币账户和一个 **持久随机数** 持有资金。保险箱私钥预先签署完整的固定目的地结果集,然后 **被销毁**。从那时起,唯一可以移动资金的交易是那四个 — 每个缺少一个签名(其授权人的),每个支付一个在资金时固定的目的地。结算一个推进随机数,原子无效其余的。
**`program`** — 资金存在于 **程序派生地址**,它在数学上是不可行的:**没有私钥存在**。只有托管程序可以通过`invoke_signed`移动资金,并且只能到一个其代码硬编码的目的地。仲裁者是一个存储的公钥,其签名仅 **触发** 一个受限指令。这是无私钥 **通过构造** — 永远不会泄漏。
两个都原子结算,可选地作为 **Jito bundle**,因此多步骤解决(释放+费用+账户关闭)作为一个整体不可分割地完成,没有前跑窗口。
## 托管与原子性 — 哪一层解决什么问题?
一种常见的直觉是使用Jito bundles或原子交换来阻止管理员偷窃。它们是错误的 **层** 来解决 **托管** 问题 — 但它们完全适用于它们自己的层,并且 **此SDK提供两者**(参见[原子交换和Jito bundles](#原子交换和Jito-bundles))。下面的重点不是“不要使用它们” — 而是“将每个用于它实际做的事情”。
| 机制 | 它解决的层 | 能阻止管理员偷窃吗? | 在此仓库中 |
| --- | --- | --- | --- |
| **Jito bundle** | 交付 — 原子,有序,防止前跑 | ❌ 一个交付信封,不是授权;每个交易仍然需要自己的签名 | ✅ 用于原子结算 — `{ viaBundle: true }` |
| **原子交换** | 同时交易 — 两个腿或两个都不 | ❌ 不适用 — 当交易即时时,它消除了对托管的需求 | ✅ 一流的`AtomicSwapClient` |
| **多签名**(Squads) | 密钥阈值 — 没有单个密钥 | ❌ 仲裁者仍然可以发送资金到任何地方 | 推荐用于仲裁者/升级权限 |
| **无私钥保险箱**(PDA / presign) | **托管 — 目的地已固定,而不是选择** | ✅ **是的,结构上** | ✅ 核心原始 |
所以:**无私钥保险箱** 用于托管,**Jito bundles** 用于原子结算,**原子交换** 用于即时交易路径 — 每个做它擅长的事情,所有这些都在此SDK中。
## 原子交换和Jito bundles
托管用于 **异步** 交易。当交易 **同时且完全在链上** 时,您不需要托管 — 以 **原子交换** 结算:两个资产在一个交易中移动,两个当事人签署,要么两个腿都执行,要么都不执行。没有托管,没有仲裁者。(验证链上:1.0令牌A↔2.0令牌B在一个交易中。)
```ts
import { AtomicSwapClient } from 'keyless-escrow';
const swap = new AtomicSwapClient({ connection });
await swap.execute(
{ a: { owner: alice.publicKey, mint: tokenA, amount: 1_000_000n },
b: { owner: bob.publicKey, mint: tokenB, amount: 2_000_000_000n } },
[alice, bob], // 两方签署 — 原子性保证
{ viaBundle: true }, // 可选:通过Jito防止前跑
);
```
而 **Jito bundles** 是一个真正的集成解决方案 — 结算或交换可以作为一个原子、防前跑的bundle交付(带有必要的提示到Jito提示账户,这是一个天真的`sendBundle`遗漏):
```ts
await escrowSvc.settle(escrow, 'release:by-arbiter', arbiter, { viaBundle: true });
```
参见 [docs/atomic-swap.md](docs/atomic-swap.md) 和 [docs/jito.md](docs/jito.md)。
## 快速开始
```bash
npm install keyless-escrow @solana/web3.js
```
```ts
import { Connection, PublicKey } from '@solana/web3.js';
import { PresignBackend } from 'keyless-escrow';
const connection = new Connection('https://api.devnet.solana.com', 'confirmed');
const escrowSvc = new PresignBackend({ connection });
// 买家打开并资助托管解决方案(买家必须已经持有`amount`的`mint`)。
const escrow = await escrowSvc.open(buyer, {
parties: { buyer: buyer.publicKey, seller: SELLER, arbiter: ARBITER },
mint: MINT,
amount: 1_000_000n, // 1.0 一个6位小数的代币
});
escrow.vaultKeyDestroyed; // true — 现在没有任何脚本可以移动资金
// 为卖家解决纠纷。只有仲裁者可以运行这个,并且它只能支付给卖家 — 从不仲裁者。
await escrowSvc.settle(escrow, 'release:by-arbiter', arbiter, { viaBundle: true });
```
在devnet上运行完整的生命周期:
```bash
node --experimental-strip-types examples/devnet-lifecycle.ts
```
## 仓库布局
| 路径 | 它是什么 | 状态 |
| --- | --- | --- |
| [`packages/sdk`](packages/sdk) | **`keyless-escrow`** — TypeScript SDK(预签名后端,Jito结算,结果) | ✅ 已准备就绪 — 已测试 |
| [`program`](program) | Anchor PDA托管程序(`program`后端) | ✅ 编译 + 链上生命周期验证(`E8Spo…Ckda`) |
| [`packages/mcp`](packages/mcp) | MCP服务器将托管生命周期暴露为代理工具 | ✅ 已准备就绪 — 握手验证 |
| [`packages/x402`](packages/x402) | x402按使用付费的托管即服务API | ✅ 已准备就绪 — 构建并提供 |
| [`apps/dashboard`](apps/dashboard) | 管理员/管理员Web控制台(托管原子释放) | ✅ 已准备就绪 — 端到端验证 |
| [`demo`](demo) | 完整的devnet生命周期浏览器演示 | ✅ 已准备就绪 — 构建 |
| [`examples`](examples) | 可运行的端到端脚本 | ✅ 开发网络生命周期 |
## 文档
- [specs/protocol.md](specs/protocol.md) — 规范授权模型 + 线上格式
- [docs/architecture.md](docs/architecture.md) — 各个部分如何组合
- [docs/atomic-swap.md](docs/atomic-swap.md) — 无托管的P2P交换 · [docs/jito.md](docs/jito.md) — 原子bundle结算
- [docs/custodial.md](docs/custodial.md) — 托管后端 + 管理员/管理员控制台(`nirholas/atomic`模式)
- [docs/security-model.md](docs/security-model.md) — 威胁模型 + 信任假设
- [program/AUDIT.md](program/AUDIT.md) — 审计范围 + 不变式 · [docs/deployment.md](docs/deployment.md) — 主网运行手册
- [SECURITY.md](SECURITY.md) · [CONTRIBUTING.md](CONTRIBUTING.md) · [CHANGELOG.md](CHANGELOG.md)
## 安全模型
SDK提供 **离线安全不变测试** (`npm test -w keyless-escrow`),证明构造而不是断言:所需的签名者恰好是`{vault, authorizer}`,目的地在保险箱签名之前固定,任何目的地更改都会破坏签名消息(盗窃尝试失败),并且只有指定的授权者可以完成每个结果。
链上`program`后端针对Solana验证器进行端到端验证:打开将存款到无私钥PDA保险箱,仲裁者释放给卖家,**未经授权的签名者试图释放被程序拒绝**(卖家不能支付自己)。
**信任注意(预签名后端):** 保证依赖于临时保险箱密钥实际上被销毁。SDK生成它,永不持久,永不返回,并在签名后覆盖它 — 但JavaScript的管理内存无法 **证明** 零化。对于一个由构造导致的无私钥保险箱,使用`program`后端(PDA没有私钥存在)。
## 开发
```bash
npm install
npm test --workspaces --if-present # 运行每个包的测试
npm run build --workspaces --if-present
```
## 许可证
MIT — 参见 [LICENSE](LICENSE)。
连接信息
猜你喜欢
暂无推荐服务器