A native Pi payment carries no observable per-unit mining origin on the Pi Mainnet ledger.
A source-verified technical analysis of fungibility in the Pi Network blockchain: why an app owner — even with the Pi Wallet SDK, the Platform API, and Soroban smart contracts — cannot distinguish between mined π, π withdrawn from an exchange (OKX / Bitget), and π sent from inside a Pi Browser app.
🏷️ Independent & unofficial — not affiliated with Pi Network. This is an independent technical analysis, not an official product. See
DISCLAIMER.md.🔐 Safe by design (read-only). No wallet connection, no passphrase, no keys, no signing, no dependencies. Audit it — see
SECURITY.md. Details:docs/FAQ-UNOFFICIAL-REPO-SCAM.md.
🗂️ Full index of every file (by file and by question): docs/INDEX.md
🌐 Website: https://aybvip.github.io/pi-fungibility/ · 📄 Machine-readable index for AI tools:
llms.txt · 📌 Cite this work: CITATION.cff
GitHub Pages is the canonical host. Every rendered page emits a
rel="canonical"tag pointing here, so optional mirrors (Netlify / Cloudflare Pages) add reach and redundancy without splitting search ranking — seedocs/DEPLOY-MIRRORS.md.
On-chain, every π is identical. The native asset has no issuer and no contract, and the ledger records balances — not “coins” tagged with an origin. Therefore:
mined π = π from OKX/Bitget = π from inside a Pi Browser app
There is no field called “origin”, “mining”, or “exchange” — not on-chain, not in the SDK, not in the Platform API, and not in the Soroban smart-contract layer. All an app sees is: amount + sender address + recipient address + memo + txid — and the memo is optional (on recent Pi Mainnet traffic the large majority of transactions carry none at all).
The official MiCA whitepaper says it plainly: Pi is the “native, fungible digital asset” and belongs to a “Functionally Fungible Group”.
On-chain observation (read-only): two real addresses labeled by the venues (external attribution).
GDFNWH6Z…75 — 165.43M π native, all inbound deposits carry to_muxed_id (muxed recipient id). The M... address rebuilds byte-for-byte from (G + id) and matches the API.GALYJFJ5…EQU — 37.54M π native, same muxed pattern.Pinned, reproducible: 5 historical transactions show Bitget sending native π directly to OKX (memo_type: "none", successful). Each payment is plain asset_type: "native" with to_muxed_id naming the recipient inside the pooled account — never a coin-origin field.
to_muxed_ididentifies who (the exchange user), not where the coin came from. The balance is one scalar, merged on arrival. Attribution is external (the ledger does not say “Bitget”/”OKX”).
Reproduce:
node examples/exchange-wallets.mjs
Full explanation + pinned hashes: docs/FAQ-EXCHANGE-ADDRESSES-OBSERVED.md.
You can trace transactions. You cannot trace coins.
| Level | Unit of information | Traceable? | What you get |
|---|---|---|---|
| 1 — Transaction / operation | txid · ledger |
✅ fully public | who sent, to whom, how much, when, the memo |
| 2 — Account | G… / M… |
✅ as a graph | you can label addresses and measure flows between them |
| 3 — Coin / unit (micro-π) | — | ❌ does not exist | ❌ “this π was mined” / “this π came from OKX” |
A worked example — following the money is not following the coins:
| Event | What the ledger shows |
|---|---|
| You deposit 100 π to an exchange | payment 100 native → <venue G…> — ✅ visible |
| You withdraw 100 π weeks later | payment 100 native → <your G…> — ✅ visible |
| “the 100 π I withdrew are the 100 π I deposited” | ❌ not provable — the venue pools its funds, and the unit has no identity |
This is not “we don’t know”. It is “there is no such property”. Receiving π merges the amount into a single scalar balance, so the map history → state is non-injective — the per-unit information is destroyed, not concealed (
examples/model-check.mjs, 2 000 seeds;examples/exhaustive-check.mjs, all 1 452 histories).
The honest boundary: the absence of per-unit origin does not hide that you ever used an exchange at the account level. If a venue’s address is publicly labeled, address-level analysis can link accounts — never coins. Anything said about “where your money came from” is an inference about an account, not a fact about a coin.
Full treatment: docs/FAQ-TRANSACTION-VS-COIN.md.
Last verified: 2026-10-18 — node examples/run-all.mjs → all checks passed (exit code 0), on Node 24
locally and Node 20/22 in CI. The self-test file alone runs 29 checks: 29 passed, 0 failed (26 offline, 3 online).
GETs against api.mainnet.minepi.com) run locally and in CI on every
push. They are reachability-dependent, so a red build can simply mean the API was unreachable.evaluate() rules, transport-failure handling, the ledger model) are
deterministic and cannot be flaky — they are what actually holds the claims up.Full breakdown — what ran, where, what it proves, and what it cannot: docs/VERIFICATION.md.
Exact revision of every official source (including a SHA-256 of the MiCA PDF): docs/SOURCES.md §0.
Everything in this repo is grounded in Pi Network’s official documentation:
| Source | Link | Used for |
|---|---|---|
| Pi Docs (official, 2026) | https://docs.minepi.com/ | Wallet SDK / Payments / Tokens |
| Pi Wallet & SDK | https://docs.minepi.com/use-native-features/Wallet | window.Pi.Wallet + pre-signed transactions |
| Accept a payment | https://docs.minepi.com/accept-payment/Payments | Pi.createPayment + allowed fields |
| Advanced Payments (A2U) | https://docs.minepi.com/accept-payment/PaymentsAdvanced | “the blockchain is the only source of truth” |
| Tokens (Stellar SDK) | https://docs.minepi.com/use-native-features/Tokens | Pi is Stellar-based; native has no issuer |
| Platform API | https://github.com/pi-apps/pi-platform-docs/blob/master/platform_API.md | Full PaymentDTO definition |
| Community Dev Guide | https://pi-apps.github.io/community-developer-guide/ | Overview |
| Pi SDK JS (GitHub) | https://github.com/pi-apps/pi-sdk-js | createPayment interface |
| MiCA Whitepaper (PDF) | https://minepi.com/wp-content/uploads/2025/11/MiCA-Whitepaper-Pi-1.pdf | “native, fungible”, “Functionally Fungible Group” |
| Stellar — Accounts | https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts | Account/Balance model |
| Stellar — Muxed accounts | https://developers.stellar.org/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos | CAP-27 M... addresses |
| PiRC — Pi Requests for Comment | https://github.com/PiNetwork/PiRC | Ecosystem-token design (PiRC1) + subscription contract (PiRC2) |
| Smart Contracts (reference impl) | https://github.com/PiNetwork/SmartContracts | Soroban token interface = scalar amounts |
| Pi blog — Subscription contract | https://minepi.com/blog/subscriptions-smart-contract | First smart contract, on Testnet (2026-04-17) |
Full quotes and citations: docs/SOURCES.md.
Horizon API, same @stellar/stellar-sdk, same networkPassphrase).asset_issuer.asset_code + asset_issuer and needs a trustline.Direct test:
// GET https://api.mainnet.minepi.com/accounts/<G_ADDRESS>
{ "balance": "2.0000000", "asset_type": "native" } // no issuer, no origin
createPayment has no “origin”// Official Pi docs
Pi.createPayment(
{ amount: 1, memo: 'Unlock Premium', metadata: { feature: 'premium' } },
{ onReadyForServerApproval, onReadyForServerCompletion, onCancel, onError }
);
Allowed fields: amount / memo / metadata only. And the Wallet docs are explicit:
“Transactions must be pre-signed; the SDK does not build or sign transactions for you.” — i.e. a plain Stellar
payment of the native asset, with no tag describing the coin’s origin.
PaymentDTO has no “origin” fieldThe full official response of GET /v2/payments/{payment_id}:
{
identifier, user_uid, amount, memo, metadata,
from_address, to_address, direction, created_at, network,
status: { developer_approved, transaction_verified, developer_completed, cancelled, user_cancelled },
transaction: { txid, verified, _link } | null
}
👉 Not a single field describes where the π came from. Even from_address tells you who sent it — not
where the coin came from.
Pi’s new Soroban smart-contract capability (PiRC1/PiRC2, on Testnet) gives a contract only the Stellar token interface:
token.approve(subscriber, spender, &amount, &expiration_ledger);
token.transfer(&from, &to, &amount);
token.transfer_from(&spender, &from, &to, &amount);
let balance = token.balance(&account); // one i128 number
let allowance = token.allowance(&from, &spender); // one i128 number
Every value is a scalar i128 — no array of units, no per-coin txid, no origin. A contract may define
rules for its own token (a different asset), but it cannot read or write native π’s per-unit
origin. → docs/FAQ-SMART-CONTRACTS-AND-TOKENS.md
Every objection reduces to one of four things — counterparty identity, asset type, balance state, or off-chain data — never coin provenance.
| Objection | Answer | Details |
|---|---|---|
| “You can follow the transactions — so you can tell where the π came from” | You can trace the transaction, never the coin. Transactions and accounts are fully traceable; the individual unit is not stored at all. Receiving π merges the amount into one scalar balance, so per-unit origin is not hidden — it does not exist. | docs/FAQ-TRANSACTION-VS-COIN.md |
| “Exchanges use M addresses, not G” | M = a muxed account (G + ID, CAP-27); identifies the counterparty, not the coin. The M is a G account plus a 64-bit id — the G account is what exists on the ledger — and the network processes the payment exactly as if muxing weren’t used. |
docs/FAQ-MUXED-M-VS-G.md |
| “Every micro-π has its own ID” | Pi uses an Account/Balance model (one number), not UTXO. 1 π = 10,000,000 micro-π is just decimal precision, not an ID. |
docs/FAQ-MICRO-PI-ID.md |
| “Doesn’t the SDK / Platform API expose where the π came from?” | No. Pi’s own docs enumerate the entire SDK/API surface — amount, memo, metadata, from_address, to_address, status, txid… Not one field carries per-unit origin. |
docs/FAQ-SDK-PLATFORM-API-FIELDS.md |
| “Pi now has Soroban smart contracts and ecosystem tokens — can’t you tag a coin’s origin now?” | No. The contract-facing token interface exposes only scalars (balance, allowance, transfer, transfer_from, approve). A contract can hold state and define rules for its own token — a different asset — but it cannot read/write native π’s per-unit origin, because none exists. |
docs/FAQ-SMART-CONTRACTS-AND-TOKENS.md |
| “The MiCA whitepaper says Pi comes only from mining” | The same paper says Pi is native, fungible, in a Functionally Fungible Group, immutable even by the issuer, with no transfer restrictions, and that exchanges are the secondary market. | docs/MICA-WHITEPAPER.md |
| “Pi once said ‘don’t buy Pi’” | That was Enclosed Mainnet policy (until Feb 20, 2025). Open Network removed the firewall and enabled CEX integration — Pi’s own blog says so. | docs/FAQ-DONT-BUY-PI.md |
| “Same coin, but not the same price” | Fungibility ≠ price. The same asset trades at different prices across venues (Bitcoin’s “Kimchi premium”, even the US dollar). Only an IOU vs native difference is real. | docs/FAQ-FUNGIBILITY-VS-PRICE.md |
| “I bought π on an exchange, put it in the same wallet as my mined π, then sent some to a friend — can they tell?” | No — and it is deterministic, not a loophole. The ledger has no per-unit identity, so per-coin origin is not an observable. Includes the honest account-level-vs-unit-level distinction. | docs/FAQ-RECIPIENT-CANNOT-TELL.md |
| “Can the Core Team set a different price for mined vs exchange Pi, or stop you selling?” | No — not on-chain. No origin to target, no price field, no issuer controls. Their power is off-chain/app-level only. | docs/FAQ-CAN-CORE-TEAM-SET-PRICE.md |
| “Fungibility is one thing — but is an exchange’s balance really backed?” | Three separate claims: A fungibility (proven), B a specific deposit is on-chain (verifiable), C reserve backing (not claimed). | docs/FAQ-EXCHANGE-DEPOSIT-VERIFY.md |
| “Has this proof been independently audited?” | No — it is self-verified and reproducible, which is not the same as a third-party audit. Status, scope, and how to do one. | docs/AUDIT-REQUEST.md |
| “Then how do I break it?” | Don’t review it — falsify it. Four checkable criteria: a per-unit field, a protocol allocation rule, a non-scalar balance, or one counterexample transaction. Any one kills the claim. Two are attempted by examples/falsify-try.mjs; none has been found. |
docs/FALSIFICATION.md |
| “Has anyone else already proven this? Are you first?” | We only claim that we could not find prior published work — not proven priority. What came before (Stellar’s Account model, Pi’s own MiCA “fungible” filing), what was searched, what appears new, and what is explicitly not claimed. | docs/PRIOR-ART.md |
| “It’s just an unofficial GitHub repo — must be a scam/phishing.” | Audit, don’t trust. This repo is read-only: no wallet, no passphrase, no keys, no signing, no deps. And “Pi not listed / network closed” is outdated (Open Network 2025-02-20). | docs/FAQ-UNOFFICIAL-REPO-SCAM.md · SECURITY.md |
| “If tainted π touches my wallet, doesn’t the taint spread through everything I own?” | No — there is no taint field for it to spread through. Contagion needs a marked object and a propagation rule; the ledger state is a scalar balance and supplies neither. And it is a hard dichotomy: if taint is non-transitive it cannot be allocated to any unit; if transitive it reaches every π and therefore distinguishes nothing. | docs/FAQ-TAINT-CONTAGION.md |
| All other objections (labeling, chain analysis, lockups, KYC, core-team data, governance…) | Catalogued with a refutation each. | docs/FAQ-ALL-OBJECTIONS.md |
| File | Description |
|---|---|
examples/provenance-check.mjs |
Zero-dependency script that queries the live chain and shows what an app can / cannot know about a coin |
examples/memo-check.mjs |
Samples 200 real Mainnet transactions and shows the large majority carry no memo at all (memo_type: "none") — so memo is optional, sender-written text, not a provenance channel |
examples/falsify-try.mjs |
Falsification attempt (F1/F3): enumerates every key the live API returns for a real payment — transaction, operations, effects, account — and scans them all for origin-like tokens; exits non-zero if it finds one |
examples/coin-id-check.mjs |
Proves the balance is a single number — there is no per-micro-π ID |
examples/mixing-check.mjs |
Live proof for the exact “mined + bought, then send” scenario: commingling → scalar balance → origin-less payment |
examples/verify-deposit.mjs |
Verifies a specific transfer is a real native Pi payment on Mainnet (Claim B); states what the ledger cannot prove (Claim C) |
examples/self-test.mjs |
Automated checks: offline unit tests (same-op rule, failed-tx gate) + offline transport-failure handling (HTTP 500/429, invalid JSON, connection failure, timeout) + online end-to-end (correct → VERIFIED, wrong → NOT VERIFIED). 29 checks |
examples/model-check.mjs |
Executable model + property check (2000 seeds, offline): state is one scalar, history → state non-injective, recipient view independent of origin |
examples/exhaustive-check.mjs |
Exhaustive bounded check (offline, no randomness): enumerates all 1452 histories in a finite domain; origin-invisibility holds for every one |
examples/run-all.mjs |
One command runs every check and prints a summary (--offline to skip network) |
examples/lib.mjs |
Shared helpers: robust API client (shows HTTP status + body on error) and strkey input validation |
examples/app-payment-flow/frontend.html |
Official Pi.createPayment flow (U2A) |
examples/app-payment-flow/server.js |
Backend handling PaymentDTO + printing its keys |
examples/payment-dto-sample.json |
Sample PaymentDTO showing the missing origin field |
PROOF.md |
The step-by-step proof |
Run it:
node examples/provenance-check.mjs
node examples/coin-id-check.mjs
node examples/memo-check.mjs
node examples/falsify-try.mjs
# or against a specific transaction:
node examples/provenance-check.mjs <TXID>
| Case | The real difference |
|---|---|
| Lockup / claimable balance | Shows as claimable_balance (with a time predicate) ≠ native. A difference of state, not of asset. |
| Unmigrated mining balance | Not on-chain at all (in-app balance) → before vs after. |
| Exchange IOU / token | May not be real native π. This is the only distinction that matters in practice. |
| Other tokens | Show asset_code + asset_issuer and require a trustline. |
Bottom line: the single native asset is fully fungible. Any real distinction is about state (locked / liquid / off-chain) or asset type (native vs token/IOU) — never mining history.
Proven ✅
Not claimed ❌
docs/FAQ-FUNGIBILITY-VS-PRICE.md).docs/AUDIT-REQUEST.md.Precise claim: “A native Pi payment carries no observable per-unit mining origin on the Pi Mainnet ledger.” No more, no less. Distinguishing A (fungibility, proven) from B (a specific deposit — verifiable) Precision package: assumptions & adversary capabilities in
docs/THREAT-MODEL.md; the three claims indocs/SCOPE.md·docs/FAQ-EXCHANGE-DEPOSIT-VERIFY.md.
claimable_balance is a scam.Found a flaw? That is the most valuable contribution. See CONTRIBUTING.md and the
audit status docs/AUDIT-REQUEST.md (this repo is not independently audited yet).
If you reference this work, please cite it — see CITATION.cff — or simply link the repository:
https://github.com/aybvip/pi-fungibility. A machine-readable index for AI tools is in llms.txt;
the website is https://aybvip.github.io/pi-fungibility/.
MIT