mpp-rb
Ruby SDK for the Machine Payments Protocol
Documentation
Full documentation, API reference, and guides are available at mpp.dev/sdk/ruby.
Install
gem install mpp-rb
Or add to your Gemfile:
gem "mpp-rb"
Quick Start
Server
require "mpp-rb"
server = Mpp.create(
method: Mpp::Methods::Tempo.tempo(
intents: {"charge" => Mpp::Methods::Tempo::ChargeIntent.new},
recipient: "0x0000000000000000000000000000000000000001",
),
)
# In your request handler (Sinatra, Rails, Rack, etc.)
result = server.charge(, "0.50", description: "Paid endpoint")
if result.is_a?(Mpp::Challenge)
# Return 402 with WWW-Authenticate header
resp = Mpp::Server::Decorator.make_challenge_response(result, server.realm)
# resp["status"], resp["headers"], resp["body"]
else
credential, receipt = result
# credential.source — payer address
# receipt.to_payment_receipt — Payment-Receipt header value
end
Multiple methods
server = Mpp.create(methods: [tempo, evm, stripe])
paid = server.compose(
[tempo, {amount: "0.01"}],
[evm, {amount: "0.01"}],
[stripe, {amount: "0.01", currency: "usd"}]
)
result = paid.call(
authorization: env["HTTP_AUTHORIZATION"],
payment_signature: env["HTTP_PAYMENT_SIGNATURE"],
accept_payment: env["HTTP_ACCEPT_PAYMENT"],
url: request.url,
http_method: request.request_method
)
if result.payment_required?
resp = result.to_response
else
credential, receipt = result.payment
end
evm.charge additionally emits PAYMENT-REQUIRED and accepts PAYMENT-SIGNATURE (x402 v2 exact) when a facilitator is configured:
# Public / testnet facilitator
x402: {facilitator: "https://x402.org/facilitator"}
# Per-request headers (bearer token, CDP JWT, etc.)
x402: {facilitator: {url: facilitator_url, headers: -> { {"Authorization" => "Bearer #{token}"} }}}
# The proc may take the request path (`/verify`, `/settle`) when headers differ per call
x402: {facilitator: {url: cdp_url, headers: ->(path) { cdp_headers(path) }}}
# Any client with #verify / #settle
x402: {facilitator: cdp_client}
Tempo charge can sponsor gas through a hosted fee payer, or skip local RPC by sending credentials to a Tempo API-compatible relay. Both use the same {url:, headers:} shape as the x402 facilitator:
# Hosted fee payer (JSON-RPC eth_signRawTransaction)
fee_payer: {url: sponsor_url, headers: -> { {"Authorization" => "Bearer #{token}"} }}
# Local co-sign
fee_payer: Mpp::Methods::Tempo::Account.from_key(ENV.fetch("FEE_PAYER_KEY"))
# Relay (POST /v1/mpp/validate then /v1/mpp/broadcast)
relay: {url: "https://api.tempo.xyz", headers: -> { {"tempo-api-key" => ENV.fetch("TEMPO_API_KEY")} }}
Client
require "mpp-rb"
account = Mpp::Methods::Tempo::Account.from_key("0x...")
transport = Mpp::Client::Transport.new(
methods: [
Mpp::Methods::Tempo.tempo(
account: account,
intents: {"charge" => Mpp::Methods::Tempo::ChargeIntent.new},
),
],
)
response = transport.request(:get, "https://mpp.dev/api/ping/paid")
Event hooks
Register hooks to observe the automatic payment lifecycle. Each registration returns an unsubscribe proc.
server.on_challenge_created do |payload|
puts "challenge: #{payload[:challenge].id}"
end
server.on_payment_success do |payload|
puts "paid: #{payload[:receipt].reference}"
end
transport.on_challenge_received do |payload|
puts "received: #{payload[:challenge].id}"
nil
end
transport.on_payment_response do |payload|
puts "retry status: #{payload[:response].code}"
end
transport.on("*") do |event|
puts "payment event: #{event.name}"
end
Client events are challenge.received, credential.created, payment.response, and payment.failed. Server events are challenge.created, payment.success, and payment.failed.
Rack Middleware
require "mpp-rb"
handler = Mpp.create(
method: Mpp::Methods::Tempo.tempo(
intents: {"charge" => Mpp::Methods::Tempo::ChargeIntent.new},
recipient: "0x0000000000000000000000000000000000000001",
),
)
# In your config.ru or Rails middleware stack:
use Mpp::Server::Middleware, handler: handler
# In your app, signal that payment is required:
env["mpp.charge"] = { amount: "0.50", description: "Paid endpoint" }
Examples
| Example | Description |
|---|---|
| tempo_charge | Tempo testnet payments via Sinatra |
| stripe_charge | Stripe payments via Shared Payment Tokens |
| compose | Tempo + Base USDC + Stripe SPTs on one endpoint |
| evm_x402 | EVM charge with x402 exact compatibility |
| tempo_feepayer | Tempo charge with a hosted fee-payer {url:, headers:} |
| tempo_relay | Tempo charge delegated to an MPP relay {url:, headers:} |
Each example is a standalone Sinatra app with /free and /paid endpoints. To run one:
cd examples/tempo_charge
bundle install
ruby app.rb
Then test with mppx, a CLI that handles the full 402 challenge/credential flow:
npx mppx http://localhost:4567/paid
Support Matrix
| Method | Charge Client | Charge Server |
|---|---|---|
| Tempo | Yes | Yes |
| Stripe | Yes | Yes |
EVM (evm.charge, x402 exact) |
No | Yes |
Tempo charge transaction construction is implemented directly in Ruby. Runtime dependency: keccak (Tempo attribution memos). Optional dependencies: eth (account signing, EIP-3009 recovery) and rlp (fee payer envelope).
Mpp.create accepts a single method: (unchanged) or methods: to register several payment methods. server.compose presents every method as multiple WWW-Authenticate challenges; evm.charge also emits PAYMENT-REQUIRED and accepts PAYMENT-SIGNATURE when a facilitator is configured. The Ruby HTTP client does not yet sign EVM or x402 credentials.
Protocol
Built on the "Payment" HTTP Authentication Scheme. See mpp-specs for the full specification.
Releasing
- Create a release PR:
- Update the version in
lib/mpp/version.rb - Run
bundle lock --update mpp-rbin the root and eachexamples/subdirectory - Commit and open a PR to verify CI passes
- Update the version in
- Merge the PR
- Tag the merge commit:
git tag v0.x.x - Push the tag:
git push origin --tags
License
MIT