π = π — Pi fungibility proof

A native Pi payment carries no observable per-unit mining origin on the Pi Mainnet ledger.

View the Project on GitHub aybvip/pi-fungibility

FAQ — “Here are two exchange addresses — can you now trace a coin’s origin?”

Short answer

No. The addresses are real, and the payments into them are plain native payments. But the to_muxed_id tag names the recipient inside a pooled account (who), not the coin’s origin (where).

The ledger shows how much went to which G account and with which muxed id — it never shows where that π came from.

What’s actually being observed (measurement)

We measured two addresses as shown by the exchanges (attribution is external):

Property Bitget (GDFNWH6Z…75) OKX (GALYJFJ5…EQU)
Asset native only native only
Balance 165,432,201.2469451 π 37,539,111.4435694 π
Subentries/flags/signers 0 / auth off / 1 signer (0/0/0 thresholds) same
Inbound deposits carry to_muxed_id (171/171 in the last 200 payments) carry to_muxed_id (197/197 in the last 200 payments)
M address rebuilt byte-for-byte from (G + id) and matches to_muxed same match

Rebuilding the muxed address (CAP-27)

For each deposit, M... = muxedFromG(G..., to_muxed_id). The API returns to_muxed (the M address) — the script rebuilds it from first principles and requires rebuilt === api.to_muxed. This is a pure check that the tag is exactly (G + id), not some other metadata.

Stellar: “Muxed accounts do not exist on the ledger, but their shared underlying G… account does.” (CAP-27). Treating M as “separate” is a misread of the addressing model.

The key distinction

What to_muxed_id tells you What it does NOT tell you
Who receives inside the pooled account (which user ID at the venue). Where the coin came from (mined / withdrawn / sent from another app).
The routing/accounting identifier for crediting the correct internal user. Any per-unit origin tag. No such field exists on the native asset.

Concrete, pinned evidence: Bitget → OKX

We verified 5 distinct historical transactions where Bitget sent native π directly to OKX, each successful, memo_type: "none", with a single native payment op to OKX (one case is multi-op but the OKX payment matches exactly):

Transaction hash Amount to_muxed_id (OKX side) Notes
6a77e7db92750aa8fcd03ea00e2ecbffdd9bb734c5ca7c66aed91984f8e75137 129.2106200 2486605 single-op
dc967226fe81530960171dc8819c2c3d7485107880ec52bfbe97ccf13fbdbca6 177.4462800 2494403 single-op
68b384aa5845309ceaccb90fceaefa7f2bdb67cf7e8e99438e55eaf380afbd19 340.1076151 1504575 single-op
ccd1af63d264fdb2e732caaac2494af2d242ec21679675cce497a74eb1a2bb9c 1116.7920500 2494316 multi-op tx contains another payment
bab994789ee5deb694b8db9513914f09b83c4b922a0d5640a5fcf8c9f90a004d 102.0257300 2489489 single-op

These hashes are pinned in examples/exchange-wallets.mjs and re-checked by the script every time it runs. The tx hash itself is immutable; the ledger state for that transaction is fixed.

Why this does NOT break fungibility

Honest caveat

Attribution is external. On-chain, there is no label reading “Bitget” or “OKX”. We label these two addresses because the venues themselves publish them in their UI/docs. The script explicitly prints this boundary and never asserts on-chain ownership. This matches the repo’s principle: address labeling = know who, not where the coin came from.

Reproduce (read-only)

node examples/exchange-wallets.mjs
node examples/self-test.mjs  # includes offline codec tests (checksums + muxed rebuild)

Outputs: examples/exchange-wallets-output.txt (committed snapshot), examples/self-test-output.txt.

Context