MPP.Credential (mpp v0.14.0)

Copy Markdown View Source

Payment credential — the client's response to a 402 challenge.

A credential is sent in the Authorization: Payment <base64url JSON> header after the client has fulfilled payment. It echoes the original challenge parameters alongside method-specific payment proof, enabling the server to verify both the challenge binding (via MPP.Challenge.verify/2) and the payment itself (via the method module).

Wire Format

The base64url-decoded JSON contains:

%{
  "challenge" => %{...},   # Echoed challenge parameters
  "payload"   => %{...},   # Method-specific payment proof
  "source"    => "did:..."  # (optional) Payer identifier
}

The challenge.request field remains as its raw base64url string — never re-serialized — to preserve the exact bytes used in HMAC computation.

Hash credentials

Several payment methods (Tempo, EVM, Hedera, Stellar, Near Intents) accept payload.type = "hash": the client broadcasts a transfer and presents the confirmed transaction identifier. That type was backfilled as first-class in the EVM spec (draft-evm-charge-00 §1.3, mpp-specs #261) alongside permit2 / authorization / transaction.

decode/1 still treats payload as an opaque map — the same as mppx Credential.deserialize and mpp-rs PaymentCredential.payload. Typed hash access is hash_payload/1 (construct) and parse_hash_payload/1 (extract), matching mpp-rs PaymentPayload::hash / charge_payload(). Method-specific hash format (0x-prefixed 32-byte hex vs chain-native) is verified by the method, not here. MPP.Verifier rejects a well-formed hash payload when the method's credential_types/0 does not include "hash".

Fields

  • challenge — echoed MPP.Challenge struct from the 402 response
  • payload — method-specific payment proof (opaque map at this layer)
  • source — (optional) payer identifier, recommended as DID format

API Functions

FunctionArityDescriptionParam Kinds
parse_hash_payload1Extract the hash from a type="hash" payload (mpp-rs PaymentPayload / charge_payload()).payload: value
hash_payload1Build a type="hash" credential payload map (mpp-rs PaymentPayload::hash).hash: value
encode1Encode a credential to a base64url JSON string (no padding) for the Authorization header.credential: value
decode1Decode a base64url JSON string into a credential with echoed challenge validation.encoded: value

Summary

Functions

Decode a base64url JSON string into a credential with echoed challenge validation.

Encode a credential to a base64url JSON string (no padding) for the Authorization header.

Build a type="hash" credential payload map (mpp-rs PaymentPayload::hash).

Extract the hash from a type="hash" payload (mpp-rs PaymentPayload / charge_payload()).

Types

t()

@type t() :: %MPP.Credential{
  challenge: MPP.Challenge.t(),
  payload: map(),
  source: String.t() | nil
}

Functions

decode(encoded)

@spec decode(String.t()) :: {:ok, t()} | {:error, atom()}

Decode a base64url JSON string into a credential with echoed challenge validation.

Parameters

  • encoded - Base64url-encoded JSON credential string (value)

Returns

{:ok, credential} on success, {:error, reason} on failure (tagged_tuple)

Errors

  • :invalid_base64
  • :invalid_json
  • :missing_required_fields
  • :invalid_optional_field
  • :empty_id
  • :invalid_method
  • :invalid_request
  • :invalid_digest

Composes With

  • encode
# descripex:contract
%{
  params: %{
    encoded: %{
      description: "Base64url-encoded JSON credential string",
      kind: :value
    }
  },
  errors: [:invalid_base64, :invalid_json, :missing_required_fields,
   :invalid_optional_field, :empty_id, :invalid_method, :invalid_request,
   :invalid_digest],
  returns: %{
    type: :tagged_tuple,
    description: "`{:ok, credential}` on success, `{:error, reason}` on failure"
  },
  composes_with: [:encode]
}

encode(credential)

@spec encode(t()) :: String.t()

Encode a credential to a base64url JSON string (no padding) for the Authorization header.

Parameters

  • credential - Credential struct to encode (value)

Returns

Base64url-encoded JSON string (string)

Composes With

  • decode
# descripex:contract
%{
  params: %{
    credential: %{description: "Credential struct to encode", kind: :value}
  },
  returns: %{type: :string, description: "Base64url-encoded JSON string"},
  composes_with: [:decode]
}

hash_payload(hash)

@spec hash_payload(String.t()) :: map()

Build a type="hash" credential payload map (mpp-rs PaymentPayload::hash).

Parameters

  • hash - Transaction hash or chain-native identifier string (value)

Returns

Map with type=hash and hash — no signature field (map)

Composes With

  • parse_hash_payload
  • encode
# descripex:contract
%{
  params: %{
    hash: %{
      description: "Transaction hash or chain-native identifier string",
      kind: :value
    }
  },
  returns: %{
    type: :map,
    description: "Map with type=hash and hash — no signature field"
  },
  composes_with: [:parse_hash_payload, :encode]
}

parse_hash_payload(payload)

@spec parse_hash_payload(t() | map()) ::
  {:ok, String.t()} | {:error, :missing_hash | :not_hash_payload}

Extract the hash from a type="hash" payload (mpp-rs PaymentPayload / charge_payload()).

Parameters

  • payload - Credential struct or payload map (value)

Returns

{:ok, hash} on a hash payload, {:error, reason} otherwise (tagged_tuple)

Errors

  • :missing_hash
  • :not_hash_payload

Composes With

  • hash_payload
# descripex:contract
%{
  params: %{
    payload: %{description: "Credential struct or payload map", kind: :value}
  },
  errors: [:missing_hash, :not_hash_payload],
  returns: %{
    type: :tagged_tuple,
    description: "`{:ok, hash}` on a hash payload, `{:error, reason}` otherwise"
  },
  composes_with: [:hash_payload]
}