---
name: phoebe-deploy
description: Deploy a fresh Phoebe instance, bind it to Atlas + ForgeTON, and verify the deployment. Use when the user says "deploy Phoebe", "stand up a Phoebe instance", "wire Phoebe to its peers", or "set up a new oracle".
---

# Deploy Phoebe

You're standing up a new Phoebe deployment. Three contracts need to know about each other: Phoebe pinned to one ForgeTON + one Atlas, ForgeTON admitting Phoebe as a consumer, Atlas admitting Phoebe as a verifier.

## Prerequisites

- A funded owner wallet (≥ 5 TON for deploy + admit + initial reward-pool seed).
- Live Atlas + ForgeTON addresses for the target network. For mainnet: `assertDeployment('mainnet').atlas` / `.forgeton` from the respective sibling SDKs (atlas-sdk + forgeton-sdk).
- Sandbox tests for your specific deploy script before sending real TON.

## Deploy step-by-step

### Step 0 — sanity-check the bundled artifact

```bash
npx phoebe hash
# → hex:    <bundled-code-hash>
#   base64: <bundled-code-hash-b64>
```

Compare against the contract you reviewed. If you're deploying from the in-repo build (not the published SDK), run `pnpm run build` from the repo root first.

### Step 1 — derive the address + deploy

```typescript
import { Address, toNano } from '@ton/core';
import { newPhoebe, PHOEBE_DEFAULTS } from '@titon-network/phoebe-sdk';

const ownerAddr    = Address.parse('0Q...');
const forgetonAddr = Address.parse('0Q...');     // from forgeton-sdk's deployments
const atlasAddr    = Address.parse('0Q...');     // from atlas-sdk's deployments

const phoebe = tonClient.open(newPhoebe({
    owner:    ownerAddr,
    forgeton: forgetonAddr,
    atlas:    atlasAddr,
    // tunables defaults: pullFee=0.01, minStorageReserve=0.1, maxPushDrift=300s
    // tunables: { pullFee: toNano('0.02') },   // override if needed
}));

// Compute the Phoebe address BEFORE sending the deploy — useful for funding
// the address ahead of time + for cross-references in your config:
const phoebeAddr = phoebe.address;
console.log('Phoebe will deploy at', phoebeAddr.toString({ testOnly: true, bounceable: false }));

// Send the deploy. Generous funding — the contract holds a `rewardPool`
// (accumulated pull fees) + `minStorageReserve` (0.1 TON default).
await phoebe.sendDeploy(ownerSender, toNano('1'));
```

### Step 2 — admit Phoebe at Atlas (Atlas owner)

This is what triggers the bootstrap `GroupKeySync` fan-out into Phoebe's cache.

```typescript
import { Atlas } from '@titon-network/atlas-sdk';
const atlas = tonClient.open(Atlas.createFromAddress(atlasAddr));

await atlas.sendSetVerifier(atlasOwnerSender, {
    value:    toNano('0.5'),       // covers Atlas compute + per-verifier fan-out
    contract: phoebeAddr,
    isActive: true,
});
```

Verify Phoebe cached the key:

```typescript
const groupKey = await phoebe.getGroupKey();
console.log(groupKey);
// → { groupId: 0, groupEpoch: 1, threshold: 1, memberCount: 1,
//     groupPk: <48-byte buffer>, cachedAt: <ts> }
```

If `groupKey === null`, Atlas hasn't fanned out yet — owner can re-trigger via:

```typescript
await phoebe.sendSyncAtlas(ownerSender, { value: toNano('0.1') });
```

### Step 3 — admit Phoebe at ForgeTON (ForgeTON owner)

This causes ForgeTON to fan out `AutomatonSync` for every active operator into Phoebe's mirror.

```typescript
import { ForgeTON } from '@titon-network/forgeton-sdk';
const forgeton = tonClient.open(ForgeTON.createFromAddress(forgetonAddr));

await forgeton.sendSetConsumer(forgetonOwnerSender, {
    value:    toNano('0.1'),
    contract: phoebeAddr,
    isActive: true,
});
```

Active operators are now mirrored. Verify by polling Phoebe:

```typescript
const op = await phoebe.getOperator(someOperatorAddr);
console.log(op);    // { isActive: true } if mirrored
```

### Step 4 — publish the feed → symbol map off-chain

Phoebe doesn't carry an on-chain feed registry — symbol display lives in operator-published config files (consumers fetch the canonical `feedId → symbol` map from the operator's published URL). The on-chain verify path is feedId-registration-agnostic.

Write `feeds.json`:

```json
{
    "0": { "symbol": "BTC/USDT", "decimalsHint": -8 },
    "1": { "symbol": "ETH/USDT", "decimalsHint": -8 }
}
```

Publish it next to your operator endpoint (S3 / GitHub Pages / IPFS). Consumers fetch it once at boot.

### Step 5 — verify the deployment

```bash
npx phoebe info <phoebe-addr> --testnet
```

Expected output:

```
phoebe @ 0:abcd…
  owner:    0:dead…
  forgeton: 0:beef…
  atlas:    0:cafe…
  schema:   storage=1 config=1
  pullFee:  0.0100 TON (10000000)
  reserve:  0.1000 TON (100000000) (rewardPool: 0.0000 TON (0))
  drift:    ±300s
  groupKey: epoch=1 t=1/n=1 pk=AABBCCDDEE…
  snapshot: NEVER PUSHED
```

`snapshot: NEVER PUSHED` is correct — operators haven't pushed yet. Once the first push lands, `snapshot:` shows the timestamp + root.

Drift check vs SDK expectation (once `PHOEBE_TESTNET` lands):

```bash
npx phoebe verify --testnet
```

## Watch out for

- **Address pinning is permanent.** `forgeton` and `atlas` are immutable post-deploy. Repointing requires a code upgrade. Triple-check the addresses before sending.
- **Workchain mismatch.** All three contracts MUST be on the same workchain (typically 0). A workchain-mismatch fan-out fails with action 36 at the sender.
- **Bootstrap order matters.** Step 2 (admit at Atlas) must happen before step 3 (admit at ForgeTON) for clean operation — without a cached `groupPk`, operators trying to push will hit `E_GROUP_KEY_NOT_SET` (180). The order shown is the recommended one.
- **Mainnet checklist.** Never deploy to mainnet without: (a) external audit cleared, (b) Pattern 2 timelocked upgrade shipped (24h delay between Propose + Execute is the mainnet-blocker), (c) ≥ 3 operators committed.

## Cross-references

- [`AGENTS.md`](../AGENTS.md) — full SDK surface
- [`phoebe-owner-ops.md`](./phoebe-owner-ops.md) — post-deploy admin tasks
- [`phoebe-debug.md`](./phoebe-debug.md) — what to do when something doesn't land
