Module: X402

Defined in:
lib/x402/rails.rb,
lib/x402/chains.rb,
lib/x402/versions.rb,
lib/x402/versions/v1.rb,
lib/x402/versions/v2.rb,
lib/x402/configuration.rb,
lib/x402/rails/railtie.rb,
lib/x402/rails/version.rb,
lib/x402/versions/base.rb,
lib/x402/payment_payload.rb,
lib/x402/payment_validator.rb,
lib/x402/facilitator_client.rb,
lib/x402/discovery_extension.rb,
lib/x402/payment_requirement.rb,
lib/x402/settlement_response.rb,
lib/x402/cdp_facilitator_auth.rb,
lib/x402/requirement_generator.rb,
lib/x402/rails/controller_extensions.rb,
lib/generators/x402/install/install_generator.rb

Defined Under Namespace

Modules: Generators, Rails, Versions Classes: CdpFacilitatorAuth, Configuration, ConfigurationError, DiscoveryExtension, FacilitatorClient, FacilitatorError, InvalidPaymentError, PaymentPayload, PaymentRequirement, PaymentValidator, RequirementGenerator, SettlementResponse

Constant Summary collapse

USDC_NAMED_USD_COIN_V2 =

Currency config constants for EIP-712 signature domain. The name field must match the on-chain ERC-20 name() return value. The version field must match the EIP-712 domain version.

{ symbol: "USDC", decimals: 6, name: "USD Coin", version: "2" }.freeze
USDC_NAMED_USDC_V2 =
{ symbol: "USDC", decimals: 6, name: "USDC", version: "2" }.freeze
USDC_NAMED_USDC =
{ symbol: "USDC", decimals: 6, name: "USDC", version: nil }.freeze
USDC_NAMED_USD_COIN =
{ symbol: "USDC", decimals: 6, name: "USD Coin", version: nil }.freeze
CHAINS =

Unified chain registry — single source of truth for all chain metadata. Each entry contains: chain_id, caip2, currency, and optionally usdc_address, explorer_url, fee_payer.

{
  # --- Base ---
  "base" => {
    chain_id: 8453,
    caip2: "eip155:8453",
    usdc_address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    explorer_url: "https://basescan.org",
    currency: USDC_NAMED_USD_COIN_V2,
  },
  "base-sepolia" => {
    chain_id: 84532,
    caip2: "eip155:84532",
    usdc_address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    explorer_url: "https://sepolia.basescan.org",
    currency: USDC_NAMED_USDC_V2,
  },

  # --- Polygon ---
  "polygon" => {
    chain_id: 137,
    caip2: "eip155:137",
    usdc_address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
    explorer_url: "https://polygonscan.com",
    currency: USDC_NAMED_USD_COIN_V2,
  },
  "polygon-amoy" => {
    chain_id: 80002,
    caip2: "eip155:80002",
    usdc_address: "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582",
    explorer_url: "https://amoy.polygonscan.com",
    currency: USDC_NAMED_USDC_V2,
  },

  # --- Avalanche ---
  "avalanche" => {
    chain_id: 43114,
    caip2: "eip155:43114",
    usdc_address: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
    explorer_url: "https://snowtrace.io",
    currency: USDC_NAMED_USDC_V2,
  },
  "avalanche-fuji" => {
    chain_id: 43113,
    caip2: "eip155:43113",
    usdc_address: "0x5425890298aed601595a70AB815c96711a31Bc65",
    explorer_url: "https://testnet.snowtrace.io",
    currency: USDC_NAMED_USD_COIN_V2,
  },

  # --- Sei ---
  "sei" => {
    chain_id: 1329,
    caip2: "eip155:1329",
    usdc_address: "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392",
    explorer_url: "https://seitrace.com",
    currency: USDC_NAMED_USDC,
  },
  "sei-testnet" => {
    chain_id: 713715,
    caip2: "eip155:713715",
    explorer_url: "https://seitrace.com/?chain=arctic-1",
    currency: USDC_NAMED_USDC,
  },

  # --- X Layer ---
  "xlayer" => {
    chain_id: 196,
    caip2: "eip155:196",
    explorer_url: "https://www.oklink.com/xlayer",
    currency: USDC_NAMED_USDC,
  },
  "xlayer-testnet" => {
    chain_id: 1952,
    caip2: "eip155:1952",
    explorer_url: "https://www.oklink.com/xlayer-test",
    currency: USDC_NAMED_USDC,
  },

  # --- SKALE ---
  "skale-base" => {
    chain_id: 1_187_947_933,
    caip2: "eip155:1187947933",
    explorer_url: "https://skale-base-explorer.skalenodes.com",
    currency: USDC_NAMED_USDC,
  },
  "skale-base-sepolia" => {
    chain_id: 324_705_682,
    caip2: "eip155:324705682",
    explorer_url: "https://base-sepolia-testnet-explorer.skalenodes.com",
    currency: USDC_NAMED_USDC,
  },

  # --- KiteAI ---
  "kiteai" => {
    chain_id: 2366,
    caip2: "eip155:2366",
    explorer_url: "https://kitescan.ai",
    currency: USDC_NAMED_USDC,
  },
  "kiteai-testnet" => {
    chain_id: 2368,
    caip2: "eip155:2368",
    explorer_url: "https://testnet.kitescan.ai",
    currency: USDC_NAMED_USDC,
  },

  # --- IoTeX (mainnet only) ---
  "iotex" => {
    chain_id: 4689,
    caip2: "eip155:4689",
    explorer_url: "https://iotexscan.io",
    currency: USDC_NAMED_USDC,
  },

  # --- Peaq (mainnet only) ---
  "peaq" => {
    chain_id: 3338,
    caip2: "eip155:3338",
    explorer_url: "https://peaq.subscan.io",
    currency: USDC_NAMED_USDC,
  },

  # --- XDC ---
  "xdc" => {
    chain_id: 50,
    caip2: "eip155:50",
    usdc_address: "0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1",
    explorer_url: "https://xdcscan.com",
    currency: USDC_NAMED_USDC_V2,
  },
  "xdc-testnet" => {
    chain_id: 51,
    caip2: "eip155:51",
    usdc_address: "0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4",
    explorer_url: "https://testnet.xdcscan.com",
    currency: USDC_NAMED_USDC_V2,
  },

  # --- Solana ---
  "solana" => {
    chain_id: 101,
    caip2: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
    usdc_address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    explorer_url: "https://explorer.solana.com",
    fee_payer: "CKPKJWNdJEqa81x7CkZ14BVPiY6y16Sxs7owznqtWYp5",
    currency: USDC_NAMED_USD_COIN,
  },
  "solana-devnet" => {
    chain_id: 103,
    caip2: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
    usdc_address: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
    explorer_url: "https://explorer.solana.com/?cluster=devnet",
    fee_payer: "CKPKJWNdJEqa81x7CkZ14BVPiY6y16Sxs7owznqtWYp5",
    currency: USDC_NAMED_USDC,
  },
}.freeze
CURRENCY_BY_CHAIN =

Derived lookups for backwards compatibility

CHAINS.transform_values { |v| v[:currency] }.freeze
CAIP2_MAPPING =
CHAINS.transform_values { |v| v[:caip2] }.freeze
REVERSE_CAIP2_MAPPING =
CAIP2_MAPPING.invert.freeze
SOLANA_CHAINS =
%w[solana solana-devnet].freeze

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.configurationObject



89
90
91
# File 'lib/x402/configuration.rb', line 89

def configuration
  @configuration ||= Configuration.new
end

Class Method Details

.asset_address_for(chain_name, symbol = nil) ⇒ Object

Raises:



200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/x402/chains.rb', line 200

def asset_address_for(chain_name, symbol = nil)
  symbol ||= X402.configuration.currency

  custom = X402.configuration.token_config(chain_name, symbol)
  return custom[:address] if custom

  if symbol.upcase == "USDC"
    builtin = CHAINS[chain_name]
    return builtin[:usdc_address] if builtin && builtin[:usdc_address]
  end

  raise ConfigurationError, "Unknown token #{symbol} for chain #{chain_name}. Register with config.register_token()"
end

.chain_config(chain_name) ⇒ Object



177
178
179
180
181
182
# File 'lib/x402/chains.rb', line 177

def chain_config(chain_name)
  custom = X402.configuration.chain_config(chain_name)
  return custom if custom

  CHAINS[chain_name] || raise(ConfigurationError, "Unsupported chain: #{chain_name}. Register with config.register_chain()")
end

.configure {|configuration| ... } ⇒ Object

Yields:



93
94
95
# File 'lib/x402/configuration.rb', line 93

def configure
  yield(configuration)
end

.currency_config_for_chain(chain_name) ⇒ Object

Raises:



184
185
186
187
188
189
# File 'lib/x402/chains.rb', line 184

def currency_config_for_chain(chain_name)
  config = CHAINS[chain_name]
  raise(ConfigurationError, "Unsupported chain for currency: #{chain_name}") unless config

  config[:currency]
end

.currency_decimals_for_chain(chain_name) ⇒ Object



228
229
230
# File 'lib/x402/chains.rb', line 228

def currency_decimals_for_chain(chain_name)
  currency_config_for_chain(chain_name)[:decimals]
end

.fee_payer_for(chain_name) ⇒ Object



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/x402/chains.rb', line 232

def fee_payer_for(chain_name)
  # Priority: 1) Programmatic config, 2) Per-chain ENV variable, 3) Generic ENV variable, 4) Default from CHAINS
  config = X402.configuration

  # Check programmatic configuration
  return config.fee_payer if config.fee_payer && !config.fee_payer.empty?

  # Check per-chain environment variable (e.g., X402_SOLANA_DEVNET_FEE_PAYER, X402_SOLANA_FEE_PAYER)
  env_var_name = "X402_#{chain_name.upcase.gsub('-', '_')}_FEE_PAYER"
  env_fee_payer = ENV[env_var_name]
  return env_fee_payer if env_fee_payer && !env_fee_payer.empty?

  # Check generic environment variable
  env_fee_payer = ENV["X402_FEE_PAYER"]
  return env_fee_payer if env_fee_payer && !env_fee_payer.empty?

  # Fall back to default from chain config
  chain_config(chain_name)[:fee_payer]
end

.from_caip2(caip2_string) ⇒ Object

Raises:



261
262
263
264
265
266
267
268
269
270
# File 'lib/x402/chains.rb', line 261

def from_caip2(caip2_string)
  return REVERSE_CAIP2_MAPPING[caip2_string] if REVERSE_CAIP2_MAPPING[caip2_string]

  X402.configuration.custom_chains.each do |name, config|
    caip2 = "#{config[:standard]}:#{config[:chain_id]}"
    return name if caip2 == caip2_string
  end

  raise(ConfigurationError, "Unknown CAIP-2 network: #{caip2_string}")
end

.loggerObject

::-qualified: bare Rails here resolves to the gem's own X402::Rails.



102
103
104
# File 'lib/x402/configuration.rb', line 102

def logger
  ::Rails.logger
end

.reset_configuration!Object



97
98
99
# File 'lib/x402/configuration.rb', line 97

def reset_configuration!
  @configuration = Configuration.new
end

.solana_chain?(chain_name) ⇒ Boolean

Returns:

  • (Boolean)


274
275
276
# File 'lib/x402/chains.rb', line 274

def solana_chain?(chain_name)
  SOLANA_CHAINS.include?(chain_name)
end

.supported_chainsObject



191
192
193
# File 'lib/x402/chains.rb', line 191

def supported_chains
  CHAINS.keys + X402.configuration.custom_chains.keys
end

.to_caip2(network_name) ⇒ Object



252
253
254
255
256
257
258
259
# File 'lib/x402/chains.rb', line 252

def to_caip2(network_name)
  custom = X402.configuration.chain_config(network_name)
  if custom
    return "#{custom[:standard]}:#{custom[:chain_id]}"
  end

  CAIP2_MAPPING[network_name] || raise(ConfigurationError, "No CAIP-2 mapping for: #{network_name}")
end

.token_config_for(chain_name, symbol = nil) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/x402/chains.rb', line 214

def token_config_for(chain_name, symbol = nil)
  symbol ||= X402.configuration.currency

  custom = X402.configuration.token_config(chain_name, symbol)
  return custom if custom

  config = CHAINS[chain_name]
  if symbol.upcase == "USDC" && config&.dig(:currency)
    config[:currency]
  else
    raise ConfigurationError, "Unknown token #{symbol} for chain #{chain_name}. Register with config.register_token()"
  end
end

.usdc_address_for(chain_name) ⇒ Object



195
196
197
198
# File 'lib/x402/chains.rb', line 195

def usdc_address_for(chain_name)
  builtin = CHAINS[chain_name]
  builtin ? builtin[:usdc_address] : nil
end