Recover Wormhole guardians' uncompressed secp256k1 public keys from VAA signatures, then submit a guardian-set update to the Stacks Wormhole Core contract.
The Stacks Wormhole Core contract stores each guardian's uncompressed
secp256k1 pubkey, because Clarity has no built-in for
compressed_pubkey → uncompressed_pubkey. The governance VAA only contains
ETH addresses, so we have to source the uncompressed pubkeys ourselves by
recovering them from real signatures.
Full details: stacks-network/stacks-core#7153.
| Part | Description | Status |
|---|---|---|
| 1 | Fetch VAAs and recover uncompressed pubkeys | ✅ |
| 2 | Build and sign the Stacks update-guardians-set transaction |
✅ |
| 3 | Broadcast the signed transaction | ✅ |
npm install
npm run recoverAll settings are read from the environment. Resolution order is
process.env > .env file > built-in default. Copy .env.example to
.env and uncomment the lines you want to override.
| Variable | Purpose | Default | Sensitive |
|---|---|---|---|
OPERATOR_PRIVATE_KEY |
Stacks operator key (hex, no 0x). Required for build-tx. |
— | Yes |
STACKS_API_URL |
Stacks API base URL. | https://api.hiro.so |
No |
STACKS_NETWORK |
mainnet or testnet. |
mainnet |
No |
STACKS_TX_FEE |
Explicit fee in uSTX for build-tx. CLI --fee overrides. |
(estimator) | No |
WORMHOLESCAN_API_URL |
Wormholescan base URL. | https://api.wormholescan.io |
No |
GUARDIAN_SET_INDEX |
Pin recovery to a specific set index. | (auto-detect) | No |
.env is gitignored. .env.example is committed as a template.
| Command | Description |
|---|---|
npm run recover |
Recover keys from live wormholescan data. Generates guardians.json. |
npm run recover -- --json |
Same, but emit JSON to stdout (logs to stderr). |
npm run build-tx |
Build and sign the update-guardians-set tx. Generates update-guardians-set.tx and update-guardians-set.summary.json. |
npm run build-tx -- --vaa <file|hex> |
Same, but use the supplied upgrade VAA instead of fetching the latest. |
npm run build-tx -- --fee <uSTX> |
Same, but pin an explicit fee (overrides STACKS_TX_FEE). |
npm run build-tx -- --guardian-pubkey N:0xHEX |
Same, but supply a pubkey for guardian index N (verified locally against the VAA's address). Repeatable. |
npm run build-tx -- --unsafe-skip-preflight |
Bypass safety checks; pads missing guardians with zeros. Offline-inspection only — not broadcastable. |
npm run broadcast-tx |
Broadcast the signed tx to the Stacks API. |
npm test |
Run the test suite against checked-in fixtures (no network). |
npm run fetch-fixtures |
Refresh tests/fixtures/data.json from wormholescan. |
npm run typecheck |
tsc --noEmit. |
npm run --silent recover -- --json 2>/dev/null | jq -r '.guardians[].uncompressedPubkey'NOTE: --silent suppresses npm's own banner
End-to-end flow once the new guardian set is live:
npm run recover: writesguardians.jsonwith all 19 recovered pubkeys.npm run build-tx: fetches the upgrade VAA, runs pre-flight checks, signs the tx, writesupdate-guardians-set.txandupdate-guardians-set.summary.json.- Inspect the signed tx:
npx @stacks/cli decode_transaction "$(cat update-guardians-set.tx)" npm run broadcast-tx: broadcasts the signed tx and prints the explorer URL.
build-tx aborts before signing if any pre-flight check fails:
guardians.jsonis missing or incomplete (you can supply missing keys with--guardian-pubkey N:0xHEX).- A recovered or supplied pubkey doesn't derive to the address at the same index in the upgrade VAA.
- The contract's
active-guardian-set-idis not exactlynewSetIndex - 1(catches "already applied" or "out of order").
The signed tx is pinned to AnchorMode.OnChainOnly so it settles in an anchor block, not a microblock.
{
"guardianSetIndex": 5,
"recoveredAt": "2026-04-29T18:42:11.123Z",
"stats": { "vaasInspected": 4321, "vaasUsed": 4100, "pubkeyMismatches": 0 },
"guardians": [
{
"index": 0,
"uncompressedPubkey": "0x...", // 64 bytes (X || Y)
"uncompressedPubkeyWithPrefix": "0x04...", // 65 bytes (0x04 || X || Y)
"ethAddress": "0x...", // 20 bytes
"sourceVaa": "4/.../804767",
"signatureCount": 3230
}
],
"missingGuardianIndices": []
}- Depends on wormholescan's
/api/v1/vaasfeed. - Only recovers keys for guardians who have signed a VAA in the recent window.
See ARCHITECTURE.md for the module map, protocol notes, and test layout.