# `X402.Extensions.ERC20ApprovalGasSponsoring`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/extensions/erc20_approval_gas_sponsoring.ex#L1)

Builds and validates the `erc20ApprovalGasSponsoring` extension.

The extension enables a gasless [Permit2](https://github.com/Uniswap/permit2)
approval flow for ERC-20 tokens that do **not** implement EIP-2612. The
client signs — but does not broadcast — a normal EVM transaction calling
`approve(Permit2, amount)`; the facilitator funds the wallet's gas if
needed, broadcasts the approval, and settles via `x402Permit2Proxy` in one
atomic bundle.

Unlike `X402.Extensions.EIP2612GasSponsoring`, nothing here is EIP-712
typed data: the signed artifact is a full RLP-encoded transaction whose
`nonce` must match the wallet's current on-chain nonce and whose fees must
match live network prices. Producing it therefore requires chain access
and transaction-signing tooling outside this library — this module builds,
validates, and attaches the extension data *around* a pre-signed
transaction supplied by the caller.

Server side, `build_extension/0` declares support under
`extensions.erc20ApprovalGasSponsoring` in a `PAYMENT-REQUIRED` response,
and `extract_info/1` / `validate_info/1` check the client-populated data
echoed back in a `PaymentPayload`.

Client side, `build_info/1` assembles the wire info for a pre-signed
approval transaction, `put_info/2` attaches it to a payload's extensions,
and `enricher/1` packages both for `X402.Client.build_payment/3`:

    {:ok, payload} =
      X402.Client.build_payment(payment_required, signer,
        extensions: [
          X402.Extensions.ERC20ApprovalGasSponsoring.enricher(
            from: wallet_address,
            signed_transaction: signed_approve_tx_hex
          )
        ]
      )

See the
[erc20ApprovalGasSponsoring extension spec](https://github.com/x402-foundation/x402/blob/main/specs/extensions/erc20_gas_sponsoring.md).

# `info`

```elixir
@type info() :: %{optional(binary()) =&gt; binary()}
```

Client-populated extension info in wire shape: string keys `"from"`,
`"asset"`, `"spender"`, `"amount"`, `"signedTransaction"`, and
`"version"`.

# `info_error`

```elixir
@type info_error() ::
  :extension_missing
  | {:missing_info_field, String.t()}
  | {:invalid_info_field, String.t()}
```

# `t`

```elixir
@type t() :: %{required(binary()) =&gt; map()}
```

A built `extensions.erc20ApprovalGasSponsoring` declaration (`info` + `schema`).

# `build_extension`
*since 0.6.0* 

```elixir
@spec build_extension() :: t()
```

Builds the server-side extension declaration (`info` + `schema`).

Resource servers advertise support by placing the declaration under
`extensions.erc20ApprovalGasSponsoring` in a `PAYMENT-REQUIRED` response;
the client populates the actual approval data.

## Examples

    iex> ext = X402.Extensions.ERC20ApprovalGasSponsoring.build_extension()
    iex> ext["info"]["version"]
    "1"
    iex> ext["schema"]["required"]
    ["from", "asset", "spender", "amount", "signedTransaction", "version"]

# `build_info`
*since 0.6.0* 

```elixir
@spec build_info(keyword()) ::
  {:ok, info()} | {:error, {:invalid_info_field, String.t()}}
```

Builds the client-populated info for a pre-signed approval transaction.

Validates the declared fields structurally (the transaction itself is
opaque to this library — the facilitator decodes and verifies it against
the declared `from`, `asset`, `spender`, and `amount`) and returns the
wire-shaped info, ready for `put_info/2`.

## Options

* `:from` (`t:String.t/0`) - Required. The address of the wallet that signed the approval transaction.

* `:signed_transaction` (`t:String.t/0`) - Required. The RLP-encoded signed EIP-1559 transaction calling
  `approve(spender, amount)`, as a `0x`-prefixed hex string. Its signer,
  target contract, calldata, nonce, and fees are verified on-chain by the
  facilitator.

* `:amount` - The approval amount declared alongside the transaction, in atomic
  units. Must match the amount in the transaction's calldata. Defaults
  to MaxUint256, matching the reference client implementations.

* `:spender` (`t:String.t/0`) - The approved spender declared alongside the transaction. Must match
  the spender in the transaction's calldata. Defaults to the canonical
  Permit2 contract.

* `:asset` (`t:String.t/0`) - Required. The ERC-20 token contract the transaction approves.

## Examples

    iex> {:ok, info} =
    ...>   X402.Extensions.ERC20ApprovalGasSponsoring.build_info(
    ...>     from: "0x857b06519E91e3A54538791bDbb0E22373e36b66",
    ...>     asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    ...>     signed_transaction: "0x02f8" <> String.duplicate("ab", 100)
    ...>   )
    iex> info["spender"]
    "0x000000000022D473030F116dDEE9F6B43aC78BA3"
    iex> info["version"]
    "1"

    iex> X402.Extensions.ERC20ApprovalGasSponsoring.build_info(
    ...>   from: "0x123",
    ...>   asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    ...>   signed_transaction: "0xabcd"
    ...> )
    {:error, {:invalid_info_field, "from"}}

# `enricher`
*since 0.6.0* 

```elixir
@spec enricher(keyword()) :: (map(), map() | nil -&gt;
                          {:ok, map()}
                          | {:error,
                             {:invalid_info_field, String.t()}
                             | :invalid_requirements})
```

Returns an enricher for `X402.Client.build_payment/3`'s `:extensions`.

The enricher attaches the pre-signed approval transaction via
`build_info/1` and `put_info/2` — but only when the server advertised
`erc20ApprovalGasSponsoring` in the `PaymentRequired` extensions;
otherwise the payload passes through unchanged. `:asset` defaults to the
accepted requirements' `asset`.

## Options

* `:from` (`t:String.t/0`) - Required. The address of the wallet that signed the approval transaction.

* `:signed_transaction` (`t:String.t/0`) - Required. The RLP-encoded signed EIP-1559 transaction calling
  `approve(spender, amount)`, as a `0x`-prefixed hex string. Its signer,
  target contract, calldata, nonce, and fees are verified on-chain by the
  facilitator.

* `:amount` - The approval amount declared alongside the transaction, in atomic
  units. Must match the amount in the transaction's calldata. Defaults
  to MaxUint256, matching the reference client implementations.

* `:spender` (`t:String.t/0`) - The approved spender declared alongside the transaction. Must match
  the spender in the transaction's calldata. Defaults to the canonical
  Permit2 contract.

* `:asset` (`t:String.t/0`) - The ERC-20 token contract the transaction
  approves. Defaults to the accepted payment
  requirements' `asset`.

# `extract_info`
*since 0.6.0* 

```elixir
@spec extract_info(map()) :: {:ok, info()} | {:error, info_error()}
```

Extracts the client-populated info from a `PaymentPayload` map.

Returns the info when the extension is present and every required field
is populated. Field formats are not checked here — see `validate_info/1`.

## Examples

    iex> X402.Extensions.ERC20ApprovalGasSponsoring.extract_info(%{"payload" => %{}})
    {:error, :extension_missing}

# `key`
*since 0.6.0* 

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

Returns the extension key, `"erc20ApprovalGasSponsoring"`.

## Examples

    iex> X402.Extensions.ERC20ApprovalGasSponsoring.key()
    "erc20ApprovalGasSponsoring"

# `max_uint256`
*since 0.6.0* 

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

Returns MaxUint256 as a decimal string — the default approval amount.

## Examples

    iex> X402.Extensions.ERC20ApprovalGasSponsoring.max_uint256()
    "115792089237316195423570985008687907853269984665640564039457584007913129639935"

# `permit2_address`
*since 0.6.0* 

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

Returns the canonical Permit2 contract address — the default spender.

## Examples

    iex> X402.Extensions.ERC20ApprovalGasSponsoring.permit2_address()
    "0x000000000022D473030F116dDEE9F6B43aC78BA3"

# `put_info`
*since 0.6.0* 

```elixir
@spec put_info(map(), info()) :: map()
```

Attaches client-populated info to a payload's extensions.

Places the info under `extensions.erc20ApprovalGasSponsoring.info`,
following the append-only rule: when the payload already echoes the
server's declaration, server-declared info fields are preserved (they win
over client values) and the declared `schema` is kept; the client's
fields are added alongside them.

## Examples

    iex> info = %{"from" => "0x1111111111111111111111111111111111111111"}
    iex> payload = X402.Extensions.ERC20ApprovalGasSponsoring.put_info(%{"payload" => %{}}, info)
    iex> payload["extensions"]["erc20ApprovalGasSponsoring"]["info"]["from"]
    "0x1111111111111111111111111111111111111111"

# `schema`
*since 0.6.0* 

```elixir
@spec schema() :: map()
```

Returns the JSON Schema (Draft 2020-12) for the client-populated info.

# `validate_info`
*since 0.6.0* 

```elixir
@spec validate_info(term()) :: :ok | {:error, info_error()}
```

Validates the format of client-populated info.

Checks that addresses match `^0x[a-fA-F0-9]{40}$`, that `amount` is a
decimal string, that `signedTransaction` is a `0x`-prefixed hex string,
and that `version` is a dotted numeric version.

## Examples

    iex> X402.Extensions.ERC20ApprovalGasSponsoring.validate_info(%{
    ...>   "from" => "0x857b06519E91e3A54538791bDbb0E22373e36b66",
    ...>   "asset" => "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    ...>   "spender" => "0x000000000022D473030F116dDEE9F6B43aC78BA3",
    ...>   "amount" => "10000",
    ...>   "signedTransaction" => "0xabcdef",
    ...>   "version" => "1"
    ...> })
    :ok

    iex> X402.Extensions.ERC20ApprovalGasSponsoring.validate_info(%{"from" => "0x123"})
    {:error, {:invalid_info_field, "from"}}

---

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