内容
# NFT 洗盘交易记忆代理
一个以记忆驱动的代理,用于检测 NFT 洗盘交易并随着时间的推移**学习更早地发现它**。当一个洗盘交易钱包对被确认时,代理不仅记录该模式,还会追溯到钱包的完整历史,以挖掘在明显的往返交易之前的最早前兆信号,并将它们存储为 CockroachDB 中的一个不断增长的**手册**。每个确认的案例都使下一次检测更早更准确。
为 **CockroachDB × AWS “用 Agentic Memory 构建”黑客马拉松**而构建。
> **数据与范围:**仅限公共链上数据(Dune Analytics `nft.trades` + Etherscan)。目标:LooksRare / 以太坊 / 2022,据独立研究(Niu et al., 2024)估计,约 94.5% 的交易量是洗盘交易。没有使用专有或交易所内部数据或方法。
---
## 演示
```bash
uvicorn app.main:app --port 8100 # 然后打开 http://localhost:8100
```
### 1. 每日监控 — 分析师的晨间工作清单
定时运行扫描每日新增交易,检测新鲜的洗盘交易环,并根据与**内存中已确认案例的匹配程度**进行排名。在连续扫描的 15 天中,**所有 110 个检测到的环都与已确认的案例相似**,因此分析师打开一个已经预处理的清单。选择一个范围或日期;点击某一列即可跳转到那一天。

### 2. 内存变得更锐利 — 重点
分析师确认一个方案;代理将其写入 CockroachDB。几天后,**同样的犯罪者以完全不同的方式返回** — 关键词过滤器可能会错过它。距离表显示内存带来了什么:同样的方案从 **0.5051 → 0.3741** 的余弦距离。被抓住,而且**更早地被抓住**。

### 3. 每个案例的证据 — 为什么这不是猜测
案例 C001:5 个钱包,351 笔交易**彼此之间**,代币 **#689 返回到组 114 次**。真正的销售会将 NFT 移交给新所有者;这些循环永远不会。按 **重放** 按钮,以块顺序观看环形交易 — 真实的链上顺序,而不是动画。

---
## 实际运行
针对实时 CockroachDB Cloud 集群,使用 2022 年 LooksRare 真实数据:
| | |
|---|---|
| 消费的交易 (`nft_trades`) | **83,901** |
| 每日检测运行(2023-02-06 → 02-20) | **15 个连续的日子** |
| 检测到的勾结案例 (`collusion_cases`) | **147** (37 个基础 + 110 个来自每日运行) |
| 每日运行环与已确认案例匹配 | **110 / 110** |
| 手册模式在矢量内存 (`flagged_patterns`) | **37** |
| 分布式矢量索引 | `flagged_patterns_embedding_idx` — **实时** |
| 嵌入 | AWS Bedrock Titan 文本嵌入 V2,1024 维 — **实时** |
| 标志理由 | AWS Bedrock 聊天模型 — **实时** |
---
## 工作原理(管道)
1. **摄取** LooksRare 2022 交易从 Dune → CockroachDB。
2. **确认** 洗盘交易对通过对称往返启发式(对交易进行近乎相同的计数)+ Etherscan 上的资金关系检查。
3. **挖掘前兆**:对于每个确认的对,拉取完整的钱包历史并工程早期信号特征(从第一个资金到第一个交易的时间,资金源多样性,对手集中度)。
4. **学习** 哪些前兆最好地预测最终确认(AWS SageMaker),将最佳信号转换为手册条目。
5. **记住**:将手册条目(AWS Bedrock)嵌入 CockroachDB 的矢量索引。
6. **检测** 新活动通过矢量相似性 + 规则阈值;代理用一个人类可读的解释来标记,引用最接近的已知先例。
7. **闭环**:人类批准/拒绝被写入 CockroachDB — 使代理随着时间变得更好的内存。
## 使用的 CockroachDB 工具
- **分布式矢量索引** — **实时**。`VECTOR(1024)` 列 + C-SPANN 索引在 `flagged_patterns` 上;每个分类都是一个相似性搜索。参见 [`sql/schema.sql`](sql/schema.sql)。
- **持久代理内存** — **实时**。`confirm()` / `reject()` 裁决是行,不是会话状态,因此改进在重启后存活并被每个代理实例共享。
- **云托管 MCP 服务器** — **实时**。“询问内存”面板以纯英语回答分析师问题;代理读取模式并在托管 MCP 端点上运行只读 `select_query`,然后显示它生成的 SQL。它从不为此路径打开自己的 SQL 连接,并且不持有任何写入凭证 — [`src/crdb_mcp.py`](src/crdb_mcp.py) 拒绝允许列表外的任何工具,因为群集管理员密钥可以调用 `insert_rows`。准确性通过手写的真实查询检查:`python -m src.eval_ask` (8/8)。
## 使用的 AWS 服务
- **Bedrock — Titan 文本嵌入 V2** — **实时**。将每个手册条目和每个传入活动嵌入到 1024 维空间中,矢量索引搜索。
- **Bedrock — 聊天模型** — **实时**。用最接近的前例写出普通语言理由。默认为 Claude Haiku 4.5;参见 *Bedrock 模型访问* 以下。
- **Lambda** — _(计划)_ 驱动 `src/daily_run.py` 的每日定时器在生产环境中。
- **SageMaker** — _(计划)_ 训练前兆信号模型,提取特征重要性。
- **S3** — _(计划)_ 原始响应 / 审计存档。
### Bedrock 模型访问
Bedrock 上的 Claude 需要一次 **“Anthropic 使用案例详情”** 表格,每个 AWS 账户(Bedrock 控制台 → *模型访问*)。在获得批准之前,该账户会收到 `ResourceNotFoundException`,因此 `src/bedrock.py` 自动回退到另一个 Bedrock 模型,并且 UI 用 **实际生成理由的模型** 来标记。覆盖任一模型与 `BEDROCK_CHAT_MODEL` / `BEDROCK_FALLBACK_CHAT_MODEL`。
---
## 安装
需要 Python 3.11+。
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # 然后填写您实际的 DUNE_API_KEY 和 ETHERSCAN_API_KEY
```
然后设置集群并加载数据:
```bash
python -m src.crdb_init # 创建模式 + 分布式矢量索引
python -m src.crdb_load # 将交易加载到 CockroachDB
python -m src.ring_detect # 查找闭环交易环 -> collusion_cases
python -m src.build_playbook # 通过 Bedrock 将确认的案例嵌入到矢量内存
python -m src.daily_run --date 2023-02-16 # 一个“今日收获”运行
```
## 运行(里程碑 1 — 本地信号管道)
```bash
# 1. 从 Dune 拉取顶级候选对(LooksRare 2022)。
# 如果您在 Dune UI 中从 sql/top_pairs.sql 创建了查询:
python -m src.dune_pull --query-id <your_query_id>
# 或尝试从 .sql 文件中编程创建:
python -m src.dune_pull --sql sql/top_pairs.sql --name "top pairs"
# 2. 按往返对称性对配对进行评分,并标记洗盘交易候选。
python -m src.pair_heuristic
# 3. 通过 Etherscan 跟踪每个候选钱包的资金历史。
python -m src.etherscan_trace
```
输出位于 `data/raw/` (原始拉取)和 `data/processed/` (评分配对,钱包特征)。
## 架构
设计重点:**证据和内存存在于同一个数据库中**。环形从交易中检测出来,写入案例,嵌入,并针对每个以前的裁决进行搜索 — 而无需将关系存储同步到单独的矢量存储。
```mermaid
flowchart LR
A["Alchemy getNFTSales<br/>Etherscan"] --> D["环形检测<br/>图循环 · networkx"]
D --> CC[("collusion_cases<br/>+ nft_trades")]
CC -- "案例注释" --> EMB["Titan 嵌入 V2<br/>1024 维"]
EMB --> FP[("flagged_patterns<br/>VECTOR 1024<br/>分布式矢量索引")]
NEW["每日运行<br/>今日收获"] --> TR["分类<br/>余弦搜索 vs 内存"]
FP --> TR
TR --> LLM["Bedrock LLM<br/>普通语言理由"]
LLM --> AN["分析师<br/>确认 / 拒绝"]
AN -- "裁决成为一行" --> FP
ASK["询问内存<br/>自然语言"] --> MCP["托管 MCP 服务器<br/>只读 SQL"]
MCP --> CC
classDef crdb fill:#1a2b3d,stroke:#5b9dff,color:#e6edf3
classDef aws fill:#3d2a1a,stroke:#ffb84d,color:#e6edf3
class CC,FP,MCP crdb
class EMB,LLM aws
```
<sub> 蓝色 = CockroachDB 云 · 琥珀色 = AWS Bedrock </sub>
**重要的循环** 是虚线:每个分析师的裁决成为 `flagged_patterns` 中的一行,因此下一次分类搜索更大的更锐利的内存 — 而且由于它通过嵌入而不是关键词匹配,因此即使方案被描述为完全不同的词语,也能被抓住。
## 许可证
MIT — 参见 [LICENSE](LICENSE)。