x402-rails 0.2.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,14 +2,23 @@
2
2
 
3
3
  module X402
4
4
  class PaymentPayload
5
- attr_accessor :x402_version, :scheme, :network, :payload
5
+ attr_accessor :x402_version, :scheme, :network, :payload, :accepted, :resource_info
6
6
 
7
7
  def initialize(attributes = {})
8
8
  attrs = attributes.with_indifferent_access
9
9
 
10
- @x402_version = attrs[:x402Version] || attrs[:x402_version] || 1
11
- @scheme = attrs[:scheme]
12
- @network = attrs[:network]
10
+ @x402_version = (attrs[:x402Version] || attrs[:x402_version] || 1).to_i
11
+ @accepted = attrs[:accepted]
12
+ @resource_info = attrs[:resource]
13
+
14
+ if @accepted
15
+ @scheme = @accepted.with_indifferent_access[:scheme]
16
+ @network = normalize_network(@accepted.with_indifferent_access[:network])
17
+ else
18
+ @scheme = attrs[:scheme]
19
+ @network = normalize_network(attrs[:network])
20
+ end
21
+
13
22
  @payload = attrs[:payload]
14
23
  end
15
24
 
@@ -25,49 +34,112 @@ module X402
25
34
  end
26
35
  end
27
36
 
37
+ def solana_chain?
38
+ X402.solana_chain?(network)
39
+ end
40
+
41
+ def evm_chain?
42
+ !solana_chain?
43
+ end
44
+
45
+ def transaction
46
+ return nil unless solana_chain?
47
+ payload&.with_indifferent_access&.[](:transaction)
48
+ end
49
+
28
50
  def authorization
51
+ return nil if solana_chain?
29
52
  @authorization ||= payload&.with_indifferent_access&.[](:authorization)
30
53
  end
31
54
 
32
55
  def signature
56
+ return nil if solana_chain?
33
57
  payload&.with_indifferent_access&.[](:signature)
34
58
  end
35
59
 
36
60
  def from_address
61
+ return nil if solana_chain?
37
62
  authorization&.with_indifferent_access&.[](:from)
38
63
  end
39
64
 
40
65
  def to_address
66
+ return nil if solana_chain?
41
67
  authorization&.with_indifferent_access&.[](:to)
42
68
  end
43
69
 
44
70
  def value
71
+ return nil if solana_chain?
45
72
  authorization&.with_indifferent_access&.[](:value)
46
73
  end
47
74
 
48
75
  def valid_after
76
+ return nil if solana_chain?
49
77
  authorization&.with_indifferent_access&.[](:validAfter) || authorization&.with_indifferent_access&.[](:valid_after)
50
78
  end
51
79
 
52
80
  def valid_before
81
+ return nil if solana_chain?
53
82
  authorization&.with_indifferent_access&.[](:validBefore) || authorization&.with_indifferent_access&.[](:valid_before)
54
83
  end
55
84
 
56
85
  def nonce
86
+ return nil if solana_chain?
57
87
  authorization&.with_indifferent_access&.[](:nonce)
58
88
  end
59
89
 
60
90
  def to_h
61
- {
62
- x402Version: x402_version,
63
- scheme: scheme,
64
- network: network,
65
- payload: payload
66
- }
91
+ version_strategy = X402::Versions.for(x402_version)
92
+
93
+ if x402_version >= 2
94
+ base = {
95
+ x402Version: x402_version,
96
+ accepted: format_accepted_for_version(version_strategy),
97
+ payload: payload,
98
+ extensions: {}
99
+ }
100
+ base[:resource] = resource_info if resource_info
101
+ base
102
+ else
103
+ {
104
+ x402Version: x402_version,
105
+ scheme: scheme,
106
+ network: version_strategy.format_network(network),
107
+ payload: payload
108
+ }
109
+ end
67
110
  end
68
111
 
69
112
  def to_json(*args)
70
113
  to_h.to_json(*args)
71
114
  end
115
+
116
+ private
117
+
118
+ def normalize_network(network_value)
119
+ return network_value unless network_value
120
+
121
+ if network_value.to_s.include?(":")
122
+ X402.from_caip2(network_value)
123
+ else
124
+ network_value
125
+ end
126
+ rescue X402::ConfigurationError
127
+ network_value
128
+ end
129
+
130
+ def format_accepted_for_version(version_strategy)
131
+ return nil unless accepted
132
+
133
+ acc = accepted.with_indifferent_access
134
+ {
135
+ scheme: acc[:scheme],
136
+ network: version_strategy.format_network(network),
137
+ amount: (acc[:amount] || acc[:maxAmountRequired]).to_s,
138
+ asset: acc[:asset],
139
+ payTo: acc[:payTo] || acc[:pay_to],
140
+ maxTimeoutSeconds: acc[:maxTimeoutSeconds] || acc[:max_timeout_seconds],
141
+ extra: acc[:extra]
142
+ }.compact
143
+ end
72
144
  end
73
145
  end
@@ -2,14 +2,15 @@
2
2
 
3
3
  module X402
4
4
  class PaymentRequirement
5
- attr_accessor :scheme, :network, :max_amount_required, :asset, :pay_to, :resource, :description, :max_timeout_seconds, :mime_type, :output_schema, :extra
5
+ attr_accessor :scheme, :network, :amount, :asset, :pay_to, :resource, :description,
6
+ :max_timeout_seconds, :mime_type, :output_schema, :extra, :version
6
7
 
7
8
  def initialize(attributes = {})
8
9
  attrs = attributes.with_indifferent_access
9
10
 
10
11
  @scheme = attrs[:scheme] || "exact"
11
- @network = attrs[:network]
12
- @max_amount_required = attrs[:maxAmountRequired] || attrs[:max_amount_required]
12
+ @network = normalize_network(attrs[:network])
13
+ @amount = attrs[:maxAmountRequired] || attrs[:max_amount_required] || attrs[:amount]
13
14
  @asset = attrs[:asset]
14
15
  @pay_to = attrs[:payTo] || attrs[:pay_to]
15
16
  @resource = attrs[:resource]
@@ -18,27 +19,47 @@ module X402
18
19
  @mime_type = attrs[:mimeType] || attrs[:mime_type] || "application/json"
19
20
  @output_schema = attrs[:outputSchema] || attrs[:output_schema]
20
21
  @extra = attrs[:extra]
22
+ @version = attrs[:version]
21
23
  end
22
24
 
23
- def to_h
24
- h = {
25
+ def max_amount_required
26
+ @amount
27
+ end
28
+
29
+ def to_h(version: nil)
30
+ v = version || @version || X402.configuration.version
31
+ version_strategy = X402::Versions.for(v)
32
+
33
+ version_strategy.format_requirement(
25
34
  scheme: scheme,
26
35
  network: network,
27
- maxAmountRequired: max_amount_required.to_s,
36
+ amount: amount,
28
37
  asset: asset,
29
- payTo: pay_to,
38
+ pay_to: pay_to,
30
39
  resource: resource,
31
40
  description: description,
32
- maxTimeoutSeconds: max_timeout_seconds,
33
- mimeType: mime_type
34
- }
35
- h[:outputSchema] = output_schema if output_schema
36
- h[:extra] = extra if extra
37
- h
41
+ max_timeout_seconds: max_timeout_seconds,
42
+ mime_type: mime_type,
43
+ extra: extra
44
+ )
38
45
  end
39
46
 
40
47
  def to_json(*args)
41
48
  to_h.to_json(*args)
42
49
  end
50
+
51
+ private
52
+
53
+ def normalize_network(network_value)
54
+ return network_value unless network_value
55
+
56
+ if network_value.to_s.include?(":")
57
+ X402.from_caip2(network_value)
58
+ else
59
+ network_value
60
+ end
61
+ rescue X402::ConfigurationError
62
+ network_value
63
+ end
43
64
  end
44
65
  end
@@ -19,17 +19,26 @@ module X402
19
19
  return validation_error("Network mismatch: expected #{requirement.network}, got #{payment_payload.network}")
20
20
  end
21
21
 
22
- # Validate recipient address
23
- unless payment_payload.to_address&.downcase == requirement.pay_to&.downcase
24
- return validation_error("Recipient mismatch: expected #{requirement.pay_to}, got #{payment_payload.to_address}")
25
- end
22
+ # For EVM chains, validate recipient and amount locally before calling facilitator
23
+ # For Solana chains, the facilitator handles all validation of the transaction
24
+ if payment_payload.evm_chain?
25
+ # Validate recipient address
26
+ unless payment_payload.to_address&.downcase == requirement.pay_to&.downcase
27
+ return validation_error("Recipient mismatch: expected #{requirement.pay_to}, got #{payment_payload.to_address}")
28
+ end
26
29
 
27
- # Validate amount
28
- payment_value = payment_payload.value.to_i
29
- required_value = requirement.max_amount_required.to_i
30
+ # Validate amount
31
+ payment_value = payment_payload.value.to_i
32
+ required_value = requirement.max_amount_required.to_i
30
33
 
31
- if payment_value < required_value
32
- return validation_error("Insufficient amount: expected at least #{required_value}, got #{payment_value}")
34
+ if payment_value < required_value
35
+ return validation_error("Insufficient amount: expected at least #{required_value}, got #{payment_value}")
36
+ end
37
+ else
38
+ # Solana: verify transaction payload exists
39
+ unless payment_payload.transaction
40
+ return validation_error("Solana payment missing transaction payload")
41
+ end
33
42
  end
34
43
 
35
44
  # Call facilitator to verify payment (does NOT settle on blockchain yet)
@@ -11,110 +11,215 @@ module X402
11
11
 
12
12
  def x402_paywall(options = {})
13
13
  amount = options[:amount] or raise ArgumentError, "amount is required"
14
- chain = options[:chain] || X402.configuration.chain
15
- currency = options[:currency] || X402.configuration.currency
14
+ chain = options[:chain]
15
+ currency = options[:currency]
16
+ protocol_version = options[:version] || X402.configuration.version
17
+ wallet_address = options[:wallet_address]
18
+ fee_payer = options[:fee_payer]
19
+ accepts = options[:accepts]
16
20
 
17
- # Check if payment header is present
18
- payment_header = request.headers["X-PAYMENT"]
21
+ begin
22
+ version_strategy = X402::Versions.for(protocol_version)
23
+ rescue X402::ConfigurationError => e
24
+ return render_configuration_error(e.message)
25
+ end
26
+
27
+ payment_header = request.headers[version_strategy.payment_header_name]
19
28
 
20
29
  if payment_header.nil? || payment_header.empty?
21
- # No payment provided, return 402 with requirements
22
- return render_payment_required(amount, chain, currency)
30
+ return render_payment_required(
31
+ amount,
32
+ chain: chain,
33
+ currency: currency,
34
+ version: protocol_version,
35
+ wallet_address: wallet_address,
36
+ fee_payer: fee_payer,
37
+ accepts: accepts
38
+ )
23
39
  end
24
40
 
25
- # Payment provided, validate it
26
- process_payment(payment_header, amount, chain, currency)
41
+ process_payment(
42
+ payment_header, amount,
43
+ chain: chain,
44
+ currency: currency,
45
+ version: protocol_version,
46
+ wallet_address: wallet_address,
47
+ fee_payer: fee_payer,
48
+ accepts: accepts
49
+ )
27
50
  end
28
51
 
29
52
  private
30
53
 
31
- def generate_payment_required_response(amount, chain, currency, error_message = nil)
54
+ def generate_payment_required_response(amount, error_message = nil,
55
+ chain: nil, currency: nil, version: nil,
56
+ wallet_address: nil, fee_payer: nil, accepts: nil)
57
+ protocol_version = version || X402.configuration.version
58
+
32
59
  requirement_response = X402::RequirementGenerator.generate(
33
60
  amount: amount,
34
61
  resource: request.original_url,
35
62
  description: "Payment required for #{request.path}",
36
63
  chain: chain,
37
- currency: currency
64
+ currency: currency,
65
+ version: protocol_version,
66
+ wallet_address: wallet_address,
67
+ fee_payer: fee_payer,
68
+ accepts: accepts
38
69
  )
39
70
  requirement_response[:error] = error_message if error_message
40
71
  requirement_response
41
72
  end
42
73
 
43
- def render_payment_required(amount, chain, currency)
44
- requirement_response = generate_payment_required_response(amount, chain, currency)
74
+ def render_payment_required(amount, chain: nil, currency: nil, version: nil,
75
+ wallet_address: nil, fee_payer: nil, accepts: nil)
76
+ protocol_version = version || X402.configuration.version
77
+ version_strategy = X402::Versions.for(protocol_version)
78
+
79
+ requirement_response = generate_payment_required_response(
80
+ amount,
81
+ chain: chain,
82
+ currency: currency,
83
+ version: protocol_version,
84
+ wallet_address: wallet_address,
85
+ fee_payer: fee_payer,
86
+ accepts: accepts
87
+ )
88
+
89
+ render_402_response(requirement_response, version_strategy)
90
+ end
91
+
92
+ def render_402_response(requirement_response, version_strategy)
93
+ if version_strategy.requirement_header_name
94
+ response.headers[version_strategy.requirement_header_name] =
95
+ Base64.strict_encode64(requirement_response.to_json)
96
+ end
97
+ render json: requirement_response, status: :payment_required
98
+ end
45
99
 
46
- # Detect if request is from browser or API client
47
- # if browser_request?
48
- # render_html_paywall(requirement_response)
49
- # else
50
- render json: requirement_response, status: :payment_required
51
- # end
100
+ def render_configuration_error(message)
101
+ # Use V1 as a safe fallback when version is invalid to avoid recursive errors
102
+ fallback_strategy = X402::Versions::V1.new
103
+ error_response = {
104
+ x402Version: 1,
105
+ error: "Configuration error: #{message}",
106
+ accepts: []
107
+ }
108
+ render_402_response(error_response, fallback_strategy)
52
109
  end
53
110
 
54
- def process_payment(payment_header, amount, chain, currency)
55
- # Parse payment payload
111
+ def process_payment(payment_header, amount, chain: nil, currency: nil,
112
+ version: nil, wallet_address: nil, fee_payer: nil, accepts: nil)
113
+ protocol_version = version || X402.configuration.version
114
+ version_strategy = X402::Versions.for(protocol_version)
115
+
56
116
  payment_payload = X402::PaymentPayload.from_header(payment_header)
57
117
 
58
- # Generate requirement for validation (must match the 402 response exactly!)
59
118
  requirement_data = X402::RequirementGenerator.generate(
60
119
  amount: amount,
61
120
  resource: request.original_url,
62
121
  description: "Payment required for #{request.path}",
63
122
  chain: chain,
64
- currency: currency
123
+ currency: currency,
124
+ version: protocol_version,
125
+ wallet_address: wallet_address,
126
+ fee_payer: fee_payer,
127
+ accepts: accepts
65
128
  )
66
129
 
67
- requirement = X402::PaymentRequirement.new(requirement_data[:accepts].first)
130
+ matching_accept = find_matching_accept(requirement_data[:accepts], payment_payload, version_strategy)
131
+
132
+ unless matching_accept
133
+ requirement_response = generate_payment_required_response(
134
+ amount, "Payment network not accepted: #{payment_payload.network}",
135
+ chain: chain, currency: currency, version: protocol_version,
136
+ wallet_address: wallet_address, fee_payer: fee_payer, accepts: accepts
137
+ )
138
+ return render_402_response(requirement_response, version_strategy)
139
+ end
140
+
141
+ resource_info = requirement_data[:resource] || {}
142
+ additional_attrs = { version: protocol_version }
143
+ additional_attrs[:resource] = resource_info[:url] if resource_info[:url]
144
+ additional_attrs[:description] = resource_info[:description] if resource_info[:description]
145
+ additional_attrs[:mime_type] = resource_info[:mimeType] if resource_info[:mimeType]
146
+ requirement_attrs = matching_accept.merge(additional_attrs)
147
+ requirement = X402::PaymentRequirement.new(requirement_attrs)
68
148
 
69
- # Validate payment (verify signature, but don't settle on blockchain yet)
70
149
  validator = X402::PaymentValidator.new
71
150
  validation_result = validator.validate(payment_payload, requirement)
72
151
 
73
152
  unless validation_result[:valid]
74
- requirement_response = generate_payment_required_response(amount, chain, currency, validation_result[:error])
75
- return render json: requirement_response, status: :payment_required
153
+ requirement_response = generate_payment_required_response(
154
+ amount, validation_result[:error],
155
+ chain: chain, currency: currency, version: protocol_version,
156
+ wallet_address: wallet_address, fee_payer: fee_payer, accepts: accepts
157
+ )
158
+ return render_402_response(requirement_response, version_strategy)
76
159
  end
77
160
 
78
- # Store payment info and requirement in request environment
79
161
  request.env["x402.payment"] = {
80
162
  payer: validation_result[:payer],
81
163
  amount: payment_payload.value,
82
164
  network: payment_payload.network,
83
165
  payload: payment_payload,
84
- requirement: requirement
166
+ requirement: requirement,
167
+ version: protocol_version
85
168
  }
86
169
 
87
- # If non-optimistic mode, settle payment synchronously before continuing
88
170
  unless X402.configuration.optimistic
89
171
  settlement_result = settle_payment_now
90
172
 
91
- # If settlement failed, abort and return 402 with payment requirements
92
173
  if settlement_result.nil? || !settlement_result.success?
93
174
  error_message = settlement_result&.error_reason || "Settlement failed"
94
- requirement_response = generate_payment_required_response(amount, chain, currency, "failed to settle payment: #{error_message}")
95
- return render json: requirement_response, status: :payment_required
175
+ requirement_response = generate_payment_required_response(
176
+ amount, "failed to settle payment: #{error_message}",
177
+ chain: chain, currency: currency, version: protocol_version,
178
+ wallet_address: wallet_address, fee_payer: fee_payer, accepts: accepts
179
+ )
180
+ return render_402_response(requirement_response, version_strategy)
96
181
  end
97
182
  end
98
183
 
99
- # Payment verified, continue with action
100
- # In optimistic mode, settlement will happen automatically via after_action callback
101
184
  rescue X402::InvalidPaymentError => e
102
- requirement_response = generate_payment_required_response(amount, chain, currency, "Invalid payment: #{e.message}")
103
- render json: requirement_response, status: :payment_required
185
+ requirement_response = generate_payment_required_response(
186
+ amount, "Invalid payment: #{e.message}",
187
+ chain: chain, currency: currency, version: protocol_version,
188
+ wallet_address: wallet_address, fee_payer: fee_payer, accepts: accepts
189
+ )
190
+ render_402_response(requirement_response, version_strategy)
104
191
  rescue X402::FacilitatorError => e
105
- requirement_response = generate_payment_required_response(amount, chain, currency, "Verification error: #{e.message}")
106
- render json: requirement_response, status: :payment_required
192
+ requirement_response = generate_payment_required_response(
193
+ amount, "Verification error: #{e.message}",
194
+ chain: chain, currency: currency, version: protocol_version,
195
+ wallet_address: wallet_address, fee_payer: fee_payer, accepts: accepts
196
+ )
197
+ render_402_response(requirement_response, version_strategy)
198
+ rescue X402::ConfigurationError => e
199
+ render_configuration_error(e.message)
200
+ end
201
+
202
+ def find_matching_accept(accepts, payment_payload, version_strategy)
203
+ payment_network = payment_payload.network
204
+
205
+ accepts.find do |accept|
206
+ accept_network = accept[:network]
207
+ next false unless accept_network.present?
208
+
209
+ if accept_network.to_s.include?(":")
210
+ version_strategy.parse_network(accept_network) == payment_network
211
+ else
212
+ accept_network == payment_network
213
+ end
214
+ end
107
215
  end
108
216
 
109
217
  def settle_x402_payment_if_needed
110
- # Only run in optimistic mode (non-optimistic settles synchronously)
111
218
  return unless X402.configuration.optimistic
112
219
 
113
- # Only settle if payment was verified
114
220
  payment_info = request.env["x402.payment"]
115
221
  return unless payment_info
116
222
 
117
- # Only settle if response is 2xx (success)
118
223
  return unless response.status >= 200 && response.status < 300
119
224
 
120
225
  perform_settlement(payment_info)
@@ -127,7 +232,6 @@ module X402
127
232
  ::Rails.logger.info("=== X402 Non-Optimistic Settlement (before response) ===")
128
233
  settlement_result = perform_settlement(payment_info)
129
234
 
130
- # Store settlement result for later use (e.g., adding to response body)
131
235
  request.env["x402.settlement_result"] = settlement_result
132
236
  settlement_result
133
237
  end
@@ -141,19 +245,19 @@ module X402
141
245
  ::Rails.logger.info("Requirement class: #{payment_info[:requirement].class}")
142
246
  ::Rails.logger.info("Requirement hash: #{payment_info[:requirement].to_h.inspect}")
143
247
 
248
+ protocol_version = payment_info[:version] || X402.configuration.version
249
+ version_strategy = X402::Versions.for(protocol_version)
250
+
144
251
  facilitator_client = X402::FacilitatorClient.new
145
252
  settlement_result = facilitator_client.settle(
146
253
  payment_info[:payload],
147
- payment_info[:requirement].to_h
254
+ payment_info[:requirement].to_h(version: protocol_version)
148
255
  )
149
256
 
150
257
  if settlement_result.success?
151
- # Add settlement response header
152
- response.headers["X-PAYMENT-RESPONSE"] = settlement_result.to_base64
258
+ response.headers[version_strategy.response_header_name] = settlement_result.to_base64
153
259
  ::Rails.logger.info("x402 settlement successful: #{settlement_result.transaction}")
154
260
  else
155
- # Settlement failed - in optimistic mode, user already got the service
156
- # In non-optimistic mode, this will be caught before the response is sent
157
261
  ::Rails.logger.error("x402 settlement failed: #{settlement_result.error_reason}")
158
262
  end
159
263
 
@@ -164,92 +268,6 @@ module X402
164
268
  end
165
269
  end
166
270
 
167
- def browser_request?
168
- # If Accept header explicitly requests JSON, return JSON even from browsers
169
- accept_header = request.headers["Accept"].to_s
170
- return false if accept_header.include?("application/json")
171
-
172
- # Otherwise, check User-Agent for browser indicators
173
- user_agent = request.headers["User-Agent"].to_s
174
- user_agent.match?(/(Mozilla|Chrome|Safari|Firefox|Edge|Opera)/i)
175
- end
176
-
177
- def render_html_paywall(requirement_response)
178
- html = <<~HTML
179
- <!DOCTYPE html>
180
- <html>
181
- <head>
182
- <title>Payment Required</title>
183
- <style>
184
- body {
185
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
186
- display: flex;
187
- justify-content: center;
188
- align-items: center;
189
- min-height: 100vh;
190
- margin: 0;
191
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
192
- }
193
- .paywall-container {
194
- background: white;
195
- padding: 3rem;
196
- border-radius: 1rem;
197
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
198
- max-width: 500px;
199
- text-align: center;
200
- }
201
- h1 {
202
- color: #333;
203
- margin-bottom: 1rem;
204
- font-size: 2rem;
205
- }
206
- p {
207
- color: #666;
208
- line-height: 1.6;
209
- margin-bottom: 2rem;
210
- }
211
- .amount {
212
- font-size: 2.5rem;
213
- font-weight: bold;
214
- color: #667eea;
215
- margin: 1.5rem 0;
216
- }
217
- .info {
218
- background: #f7f7f7;
219
- padding: 1rem;
220
- border-radius: 0.5rem;
221
- margin: 1.5rem 0;
222
- font-size: 0.9rem;
223
- }
224
- code {
225
- background: #e0e0e0;
226
- padding: 0.2rem 0.5rem;
227
- border-radius: 0.25rem;
228
- font-family: monospace;
229
- }
230
- </style>
231
- </head>
232
- <body>
233
- <div class="paywall-container">
234
- <h1>💳 Payment Required</h1>
235
- <p>This resource requires payment to access.</p>
236
- <div class="amount">$#{format('%.3f', requirement_response[:accepts].first[:maxAmountRequired].to_i / 1_000_000.0)}</div>
237
- <div class="info">
238
- <p><strong>Network:</strong> #{requirement_response[:accepts].first[:network]}</p>
239
- <p><strong>Asset:</strong> USDC</p>
240
- <p><strong>Resource:</strong> #{requirement_response[:accepts].first[:resource]}</p>
241
- </div>
242
- <p style="font-size: 0.85rem; color: #999;">
243
- This resource uses the x402 payment protocol.
244
- API clients can make payments programmatically by including the X-PAYMENT header.
245
- </p>
246
- </div>
247
- </body>
248
- </html>
249
- HTML
250
-
251
- render html: html.html_safe, status: :payment_required
252
- end
253
271
  end
254
272
  end
255
273
  end
@@ -8,9 +8,9 @@ X402.configure do |config|
8
8
  # Set this via environment variable: X402_WALLET_ADDRESS
9
9
  config.wallet_address = ENV.fetch("X402_WALLET_ADDRESS", nil)
10
10
 
11
- # Facilitator URL (default: https://x402.org/facilitator)
11
+ # Facilitator URL (default: https://www.x402.org/facilitator)
12
12
  # The facilitator handles payment verification and settlement
13
- config.facilitator = ENV.fetch("X402_FACILITATOR_URL", "https://x402.org/facilitator")
13
+ config.facilitator = ENV.fetch("X402_FACILITATOR_URL", "https://www.x402.org/facilitator")
14
14
 
15
15
  # Blockchain network to use
16
16
  # Options: "base-sepolia" (testnet), "base" (mainnet), "avalanche-fuji" (testnet), "avalanche" (mainnet)
@@ -19,17 +19,6 @@ X402.configure do |config|
19
19
 
20
20
  # Currency symbol (currently only USDC is supported)
21
21
  config.currency = ENV.fetch("X402_CURRENCY", "USDC")
22
-
23
- # Custom RPC URLs (optional)
24
- # Use custom RPC endpoints from providers like QuickNode, Alchemy, or Infura
25
- # for better reliability and rate limits. Uncomment and configure as needed:
26
- #
27
- # config.rpc_urls["base"] = "https://your-base-rpc.quiknode.pro/your-key"
28
- # config.rpc_urls["base-sepolia"] = "https://your-sepolia-rpc.quiknode.pro/your-key"
29
- # config.rpc_urls["avalanche"] = "https://your-avalanche-rpc.quiknode.pro/your-key"
30
- #
31
- # Or use environment variables (see README.md for details):
32
- # X402_BASE_RPC_URL, X402_BASE_SEPOLIA_RPC_URL, etc.
33
22
  end
34
23
 
35
24
  # Validate configuration on initialization
@@ -2,6 +2,6 @@
2
2
 
3
3
  module X402
4
4
  module Rails
5
- VERSION = "0.2.1"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end