# `Onchain.Signer`
[🔗](https://github.com/ZenHive/onchain-stack/blob/onchain-v0.16.0/packages/onchain/lib/onchain/signer.ex#L1)

Onchain.Signer is a GenServer which can sign messages. The runtime carrier
is a `{backend_module, config}` pair implementing `Onchain.Signer.Backend`
(for instance `Onchain.Signer.Secp256k1` with a local key, or
`Onchain.Signer.CloudKMS` with GCP Cloud KMS coordinates). A legacy
`{module, function, args}` MFA is also accepted so existing call sites
(`Onchain.Signer.sign_direct/4`, and `start_link/1` handed a 3-tuple)
keep working. In either case, start the GenServer and call
`Onchain.Signer.sign(MySigner, "message")` to get a packed
`r || s || v` Ethereum signature (`Onchain.Configuration.signature()`).

The packed form is 65 bytes when EIP-155 `v` fits in one byte and longer
otherwise. At chain id 110, `v` is 255 or 256 depending on recovery parity,
producing 65 or 66 bytes respectively. Chain binding for legacy transactions
lives in that trailing `v` because `Transaction.V1.add_signature/2` copies
it into the RLP `v` field; emitting a parity-only byte here would silently
drop EIP-155 replay protection. Typed transactions already take y-parity
from whatever width they receive.

This library never emits a packed Ethereum signature with `s > n/2`
(EIP-2). Low-s canonicalization is applied at the emission funnel, not by
the configured backend: both the `{backend, config}` path and the legacy
MFA path pass through `Onchain.Recover.normalize_low_s/1` before the
recovery-bit search and EIP-155 packing. The MFA carrier remains available
for existing callers and cannot bypass the invariant.

Note: we also enforce that a given signer process knows its public key,
such that we can verify signatures recovery bits. That is, since CloudKMS
and other signing tools don't return a recovery bit, necessary for Ethereum,
we test all 4 possible bits to make sure a signature recovers to the correct
signer address, but we need to know what that address should be to accomplish
this task.

Additionally, chain_id is used to return EIP-155 compliant signatures.

For stateless EIP-1559 writes, `build_transaction/3`, `sign_transaction/3`,
and `encode_transaction/1` build and sign with an explicitly supplied private
key through the same backend carrier. `send_transaction/3` also estimates gas
when omitted and broadcasts the encoded transaction. Signing returns the
complete signed `%Onchain.Transaction.V2{}`; encoding is a separate step.

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `send_transaction!` | 3 | Build, sign, encode, and broadcast a transaction in one call. Raises on error. | `to: value`, `calldata: value`, `opts: value` |
| `send_transaction` | 3 | Build, sign, encode, and broadcast a transaction in one call. | `to: value`, `calldata: value`, `opts: value` |
| `encode_transaction!` | 1 | Encode a signed transaction to 0x-prefixed hex for broadcast. Raises on error. | `signed_trx: value` |
| `encode_transaction` | 1 | Encode a signed transaction to 0x-prefixed hex for broadcast. | `signed_trx: value` |
| `sign_transaction!` | 3 | Sign a transaction with a private key. Raises on error. | `unsigned_trx: value`, `private_key: value`, `chain_id: value` |
| `sign_transaction` | 3 | Sign a transaction with a private key. | `unsigned_trx: value`, `private_key: value`, `chain_id: value` |
| `build_transaction!` | 3 | Build an unsigned EIP-1559 transaction. Raises on error. | `to: value`, `calldata: value`, `opts: value` |
| `build_transaction` | 3 | Build an unsigned EIP-1559 transaction. | `to: value`, `calldata: value`, `opts: value` |
| `address_from_key!` | 1 | Derive checksummed Ethereum address from a private key. Raises on error. | `private_key: value` |
| `address_from_key` | 1 | Derive checksummed Ethereum address from a private key. | `private_key: value` |
| `sign_direct` | 4 | Sign a message directly with a backend carrier or MFA and known signer address. | `message: value`, `address: value`, `signer_mfa: value`, `chain_id_or_name: value` |
| `chain_id` | 1 | Get the chain id configured for a signer process. | `name: value` |
| `address` | 1 | Get the Ethereum address controlled by a signer process. | `name: value` |
| `sign_typed_data` | 3 | Sign EIP-712 typed data the way eth_signTypedData_v4 / ethers signTypedData does. | `typed: value`, `name: value`, `opts: value` |
| `sign_message` | 3 | Sign a message the way a wallet's personal_sign / ethers signMessage does. | `message: value`, `name: value`, `opts: value` |
| `sign` | 3 | Sign a message with a running signer process. | `message: value`, `name: value`, `opts: value` |
| `start_link` | 1 | Start a signer process backed by the provided signer backend carrier. | `signer_options: value` |
| `child_spec` | 1 | Build the supervisor child specification for a signer process. | `init_arg: value` |

# `address`

```elixir
@spec address(GenServer.server()) :: Onchain.Configuration.address()
```

Gets the address for this signer.

## Examples

    iex> signer_proc = Onchain.Test.Signer.start_signer()
    iex> Onchain.Signer.address(signer_proc) |> Onchain.Hex.to_address()
    "0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"

# `address_from_key`

```elixir
@spec address_from_key(binary()) ::
  {:ok, String.t()} | {:error, {:invalid_private_key, term()}}
```

Derive checksummed Ethereum address from a private key.

## Parameters

  * `private_key` - 32-byte binary or hex string (with or without 0x) (value)

## Returns

EIP-55 checksummed address (`{:ok, String.t()} | {:error, {:invalid_private_key, term()}}`)

```elixir
# descripex:contract
%{
  params: %{
    private_key: %{
      description: "32-byte binary or hex string (with or without 0x)",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, {:invalid_private_key, term()}}",
    description: "EIP-55 checksummed address",
    example: "0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"
  }
}
```

# `address_from_key!`

```elixir
@spec address_from_key!(binary()) :: String.t()
```

Derive checksummed Ethereum address from a private key. Raises on error.

## Parameters

  * `private_key` - 32-byte binary or hex string (with or without 0x) (value)

## Returns

EIP-55 checksummed address (`string`)

```elixir
# descripex:contract
%{
  params: %{
    private_key: %{
      description: "32-byte binary or hex string (with or without 0x)",
      kind: :value
    }
  },
  returns: %{type: :string, description: "EIP-55 checksummed address"}
}
```

# `build_transaction`

```elixir
@spec build_transaction(binary(), binary() | {:raw, binary()}, keyword()) ::
  {:ok, Onchain.Transaction.V2.t()} | {:error, term()}
```

Build an unsigned EIP-1559 transaction.

## Parameters

  * `to` - Destination address (hex string or 20-byte binary) (value)
  * `calldata` - Raw binary calldata (use Hex.decode!/1 on Onchain.ABI.encode_call output), \{:raw, binary\} for literal bytes that start with 0x, or <<>> for plain ETH transfer (value)
  * `opts` - Required: :nonce, :chain_id. Optional: :gas_limit, :max_fee_per_gas, :max_priority_fee_per_gas, :value, :access_list. Gas params accept integers (wei) or \{n, :gwei\} tuples. (value)

## Returns

Unsigned EIP-1559 transaction struct (`{:ok, %Onchain.Transaction.V2{}} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      description: "Required: :nonce, :chain_id. Optional: :gas_limit, :max_fee_per_gas, :max_priority_fee_per_gas, :value, :access_list. Gas params accept integers (wei) or {n, :gwei} tuples.",
      kind: :value
    },
    to: %{
      description: "Destination address (hex string or 20-byte binary)",
      kind: :value
    },
    calldata: %{
      description: "Raw binary calldata (use Hex.decode!/1 on Onchain.ABI.encode_call output), {:raw, binary} for literal bytes that start with 0x, or <<>> for plain ETH transfer",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, %Onchain.Transaction.V2{}} | {:error, term()}",
    description: "Unsigned EIP-1559 transaction struct"
  }
}
```

# `build_transaction!`

```elixir
@spec build_transaction!(binary(), binary() | {:raw, binary()}, keyword()) ::
  Onchain.Transaction.V2.t()
```

Build an unsigned EIP-1559 transaction. Raises on error.

## Parameters

  * `to` - Destination address (hex string or 20-byte binary) (value)
  * `calldata` - Raw binary calldata (use Hex.decode!/1 on Onchain.ABI.encode_call output), \{:raw, binary\} for literal bytes that start with 0x, or <<>> for plain ETH transfer (value)
  * `opts` - Required: :nonce, :chain_id. Optional: :gas_limit, :max_fee_per_gas, :max_priority_fee_per_gas, :value, :access_list. Gas params accept integers (wei) or \{n, :gwei\} tuples. (value)

## Returns

Unsigned EIP-1559 transaction struct (`%Onchain.Transaction.V2{}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      description: "Required: :nonce, :chain_id. Optional: :gas_limit, :max_fee_per_gas, :max_priority_fee_per_gas, :value, :access_list. Gas params accept integers (wei) or {n, :gwei} tuples.",
      kind: :value
    },
    to: %{
      description: "Destination address (hex string or 20-byte binary)",
      kind: :value
    },
    calldata: %{
      description: "Raw binary calldata (use Hex.decode!/1 on Onchain.ABI.encode_call output), {:raw, binary} for literal bytes that start with 0x, or <<>> for plain ETH transfer",
      kind: :value
    }
  },
  returns: %{
    type: "%Onchain.Transaction.V2{}",
    description: "Unsigned EIP-1559 transaction struct"
  }
}
```

# `chain_id`

```elixir
@spec chain_id(GenServer.server()) :: integer()
```

Gets the chain id for this signer.

## Examples

    iex> signer_proc = Onchain.Test.Signer.start_signer()
    iex> Onchain.Signer.chain_id(signer_proc)
    5

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `encode_transaction`

```elixir
@spec encode_transaction(Onchain.Transaction.V2.t()) ::
  {:ok, String.t()} | {:error, {:encode_error, :unsigned_transaction}}
```

Encode a signed transaction to 0x-prefixed hex for broadcast.

## Parameters

  * `signed_trx` - Signed %Onchain.Transaction.V2\{\} struct (value)

## Returns

0x-prefixed hex string ready for eth_sendRawTransaction (`{:ok, String.t()} | {:error, {:encode_error, :unsigned_transaction}}`)

```elixir
# descripex:contract
%{
  params: %{
    signed_trx: %{
      description: "Signed %Onchain.Transaction.V2{} struct",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, {:encode_error, :unsigned_transaction}}",
    description: "0x-prefixed hex string ready for eth_sendRawTransaction",
    example: "0x02f8..."
  }
}
```

# `encode_transaction!`

```elixir
@spec encode_transaction!(Onchain.Transaction.V2.t()) :: String.t()
```

Encode a signed transaction to 0x-prefixed hex for broadcast. Raises on error.

## Parameters

  * `signed_trx` - Signed %Onchain.Transaction.V2\{\} struct (value)

## Returns

0x-prefixed hex string ready for broadcast (`string`)

```elixir
# descripex:contract
%{
  params: %{
    signed_trx: %{
      description: "Signed %Onchain.Transaction.V2{} struct",
      kind: :value
    }
  },
  returns: %{
    type: :string,
    description: "0x-prefixed hex string ready for broadcast"
  }
}
```

# `send_transaction`

```elixir
@spec send_transaction(binary(), binary() | {:raw, binary()}, keyword()) ::
  {:ok, String.t()} | {:error, term()}
```

Build, sign, encode, and broadcast a transaction in one call.

## Parameters

  * `to` - Destination address (hex string or 20-byte binary) (value)
  * `calldata` - Raw binary calldata (use Hex.decode!/1 on Onchain.ABI.encode_call output), \{:raw, binary\} for literal bytes that start with 0x, or <<>> for plain ETH transfer (value)
  * `opts` - Required: :private_key, :nonce, :chain_id, :rpc_url. Optional: :gas_limit, :max_fee_per_gas, :max_priority_fee_per_gas, :value, :access_list. When :gas_limit is omitted it is auto-estimated via eth_estimateGas (1.25× headroom) from the signer's address; a failed estimate returns \{:error, _\} rather than falling back to a default. (value)

## Returns

Transaction hash hex string (`{:ok, String.t()} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      description: "Required: :private_key, :nonce, :chain_id, :rpc_url. Optional: :gas_limit, :max_fee_per_gas, :max_priority_fee_per_gas, :value, :access_list. When :gas_limit is omitted it is auto-estimated via eth_estimateGas (1.25× headroom) from the signer's address; a failed estimate returns {:error, _} rather than falling back to a default.",
      kind: :value
    },
    to: %{
      description: "Destination address (hex string or 20-byte binary)",
      kind: :value
    },
    calldata: %{
      description: "Raw binary calldata (use Hex.decode!/1 on Onchain.ABI.encode_call output), {:raw, binary} for literal bytes that start with 0x, or <<>> for plain ETH transfer",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, String.t()} | {:error, term()}",
    description: "Transaction hash hex string"
  }
}
```

# `send_transaction!`

```elixir
@spec send_transaction!(binary(), binary() | {:raw, binary()}, keyword()) ::
  String.t()
```

Build, sign, encode, and broadcast a transaction in one call. Raises on error.

## Parameters

  * `to` - Destination address (hex string or 20-byte binary) (value)
  * `calldata` - Raw binary calldata (use Hex.decode!/1 on Onchain.ABI.encode_call output), \{:raw, binary\} for literal bytes that start with 0x, or <<>> for plain ETH transfer (value)
  * `opts` - Required: :private_key, :nonce, :chain_id, :rpc_url. Optional: :gas_limit, :max_fee_per_gas, :max_priority_fee_per_gas, :value, :access_list. When :gas_limit is omitted it is auto-estimated via eth_estimateGas (1.25× headroom) from the signer's address; a failed estimate returns \{:error, _\} rather than falling back to a default. (value)

## Returns

Transaction hash hex string (`string`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      description: "Required: :private_key, :nonce, :chain_id, :rpc_url. Optional: :gas_limit, :max_fee_per_gas, :max_priority_fee_per_gas, :value, :access_list. When :gas_limit is omitted it is auto-estimated via eth_estimateGas (1.25× headroom) from the signer's address; a failed estimate returns {:error, _} rather than falling back to a default.",
      kind: :value
    },
    to: %{
      description: "Destination address (hex string or 20-byte binary)",
      kind: :value
    },
    calldata: %{
      description: "Raw binary calldata (use Hex.decode!/1 on Onchain.ABI.encode_call output), {:raw, binary} for literal bytes that start with 0x, or <<>> for plain ETH transfer",
      kind: :value
    }
  },
  returns: %{type: :string, description: "Transaction hash hex string"}
}
```

# `sign`

```elixir
@spec sign(String.t(), GenServer.server(), Keyword.t()) ::
  {:ok, Onchain.Configuration.signature()} | {:error, term()}
```

Signs a message using this signing key.

## Examples

    iex> signer_proc = Onchain.Test.Signer.start_signer()
    iex> {:ok, sig} = Onchain.Signer.sign("test", signer_proc)
    iex> Onchain.Recover.recover_eth("test", sig)
    ...> |> Onchain.Hex.to_address()
    "0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"

    iex> signer_proc = Onchain.Test.Signer.start_signer()
    iex> {:ok, <<_r::256, _s::256, v::binary>>} = Onchain.Signer.sign("test", signer_proc, chain_id: 0x05f5e0ff)
    iex> :binary.decode_unsigned(v)
    0x05f5e0ff * 2 + 35 + 1

# `sign_direct`

```elixir
@spec sign_direct(
  binary(),
  binary(),
  Onchain.Signer.Backend.t() | {module(), atom(), [any()]},
  integer() | atom() | nil
) :: {:ok, Onchain.Configuration.signature()} | {:error, term()}
```

Directly sign a message, not using a signer process.

This is mostly used internally, but can be used safely externally as well.
The returned packed `r || s || v` signature is always low-s (EIP-2), regardless of
whether the backend normalized. Width follows `Onchain.Configuration.signature()`:
65 bytes when EIP-155 `v` fits in one byte, longer when it does not.

Backend carriers must use `:secp256k1` and receive the message's keccak digest
through `sign_payload/2`. Legacy MFAs receive the original message.

# `sign_message`

```elixir
@spec sign_message(binary(), GenServer.server(), Keyword.t()) ::
  {:ok, Onchain.Configuration.signature()} | {:error, term()}
```

Signs a message as `personal_sign` / ethers `signMessage` / viem `signMessage` do.

Two differences from `sign/3`, both of which trip callers coming from
JavaScript libraries: the EIP-191 `"\x19Ethereum Signed Message:\n"` prefix is
applied before hashing, and `v` is 27/28 rather than the EIP-155 form.
`sign/3` remains the primitive for transaction and EIP-712 digests, which
must **not** carry the prefix.

## Examples

    iex> signer_proc = Onchain.Test.Signer.start_signer()
    iex> {:ok, sig} = Onchain.Signer.sign_message("hello", signer_proc)
    iex> Onchain.Recover.recover_personal_sign("hello", sig) |> Onchain.Hex.to_address()
    "0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"

    iex> signer_proc = Onchain.Test.Signer.start_signer()
    iex> {:ok, <<_r::256, _s::256, v>>} = Onchain.Signer.sign_message("hello", signer_proc)
    iex> v in [27, 28]
    true

# `sign_transaction`

```elixir
@spec sign_transaction(Onchain.Transaction.V2.t(), binary(), pos_integer()) ::
  {:ok, Onchain.Transaction.V2.t()}
  | {:error, {:sign_error, term()} | {:invalid_private_key, term()}}
```

Sign a transaction with a private key.

## Parameters

  * `unsigned_trx` - Unsigned %Onchain.Transaction.V2\{\} struct (value)
  * `private_key` - 32-byte binary or hex string (with or without 0x) (value)
  * `chain_id` - Chain ID integer (1 = mainnet, 11155111 = Sepolia) (value)

## Returns

Signed transaction with signature fields populated (`{:ok, %Onchain.Transaction.V2{}} | {:error, {:sign_error, term()}}`)

```elixir
# descripex:contract
%{
  params: %{
    private_key: %{
      description: "32-byte binary or hex string (with or without 0x)",
      kind: :value
    },
    chain_id: %{
      description: "Chain ID integer (1 = mainnet, 11155111 = Sepolia)",
      kind: :value
    },
    unsigned_trx: %{
      description: "Unsigned %Onchain.Transaction.V2{} struct",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, %Onchain.Transaction.V2{}} | {:error, {:sign_error, term()}}",
    description: "Signed transaction with signature fields populated"
  }
}
```

# `sign_transaction!`

```elixir
@spec sign_transaction!(Onchain.Transaction.V2.t(), binary(), pos_integer()) ::
  Onchain.Transaction.V2.t()
```

Sign a transaction with a private key. Raises on error.

## Parameters

  * `unsigned_trx` - Unsigned %Onchain.Transaction.V2\{\} struct (value)
  * `private_key` - 32-byte binary or hex string (with or without 0x) (value)
  * `chain_id` - Chain ID integer (1 = mainnet, 11155111 = Sepolia) (value)

## Returns

Signed transaction with signature fields populated (`%Onchain.Transaction.V2{}`)

```elixir
# descripex:contract
%{
  params: %{
    private_key: %{
      description: "32-byte binary or hex string (with or without 0x)",
      kind: :value
    },
    chain_id: %{
      description: "Chain ID integer (1 = mainnet, 11155111 = Sepolia)",
      kind: :value
    },
    unsigned_trx: %{
      description: "Unsigned %Onchain.Transaction.V2{} struct",
      kind: :value
    }
  },
  returns: %{
    type: "%Onchain.Transaction.V2{}",
    description: "Signed transaction with signature fields populated"
  }
}
```

# `sign_typed_data`

```elixir
@spec sign_typed_data(Onchain.Typed.t(), GenServer.server(), Keyword.t()) ::
  {:ok, Onchain.Configuration.signature()} | {:error, term()}
```

Signs EIP-712 typed data as `eth_signTypedData_v4` / ethers `signTypedData` /
viem `signTypedData` do.

Encodes the `0x1901 || domainSeparator || hashStruct` payload with
`Onchain.Typed.encode/1` and signs it with `v` in 27/28. No EIP-191
prefix is involved — that is `sign_message/3`. `sign/3` with the encoded
payload gives the same digest with an EIP-155 `v` instead.

## Examples

    iex> signer_proc = Onchain.Test.Signer.start_signer()
    iex> typed = %Onchain.Typed{
    ...>   domain: %Onchain.Typed.Domain{name: "Complex Array", version: "1"},
    ...>   types: %{"Array" => %Onchain.Typed.Type{fields: [{"a", {:uint, 256}}, {"b", {:uint, 256}}, {"c", :string}, {"d", :bool}]}},
    ...>   value: %{"a" => 55, "b" => 66, "c" => "Hello", "d" => true}
    ...> }
    iex> {:ok, sig} = Onchain.Signer.sign_typed_data(typed, signer_proc)
    iex> Onchain.Recover.recover_eth(Onchain.Typed.encode(typed), sig) |> Onchain.Hex.to_address()
    "0x63Cc7c25e0cdb121aBb0fE477a6b9901889F99A7"
    iex> <<_r::256, _s::256, v>> = sig
    iex> v in [27, 28]
    true

# `start_link`

```elixir
@spec start_link(
  mfa: Onchain.Signer.Backend.t() | {module(), atom(), [any()]},
  name: GenServer.name() | nil
) :: GenServer.on_start()
```

Starts a new Onchain.Signer process.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
