Skip to content

feat(signer): sign OpenZeppelin smart-account authorizations (no new flags) - #27

Merged
willemneal merged 1 commit into
aha-mainfrom
feat/smart-account-signing
Sep 3, 2026
Merged

feat(signer): sign OpenZeppelin smart-account authorizations (no new flags)#27
willemneal merged 1 commit into
aha-mainfrom
feat/smart-account-signing

Conversation

@willemneal

Copy link
Copy Markdown
Member

Implements #26 (a focused first cut).

What

stellar can now sign an operation whose require_auth() resolves to an OpenZeppelin smart account (a C… address with a custom __check_auth) — so stellar registry publish --author <smart-account> --sign-with-key <delegate> works with the CLI alone, no bespoke signer binary.

No smart-account-specific flags. Signing is keyed only on the auth entry's own contract address + --sign-with-key.

How

  • Discovery (config, where the RPC client lives). When the tx has a contract-address auth credential, resolve the authorizing context rule from the account's own on-chain views — get_context_rules_count / get_context_rule over read-only simulateTransaction — and match by scope (the invocation's target contract, or Default) and signer (the --sign-with-key key). That yields the rule id, the mode (Delegated/External), and the verifier — none are user flags. (Same read path perch's perch-deploy scan_rules uses.)
  • Signing (signer/smart_account.rs, RPC-free). Builds the OZ AuthPayload { signers, context_rule_ids } over the rule-bound digest — delivered as a CAP-71 AddressWithDelegates for a Delegated signer (the delegate signs the SorobanAuthorizationWithAddress preimage), or an Address credential for an External signer.
  • Fee correctness (tx.rs). A smart account's __check_auth runs verifier + policy cross-calls that recording-mode simulation doesn't execute, so sim_sign_and_send_tx re-simulates in enforce mode when a smart-account entry was signed, capturing the true footprint before the fee is set.

The contract-address arm of sign_soroban_authorizations (previously a hard MissingSignerForAddress) becomes this path; sign_soroban_authorizations now takes &[SmartAccountAuth] and returns SignedAuthTxn { tx, smart_account_signed }.

Scope / follow-ups (tracked in #26)

Verification

cargo check -p soroban-cli --all-targets clean under -D warnings; cargo test -p soroban-cli --lib signer:: green (25 tests, incl. 3 new).

Note

Filed on the fork so we can cut an RC (cargo binstall-able) and consume it before/independently of upstreaming.

🤖 Generated with Claude Code

…flags)

`stellar` could not sign an operation whose `require_auth()` resolves to a
smart-contract account (a C… address with a custom `__check_auth`): the signer
rejected contract-address credentials and skipped CAP-71 `AddressWithDelegates`.
So `stellar registry publish --author <smart-account>` was impossible with the
CLI alone.

This adds smart-account signing keyed only on (the auth entry's own contract
address) + (`--sign-with-key`) — no smart-account-specific flags. When the
signing loop hits a contract-address credential, `config` DISCOVERS the
authorizing OpenZeppelin context rule from the account's own on-chain views
(`get_context_rules_count` / `get_context_rule`, read over read-only
simulation), matching the rule by scope (the invocation's target contract) and
signer (the signing key), and derives the rule id + mode + verifier. The
RPC-free signer then builds the credential: OZ `AuthPayload { signers,
context_rule_ids }` over the rule-bound digest, delivered as a CAP-71
`AddressWithDelegates` for a Delegated signer, or an `Address` credential for an
External signer.

Because a smart account's `__check_auth` runs verifier + policy cross-calls that
recording-mode simulation does not execute, `sim_sign_and_send_tx` re-simulates
in enforce mode when a smart-account entry was signed, so the fee reflects the
true footprint.

Normal G-account flows are unaffected — discovery only runs when a
contract-address auth entry is present.

- new: cmd/soroban-cli/src/signer/smart_account.rs (signing + discovery)
- signer::sign_soroban_authorizations takes &[SmartAccountAuth], returns
  SignedAuthTxn { tx, smart_account_signed }
- config discovers per contract-address entry and builds the delegate from
  --sign-with-key; tx.rs gates the enforce re-sim on the flag

Refs #26

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R32ivDDvuLnRZrmzkvzoeu
@willemneal
willemneal merged commit 4b3dc8b into aha-main Sep 3, 2026
20 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant