# SpyON / SXP500 — public Solana Devnet evidence, part 2 (round trip closed)

Evidence date: **10 September 2026, 23:36 UTC**. Network: **Solana Devnet only**. All assets are valueless test assets. No mainnet transaction, real BTC, real USDC, broker account or customer funds are involved. Previous evidence: [DEVNET_EVIDENCE_2026-08-27.md](DEVNET_EVIDENCE_2026-08-27.md) (buy side).

Operator: Claude (Anthropic) on Cristi's Mac, from the canonical working tree `07_REPO_CANONIC` (sources = `SpyON_Public_Review_Package_2026-08-27.zip`, SHA-256 `da4878af…8342` re-verified before extraction). Command run exactly as prepared on 27 August:

```bash
SOLANA_NETWORK=devnet \
ANCHOR_PROVIDER_URL=https://api.devnet.solana.com \
ANCHOR_WALLET=target/devnet-admin.json \
ORACLE_PRICE_USD=650 \
npm run devnet:redeem:synthetic
```

## A1 — Epoch #1 redemption: SXP → test USDC → TEST BTC

| Step | Finalized Devnet transaction | Block time (UTC) | Slot |
|---|---|---|---|
| 1. Fresh manual **test-only** oracle observation (650.000000 USD, conf 0.65) | [`5AUSRP…jh77`](https://explorer.solana.com/tx/5AUSRPLKC3QRzPjYo3hnGpcxM3cbPCqaqqTWcuKMsGDHoH3PdD1tr8n6KtBuK5kkyHsVqWBzuenbEV17g34zjh77?cluster=devnet) | 2026-09-10 23:36:12 | 496387607 |
| 2. Epoch #1 settled | [`yi9ytF…SnJe`](https://explorer.solana.com/tx/yi9ytFTSF9DKYTnP1bZ9dtUgX1TQDN3rGkhmD3Gzhphh1oaVgVEJxcYHNRpHewABSXiwPHTN79LBAa3bckLSnJe?cluster=devnet) | 2026-09-10 23:36:14 | 496387614 |
| 3. Burn 0.153461538 SXP → claim 99.500625 test USDC | [`4kbMh1…mKhn`](https://explorer.solana.com/tx/4kbMh1KTgbwXtQuRvSMgKseJuctBg7YNyqHgQ71mpeEhZfn1fnp8pxtZ7iAgR72zfyDCqsrLgmnQEL3Abt7FmKhn?cluster=devnet) | 2026-09-10 23:36:28 | 496387694 |
| 4. Swap 99.500625 test USDC → 0.00152924 TEST BTC | [`25N5wP…QFqv`](https://explorer.solana.com/tx/25N5wP7U4zEg8JLAHvUvC8tj2msDNua1nmbNzvytfDvqfUfVccDjfHM5HdRjPAc4zTPoUj4DVXjjXj2DNM9oQFqv?cluster=devnet) | 2026-09-10 23:36:37 | 496387742 |

Each signature was independently checked with `solana confirm -u devnet <sig>` → `Finalized`, and via `getTransaction` (commitment `finalized`, `meta.err = null`).

### Supply and balances

| Item | Before (23:36:02 UTC, slot 496387537) | After (23:36:42 UTC, slot 496387723) |
|---|---|---|
| `getTokenSupply` on epoch #1 SXP mint `2FcA2dtCPt77taEFawuPzviCw5mjhSKQv8BVsSPMNjo5` | 153,461,538 base units (0.153461538 SXP) | **0** |
| Wallet `C5u96…Xdd9` SXP (long) | 153,461,538 | 0 |
| Wallet TEST BTC (8 decimals) | 497,000,000 (4.97) | 497,152,924 (4.97152924) |
| Wallet test USDC (6 decimals) | 2,848,050,000 | 2,848,050,000 (claim +99.500625, swap −99.500625) |
| Wallet short (opposing liquidity) | 2,000,000,000 | 2,000,000,000 (unchanged; short side claimable separately) |

### Round trip, both directions, all public

- 27 Aug 2026: 0.01 TEST BTC → 649.35 test USDC (`3GRre7…F77`), 100 test USDC → 0.153461538 SXP (`xwqL3B…n74y`).
- 10 Sep 2026: 0.153461538 SXP → 99.500625 test USDC (`4kbMh1…mKhn`), 99.500625 test USDC → 0.00152924 TEST BTC (`25N5wP…QFqv`).
- Net: 100 test USDC in → 99.500625 test USDC out = 0.25 % fee on entry + 0.25 % on exit (0.499375 %), with the index reference unchanged at 650 USD on both observations. SXP supply for epoch #1 returned to zero.

### Notes

- Public RPC returned HTTP 429 (rate limit) several times during the run; the client retried automatically and every transaction finalized. No faucet was used; wallet SOL went from 5.71629276 to a lower value only by transaction fees and rent.
- The manual oracle observation is test-only (see A6 for Pyth/Hermes). No contract change, no key change.
- Private keypair files stayed in `target/` (git-ignored) and in the private key folder; none were displayed, copied to chat, repo, Trello or archives.

## A2 — Test suites re-run on 10 September 2026

Toolchain: Node v26.7.0, npm 11.19.0, cargo 1.98.0 (2026-08-05), anchor-cli 1.1.2, solana-cli 3.1.10 (Agave). macOS (Darwin 24.3.0).

| Suite | Command | Result | Time (UTC) |
|---|---|---|---|
| TypeScript unit + tools (shared, sdk, relayer, guardian, web, tools) | `npm test` | **32 passed, 0 failed** | 23:38:57 – 23:39:00 |
| Rust unit tests (open_sp_protocol 1 + open_sp_synthetic 4) | `cargo test` | **5 passed, 0 failed** | 23:39:17 – 23:39:31 |
| Local-validator integration (paper vault + DeFi V2 long/short settlement + tools) | `anchor test --skip-build --validator legacy --provider.wallet target/local-admin.json` | **7 passed, 0 failed** (DeFi V2 E2E 14.5 s) | 23:43:39 – 23:44:02 |

### Finding fixed on the way (no test disabled, no contract change)

The first E2E run (23:39:42) failed with `program.methods.initializeTestSwap is not a function`. Cause: the IDL and `.so` shipped in `05_BUILD_SOLANA/` (built 27 Aug 14:02, 10 instructions, 500,264 bytes) pre-date the final source `programs/open_sp_synthetic/src/lib.rs` (27 Aug 15:54, 15 instructions incl. the TEST BTC/USDC swap). `anchor build` from the packaged source regenerated `target/idl/open_sp_synthetic.json` (15 instructions) and `target/deploy/open_sp_synthetic.so` (622,800 bytes). Second run: all green.

### Reproducible build check against Devnet

```
solana program dump -u devnet EeCeb8mxJPVa1VotT6bn5N6upB8zLsv1vZFvgRPsuMHp
sha256(first 622,800 bytes of on-chain dump) = 8f95f667d8d5fe85ee588011d308efdf56678d818b5b8f80193b58227d0c60ae
sha256(target/deploy/open_sp_synthetic.so)    = 8f95f667d8d5fe85ee588011d308efdf56678d818b5b8f80193b58227d0c60ae
```

The program deployed on Devnet is byte-identical to a fresh `anchor build` of the public source package. The stale 500,264-byte binary in `05_BUILD_SOLANA/` is kept as history; fresh artifacts are copied to `05_BUILD_SOLANA/2026-09-10/`.

## A4 (part 1) — Separate Devnet test wallet, funded and signing on its own

A new keypair was generated only for Devnet tests (`target/devnet-test-user.json`, git-ignored; copy in the private key folder; never displayed). Public key: **`HVnCu2kEehZtwC9XPfskfLoL9mPfC1i4kv3c4YA3T2VB`**.

| Step (all 23:46–23:55 UTC) | Finalized Devnet transaction |
|---|---|
| 0.05 SOL devnet from the admin wallet (no faucet) | [`2YPNbj…xzxY`](https://explorer.solana.com/tx/2YPNbjRXs9zyLbWcX5ne5Hv9vQuqo4H3gQ5ZHBfpByP7aVKyEDn3Phq1hZ6rhDzwBY8mYoeb7fp9SwczxwnUxzxY?cluster=devnet) |
| `npm run fund:test-user` 0.05 TEST BTC | [`51Lefs…cgsd`](https://explorer.solana.com/tx/51Lefs1Atcti4shmRBnbm3JXLvktWHvSKEPNDLV9egz6MsDF6JtM96ZFKSTZF7fvf2BCGmnSJvx4TAVGE7Aqcgsd?cluster=devnet) |
| `npm run fund:test-user` 1,000 test USDC | [`3iLKEa…Ff3o8`](https://explorer.solana.com/tx/3iLKEaEz7hYKCZ8niqKvUgoGzEGoHiw8iatTTVRMb9NPqyYmHpkvdjiCGqu8zenvAgXLbzaMif9vfE1sB23Ff3o8?cluster=devnet) |
| **Test wallet signs** 0.01 TEST BTC → 649.35 test USDC (SDK, its own key) | [`4x2R84…EeX4`](https://explorer.solana.com/tx/4x2R84TqF6kn5Yzzw4u8XUVgZFRfXAVJKS4UmL1s9FytLcNrsDw6rsB7Jkaj3JgVspzMABFap4P6JMHLFj19EeX4?cluster=devnet), [`2y4beY…SME7e`](https://explorer.solana.com/tx/2y4beY9pZcPGNYjditX9DtjwVj3o3duWSpFrYqCoMmTci3Y2MbdbrPPrXZdBY6ZHzGEcXNSPWXJ5iRJHrF83SME7e?cluster=devnet) |
| **Test wallet signs** 100 test USDC → 0.153461538 SXP (epoch #2) | [`4xVfiK…JJx7`](https://explorer.solana.com/tx/4xVfiK3UKG5DFkugA5SLaJeW5M51cniqYoCyndcM8GnPcxYwhSHQMvQhGen5zn7r296fg6FdXFGbC7nJ3MNfJJx7?cluster=devnet) |

Part 2 (Phantom/Solflare signing by Cristi through the Devnet UI) is documented in `docs/PHANTOM_DEVNET_TEST.ro.md` and stays open until he signs.

## A5 — Epoch #2 opened + stress tests on Devnet

Epoch #2 (`EyaiVGERuNAHMkVht1AyjARjWD47adCebLM7hnnmYQVP`) opened 23:48:38 UTC with funding window 2 h (ends 2026-09-11 01:48:38 Z) and maturity 4 h (settle after 2026-09-11 03:48:38 Z), start price 650.000000 (manual test oracle). SXP mint #2 `DeJhVjaZhDP1GYyn2daMhk8nFkMza1CnVXDRoAaGGwvw`, short mint `H9Nwocn45pHecxGypbaKmZ164yUMEpZjMVuyrfWoog3k`, short vault `DFrN9ZxszJgcDV5eLjrK9eDBSQT3ChhGaRqTwx83L4cu`. Signatures: oracle observation [`51Us1N…gwyS`](https://explorer.solana.com/tx/51Us1NVDT4wgGJWa1bAZY65Ru6RvowyV6eYTMxVMxCinycsVqXuoXfoeLtFpqKo5hyA5wU8NWWpYLzjfFbVigwyS?cluster=devnet) (23:48:36), open epoch [`27fGkK…LmPi`](https://explorer.solana.com/tx/27fGkKr2br9cqStMVwp28QX9DgaEQPzRgedcb2hnazwm5fDQ6HvFMYsBgKCWXLeuxD1GENpjfmGoUfFKcfcXLmPi?cluster=devnet) (23:48:38).

Runner: `scripts/devnet-stress-epoch2.ts` (Devnet-only safety locks). Negative cases were sent **without preflight**, so each refusal is a public failed transaction whose log carries the AnchorError code.

| Check | Expected guard | Result on Devnet |
|---|---|---|
| Mint 100 tUSDC with **0** opposing liquidity | `InsufficientShortLiquidity` (6022) | refused on-chain [`52ty8z…zsp1`](https://explorer.solana.com/tx/52ty8zc1to9bgNCYzgKFTeiYfhJWiaU2mVASEALaYhqwLpDoVcueecUrDdS4qgAyXrXdA2yAhmdAepe3mWqLzsp1?cluster=devnet) 23:54:24 |
| Admin seeds 500 tUSDC opposing liquidity | — | [`3Drqzg…Ucms`](https://explorer.solana.com/tx/3DrqzgqJLGb6swsLdR3gcYyzEFDT4kNxo56HidqBPWi6x8Bs6YTj8hTMYhNho9RsaysTxC7SyfTitziLq4rgUcmS?cluster=devnet) |
| Mint 600 tUSDC > 500 capacity | `InsufficientShortLiquidity` (6022) | refused on-chain [`2s27V7…HKmv`](https://explorer.solana.com/tx/2s27V7jq7LiWrijF1Dmi6p92jTmtEaNHVKrkDxapSHXk9qruq82Gbm2fNfx3bAEkKeSSqgyWFvXEiyD6W4WQHKmv?cluster=devnet) 23:54:32 |
| Mint 100 tUSDC within capacity (test wallet) | accepted | [`4xVfiK…JJx7`](https://explorer.solana.com/tx/4xVfiK3UKG5DFkugA5SLaJeW5M51cniqYoCyndcM8GnPcxYwhSHQMvQhGen5zn7r296fg6FdXFGbC7nJ3MNfJJx7?cluster=devnet) → 0.153461538 SXP, long principal 99.75 |
| Mint with oracle older than 60 s (age 66 s+) | `StaleOracle` (6012) | refused on-chain [`yXkBQB…RPak`](https://explorer.solana.com/tx/yXkBQBHBhKNe1nqa4ZvxCWbunHLW1qM5G6iY3VL8QYMcefQKadSwCh2njuwzXQvuqg9y7HgxkhgyT4mWrYxRPak?cluster=devnet) 23:55:36 |
| Settle epoch #2 before maturity | `EpochNotMature` (6019) | refused on-chain [`2cBvVV…eXNr`](https://explorer.solana.com/tx/2cBvVVtyEimGnDgNaRRN92Pys5E8PHS4NBuMDBzSeSrGo6bVz1f2pzUTWQvmwfEpbCDBZKv7vQbiESQuciU8eXNr?cluster=devnet) 23:55:38 |
| **Simultaneous withdrawals** — admin and test wallet `claim_short` 1,000 tUSDC each on settled epoch #1 at the same moment (Promise.all, 1.9 s wall clock) | both succeed, vault conserved | admin [`5N9WMi…dNvPK`](https://explorer.solana.com/tx/5N9WMijGqAu8DacGSzXdLQuSDaobXxEkv1Aq55yYhqRv7cC8ARYnw7mhAjLdbAjy8TNXewmSztnVzsL5AoqdNvPK?cluster=devnet), test wallet [`3SaM6e…373Xe`](https://explorer.solana.com/tx/3SaM6eQMQwzFcbm5QUzZ3ePHJsTHZn99gLCo5nezbz7r79brCG1d98tmYS4SPCbJdpjNyh5H1VCYgsxfHs3373Xe?cluster=devnet); epoch #1 short vault 2,000,000,000 → **0** (conserved: 2 × 1,000,000,000) |
| Simultaneous swaps tUSDC → TEST BTC (10 each, both wallets) | both succeed | admin [`4vJ5e3…jSkdo`](https://explorer.solana.com/tx/4vJ5e3KAZaqSRyJWVa35xkaaCtuid62YgZd5F7gLLC5PNj68557XFaYM9QiaJsDMLQDumBr7TcXssGubiipjSkdo?cluster=devnet), test wallet [`3PvMqZ…VGa9v`](https://explorer.solana.com/tx/3PvMqZCD3EzfPrh9ZYNmA42WwkFHYbVeCz6iF9guzYo1cSDQ4TBocJSQKb4aStK3LGxSBuoaPqipo7mkeoBVGa9v?cluster=devnet) |

Epoch #1 is now fully unwound on both sides: long supply 0 (A1) and short vault 0 (this section).

### Guardian (read-only, no signer) against Devnet

`SNAPSHOT_SOURCE=solana SOLANA_NETWORK=devnet` — `GET /api/risk`:

- 23:56:54 UTC, oracle age 146 s → `severity: critical`, `recommendedAction: "pause-recommended"`, finding `ORACLE_STALE: Oracle age 146s exceeds 60s.`; coverage 300.62 % (long 99.75 + short 500 vs required 199.5).
- Fresh observation published [`3Spz99…9veb`](https://explorer.solana.com/tx/3Spz99R3QjzLHoetrcFDj6ijGVVnSLz3mvmmrsUtLkbuRwH1zqCus2soBSAnMuLxhx2Fz8X2zmb6wBD1yKFi9veb?cluster=devnet) (23:56:56) → 23:57:04: `severity: healthy`, `recommendedAction: "none"`, oracle age 7 s.

Full JSON of the run: `docs/evidence/2026-09-10-stress-epoch2.json` and `docs/evidence/2026-09-10-guardian.log`.

## A6 — Pyth/Hermes oracle on Devnet: prepared, **NEVERIFICAT** (two external blockers, documented)

What was checked on 11 September 2026, 00:01–00:05 UTC:

1. **Official feed IDs** (Hermes `/v2/price_feeds`, public metadata): `Equity.US.SPY/USD` = `19e09bb805456ada3979a7d1cbb4b6d63babc3a0f8e8a9509f68afa5c4c11cd5`; `Equity.US.VOO/USD` = `236b30dd09a9c00dfeec156c7b1efd646c0f01825a1758e3e4a0679e3bdff179`. Market hours for SPY: closed at check time, next open 2026-09-11 13:30 UTC.
2. **Hermes price updates now require an API key.** `GET https://hermes.pyth.network/v2/updates/price/latest?ids[]=<SPY>` → **HTTP 401 `unauthorized`** (same for VOO, for BTC/USD, for `hermes-beta` and for the v1 `/api/latest_price_feeds` route). Recorded with the new `PYTH_DRY_RUN=true` mode of `scripts/publish-pyth-observation.ts` at 00:04:53 UTC (`apiKeyProvided: false`). Obtaining a Pyth/Hermes key means creating an account → **Cristi's decision** (rule: no new accounts by the AI). The publisher already supports `PYTH_API_KEY` (sent as a Bearer header, kept only in local env).
3. **The Devnet instance is configured with a documented test feed ID.** `deploy-devnet-synthetic.ts` initialised the config with `sha256("SPION_PROTOCOL_DEVNET_TEST_ORACLE_V1")` = `59e22f15e256093bbd4e768cc98e9fa2d21673bfa5887c5f2e157d50a8437873` (27 Aug). The contract stores `oracle_feed_id` at `initialize_synthetic` and has no instruction to rotate it, and `publish-pyth-observation.ts` refuses a feed ID that differs from the on-chain one. Switching the Devnet instance to the official SPY feed therefore requires a **fresh program instance** (new program ID + re-initialisation ≈ 5.4 SOL devnet, all public addresses change) — not done without Cristi's go, and pointless until a Hermes key exists.

What stays true: the contract-side guards that A6 is meant to exercise (freshness ≤ 60 s, confidence ≤ 100 bps, move breaker 2000 bps, monotonic publish time, market-open flag, source hash) are enforced on-chain and were proven in A5 with the manual test oracle (StaleOracle refusal on-chain; guardian pause-recommended → healthy). The Pyth parser/normaliser is covered by `tests/pyth-observation.test.ts` (2 tests, green in A2).

Next step when Cristi decides: (a) create a Pyth/Hermes API key; (b) `PYTH_DRY_RUN=true PYTH_API_KEY=… PYTH_FEED_ID_HEX=19e09bb8…` to verify live SPY observations during US market hours; (c) either deploy a fresh Devnet instance initialised with `ORACLE_FEED_ID_HEX=19e09bb8…` or accept the documented test feed for the Devnet lab. Manual observation stays demo-only.

## A7 — Devnet mode in the web interface, end to end (11 September 2026, 00:06–00:15 UTC)

- `npm run build --workspace @open-sp/web` with `VITE_PROTOCOL_MODE=devnet`: **green** (00:06:18–00:06:21 UTC, Vite 1.46 s, bundle 716 kB). Documentation: `docs/DEVNET_MODE.ro.md`.
- New, gated lab option: `VITE_DEVNET_BURNER_WALLET=true` adds the wallet-adapter **Burner Wallet** (throwaway in-browser keypair) next to Phantom/Solflare, only in devnet/localnet mode. Never available in demo mode, never for mainnet. Its key lives only in the page: reloading the page discards it (which is what happened after this test — the 0.9965025 SXP it holds are unreachable and valueless).
- The interface read everything on-chain (program deployed, config active, epoch #2 open with settlement time, TEST BTC pool 5.0481 tBTC / 1,872.75 tUSDC, available capacity 400.25 tUSDC, wallet balances).

Burner wallet `6FG8KuaMMSabg63ZuznTvDpbDacnFu9qvTeHNiUq8Xhq`, funded from the admin wallet (0.05 SOL devnet [`45cnx5…c3SB`](https://explorer.solana.com/tx/45cnx5aHdBoi83bJ5TyrtWcwuKv7FqRuDbWpMSdYXj14x5E6454LrybwbLQ1hTmPmJshSEC2VURzwufvtqgkc3SB?cluster=devnet), 0.02 TEST BTC [`5T1y8V…76dX`](https://explorer.solana.com/tx/5T1y8VNoZ6nVZUpB7LHfC5x5kQrhXVzRqRqPtvqDBpNkUyexVS16FczoNccgvnfDCLreohafAA7cdqdVtriV76dX?cluster=devnet)).

| UI action (button **Open test SXP position**, 0.01 TEST BTC) | Signed in the browser by the burner wallet | Result |
|---|---|---|
| 1st attempt, 00:13:48 — swap 0.01 TEST BTC → 649.35 tUSDC | [`3aXRCa…znVy`](https://explorer.solana.com/tx/3aXRCatR2YE1fo4u763D28TXSnQBSkStXWc7MrV2VAi5gf6CQcQ2ajLVDTkKGQSyXavK79yPATF6EswBKABDznVy?cluster=devnet) | confirmed |
| 1st attempt — open long 649.35 tUSDC with 400.25 capacity | (simulation) | **refused: InsufficientShortLiquidity (0x1786)**, shown verbatim in the UI notice — the guard also protects the UI path |
| admin seeds 600 tUSDC opposing liquidity (`npm run seed:synthetic-liquidity`) | [`KrkAYo…UrXo`](https://explorer.solana.com/tx/KrkAYoxqCdhDS1b2Hnrn59hyxE246eN5PXjBudLa9omtVCwqgCtY8KGhJD4zg5dU2JxTmfg8yuesJYy17GrUrXo?cluster=devnet) | capacity → 1,000.25 |
| 2nd attempt, 00:14:53 — swap 0.01 TEST BTC → 649.35 tUSDC | [`2XsQA2…Bh8j`](https://explorer.solana.com/tx/2XsQA2xpxND4vhxMNrUJLQ2ZKkVu3wy1nza6sAgqFDeMsGJZzSPBWfhpZEvxSYLyrPuMhBq7RwD49aKVgKXvBh8j?cluster=devnet) | confirmed |
| 2nd attempt, 00:14:56 — open long 649.35 tUSDC → **0.9965025 SXP** | [`mpQgZ7…93GV`](https://explorer.solana.com/tx/mpQgZ7rmgNy28NQG7mmuTLGBttBSYzKRH9Yty4aJkcyUpYBWpjgTFyLf2UayciS2AJpXXD7k2bnkW4a2e4P93GV?cluster=devnet) | confirmed; UI notice „Complete valueless test route confirmed: 2XsQA2xp…gKXvBh8j + mpQgZ7rm…2e4P93GV” |

Epoch #2 after the UI route: long principal 747.476625 tUSDC, opposing collateral 1,100 tUSDC, capacity left 352.523375 tUSDC. The return leg (SXP → tUSDC → TEST BTC) is only possible after settlement (≥ 2026-09-11 03:48:38 UTC); it is proven for epoch #1 in A1 and is scheduled for the separate test wallet on epoch #2 (see the A1-style command; the burner's SXP cannot be redeemed because its key was discarded by design).

Screenshot (no keys visible): `docs/evidence/2026-09-11-ui-devnet-mode.png`.

## A8 — Public package v2 and documentation (11 September 2026)

- Source package: `SpyON_Public_Review_Package_2026-09-11.zip`, built by `scripts/build-public-package.sh` from git-tracked files only (no `target/`, no `.env*`, no keys), secret-scanned (keypair arrays, private-key markers, token shapes, key file names). Its SHA-256 is published **next to it** at https://spyon-sxp500-demo.pages.dev/SpyON_Public_Review_Package_2026-09-11.sha256 (and in the Trello card / MASTER_PROJECT.md), never inside the package itself — a hash written inside the archive it describes could never be correct.
- Reviewer brief v2 (EN): https://spyon-sxp500-demo.pages.dev/SpyON_Grant_Reviewer_Brief_EN_v2.pdf (source `docs/brief/`).
- This report: https://spyon-sxp500-demo.pages.dev/DEVNET_EVIDENCE_2026-09-10.md · previous: https://spyon-sxp500-demo.pages.dev/DEVNET_EVIDENCE_2026-08-27.md · Devnet mode guide: https://spyon-sxp500-demo.pages.dev/DEVNET_MODE.ro.md · UI screenshot: https://spyon-sxp500-demo.pages.dev/2026-09-11-ui-devnet-mode.png
- The public demo itself stays the browser-only mock (built with `VITE_PROTOCOL_MODE=demo`, burner wallet disabled). The 27 August archives and their hashes are untouched.

### A8 published and verified live (11 September 2026, 01:40 UTC)

Deployed to Cloudflare Pages with Cristi's explicit approval (`wrangler pages deploy`, project `spyon-sxp500-demo`, branch `main`). Every public URL returns HTTP 200 on the production domain and both package hashes were re-verified **by downloading from the live site**:

| Published URL (https://spyon-sxp500-demo.pages.dev/…) | Check |
|---|---|
| `SpyON_Public_Review_Package_2026-09-11.zip` | downloaded from the live site → its SHA-256 equals the `SpyON_Public_Review_Package_2026-09-11.sha256` published next to it. The hash is deliberately **not** written inside this file, because this file ships inside that package; take it from the `.sha256` URL, from the Trello card, or from `MASTER_PROJECT.md`. |
| `SpyON_Public_Review_Package_2026-08-27.zip` (kept) | downloaded → `sha256 da4878af129a90fdc304bdc1c8b64b420b282b9297631ca2f601162195538342` = unchanged since 27 August |
| `SpyON_Grant_Reviewer_Brief_EN_v2.pdf`, `SpyON_Grant_Reviewer_Brief_EN.pdf`, `SpyON_Whitepaper_RO.pdf` | 200, `application/pdf` |
| `DEVNET_EVIDENCE_2026-09-10.md`, `DEVNET_EVIDENCE_2026-08-27.md`, `DEVNET_MODE.ro.md` | 200, `text/markdown` |
| `2026-09-11-ui-devnet-mode.png` | 200, `image/png` |

The public site itself is still the browser-only mock (`VITE_PROTOCOL_MODE=demo`, burner wallet compiled out): the badge reads `Public demo · browser-only`, no wallet transaction is requested. Its **Reviewer kit** now links to the v2 brief, the v2 source package, this evidence report and the Devnet-mode guide.

## A6 update — Pyth without an API key, proven on localnet with real data (11 September 2026, 01:52–02:00 UTC)

The earlier Hermes blocker has a way around it that needs no account: **the Pyth Solana Receiver's sponsored price accounts are readable from any Solana RPC**, so the price can be taken on-chain instead of over HTTP. New code: `parsePriceUpdateAccount` / `pythPriceAccountSeeds` in `scripts/pyth-observation.ts` (3 unit tests) and `scripts/publish-pyth-onchain-observation.ts` (`npm run oracle:publish:onchain`), which verifies the feed id against the on-chain SpyON config, normalises the exponent to E6 and hashes the exact account bytes as the source proof.

Feeds located on Devnet under the push-oracle program `pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT`:

| Feed | Price account (shard 0) | Observed |
|---|---|---|
| `Equity.US.SPY/USD` (`19e09bb8…1cd5`) | `9owhtgrdLiUMAH9JKxYFt5pUY4Luy4EzzLhdcWPVuDyy` | 750.525080 USD, confidence 3 bps, published 2026-07-02 — Devnet's equity feed is **not** maintained |
| `Crypto.BTC/USD` (`e62df6c8…5b43`) | `4cSM2e6rvbGQUFiJbqytoVMi5GgghSMr8LwVrT9VPSPo` | 76,896.843592 USD, confidence 1 bps, refreshed every few minutes |

Localnet lab (`scripts/pyth-localnet-lab.sh`, log `docs/evidence/2026-09-11-pyth-localnet.log`), each run on a fresh ledger and a fresh SpyON instance:

1. **Accepted** — a real, fresh Pyth BTC/USD observation passed every guard (freshness, confidence 1 bps against a 100 bps ceiling, monotonic publish time, move breaker) and was written to the oracle account.
2. **Refused** — the real official SPY observation was rejected with `StaleOracle` (6012), because Pyth's Devnet copy of the equity feed is over two months old. The guard behaving correctly on genuine data.
3. **Refused** — publishing a feed other than the one the instance was initialised with is rejected before any transaction is built; the feed id is fixed at initialisation.

The lab widens `max_price_age_seconds` to a documented 600 s, because Devnet's sponsored feeds refresh every few minutes rather than every minute. `MAX_PRICE_AGE_SECONDS` is now an explicit initializer parameter that still defaults to 60 and is **locked to 60 on Devnet**; production keeps 60 and posts its own price update immediately before use, which is how Pyth's pull model is meant to work.

**A6 therefore stays open.** What remains is publishing observations on the project's own Devnet instance, and that needs a decision rather than code: the live instance is bound to the documented test feed id, immutable after initialisation, so the official US-500 feed requires a fresh instance whose public addresses would all change — and even then, Devnet's SPY feed is too stale to pass a 60-second window.

## Test-harness defects found and fixed (11 September 2026, 02:03–02:09 UTC)

- **Commitment race in the DeFi V2 end-to-end test.** Settlement was acknowledged at `processed` while the SDK reads at `confirmed`, so the following claim could execute against pre-settlement state and fail with `EpochNotSettled`. It passed on an idle machine and failed on a loaded one. Reproduced on the unmodified baseline commit first, to prove it was not a regression from this phase's work, then fixed by running the test's provider at `confirmed`. Nothing was disabled.
- **Type checking relied on a hoisted `@types/node` 12**, which does not understand `node:` protocol imports; it is now declared at the root at version 24.
- The tested `@solana/web3.js` (1.98.4) and `@solana/spl-token` (0.4.14) are pinned exactly, so adding a UI dependency cannot silently move the versions the protocol tests run against.

Final suite run, 02:08:59 UTC: **35 TypeScript, 5 Rust, 10 local-validator tests passing, 0 failing, typecheck green.** Log: `docs/evidence/2026-09-11-final-suites.log`.

## A6 final position — why it cannot be published on Devnet today (11 September 2026, 02:20 UTC)

New read-only checker: `npm run oracle:pyth:readiness` (`scripts/pyth-readiness.ts`) applies every guard the contract would apply, against the live config and the current on-chain Pyth price, and signs nothing. Output for the official US-500 feed against the live Devnet instance is saved at `docs/evidence/2026-09-11-pyth-readiness-spy.json`:

| Guard | Verdict |
|---|---|
| Pyth price account exists | pass — `9owhtgrdLiUMAH9JKxYFt5pUY4Luy4EzzLhdcWPVuDyy` |
| Confidence | **pass** — 3 bps against a 100 bps ceiling |
| Move breaker | **pass** — 650 → 750.525080 is 1,546 bps against a 2,000 bps ceiling |
| Freshness | fail — Pyth's Devnet copy is 6,093,092 s old (2 July 2026) |
| Monotonic publish time | fail — same cause |
| Feed id bound to config | fail — the config holds the August test id, fixed at initialisation |

So the price itself would be accepted; the data is simply not being maintained on Devnet. Three independent facts close the door today:

1. **Hermes price routes now require an API key.** `/v2/updates/price/latest`, `/api/latest_vaas` and the beta host all return HTTP 401; only the `/v2/price_feeds` metadata route is open. Without signed update data, nobody can refresh a Devnet feed through the pull oracle.
2. **Devnet's sponsored feeds are idle.** A sweep of all 1,892 feeds in Pyth's catalogue found **zero** with an on-chain observation newer than 120 seconds; BTC/USD last published at 01:51:34 UTC and then stopped. A 60-second window cannot be met by any feed on Devnet right now.
3. **The feed id is immutable after initialisation**, so the live instance stays bound to its documented August test id; only a fresh program instance could carry the official one, and that instance would still be waiting on point 2.

What is proven instead, with real Pyth data and no API key: the adapter, the guard behaviour (accepted when fresh, `StaleOracle` on the real official feed, refusal on a feed that is not the configured one) and the unit tests — see the A6 update above. A6 therefore stays **unticked**: the remaining half depends on Pyth's Devnet publisher resuming, or on a decision to run a fresh instance. The readiness checker reports the moment it becomes possible.

## A6 COMPLETED — real Pyth observations published on Solana Devnet, through the guards (11 September 2026, 03:11–03:17 UTC)

The blocker was never the code: it was that the project's production instance is bound, immutably, to its August test feed id, and no feed in that instance's price band is maintained on Devnet. The way through is a **separate oracle-lab instance on Devnet**, deployed alongside the production one and touching nothing it owns.

- Lab program: **`Fms8JTnUqVF7Mt9RRHwv2rq7Qc56mxhkKCAC2kW6bBJn`** — the same source, built with its own program id, deployed with [`PbfySR…CwC4A`](https://explorer.solana.com/tx/PbfySRKhAYAGTkTq6geW2MwY6nHdy8ENZgiRUT8d5k86dqmfdGh7Hj1Mj8MAXaQiTWzhM9FLjRGjPeCQf2CwC4A?cluster=devnet) using 3.17 SOL of valueless Devnet rent. Working tree: `08_ORACLE_LAB_DEVNET`.
- Config `9Ryyq73qzZujEH65DZfBT6TTP9SHUWk6PWvGoE1gXPnt`, oracle `3sRAvZqhk14BNhmuzkYxgWaUB5THZNj2kVgiFyT48XV5`, bound at initialisation to the **documented test feed** `Crypto.BTC/USD` = `e62df6c8…5b43`. It is the documented-test-feed option the task allows, chosen for one stated reason: on Devnet, Pyth maintains its crypto feeds and has not updated its equity feeds since 2 July, so `Equity.US.SPY/USD` cannot satisfy any freshness window.
- **Guards left at their production settings**: `max_price_age_seconds` 60, `max_confidence_bps` 100. Nothing was widened to make this pass.
- **No API key.** Prices are read from the Pyth Solana Receiver account `4cSM2e6rvbGQUFiJbqytoVMi5GgghSMr8LwVrT9VPSPo`; the source hash committed on-chain is the SHA-256 of those exact account bytes.

| # | What | Devnet signature | Guard evidence |
|---|---|---|---|
| 1 | Real Pyth observation **accepted**, 76,721.366143 USD | [`3hV4af…xh5r`](https://explorer.solana.com/tx/3hV4afpoUmxgeZScjQm16GJEo7KvzyKhXb7ewBJEVoPn6nGyRemzAC8TGiCiaLsZ2DLHXX8zJGrUNvByvxczxh5r?cluster=devnet) | age **23 s** of 60, confidence **2 bps** of 100; oracle sequence 1; stored price, confidence, publish time and source hash identical to the Pyth account |
| 2 | The same observation replayed once aged, **refused on-chain** | [`5k27gc…9RHU`](https://explorer.solana.com/tx/5k27gcb5f4FQAa5MBkPTVnEbQE5iJrrf6oQ16AzoNE6MdgNc6ZScS8zZthZFvnuvGJwgvRb18pyvUNYjrZii9RHU?cluster=devnet) | age 67 s of 60 → `StaleOracle` (6012), sent without preflight so the refusal is a public failed transaction |
| 3 | Next real Pyth observation **accepted**, 76,779.514797 USD | [`pt1Q6G…qrLh`](https://explorer.solana.com/tx/pt1Q6GEhJqWAUJM3ewH8XjrVZhQqn2BLnmmTViVhTqgfQvdZLCTCEutyJWEaYAVPtS6poHc71qbo7XwtPbGqrLh?cluster=devnet) | age **8 s** of 60, confidence 2 bps; oracle **sequence 2**, so monotonic publish time and the move breaker also passed against the previous real observation |

Tooling now in the canonical repo: `npm run oracle:publish:onchain` (publish once), `npm run oracle:publish:when-fresh` (wait for Pyth's next update, then publish inside the window — Devnet's sponsored feeds refresh roughly every five minutes, which the poll logs show), `npm run oracle:pyth:stale-refusal` (record the refusal), `npm run oracle:pyth:readiness` (read-only pre-flight against any instance). Raw output: `docs/evidence/2026-09-11-pyth-devnet-publish.json`, `-publish-2.json`, `-stale-refusal.json`.

**The production instance is untouched.** It keeps its addresses, its epochs, its published links and its manual test observation; the lab instance exists only to prove the Pyth path on Devnet. Moving production onto a Pyth feed is a separate decision, and the honest constraint stands: an equity feed for the US-500 reference is not maintained on Devnet today, so a production-grade oracle for that reference belongs to the mainnet gate (G2), with Pyth's pull model posting its own update immediately before use.

## Epoch #2 settled and redeemed by the separate test wallet (11 September 2026, 04:09 UTC)

The second full round trip, this time signed by the separate test wallet rather than the admin, completing what A1 proved for epoch #1. Scheduled in advance and executed automatically once the contract's own time lock expired at 03:48:38 UTC.

| Step | Finalized Devnet transaction |
|---|---|
| Fresh manual test observation, 650 USD | [`4DcBoD…fyV4J`](https://explorer.solana.com/tx/4DcBoDFZukZ3E1whwE9R2rvmgfwBUTb54EUSuw9wkbWkiLtMNnLPxe5WzbXKjfHYU87FRgEio1QZDsRvhHjfyV4J?cluster=devnet) |
| Epoch #2 settled | [`vGk3Vp…6NNj`](https://explorer.solana.com/tx/vGk3VpCUaKBFmcUz2krHfuvsn6aHhE3Q925UsYEbFK3m9sXiALziEs9nPkqJ7yGaLKSng3vsNKYL8ytgc1s6NNj?cluster=devnet) |
| **Test wallet** burns 0.153461538 SXP → 99.500625 test USDC | [`3Km2VB…pZaqJ`](https://explorer.solana.com/tx/3Km2VBh13bkdQeZzsaAMkmEzLom78EXH7vwESMMrbJkL89SbRT12MSeVQL5MYp9JDFmANeYgMNQ3hJkgdi3pZaqJ?cluster=devnet) |
| **Test wallet** swaps 99.500625 test USDC → 0.00152924 TEST BTC | [`567HHc…1END`](https://explorer.solana.com/tx/567HHcUifBjXcnaE25EntCeKfeGy7qma2HV9yutb6kVMVnW731zqeuTTbe5taAhBwagiK1jCp3Nw1AReR99f1END?cluster=devnet) |

Settlement arithmetic: start and end price both 650.000000, so `settled_return_bps` is 0; the long pool stayed at 747.476625 test USDC and the opposing pool at 1,100. The test wallet's SXP went to 0 and its TEST BTC from 0.03015369 to 0.03168293, the same 0.00152924 the quote promised.

Epoch #2's SXP mint keeps a supply of 996,502,500 base units. That is the browser burner wallet's position from A7, and it is unredeemable on purpose: the burner key lives only in the page and was discarded when the tab closed. Valueless test tokens, deliberately stranded, recorded here so the supply figure is not mistaken for an accounting error.

Raw output: `docs/evidence/2026-09-11-epoch2-settle-redeem.json`.
