Skip to main content
The @monad-crypto/mpp package implements Monad payments for the Machine Payments Protocol (MPP). It enables one-time payment processing through ERC-20 token transfers on Monad. This guide covers the Monad-specific @monad-crypto/mpp package. For general MPP concepts, see the MPPX documentation.

Installation

Install the package and its peer dependencies:
npm install @monad-crypto/mpp mppx viem

Server

The server defines what payment is required and verifies credentials submitted by clients. Import monad from the server entrypoint and pass it to Mppx.create.
server.ts
import { monad } from "@monad-crypto/mpp/server";
import { Mppx } from "mppx";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.SERVER_PRIVATE_KEY as `0x${string}`);

const mppx = Mppx.create({
  methods: [
    monad({
      account,
      recipient: account.address,
    }),
  ],
});

Gate an endpoint

Use mppx.charge() as middleware to require payment before serving a response.
server.ts
import { Hono } from "hono";

const app = new Hono();

app.get("/premium", mppx.charge(), async (ctx) => {
  return ctx.json({ message: "Premium content" });
});

export default app;
When a client requests /premium without a valid payment credential, the server responds with a 402 Payment Required status and a challenge describing the payment requirements. The client then submits a credential (transaction hash or signed authorization) to complete the payment.

Testnet

By default, the package connects to Monad mainnet (chain ID 143). To use testnet, set testnet: true.
monad({
  account,
  recipient: account.address,
  testnet: true,
})

Client

The client handles wallet interactions and credential creation. Import monad from the client entrypoint. With a local private key, the client defaults to pull mode — it signs an ERC-3009 authorization without broadcasting a transaction.
client.ts
import { monad } from "@monad-crypto/mpp/client";
import { Mppx } from "mppx/client";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.CLIENT_PRIVATE_KEY as `0x${string}`);

const mppx = Mppx.create({
  methods: [monad({ account })],
});

// Make a paid request
const response = await fetch("http://localhost:3000/premium");
const data = await response.json();
console.log(data);

Push vs pull mode

PushPull
Who pays gasClientServer
TransactionClient broadcasts ERC-20 transferServer calls transferWithAuthorization
CredentialTransaction hashSigned ERC-3009 authorization
Default forJSON-RPC accounts (browser wallets)Local accounts (private keys)
Trade-offClient needs gas tokensServer needs gas tokens

Overriding the mode

Use the mode option to set the payment mode explicitly, regardless of account type.
monad({
  account,
  mode: "push", // Always broadcast a transaction
})

monad({
  account,
  mode: "pull", // Always sign an authorization
})

Monkey patch

The client Mppx.create() function monkey patches the Web fetch API to use the configured payment method whenever it receives a 402 Payment Required HTTP response. You can also use mppx.fetch directly.
client.ts
import { monad } from "@monad-crypto/mpp/client";
import { Mppx } from "mppx/client";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.CLIENT_PRIVATE_KEY as `0x${string}`);

const mppx = Mppx.create({
  methods: [monad({ account })],
  polyfill: false,
});

const response = await mppx.fetch("http://localhost:3000/premium");
const data = await response.json();
console.log(data);