Merge pull request #128 from utxos-dev/fix/get-wallets-by-tag-url
fix(cardano): correct getWalletsByTag URL path and encode tag
fix(cardano): correct getWalletsByTag URL path and encode tag
Three coupled changes that take the spawn flow from build-only to accepted-on-chain in the antithesis local cluster. `Game.hs` no longer emits an explicit `payTo` for the new PILOT NFT — `balanceTx`'s residual-MA folding (lambdasistemi/cardano-node-clients PR #77) drops the NFT into the player's ADA change output, matching mainnet asteria's 3-output spawn shape. `PlayerMain.hs` calls the new `buildWith` with `boExUnitsMargin = 1.2x` to mirror cardano-cli's submit-time overshoot. Without it the asteria spend script's actual cost on the cluster's submit-time evaluator runs ~751 mem above what the client-side `evalTxExUnits` reports (cardano-ledger version drift between CHaP 2026-02-17 here and cardano-node 10.7.1's CHaP). `configurator.sh` writes Conway-mainnet `maxTxExUnits` (16.5 M mem, 10 B steps) into `alonzo-genesis.json` so the cluster's budget matches what the validator was deployed against on mainnet. `docker-compose.yaml` mounts the script for local iteration without rebuilding the configurator image, and `apply-params.sh` uses `--trace-level silent` so the validator's runtime trace overhead is stripped. Pin bumped to the merged main SHA of cardano-node-clients (f578d6cf...). Verified: asteria_player_ship_spawn_attempted_1 → ship_spawn_built_1 → ship_spawned_1 on a single attempt with no failures, on the local docker-compose cluster.
The endpoint was hitting `api/project-wallet/{projectId}/cardano/tag/{tag}`,
which does not exist on the server — the route is
`api/project-wallet/{projectId}/tag/{tagstring}`. Every call returned 404
HTML even when matching wallets existed.
Also URL-encode the tag so emails and any tag with special characters
(`@`, `/`, spaces, etc.) round-trip safely through the path segment.
Bumped version to 0.2.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
CATS token metadata for Cardano Token Registry
feat: add getProjectWalletsByTag and normalize wallet shape
Main shipped 0.2.1 in #126; this branch's 0.2.1 collides, so bump to 0.2.2. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- Add `getProjectWalletsByTag(tag)` on `WalletDeveloperControlled`, consuming the existing `GET /api/project-wallet/:projectId/tag/:tag` endpoint. URL-encodes the tag and rejects empty/whitespace input. - Add `normalizeWalletInfo` helper that maps the backend's flat `Web3ProjectWallet` response (top-level `pubKeyHash`, `stakeCredentialHash`) into the SDK's nested `MultiChainWalletInfo` shape. Apply it in `getProjectWallet` and `getProjectWalletsByTag`, replacing the unsafe `data as MultiChainWalletInfo` casts that previously left `walletInfo.chains.cardano.*` undefined for callers. Spark is intentionally omitted because the backend doesn't persist Spark public keys. - Drop the dead `walletInfo.chains.spark` gate in `getWallet`. Spark wallets are derived entirely from the mnemonic; the gate previously prevented the spark branch from ever running in production. - Update tests to mock the actual flat backend shape and assert the normalized output. 40/40 passing. Bumps version 0.2.0 -> 0.2.1. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Signed-off-by: Chris Gianelloni <[email protected]>
Flake lock file updates:
• Updated input 'CHaP':
'github:input-output-hk/cardano-haskell-packages/d8156d61840f90f0721c396f0598652f7aaf402a?narHash=sha256-kB2azmnVPcQ4pFBvXCc3iKlMuoLsaPRbVP0LfD5j2Zg%3D' (2026-04-15)
→ 'github:input-output-hk/cardano-haskell-packages/3932849518c1c50018ee3dfba06b391f93a33f76?narHash=sha256-c9oScWEa0q5X0VF7G6mSsOrh%2BglHfwnUGdbHnPTVPVQ%3D' (2026-04-24)
• Updated input 'emanote':
'github:srid/emanote/ce5b43a53e340d86b6efcd93b0c7b8015b83d930?narHash=sha256-W%2BJxQDt4Ia5dNsoKqVP4mita%2BGYBc0j/ANqeoYT8reA%3D' (2026-04-15)
→ 'github:srid/emanote/51993925f8120c6c1fa40b5c6061ac05c459054f?narHash=sha256-k4WxRhYi0n3hheV/DtWV53qTMdA2NhWNEdQ/KjcBXL4%3D' (2026-04-26)
• Added input 'emanote/dpella-jsonrpc':
'github:dpella/jsonrpc/0a708eb6c2744e1d69822d1cb90e9e352455a51b?narHash=sha256-gdBzrXo/tm9Xcs35yYiZkf9OI3S/FEV/5eGY183ywm4%3D' (2026-02-16)
• Added input 'emanote/dpella-mcp':
'github:dpella/mcp/52d13472d23ec11b9f6109f0fbf5159e9fda93da?narHash=sha256-wfh76/wO6j5JCIB%2B%2BvfZn4LaFz1KAdy7hBjaFIpW5ZI%3D' (2026-02-19)
• Updated input 'emanote/ema':
'github:srid/ema/7ff434cf8f494c62de7fe6c1a36d8681929beb93?narHash=sha256-vDhjw%2BCm7HniaiIHirwM0B2yzzLYLO3HHMaZsQRL3uw%3D' (2025-07-22)
→ 'github:srid/ema/e92e52dbefea57de08ef64b4db0ea795170d6b81?narHash=sha256-rMb1a7VzMS2nmfEWAtJEKgZYg9Mc5JdSNK4y%2Bz1S/ec%3D' (2026-04-23)
• Updated input 'emanote/haskell-flake':
'github:srid/haskell-flake/39065472d2587af93a502423276bfb98c2c6fb09?narHash=sha256-Yif99ho8GNgXP0l9vxPHCKi7X16Cf7rwVd%2BHW1cMVeQ%3D' (2025-07-14)
→ 'github:srid/haskell-flake/f52ac89b2232dd50e5d1110416ebc5bbb09265bd?narHash=sha256-Na95Y2awqZsLhFNfBNbLj0hk4zyE3eKUROB2o9Qdqi8%3D' (2026-04-21)
• Updated input 'emanote/heist-extra':
'github:srid/heist-extra/81f1ea0cf1226215430171dbe613a2988c6cc46a?narHash=sha256-ytHgIoRlkI5K0SDq33znlY0wjlqcwoQCe1z9JfHT/Fw%3D' (2025-12-19)
→ 'github:srid/heist-extra/bddc871dd0fe68c6376c6514498320b23ff9320e?narHash=sha256-4ArDqHLR82XJJE9AA3sX1w9eyChY1HRVwAzTYwRdEx8%3D' (2026-04-25)
• Added input 'emanote/nixpkgs-latest':
'github:nixos/nixpkgs/01fbdeef22b76df85ea168fbfe1bfd9e63681b30?narHash=sha256-GMSVw35Q%2B294GlrTUKlx087E31z7KurReQ1YHSKp5iw%3D' (2026-04-23)
• Updated input 'haskell-nix':
'github:input-output-hk/haskell.nix/d2cffda795ee9dc29fa6c1f0904049bf5f10741a?narHash=sha256-kC0%2BbV0Iqv7XQdbPyPVusg70FxHFvUdO4r0QQuy3XlA%3D' (2026-04-19)
→ 'github:input-output-hk/haskell.nix/0d65e5a5caa0d79b2aa02c49602eff0c3a079f23?narHash=sha256-T5wVbrpXJGVPefF6%2BttKcNepqk0fAST6OKxSstJyzSc%3D' (2026-04-26)
• Updated input 'haskell-nix/hackage':
'github:input-output-hk/hackage.nix/666ee3dc235848d35582cc60e0b18ffe811b27c9?narHash=sha256-FbPp0Go9vbeA%2BzhxqkCrsWYguBzVryu2Ecg01Ph2nyA%3D' (2026-04-19)
→ 'github:input-output-hk/hackage.nix/164126a0bf3cf52632a0d90cbeeb930720a02b44?narHash=sha256-USlr9HTzI/HjQC3COQ6PUZ0jtlA3grrffVOM3fr%2BSeQ%3D' (2026-04-26)
• Updated input 'haskell-nix/hackage-for-stackage':
'github:input-output-hk/hackage.nix/eea910a8d440a735513d00224cb018ab934bc25d?narHash=sha256-P5G4kyt3MGSSl2R4bqy5iqPEVtZRvj%2B6DARbcuTULS0%3D' (2026-04-19)
→ 'github:input-output-hk/hackage.nix/b0ff6df0b5220214704f2590ead8a58e97af84a0?narHash=sha256-VpWgq0FevPggXeiuHickC/q62qxm6czZIDZ2aeIkNuA%3D' (2026-04-26)
• Updated input 'haskell-nix/nixpkgs-2511':
'github:NixOS/nixpkgs/b0924ea1889b366de6bb0018a9db70b2c43a15f8?narHash=sha256-hLp6T/vKdrBQolpbN3EhJOKTXZYxJZPzpnoZz%2BfEGlE%3D' (2025-12-01)
→ 'github:NixOS/nixpkgs/74b87959b2d16f59f54d8559cf3cf26b9d907949?narHash=sha256-msT6frWJSQ2WR%2B0cpk%2BKPcZdLTLagUIsJwQwIX9JNSo%3D' (2026-04-09)
• Updated input 'haskell-nix/nixpkgs-unstable':
'github:NixOS/nixpkgs/c1cb7d097cb250f6e1904aacd5f2ba5ffd8a49ce?narHash=sha256-hdFa0TAVQAQLDF31cEW3enWmBP%2Bb592OvHs6WVe3D8k%3D' (2025-12-01)
→ 'github:NixOS/nixpkgs/13043924aaa7375ce482ebe2494338e058282925?narHash=sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90%3D' (2026-04-11)
• Updated input 'haskell-nix/stackage':
'github:input-output-hk/stackage.nix/687b5f55faed3953a03d4621e80ec6e51d911501?narHash=sha256-AyAVUulMNWXGGBNx2nUtS5Ywt3tERGVGYiG9gqAnm/E%3D' (2026-04-19)
→ 'github:input-output-hk/stackage.nix/3b6af61aee3e6d6479ffd8f8ef012df6e96f0983?narHash=sha256-3qY4r/l1vEpz5cybYkA0A42t6Ou4mhtx6x/awuRTyRU%3D' (2026-04-26)
• Updated input 'hercules-ci-effects':
'github:hercules-ci/hercules-ci-effects/4a80b7e95a298b7bb4418c0a2b55fe95a662c377?narHash=sha256-3A2B8k6YCuzt5pT/CQEltUghtE6heSlk2tMYkg/fUWI%3D' (2026-04-16)
→ 'github:hercules-ci/hercules-ci-effects/e2456ee419f9d75f8382e3d6c5af4690b316a5a8?narHash=sha256-wA%2BONiwbvQIy7ERJx/ruhV7y5xku6XKstXCII5bIbdI%3D' (2026-04-19)
• Updated input 'hercules-ci-effects/flake-parts':
'github:hercules-ci/flake-parts/f20dc5d9b8027381c474144ecabc9034d6a839a3?narHash=sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0%3D' (2026-03-01)
→ 'github:hercules-ci/flake-parts/3107b77cd68437b9a76194f0f7f9c55f2329ca5b?narHash=sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA%3D' (2026-04-01)
• Updated input 'hercules-ci-effects/nixpkgs':
'github:NixOS/nixpkgs/c06b4ae3d6599a672a6210b7021d699c351eebda?narHash=sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk%3D' (2026-03-13)
→ 'github:NixOS/nixpkgs/4c1018dae018162ec878d42fec712642d214fdfa?narHash=sha256-ar3rofg%2BawPB8QXDaFJhJ2jJhu%2BKqN/PRCXeyuXR76E%3D' (2026-04-09)
• Updated input 'nixpkgs':
'github:NixOS/nixpkgs/399894fbc01ae0a44cf50137800290e9c2b113b1?narHash=sha256-bMHNn/otkxF8gERYpjX/C21nvimwatbrKb9PPX5vIyM%3D' (2026-04-19)
→ 'github:NixOS/nixpkgs/a5951fb3cf2eeeac787ece2e04fd23823c75ab73?narHash=sha256-RpIS/Whk/kw4A78sfi23kKzXumQLxxHQRf/11K5J80g%3D' (2026-04-26)
• Updated input 'pre-commit-hooks':
'github:cachix/pre-commit-hooks.nix/580633fa3fe5fc0379905986543fd7495481913d?narHash=sha256-8Psjt%2BTWvE4thRKktJsXfR6PA/fWWsZ04DVaY6PUhr4%3D' (2026-04-07)
→ 'github:cachix/pre-commit-hooks.nix/3cfd774b0a530725a077e17354fbdb87ea1c4aad?narHash=sha256-PcRvlWayisPSjd0UcRQbhG8Oqw78AcPE6x872cPRHN8%3D' (2026-04-21)
Describe quantum attacks and affected primitives
- check-progress.sh: shows running sim processes and sweep log status - CLAUDE.md: documents script usage, directory structure, configuration, engine modes, and reproducibility for the CIP experiment suite Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* Add well-formedness check for RequiredTopLevelGuards * Apply suggestion from Claude
Iteration 6b. The player binary now attempts to spawn a ship via the asteria add_new_ship transaction: - src/Asteria/Game.hs: SpawnShipParams + spawnShipProgram. Builds the full asteria add_new_ship tx shape — spendScript asteria with AddNewShip redeemer, attach asteria/spacetime/pellet scripts, mint SHIP+PILOT (spacetime) + initial FUEL (pellet), output updated asteria UTxO at the asteria addr, output ship UTxO at the spacetime addr, output pilot NFT to the wallet, validity range upper bound from current wallclock + buffer. All values mirror the iter-4 game constants exactly. - src/Asteria/Datums.hs: ShipyardRedeemer (MintShip / BurnShip) and FuelRedeemer (MintFuel / BurnFuel) added. - app/PlayerMain.hs: when player_id=1 and not yet spawned, calls attemptSpawn. Computes validToSlot from posixMsToSlot + 60s buffer to dodge OutsideValidityIntervalUTxO. Emits SDK events at every milestone (attempted, built, spawned/failed) with the full error in the details body. - testnets/cardano_node_master/docker-compose.yaml: mount utxo-keys onto both player containers (was bootstrap-only). - components/configurator/configurator.sh: bump Alonzo maxTxExUnits to 14M / 14B (was 10M / 10B). Asteria's add_new_ship script is large enough to exceed the cardano-cli default per-tx CPU step limit. What works: - spawn tx builds: passes asteria validator's structural checks (datum, value, ship name, pilot name, fuel mint, validity range, pilot NFT to wallet). - balanceTx + signing produces a tx that's mempool-accepted (Submitted _ returned). Outstanding upstream blocker: lambdasistemi/cardano-node-clients#76 — evalBudgetExUnits is hardcoded at 10B steps in the TxBuild library, so even with the cluster's per-tx limit raised to 14B, the redeemer's exUnits get capped at the eval budget. Phase-2 re-runs the script and reports "overspending the budget" by ~600k steps. Iter 6c picks up after that fix lands; the spawn machinery in this PR is correct, just constrained by the upstream cap.
Iteration 6b. The player binary now attempts to spawn a ship via the asteria add_new_ship transaction: - src/Asteria/Game.hs: SpawnShipParams + spawnShipProgram. Builds the full asteria add_new_ship tx shape — spendScript asteria with AddNewShip redeemer, attach asteria/spacetime/pellet scripts, mint SHIP+PILOT (spacetime) + initial FUEL (pellet), output updated asteria UTxO at the asteria addr, output ship UTxO at the spacetime addr, output pilot NFT to the wallet, validity range upper bound from current wallclock + buffer. All values mirror the iter-4 game constants exactly. - src/Asteria/Datums.hs: ShipyardRedeemer (MintShip / BurnShip) and FuelRedeemer (MintFuel / BurnFuel) added. - app/PlayerMain.hs: when player_id=1 and not yet spawned, calls attemptSpawn. Computes validToSlot from posixMsToSlot + 60s buffer to dodge OutsideValidityIntervalUTxO. Emits SDK events at every milestone (attempted, built, spawned/failed) with the full error in the details body. - testnets/cardano_node_master/docker-compose.yaml: mount utxo-keys onto both player containers (was bootstrap-only). - components/configurator/configurator.sh: bump Alonzo maxTxExUnits to 14M / 14B (was 10M / 10B). Asteria's add_new_ship script is large enough to exceed the cardano-cli default per-tx CPU step limit. What works: - spawn tx builds: passes asteria validator's structural checks (datum, value, ship name, pilot name, fuel mint, validity range, pilot NFT to wallet). - balanceTx + signing produces a tx that's mempool-accepted (Submitted _ returned). Outstanding upstream blocker: lambdasistemi/cardano-node-clients#76 — evalBudgetExUnits is hardcoded at 10B steps in the TxBuild library, so even with the cluster's per-tx limit raised to 14B, the redeemer's exUnits get capped at the eval budget. Phase-2 re-runs the script and reports "overspending the budget" by ~600k steps. Iter 6c picks up after that fix lands; the spawn machinery in this PR is correct, just constrained by the upstream cap.
Iteration 6.
- src/Asteria/RandomSource.hs: pluggable randomness as a record-of-
functions. newSystemSource seeds System.Random from the player's
ASTERIA_PLAYER_ID hash so each replica has a distinct deterministic
stream. randomInRange wraps getU64 with a modulo-based range draw.
Iteration 7 will add a sibling implementation that subprocesses to
Python's antithesis.random.get_random() so the hypervisor controls
every decision; the consuming code is already abstracted over the
RandomSource interface.
- app/PlayerMain.hs: replaces the pp-query loop with the asteria
observation loop:
1. queryUTxOs at the asteria spend address.
2. emit asteria_player_asteria_observed_<id> sometimes(true/false)
depending on whether the bootstrap-created UTxO is visible.
3. decode the inline AsteriaDatum and emit
asteria_player_ship_counter_<id> with the current ship_counter.
4. draw a random (delta_x, delta_y) in [-5, 5] via randomInRange,
emit asteria_player_move_planned_<id> with the deltas.
5. sleep a random 1..5 seconds.
This proves discovery + decode + RandomSource end-to-end against the
running cluster. Iter 6b will replace step 4's plan with actually
building and submitting the mintShip / moveShip txs.
Verified:
asteria_player_ship_counter_1 {"ship_counter": 0}
asteria_player_move_planned_1 {"delta_x": 3, "delta_y": 4}
asteria_player_ship_counter_2 {"ship_counter": 0}
asteria_player_move_planned_2 {"delta_x": 0, "delta_y": -4}
Distinct deltas per replica per iteration confirm the seed-by-id
strategy is producing independent streams.
Iteration 6.
- src/Asteria/RandomSource.hs: pluggable randomness as a record-of-
functions. newSystemSource seeds System.Random from the player's
ASTERIA_PLAYER_ID hash so each replica has a distinct deterministic
stream. randomInRange wraps getU64 with a modulo-based range draw.
Iteration 7 will add a sibling implementation that subprocesses to
Python's antithesis.random.get_random() so the hypervisor controls
every decision; the consuming code is already abstracted over the
RandomSource interface.
- app/PlayerMain.hs: replaces the pp-query loop with the asteria
observation loop:
1. queryUTxOs at the asteria spend address.
2. emit asteria_player_asteria_observed_<id> sometimes(true/false)
depending on whether the bootstrap-created UTxO is visible.
3. decode the inline AsteriaDatum and emit
asteria_player_ship_counter_<id> with the current ship_counter.
4. draw a random (delta_x, delta_y) in [-5, 5] via randomInRange,
emit asteria_player_move_planned_<id> with the deltas.
5. sleep a random 1..5 seconds.
This proves discovery + decode + RandomSource end-to-end against the
running cluster. Iter 6b will replace step 4's plan with actually
building and submitting the mintShip / moveShip txs.
Verified:
asteria_player_ship_counter_1 {"ship_counter": 0}
asteria_player_move_planned_1 {"delta_x": 3, "delta_y": 4}
asteria_player_ship_counter_2 {"ship_counter": 0}
asteria_player_move_planned_2 {"delta_x": 0, "delta_y": -4}
Distinct deltas per replica per iteration confirm the seed-by-id
strategy is producing independent streams.
Iteration 5b. The bootstrap binary now mints the asteria admin
NFT and locks it at the asteria spend address with the initial
inline AsteriaDatum — the on-chain "asteria UTxO" that the
move_ship / mine_asteria / consume_asteria validators reference.
- aiken/validators/admin_mint.ak: new always-true mint policy.
Its hash is baked into admin_token by apply-params.sh so the
four parameterized validators can reference it.
- aiken/apply-params.sh: computes admin_mint hash from the
freshly-built plutus.json and uses it as admin_token's
policy_id (was 28 zero bytes, now the real hash).
- aiken/plutus-applied.json: regenerated with the corrected
admin_token. New hashes:
pellet: 009080306c62ac5db9c4c04ca4eed6e6b567405c69c34049381824dc
deploy: 4e2021af9fd97e24d9c0ae06c616ebcaa5f947b2ea5b643733467d3d
asteria: 0824601a437559eb07d613f2cfb74bb87b2825b2f9b30e7a2ecb88e5
spacetime: dd1a2baf950b225d26208acc981dbdb93f7891e3d933c4a20590e31e
admin_mint: def68337867cb4f1f95b6b811fedbfcdd7780d10a95cc072077088ea
- src/Asteria/Validators.hs: expose adminMintScript.
- src/Asteria/Crypto.hs: tiny helper that bridges
cardano-crypto-class hashes to PlutusTx BuiltinByteString.
- src/Asteria/Datums.hs: AsteriaDatum, ShipDatum, PelletDatum,
AsteriaRedeemer, ShipRedeemer, PelletRedeemer with ToData /
FromData matching Aiken's encoding.
- app/BootstrapMain.hs: replace the no-op self-pay with the
asteria-creation tx — spend genesis UTxO, attach admin_mint,
mint 1 admin NFT, lock 5 ADA + admin NFT at the asteria spend
address with inline AsteriaDatum {ship_counter=0,
shipyard_policy=spacetime_hash}. Bumped wait timeout to 180s
to cover cluster cold-start block-propagation latency.
Verified locally: docker compose up -d → asteria-bootstrap
exits 0, on-chain query confirms a UTxO at addr_test1wq...
with inline datum {ship_counter=0, shipyard_policy=dd1a2baf...}.