Skip to content

plugin.delivery Integration Guide

Add x402 pricing to plugin marketplace in nirholas/plugin.delivery

Overview

The plugin.delivery repository (9⭐) is a plugin marketplace. This guide shows how to add x402 pricing for paid plugin distribution with revenue sharing via x402RevenueSplitter.

Installation

cd plugin.delivery
npm install @nirholas/x402-ecosystem

Integration Steps

1. Add Pricing Schema to Plugins

// src/types/plugin.ts
import type { Address } from "@nirholas/x402-ecosystem";

/**
 * Plugin pricing configuration
 */
export interface PluginPricing {
  type: "free" | "paid" | "freemium" | "subscription";
  price?: string; // USD amount
  currency?: "USDC" | "USDs";
  period?: "one-time" | "monthly" | "yearly";
  trialDays?: number;
  revenueSplit?: RevenueSplitConfig[];
}

/**
 * Revenue split configuration
 */
export interface RevenueSplitConfig {
  address: Address;
  percentage: number;
  label: string;
}

/**
 * Extended plugin definition with pricing
 */
export interface PaidPlugin {
  id: string;
  name: string;
  description: string;
  version: string;
  author: {
    name: string;
    address: Address;
  };
  pricing: PluginPricing;
  downloads: number;
  rating: number;
  verified: boolean;
  categories: string[];
  repository?: string;
  homepage?: string;
}

2. Create Plugin Payment Service

// src/services/payment.ts
import { 
  PayableAgent, 
  ToolMarketplace,
  type PaymentResult,
} from "@nirholas/x402-ecosystem";
import type { PaidPlugin, RevenueSplitConfig } from "../types/plugin.js";

export class PluginPaymentService {
  private readonly marketplace: ToolMarketplace;
  private readonly platformAddress: `0x${string}`;
  private readonly platformFee: number;

  constructor(config: {
    platformAddress: `0x${string}`;
    platformFee?: number; // Default 10%
  }) {
    this.marketplace = new ToolMarketplace();
    this.platformAddress = config.platformAddress;
    this.platformFee = config.platformFee ?? 10;
  }

  /**
   * Process plugin purchase
   */
  async purchasePlugin(
    plugin: PaidPlugin,
    buyer: PayableAgent
  ): Promise<PaymentResult> {
    if (plugin.pricing.type === "free") {
      return {
        success: true,
        amount: "0",
        token: "USDC",
        recipient: plugin.author.address,
        chain: "eip155:8453",
        timestamp: Date.now(),
      };
    }

    const price = plugin.pricing.price ?? "0";

    // Create payment with revenue split
    const revenueSplit = this.calculateRevenueSplit(plugin);

    // Process payment through x402
    return buyer.payWithRevenueSplit(
      price,
      revenueSplit,
      `Purchase: ${plugin.name} v${plugin.version}`
    );
  }

  /**
   * Calculate revenue split for a plugin
   */
  private calculateRevenueSplit(plugin: PaidPlugin): RevenueSplitConfig[] {
    // If custom split defined, use it
    if (plugin.pricing.revenueSplit?.length) {
      return this.ensurePlatformFee(plugin.pricing.revenueSplit);
    }

    // Default split: 90% to author, 10% to platform
    return [
      {
        address: plugin.author.address,
        percentage: 100 - this.platformFee,
        label: "author",
      },
      {
        address: this.platformAddress,
        percentage: this.platformFee,
        label: "platform",
      },
    ];
  }

  /**
   * Ensure platform fee is included in split
   */
  private ensurePlatformFee(
    split: RevenueSplitConfig[]
  ): RevenueSplitConfig[] {
    const platformEntry = split.find(s => s.label === "platform");

    if (!platformEntry) {
      // Reduce all other percentages proportionally to add platform fee
      const scaleFactor = (100 - this.platformFee) / 100;
      const adjusted = split.map(s => ({
        ...s,
        percentage: s.percentage * scaleFactor,
      }));

      adjusted.push({
        address: this.platformAddress,
        percentage: this.platformFee,
        label: "platform",
      });

      return adjusted;
    }

    return split;
  }

  /**
   * Register plugin with x402 marketplace
   */
  async registerPluginForPayments(plugin: PaidPlugin): Promise<void> {
    if (plugin.pricing.type === "free") return;

    await this.marketplace.registerTool({
      name: `plugin-${plugin.id}`,
      description: plugin.description,
      endpoint: `https://plugin.delivery/api/plugins/${plugin.id}/download`,
      price: plugin.pricing.price ?? "0",
      owner: plugin.author.address,
      category: "Plugin",
      revenueSplit: this.calculateRevenueSplit(plugin),
    });
  }
}

3. Create Pricing UI Components

// src/components/PluginPricing.tsx
import React from "react";
import type { PluginPricing } from "../types/plugin.js";

interface Props {
  pricing: PluginPricing;
  onPurchase: () => void;
  isPurchasing: boolean;
}

export function PluginPricingCard({ pricing, onPurchase, isPurchasing }: Props) {
  if (pricing.type === "free") {
    return (
      <div className="pricing-card free">
        <span className="price">Free</span>
        <button onClick={onPurchase} disabled={isPurchasing}>
          Install
        </button>
      </div>
    );
  }

  const priceDisplay = formatPrice(pricing.price ?? "0", pricing.period);

  return (
    <div className="pricing-card paid">
      <span className="price">${priceDisplay}</span>
      {pricing.trialDays && (
        <span className="trial">{pricing.trialDays}-day free trial</span>
      )}
      <button 
        onClick={onPurchase} 
        disabled={isPurchasing}
        className="purchase-btn"
      >
        {isPurchasing ? "Processing..." : "Purchase with USDC"}
      </button>
      <span className="payment-info">
        Powered by x402 • Instant settlement
      </span>
    </div>
  );
}

function formatPrice(price: string, period?: string): string {
  switch (period) {
    case "monthly": return `${price}/mo`;
    case "yearly": return `${price}/yr`;
    case "one-time": return price;
    default: return price;
  }
}

4. Create Purchase Flow

// src/hooks/usePurchase.ts
import { useState } from "react";
import { PayableAgent } from "@nirholas/x402-ecosystem";
import { PluginPaymentService } from "../services/payment.js";
import type { PaidPlugin } from "../types/plugin.js";

export function usePurchasePlugin() {
  const [isPurchasing, setIsPurchasing] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const purchase = async (plugin: PaidPlugin, privateKey: `0x${string}`) => {
    setIsPurchasing(true);
    setError(null);

    try {
      const agent = new PayableAgent({
        privateKey,
        maxPaymentPerRequest: plugin.pricing.price ?? "100",
      });

      const service = new PluginPaymentService({
        platformAddress: process.env.NEXT_PUBLIC_PLATFORM_ADDRESS as `0x${string}`,
      });

      const result = await service.purchasePlugin(plugin, agent);

      if (result.success) {
        // Grant access to plugin
        await grantPluginAccess(plugin.id, agent.address);
        return result;
      } else {
        throw new Error(result.error ?? "Payment failed");
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : "Unknown error");
      throw err;
    } finally {
      setIsPurchasing(false);
    }
  };

  return { purchase, isPurchasing, error };
}

async function grantPluginAccess(pluginId: string, address: string) {
  // Record purchase and grant access
  await fetch("/api/plugins/grant-access", {
    method: "POST",
    body: JSON.stringify({ pluginId, address }),
  });
}

5. API Routes for Payments

// src/pages/api/plugins/[id]/purchase.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { PluginPaymentService } from "../../../../services/payment.js";
import { verifyX402Payment } from "@nirholas/x402-ecosystem";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" });
  }

  const { id } = req.query;
  const paymentHeader = req.headers["x-402-payment"] as string;

  if (!paymentHeader) {
    // Return 402 with payment requirements
    const plugin = await getPlugin(id as string);

    return res.status(402).json({
      error: "Payment Required",
      paymentDetails: {
        amount: plugin.pricing.price,
        token: plugin.pricing.currency ?? "USDC",
        recipient: plugin.author.address,
        network: "eip155:8453",
      },
    });
  }

  // Verify payment
  const verification = await verifyX402Payment(paymentHeader);

  if (!verification.valid) {
    return res.status(402).json({
      error: "Invalid payment",
      details: verification.error,
    });
  }

  // Grant access
  await grantAccess(id as string, verification.payer);

  return res.json({
    success: true,
    message: "Plugin purchased successfully",
    downloadUrl: `/api/plugins/${id}/download`,
  });
}

async function getPlugin(id: string) {
  // Fetch plugin from database
  return {} as any;
}

async function grantAccess(pluginId: string, address: string) {
  // Store purchase record
}

6. Revenue Dashboard

// src/components/RevenueDashboard.tsx
import React, { useEffect, useState } from "react";
import type { Address } from "@nirholas/x402-ecosystem";

interface RevenueStats {
  totalEarned: string;
  thisMonth: string;
  pendingPayout: string;
  plugins: Array<{
    name: string;
    sales: number;
    revenue: string;
  }>;
}

export function RevenueDashboard({ authorAddress }: { authorAddress: Address }) {
  const [stats, setStats] = useState<RevenueStats | null>(null);

  useEffect(() => {
    fetchRevenueStats(authorAddress).then(setStats);
  }, [authorAddress]);

  if (!stats) return <div>Loading...</div>;

  return (
    <div className="revenue-dashboard">
      <h2>Revenue Dashboard</h2>

      <div className="stats-grid">
        <div className="stat">
          <label>Total Earned</label>
          <span>${stats.totalEarned}</span>
        </div>
        <div className="stat">
          <label>This Month</label>
          <span>${stats.thisMonth}</span>
        </div>
        <div className="stat">
          <label>Pending</label>
          <span>${stats.pendingPayout}</span>
        </div>
      </div>

      <h3>Plugin Performance</h3>
      <table>
        <thead>
          <tr>
            <th>Plugin</th>
            <th>Sales</th>
            <th>Revenue</th>
          </tr>
        </thead>
        <tbody>
          {stats.plugins.map(plugin => (
            <tr key={plugin.name}>
              <td>{plugin.name}</td>
              <td>{plugin.sales}</td>
              <td>${plugin.revenue}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

async function fetchRevenueStats(address: Address): Promise<RevenueStats> {
  const res = await fetch(`/api/revenue/${address}`);
  return res.json();
}

Plugin Manifest Schema

Update plugin manifest to include pricing:

{
  "name": "my-awesome-plugin",
  "version": "1.0.0",
  "description": "An awesome plugin",
  "author": {
    "name": "Developer",
    "address": "0x..."
  },
  "pricing": {
    "type": "paid",
    "price": "9.99",
    "currency": "USDC",
    "period": "one-time",
    "revenueSplit": [
      { "address": "0x...", "percentage": 85, "label": "author" },
      { "address": "0x...", "percentage": 10, "label": "platform" },
      { "address": "0x...", "percentage": 5, "label": "marketing" }
    ]
  }
}

Example Pricing Models

Model Use Case Example
Free Open source plugins Community tools
One-time Complete plugins $19.99 once
Monthly Plugins with updates $4.99/mo
Freemium Basic free, premium paid Free + $9.99 Pro

Testing

// test/payment.test.ts
import { describe, it, expect } from "vitest";
import { PluginPaymentService } from "../src/services/payment.js";

describe("PluginPaymentService", () => {
  it("should calculate correct revenue split", () => {
    const service = new PluginPaymentService({
      platformAddress: "0x..." as `0x${string}`,
      platformFee: 10,
    });

    const plugin = {
      id: "test",
      pricing: { type: "paid", price: "10.00" },
      author: { address: "0x..." as `0x${string}` },
    };

    const split = service["calculateRevenueSplit"](plugin as any);

    expect(split).toHaveLength(2);
    expect(split[0].percentage).toBe(90);
    expect(split[1].percentage).toBe(10);
  });
});

Next Steps

  1. Clone plugin.delivery repository
  2. Add @nirholas/x402-ecosystem dependency
  3. Add pricing schema to plugin types
  4. Create payment service
  5. Build purchase UI flow
  6. Add revenue dashboard
  7. Update documentation