# `MPP.Methods.EVM`
[🔗](https://github.com/ZenHive/mpp/blob/v0.10.0/lib/mpp/methods/evm.ex#L4)

Generic EVM payment method — verifies on-chain ERC-20 or native ETH transfers.

Works on any EVM chain (Ethereum, Base, Polygon, Arbitrum, etc.) by configuring
the appropriate RPC endpoint and chain ID. The client broadcasts a transaction,
then sends the transaction hash as a credential. The server fetches the receipt
via RPC and verifies the transfer matches the charge intent.

## Configuration

Pass EVM-specific config via `:method_config` in `MPP.Plug` opts:

    plug MPP.Plug,
      secret_key: "hmac-secret",
      realm: "api.example.com",
      method: MPP.Methods.EVM,
      amount: "1000000",
      currency: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      method_config: %{
        "rpc_url" => "https://mainnet.infura.io/v3/YOUR_KEY",
        "chain_id" => 1
      }

## Config Keys

  * `"rpc_url"` — (required) JSON-RPC endpoint URL for the target EVM chain
  * `"chain_id"` — (optional) network chain ID, included in challenge details
    so the client knows which chain to transact on
  * `"store"` — (optional) replay-dedup store, **on by default** (see "Replay
    protection"): when absent, the app-started `MPP.Tempo.ConCacheStore` enforces
    single-use of each on-chain transaction hash out of the box. Pass a module
    implementing `MPP.Tempo.Store` (Redis/Postgres for multi-node) or
    `{MPP.Tempo.ConCacheStore, opts}`; a configured store MUST implement the atomic
    `check_and_mark/2`. Pass `store: false` to opt out (not recommended)
  * `"req_options"` — (optional) merged into the `Onchain.RPC` call as
    `:req_options` (e.g. `[plug: {Req.Test, MyMod}]`) for testing stubs

## Replay protection

This method verifies an **already-broadcast** transaction by hash and matches it
against the charge's `token`/`to`/`amount`. That match alone does not bind the
proof to a single use, so without a dedup store one settled transfer can satisfy
unlimited future charges on a static-price route (replay).

Configure `"store"` to make each transaction hash single-use: the hash is checked
before verification and atomically committed after, reusing the same
`MPP.Tempo.Store` behaviour (and `MPP.Tempo.ConCacheStore`) as the Tempo method.
The store's TTL must be ≥ your challenge `expires_in` (a good default is 2×) so a
hash cannot be evicted and replayed while its challenge is still valid.

**Residual limitation:** a store makes each transaction single-use, but the
hash-pointer credential carries no on-chain binding to a *specific* challenge — so
on its *first* presentation, any unrelated transfer that happens to match
`token`/`to`/`amount` is accepted. Binding a payment to one challenge on-chain is
the EIP-3009 authorization path (nonce = `keccak256(challengeId ‖ realm)`), tracked
separately in the roadmap; prefer it when per-challenge attribution is required.

## Credential Payload

The credential `payload` map must contain:

  * `"hash"` — (required) 0x-prefixed transaction hash (32 bytes, 66 hex chars)

## Currency Conventions

  * ERC-20 tokens: use the token contract address as currency
    (e.g., `"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"` for USDC on Ethereum)
  * Native ETH: use `"ETH"` or the zero address
    (`"0x0000000000000000000000000000000000000000"`)

## Dependencies

Requires the `onchain` package for address validation and Transfer event parsing.

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `challenge_method_details` | 1 | Return EVM-specific fields (`chainId`) for the 402 challenge. | `charge: value` |
| `verify` | 2 | Verify an EVM credential by checking on-chain settlement via transaction receipt. | `payload: value`, `charge: value` |
| `validate_config!` | 1 | Validate EVM method_config at init time. Raises on missing `rpc_url`. | `config: value` |
| `method_name` | 0 | Return the payment method identifier for EVM. | - |

# `challenge_method_details`

```elixir
@spec challenge_method_details(MPP.Intents.Charge.t()) :: map() | nil
@spec challenge_method_details(MPP.Intents.Charge.t()) :: map() | nil
```

Return EVM-specific fields (`chainId`) for the 402 challenge.

## Parameters

  * `charge` - Charge struct with method_details optionally containing `chain_id` (value)

## Returns

Map with `chainId` key, or `nil` if no `chain_id` configured (`map_or_nil`)

```elixir
# descripex:contract
%{
  params: %{
    charge: %{
      description: "Charge struct with method_details optionally containing `chain_id`",
      kind: :value
    }
  },
  returns: %{
    type: :map_or_nil,
    description: "Map with `chainId` key, or `nil` if no `chain_id` configured"
  }
}
```

# `method_name`

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

Return the payment method identifier for EVM.

```elixir
# descripex:contract
%{}
```

# `validate_config!`

```elixir
@spec validate_config!(map()) :: :ok
@spec validate_config!(map()) :: :ok
```

Validate EVM method_config at init time. Raises on missing `rpc_url`.

## Parameters

  * `config` - method_config map to validate (value)

## Returns

`:ok` on success, raises `ArgumentError` on missing keys (`atom`)

```elixir
# descripex:contract
%{
  params: %{
    config: %{description: "method_config map to validate", kind: :value}
  },
  returns: %{
    type: :atom,
    description: "`:ok` on success, raises `ArgumentError` on missing keys"
  }
}
```

# `verify`

```elixir
@spec verify(map(), MPP.Intents.Charge.t()) ::
  {:ok, MPP.Receipt.t()} | {:error, MPP.Errors.t()}
```

Verify an EVM credential by checking on-chain settlement via transaction receipt.

## Parameters

  * `payload` - Credential payload map with `"hash"` (0x-prefixed transaction hash) (value)
  * `charge` - Charge intent struct with amount, currency, recipient, and method_details (including `rpc_url`) (value)

## Returns

`{:ok, receipt}` on success, `{:error, error}` on failure (`tagged_tuple`)

## Errors

  * `:invalid_payload`
  * `:verification_failed`

```elixir
# descripex:contract
%{
  params: %{
    payload: %{
      description: "Credential payload map with `\"hash\"` (0x-prefixed transaction hash)",
      kind: :value
    },
    charge: %{
      description: "Charge intent struct with amount, currency, recipient, and method_details (including `rpc_url`)",
      kind: :value
    }
  },
  errors: [:invalid_payload, :verification_failed],
  returns: %{
    type: :tagged_tuple,
    description: "`{:ok, receipt}` on success, `{:error, error}` on failure"
  }
}
```

---

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