Meme Atlas AZ · pumpswap-pool-fields

PumpSwap liquidity pool 怎麼讀:Pool 帳戶欄位、effective quote reserves 與金庫餘額為什麼不等於流動性

PumpSwap 是 pump.fun 的恆定乘積 AMM,代幣從 bonding curve 遷移後就在這裡交易。本文照官方程式文件讀 Pool 帳戶的欄位、報價用的 effective quote reserves、可以是負數的 virtual_quote_reserves,以及同一枚幣為什麼可能有好幾個池。

發布
更新
編輯責任
Meme Atlas AZ

一枚 pump.fun 代幣的 bonding curve 賣完之後,交易就搬到 PumpSwap。很多人這時會打開池子的金庫帳戶,看裡面有多少 SOL,當成這個池的流動性。照官方文件現在的寫法,這樣讀會讀錯。

本文照 pump.fun 在 GitHub 公開的程式文件(倉庫 pump-fun/pump-public-docs,讀取時最後一次提交在 2026-10-13)讀 PumpSwap 的池帳戶。曲線那一段怎麼讀,見本站pump.fun 是什麼。引號內的英文是文件原文,中文說明為本文轉述;標明「本文整理」的部分是我們把文件的條文拼在一起的讀法。

PumpSwap 是什麼:一個恆定乘積 AMM

文件開頭一句定位:"PumpSwap program is a constant-product AMM",程式地址是 `pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA`,主網與 Devnet 相同。

整個程式只有一個 `GlobalConfig` 帳戶,地址由 `["global_config"]` 種子推導。裡面有一個 `admin` 欄位,文件說它是 "the pubkey authorized to update the `GlobalConfig` account";管理指令裡的 `disable` 允許這個地址 "globally disable any Pump Swap operation"。研究紀錄要寫清楚的是:這個池子有一組可以被修改的全域設定,而修改權在一個地址手上。

買賣的手續費也寫在 `GlobalConfig`,是兩個以 basis points 計的欄位,`admin` 可以用 `update_fee_config` 調整。本文不抄費率數字,以讀取當下的帳戶內容為準。

Pool 帳戶:地址怎麼來、每一欄是什麼

每個池是一個 `Pool` 帳戶,地址由 `["pool", index, creator, baseMint, quoteMint]` 這組種子推導。文件列的主要欄位:

  • `base_mint`、`quote_mint`:池裡兩種資產的 mint 地址。文件貼的範例池,`quote_mint` 是 `So11111111111111111111111111111111111111112`。
  • `lp_mint`:LP 代幣的 mint。
  • `pool_base_token_account`、`pool_quote_token_account`:池子持有兩種資產的關聯代幣帳戶,也就是常說的金庫。
  • `lp_supply`:LP 的供應,定義見下文。
  • `coin_creator`:累積幣創作者手續費的地址;在創作者手續費出現之前就存在的池,這一欄是 `Pubkey::default()`。
  • `virtual_quote_reserves`:型別是 `i128`,帶正負號。這一欄是本文的重點。
  • 幾個旗標:`is_mayhem_mode`、`is_cashback_coin`、`is_holder_reward`。

後來加在帳戶尾端的欄位,文件提醒舊池讀不到:"Pools written before an appended field existed are shorter than the current layout; read the missing trailing fields as `0` / `false`." 同一個程式底下,新舊池的帳戶長度可以不一樣。

同一枚幣可能不只一個池

種子裡有 `index` 和 `creator`。文件對 `create_pool` 的說明是:"The `index` allows the same `creator` to create multiple pools for the same `(baseMint, quoteMint)` pair." 換句話說,同一組幣對可以由不同的建立者、或同一個建立者用不同的 index,各開一個池。

從 bonding curve 遷移出來的那一個有固定的 index。文件寫,經由 Pump 程式 `migrate` 指令建立的池使用的 index 是 0。所以在聚合器上看到某枚幣有好幾個 PumpSwap 池時,先讀每個池帳戶的 `index` 和 `creator`,再決定哪一個才是遷移產生的池。本文的建議是:研究紀錄裡池地址一律和這兩欄一起記,不要只寫「PumpSwap 池」。

報價用的是 effective quote reserves

文件專門寫了一節報價規則:"Buys and sells are priced on the pool's effective quote reserves, which are the raw quote-vault token balance plus `Pool::virtual_quote_reserves`":

```text effective_quote_reserves = pool_quote_token_account.amount + Pool::virtual_quote_reserves ```

接著是一句直接的要求:"Use `effective_quote_reserves` (not the raw `pool_quote_token_account.amount`) wherever you quote, price, or index a pool, for both `buy` and `sell`." base 那一側沒有這個調整,文件寫 base 儲備仍然是 `pool_base_token_account.amount`。

文件裡兩種說法:是 0,還是可以為負

讀到這裡會遇到一個不一致。PumpSwap 主文件在 `Pool` 欄位說明裡寫,`virtual_quote_reserves` "is `0` on all pools today, so effective quote reserves currently equal the raw vault balance"。這份檔案在倉庫裡最後一次修改是 2026-09-12。

另一份專門文件〈Negative Virtual Quote Reserves〉的第一句卻是:"Starting September 30, `Pool::virtual_quote_reserves` on PumpSwap pools can be negative." 這份檔案 2026-09-29 加入倉庫,2026-10-07 又改過一次。

兩份都是官方文件,時間順序也清楚:「目前都是 0」那句寫在變更之前,沒有跟著改。本文以較新的那一份為準來讀,同時把主文件那句照錄在這裡,讓讀者知道為什麼舊的整合程式可能把這一欄寫死成 0。

為什麼會是負數:還沒清掃的手續費(本文整理)

〈Virtual Quote Reserves and Fees Kept in the Pool〉解釋了原因。PumpSwap 的 v2 交易指令(`buy_v2`、`buy_exact_quote_in_v2`、`sell_v2`)和 `multi_hop_swap`,會把 protocol 手續費與 creator 手續費先留在池子的 quote 金庫裡,不在每一筆交易時付出去。文件的態度很明確:"That money is not liquidity. It must not change the price."

所以每留下一筆手續費,程式就從 `virtual_quote_reserves` 減掉同樣的數;等清掃(sweep)把手續費付出去,再加回來。文件給的關係式是:

```text pool_quote_token_account.amount = real_quote_reserves + Pool::protocol_fees + Pool::creator_fees Pool::virtual_quote_reserves = boost_reserves - Pool::protocol_fees - Pool::creator_fees ```

兩式相加,等待中的手續費正負抵銷,effective quote reserves 就等於真正的流動性加上 boost。用一個符號示意(本文整理,F 代表留在金庫、還沒清掃的手續費,池子沒有 boost):

  • 交易前:金庫 100 SOL,`virtual_quote_reserves` 為 0,effective 為 100。
  • 一筆 v2 買入付進 10 SOL:金庫變成 110,`virtual_quote_reserves` 變成 −F,effective 是 110 − F。
  • 清掃把 F 付出去:金庫變成 110 − F,`virtual_quote_reserves` 回到 0,effective 仍是 110 − F。

清掃前後價格不變,因為 effective quote reserves 從頭到尾都沒把 F 算進去。這也是文件那句提醒的意思:"Do not read the raw vault balance as liquidity. It now includes fees waiting to be swept."

文件同時給了兩個保證:`pool_quote_token_account.amount + virtual_quote_reserves` 不會溢位,也不會是負數。對自己寫索引的人,文件的要求是用有正負號的欄位存這個值,因為 "a `u64` column will reject or corrupt negative values"。

還有一個容易誤判的地方:池子是不是 boosted。文件寫判斷條件是 `virtual_quote_reserves + protocol_fees + creator_fees` 不為 0,"not when the stored field alone is not `0`"。只看 `virtual_quote_reserves` 不是 0,就說這個池有 boost,會把等待清掃的手續費誤認成 boost。

不想輪詢帳戶:從交易事件重建池子狀態

自己做索引、不想每一筆都去讀池帳戶的人,文件給了另一條路:"Indexers: the `BuyEvent` and `SellEvent` logs include `virtual_quote_reserves` (appended field), so effective quote reserves can be reconstructed directly from the event stream." 每一筆買賣的事件裡都帶著那一刻的 `virtual_quote_reserves`,加上金庫餘額就能還原報價用的儲備。

手續費那份文件又補了幾個欄位:交易事件會回報有正負號的 `virtual_quote_reserves`、`protocol_fee`、`coin_creator_fee` 與 `creator_fee_unclaimed`;每一次清掃付款則另有 `SweepPoolFeeEvent`。是否 boosted 也不必自己算,`BuyEvent`/`SellEvent` 以 `can_boost` 帶出。本文的建議是兩種讀法都留:事件流用來追時間序列,帳戶讀數用來在某個時間點對帳,兩者對不上時,先查中間有沒有漏掉清掃事件。

存入與提領:LP 換回兩種資產

池子的流動性由存入(deposit)與提領(withdraw)兩個指令改變。文件的寫法是:`deposit` "allows a `user` to deposit at most `maxBaseIn` and `maxQuoteIn` tokens into the `pool` in order to get exactly `lpTokenOut` tokens in return";`withdraw` 則是燒掉剛好 `lpTokenIn` 的 LP,換回至少 `minBaseOut` 與 `minQuoteOut`。兩個指令都是指定 LP 的數量,再替兩種資產設上下限,和買賣指令用上下限防滑點是同一個思路。

文件另外寫明 "No fees are charged on `deposit` / `withdraw` instruction",手續費只出現在買賣。所以讀到金庫餘額突然變多或變少、卻找不到對應的買賣事件時,先查同一時段有沒有存入或提領,再判斷是不是清掃。

lp_supply 不扣掉銷毀

`lp_supply` 的定義是 "the total supply of the `lp_mint` without burns and lock-ups"。文件舉例說,有人存入流動性、再自己把 LP 代幣銷毀,`Pool::lp_supply` 仍然反映原本的供應;這樣池子才分得出 LP 是被使用者直接銷毀,還是經由 `withdraw` 指令銷毀。

要核對 LP 有沒有被銷毀,讀的是 `lp_mint` 在鏈上的供應,不是 `lp_supply` 這一欄。這一點在曲線那篇也提過,放在這裡是因為讀池子時最常把兩者混在一起。

最後一筆買單:池子還沒開就先交易

〈Synthetic Migration〉寫了一個新規則。用 v3 買入指令(`buy_v3`、`buy_exact_quote_in_v3`)或 `multi_hop_swap` 的曲線那一跳,"the buy that empties the curve has no max size":這筆買單先把曲線剩下的全部買完,曲線隨即完成,超出的部分再以「遷移後那個池會收的價格」,從原本要放進 PumpSwap 池的代幣裡買。文件稱之為 synthetic migration,"the buy trades against the pool before the pool exists"。

之後 `migrate_v2` 用這筆買單留下的儲備建池,文件寫 "The pool opens at the price where the buy stopped."。對讀池子的人,這代表兩件事:

  • 池子開張時的第一組儲備,可能已經被這筆買單動過。文件說這時會依序發出 `TradeEvent`、`CompleteEvent` 與 `PostCompleteBuyEvent`,最後一個事件記了池子開張時的 `pool_base_reserves_after` 與 `pool_quote_reserves_after`。
  • 買家這筆交易拿到的總量,文件寫是 `TradeEvent` 加上 `PostCompleteBuyEvent` 的數量,只看第一個事件會少算。

不是每一枚幣都適用。文件寫 mayhem 模式的幣沒有 synthetic migration,舊的 `buy`、`buy_v2`、`buy_exact_quote_in_v2` 也維持原本的行為。

研究紀錄要留哪幾格

  • 池地址、`index`、`creator`、`base_mint`、`quote_mint`。有好幾個池時,標明哪一個的 index 是 0。
  • 讀取時間,以及當下的 `pool_base_token_account.amount`、`pool_quote_token_account.amount`、`virtual_quote_reserves`(帶正負號)。
  • 用 effective quote reserves 算出的 quote 一側,另記一格,不和金庫餘額混寫。金庫餘額裡含有等待清掃的手續費。
  • 若要判斷 boost,記下 `protocol_fees`、`creator_fees`,三欄一起看。
  • LP:`lp_mint` 在鏈上的供應,與 `lp_supply` 分開記。
  • 文件版本:倉庫的提交日期。這一頁引用的兩份文件之間有一句沒同步,日後再讀時先看提交紀錄。

池子深度怎麼讀,見本站DEX 流動性深度;欄位讀不到值時該怎麼記,見代幣資料 API 回傳 null 或查不到。

這一頁講的是池帳戶與文件條文的讀法;它不替 PumpSwap 上的任何一個池或任何一枚代幣下判斷,也不是交易建議。

內容來源