Skip to content

Conversation

felipemadero
Copy link
Collaborator

@felipemadero felipemadero commented Oct 10, 2025

Why this should be merged

This PR consolidates EVM signing functionality from multiple patterns (raw private key strings, keychains, address-based operations) into a single unified Signer struct. This refactoring:

  • Prepares for wallet integration: The Signer accepts c.EthKeychain (from avalanchego/wallet/chain/c), establishing a direct path to integrate with Avalanche's wallet infrastructure in future update
  • Enables hardware and remote signing: By abstracting the signing interface, the codebase now supports Ledger hardware wallets, remote signers, and other signing backends—not just private keys
  • Cleaner support of raw transactions: Provides both crypto signing mode (for real transactions) and NoOp mode (for generating unsigned/raw transactions), cleaning up the previous implementations for that on evm/contract

How this works

The PR introduces a new evm.Signer struct that wraps Avalanche's keychain.Signer interface, enabling support for various signing backends (private keys, Ledger hardware wallets, remote signers, etc.).

Three constructors are provided:

  1. NewSigner(kc c.EthKeychain): Creates a signer backed by any Avalanche EthKeychain implementation
  2. NewSignerFromPrivateKey(privateKey string): Convenience constructor that creates a signer directly from a private key string, handling SoftKey creation and keychain extraction internally
  3. NewNoOpSigner(addr common.Address): Creates a signer that doesn't sign, useful for generating raw unsigned transactions

The Signer provides:

  • SignTx(): Signs transactions using the underlying keychain (or passes through for NoOp mode)
  • Address(): Returns the associated EVM address
  • TransactOpts(): Generates bind.TransactOpts for contract interactions
  • IsNoOp(): Checks if in NoOp mode

It is nil safe (no panics)

All EVM client methods have been refactored to accept *Signer instead of private key strings.

How this was tested

  • Added evm/signer_test.go altogether with mocks on mocks/keychain
  • Old unit tests passing
  • CLI CI passing

- introduce evm.Signer interface with AvalancheGo, no-op, and null implementations
- replace private key fields/params with signer instances across blockchain, contract, and validator manager flows
- update EVM client helpers and contract utilities to sign transactions via signer
- adapt PoS/PoA initialization, precompile helpers, and interchain deployer to signer-based workflow
- refresh EVM tests and add key path fixtures to cover the new signing model
Refactor the EVM signer architecture from an interface-based pattern to a
concrete struct pattern for better type safety and nil-safety:

- Replace Signer interface with concrete *Signer struct
- Consolidate AvalancheGoSigner, NoOpSigner, and NullSigner into single type
- Add NewCryptoSigner() for real cryptographic signing via keychain
- Add NewNoOpSigner() for generating unsigned transactions
- Implement nil-safe methods (IsNoOp, SignTx, Address, TransactOpts)
- Use nil checking instead of type assertions for NoOp detection
- Update all function signatures across codebase to use *Signer
- Add unit tests for signer
- Add NewSignerFromPrivateKey() convenience constructor that creates a signer
  directly from a private key string, eliminating the need to manually create
  a SoftKey and extract the keychain
- Rename NewCryptoSigner() to NewSigner() for cleaner API - the "Crypto"
  qualifier is redundant since actual signing is the default expectation,
  with NewNoOpSigner() being the explicitly-named special case
- Add comprehensive unit tests for NewSignerFromPrivateKey covering success
  and error cases
- Update all existing test references from NewCryptoSigner to NewSigner
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