Metadata-Version: 2.4
Name: titon-network-phoebe-sdk
Version: 0.5.0
Summary: Python SDK for Phoebe — TON-native BLS-attested price oracle. Push BLS-signed Merkle-root snapshots, pull individual prices, verify off-chain. Byte-parity with @titon-network/phoebe-sdk.
Project-URL: Homepage, https://github.com/titon-network/phoebe
Project-URL: Repository, https://github.com/titon-network/phoebe.git
Project-URL: Issues, https://github.com/titon-network/phoebe/issues
Author: titon.network
License-Expression: MIT
License-File: LICENSE
Keywords: atlas,blockchain,bls12-381,forgeton,merkle-proof,oracle,phoebe,price-oracle,threshold-bls,tolk,ton
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: aiohttp>=3.9
Requires-Dist: py-ecc>=7.0.0
Requires-Dist: pytoniq-core>=0.1.40
Requires-Dist: pytoniq>=0.1.40
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# titon-network-phoebe-sdk

Python SDK for [**Phoebe**](https://github.com/titon-network/phoebe) — TON's
BLS-attested Merkle-root price oracle. Operators heartbeat a signed
snapshot root every ~30s; consumers pull individual feeds via cell-merkle
proofs. One `BLS_VERIFY` + one cell-walk per pull, for any feed in a
256-feed snapshot.

**Version-matched companion to [`@titon-network/phoebe-sdk@0.5.0`](../typescript/)** — byte-identical
init cells, Merkle roots, BLS hash domain, and PriceLeaf wire codec. Pick
your language; deploys at the same address.

## Install

```bash
pip install titon-network-phoebe-sdk
```

Requires Python ≥ 3.11.

## The one-call dapp surface — `fetch_verified_price`

The shortest path from "I need a price" to "I have a verified leaf + proof
ready for my consumer contract":

```python
import asyncio
from pytoniq import LiteBalancer
from phoebe_sdk import Phoebe, assert_deployment, fetch_verified_price

async def main():
    client = LiteBalancer.from_mainnet_config(trust_level=2)
    await client.start_up()

    dep = assert_deployment("mainnet")
    phoebe = Phoebe.create_from_address(dep.phoebe, client=client)

    # One call: fetches leaves from every operator in parallel, reconstructs
    # the merkle root locally, compares to phoebe.lastRoot on-chain, returns
    # the verified leaf + proof. Lying / stale operators caught by hash
    # mismatch; helper falls through to the next in list.
    quote = await fetch_verified_price(phoebe, feed_id=0, operators=dep.operators)

    print(f"TON/USD = {quote.mantissa} × 10^{quote.expo}")
    print(f"snapshot age: {quote.age_sec}s")
    # quote.proof + quote.leaf are now ready to submit to your consumer
    # contract via Phoebe.send_request_price.

    await client.close_all()

asyncio.run(main())
```

Trust model: NO trust in any operator. The reconstructed merkle root is
compared against `phoebe.lastRoot` on-chain; mismatched / stale / buggy
operators are skipped. A malicious operator can't forge a matching root
because they don't hold the threshold BLS secret that signed it on-chain.

## Three surfaces

### 1. Contract wrapper — `Phoebe`

```python
from phoebe_sdk import Phoebe, new_phoebe

# Existing deploy:
phoebe = Phoebe.create_from_address(addr, client=client)
config = await phoebe.get_config()
snapshot = await phoebe.get_snapshot()

# Fresh deploy:
phoebe = new_phoebe(
    owner=owner_addr, forgeton=forgeton_addr, atlas=atlas_addr, client=client
)
await phoebe.send_deploy(wallet, value=1_000_000_000)  # 1 TON seed
```

Every on-chain receiver has a `send_*` method:
`send_push_snapshot`, `send_request_price`, `send_claim_reward`,
`send_pause`, `send_unpause`, `send_update_config`, `send_withdraw_fees`,
`send_sync_atlas`, `send_propose_code_upgrade`, `send_execute_code_upgrade`,
`send_cancel_code_upgrade`, `send_gc_operator`, plus cross-contract sandbox
helpers `send_automaton_sync` / `send_group_key_sync`.

Every getter has a `get_*`: `get_ownership`, `get_paused`, `get_config`,
`get_schema_versions`, `get_operator`, `get_group_key`, `get_snapshot`,
`get_last_submitter`, `get_unclaimed_reward`, `get_pending_upgrade`.

### 2. Off-chain Merkle tree + BLS primitives

```python
from phoebe_sdk import (
    PhoebeMerkleTree, PriceLeaf,
    compute_snapshot_hash, sign_message, aggregate_signatures,
)

# Build the 256-leaf merkle tree (sparse → placeholders fill the rest).
tree = PhoebeMerkleTree.from_sparse_leaves({
    0: PriceLeaf(feed_id=0, mantissa=3_500_000_000, expo=-9,
                 conf_bps=50, pub_time=1_700_000_000),
})
root = tree.root_as_int()  # int form for PushSnapshot.root

# Threshold-BLS-sign (phoebeHash, timestamp, root) under groupPk.
ts = int(time.time())
msg = compute_snapshot_hash(phoebe.address, ts, root)
sig = sign_message(operator_sk, msg)
agg_sig = aggregate_signatures([sig])  # solo or aggregated partials

await phoebe.send_push_snapshot(wallet, value=100_000_000,
                                timestamp=ts, root=root, agg_sig=agg_sig)
```

### 3. Event decoding + error explaining

```python
from phoebe_sdk import decode_event, explain_error, PhoebeError

ev = decode_event(external_out_body)
if ev.kind == "SnapshotPushed":
    print(f"root 0x{ev.root:x} from {ev.submitter} at ts={ev.timestamp}")

# Structured explanation for any exit code:
e = explain_error(161)
# ErrorExplanation(code=161, origin='phoebe', name='InvalidBlsSignature',
#                  message='...', hint='Most likely causes: wrong DST...')
```

## Cross-language byte parity

The TS and Python SDKs ship at the **same version** and produce
byte-identical:

- **Init cells** — `Phoebe.create_from_config(...)` derives the same
  deployment address from the same `(owner, forgeton, atlas)` tuple in
  either language. Test: `test_address_parity` cross-verifies against the
  live testnet + mainnet addresses.
- **`PriceLeaf` cells** — same 200-bit packed layout, same `cell.hash()`.
- **Merkle roots** — `PhoebeMerkleTree.root()` matches TS
  `PhoebeMerkleTree.root()` byte-for-byte for the same leaf set.
- **BLS hash domain** — `compute_snapshot_hash(phoebe, ts, root)` returns
  the same 68-byte buffer as TS; signatures from
  `py_ecc.bls.G2ProofOfPossession.Sign` verify under the same `groupPk`
  as `@noble/curves/bls12-381` `longSignatures.sign`.
- **Pruned merkle proofs** — `tree.proof(feed_id)` produces a level-0
  merkle_proof exotic cell that hashes identically to the TS output and
  validates on-chain.

## Live deployments

```python
from phoebe_sdk import PHOEBE_TESTNET, PHOEBE_MAINNET, assert_deployment
```

- **Testnet**: `0QDpuv4PEKctvS4Oj1bZ8doILQbC1iM7TNzTz4OIHVXPB-Mm`
- **Mainnet**: `UQA0puv5JEGFvyRYW_XEveKiI0--XdVKsJv8wk0CmhFlc0-u`
  (multi-op n=2, threshold=2, epoch=1)

Both have `operators=[...]` populated on mainnet — `fetch_verified_price`
will work out of the box.

## What's not here

- **Sandbox testing helper** (`testing/`) — TS-only today. `pytoniq` doesn't
  ship a parallel `@ton/sandbox`. Run sandbox tests against the TS SDK; use
  this Python SDK for production consumers + live monitoring.
- **CLI** — phoebe TS ships a `phoebe describe` / `phoebe estimate` CLI;
  Python parity is a follow-up. Use `python -c 'from phoebe_sdk import …'`
  one-liners for now.
- **Owner-side deploy + wire-up scripts** — Atlas SetVerifier + ForgeTON
  SetConsumer require the sibling TS SDKs. Run TS deploy
  (`phoebe/scripts/deployPhoebeScripted.ts` →
  `wirePhoebeScripted.ts`), then read state from Python.
- **Operator-side daemon** — production operators run the
  [`automaton`](../../../automaton/) TS daemon. Python has the BLS + sign
  primitives but no daemon harness.

## Mirror checklist

When editing this Python SDK, mirror to the TS sibling:

| Python change | TS mirror |
|---------------|-----------|
| Add a row to `errors.py:_PHOEBE_MESSAGES` | Add to `sdks/typescript/src/errors.ts:PHOEBE_MESSAGES` |
| Bump `PHOEBE_STORAGE_VERSION` / `PHOEBE_CONFIG_BLOB_VERSION` | Update `sdks/typescript/src/opcodes.ts` + `contracts/Phoebe.ts` init cell layout |
| Add a new `send_*` / `get_*` | Add the same method to `sdks/typescript/src/contracts/Phoebe.ts` |
| New `OperatorEndpoint` entry on a deployment | Update both `deployments.py` and `sdks/typescript/src/deployments.ts` |

## License

MIT
