# `X402.AuthCapture.EVM`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/auth_capture/evm.ex#L1)

EVM binding primitives for the `auth-capture` scheme: commerce-payments
deployments, `PaymentInfo` hashing, authorizer typed data, and
`AuthCaptureEscrow` calldata.

Everything here is pure: the module hashes, encodes, signs, and decodes
but never talks to a node. `X402.Verify.AuthCaptureEVM` layers the RPC
checks on top. These primitives do not submit transactions or provide
durable lifecycle orchestration.

## PaymentInfo

The escrow's struct keeps its canonical Solidity names on the wire so its
EIP-712 typehash matches the contract byte for byte. Maps use string keys:

    %{
      "operator" => "0x...",            # extra.captureAuthorizer
      "payer" => "0x...",               # the payload's `from`
      "receiver" => "0x...",            # requirements.payTo
      "token" => "0x...",               # requirements.asset
      "maxAmount" => "1000000",         # requirements.amount
      "preApprovalExpiry" => 1_740_675_754,
      "authorizationExpiry" => 1_740_758_554,
      "refundExpiry" => 1_741_276_954,
      "minFeeBps" => 100,
      "maxFeeBps" => 100,
      "feeReceiver" => "0x...",         # extra.feeRecipient
      "salt" => "0x..."                 # 32-byte hex, zero-padded
    }

Integer fields accept integers or decimal strings; `salt` is always the
`0x`-prefixed 32-byte hex spelling the spec pins.

## Payment identity

Two hashes derive from the struct and are not interchangeable:

* `signature_nonce/3` — `keccak256(abi.encode(chainId, escrow,
  keccak256(abi.encode(TYPEHASH, info with payer = 0))))`, the nonce
  inside the client's token authorization.
* `payment_info_hash/3` — `AuthCaptureEscrow.getHash(info)`, the escrow's
  canonical identifier keyed over the real payer.

Requires the optional `ex_keccak` dependency (and `ex_secp256k1` for
signing and recovery).

# `deployment`

```elixir
@type deployment() :: %{
  version: :v1_1 | :v1_0,
  escrow: String.t(),
  eip3009_collector: String.t(),
  permit2_collector: String.t(),
  refund_collector: String.t()
}
```

A resolved commerce-payments deployment.

# `encode_error`

```elixir
@type encode_error() ::
  :missing_dependency
  | :invalid_address
  | :invalid_amount
  | :invalid_bytes32
  | :invalid_word
  | {:invalid_payment_info, String.t()}
  | {:missing_field, String.t()}
```

Encoding failures shared by the hashing and calldata helpers.

# `operation`

```elixir
@type operation() :: :charge | :capture | :void | :refund
```

An authorizer-signed operation.

# `payment_info`

```elixir
@type payment_info() :: %{optional(String.t()) =&gt; term()}
```

A `PaymentInfo` struct in wire spelling (string keys).

# `payment_state`

```elixir
@type payment_state() :: %{
  collected?: boolean(),
  capturable_amount: non_neg_integer(),
  refundable_amount: non_neg_integer()
}
```

The decoded `paymentState(bytes32)` tuple.

# `consent_digest`
*since 0.9.0* 

```elixir
@spec consent_digest(operation(), map(), map(), :v1_1 | :v1_0) ::
  {:ok, &lt;&lt;_::256&gt;&gt;} | {:error, encode_error()}
```

Computes the EIP-712 digest an authorizer signs for an operation.

`params` uses the wire field names of the signed type: `paymentInfoHash`,
`amount`, `tokenCollector`, `collectorDataHash`, `feeAmount` (v1.1) or
`feeBps` (v1.0), `feeReceiver`, `expectedCapturableAmount`, and
`expectedRefundableAmount` — each operation reads the subset its type
declares. `Void` needs only `paymentInfoHash`.

# `consent_type`
*since 0.9.0* 

```elixir
@spec consent_type(operation(), :v1_1 | :v1_0) :: String.t()
```

The EIP-712 type string an operation's consent is signed over.

## Examples

    iex> X402.AuthCapture.EVM.consent_type(:void, :v1_1)
    "Void(bytes32 paymentInfoHash)"

# `fee_field`
*since 0.9.0* 

```elixir
@spec fee_field(:v1_1 | :v1_0) :: String.t()
```

The submitted fee field a deployment takes on `charge` and `capture`.

## Examples

    iex> X402.AuthCapture.EVM.fee_field(:v1_1)
    "feeAmount"

    iex> X402.AuthCapture.EVM.fee_field(:v1_0)
    "feeBps"

# `operator_domain`
*since 0.9.0* 

```elixir
@spec operator_domain(non_neg_integer(), String.t()) :: map()
```

The EIP-712 domain every authorizer signature uses, with the capture
authorizer as `verifyingContract`.

## Examples

    iex> X402.AuthCapture.EVM.operator_domain(84532, "0x1563915e194d8cfba1943570603f7606a3115508")
    %{
      name: "x402 Auth Capture Operator",
      version: "1",
      chain_id: 84532,
      verifying_contract: "0x1563915e194d8cfba1943570603f7606a3115508"
    }

# `recover_consent`
*since 0.9.0* 

```elixir
@spec recover_consent(operation(), map(), map(), :v1_1 | :v1_0, binary()) ::
  {:ok, String.t()} | {:error, term()}
```

Recovers the ECDSA signer of an operation's consent signature.

ERC-1271 authorizers need a node call and are handled by
`X402.Verify.AuthCaptureEVM`.

# `sign_consent`
*since 0.9.0* 

```elixir
@spec sign_consent(X402.Signer.t(), operation(), map(), map(), :v1_1 | :v1_0) ::
  {:ok, String.t()} | {:error, term()}
```

Signs an operation's consent digest with the receiver authorizer's signer.

Returns the `0x`-prefixed 65-byte signature.

# `authorize_calldata`
*since 0.9.0* 

```elixir
@spec authorize_calldata(map(), term(), String.t(), binary()) ::
  {:ok, binary()} | {:error, encode_error()}
```

Encodes `authorize(PaymentInfo, uint256 amount, address tokenCollector, bytes collectorData)`.

# `capture_calldata`
*since 0.9.0* 

```elixir
@spec capture_calldata(:v1_1 | :v1_0, map(), term(), term(), String.t()) ::
  {:ok, binary()} | {:error, encode_error()}
```

Encodes `capture(PaymentInfo, uint256 amount, <fee>, address feeReceiver)`
(fee argument per `charge_calldata/7`).

# `charge_calldata`
*since 0.9.0* 

```elixir
@spec charge_calldata(
  :v1_1 | :v1_0,
  map(),
  term(),
  String.t(),
  binary(),
  term(),
  String.t()
) ::
  {:ok, binary()} | {:error, encode_error()}
```

Encodes `charge(PaymentInfo, uint256 amount, address tokenCollector, bytes collectorData, <fee>, address feeReceiver)`.

The fee argument is `uint256 feeAmount` on v1.1 and `uint16 feeBps` on
v1.0 — the value passes through untouched, so pass the field the
deployment takes.

# `classify_revert`
*since 0.9.0* 

```elixir
@spec classify_revert(map()) :: atom() | nil
```

Classifies a node revert onto the spec's typed simulation reverts.

Reads the custom-error selector from the error `data` when present and
otherwise looks for the error name in the message. Returns `nil` for an
unmapped revert.

## Examples

    iex> X402.AuthCapture.EVM.classify_revert(%{code: 3, message: "execution reverted", data: "0xad7c145a"})
    :payment_already_collected

    iex> X402.AuthCapture.EVM.classify_revert(%{code: 3, message: "reverted: AfterRefundExpiry(1, 2)", data: nil})
    :refund_deadline_expired

    iex> X402.AuthCapture.EVM.classify_revert(%{code: 3, message: "boom", data: nil})
    nil

# `collector_data`
*since 0.9.0* 

```elixir
@spec collector_data(:eip3009 | :permit2, binary()) :: binary()
```

Encodes the signature bytes for the selected token collector.

EIP-3009 takes the original bytes; Permit2 takes `abi.encode(bytes)`.
An ERC-6492 wrapper remains intact inside those bytes.

## Examples

    iex> X402.AuthCapture.EVM.collector_data(:eip3009, <<1, 2>>)
    <<1, 2>>

    iex> byte_size(X402.AuthCapture.EVM.collector_data(:permit2, <<1, 2>>))
    96

# `custom_errors`
*since 0.9.0* 

```elixir
@spec custom_errors() :: %{required(&lt;&lt;_::32&gt;&gt;) =&gt; {String.t(), atom()}}
```

Returns the custom-error selector table (`selector => {name, reason}`).

# `decode_payment_state`
*since 0.9.0* 

```elixir
@spec decode_payment_state(term()) ::
  {:ok, payment_state()} | {:error, :invalid_payment_state}
```

Decodes the `paymentState(bytes32)` return data.

## Examples

    iex> X402.AuthCapture.EVM.decode_payment_state(
    ...>   "0x" <> String.duplicate("00", 31) <> "01" <>
    ...>     String.duplicate("00", 30) <> "2710" <> String.duplicate("00", 32)
    ...> )
    {:ok, %{collected?: true, capturable_amount: 10000, refundable_amount: 0}}

    iex> X402.AuthCapture.EVM.decode_payment_state("0x")
    {:error, :invalid_payment_state}

# `event_topic`
*since 0.9.0* 

```elixir
@spec event_topic(atom()) :: String.t()
```

Returns the `0x` topic of an escrow event.

## Examples

    iex> X402.AuthCapture.EVM.event_topic(:voided)
    "0xcadce8c3acb008e3e1c64ca7f60d22a3c87069183182b7dbb9e4d8cfb3a15842"

# `payment_state_calldata`
*since 0.9.0* 

```elixir
@spec payment_state_calldata(String.t()) ::
  {:ok, binary()} | {:error, :invalid_bytes32}
```

Encodes `paymentState(bytes32 paymentInfoHash)`.

# `reclaim_calldata`
*since 0.9.0* 

```elixir
@spec reclaim_calldata(map()) :: {:ok, binary()} | {:error, encode_error()}
```

Encodes `reclaim(PaymentInfo)` — the payer's own call after the capture
deadline, never relayed by a facilitator.

# `refund_calldata`
*since 0.9.0* 

```elixir
@spec refund_calldata(map(), term(), String.t(), binary()) ::
  {:ok, binary()} | {:error, encode_error()}
```

Encodes `refund(PaymentInfo, uint256 amount, address tokenCollector, bytes collectorData)`.

Facilitator-relayed refunds use the deployment's operator refund
collector with empty `collectorData`.

# `selector`
*since 0.9.0* 

```elixir
@spec selector(atom()) :: &lt;&lt;_::32&gt;&gt;
```

Returns the four-byte selector of an escrow function.

## Examples

    iex> X402.AuthCapture.EVM.selector(:void)
    <<0xFA, 0x1C, 0xAD, 0x17>>

    iex> X402.AuthCapture.EVM.selector(:payment_state)
    <<0x34, 0xB7, 0x78, 0xED>>

# `void_calldata`
*since 0.9.0* 

```elixir
@spec void_calldata(map()) :: {:ok, binary()} | {:error, encode_error()}
```

Encodes `void(PaymentInfo)`.

# `authorization_typed_data`
*since 0.9.0* 

```elixir
@spec authorization_typed_data(:eip3009 | :permit2, map(), map()) :: map()
```

Returns standard EIP-712 JSON for the client's token authorization.

Includes `types`, `primaryType`, a camel-case `domain`, and only the
signed message fields. Permit2 has no domain version or signed `from`.

# `permit_transfer_digest`
*since 0.9.0* 

```elixir
@spec permit_transfer_digest(map(), map()) ::
  {:ok, &lt;&lt;_::256&gt;&gt;} | {:error, encode_error()}
```

Computes the EIP-712 digest of a witness-less Permit2 `PermitTransferFrom`.

`domain` is the canonical Permit2 domain (see `X402.Permit2.domain/1`);
the authorization carries `permitted.token`, `permitted.amount`,
`spender`, `nonce` (decimal `uint256`), and `deadline`.

# `receive_authorization_digest`
*since 0.9.0* 

```elixir
@spec receive_authorization_digest(map(), map()) ::
  {:ok, &lt;&lt;_::256&gt;&gt;} | {:error, encode_error()}
```

Computes the EIP-712 digest of an EIP-3009 `ReceiveWithAuthorization`.

Same field layout as `TransferWithAuthorization`, different type name —
the token collector calls `receiveWithAuthorization`. `domain` is the
token's EIP-712 domain (see `X402.EIP712.domain/1`).

# `bound?`
*since 0.9.0* 

```elixir
@spec bound?(map()) :: boolean()
```

Whether salt binding is on: `extra.receiverAuthorizer` or `extra.policy`
is a non-zero address.

## Examples

    iex> X402.AuthCapture.EVM.bound?(%{"extra" => %{}})
    false

    iex> X402.AuthCapture.EVM.bound?(%{
    ...>   "extra" => %{"receiverAuthorizer" => "0x2222222222222222222222222222222222222222"}
    ...> })
    true

# `deployment`
*since 0.9.0* 

```elixir
@spec deployment(:v1_1 | :v1_0) :: deployment()
```

Returns a canonical commerce-payments deployment by version.

## Examples

    iex> X402.AuthCapture.EVM.deployment(:v1_1).escrow
    "0xf96815976523E00e65Be8f34cA5e64b4f41EB19c"

    iex> X402.AuthCapture.EVM.deployment(:v1_0).eip3009_collector
    "0x0E3dF9510de65469C4518D7843919c0b8C7A7757"

# `nonzero_address?`
*since 0.9.0* 

```elixir
@spec nonzero_address?(term()) :: boolean()
```

Whether the value is a well-formed, non-zero EVM address.

## Examples

    iex> X402.AuthCapture.EVM.nonzero_address?("0x2222222222222222222222222222222222222222")
    true

    iex> X402.AuthCapture.EVM.nonzero_address?("0x0000000000000000000000000000000000000000")
    false

    iex> X402.AuthCapture.EVM.nonzero_address?(nil)
    false

# `resolve_deployment`
*since 0.9.0* 

```elixir
@spec resolve_deployment(map()) :: {:ok, deployment()} | {:error, :invalid_escrow}
```

Resolves the deployment a requirements entry (or its `extra`) selects.

An absent `extra.authCaptureEscrow` is the v1.1 escrow; either canonical
escrow address (case-insensitive) selects its deployment; any other value
is `{:error, :invalid_escrow}`.

## Examples

    iex> {:ok, deployment} = X402.AuthCapture.EVM.resolve_deployment(%{"extra" => %{}})
    iex> deployment.version
    :v1_1

    iex> {:ok, deployment} =
    ...>   X402.AuthCapture.EVM.resolve_deployment(%{
    ...>     "extra" => %{"authCaptureEscrow" => "0xbdea0d1bcc5966192b070fdf62ab4ef5b4420cff"}
    ...>   })
    iex> deployment.version
    :v1_0

    iex> X402.AuthCapture.EVM.resolve_deployment(%{
    ...>   "extra" => %{"authCaptureEscrow" => "0x1111111111111111111111111111111111111111"}
    ...> })
    {:error, :invalid_escrow}

# `zero_address`
*since 0.9.0* 

```elixir
@spec zero_address() :: String.t()
```

The zero address, which the scheme reads for every absent operator field.

## Examples

    iex> X402.AuthCapture.EVM.zero_address()
    "0x0000000000000000000000000000000000000000"

# `check_fee`
*since 0.9.0* 

```elixir
@spec check_fee(
  :v1_1 | :v1_0,
  non_neg_integer(),
  non_neg_integer(),
  String.t() | nil,
  non_neg_integer(),
  non_neg_integer(),
  String.t()
) :: :ok | {:error, :fee_bps_out_of_range | :fee_receiver | :zero_fee_receiver}
```

Checks the submitted fee and fee receiver against the client-signed
bounds, per the spec's fee system.

v1.1 requires `amount * minFeeBps / 10000 <= feeAmount <= amount * maxFeeBps / 10000`;
v1.0 requires `minFeeBps <= feeBps <= maxFeeBps`. A non-zero
`PaymentInfo.feeReceiver` must equal the submitted one; a zero one admits
any non-zero address, and a zero submitted receiver with a non-zero fee
reverts onchain.

## Examples

    iex> X402.AuthCapture.EVM.check_fee(:v1_1, 750_000, 7500, "0x2222222222222222222222222222222222222222", 100, 100, "0x2222222222222222222222222222222222222222")
    :ok

    iex> X402.AuthCapture.EVM.check_fee(:v1_1, 750_000, 7501, "0x2222222222222222222222222222222222222222", 100, 100, "0x2222222222222222222222222222222222222222")
    {:error, :fee_bps_out_of_range}

    iex> X402.AuthCapture.EVM.check_fee(:v1_0, 750_000, 50, "0x2222222222222222222222222222222222222222", 100, 100, "0x2222222222222222222222222222222222222222")
    {:error, :fee_bps_out_of_range}

    iex> X402.AuthCapture.EVM.check_fee(:v1_1, 750_000, 7500, "0x3333333333333333333333333333333333333333", 100, 100, "0x2222222222222222222222222222222222222222")
    {:error, :fee_receiver}

# `fee_amount`
*since 0.9.0* 

```elixir
@spec fee_amount(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
```

The escrow's fee arithmetic: `amount * bps / 10000` with integer division.

## Examples

    iex> X402.AuthCapture.EVM.fee_amount(750_000, 100)
    7500

    iex> X402.AuthCapture.EVM.fee_amount(999, 100)
    9

# `bound_salt`
*since 0.9.0* 

```elixir
@spec bound_salt(String.t() | nil, String.t() | nil, String.t()) ::
  {:ok, String.t()} | {:error, encode_error()}
```

Computes the bound salt:
`keccak256(abi.encode(SALT_BINDING_TYPEHASH, receiverAuthorizer, policy, saltNonce))`.

Absent addresses are the zero address; `salt_nonce` is 32-byte hex.

# `bytes32_hex`
*since 0.9.0* 

```elixir
@spec bytes32_hex(term()) :: {:ok, String.t()} | {:error, :invalid_bytes32}
```

Spells a `uint256` (integer or decimal string) as the zero-padded 32-byte
hex the scheme pins for `salt` and `saltNonce`.

## Examples

    iex> X402.AuthCapture.EVM.bytes32_hex(255)
    {:ok, "0x00000000000000000000000000000000000000000000000000000000000000ff"}

    iex> X402.AuthCapture.EVM.bytes32_hex("0x" <> String.duplicate("ab", 32))
    {:ok, "0x" <> String.duplicate("ab", 32)}

    iex> X402.AuthCapture.EVM.bytes32_hex("nope")
    {:error, :invalid_bytes32}

# `encode_payment_info`
*since 0.9.0* 

```elixir
@spec encode_payment_info(map()) ::
  {:ok, binary()} | {:error, {:invalid_payment_info, String.t()}}
```

ABI-encodes a `PaymentInfo` struct into its twelve 32-byte words.

Validates each field against its Solidity width (`uint120`, `uint48`,
`uint16`) and returns `{:error, {:invalid_payment_info, field}}` for the
first field that does not fit.

## Examples

    iex> info = %{
    ...>   "operator" => "0x1563915e194d8cfba1943570603f7606a3115508",
    ...>   "payer" => "0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a",
    ...>   "receiver" => "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
    ...>   "token" => "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    ...>   "maxAmount" => "10000",
    ...>   "preApprovalExpiry" => 1,
    ...>   "authorizationExpiry" => 2,
    ...>   "refundExpiry" => 3,
    ...>   "minFeeBps" => 0,
    ...>   "maxFeeBps" => 100,
    ...>   "feeReceiver" => "0x0000000000000000000000000000000000000000",
    ...>   "salt" => "0x" <> String.duplicate("00", 31) <> "01"
    ...> }
    iex> {:ok, encoded} = X402.AuthCapture.EVM.encode_payment_info(info)
    iex> byte_size(encoded)
    384

    iex> X402.AuthCapture.EVM.encode_payment_info(%{"operator" => "0x1"})
    {:error, {:invalid_payment_info, "operator"}}

# `match_payment_info`
*since 0.9.0* 

```elixir
@spec match_payment_info(map(), map()) :: :ok | {:error, term()}
```

Checks stored `PaymentInfo` against its original requirements.

Reconstructs and compares the canonical ABI encoding, including every
Solidity width, and checks expiry ordering. The payer, preapproval expiry
and salt must come from retained client state; this does not verify their
signature, salt commitment, or onchain existence.

## Examples

    iex> X402.AuthCapture.EVM.match_payment_info(%{}, %{})
    {:error, {:missing_field, "captureAuthorizer"}}

# `parse_uint256`
*since 0.9.0* 

```elixir
@spec parse_uint256(term()) :: {:ok, non_neg_integer()} | {:error, :invalid_amount}
```

Parses a non-negative uint256 integer or decimal string.

Rejects signs, whitespace and strings longer than 78 digits before
integer conversion. Hex input is reserved for the bytes32 helpers.

## Examples

    iex> X402.AuthCapture.EVM.parse_uint256("1000")
    {:ok, 1000}

    iex> X402.AuthCapture.EVM.parse_uint256("+1")
    {:error, :invalid_amount}

# `payment_info`
*since 0.9.0* 

```elixir
@spec payment_info(map(), String.t(), non_neg_integer(), String.t()) ::
  {:ok, payment_info()} | {:error, {:missing_field, String.t()}}
```

Builds the `PaymentInfo` struct for a payment from its requirements.

`payer` is the client address, `pre_approval_expiry` the authorization's
`validBefore` / `deadline`, and `salt` the wire `payload.salt`. Every
other field comes from `requirements` and its `extra`, as the spec's
PaymentInfo appendix sets out.

## Examples

    iex> requirements = %{
    ...>   "amount" => "10000",
    ...>   "asset" => "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    ...>   "payTo" => "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
    ...>   "extra" => %{
    ...>     "captureAuthorizer" => "0x1563915e194d8cfba1943570603f7606a3115508",
    ...>     "feeRecipient" => "0x0000000000000000000000000000000000000000",
    ...>     "captureDeadline" => 1_800_000_000,
    ...>     "refundDeadline" => 1_800_100_000,
    ...>     "minFeeBps" => 0,
    ...>     "maxFeeBps" => 0
    ...>   }
    ...> }
    iex> {:ok, info} =
    ...>   X402.AuthCapture.EVM.payment_info(
    ...>     requirements,
    ...>     "0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a",
    ...>     1_799_000_000,
    ...>     "0x" <> String.duplicate("ab", 32)
    ...>   )
    iex> {info["operator"], info["maxAmount"], info["authorizationExpiry"]}
    {"0x1563915e194d8cfba1943570603f7606a3115508", "10000", 1800000000}

    iex> X402.AuthCapture.EVM.payment_info(%{"extra" => %{}}, "0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a", 1, "0x00")
    {:error, {:missing_field, "captureAuthorizer"}}

# `payment_info_from_payload`
*since 0.9.0* 

```elixir
@spec payment_info_from_payload(map(), map()) ::
  {:ok, payment_info()}
  | {:error, :payload_format | {:missing_field, String.t()}}
```

Reconstructs the `PaymentInfo` a client payment payload commits to.

Accepts the full v2 envelope or the inner `payload` map. The payer and
`preApprovalExpiry` come from the EIP-3009 `authorization` (`from`,
`validBefore`) or the `permit2Authorization` (`from`, `deadline`), and
the salt from `payload.salt`.

# `payment_info_hash`
*since 0.9.0* 

```elixir
@spec payment_info_hash(non_neg_integer(), String.t(), map()) ::
  {:ok, String.t()} | {:error, encode_error()}
```

Computes `AuthCaptureEscrow.getHash(paymentInfo)` — the escrow's canonical
payment identifier, as `0x`-prefixed hex.

`keccak256(abi.encode(chainId, escrow, keccak256(abi.encode(PAYMENT_INFO_TYPEHASH, info))))`.

# `payment_info_type`
*since 0.9.0* 

```elixir
@spec payment_info_type() :: String.t()
```

The `PaymentInfo` EIP-712 type string, whose keccak256 is the escrow's
`PAYMENT_INFO_TYPEHASH`.

# `permit2_nonce`
*since 0.9.0* 

```elixir
@spec permit2_nonce(String.t()) :: {:ok, String.t()} | {:error, :invalid_bytes32}
```

Spells a 32-byte hex value as the decimal `uint256` string Permit2 nonces
use on the wire.

## Examples

    iex> X402.AuthCapture.EVM.permit2_nonce("0x" <> String.duplicate("00", 31) <> "ff")
    {:ok, "255"}

    iex> X402.AuthCapture.EVM.permit2_nonce("0xzz")
    {:error, :invalid_bytes32}

# `random_bytes32`
*since 0.9.0* 

```elixir
@spec random_bytes32() :: String.t()
```

Returns fresh random 32 bytes as zero-padded `0x` hex — a `salt` when
unbound, a `saltNonce` when bound.

## Examples

    iex> X402.AuthCapture.EVM.random_bytes32() =~ ~r/^0x[0-9a-f]{64}$/
    true

# `salt_binding_type`
*since 0.9.0* 

```elixir
@spec salt_binding_type() :: String.t()
```

The salt-binding type string (`SALT_BINDING_TYPEHASH` preimage).

# `signature_nonce`
*since 0.9.0* 

```elixir
@spec signature_nonce(non_neg_integer(), String.t(), map()) ::
  {:ok, String.t()} | {:error, encode_error()}
```

Computes the payment's `signatureNonce`: the payer-agnostic identity that
becomes the EIP-3009 `nonce` (as `0x` hex) and the Permit2 `nonce` (as the
decimal `uint256`, see `permit2_nonce/1`).

The struct is hashed with `payer` zeroed and every other field holding
its onchain value.

---

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