# OPCODES.md — flat opcode reference

Every wire-struct opcode in the Phoebe ABI. Mirrors `phoebe/contracts/messages.tolk` 1:1.

To use at runtime:

```typescript
import { OP } from '@titon-network/phoebe-sdk';
OP.PushSnapshot;        // 0x70
OP.RequestPrice;        // 0x71
OP.FulfillPrice;        // 0x72
```

CLI: `npx phoebe schema` (full SDK constants).

---

## Cross-contract — inbound from peer protocols

| Hex | Name | Direction | Source |
|-----|------|-----------|--------|
| `0x1A` | `AutomatonSync` | inbound ← ForgeTON | byte-identical to `forgeton/contracts/messages.tolk` |
| `0x51` | `GroupKeySync` | inbound ← Atlas | byte-identical to `atlas/contracts/messages.tolk` |

## Cross-contract — outbound

| Hex | Name | Direction | Notes |
|-----|------|-----------|-------|
| `0x50` | `SyncRequest` | outbound → Atlas | Owner-triggered Atlas re-bootstrap. Atlas re-pushes the current `GroupKeySync`. |

## Phoebe-specific (0x70 – 0x7F)

| Hex | Name | Direction | Notes |
|-----|------|-----------|-------|
| `0x70` | `PushSnapshot` | operator → Phoebe | BLS-attested snapshot push. Internal message; carries `signedDataRef` (phoebeHash + timestamp + root) + `aggSigRef` (96-byte G2 sig). |
| `0x71` | `RequestPrice` | consumer → Phoebe | Permissionless + fee-gated. Carries queryId + feedId + Merkle proof (ref) + claimed leaf (inline) + maxStaleness + optional `freshUpdateRef: Cell<FreshUpdate>?`. Two modes: (A) **cached fast-path** (~13k gas) when `freshUpdateRef = null` — proof walks against `cfg.lastRoot`; (B) **fresh-update path** (~73k gas) when `freshUpdateRef` is set — Phoebe BLS-verifies the attached signed snapshot, advances the cache, and walks the proof against the fresh root. Mode B is permissionless (BLS verify under groupPk is the auth). |
| `0x72` | `FulfillPrice` | Phoebe → consumer | Frozen public ABI for consumer receivers. Body: `(queryId, leaf)`. `bounce: false`. |
| `0x73 – 0x7F` | reserved | — | Future Phoebe consumer/operator ops. |

## Events (0x80 – 0x8C)

All emitted via `createExternalLogMessage` (dest: addr_none). Decode via `decodeEvent(body)` from the SDK.

| Hex | Name | Payload (after 32-bit opcode) |
|-----|------|-------------------------------|
| `0x80` | `EvtOperatorMirrored` | `automaton: address`, `isActive: bool`, `cause: uint8` (1=FIRST_SYNC, 2=ACTIVATION_CHANGE) |
| `0x81` | `EvtGroupKeyCached` | `groupId: uint8`, `oldEpoch: uint32`, `newEpoch: uint32`, `groupPk: bits384`, `threshold: uint8`, `memberCount: uint8` |
| `0x82` | `EvtSnapshotPushed` | `submitter: address`, `timestamp: uint32`, `root: uint256`, `feedCount: uint16` |
| `0x83` | `EvtPricePulled` | `consumer: address`, `queryId: uint64`, `feedId: uint16`, `snapshotAt: uint32` |
| `0x84` | _reserved_ | (was `EvtFeedRegistered`; deleted pre-deploy with the on-chain feed registry.) |
| `0x85` | `EvtPaused` | (no payload) |
| `0x86` | `EvtUnpaused` | (no payload) |
| `0x87` | `EvtConfigUpdated` | `pullFee: coins`, `minStorageReserve: coins`, `maxPushDrift: uint32`, `operatorBps: uint16`, `maxInactivity: uint32` |
| `0x88` | `EvtFeesWithdrawn` | `to: address`, `amount: coins` |
| `0x89` | `EvtCodeUpgradeProposed` | `newCodeHash: uint256`, `eta: uint32` |
| `0x8A` | `EvtCodeUpgradeExecuted` | `oldCodeHash: uint256`, `newCodeHash: uint256` |
| `0x8B` | `EvtCodeUpgradeCancelled` | `newCodeHash: uint256` |
| `0x8C` | `EvtRewardClaimed` | `claimant: address`, `to: address`, `amount: coins` |

## Owner admin + operator-claim ops (0xA0 – 0xAF)

| Hex | Name | Notes |
|-----|------|-------|
| `0xA0` | `Pause` | Owner-only. Blocks PushSnapshot + RequestPrice; admin paths still work. |
| `0xA1` | `Unpause` | Owner-only. |
| `0xA2` | `UpdateConfig` | Owner-only. Partial-update — null fields preserve existing value. Accepts `operatorBps?: uint16` (0..10000) and `maxInactivity?: uint32` (0..86400; 0 = disabled — pull-side B filter off). |
| `0xA3` | `WithdrawFees` | Owner-only. Drains `rewardPool` (owner share of pullFees) to `to` address. Does NOT drain operator unclaimed balances. |
| `0xA4` | `SyncAtlas` | Owner-only. Triggers Atlas re-bootstrap (sends `SyncRequest 0x50` to Atlas). |
| `0xA5` | `ProposeCodeUpgrade` | Owner-only. Step 1/3 of timelocked upgrade. `eta >= now + 24h`. |
| `0xA6` | `ExecuteCodeUpgrade` | Owner-only. Step 2/3. After `eta` elapsed. |
| `0xA7` | `CancelCodeUpgrade` | Owner-only. Step 3/3 alternative. |
| `0xA8` | `ClaimReward` | Operator-gated. Sender must be in `storage.operators` (active OR inactive — earned is earned). Pays operator's accrued pull-fee share to `to`. `amount: 0n` = claim all. |

## Reserved opcode ranges

| Range | Status |
|-------|--------|
| `0x73 – 0x7F` | Reserved for future Phoebe operator/consumer ops |
| `0x8D – 0x8F` | Unallocated — available for new events |
| `0x90 – 0x9F` | Unallocated — available for future events |
| `0xA9 – 0xAF` | Reserved for future owner / operator / permissionless ops |
