Extension: builder-code

August 31, 2026 · View on GitHub

Summary

The builder-code extension enables on-chain attribution tracking for x402 payments by appending ERC-8021 Schema 2 builder codes to settlement transaction calldata. It attributes which application exposed the paid endpoint and which facilitator settled the payment.

This extension implements Schema 2 (CBOR-encoded) of ERC-8021. The m (custom metadata) and r (custom registries) fields are not supported.


ERC-8021 Schema 2 Overview

ERC-8021 defines a structured data suffix appended to transaction calldata for entity attribution. Schema 2 uses CBOR encoding for extensibility.

Suffix Format

The complete suffix appended to calldata is (ordered end of calldata backwards):

ComponentSizeDescription
ercMarker16 bytesConstant identifier: 80218021802180218021802180218021
schemaId1 byte0x02 for Schema 2
cborLength2 bytesLength of CBOR data (big-endian)
cborDatavariableCBOR-encoded map of attribution fields

Wire order: [cborData][cborLength (2B)][schemaId (1B)][ercMarker (16B)]

CBOR Map Fields

KeyTypeDescription
astringApp code — the application that exposed the paid endpoint
wstringWallet code — the facilitator that settled the payment on-chain
sstring or array of stringsService code(s) — client-provided attribution

All fields are optional.

Builder Code Format

Codes must match the pattern ^[a-z0-9_]{1,32}$:

  • Length: 1-32 characters
  • Characters: lowercase alphanumeric and underscores only

PaymentRequired

The application declares its builder code per-route in the payment middleware configuration.

{
  "x402Version": 2,
  "error": "Payment required",
  "accepts": [ ... ],
  "extensions": {
    "builder-code": {
      "info": {
        "a": "my_app"
      },
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "a": {
            "type": "string",
            "pattern": "^[a-z0-9_]{1,32}$",
            "description": "App builder code"
          },
          "w": {
            "type": "string",
            "pattern": "^[a-z0-9_]{1,32}$",
            "description": "Wallet builder code"
          },
          "s": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[a-z0-9_]{1,32}$"
            },
            "description": "Service builder codes"
          }
        },
        "additionalProperties": false
      }
    }
  }
}

PaymentPayload

The client attaches its own service code(s) (s) when the BuilderCodeClientExtension is registered. When the server declared builder-code in PaymentRequired, the client also echoes the server's app code (a).

{
  "extensions": {
    "builder-code": {
      "a": "my_app",
      "s": "my_client"
    }
  }
}

When the server did not declare builder-code, the client still attaches s but MUST NOT set a:

{
  "extensions": {
    "builder-code": {
      "s": "my_client"
    }
  }
}

Layered clients (e.g. an MCP server acting as middleware) can attribute multiple participants by listing several codes as an array:

{
  "extensions": {
    "builder-code": {
      "a": "my_app",
      "s": ["base_mcp", "demo_app"]
    }
  }
}

The w (wallet) field is not set by the client. It is added by the facilitator at settlement time.


Builder Code Fields

FieldSet byWhenDescription
aApplicationPer-route middleware configurationIdentifies the application exposing the paid endpoint
wFacilitatorSettlementIdentifies the facilitator settling the payment on-chain
sClient, server, and/or facilitatorPayment payload construction / settlementIdentifies participants in the payment path
FieldServer advertises builder-code?Client behavior
aYesEcho server value (via core merge)
aNoMUST NOT set
sEitherSHOULD attach when BuilderCodeClientExtension is registered; when the server also declares s, core merge concatenates client then server codes (deduped)

s accepts a bare string or an array of strings on either side; a scalar on one side merges as a single-element array against an array on the other.

Each party that can contribute to s has its own dedicated, non-overlapping reservation so it cannot be crowded out by another party:

PartyConstantMax entries
ClientMAX_CLIENT_SERVICE_CODES5
ServerMAX_SERVER_SERVICE_CODES5
FacilitatorMAX_FACILITATOR_SERVICE_CODES1
TotalMAX_SERVICE_CODES11

Declaring more than a party's own reservation at that layer MUST be rejected (see Builder Code Validation).


Facilitator Behavior

When a facilitator settles a payment containing the builder-code extension, it:

  1. Reads a (app code) and s (service codes) from the payment payload extensions
  2. Adds its own builder code as the w (wallet) field
  3. Optionally appends its own service code to s (deduped against the echoed entries), up to its MAX_FACILITATOR_SERVICE_CODES reservation
  4. Encodes the combined data as an ERC-8021 Schema 2 CBOR suffix
  5. Appends the suffix to the settlement transaction calldata

The facilitator's builder code and service code are configured at initialization and validated against the same ^[a-z0-9_]{1,32}$ pattern.

Calldata Suffix Construction

The facilitator builds the suffix as follows:

  1. CBOR-encode a map containing all present fields (a, s, w)
  2. Compute cborLength as the byte length of the CBOR data (2 bytes, big-endian)
  3. Append: [cborData][cborLength][0x02][80218021802180218021802180218021]
  4. Return the hex-encoded result for the settlement mechanism to append to calldata

Protocol Flow

Client (App)                   Resource Server                Facilitator
      |                              |                              |
  1.  |--- request ----------------->|                              |
      |                              |                              |
  2.  |<-- 402 PaymentRequired ------|                              |
      |   extensions.builder-code:   |                              |
      |     { a: "my_app" }         |                              |
      |                              |                              |
  3.  | (sign payment, echo extensions)                             |
      |                              |                              |
  4.  |--- request + payment ------->|                              |
      |   extensions.builder-code:   |                              |
      |     { a: "my_app",          |                              |
      |       s: "my_client" }      |                              |
      |                              |                              |
  5.  |                              |--- verify/settle ----------->|
      |                              |   extensions.builder-code:   |
      |                              |     { a: "my_app",          |
      |                              |       s: "my_client" }      |
      |                              |                              |
  6.  |                              |         Facilitator adds w,  |
      |                              |         encodes CBOR suffix, |
      |                              |         appends to calldata: |
      |                              |         [cbor({a:"my_app",   |
      |                              |          s:["my_client"],    |
      |                              |          w:"my_fac"})]       |
      |                              |         [cborLen][0x02][mark] |
      |                              |                              |
  7.  |<-- 200 OK + resource data ---|                              |
      |                              |                              |

Examples

Single App Attribution

Application declares its builder code:

{
  "extensions": {
    "builder-code": {
      "info": {
        "a": "bc_myapp"
      },
      "schema": { ... }
    }
  }
}

Settlement calldata suffix (hex):

{original_calldata} a161616862635f6d79617070 000c 02 80218021802180218021802180218021

Decoded:

  • CBOR: {"a": "bc_myapp"}
  • cborLength: 0x000c (12 bytes)
  • schemaId: 0x02
  • marker: 80218021802180218021802180218021

App + Facilitator Attribution

After facilitator adds its w code at settlement:

{original_calldata} a261616862635f6d7961707061777062635f6d79666163696c697461746f72 001f 02 80218021802180218021802180218021

Decoded:

  • CBOR: {"a": "bc_myapp", "w": "bc_myfacilitator"}
  • cborLength: 0x001f (31 bytes)
  • schemaId: 0x02
  • marker: 80218021802180218021802180218021

Validation

Builder Code Validation

All builder codes (a, w, and each entry in s) must:

  • Match ^[a-z0-9_]{1,32}$
  • Be 1-32 characters long
  • Contain only lowercase letters, digits, and underscores

Invalid codes must be rejected at declaration time (application), at construction time (client), and at construction or first-use (settlement) time (facilitator). The facilitator validates each entry in s for format only — s is client self-reported and cannot be verified against any authoritative source. Declaring or attaching more service codes than a party's own reservation (MAX_CLIENT_SERVICE_CODES, MAX_SERVER_SERVICE_CODES, or MAX_FACILITATOR_SERVICE_CODES) must be rejected at that layer.

The resource server MUST also reject the payment (extension_echo_mismatch) before verification/settlement when the client-echoed s array exceeds the combined client+server budget (MAX_CLIENT_SERVICE_CODES + MAX_SERVER_SERVICE_CODES), even if it still contains every server-declared entry as a subset — this prevents a hand-crafted payload from padding s with extra entries that could later crowd out a legitimately declared entry once truncated further downstream. As a further defensive backstop for facilitators invoked without that resource-server validation (e.g. a hand-crafted payload sent directly to a facilitator), the facilitator additionally truncates the echoed client+server s entries to that same combined budget before appending its own service code, capping the final encoded s at MAX_SERVICE_CODES entries.

App Code Echo Validation

The resource server is the authority for a. Before forwarding a v2 payment to the facilitator, the resource server MUST reject the payment (extension_echo_mismatch) when PaymentPayload.extensions["builder-code"].a is present and does not exactly match PaymentRequired.extensions["builder-code"].info.a (including when the server did not declare a).

Schema Validation

The schema field uses JSON Schema Draft 2020-12. Facilitators should validate info against the provided schema.


Parsing

Off-chain parsers can extract builder code attribution from settlement calldata using the ERC-8021 parsing algorithm:

  1. Extract the last 16 bytes and verify they match the ERC-8021 marker (80218021...)
  2. Extract the preceding byte as schemaId and verify it equals 0x02
  3. Extract the preceding 2 bytes as cborLength (big-endian)
  4. Extract the preceding cborLength bytes as cborData
  5. Decode cborData as a CBOR map
  6. Read a (app code), w (wallet code), and s (service codes array) from the map

Responsibilities

RoleResponsibility
ApplicationDeclares a (app code) per-route in the payment middleware configuration, and optionally up to MAX_SERVER_SERVICE_CODES of its own service code(s) as s (e.g. attribution for a server-side SDK)
ClientAttaches up to MAX_CLIENT_SERVICE_CODES service code(s) as s when BuilderCodeClientExtension is registered; echoes a only when the server declared builder-code
FacilitatorAdds w (wallet code) at settlement, optionally appends up to MAX_FACILITATOR_SERVICE_CODES of its own service code(s) to s, encodes the full CBOR suffix (a, s, w), appends to calldata