npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ppgppgppg/merchant-sdk

v1.0.3

Published

x402 Merchant SDK — compliance credential generation and buyer verification for paid APIs

Downloads

273

Readme

x402 Merchant SDK

Integrate your API with the x402 payment ecosystem — generate compliance credentials for payment challenges, and verify buyer identity before accepting payment.

When an AI agent calls your API through the x402 gateway, it follows the x402 HTTP payment protocol. This SDK handles the compliance layer on top of x402, so you can focus on building your API.


How it fits in

AI Agent (Cursor / Claude)
    │
    ▼
x402 MCP Gateway          ← buyer sends: agentry-payer-id, agentry-intent-vc
    │
    ▼ HTTP (x402 protocol)
Your API  ◄── this SDK
    │
    │  Request 1 — no payment:
    │    sdk.createCartMandate()  → inject into 402 response header
    │
    │  Request 2 — payment present:
    │    x402 facilitator verify()
    │    sdk.verifyPayer()        → KYC + AP2 compliance check
    │    business logic
    │    x402 facilitator settle() → on-chain USDC transfer
    ▼
Return 200 + payment-response header

Prerequisites

  • Node.js >= 18
  • A merchant account — obtain the following from your account dashboard:
    • DASHBOARD_BASE_URL — the compliance API base URL
    • AGENTRY_API_KEY — your merchant API key (agt_...)
    • AGENTRY_MERCHANT_ID — your merchant ID (AGT-...)
  • An EVM wallet address to receive USDC payments
  • Your API must implement the x402 protocol — return HTTP 402 with a payment-required header when no payment is present

Install

npm install @ppgppgppg/merchant-sdk
# or
pnpm add @ppgppgppg/merchant-sdk

Complete Working Example (Express + x402)

Below is a production-ready Express endpoint that:

  • Charges 0.10 USDC per request
  • Generates a compliance credential (CartMandate) on the first call
  • Verifies buyer identity (KYC + AP2) before accepting payment
  • Settles on-chain via the x402 Facilitator
import express from "express";
import { AgentryMerchantSDK } from "@ppgppgppg/merchant-sdk";
import {
  encodePaymentRequiredHeader,
  encodePaymentResponseHeader,
  decodePaymentSignatureHeader,
} from "@x402/core/http";
import { HTTPFacilitatorClient } from "@x402/core/server";

const app = express();
app.use(express.json());

// ── 1. Initialize the SDK ──────────────────────────────────────────────
const sdk = new AgentryMerchantSDK({
  dashboardBaseUrl: process.env.DASHBOARD_BASE_URL!,
  apiKey: process.env.AGENTRY_API_KEY!,
  merchantId: process.env.AGENTRY_MERCHANT_ID!,
  merchantAddress: process.env.MERCHANT_WALLET_ADDRESS!,
  payTo: process.env.MERCHANT_WALLET_ADDRESS!,
});

// ── 2. Initialize the x402 Facilitator (on-chain settlement) ─────────
const facilitator = new HTTPFacilitatorClient(
  process.env.FACILITATOR_URL ?? "https://x402.org/facilitator"
);

// ── 3. Define payment requirements ───────────────────────────────────
const PRICE_ATOMIC = "100000"; // 0.10 USDC (6 decimals)

const paymentRequirements = {
  scheme: "exact",
  network: "eip155:84532",                             // Base Sepolia testnet
  asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // USDC on Base Sepolia
  amount: PRICE_ATOMIC,
  payTo: process.env.MERCHANT_WALLET_ADDRESS!,
  maxTimeoutSeconds: 300,
  extra: { name: "USDC", version: "2" },
};

// ── 4. Your paid endpoint ─────────────────────────────────────────────
app.get("/api/data", async (req, res) => {
  const signatureHeader = req.headers["payment-signature"] as string | undefined;

  // ── Request 1: No payment — return 402 challenge ──────────────────
  if (!signatureHeader) {
    const resourceUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;

    const paymentRequired = {
      x402Version: 2,
      error: "Payment required to access this resource",
      resource: { url: resourceUrl, mimeType: "application/json" },
      accepts: [{ ...paymentRequirements }],
    };

    // Generate CartMandate — fail-close: if this fails, return 503
    let cartMandate;
    try {
      cartMandate = await sdk.createCartMandate({ price: "0.10", currency: "USDC" });
    } catch (err) {
      res.status(503).json({ error: "CART_MANDATE_FAILED", message: "Try again later." });
      return;
    }

    // Inject CartMandate into the payment challenge
    paymentRequired.accepts[0].extra = {
      ...paymentRequired.accepts[0].extra,
      cartMandate,
    };

    // Optional: bidirectional compliance check (recommended)
    // If the gateway sent the buyer's intent VC, verify compatibility now
    // so incompatible buyers are rejected before the payment attempt
    const intentVcHeader = req.headers["agentry-intent-vc"] as string | undefined;
    if (intentVcHeader) {
      let intentVcJson: Record<string, unknown>;
      try {
        intentVcJson = JSON.parse(Buffer.from(intentVcHeader, "base64").toString("utf-8"));
      } catch {
        res.status(400).json({ error: "INVALID_INTENT_VC_HEADER" });
        return;
      }

      const compatResult = await sdk.verifyBuyerIntent(intentVcJson, cartMandate.vcJson);
      if (!compatResult.passed) {
        res.status(403).json({
          error: "INTENT_INCOMPATIBLE",
          code: compatResult.errorCode,
          message: compatResult.reason,
        });
        return;
      }
    }

    res.setHeader("payment-required", encodePaymentRequiredHeader(paymentRequired as any));
    res.status(402).json({ error: "Payment required" });
    return;
  }

  // ── Request 2: Payment present — verify, check compliance, settle ─
  let paymentPayload;
  try {
    paymentPayload = decodePaymentSignatureHeader(signatureHeader);
  } catch {
    res.status(400).json({ error: "INVALID_PAYMENT_SIGNATURE" });
    return;
  }

  // Step A: Verify payment signature on-chain via x402 Facilitator
  let verifyResult;
  try {
    verifyResult = await facilitator.verify(paymentPayload, paymentRequirements as any);
  } catch (err) {
    res.status(502).json({ error: "FACILITATOR_UNAVAILABLE" });
    return;
  }

  if (!verifyResult.isValid) {
    res.status(402).json({ error: "PAYMENT_VERIFICATION_FAILED", reason: (verifyResult as any).invalidReason });
    return;
  }

  // Step B: Verify buyer identity — KYC + AP2 compliance
  const payerFacilitateId = req.headers["agentry-payer-id"] as string | undefined;
  if (!payerFacilitateId) {
    res.status(403).json({ error: "MISSING_PAYER_ID" });
    return;
  }

  try {
    const verification = await sdk.verifyPayer(payerFacilitateId);
    if (!verification.passed) {
      res.status(403).json({ error: "COMPLIANCE_CHECK_FAILED", reason: verification.reason });
      return;
    }
  } catch {
    res.status(503).json({ error: "COMPLIANCE_CHECK_ERROR" });
    return;
  }

  // Step C: Execute your business logic here
  const responseData = { message: "Here is your data", timestamp: new Date().toISOString() };

  // Step D: Settle on-chain and return response
  const settleResult = await facilitator.settle(paymentPayload, paymentRequirements as any);
  res.setHeader("payment-response", encodePaymentResponseHeader(settleResult as any));
  res.json(responseData);
});

app.listen(process.env.PORT ?? 3000, () => {
  console.log("Server running on port", process.env.PORT ?? 3000);
});

Environment Variables

DASHBOARD_BASE_URL=https://your-dashboard-url         # provided by the platform
AGENTRY_API_KEY=agt_your_api_key                       # from your merchant account
AGENTRY_MERCHANT_ID=AGT-XXXXX                          # from your merchant account
MERCHANT_WALLET_ADDRESS=0xYourEVMWalletAddress         # EVM address to receive USDC
FACILITATOR_URL=https://x402.org/facilitator           # x402 on-chain settlement
PORT=3000

Request & Response Flow

First Request (no payment-signature header)

Your API receives a regular HTTP request. Since there is no payment, return HTTP 402 with a payment-required header.

Required response header:

payment-required: <Base64-encoded PaymentRequired JSON>

The PaymentRequired JSON structure:

{
  "x402Version": 2,
  "error": "Payment required",
  "resource": { "url": "https://your-api.com/endpoint", "mimeType": "application/json" },
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:84532",
    "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "amount": "100000",
    "payTo": "0xYourWalletAddress",
    "maxTimeoutSeconds": 300,
    "extra": {
      "name": "USDC",
      "version": "2",
      "cartMandate": { /* generated by sdk.createCartMandate() */ }
    }
  }]
}

Optional incoming header (bidirectional compliance):

agentry-intent-vc: <Base64-encoded IntentVC JSON>

If present, call sdk.verifyBuyerIntent() before returning the 402. Returns 403 if buyer's budget is insufficient.

Second Request (with payment-signature header)

Incoming headers:

payment-signature: <Base64-encoded PaymentPayload JSON>
agentry-payer-id: FACILITATE-XXXXXXXX

Required response header on success:

payment-response: <Base64-encoded SettleResponse JSON>

API Reference

new AgentryMerchantSDK(config)

interface AgentryMerchantSDKConfig {
  dashboardBaseUrl: string;  // Compliance API base URL (from your account)
  apiKey: string;            // Your merchant API key (agt_...)
  merchantId: string;        // Your merchant ID (AGT-...)
  merchantAddress: string;   // Your EVM wallet address
  payTo: string;             // EVM address to receive USDC (usually same as merchantAddress)
  timeoutMs?: number;        // Per-request timeout in ms. Default: 8000
  maxRetries?: number;       // Max retry attempts. Default: 5
}

sdk.createCartMandate(params)

Call when: Handling the first request (no payment-signature).

Generates a signed compliance credential (W3C Verifiable Credential) for this transaction. Must be injected into accepts[0].extra.cartMandate in your payment-required header.

const cartMandate = await sdk.createCartMandate({ price: "0.10", currency: "USDC" });

| Parameter | Type | Description | |-----------|------|-------------| | price | string | Human-readable amount, e.g. "0.10" | | currency | string | Currency symbol, e.g. "USDC" |

Returns: Promise<CartMandate>

interface CartMandate {
  mandateId: string;
  merchant_id: string;
  total_amount: string;
  currency: string;
  vcJson: Record<string, unknown>; // W3C VC — inject into 402 extra
  expiresAt: string;               // ISO 8601
}

Fail-close rule: If this throws, return HTTP 503. Never skip the CartMandate. The gateway will reject payments without it.


sdk.verifyPayer(payerFacilitateId)

Call when: Handling the second request (after facilitator.verify() succeeds).

Runs KYC check and AP2 intent verification for the buyer in parallel.

const payerFacilitateId = req.headers["agentry-payer-id"] as string;
const result = await sdk.verifyPayer(payerFacilitateId);
if (!result.passed) {
  res.status(403).json({ error: "COMPLIANCE_CHECK_FAILED", reason: result.reason });
  return;
}

| Parameter | Type | Description | |-----------|------|-------------| | payerFacilitateId | string | Buyer's facilitateId (value of the agentry-payer-id request header) |

Returns: Promise<PayerVerificationResult>

interface PayerVerificationResult {
  passed: boolean;
  kycResult?: { kycCompleted: boolean; kycStatus: "approved" | "pending" | "rejected" | null };
  ap2Result?: { found: boolean };
  reason?: string; // human-readable failure reason when passed=false
}

Fail-close rule: If passed is false, reject (403). If the call throws, reject (503). Never serve a response when compliance fails.


sdk.verifyBuyerIntent(intentVcJson, cartMandateVcJson)

Call when: Handling the first request, if the agentry-intent-vc header is present.

Performs a bidirectional compatibility check — verifies that the buyer's spending mandate is compatible with your CartMandate (currency match, per-transaction limit, total budget). Allows you to reject incompatible buyers early, before any payment attempt.

const compatResult = await sdk.verifyBuyerIntent(intentVcJson, cartMandate.vcJson);
if (!compatResult.passed) {
  res.status(403).json({
    error: "INTENT_INCOMPATIBLE",
    code: compatResult.errorCode,
    message: compatResult.reason,
  });
  return;
}

| Parameter | Type | Description | |-----------|------|-------------| | intentVcJson | Record<string, unknown> | Buyer's intent VC (decoded from agentry-intent-vc header) | | cartMandateVcJson | Record<string, unknown> | Your CartMandate VC (cartMandate.vcJson from createCartMandate()) |

Returns: Promise<IntentCompatibilityResult>

interface IntentCompatibilityResult {
  passed: boolean;
  errorCode?: "INTENT_VC_INVALID" | "BUDGET_EXCEEDED" | "PER_TX_LIMIT_EXCEEDED"
            | "CURRENCY_MISMATCH" | "COMPATIBILITY_FAILED" | "VERIFY_BATCH_ERROR";
  reason?: string;
  compatibility?: {
    compatible: boolean;
    currencyMatch: boolean;
    withinPerTxLimit: boolean;
    withinTotalBudget: boolean;
    cartAmount: string;
    remainingBudget: string | null;
  };
}

This method is optional but recommended. If the header is absent (older gateway versions), skip the call and proceed normally.


Error Reference

| HTTP Status | Error Code | When it occurs | |-------------|-----------|----------------| | 400 | INVALID_PAYMENT_SIGNATURE | payment-signature header is malformed | | 400 | INVALID_INTENT_VC_HEADER | agentry-intent-vc header is not valid Base64 JSON | | 402 | PAYMENT_VERIFICATION_FAILED | x402 Facilitator rejected the payment signature | | 403 | MISSING_PAYER_ID | agentry-payer-id header is missing on second request | | 403 | COMPLIANCE_CHECK_FAILED | Buyer failed KYC or AP2 check | | 403 | INTENT_INCOMPATIBLE | Buyer's budget is insufficient for this transaction | | 502 | FACILITATOR_UNAVAILABLE | Could not reach the x402 Facilitator | | 503 | CART_MANDATE_FAILED | Could not generate CartMandate (compliance service unavailable) | | 503 | COMPLIANCE_CHECK_ERROR | Compliance service threw an unexpected error |


Register Your API

Once your API is live and passing payments, register it so AI agents can discover and call it:

  1. Log in to your merchant dashboard
  2. Go to Tools → Register Tool
  3. Fill in the following fields:

| Field | Example | Description | |-------|---------|-------------| | Tool Name | weather_get_current | Unique identifier used as the MCP tool name by the AI agent | | Description | "Get current weather for a city" | The AI reads this to decide when to call your tool | | Input Schema | { "type": "object", "properties": { "city": { "type": "string" } } } | JSON Schema of the parameters your endpoint accepts | | Endpoint URL | https://your-api.com/v1/weather | Full URL of your paid endpoint | | HTTP Method | GET or POST | HTTP method | | Price | 0.10 USDC | Amount charged per call |

  1. Save — your tool is now discoverable by any agent using the gateway.

Tip: The description field directly influences when the AI decides to call your tool. Make it specific and action-oriented: "Get real-time stock price for a given ticker symbol" is better than "Stock data".


Checklist Before Going Live

  • [ ] createCartMandate() called on first request, CartMandate injected into accepts[0].extra
  • [ ] payment-required header set with Base64-encoded PaymentRequired JSON
  • [ ] facilitator.verify() called before executing any business logic
  • [ ] agentry-payer-id header read and passed to verifyPayer()
  • [ ] verifyPayer() result checked — return 403 if passed=false
  • [ ] Business logic runs only after all checks pass
  • [ ] facilitator.settle() called before sending the 200 response
  • [ ] payment-response header set on 200 response
  • [ ] All errors follow fail-close: 503 if compliance service is down, never serve without verification
  • [ ] Tool registered on the merchant dashboard

License

MIT