Skip to content

feat: EIP-712 typed data signing via API key#148

Merged
njdawn merged 18 commits intoopen-wallet-standard:mainfrom
ggonzalez94:feat/eip712-api-key-signing
Apr 8, 2026
Merged

feat: EIP-712 typed data signing via API key#148
njdawn merged 18 commits intoopen-wallet-standard:mainfrom
ggonzalez94:feat/eip712-api-key-signing

Conversation

@ggonzalez94
Copy link
Copy Markdown
Contributor

@ggonzalez94 ggonzalez94 commented Mar 30, 2026

Fixes #147

Summary

The feature adds policy-gated EIP-712 typed data signing for API key holders (agents). Before this PR, signTypedData only worked in owner mode (with a passphrase). Now agents using ows_key_ tokens can sign typed data too — but only if their policy allows it..

What changed

  • TypedDataContext in PolicyContext — structured EIP-712 domain fields (verifyingContract, chainId, primaryType, name, version) are now available to both declarative rules and executable policies during typed data signing. Only AllowedTypedDataContracts has been wired to keep the scope small. But this can be easily extended
  • AllowedTypedDataContracts rule — new declarative policy rule that whitelists domain.verifyingContract addresses. If not set by the policy, anything can be signed.

Design tradeoffs

  • No opt-in gate for existing keys. Existing API keys gain typed data signing capability after upgrade. We accepted this because sign_message already provides equivalent blast radius (EIP-191 messages can authorize off-chain operations too). Users who want to restrict typed data signing add AllowedTypedDataContracts to their policies.
  • Some API divergence vs tx signing and message signing. TransactionContext values in sign_with_api_key and sign_message_with_api_key are populated with none, this means typed data gets rich, structured context with a dedicated declarative rule. Transactions get nothing — to, value, and data are all None despite the struct having those fields. The TransactionContext was designed for this but never wired up. This PR establishes what I think is the right pattern and can be later extended to tx and message signing. But if consistency is more important we could drop this more rich TransactionContext from typed data and only add it together with the other tx types.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 30, 2026

@ggonzalez94 is attempting to deploy a commit to the MoonPay Team on Vercel.

A member of the Team first needs to authorize it.

@ggonzalez94 ggonzalez94 marked this pull request as ready for review March 31, 2026 19:49
@ggonzalez94 ggonzalez94 requested a review from njdawn as a code owner March 31, 2026 19:49
ggonzalez94 and others added 9 commits March 31, 2026 15:49
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the core EIP-712 typed data signing function for API key
(agent) mode. Gates on EVM-only, parses typed data JSON before policy
evaluation to populate TypedDataContext, and calls EvmSigner::sign_typed_data.
Includes 6 integration tests covering happy path, non-EVM rejection,
wrong contract denial, malformed JSON, expired key, and wallet scope.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove hard-blocks in ops.rs and sign_message.rs that rejected API key
usage for typed data signing. Wire up the dispatcher to delegate to
sign_typed_data_with_api_key, and handle the new AllowedTypedDataContracts
policy rule in the CLI policy display.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rule

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…t bypass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ggonzalez94
Copy link
Copy Markdown
Contributor Author

@njdawn this has been a blocker missing feature for me. Please take a look when you can(especially at the tradeoffs, since they impact the consistency of what is possible to express with typed data signing vs tx and message signing). I've tested it on my use case that needed EIP-712 signatures, and it is working well as far as I can tell.

ggonzalez94 and others added 2 commits March 31, 2026 15:51
- Fix Node.js and Python binding tests: the denied-chain test case
  used typed data with chainId=8453 but chain="ethereum", which now
  correctly triggers the new chain mismatch check instead of AllowedChains.
  Updated tests to use matching chainId so AllowedChains is exercised.
- Run cargo fmt to satisfy CI formatting checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…efactor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ggonzalez94
Copy link
Copy Markdown
Contributor Author

@njdawn this has been a blocker missing feature for me. Please take a look when you can(especially at the tradeoffs, since they impact the consistency of what is possible to express with typed data signing vs tx and message signing). I've tested it on my use case that needed EIP-712 signatures, and it is working well as far as I can tell.

#153 explains this better

ggonzalez94 and others added 6 commits April 1, 2026 16:02
Merge the two separate `use ows_signer` statements into a single
import block, resolving the CI cargo fmt check failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deduplicate the chainId parsing logic that was repeated in the domain
validation (step 5b) and TypedDataContext construction (step 6) of
sign_typed_data_with_api_key.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify that an empty contracts list [] in AllowedTypedDataContracts
denies all typed data signing, confirming no accidental allow-all
behavior when no contracts are specified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use consistent terminology across the codebase — the rest of OWS
uses 'allowlist', so the AllowedTypedDataContracts docs should too.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the `import copy` statement from inside the function body to
the module-level imports where it belongs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use a shared EVM chain-id helper instead of ad hoc parsing in typed-data and x402 flows.

Stop reusing transaction.raw_hex for typed-data JSON bytes and validate the executable policy path against typed_data.raw_json instead.

Add Node and Python binding coverage for AllowedTypedDataContracts.
Copy link
Copy Markdown
Contributor

@njdawn njdawn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm! fix ci, and then can merge

@ggonzalez94
Copy link
Copy Markdown
Contributor Author

lgtm! fix ci, and then can merge

should be fixed now

@ggonzalez94
Copy link
Copy Markdown
Contributor Author

@njdawn CI should be green now — anything else needed before we can merge this?

@njdawn njdawn merged commit 8697778 into open-wallet-standard:main Apr 8, 2026
8 of 9 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.

EIP-712 typed data signing is not supported via API key (agent mode)

2 participants