QUEST LOG · September 27, 2026· UPDATED October 29, 2026
12 秒,我的測試網私鑰被掃空了
「你可以幫我看一下這兩個的鏈上紀錄嗎,是不是私鑰外洩了?」
黑客松截止前三十幾個小時,我在 Sepolia 上轉了 0.2 顆測試 ETH 進部署用的錢包。
12 秒後,錢就不見了。
這篇是那個 session 的故事。它大約燒了 4.1 億 token,本來的任務是「幫我把 ENS v2 跟 Uniswap 的機制寫成合約、部署上鏈」,結果中間插播了一段資安事件,最後還碰上有人 force push main。
這是我在 9 月的 ETHTokyo 2026 做的作品。先講結果:沒拿名,但做得很爽。
# 原本的設計:名字樹就是族譜
先講一下我們想做什麼。UNMAPPED(原本叫 Unwritten)是一個用 AI 生成遊戲世界的 app,每個世界是一張「卡帶」,而且可以被別人分岔出去 remix。
一開始我丟了一份從 ChatGPT 那邊聊出來的設計給它看,問「目前裡面的機制還行嗎」。來回討論了 NFC 實體卡(決定先不上)、Uniswap 一定要有、有沒有更好的 EIP 可以混,最後收斂成一句話:
ENS 決定這個世界是誰、屬於誰,Uniswap 決定它值多少錢、怎麼交易,而 hook 把交易的錢照族譜分給名字的主人。
具體一點:
- 名字樹 = 族譜:
night.mushroom.zelda.unmapped.eth從右往左讀,就是「zelda 被改成 mushroom,mushroom 再被改成 night」 - CCA 公平發行:每個世界的代幣用 Uniswap 的 Continuous Clearing Auction 賣,remix 要用上游世界的代幣出價,每發行一個新 remix,就是在買它的祖先
- v4 hook 分潤:拍賣畢業後自動開 v4 池,hook 把交易的錢照族譜分給名字的主人
我說「走 ensv2 和 cca 感覺最好對吧 幫我先寫一套 lineage market」,它就寫了,24 個檔案一個 commit。
接下來只剩部署。
# 「錢還不夠」
我說「sepolia eth都處理好了」。它先做三件唯讀的檢查:.env 裡有沒有私鑰(不印出來)、那個地址的餘額、目前的 gas price。
先別急著部署:
.env裡這把私鑰的地址,餘額還不夠。
餘額:0.00046 Sepolia ETH
我以為是還沒到帳,過了幾分鐘問「有了嗎」:
還沒有,錢還不夠。
.env裡的私鑰換過了(檔案 01:41 被改過)……上次檢查的舊地址0x0c9F…2d34,現在是 0 ETH。
我明明有轉錢進去,為什麼兩個地址都沒錢?
截止前三十幾個小時看到這種東西,第一個念頭就不是「到帳慢」了。
# 鏈上的 12 秒
我請它查兩個地址的鏈上紀錄。它先講清楚自己做過什麼:之前所有 dry run 用的都是每次隨機產生的臨時帳號,從來沒用 .env 的私鑰送過任何交易。然後回來的結論是:
舊私鑰確定外洩了,而且早在一年多前就被偷,不是這個專案造成的。
經過是這樣(時間照它報的台灣時間):
- 00:36:12:我從新地址轉了 0.1998 ETH 進舊地址
- 00:36:24,12 秒後:有人用這把私鑰簽出一筆 EIP-7702 交易,把這個地址委派給一個 sweeper 合約,同一筆交易就把 0.4664 ETH 全部轉走
- 00:40:48:又有 0.05 ETH 從 faucet 進來,委派的程式碼在同一筆交易裡就自動轉走了
收錢的那個地址,在 Sepolia 上累積了 117 ETH,最近 50 筆轉入來自 18 個不同地址。這是一隻專門掃外洩私鑰的機器人。
再往回查,同一把私鑰在 Arbitrum One 主網上,2025-07-20 就把 1.86 USDC 和 0.0035 ETH 轉到同一個地址了。
也就是說,這把鑰匙一年多前就外洩了,我一直不知道,還拿它當黑客松的部署錢包。至於當初是從哪裡漏出去的,鏈上看不出來,能確定的只有:它早就不是我一個人的鑰匙了。
# EIP-7702 讓「外洩」變成「詛咒」
以前私鑰外洩,機器人要搶在你之前把錢轉走,是一場 gas 競賽。
EIP-7702 之後不一樣了。攻擊者把你的 EOA 委派給一個合約,從此任何轉進來的錢,在同一筆交易裡就被合約自動轉走。不用搶、不用盯,連 faucet 給的 0.05 都逃不掉。
AI 的原話:
這個地址已經廢了。 7702 委派還掛在上面,之後轉進去的任何東西都會被立刻轉走,包括 faucet 領的錢,請不要再往裡面轉。
以前外洩的私鑰是一個漏洞,現在是一個永久的黑洞。
# 換鑰匙,然後把錢包從 app 裡拿掉
處理很直接:我叫它「你創一個我 faucet 好了」。它產生一把新私鑰寫進 .env(在 .gitignore 裡、權限 600),私鑰本身不印在任何地方,只給我地址。新的那把在 Sepolia 上 nonce 0、餘額 0、沒有委派程式碼。
部署之前,它還先把部署腳本改成可以安全重跑:萬一中途失敗,已經部署好的合約會跳過,不會重複花 gas。commit 之前也再確認一次上鏈的 log 裡沒有私鑰。
然後 unmapped.eth 註冊、三份合約上 Sepolia,大約 4–5 分鐘。
這件事之後,我的下一個需求是:
app端不要碰到錢包會比較好 勁量只用裡面有的那個passkey
於是玩家這邊改成 passkey 帳戶:PasskeyAccountFactory 用公鑰推算地址、第一次使用才部署;gas 由一個代付站出。最後它在 app 裡把第一個世界走完一整輪:
發行 → 拍賣 → 結算 → 開池 → 買入 → 發分潤。全部都是在 app 裡操作、真的上鏈,玩家從頭到尾沒有連錢包。
買入那一筆是 10 USDC 換到 536 AETHERLAND,這個世界因此累積了 5.408 AETHERLAND 的分潤。
# 插曲:有人 force push 了 main
正當另一個 session 準備把這批改動分兩次 commit 的時候,訊息傳過來:
遠端 main 被一個 Codex 任務改寫並 force push(它說是你要求的),新的 HEAD 是 7a04adf,舊的 commit 雜湊全部換掉了。
我只回了一句「你確保一下」。它沒有直接相信那個 session 的計畫,而是自己 fetch 新的 main,逐一比對被改寫的檔案有沒有跟市場這批重疊,確認工作區不會默默把那些改動蓋回去。
聽起來很恐怖,其實沒什麼:就是有衝突,總要有人改,誰改都沒差。那時候同一個 repo 上同時有 Claude 好幾個 session 加上 Codex 在跑,一個做市場、一個做第二階段、一個管 commit,各自跑各自的 E2E。
多 session 各自跑 E2E,效率是真的高。 代價就是偶爾要有人停下來確認一下,大家改的還是不是同一份東西。
那時候離截止(9/27 09:00 JST)大約還有 28 小時。合約編譯出來 20,595 bytes,離 24,576 的上限還有一點空間。
# 學到的事
- 測試網的錢包也是錢包。 這把私鑰在主網上被偷過,我卻拿它來部署。Sepolia ETH 不值錢,但同一把鑰匙在別的鏈上可能值錢
- EIP-7702 改變了外洩的後果。 外洩的地址不是「要小心」,是「直接報廢」,連轉進去的測試幣都會被吃掉
- 讓 AI 先做唯讀檢查。 它在送出任何交易前,先查餘額、查私鑰對應的地址、查 gas。就是這個習慣,讓「錢不見了」在部署前就被抓出來,而不是部署到一半
- 玩家不該碰到私鑰。 這件事之後我更確定 app 端要全部走 passkey,連「錢包」這兩個字都不要出現
說真的,如果沒有這次被掃,我大概會一直用那把鑰匙到某一天在主網上出事。
12 秒很短,但比一年多前外洩之後、我一直沒發現的那段時間誠實多了。
ETHTokyo 最後沒拿名。但一個被掃空的錢包、一套從發行到分潤都真的上鏈的流程、還有一群 session 一起趕死線的體驗,對我來說已經值回票價。
(這篇 90% 由 AI 撰寫,畢竟過一週我就被駭客從 solana 幹走我所有 JitoSOL,導致我應該不會再碰 Solana Defi,除非自己做)
