527 commits in all time Mar 19, 2026 12:11 – Jun 17, 2026 12:11 UTC
manuelmauro algonaut
fix(transaction): omit zero vote rounds; green the integration suite (#368)
The online key-registration acceptance scenario surfaced a real encoding bug:
the `KeyRegistration` -> `ApiTransaction` conversion assigned `vote_first` and
`vote_last` directly, so `vote_first == Round(0)` encoded `votefst: 0`. Canonical
Algorand msgpack omits zero ints, so the signed bytes differed from the node's
re-encoding and the keyreg failed signature verification. Omit zero vote rounds
(matching `vote_key_dilution`); add a regression test.

Also fix the integration-suite harness so `make harness && make cucumber` passes
end-to-end and the now-gating `cargo-test-integration` job goes green:

- Fund payment/keyreg senders from the funded account, and make the transient
  account a genuinely new, exactly-funded account. kmd's `list_keys` ordering is
  unstable, so the old `accounts[0]` senders overspent on some harness
  instances, and reusing the shared funder as the transient leaked rekeys/spends
  across scenarios and hid an overspend assertion.
- Register a throwaway funded account for keyreg (never the consensus creator).
- composer-clone: `take_unsigned_group` re-derives the group from a backup after
  signing, so a post-clone simulate still has a group.
- Read ABI returns from a composer *simulate* (not only execute).
- Lazily create a wallet for the "Wallet handle" scenario (no explicit create).
- Exclude two scenarios that assert behavior Algorand / the composer don't
  support, with tracking comments: the composer's simulate always allows empty
  signatures (so it can't surface "signedtxn has no sig"), and clearing all four
  asset roles is a destroy (so the asset can't "persist with cleared roles").

Suite result on a fresh harness: 274 passed / 0 failed (plus gated and excluded
skips).
Git Commit 4144110e Branch main Document 8/171 ++ 34 --
manuelmauro algonaut
fix(transaction): omit zero vote rounds; green the integration suite
The online key-registration acceptance scenario surfaced a real encoding bug:
the `KeyRegistration` -> `ApiTransaction` conversion assigned `vote_first` and
`vote_last` directly, so `vote_first == Round(0)` encoded `votefst: 0`. Canonical
Algorand msgpack omits zero ints, so the signed bytes differed from the node's
re-encoding and the keyreg failed signature verification. Omit zero vote rounds
(matching `vote_key_dilution`); add a regression test.

Also fix the integration-suite harness so `make harness && make cucumber` passes
end-to-end and the now-gating `cargo-test-integration` job goes green:

- Fund payment/keyreg senders from the funded account, and make the transient
  account a genuinely new, exactly-funded account. kmd's `list_keys` ordering is
  unstable, so the old `accounts[0]` senders overspent on some harness
  instances, and reusing the shared funder as the transient leaked rekeys/spends
  across scenarios and hid an overspend assertion.
- Register a throwaway funded account for keyreg (never the consensus creator).
- composer-clone: `take_unsigned_group` re-derives the group from a backup after
  signing, so a post-clone simulate still has a group.
- Read ABI returns from a composer *simulate* (not only execute).
- Lazily create a wallet for the "Wallet handle" scenario (no explicit create).
- Exclude two scenarios that assert behavior Algorand / the composer don't
  support, with tracking comments: the composer's simulate always allows empty
  signatures (so it can't surface "signedtxn has no sig"), and clearing all four
  asset roles is a destroy (so the asset can't "persist with cleared roles").

Suite result on a fresh harness: 274 passed / 0 failed (plus gated and excluded
skips).
Git Commit 3f9b02ec Branch pull/368/head Document 8/171 ++ 34 --
manuelmauro algonaut
test(cucumber): fund payment and rekey senders from a funded account (#367)
Several integration steps used `accounts[0]` as the payment or funding sender,
but kmd's `list_keys` ordering is unstable, so `accounts[0]` is not reliably the
pre-funded creator. On harness instances where it lands on an unfunded account,
the simulated/submitted payments overspend (account MicroAlgos:0), cascading
into auth and "tx id not set" failures across `simulate`, `send`, and `rekey`.

Use `pick_funded_account` (the highest-balance wallet account) as the sender in
`build_default_payment` and the rekey funding step. The sign step exports
`tx.sender()`'s key, so sender and signer stay consistent.

On a fresh harness this drops the suite from 13 failing to 6. The remaining six
are distinct issues (asset-index tracking, the kmd wallet-handle scenario, the
keyreg sender, the composer-clone state machine, and the ABI-return decode) and
are left to follow-ups.
Git Commit 31e4967d Branch main Document 2/18 ++ 11 --
manuelmauro algonaut
test(cucumber): fund payment and rekey senders from a funded account
Several integration steps used `accounts[0]` as the payment or funding sender,
but kmd's `list_keys` ordering is unstable, so `accounts[0]` is not reliably the
pre-funded creator. On harness instances where it lands on an unfunded account,
the simulated/submitted payments overspend (account MicroAlgos:0), cascading
into auth and "tx id not set" failures across `simulate`, `send`, and `rekey`.

Use `pick_funded_account` (the highest-balance wallet account) as the sender in
`build_default_payment` and the rekey funding step. The sign step exports
`tx.sender()`'s key, so sender and signer stay consistent.

On a fresh harness this drops the suite from 13 failing to 6. The remaining six
are distinct issues (asset-index tracking, the kmd wallet-handle scenario, the
keyreg sender, the composer-clone state machine, and the ABI-return decode) and
are left to follow-ups.
Git Commit 9d68362c Branch pull/367/head Document 2/18 ++ 11 --
manuelmauro algonaut
fix(cucumber): fail the test run when scenarios fail (#366)
The CI job `cargo-test-integration` runs the acceptance suite
(`cargo test --test cucumber --`), but the runner called cucumber's
`filter_run(...)` and discarded the returned writer. `filter_run` reports
failed/errored steps without failing the process, and the suite is
`harness = false` (so `main()`'s exit code is the test result) — so `main()`
always returned success and the 12-minute CI job lit green even on a red suite.

Capture each feature run's writer, check `writer.execution_has_failed()`
(failed steps / parsing errors / hook errors), print a `FAILED <feature>` line,
accumulate across all features, and `std::process::exit(1)` at the end if any
failed. Every feature still runs first, so the full failure picture is reported.

This makes `cargo-test-integration` honestly reflect the suite's state instead
of masking failures.
Git Commit cb316e01 Branch main Document 1/39 ++ 8 --
manuelmauro algonaut
Merge 4949e7a5e12c16c98e4f9bff34f7b166d39caec4 into 650b36a288b1e9e48600a597b6f44c179a0fa89b
Git Commit 44c8811c Branch pull/366/merge Document 1/39 ++ 8 --
manuelmauro algonaut
fix(cucumber): fail the test run when scenarios fail
The CI job `cargo-test-integration` runs the acceptance suite
(`cargo test --test cucumber --`), but the runner called cucumber's
`filter_run(...)` and discarded the returned writer. `filter_run` reports
failed/errored steps without failing the process, and the suite is
`harness = false` (so `main()`'s exit code is the test result) — so `main()`
always returned success and the 12-minute CI job lit green even on a red suite.

Capture each feature run's writer, check `writer.execution_has_failed()`
(failed steps / parsing errors / hook errors), print a `FAILED <feature>` line,
accumulate across all features, and `std::process::exit(1)` at the end if any
failed. Every feature still runs first, so the full failure picture is reported.

This makes `cargo-test-integration` honestly reflect the suite's state instead
of masking failures.
Git Commit 4949e7a5 Branch fix/cucumber-runner-gates-ci Document 1/39 ++ 8 --
manuelmauro algonaut
feat(simulate)!: return SimulateResponse from algod.simulate; add failure accessors (#365)
Migrate the public `Algod::simulate` to return the hand-named `SimulateResponse`
wrapper instead of the generated `SimulateTransactionResponse`, advancing the
`hide-generated-types` migration. The atomic composer keeps raw access for ABI
return decoding via a new `pub(crate) Algod::simulate_raw`.

Grow the `SimulateResponse` accessor set with the fields callers need to inspect
a failed simulation: `failed_at`, `max_log_calls`, `max_log_size` (joining the
existing `would_succeed`, `failure_message`, `extra_opcode_budget`).

Unify the cucumber simulate plumbing on the wrapper: a single `simulate_response`
field feeds the failure/success/power-pack assertions through the accessors,
eliminating the `simulate_response`/`simulate_outcome` split that left
composer-driven scenarios reading an unset field ("no simulate response"). The
deep exec-trace assertions (tag-filtered today) move to a separate `simulate_raw`
field; migrating their generated trace models to typed accessors is a tracked
follow-up.

BREAKING CHANGE: `Algod::simulate` now returns `SimulateResponse` rather than the
generated `SimulateTransactionResponse`. Pre-1.0; no backward-compatibility
guarantee.
Git Commit 650b36a2 Branch main Document 5/102 ++ 49 --
manuelmauro algonaut
feat(simulate)!: return SimulateResponse from algod.simulate; add failure accessors
Migrate the public `Algod::simulate` to return the hand-named `SimulateResponse`
wrapper instead of the generated `SimulateTransactionResponse`, advancing the
`hide-generated-types` migration. The atomic composer keeps raw access for ABI
return decoding via a new `pub(crate) Algod::simulate_raw`.

Grow the `SimulateResponse` accessor set with the fields callers need to inspect
a failed simulation: `failed_at`, `max_log_calls`, `max_log_size` (joining the
existing `would_succeed`, `failure_message`, `extra_opcode_budget`).

Unify the cucumber simulate plumbing on the wrapper: a single `simulate_response`
field feeds the failure/success/power-pack assertions through the accessors,
eliminating the `simulate_response`/`simulate_outcome` split that left
composer-driven scenarios reading an unset field ("no simulate response"). The
deep exec-trace assertions (tag-filtered today) move to a separate `simulate_raw`
field; migrating their generated trace models to typed accessors is a tracked
follow-up.

BREAKING CHANGE: `Algod::simulate` now returns `SimulateResponse` rather than the
generated `SimulateTransactionResponse`. Pre-1.0; no backward-compatibility
guarantee.
Git Commit 70905c7c Branch pull/365/head Document 5/102 ++ 49 --
manuelmauro algonaut
fix(transaction): omit sgnr from non-rekeyed signed transactions (#364)
`ApiSignedTransaction.auth_address` (the `sgnr` field) was the only optional
field without `skip_serializing_if`, so every non-rekeyed signed transaction
encoded a spurious `sgnr: null`. Algorand's canonical msgpack omits absent
fields, so `to_msg_pack()` differed byte-for-byte from kmd's and the node's
signed transaction — surfaced by the kmd "sign both ways" acceptance scenarios
(`sdk signed tx != kmd signed tx`).

Add `skip_serializing_if` to `sgnr` and move it to its canonical sorted slot
(before `sig`: `'g' < 'i'`) so rekeyed transactions encode canonically too. Add
a regression test asserting `sgnr` is present iff the transaction is rekeyed.
Git Commit 3718c182 Branch main Document 2/52 ++ 3 --
manuelmauro algonaut
test(cucumber): restore resource path broken by suite relocation (#344) (#363)
PR #344 relocated the cucumber suite to `tests/cucumber/`, moving the
resource files to `tests/cucumber/features/resources/` but leaving four
step-def helpers pointing at the old `tests/features/resources/` path.
Every scenario reading a TEAL program or JSON response fixture panicked
with `NotFound`, taking down whole features (ABI, Compile, Dryrun, Dryrun
Testing, and the v2 algod/indexer response suites).

Point the four helpers at the relocated directory. On a fresh sandbox this
recovers ~78 scenarios — a full `make harness && make cucumber` run drops
from ~93 failing to 10.
Git Commit e5030ab0 Branch main Document 4/7 ++ 7 --
manuelmauro algonaut
Merge 4d7ceeeb7482a11e492e8f53f3270cb5c6783b75 into 455546bfc58eaec760d6827483cf4d3ac307d9b3
Git Commit 32bdb612 Branch pull/364/merge Document 2/52 ++ 3 --
manuelmauro algonaut
fix(transaction): omit sgnr from non-rekeyed signed transactions
`ApiSignedTransaction.auth_address` (the `sgnr` field) was the only optional
field without `skip_serializing_if`, so every non-rekeyed signed transaction
encoded a spurious `sgnr: null`. Algorand's canonical msgpack omits absent
fields, so `to_msg_pack()` differed byte-for-byte from kmd's and the node's
signed transaction — surfaced by the kmd "sign both ways" acceptance scenarios
(`sdk signed tx != kmd signed tx`).

Add `skip_serializing_if` to `sgnr` and move it to its canonical sorted slot
(before `sig`: `'g' < 'i'`) so rekeyed transactions encode canonically too. Add
a regression test asserting `sgnr` is present iff the transaction is rekeyed.
Git Commit 4d7ceeeb Branch fix/signed-tx-sgnr-canonical Document 2/52 ++ 3 --
manuelmauro algonaut
Merge 603fc76d82aca3337d49d53969a3560622a848cd into 455546bfc58eaec760d6827483cf4d3ac307d9b3
Git Commit 36069b25 Branch pull/363/merge Document 4/7 ++ 7 --
manuelmauro algonaut
test(cucumber): restore resource path broken by suite relocation (#344)
PR #344 relocated the cucumber suite to `tests/cucumber/`, moving the
resource files to `tests/cucumber/features/resources/` but leaving four
step-def helpers pointing at the old `tests/features/resources/` path.
Every scenario reading a TEAL program or JSON response fixture panicked
with `NotFound`, taking down whole features (ABI, Compile, Dryrun, Dryrun
Testing, and the v2 algod/indexer response suites).

Point the four helpers at the relocated directory. On a fresh sandbox this
recovers ~78 scenarios — a full `make harness && make cucumber` run drops
from ~93 failing to 10.
Git Commit 603fc76d Branch fix/integration-suite-health Document 4/7 ++ 7 --
manuelmauro algonaut
docs(skill): add GitHub release step to prepare-release (#362)
Git Commit 455546bf Branch main Document 1/16 ++ 0 --
manuelmauro algonaut
Merge 1b4a89c479c151f5603ed6b44213d0c2297d72f2 into 5d94ed0047b615506eb6ad6b2f4b3f018b1ada42
Git Commit 8b1c5725 Branch pull/362/merge Document 1/16 ++ 0 --
manuelmauro algonaut
docs(skill): add GitHub release step to prepare-release
Git Commit 1b4a89c4 Branch docs/prepare-release-github-step Document 1/16 ++ 0 --
manuelmauro algonaut
chore(release): v0.9.0 — ARC-56 contract clients (#361)
* chore: add prepare-release skill tailored to the workspace

* chore(release): v0.9.0
Git Commit 5d94ed00 Branch main Document 16/230 ++ 26 --
manuelmauro algonaut
Merge 3928b60f6e8b173e82c3bc69ea4902b30b9a5457 into 125f8559d2e6d1970adb0e9eb45ae238dcfe6795
Git Commit 1871e78d Branch pull/361/merge Document 16/230 ++ 26 --
manuelmauro algonaut
chore(release): v0.9.0
Git Commit 3928b60f Branch release/v0.9.0 Document 15/29 ++ 26 --
manuelmauro algonaut
chore: add prepare-release skill tailored to the workspace
Git Commit ac37b69f Branch release/v0.9.0 Document 1/201 ++ 0 --
manuelmauro algonaut
Merge 1710adfe341e8643935189b3178512aa3c0fc629 into 125f8559d2e6d1970adb0e9eb45ae238dcfe6795
Git Commit a46ce934 Branch pull/343/merge Document 20/2,926 ++ 10 --
manuelmauro algonaut
fix(walletconnect): mirror @perawallet/connect proposal and relay
Match Pera's own dApp SDK (@perawallet/connect, branch feat/wallet-connect-v2,
@walletconnect/sign-client 2.17.0) byte-for-byte on the wire so the Pera wallet
sees a proposal identical to the one it is built to accept:

- relay endpoint back to wss://relay.walletconnect.com (Pera hardcodes .com;
  .com and .org federate, verified, but match Pera exactly).
- requiredNamespaces.algorand requests both MainNet and TestNet chains and both
  methods (algo_signTxn + algo_signData), matching the SDK's connect() call.
  An Algorand address is the same on both networks, so one account satisfies it.
- the relay example uses the default (Pera-mirroring) config.

Verified: the proposal completes a full handshake (propose -> approve -> settle
-> Active -> connected address) against both the latest @walletconnect/sign-client
and Pera's pinned 2.17.0. The dApp is therefore protocol-correct against Pera's
exact WalletConnect stack; any remaining failure with the Pera mobile app is
app/environment specific rather than a wire-format issue.
Git Commit 1710adfe Branch feat/pera-walletconnect-signer Document 3/17 ++ 15 --
manuelmauro algonaut
fix(walletconnect): complete the WalletConnect v2 relay handshake
The batteries-included relay client could not establish a session with a
real WalletConnect v2 wallet. Verified end-to-end against the canonical
@walletconnect/sign-client reference wallet (propose -> approve -> settle
-> Active -> connected address).

- request id: ms * 1_000 + entropy (16 digits, within JS
  Number.MAX_SAFE_INTEGER) to match WalletConnect's payloadId. The
  previous ms * 1_000_000 produced 19-digit ids that overflow a wallet's
  JSON number handling, so it received the proposal but could never
  respond and the handshake silently stalled.
- message dispatch: parse incoming frames as JSON-RPC requests before
  responses in all three sites (handle_message, the send_relay_request
  read loop, and the decrypted-payload dispatch). JsonRpcResponse
  deserializes leniently and was swallowing irn_subscription pushes and
  wc_sessionSettle, dropping the wallet's replies.
- relay envelope: base64 standard-with-padding (base64pad), matching WC.
- session topic: sha256 over the raw symmetric-key bytes (hashKey).
- settlement: acknowledge wc_sessionSettle so the wallet completes.
- proposal: request a single chain under requiredNamespaces (wallets
  reject a proposal that requires multiple chains at once); fix the
  default and export SessionProposalConfig so callers pick the network.
  The relay example now targets TestNet.
- relay endpoint: wss://relay.walletconnect.org.

Connection is verified against the reference wallet; live Pera Wallet
interop and the algo_signTxn signing path remain unverified.
Git Commit e6366d49 Branch feat/pera-walletconnect-signer Document 6/123 ++ 74 --