# Signer Sidekick — standalone dApps

Seven single-file HTML pages (6 dApps + a hub) that walk the **on-chain / wallet steps** of bringing a Stacks PoX-5 signer online and staking to it, plus one offline key helper. Each file is self-contained: open it in a browser, no build step, no server. They reuse the exact contract interfaces, chain IDs, hashes, and pre-sign verification from [`stx-labs/signer-sidekick`](https://github.com/stx-labs/signer-sidekick) where it provides them, and read the pox-5 contract directly for the staking flow (which Sidekick deliberately scopes out).

They talk to your wallet over SIP-030 (`@stacks/connect` 8.2.6) and to the public Hiro API for reads.

## Network switch

Every network-aware page (1, 3, 4, 5, 6) has a **Testnet / Mainnet** switch. Testnet is the default; selecting Mainnet is deliberate (red banner, Tier 2 badge) but fully allowed, including the read-only analysis page. The PoX-5 boot contract is pinned per network and checked against the live node before anything is signed:

- Testnet: `ST000000000000000000002AMW42H.pox-5` on the **primary Hiro testnet** (Leather's built-in Testnet; chain `0x80000000`, API `https://api.testnet.hiro.so`). The old `pox5-testnet` custom network is retired.
- Mainnet: `SP000000000000000000002Q6VF78.pox-5` (chain `0x00000001`) — same boot-code address as `pox-4`, only the trailing version differs.

### URL parameters

Each page carries its state in the URL as `?chain=testnet|mainnet&api=<node>`, so a refresh keeps the selected network and the switch updates the URL in place. Add `&api=https://your-node` to read against your own Stacks node (handy for signers verifying on-chain data). The `api` override affects read calls only; signing always goes through the wallet on its network with the pinned contract and post-conditions.

## Two separate keys — keep them apart

- **Signer key** — a secp256k1 key that lives on the signer node. Generated by dApp 2 (or, preferred, on the node host). The chain only ever needs its **public** key.
- **Manager-admin / staking wallet** — the funded key that deploys the manager and broadcasts `register-self`; keep it in a hardware wallet (Ledger) on mainnet. Connected in dApps 1/3/4; it becomes the manager's admin on deploy (`map-set admins tx-sender`). Never paste the signer private key into dApps 1/3/4/5/6 — they only need the public key.

## Order of operations

| Step | Tool | Key used | Signs? |
|---|---|---|---|
| 1 | **01 Connect + Preflight** | manager-admin | no (reads) |
| 2 | **02 Signer key helper** (or node-host CLI) | signer | no (offline gen) |
| — | *On signer host:* configure signer with the key | signer | — |
| 3 | **03 Deploy signer-manager** | manager-admin | yes (deploy) |
| — | *On signer host:* `stacks-signer generate-staking-signature --signer-manager <manager> --auth-id <id> --json` | signer | produces grant JSON |
| 4 | **04 Register-self** (paste grant JSON) | manager-admin | yes (contract call) |
| 5 | **05 Stake** (delegator or signer's own STX) | staker | yes (contract call) |
| 6 | **06 Rewards + status** | none | no (reads) |

## register-self, in one call

`register-self(signer-manager, signer-key (buff 33), auth-id uint, signer-sig (buff 65))` does three things atomically: `authorize-admin`, `pox-5 grant-signer-key` (recovers the pubkey from `signer-sig` over the live grant-message hash, asserts it equals `signer-key`, records the one-time grant), then `pox-5 register-signer`. "A new grant" = re-running `generate-staking-signature` with a new `auth-id` (and new `signer-key` if rotating) — no redeploy.

## Staking — no pool manager, no aggregate-commit

This pox-5 variant has **no `delegate-stx` / `stack-aggregation-commit`**. Anyone stakes to a signer by calling `pox-5 stake` and naming that signer's `signer-manager`; the STX locks in place (solo-style) effective next cycle, and the contract aggregates `signer-delegated-per-cycle` continuously. A signer staking their own STX uses the same call pointed at their own manager. `stake(signer-manager, amount-ustx, num-cycles, start-burn-ht, signer-calldata)`:

- **num-cycles** — 1 to 96 (`MAX_NUM_CYCLES`), not the pox-4 cap of 12.
- **start-burn-ht** — must fall in the current reward cycle; the stake always starts next cycle. dApp 5 auto-fills the current burn height (refreshed at broadcast); you don't set a date, `num-cycles` sets duration.
- **signer-calldata** — optional `{ pox-addr:{version,hashbytes}, max-fee }` (consensus-serialized) = your BTC reward address preference; `none` clears it. dApp 5 builds it from a pasted BTC address via `@stacks/stacking`'s `poxAddressToTuple`.

Contract gates (enforced regardless of the UI): the signer must be registered with a valid grant, not during the prepare phase, and your balance must cover the amount. An individual stake below 50,000 STX still counts toward the signer's aggregate — the minimum applies to the signer's total.

### Currently-staked line

dApps 4 and 5 show, under the signer-manager input, a non-blocking line that slow-loads the STX currently staked to that signer (`get-amount-delegated-for-signer` for the upcoming cycle) and whether it meets the 50,000-STX minimum. dApp 5 also shows an advisory registration check. Neither blocks the rest of the page.

## Deploy: reference contract & hashes

dApp 3 links the repo's reference contracts and shows raw + canonical SHA-256 of the pasted source (canonicalization ported from `manager-adapter.ts`):

- Mainnet reference is the signer-sidekick generated mainnet `signer-manager.clar`, pinned to the immutable [PR #9 head commit `214c67e`](https://github.com/stx-labs/signer-sidekick/blob/214c67eae2f1ce1c3c818ab6528ce4f2e1bdc22a/contracts/reference-manager/generated/mainnet/signer-manager.clar): canonical `004da6bde5f91b9cdf555a020494cab73d29cc75733ad0c05e4f4b32a94e251b`. (That repo file's own raw SHA-256 is `05aaf409ed285f02d8b6d5d540f94feb8baea139a14263b7e7de7ba9f054d3c5`; the app pins raw `48d22cf832f2b61118327265b6b7f303c911a98bdb5af2a54a931a1cf432e316` from a reformatted copy — both share the canonical, which is the reliable identity.)
- Testnet reference is no314/stx-fan `signer-manager`, pinned to the immutable [PR #2 head commit `efe2640`](https://github.com/no314/stx-fan/blob/efe2640a629ce6a25270f82bfe13d710fe07b699/signer/contract/signer-manager) (canonical `97e003554c90ff8cefe0a17ee7f52e47fd42464a464ff02f666b117740e84214`, raw `b9c49ce03453a734fed8cf0d9202adb807d53d585108cdf4839be86728693e76`). This is the same `claim-staker-rewards`→`{ earned, withdrawal-request }` change as mainnet PR #9, with testnet principals: pox-5 `ST000…AMW42H` and sBTC token/registry/withdrawal at `SN3VMHXEN64ZZF71JQ5VESXDWTR301XTTXGF4J8F1` — matching the primary testnet node config's `pox_5_sbtc_contract`.
- dApp 3 auto-flags a match on raw **or** canonical. The difference between networks is the embedded PoX-5 boot principal and sBTC contracts — verify the embedded sBTC contracts exist on your target network.

### How the source check works, and its limits

dApp 3 compares the source you paste against the pinned reference two ways and flags a match on **either**:

- **Raw SHA-256** — hash of the exact bytes you paste. Any byte difference (a space, a newline, line endings) changes it.
- **Canonical SHA-256** — hash after stripping `;;` comments and collapsing runs of whitespace to single spaces (string literals preserved). Survives re-indentation, line-wrapping, and blank-line changes, but is still sensitive to spacing *around* parentheses.
- **Structure SHA-256** — a formatting-independent token hash: it tokenizes the Clarity (parentheses, braces, atoms, string literals) and drops **all** whitespace, comments, and commas. A structure match means the code is identical even after `clarinet format` or any re-indentation. This is the check that catches a reformatted-but-identical deployment; it does not cover comment text.

The check is deliberately narrow, and will report **"unverified" for a contract that is logically identical** in these cases:

- **Reformatting.** The canonical hash collapses whitespace *runs* but does not normalize spacing *around delimiters*: `(ok true)` and `( ok true )` canonicalize differently, so `clarinet format` (and similar tools) change both the raw and canonical hashes. The **Structure SHA-256 covers this** — it token-matches regardless of formatting, so a reformatted-but-identical contract still shows a match (as "structure match — reformatted"). Comment-only differences are also ignored by the structure hash.
- **Embedded principals.** The hash covers the whole source, including the embedded PoX-5 boot address and sBTC contract principals, so testnet vs mainnet references necessarily differ, and any principal edit changes the hash.
- **Advisory, not on-chain verification.** The check compares your pasted text to a pinned reference hash. It does not read the deployed contract's on-chain source, and does not use the same algorithm/canonicalization as Clarity's `contract-hash?` (which is `SHA-512/256` over the deployed bytes — a different hash function *and* a different normalization, so it will never equal these SHA-256 values). Treat a match as "this text equals the reviewed reference," not as proof of what is deployed.

The Structure SHA-256 handles reformatted deployments directly. For an ecosystem-standard match to `clarinet format` + `SHA-512/256` (what Clarity's `contract-hash?` returns, per draft SIP-043), a WASM formatter would be needed — tracked in `IMPROVEMENTS.md`.

**Deploy payload / Ledger (temporary).** The manager uses Clarity-4+ constructs (`as-contract?`), so it must deploy as a Clarity-6 VersionedSmartContract (`0x06`, `clarityVersion: 6`). The Ledger Stacks app can't sign `0x06` yet, and omitting the version (`0x01`) makes the node analyse under the network's default Clarity (v3) → `as-contract?` unresolved → deploy aborts. So: **deploy from a software wallet**, then use dApp 3's **Admin rotation** to move admin to your cold/Ledger key (`update-admin` is a normal `0x02` call the Ledger signs). Once the Ledger app supports Clarity 6, deploy straight from hardware.

## Running them

1. Install **Leather** (the only supported wallet — see `xverse.html` for why Xverse can't be used). For testnet, select Leather's built-in **Testnet** network (the primary Hiro testnet, `https://api.testnet.hiro.so`).
2. Open `index.html`, or any page directly. Each page loads its Stacks libraries from a pinned esm.sh URL, and **falls back to the vendored copies in `js/` on any failure** (CDN unreachable or a broken upstream publish), so the apps keep working offline and survive npm/CDN breakage. `@stacks/connect` loads directly from `js/` because esm.sh currently cannot resolve its mixed v6/v7 dependency graph.
3. Keep the `js/` folder next to the HTML files. It contains self-contained bundles (`connect.js`, `transactions.js`, `stacking.js`, `walletsdk.js`, `secp.js`) built from the pinned versions; they are the offline fallback. To refresh them, re-bundle the pinned versions with esbuild and replace the files.
4. Serve over `http://` (`python3 -m http.server` in this folder) rather than `file://` — some wallets restrict `file://` origins, and `file://` also blocks the `js/` module fallback.
4. Fund the relevant address before deploying/registering/staking (faucet on testnet).

## Security tier

- **Testnet → Tier 1** (faucet tokens, no real value). **Mainnet → Tier 2** (real value; staking locks STX).
- **Structural invariants on at every tier:** deny-mode post-conditions on every call (deploy, register-self, and `stake` move no assets to another principal — staking locks in place — so 0 conditions); PoX-5 and manager identity pinned/verified against the live network before signing; keys stay in the wallet.

### How this differs from full Sidekick

Sidekick's backend seals each transaction into an integrity-checked intent and the browser only executes it; it also **does not do end-user staking at all**. These standalone dApps reconstruct calls client-side and pin/verify contract identity in the page, and add the staking flow directly against pox-5. dApp 4 replicates Sidekick's full pre-sign verification (live message-hash recovery, signature validity, grant-not-used). The trust root is the pinned value you enter, not a backend seal. For unattended or multi-operator mainnet use, run Sidekick itself.

## Mainnet promotion checklist (Tier 1 → Tier 2)

- [ ] Switch the selector to **Mainnet** deliberately; confirm the wallet is a mainnet (`SP…`) account on a Ledger.
- [ ] PoX-5 is pinned to `SP000000000000000000002Q6VF78.pox-5` and enforced (blocks on mismatch) in dApps 1/4/5/6.
- [ ] In dApp 3, confirm the pasted source's raw **or** canonical SHA-256 matches the pinned mainnet reference (auto-flagged) and your own `manager render` manifest.
- [ ] For staking, double-check `num-cycles`, the reward BTC address, and that you intend to lock real STX for that duration.
- [ ] Post-condition mode is **deny** on every call (already enforced); add explicit-amount post-conditions to any future asset-moving call you introduce.
- [ ] Remove every testnet shortcut; verify the signer key is stored per the signer guide, isolated from the Ledger key.
- [ ] Name an owner for contract-level risk (audit, oracle trust, front-running/MEV, governance).

## Files

- `index.html` — hub linking all dApps in flow order (1–6).
- `01-connect-preflight.html` — connect admin wallet, verify chain/address, balance, PoX-active, readiness.
- `02-signer-key-helper.html` — offline BIP39 seed + account-0 address/key/pubkey for the signer, plus an advanced/testing **signer grant JSON generator** (reproduces the pox-5 grant hash and signs it → the JSON dApp 4 expects; network-specific).
- `03-deploy-manager.html` — deploy the `signer-manager` (Clarity-6 / `0x06`) from a **software wallet** (Ledger can't sign this payload yet — temporary), with reference-hash check and an **admin-rotation** card (`update-admin`) to move admin to a cold/Ledger key.
- `04-register-self.html` — verify the signer grant against live PoX-5, then broadcast `register-self`; shows staked amount.
- `05-stake.html` — lock STX to a signer via `pox-5 stake`; shows staked amount + registration pre-check.
- `06-rewards-status.html` — read-only registration / grant / delegated-vs-minimum / activity dashboard.
- `07-claim-rewards.html` — claim sBTC yield: `claim-rewards` (pool collect, pox-5 → manager) then `claim-staker-rewards` (per staker, manager → user). Permissionless; cycle picker + progress bar; allow-mode (dynamic reward amounts); collapsed pool-operator per-address staking check (full roster needs an indexer — the public events endpoint times out).
- `05-rewards-status.html` — redirect stub → `06-rewards-status.html` (old name kept for links).

Library pins: `@stacks/connect@8.2.6` (latest, maintained by stx-labs), `@stacks/transactions@7.5.0`, `@stacks/stacking@7.5.0`, `@stacks/wallet-sdk@7.5.0`, `@noble/secp256k1@2.1.0`.
