x402-rails 0.2.0 → 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.
data/lib/x402/rails.rb CHANGED
@@ -9,6 +9,7 @@ require "base64"
9
9
  require_relative "rails/version"
10
10
  require_relative "../x402/configuration"
11
11
  require_relative "../x402/chains"
12
+ require_relative "../x402/versions"
12
13
  require_relative "../x402/payment_requirement"
13
14
  require_relative "../x402/payment_payload"
14
15
  require_relative "../x402/settlement_response"
@@ -2,54 +2,97 @@
2
2
 
3
3
  module X402
4
4
  class RequirementGenerator
5
- def self.generate(amount:, resource:, description: nil, chain: nil, currency: nil)
5
+ def self.generate(amount:, resource:, description: nil, chain: nil, currency: nil,
6
+ wallet_address: nil, fee_payer: nil, version: nil, accepts: nil)
6
7
  config = X402.configuration
7
- chain_name = chain || config.chain
8
+ protocol_version = version || config.version
9
+ version_strategy = X402::Versions.for(protocol_version)
8
10
 
9
- # Get chain and currency configuration for this specific chain
10
- chain_config = X402.chain_config(chain_name)
11
- currency_config = X402.currency_config_for_chain(chain_name)
11
+ accepted_payments = resolve_accepted_payments(
12
+ accepts: accepts,
13
+ chain: chain,
14
+ currency: currency
15
+ )
12
16
 
13
- # Convert amount to atomic units
14
- atomic_amount = convert_to_atomic(amount, currency_config[:decimals])
17
+ formatted_accepts = accepted_payments.map do |payment|
18
+ build_formatted_requirement(
19
+ amount: amount,
20
+ resource: resource,
21
+ description: description,
22
+ chain: payment[:chain],
23
+ currency: payment[:currency],
24
+ wallet_address: wallet_address || payment[:wallet_address] || config.wallet_address,
25
+ fee_payer: fee_payer,
26
+ version_strategy: version_strategy
27
+ )
28
+ end
15
29
 
16
- # Get asset address (USDC contract address for the chain)
17
- asset_address = X402.usdc_address_for(chain_name)
30
+ version_strategy.format_requirement_response(
31
+ accepts: formatted_accepts,
32
+ resource: {
33
+ url: resource,
34
+ description: description || "Payment required for #{resource}",
35
+ mime_type: "application/json"
36
+ },
37
+ error: "Payment required to access this resource"
38
+ )
39
+ end
18
40
 
19
- # Build EIP-712 domain info (required for signature verification)
20
- # IMPORTANT: The name must match what the USDC contract returns for name()
21
- # Testnets use "USDC", mainnets use "USD Coin" or "USDC" depending on chain
22
- eip712_domain = {
23
- name: currency_config[:name],
24
- version: currency_config[:version]
25
- }
41
+ def self.build_formatted_requirement(amount:, resource:, description:, chain:, currency:,
42
+ wallet_address:, fee_payer:, version_strategy:)
43
+ token_config = X402.token_config_for(chain, currency)
44
+ asset_address = X402.asset_address_for(chain, currency)
45
+ atomic_amount = convert_to_atomic(amount, token_config[:decimals])
46
+ extra_data = build_extra_data(chain, token_config, fee_payer)
26
47
 
27
- # Create payment requirement
28
- requirement = PaymentRequirement.new(
48
+ internal_requirement = {
29
49
  scheme: "exact",
30
- network: chain_name,
31
- max_amount_required: atomic_amount,
50
+ network: chain,
51
+ amount: atomic_amount,
32
52
  asset: asset_address,
33
- pay_to: config.wallet_address,
53
+ pay_to: wallet_address,
34
54
  resource: resource,
35
55
  description: description || "Payment required for #{resource}",
36
56
  max_timeout_seconds: 600,
37
57
  mime_type: "application/json",
38
- extra: eip712_domain
39
- )
40
-
41
- # Build full response
42
- {
43
- x402Version: 1,
44
- error: "Payment required to access this resource",
45
- accepts: [requirement.to_h]
58
+ extra: extra_data
46
59
  }
60
+
61
+ version_strategy.format_requirement(internal_requirement)
62
+ end
63
+
64
+ def self.resolve_accepted_payments(accepts:, chain:, currency:)
65
+ config = X402.configuration
66
+
67
+ if accepts && !accepts.empty?
68
+ accepts.map do |acc|
69
+ {
70
+ chain: acc[:chain],
71
+ currency: acc[:currency] || "USDC",
72
+ wallet_address: acc[:wallet_address]
73
+ }
74
+ end
75
+ elsif chain
76
+ [{ chain: chain, currency: currency || config.currency, wallet_address: nil }]
77
+ else
78
+ config.effective_accepted_payments
79
+ end
47
80
  end
48
81
 
49
82
  def self.convert_to_atomic(amount, decimals)
50
- # Convert USD amount to atomic units
51
- # For USDC with 6 decimals: $0.001 = 1000 atomic units
52
83
  (amount.to_f * (10**decimals)).to_i
53
84
  end
85
+
86
+ def self.build_extra_data(chain_name, token_config, fee_payer_override)
87
+ if X402.solana_chain?(chain_name)
88
+ fee_payer = fee_payer_override || X402.fee_payer_for(chain_name)
89
+ { feePayer: fee_payer }
90
+ else
91
+ {
92
+ name: token_config[:name],
93
+ version: token_config[:version]
94
+ }
95
+ end
96
+ end
54
97
  end
55
98
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module X402
4
+ module Versions
5
+ class Base
6
+ def version_number
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def payment_header_name
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def response_header_name
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def requirement_header_name
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def format_network(internal_network)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def parse_network(network)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def format_requirement(requirement)
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def format_requirement_response(accepts:, resource:, error:)
35
+ raise NotImplementedError
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module X402
4
+ module Versions
5
+ class V1 < Base
6
+ def version_number
7
+ 1
8
+ end
9
+
10
+ def payment_header_name
11
+ "X-PAYMENT"
12
+ end
13
+
14
+ def response_header_name
15
+ "X-PAYMENT-RESPONSE"
16
+ end
17
+
18
+ def requirement_header_name
19
+ nil
20
+ end
21
+
22
+ def format_network(internal_network)
23
+ internal_network
24
+ end
25
+
26
+ def parse_network(network)
27
+ network
28
+ end
29
+
30
+ def format_requirement(requirement)
31
+ {
32
+ scheme: requirement[:scheme],
33
+ network: format_network(requirement[:network]),
34
+ maxAmountRequired: requirement[:amount].to_s,
35
+ asset: requirement[:asset],
36
+ payTo: requirement[:pay_to],
37
+ resource: requirement[:resource],
38
+ description: requirement[:description],
39
+ maxTimeoutSeconds: requirement[:max_timeout_seconds],
40
+ mimeType: requirement[:mime_type],
41
+ extra: requirement[:extra]
42
+ }.compact
43
+ end
44
+
45
+ def format_requirement_response(accepts:, resource:, error:)
46
+ {
47
+ x402Version: 1,
48
+ error: error,
49
+ accepts: accepts
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module X402
4
+ module Versions
5
+ class V2 < Base
6
+ def version_number
7
+ 2
8
+ end
9
+
10
+ def payment_header_name
11
+ "PAYMENT-SIGNATURE"
12
+ end
13
+
14
+ def response_header_name
15
+ "PAYMENT-RESPONSE"
16
+ end
17
+
18
+ def requirement_header_name
19
+ "PAYMENT-REQUIRED"
20
+ end
21
+
22
+ def format_network(internal_network)
23
+ X402.to_caip2(internal_network)
24
+ end
25
+
26
+ def parse_network(network)
27
+ if network.include?(":")
28
+ X402.from_caip2(network)
29
+ else
30
+ network
31
+ end
32
+ end
33
+
34
+ def format_requirement(requirement)
35
+ {
36
+ scheme: requirement[:scheme],
37
+ network: format_network(requirement[:network]),
38
+ amount: requirement[:amount].to_s,
39
+ asset: requirement[:asset],
40
+ payTo: requirement[:pay_to],
41
+ maxTimeoutSeconds: requirement[:max_timeout_seconds],
42
+ extra: requirement[:extra]
43
+ }.compact
44
+ end
45
+
46
+ def format_requirement_response(accepts:, resource:, error:)
47
+ {
48
+ x402Version: 2,
49
+ error: error,
50
+ resource: {
51
+ url: resource[:url],
52
+ description: resource[:description],
53
+ mimeType: resource[:mime_type]
54
+ }.compact,
55
+ accepts: accepts,
56
+ extensions: {}
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "versions/base"
4
+ require_relative "versions/v1"
5
+ require_relative "versions/v2"
6
+
7
+ module X402
8
+ module Versions
9
+ def self.for(version)
10
+ case version.to_i
11
+ when 1 then V1.new
12
+ when 2 then V2.new
13
+ else raise ConfigurationError, "Unsupported x402 version: #{version}"
14
+ end
15
+ end
16
+ end
17
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: x402-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - QuickNode
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-30 00:00:00.000000000 Z
11
+ date: 2026-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -128,9 +128,11 @@ email:
128
128
  - zach+x402@quiknode.io
129
129
  executables: []
130
130
  extensions: []
131
- extra_rdoc_files: []
131
+ extra_rdoc_files:
132
+ - README.md
132
133
  files:
133
134
  - ".rspec"
135
+ - CHANGELOG.md
134
136
  - LICENSE.txt
135
137
  - README.md
136
138
  - Rakefile
@@ -149,12 +151,17 @@ files:
149
151
  - lib/x402/rails/version.rb
150
152
  - lib/x402/requirement_generator.rb
151
153
  - lib/x402/settlement_response.rb
154
+ - lib/x402/versions.rb
155
+ - lib/x402/versions/base.rb
156
+ - lib/x402/versions/v1.rb
157
+ - lib/x402/versions/v2.rb
152
158
  homepage: https://github.com/quiknode-labs/x402-rails
153
159
  licenses:
154
160
  - MIT
155
161
  metadata:
156
162
  homepage_uri: https://github.com/quiknode-labs/x402-rails
157
163
  source_code_uri: https://github.com/quiknode-labs/x402-rails
164
+ documentation_uri: https://github.com/quiknode-labs/x402-rails#readme
158
165
  post_install_message:
159
166
  rdoc_options: []
160
167
  require_paths:
@@ -173,5 +180,5 @@ requirements: []
173
180
  rubygems_version: 3.5.16
174
181
  signing_key:
175
182
  specification_version: 4
176
- summary: Rails integration for x402 payment protocol
183
+ summary: Rails integration for x402 payment protocol (supporting x402 v2).
177
184
  test_files: []