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"req_options"— (optional) merged into theOnchain.RPCcall as:req_options(e.g.[plug: {Req.Test, MyMod}]) for testing stubs
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 (optional dependency) for address validation
and Transfer event parsing. The method checks availability at init time
via validate_config!/1.
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 or unavailable onchain dependency. | config: value |
method_name | 0 | Return the payment method identifier for EVM. | - |
Summary
Functions
Return EVM-specific fields (chainId) for the 402 challenge.
Return the payment method identifier for EVM.
Validate EVM method_config at init time. Raises on missing rpc_url or unavailable onchain dependency.
Verify an EVM credential by checking on-chain settlement via transaction receipt.
Functions
@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 containingchain_id(value)
Returns
Map with chainId key, or nil if no chain_id configured (map_or_nil)
# 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"
}
}
@spec method_name() :: String.t()
Return the payment method identifier for EVM.
# descripex:contract
%{}
Validate EVM method_config at init time. Raises on missing rpc_url or unavailable onchain dependency.
Parameters
config- method_config map to validate (value)
Returns
:ok on success, raises ArgumentError on missing keys (atom)
# descripex:contract
%{
params: %{
config: %{description: "method_config map to validate", kind: :value}
},
returns: %{
type: :atom,
description: "`:ok` on success, raises `ArgumentError` on missing keys"
}
}
@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 (includingrpc_url) (value)
Returns
{:ok, receipt} on success, {:error, error} on failure (tagged_tuple)
Errors
:invalid_payload:verification_failed
# 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"
}
}