錢包裡的 USDT,其實不是”幣”
做錢包時可能會遇到一個挺反直覺的點:USDT、USDC 這些天天用的”穩定幣”,在鏈上根本不是”幣”。這篇分享下”原生幣 vs Token”區別的過程

一、原生幣 vs Token:一個本質區別
鏈上的資產其實分為兩類:
| 原生幣(Coin) | 代幣(Token) | |
|---|---|---|
| 例子 | ETH、BNB、TRX、SOL、BTC | USDT、USDC、各種項目幣 |
| 是什麼 | 鏈自帶的貨幣 | 鏈上的一個智能合約 |
| 記在哪 | 直接記在鏈本身的帳本上 | 記在那個合約自己的帳本裡 |
| 用途 | 可以用來付 gas(手續費) | 不能直接付 gas |
關鍵的一句話:
❝
USDT(以太坊上的)本質是一個智慧合約裡的一行記帳。 以太坊這條鏈本身”不知道”你有多少 USDT——只有 USDT 那個合約自己知道。
那個合約內部,大致上就維護著一張表:”哪個地址 → 有多少 USDT”。你轉 USDT,就是去改這張表裡的數字。

二、為什麼錢包”看不到”你的代幣
這個差異直接解釋了一個常見問題:為什麼錢包不會自動顯示我所有的代幣?
- 查 ETH 餘額:餘額就在鏈的帳戶狀態裡,錢包直接問一句就有了
- 查 USDT 餘額:餘額不在帳戶狀態裡,而在 USDT 合約的儲存裡。錢包必須主動去”調用”這個合約,問它 balanceOf(我的地址)
// 查出原生幣(ETH)餘額:鏈上帳戶狀態直接就有
val ethBalance = rpc.call("eth_getBalance", myAddress, "latest")
// 查代幣(USDT)餘額:得去"呼叫"USDT 合約,問它 balanceOf
val usdtBalance = rpc.call("eth_call", mapOf(
"to" to usdtContractAddress, // 得先知道 USDT 的合約地址
"data" to encodeBalanceOf(myAddress) // 編碼一次 balanceOf 調用
), "latest")
所以錢包不會自動知道你持有哪些代幣──它得先知道這個代幣的合約地址,才能一個個去查。
這就是為什麼”添加代幣”時要你貼上一段合約地址。主流錢包內建了 USDT、USDC 這些熱門幣的清單,所以預設能看到;冷門一點的幣,就得你手動加。
三、為什麼要轉 USDT,還得先有 ETH
既然 Token 是合約,那”轉 USDT”這個動作,本質是:
❝
呼叫 USDT 合約的 transfer 函數 = 發動一筆交易 = 要付 gas = gas 用 ETH 付。
於是有了那個經典困惑:
❝
“我錢包裡明明有 100 USDT,為什麼轉不出去?”
因為你沒有 ETH 付 gas。 USDT 自己不能用來付 gas,你得另外備一點原生幣。這是錢包客服天天遇到的問題。
❝
補充:新的”帳戶抽象」(ERC-4337)技術正在讓”用代幣付 gas”變得可能,但目前大多數場景,轉代幣還是得有原生幣。
四、approve 授權:最容易踩的坑
如果你用過 DEX(去中心化交易所),一定看過一步叫 approve(授權)。
為什麼需要它? 一個合約(例如 Uniswap)不能直接伸手拿走你的代幣。要讓它能動你的幣,你得先授權一個額度給它:
你調用代幣合約的 approve(某合約, 額度)
代幣合約記下:”這個合約可以動我最多 X 個幣”
之後那個合約就能在額度內,把你的幣轉走(transferFrom)
坑在哪? 很多 dApp 為了圖方便(省得你每次交易都重新授權一次),預設申請的是無限額度授權。
這意味著:
❝
那個合約理論上能把你這種代幣全部轉走,而且一直有效,直到你主動撤銷。
如果那個合約有 bug、被駭客攻破、或者它本身就是個釣魚合約——你的代幣會被悄悄掏空,而你”什麼都沒再操作”。鏈上不少盜幣事故就是這麼來的。
怎麼防:
授權時盡量只給夠用的額度,別一上來就無限授權
定期檢查並撤銷不再用的授權(revoke.cash 這類工具可以檢查和撤銷)
❝
順帶:approve 本身也是一筆交易(要花 gas)。所以一次 swap 常常是兩筆交易──先 approve,再真正 swap。這也是另一個常被用戶問”為什麼要扣兩次手續費”的點。
五、幾個真實的”代幣事故”
事故原因有 USDT 卻轉不動沒有原生幣付 gas(見第三節)選錯鏈/網絡同一個 0x 地址在 ETH、BSC、Polygon 上都存在,但這是三條不同的鏈,USDT 在每條鏈上是不同的合約。在 BSC 上轉的 USDT,對方卻只在 ETH 主網收 → 容易出事錢包裡冒出”假 USDT”任何人都能部署一個叫 “USDT” 的合約,再空投給你。名字、圖示都能仿。別看名字,要看合約地址
六、給錢包開發者的注意點
decimals 一定從合約讀,別寫死──USDT 用的是 6 位小數,不是大多數代幣的 18 位。寫死成 18,餘額會顯示成實際的 1/10¹²——一個幾乎看不見的小數。
維護一個代幣白名單,同時讓用戶能用合約地址添加自訂代幣。
用戶有代幣、但沒原生幣時,轉帳前要先明確提示”gas 不足”,而不是讓交易默默失敗。
approve 的 UI 要做好:顯示授權給誰、額度多大;遇到無限額度授權要醒目警告,最好提供”只授權本次所需額度”的選項。
假幣識別:依合約地址認代幣,不按名字認。 UI 上對不在白名單裡的代幣給予提示。
❝
Token 是”鏈上一個合約”這個思路,在別的鏈上是一樣的——Tron 的 TRC-20、BNB Chain 的 BEP-20、Solana 的 SPL,換了名字,本質都一樣。
七、想深挖時的入口
| 主題 | 資源 |
|---|---|
| ERC-20 標準 | EIP-20 |
| 查看 / 撤銷代幣授權 | revoke.cash |
| 帳戶抽象 | EIP-4337 |
八、小結
對開發者:decimals 別寫死、依合約地址認幣、approve UI 要警告
鏈上資產分兩類:原生幣(鏈自帶,能付 gas)和 Token(本質是個智能合約)
USDT 不是”幣”,是 USDT 合約帳本裡的一行數字
錢包查代幣餘額要主動調合約,所以加代幣要填合約地址
轉代幣要花原生幣當 gas——”有 USDT 沒 ETH 轉不動”就是這麼來的
approve 的無限授權是最大的坑,要少授權、勤撤銷