# Example: price-aware vault

A minimal Tolk vault that gates withdrawals on a Phoebe-verified price. Shows
the end-to-end pattern: consumer dapp → `RequestPrice` → Phoebe verifies
proof + signature → `FulfillPrice` callback delivers a trustworthy `PriceLeaf`
→ vault uses the price to compute payout.

## What it does

Anyone can deposit TON. To withdraw, a depositor calls `RequestWithdraw(amount)`:

1. The vault stores a pending `(depositor, amount, queryId)` record.
2. The vault sends `RequestPrice` to Phoebe, asking for the current price of
   feedId 0 (BTC/USD by convention in v1).
3. Phoebe verifies the consumer-supplied Merkle proof against its
   BLS-attested cached root, then sends `FulfillPrice { queryId, leaf }`
   back to the vault.
4. The vault looks up the pending record by `queryId`, computes a quoted
   payout (e.g. `amount × leaf.mantissa × 10^leaf.expo`), and sends TON
   back to the depositor.

This is the canonical "price-quoted action" pattern — replace
"withdrawal" with "settlement", "liquidation check", "option strike",
"DEX fill", etc.

## File map

| File | Purpose |
|------|---------|
| `contracts/price-aware-vault.tolk` | Tolk source: `Deposit` / `RequestWithdraw` / `FulfillPrice` receivers |
| `PriceAwareVault.compile.ts` | Blueprint compile descriptor |
| `PriceAwareVault.ts` | TS wrapper — `sendDeposit` / `sendRequestWithdraw` / `getPending` |

## Wire it up locally

```typescript
import { Blockchain } from '@ton/sandbox';
import { toNano } from '@ton/core';
import { compile } from '@ton/blueprint';

import { deployPhoebeFixture } from '@titon-network/phoebe-sdk/testing';
import { PhoebeMerkleTree } from '@titon-network/phoebe-sdk';

import { PriceAwareVault } from './PriceAwareVault';

const blockchain = await Blockchain.create();

// 1. Stand up Phoebe + Atlas + ForgeTON in the sandbox.
const fx = await deployPhoebeFixture(blockchain);

// 2. Operator pushes a snapshot containing a BTC/USD leaf at slot 0.
const btcLeaf = {
    feedId:   0,
    mantissa: 6_543_210n * 100_000_000n,    // $65,432.10
    expo:     -8,
    confBps:  50,
    pubTime:  blockchain.now ?? 0,
};
const { tree } = await fx.pushSnapshot({ leaves: new Map([[0, btcLeaf]]) });

// 3. Deploy the vault, pinned to Phoebe.
const vaultCode = await compile('PriceAwareVault');
const vault = blockchain.openContract(PriceAwareVault.createFromConfig(
    { owner: fx.phoebeOwner.address, phoebe: fx.phoebe.address },
    vaultCode,
));
await vault.sendDeploy(fx.phoebeOwner.getSender(), toNano('1'));

// 4. Depositor sends TON, then asks for a price-quoted withdrawal.
const alice = await blockchain.treasury('alice');
await vault.sendDeposit(alice.getSender(), { value: toNano('10') });
await vault.sendRequestWithdraw(alice.getSender(), {
    value:    toNano('0.5'),                   // gas + Phoebe pull fee
    amount:   toNano('1'),                      // 1 TON of withdrawal
    feedId:   0,
    leaf:     btcLeaf,
    proof:    tree.proof(0),
});

// Phoebe → vault FulfillPrice fires automatically inside the same tx batch.
// `getPending(alice.address)` is now empty; alice received her quoted TON.
```

## Notes

- This example uses a treasury for the depositor (so the FulfillPrice
  outbound to the vault works in-tx). In production the depositor would be
  any contract that can hold TON.
- The vault never trusts user input for price — it ONLY uses the
  `leaf.mantissa` from the FulfillPrice callback, which Phoebe has
  cryptographically validated.
- The `queryId` correlation is critical: the vault must match the
  callback to the right pending withdrawal. Use `QueryIdStream` from the
  SDK if you need monotone-unique IDs across many concurrent requests.

### Mode A vs Mode B (cached fast-path vs Pyth-style update + read)

This example uses **mode A** (cached fast-path, ~13k gas) — it reads
whatever root is currently on-chain. That's appropriate for a low-stakes
educational example. For production protocols where 30s heartbeat
freshness could cause real loss (lending liquidations, perpetuals
funding, options settlement), use **mode B**: attach a freshly signed
snapshot inline by passing `freshUpdate` to `sendRequestPrice`. Costs
~73k gas instead of ~13k but gives you sub-heartbeat freshness AND
advances the cache for everyone in the same block (positive
externality). See [`skills/phoebe-integrate-consumer.md`](../../skills/phoebe-integrate-consumer.md)
"Optional: Pyth-style update + read (mode B)" for the canonical
walkthrough, and [`skills/phoebe-operator-setup.md`](../../skills/phoebe-operator-setup.md)
"Step 4b" for the operator-side `/signed-snapshot` endpoint that
mode-B consumers depend on.
