mpp-rb 0.1.3 → 0.1.5
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.
- checksums.yaml +4 -4
- data/README.md +63 -1
- data/lib/mpp/challenge.rb +15 -5
- data/lib/mpp/client/transport.rb +5 -4
- data/lib/mpp/errors.rb +22 -0
- data/lib/mpp/http/headers.rb +61 -0
- data/lib/mpp/http.rb +8 -0
- data/lib/mpp/methods/evm/assets.rb +103 -0
- data/lib/mpp/methods/evm/authorization.rb +140 -0
- data/lib/mpp/methods/evm/charge_intent.rb +129 -0
- data/lib/mpp/methods/evm/evm_method.rb +117 -0
- data/lib/mpp/methods/evm.rb +64 -0
- data/lib/mpp/methods/stripe/charge_intent.rb +4 -1
- data/lib/mpp/methods/tempo/attribution.rb +3 -9
- data/lib/mpp/methods/tempo/client_method.rb +17 -6
- data/lib/mpp/methods/tempo/fee_payer_client.rb +120 -0
- data/lib/mpp/methods/tempo/intents.rb +27 -7
- data/lib/mpp/methods/tempo/proof.rb +39 -12
- data/lib/mpp/methods/tempo/relay.rb +257 -0
- data/lib/mpp/methods/tempo.rb +2 -0
- data/lib/mpp/parsing.rb +2 -0
- data/lib/mpp/receipt.rb +4 -3
- data/lib/mpp/server/accept_payment.rb +194 -0
- data/lib/mpp/server/compose.rb +387 -0
- data/lib/mpp/server/decorator.rb +28 -7
- data/lib/mpp/server/middleware.rb +162 -30
- data/lib/mpp/server/mpp_handler.rb +267 -33
- data/lib/mpp/server/result.rb +100 -0
- data/lib/mpp/server.rb +4 -0
- data/lib/mpp/version.rb +1 -1
- data/lib/mpp/x402/facilitator.rb +127 -0
- data/lib/mpp/x402/header.rb +131 -0
- data/lib/mpp/x402/server.rb +247 -0
- data/lib/mpp/x402/types.rb +33 -0
- data/lib/mpp/x402.rb +12 -0
- data/lib/mpp.rb +6 -3
- metadata +32 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "types"
|
|
5
|
+
require "base64"
|
|
6
|
+
require "json"
|
|
7
|
+
|
|
8
|
+
module Mpp
|
|
9
|
+
module X402
|
|
10
|
+
# Base64 JSON codecs for x402 v2 headers.
|
|
11
|
+
#
|
|
12
|
+
# x402 uses standard (not url-safe) base64 of JSON, unlike Payment-auth
|
|
13
|
+
# which uses base64url.
|
|
14
|
+
module Header
|
|
15
|
+
extend T::Sig
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
sig { params(payment_required: T::Hash[T.untyped, T.untyped]).returns(String) }
|
|
20
|
+
def encode_payment_required(payment_required)
|
|
21
|
+
encode_json(payment_required)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
sig { params(value: String).returns(T::Hash[T.untyped, T.untyped]) }
|
|
25
|
+
def decode_payment_required(value)
|
|
26
|
+
parsed = decode_json(value)
|
|
27
|
+
validate_payment_required!(parsed)
|
|
28
|
+
parsed
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { params(payment_payload: T::Hash[T.untyped, T.untyped]).returns(String) }
|
|
32
|
+
def encode_payment_signature(payment_payload)
|
|
33
|
+
encode_json(payment_payload)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
sig { params(value: String).returns(T::Hash[T.untyped, T.untyped]) }
|
|
37
|
+
def decode_payment_signature(value)
|
|
38
|
+
parsed = decode_json(value)
|
|
39
|
+
validate_payment_payload!(parsed)
|
|
40
|
+
parsed
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
sig { params(settle_response: T::Hash[T.untyped, T.untyped]).returns(String) }
|
|
44
|
+
def encode_payment_response(settle_response)
|
|
45
|
+
encode_json(settle_response)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
sig { params(value: String).returns(T::Hash[T.untyped, T.untyped]) }
|
|
49
|
+
def decode_payment_response(value)
|
|
50
|
+
parsed = decode_json(value)
|
|
51
|
+
validate_settle_response!(parsed)
|
|
52
|
+
parsed
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sig { params(obj: T.untyped).returns(String) }
|
|
56
|
+
def encode_json(obj)
|
|
57
|
+
json = Mpp::Json.compact_encode(obj)
|
|
58
|
+
Base64.strict_encode64(json)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
sig { params(value: String).returns(T::Hash[T.untyped, T.untyped]) }
|
|
62
|
+
def decode_json(value)
|
|
63
|
+
json = decode_base64(value)
|
|
64
|
+
parsed = JSON.parse(json)
|
|
65
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless parsed.is_a?(Hash)
|
|
66
|
+
|
|
67
|
+
parsed
|
|
68
|
+
rescue JSON::ParserError
|
|
69
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header."
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
sig { params(value: String).returns(String) }
|
|
73
|
+
def decode_base64(value)
|
|
74
|
+
Base64.strict_decode64(value)
|
|
75
|
+
rescue ArgumentError
|
|
76
|
+
padded = value + ("=" * ((-value.length) % 4))
|
|
77
|
+
begin
|
|
78
|
+
Base64.urlsafe_decode64(padded)
|
|
79
|
+
rescue ArgumentError
|
|
80
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
private_class_method :decode_base64
|
|
84
|
+
|
|
85
|
+
sig { params(parsed: T::Hash[T.untyped, T.untyped]).void }
|
|
86
|
+
def validate_payment_required!(parsed)
|
|
87
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless parsed["x402Version"] == VERSION
|
|
88
|
+
accepts = parsed["accepts"]
|
|
89
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless accepts.is_a?(Array) && !accepts.empty?
|
|
90
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless parsed["resource"].is_a?(Hash)
|
|
91
|
+
|
|
92
|
+
accepts.each { |item| validate_payment_requirements!(item) }
|
|
93
|
+
end
|
|
94
|
+
private_class_method :validate_payment_required!
|
|
95
|
+
|
|
96
|
+
sig { params(parsed: T::Hash[T.untyped, T.untyped]).void }
|
|
97
|
+
def validate_payment_payload!(parsed)
|
|
98
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless parsed["x402Version"] == VERSION
|
|
99
|
+
validate_payment_requirements!(parsed["accepted"])
|
|
100
|
+
payload = parsed["payload"]
|
|
101
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless payload.is_a?(Hash)
|
|
102
|
+
Kernel.raise Mpp::ParseError, "EVM charge only supports x402 EIP-3009 authorization payloads" unless payload.key?("authorization")
|
|
103
|
+
end
|
|
104
|
+
private_class_method :validate_payment_payload!
|
|
105
|
+
|
|
106
|
+
sig { params(parsed: T::Hash[T.untyped, T.untyped]).void }
|
|
107
|
+
def validate_settle_response!(parsed)
|
|
108
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless parsed.key?("success")
|
|
109
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless parsed["transaction"].is_a?(String)
|
|
110
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless parsed["network"].is_a?(String)
|
|
111
|
+
end
|
|
112
|
+
private_class_method :validate_settle_response!
|
|
113
|
+
|
|
114
|
+
sig { params(requirements: T.untyped).void }
|
|
115
|
+
def validate_payment_requirements!(requirements)
|
|
116
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless requirements.is_a?(Hash)
|
|
117
|
+
Kernel.raise Mpp::ParseError, "x402 exact requires scheme \"exact\"" unless requirements["scheme"] == SCHEME_EXACT
|
|
118
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless requirements["amount"].is_a?(String) && requirements["amount"].match?(/\A\d+\z/)
|
|
119
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless requirements["asset"].is_a?(String) && !requirements["asset"].empty?
|
|
120
|
+
Kernel.raise Mpp::ParseError, "Invalid EVM CAIP-2 network" unless requirements["network"].is_a?(String) && requirements["network"].start_with?(EVM_NETWORK_PREFIX)
|
|
121
|
+
Kernel.raise Mpp::ParseError, "Invalid base64 JSON header." unless requirements["payTo"].is_a?(String) && !requirements["payTo"].empty?
|
|
122
|
+
extra = requirements["extra"]
|
|
123
|
+
return if extra.nil?
|
|
124
|
+
|
|
125
|
+
transfer = extra["assetTransferMethod"]
|
|
126
|
+
Kernel.raise Mpp::ParseError, "x402 exact permit2 signing is not implemented" if transfer == "permit2"
|
|
127
|
+
end
|
|
128
|
+
private_class_method :validate_payment_requirements!
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "header"
|
|
5
|
+
require "digest"
|
|
6
|
+
require "openssl"
|
|
7
|
+
|
|
8
|
+
module Mpp
|
|
9
|
+
module X402
|
|
10
|
+
# Server-side x402 exact binding, challenge decoration, and header merge.
|
|
11
|
+
module Server
|
|
12
|
+
extend T::Sig
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
sig { params(values: T::Array[String]).returns(String) }
|
|
17
|
+
def merge_payment_required(values)
|
|
18
|
+
decoded = values.map { |value| Header.decode_payment_required(value) }
|
|
19
|
+
first = decoded.first
|
|
20
|
+
Kernel.raise ArgumentError, "Expected at least one x402 payment-required header." unless first
|
|
21
|
+
|
|
22
|
+
rest = decoded.drop(1)
|
|
23
|
+
incompatible = rest.any? do |value|
|
|
24
|
+
value["resource"] != first["resource"] || value["extensions"] != first["extensions"]
|
|
25
|
+
end
|
|
26
|
+
if incompatible
|
|
27
|
+
error = [first["error"], "Cannot merge x402 payment requirements with different resources."]
|
|
28
|
+
.compact
|
|
29
|
+
.reject { |item| item.to_s.empty? }
|
|
30
|
+
.join("; ")
|
|
31
|
+
return Header.encode_payment_required(first.merge("error" => error))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
errors = [first["error"], *rest.map { |value| value["error"] }]
|
|
35
|
+
.compact
|
|
36
|
+
.reject { |item| item.to_s.empty? }
|
|
37
|
+
merged = first.merge("accepts" => decoded.flat_map { |value| value["accepts"] })
|
|
38
|
+
merged["error"] = errors.join("; ") unless errors.empty?
|
|
39
|
+
Header.encode_payment_required(merged)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sig do
|
|
43
|
+
params(
|
|
44
|
+
request: T::Hash[T.untyped, T.untyped],
|
|
45
|
+
authorization: T::Hash[T.untyped, T.untyped],
|
|
46
|
+
max_timeout_seconds: Integer
|
|
47
|
+
).returns(T::Hash[String, T.untyped])
|
|
48
|
+
end
|
|
49
|
+
def to_payment_requirements(request, authorization:, max_timeout_seconds:)
|
|
50
|
+
chain_id = request.dig("methodDetails", "chainId")
|
|
51
|
+
Kernel.raise ArgumentError, "EVM charge request requires methodDetails.chainId" if chain_id.nil?
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
"amount" => request["amount"].to_s,
|
|
55
|
+
"asset" => request["currency"].to_s,
|
|
56
|
+
"extra" => {
|
|
57
|
+
"assetTransferMethod" => ASSET_TRANSFER_EIP3009,
|
|
58
|
+
"name" => authorization["name"] || authorization[:name],
|
|
59
|
+
"version" => authorization["version"] || authorization[:version]
|
|
60
|
+
},
|
|
61
|
+
"maxTimeoutSeconds" => max_timeout_seconds,
|
|
62
|
+
"network" => "#{EVM_NETWORK_PREFIX}#{chain_id}",
|
|
63
|
+
"payTo" => request["recipient"].to_s,
|
|
64
|
+
"scheme" => SCHEME_EXACT
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
sig do
|
|
69
|
+
params(
|
|
70
|
+
requirements: T::Hash[T.untyped, T.untyped],
|
|
71
|
+
resource_url: String,
|
|
72
|
+
extensions: T.nilable(T::Hash[T.untyped, T.untyped]),
|
|
73
|
+
error: T.nilable(String)
|
|
74
|
+
).returns(T::Hash[String, T.untyped])
|
|
75
|
+
end
|
|
76
|
+
def payment_required_body(requirements:, resource_url:, extensions: nil, error: nil)
|
|
77
|
+
body = {
|
|
78
|
+
"accepts" => [requirements],
|
|
79
|
+
"resource" => {"url" => resource_url},
|
|
80
|
+
"x402Version" => VERSION
|
|
81
|
+
}
|
|
82
|
+
body["extensions"] = extensions if extensions
|
|
83
|
+
body["error"] = error if error
|
|
84
|
+
body
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
sig do
|
|
88
|
+
params(
|
|
89
|
+
challenge: Mpp::Challenge,
|
|
90
|
+
http_method: T.nilable(String)
|
|
91
|
+
).returns(T.nilable(T::Hash[String, T.untyped]))
|
|
92
|
+
end
|
|
93
|
+
def route_extensions(challenge, http_method)
|
|
94
|
+
return nil if http_method.nil? || http_method.empty?
|
|
95
|
+
|
|
96
|
+
binding = {"method" => http_method}
|
|
97
|
+
binding["digest"] = challenge.digest if challenge.digest
|
|
98
|
+
binding["opaque"] = Mpp::Parsing.b64_encode(challenge.opaque) if challenge.opaque
|
|
99
|
+
{
|
|
100
|
+
MPPX_EXTENSION_KEY => {
|
|
101
|
+
"info" => binding,
|
|
102
|
+
"schema" => MPPX_ROUTE_BINDING_SCHEMA
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
sig do
|
|
108
|
+
params(
|
|
109
|
+
accepted: T::Hash[T.untyped, T.untyped],
|
|
110
|
+
resource: T::Hash[T.untyped, T.untyped],
|
|
111
|
+
extensions: T::Hash[T.untyped, T.untyped]
|
|
112
|
+
).returns(String)
|
|
113
|
+
end
|
|
114
|
+
def route_nonce(accepted:, resource:, extensions:)
|
|
115
|
+
input = [
|
|
116
|
+
serialize_request(accepted),
|
|
117
|
+
serialize_request(resource),
|
|
118
|
+
serialize_request(extensions)
|
|
119
|
+
].join("|")
|
|
120
|
+
digest = Digest::SHA256.digest(input)
|
|
121
|
+
"0x#{digest.unpack1("H*")}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
sig { params(value: T.untyped).returns(String) }
|
|
125
|
+
def serialize_request(value)
|
|
126
|
+
json = Mpp::Json.compact_encode(value)
|
|
127
|
+
Mpp.b64url_encode(json)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
sig do
|
|
131
|
+
params(
|
|
132
|
+
payment_payload: T::Hash[T.untyped, T.untyped],
|
|
133
|
+
requirements: T::Hash[T.untyped, T.untyped],
|
|
134
|
+
resource_url: T.nilable(String),
|
|
135
|
+
challenge: Mpp::Challenge,
|
|
136
|
+
body: T.untyped,
|
|
137
|
+
route_binding: Symbol,
|
|
138
|
+
http_method: T.nilable(String)
|
|
139
|
+
).returns(T::Hash[T.untyped, T.untyped])
|
|
140
|
+
end
|
|
141
|
+
def bind_credential(payment_payload:, requirements:, resource_url:, challenge:, body:, route_binding:, http_method: nil)
|
|
142
|
+
unless canonical_equal?(payment_payload["accepted"], requirements)
|
|
143
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "x402 payment payload does not match route requirements")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
assert_body_digest!(challenge, body)
|
|
147
|
+
|
|
148
|
+
expected_resource = {"url" => resource_url}
|
|
149
|
+
client_nonce = payment_payload.dig("extensions", MPPX_EXTENSION_KEY, "info", "nonce")
|
|
150
|
+
route_bound = !client_nonce.nil?
|
|
151
|
+
route_requires_binding = !challenge.digest.nil? || !challenge.opaque.nil?
|
|
152
|
+
|
|
153
|
+
if route_requires_binding && !route_bound && route_binding == :required
|
|
154
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "x402 payment payload does not bind required route metadata")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
payload_resource = payment_payload["resource"]
|
|
158
|
+
if route_bound
|
|
159
|
+
unless payload_resource.is_a?(Hash) && payload_resource["url"] == expected_resource["url"]
|
|
160
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "x402 payment payload resource does not match route resource")
|
|
161
|
+
end
|
|
162
|
+
expected_extensions = route_extensions(challenge, http_method) || {}
|
|
163
|
+
unless contains_extensions?(payment_payload["extensions"], expected_extensions)
|
|
164
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "x402 payment payload extensions do not match route binding")
|
|
165
|
+
end
|
|
166
|
+
elsif resource_url
|
|
167
|
+
if payload_resource.is_a?(Hash) && payload_resource["url"] != resource_url
|
|
168
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "x402 payment payload resource does not match route resource")
|
|
169
|
+
end
|
|
170
|
+
if route_requires_binding && (payload_resource.nil? || payload_resource["url"] != resource_url)
|
|
171
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "x402 payment payload resource does not match route resource")
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
authorization = payload_to_authorization(payment_payload)
|
|
176
|
+
if route_bound
|
|
177
|
+
expected_nonce = route_nonce(
|
|
178
|
+
accepted: requirements,
|
|
179
|
+
extensions: payment_payload["extensions"],
|
|
180
|
+
resource: expected_resource
|
|
181
|
+
)
|
|
182
|
+
unless authorization["nonce"].to_s.downcase == expected_nonce.downcase
|
|
183
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "x402 authorization nonce does not match route binding")
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
authorization
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
sig { params(payment_payload: T::Hash[T.untyped, T.untyped]).returns(T::Hash[String, T.untyped]) }
|
|
191
|
+
def payload_to_authorization(payment_payload)
|
|
192
|
+
payload = payment_payload["payload"]
|
|
193
|
+
unless payload.is_a?(Hash) && payload["authorization"].is_a?(Hash)
|
|
194
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "EVM charge only supports x402 EIP-3009 authorization payloads")
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
authorization = payload["authorization"]
|
|
198
|
+
{
|
|
199
|
+
"from" => authorization["from"].to_s,
|
|
200
|
+
"nonce" => authorization["nonce"].to_s,
|
|
201
|
+
"signature" => payload["signature"].to_s,
|
|
202
|
+
"to" => authorization["to"].to_s,
|
|
203
|
+
"type" => "authorization",
|
|
204
|
+
"validAfter" => authorization["validAfter"].to_s,
|
|
205
|
+
"validBefore" => authorization["validBefore"].to_s,
|
|
206
|
+
"value" => authorization["value"].to_s
|
|
207
|
+
}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
sig { params(left: T.untyped, right: T.untyped).returns(T::Boolean) }
|
|
211
|
+
def canonical_equal?(left, right)
|
|
212
|
+
Mpp::Json.compact_encode(left) == Mpp::Json.compact_encode(right)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
sig { params(challenge: Mpp::Challenge, body: T.untyped).void }
|
|
216
|
+
def assert_body_digest!(challenge, body)
|
|
217
|
+
return if body.nil? || challenge.digest.nil?
|
|
218
|
+
return if Mpp::BodyDigest.verify(T.must(challenge.digest), body)
|
|
219
|
+
|
|
220
|
+
Kernel.raise Mpp::VerificationFailedError.new(reason: "x402 payment body digest mismatch")
|
|
221
|
+
end
|
|
222
|
+
private_class_method :assert_body_digest!
|
|
223
|
+
|
|
224
|
+
sig { params(actual: T.untyped, expected: T::Hash[T.untyped, T.untyped]).returns(T::Boolean) }
|
|
225
|
+
def contains_extensions?(actual, expected)
|
|
226
|
+
return false unless actual.is_a?(Hash)
|
|
227
|
+
|
|
228
|
+
expected.all? do |key, expected_extension|
|
|
229
|
+
actual_extension = actual[key]
|
|
230
|
+
next false unless actual_extension.is_a?(Hash)
|
|
231
|
+
|
|
232
|
+
canonical_equal?(actual_extension["schema"], expected_extension["schema"]) &&
|
|
233
|
+
canonical_equal?(strip_client_nonce(actual_extension["info"]), expected_extension["info"])
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
private_class_method :contains_extensions?
|
|
237
|
+
|
|
238
|
+
sig { params(info: T.untyped).returns(T.untyped) }
|
|
239
|
+
def strip_client_nonce(info)
|
|
240
|
+
return info unless info.is_a?(Hash)
|
|
241
|
+
|
|
242
|
+
info.except("nonce")
|
|
243
|
+
end
|
|
244
|
+
private_class_method :strip_client_nonce
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Mpp
|
|
5
|
+
module X402
|
|
6
|
+
VERSION = 2
|
|
7
|
+
PAYMENT_METHOD = "evm"
|
|
8
|
+
EXACT_INTENT = "charge"
|
|
9
|
+
SCHEME_EXACT = "exact"
|
|
10
|
+
ASSET_TRANSFER_EIP3009 = "eip3009"
|
|
11
|
+
EVM_NETWORK_PREFIX = "eip155:"
|
|
12
|
+
SYNTHETIC_CHALLENGE_ID_PREFIX = "x402:"
|
|
13
|
+
|
|
14
|
+
PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED"
|
|
15
|
+
PAYMENT_SIGNATURE_HEADER = "PAYMENT-SIGNATURE"
|
|
16
|
+
PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE"
|
|
17
|
+
|
|
18
|
+
MPPX_EXTENSION_KEY = "mppx"
|
|
19
|
+
|
|
20
|
+
MPPX_ROUTE_BINDING_SCHEMA = T.let({
|
|
21
|
+
"additionalProperties" => false,
|
|
22
|
+
"properties" => {
|
|
23
|
+
"_mppx_scope" => {"type" => "string"},
|
|
24
|
+
"digest" => {"type" => "string"},
|
|
25
|
+
"method" => {"type" => "string"},
|
|
26
|
+
"nonce" => {"type" => "string"},
|
|
27
|
+
"opaque" => {"type" => "string"}
|
|
28
|
+
},
|
|
29
|
+
"required" => ["method"],
|
|
30
|
+
"type" => "object"
|
|
31
|
+
}.freeze, T::Hash[String, T.untyped])
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/mpp/x402.rb
ADDED
data/lib/mpp.rb
CHANGED
|
@@ -22,6 +22,8 @@ module Mpp
|
|
|
22
22
|
autoload :Events, "mpp/events"
|
|
23
23
|
autoload :Units, "mpp/units"
|
|
24
24
|
autoload :MemoryStore, "mpp/store"
|
|
25
|
+
autoload :Http, "mpp/http"
|
|
26
|
+
autoload :X402, "mpp/x402"
|
|
25
27
|
|
|
26
28
|
# Server module (autoloaded)
|
|
27
29
|
autoload :Server, "mpp/server"
|
|
@@ -33,6 +35,7 @@ module Mpp
|
|
|
33
35
|
module Methods
|
|
34
36
|
autoload :Tempo, "mpp/methods/tempo"
|
|
35
37
|
autoload :Stripe, "mpp/methods/stripe"
|
|
38
|
+
autoload :Evm, "mpp/methods/evm"
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
# Extensions namespace
|
|
@@ -40,9 +43,9 @@ module Mpp
|
|
|
40
43
|
autoload :MCP, "mpp/extensions/mcp"
|
|
41
44
|
end
|
|
42
45
|
|
|
43
|
-
sig { params(method: T.untyped, realm: T.untyped, secret_key: T.untyped, events: T.nilable(Mpp::Events::Dispatcher)).returns(T.untyped) }
|
|
44
|
-
def self.create(method
|
|
45
|
-
Server::MppHandler.create(method: method, realm: realm, secret_key: secret_key, events: events)
|
|
46
|
+
sig { params(method: T.untyped, methods: T.nilable(T::Array[T.untyped]), realm: T.untyped, secret_key: T.untyped, events: T.nilable(Mpp::Events::Dispatcher)).returns(T.untyped) }
|
|
47
|
+
def self.create(method: nil, methods: nil, realm: nil, secret_key: nil, events: nil)
|
|
48
|
+
Server::MppHandler.create(method: method, methods: methods, realm: realm, secret_key: secret_key, events: events)
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
# Error hierarchy
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mpp-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stripe
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: keccak
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.3'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.3'
|
|
26
40
|
description: Ruby SDK for the Machine Payments Protocol (MPP) — an HTTP 402 Payment
|
|
27
41
|
Authentication scheme.
|
|
28
42
|
executables: []
|
|
@@ -50,7 +64,14 @@ files:
|
|
|
50
64
|
- lib/mpp/extensions/mcp/errors.rb
|
|
51
65
|
- lib/mpp/extensions/mcp/types.rb
|
|
52
66
|
- lib/mpp/extensions/mcp/verify.rb
|
|
67
|
+
- lib/mpp/http.rb
|
|
68
|
+
- lib/mpp/http/headers.rb
|
|
53
69
|
- lib/mpp/json.rb
|
|
70
|
+
- lib/mpp/methods/evm.rb
|
|
71
|
+
- lib/mpp/methods/evm/assets.rb
|
|
72
|
+
- lib/mpp/methods/evm/authorization.rb
|
|
73
|
+
- lib/mpp/methods/evm/charge_intent.rb
|
|
74
|
+
- lib/mpp/methods/evm/evm_method.rb
|
|
54
75
|
- lib/mpp/methods/stripe.rb
|
|
55
76
|
- lib/mpp/methods/stripe/charge_intent.rb
|
|
56
77
|
- lib/mpp/methods/stripe/client_method.rb
|
|
@@ -61,11 +82,13 @@ files:
|
|
|
61
82
|
- lib/mpp/methods/tempo/attribution.rb
|
|
62
83
|
- lib/mpp/methods/tempo/client_method.rb
|
|
63
84
|
- lib/mpp/methods/tempo/defaults.rb
|
|
85
|
+
- lib/mpp/methods/tempo/fee_payer_client.rb
|
|
64
86
|
- lib/mpp/methods/tempo/fee_payer_envelope.rb
|
|
65
87
|
- lib/mpp/methods/tempo/fee_payer_policy.rb
|
|
66
88
|
- lib/mpp/methods/tempo/intents.rb
|
|
67
89
|
- lib/mpp/methods/tempo/keychain.rb
|
|
68
90
|
- lib/mpp/methods/tempo/proof.rb
|
|
91
|
+
- lib/mpp/methods/tempo/relay.rb
|
|
69
92
|
- lib/mpp/methods/tempo/rpc.rb
|
|
70
93
|
- lib/mpp/methods/tempo/schemas.rb
|
|
71
94
|
- lib/mpp/methods/tempo/transaction.rb
|
|
@@ -73,16 +96,24 @@ files:
|
|
|
73
96
|
- lib/mpp/receipt.rb
|
|
74
97
|
- lib/mpp/secure_compare.rb
|
|
75
98
|
- lib/mpp/server.rb
|
|
99
|
+
- lib/mpp/server/accept_payment.rb
|
|
100
|
+
- lib/mpp/server/compose.rb
|
|
76
101
|
- lib/mpp/server/decorator.rb
|
|
77
102
|
- lib/mpp/server/defaults.rb
|
|
78
103
|
- lib/mpp/server/intent.rb
|
|
79
104
|
- lib/mpp/server/method.rb
|
|
80
105
|
- lib/mpp/server/middleware.rb
|
|
81
106
|
- lib/mpp/server/mpp_handler.rb
|
|
107
|
+
- lib/mpp/server/result.rb
|
|
82
108
|
- lib/mpp/server/verify.rb
|
|
83
109
|
- lib/mpp/store.rb
|
|
84
110
|
- lib/mpp/units.rb
|
|
85
111
|
- lib/mpp/version.rb
|
|
112
|
+
- lib/mpp/x402.rb
|
|
113
|
+
- lib/mpp/x402/facilitator.rb
|
|
114
|
+
- lib/mpp/x402/header.rb
|
|
115
|
+
- lib/mpp/x402/server.rb
|
|
116
|
+
- lib/mpp/x402/types.rb
|
|
86
117
|
homepage: https://github.com/stripe/mpp-rb
|
|
87
118
|
licenses:
|
|
88
119
|
- MIT
|