asteria-game: one-shot admin_mint policy parameterised on seed UTxO
Closes the always-true admin_mint placeholder PR #67 ships. The
bootstrap's "no double-mint on container restart" contract was
previously enforced only by the off-chain isAlreadyDeployed
check; this commit puts the contract on chain.
Aiken side (components/asteria-game/aiken/validators/admin_mint.ak):
- validator admin_mint(seed: OutputReference) succeeds iff the
tx consumes the seed AND the bundle minted under this policy
is exactly [(asteriaAdmin, 1)]. Seed consumption is permanent
on Cardano UTxO, so admin_mint can fire at most once across
all chain history.
- aiken/plutus.json regenerated; admin_mint now declares one
parameter "seed". All 91 existing tests pass.
- apply-params.sh / plutus-applied.json no longer in the build
path — Haskell applies the seed at runtime.
Haskell side:
- new Asteria.Validators.applyScripts: takes a seed TxIn and
returns AppliedScripts { adminMint, pellet, asteria,
spacetime } with both Plutus scripts and ledger-side hashes.
Built on plutus-ledger-api's uncheckedDeserialiseUPLC +
UntypedPlutusCore.applyProgram + serialiseUPLC. Hash
dependencies are threaded in declaration order
(admin_mint → pellet → asteria → spacetime).
- new Asteria.Deploy module: read/write
/asteria-deploy/seed.json. Bootstrap is the only writer;
player + invariant are readers. ASTERIA_DEPLOY_DIR env var
overrides the path.
- BootstrapMain: reads seed.json on startup. If present,
re-derives the same scripts (deterministic). If absent,
picks a fresh wallet UTxO via pickWalletUtxo, writes the
seed to disk BEFORE submitting the deploy tx (durable order
so a crash leaves either no file or a consistent file).
isAlreadyDeployed simplified to "any UTxO at the per-deploy
asteria addr" — under the one-shot policy at most one such
UTxO can ever exist.
- PlayerMain + InvariantMain: read seed.json, applyScripts,
use AppliedScripts.as{Asteria,Spacetime,Pellet,AdminMint}
{Script,Hash} in place of former top-level constants. If
seed file missing, emit asteria_*_seed_missing_<id>
sdkUnreachable and exit cleanly (correct: bootstrap hasn't
run yet on this cluster).
Compose:
- new asteria-deploy named volume mounted at /asteria-deploy
on the asteria-game container.
Locally validated end-to-end on testnets/asteria_game/:
- pre-bootstrap: asteria_invariant_seed_missing_admin_singleton
fires (correct)
- bootstrap cold: asteria_bootstrap_seed_picked +
asteria_bootstrap_seed_persisted + asteria_bootstrap_asteria_created
(asteria addr is now per-seed: 77a02b8e... instead of the
previous hardcoded 0824601a...)
- container restart: asteria_bootstrap_seed_reused +
asteria_bootstrap_already_deployed short-circuit
- spawn: asteria_player_ship_spawned_1 succeeds with the
seed-derived validators
- consistency: ship_counter=1, ship_token_count=1, hit=true
- admin_singleton: count=1, hit=true (post-bootstrap and
post-spawn)