mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-11-02 06:16:10 +00:00
175 lines
6.5 KiB
Python
175 lines
6.5 KiB
Python
"""Read a Token-2022 mint's extensions, and its real scaled-UI multiplier.
|
|
|
|
Usage:
|
|
uv run cookbook/solana/solana_read_token2022_mint.py <MINT>
|
|
|
|
Token-2022 mints carry extensions the original SPL Token program has no concept
|
|
of. Four of them change what a balance means or whether a transfer works at all,
|
|
and every tokenized equity pump.fun accepts as a quote asset carries all four:
|
|
|
|
- **scaled UI amount** — the raw balance is multiplied by a number the issuer
|
|
sets, which is how stock splits and dividends are applied without touching
|
|
anyone's account. Raw is what instructions take; raw x multiplier is what a
|
|
human should see.
|
|
- **pausable** — the issuer can freeze all transfers. While a stock mint is
|
|
paused, every trade on every coin priced in it fails.
|
|
- **permanent delegate** — an authority that can move tokens out of any account
|
|
holding this mint, forever.
|
|
- **transfer hook** — a program invoked on every transfer. pump.fun only accepts
|
|
a quote mint whose hook has no program set, so these read as `None`.
|
|
|
|
The RPC returns two multipliers:
|
|
|
|
"multiplier": "1.0026642075893797"
|
|
"newMultiplier": "1.0032690125398187"
|
|
"newMultiplierEffectiveTimestamp": 1786149000
|
|
|
|
The field called `multiplier` is the **old** one. Once the effective timestamp
|
|
has passed, the live multiplier is `newMultiplier` and nothing renames the
|
|
fields, so reading the obvious one gives a number that is quietly wrong. This
|
|
script compares against the clock and prints which is in force.
|
|
"""
|
|
|
|
import argparse
|
|
import asyncio
|
|
import os
|
|
import time
|
|
|
|
from dotenv import load_dotenv
|
|
from solana.rpc.async_api import AsyncClient
|
|
from solders.pubkey import Pubkey
|
|
|
|
load_dotenv()
|
|
|
|
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
|
|
|
TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
|
TOKEN_2022_PROGRAM = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
|
|
|
|
|
|
def effective_multiplier(scaled: dict, now: int) -> tuple[float, str]:
|
|
"""Pick the scaled-UI multiplier actually in force right now.
|
|
|
|
Args:
|
|
scaled: The `scaledUiAmountConfig` extension state
|
|
now: Current unix time in seconds
|
|
|
|
Returns:
|
|
The live multiplier, and a note saying which field it came from
|
|
"""
|
|
current = float(scaled.get("multiplier", 1) or 1)
|
|
upcoming = float(scaled.get("newMultiplier", current) or current)
|
|
effective_at = int(scaled.get("newMultiplierEffectiveTimestamp", 0) or 0)
|
|
|
|
if effective_at and now >= effective_at:
|
|
return upcoming, "newMultiplier (its effective time has passed)"
|
|
if effective_at and upcoming != current:
|
|
return current, f"multiplier (newMultiplier takes over at {effective_at})"
|
|
return current, "multiplier"
|
|
|
|
|
|
async def read_mint(mint: Pubkey) -> None:
|
|
"""Print one mint's basics and every extension it carries.
|
|
|
|
Args:
|
|
mint: The mint to read
|
|
"""
|
|
async with AsyncClient(RPC_ENDPOINT) as client:
|
|
response = await client.get_account_info_json_parsed(mint)
|
|
if response.value is None:
|
|
print(f"{mint} does not exist on this cluster.")
|
|
return
|
|
|
|
owner = response.value.owner
|
|
if owner == TOKEN_PROGRAM:
|
|
print(f"{mint} is an SPL Token mint — it carries no extensions.")
|
|
return
|
|
if owner != TOKEN_2022_PROGRAM:
|
|
print(f"{mint} is owned by {owner}, which is not a token program.")
|
|
return
|
|
|
|
info = response.value.data.parsed["info"]
|
|
extensions = {
|
|
e.get("extension"): e.get("state", {})
|
|
for e in (info.get("extensions") or [])
|
|
if isinstance(e, dict)
|
|
}
|
|
metadata = extensions.get("tokenMetadata", {}) or {}
|
|
decimals = info["decimals"]
|
|
|
|
print(f"Mint: {mint}")
|
|
print(f"Name: {metadata.get('name', '(none)')}")
|
|
print(f"Symbol: {metadata.get('symbol', '(none)')}")
|
|
print(f"Decimals: {decimals}")
|
|
print(f"Supply: {info['supply']} raw")
|
|
|
|
print_extensions(extensions, decimals, int(info["supply"]))
|
|
|
|
|
|
def print_extensions(extensions: dict, decimals: int, raw_supply: int) -> None:
|
|
"""Print the extensions that change what a balance means or if it can move.
|
|
|
|
Args:
|
|
extensions: Extension name -> state, from the jsonParsed mint
|
|
decimals: The mint's decimal count
|
|
raw_supply: The mint's supply in raw units
|
|
"""
|
|
if "scaledUiAmountConfig" in extensions:
|
|
scaled = extensions["scaledUiAmountConfig"]
|
|
multiplier, source = effective_multiplier(scaled, int(time.time()))
|
|
supply = raw_supply / 10**decimals
|
|
print("\nScaled UI amount")
|
|
print(f" in force now: {multiplier} <- from {source}")
|
|
print(f" raw field says: {scaled.get('multiplier')}")
|
|
print(f" supply shown to a human: {supply * multiplier:,.6f}")
|
|
print(" Instructions take raw amounts; only display uses the multiplier.")
|
|
|
|
if "pausableConfig" in extensions:
|
|
paused = extensions["pausableConfig"].get("paused")
|
|
note = " ALL TRANSFERS FROZEN" if paused else ""
|
|
print(f"\nPausable: paused={paused}{note}")
|
|
|
|
if "permanentDelegate" in extensions:
|
|
delegate = extensions["permanentDelegate"].get("delegate")
|
|
print(f"\nPermanent delegate: {delegate}")
|
|
print(" This authority can move these tokens out of any account.")
|
|
|
|
if "transferHook" in extensions:
|
|
program = extensions["transferHook"].get("programId")
|
|
print(f"\nTransfer hook program: {program or 'none'}")
|
|
if program:
|
|
print(" Every transfer calls it, and may need extra accounts.")
|
|
|
|
if "defaultAccountState" in extensions:
|
|
state = extensions["defaultAccountState"].get("accountState")
|
|
print(f"\nDefault account state: {state}")
|
|
if state == "frozen":
|
|
print(" A new token account starts frozen and needs thawing.")
|
|
|
|
others = sorted(
|
|
set(extensions)
|
|
- {
|
|
"scaledUiAmountConfig",
|
|
"pausableConfig",
|
|
"permanentDelegate",
|
|
"transferHook",
|
|
"defaultAccountState",
|
|
"tokenMetadata",
|
|
}
|
|
)
|
|
if others:
|
|
print(f"\nOther extensions: {', '.join(others)}")
|
|
|
|
|
|
def main() -> None:
|
|
"""Parse the command line and read the mint."""
|
|
parser = argparse.ArgumentParser(description="Read a Token-2022 mint")
|
|
parser.add_argument("mint", help="The mint address")
|
|
args = parser.parse_args()
|
|
|
|
asyncio.run(read_mint(Pubkey.from_string(args.mint)))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|