ai-paywall-sdk-sui

Drop-in AI bot paywall on SUI. Provide your deployed Move package ID and server keypair — SUI micropayments land in your on-chain account directly. No API key, no signup, no custodian.

Overview

Tollgate intercepts HTTP requests at the middleware layer. When it detects an AI bot, it creates a PaywallChallenge shared object on SUI and returns HTTP 402 with the object ID, price in MIST, and the Move call target. Human visitors pass through with zero overhead.

On retry with valid X-SUI-PAYMENT-TX and X-SUI-CHALLENGE-ID headers, the SDK verifies the PaymentVerified event on-chain and unlocks content. Replay protection is intrinsic — consuming the challenge object in pay_and_unlock atomically deletes it.

No Supabase. No database. The Move contract IS the replay protection. A second attempt with the same challenge ID fails because the object no longer exists on-chain.

Prerequisites

Before using the publisher SDK, you need:

  1. SUI CLI installed and a funded testnet address
  2. The Tollgate Move package deployed (or use the shared testnet deployment)
  3. The server keypair private key (the address that creates challenges)

Deploy the Move contract

bash
# Clone the Tollgate repo and deploy
cd move/tollgate
sui client publish --skip-dependency-verification

# Note the Package Object ID from the output.
# Set SUI_PACKAGE_ID in your .env.

Export your server key

bash
# Get the bech32 private key for your active SUI address:
sui keytool export --key-identity <your-address>

# Or use the included helper script:
node scripts/export-sui-key.js

# Output: suiprivkey1qr9vrgz...
# Set as SUI_SERVER_SECRET_KEY in your .env

Fund the server address

bash
# Testnet faucet (or visit https://faucet.sui.io)
sui client faucet

# Check balance
sui client balance

Installation

bash
npm install ai-paywall-sdk-sui @mysten/sui

@mysten/sui is a peer dependency — your project controls the version.

Quick Start

Express

js
import express from "express";
import { createPaywall } from "ai-paywall-sdk-sui";
import { expressMiddleware } from "ai-paywall-sdk-sui/express";

const paywall = createPaywall({
  packageId: process.env.SUI_PACKAGE_ID,
  serverKey: process.env.SUI_SERVER_SECRET_KEY,
  network: "testnet",
  protect: ["/articles/*", "/blog/*"],
  priceMist: 1_000_000, // 0.001 SUI per crawl
});

const app = express();
app.use(expressMiddleware(paywall));

// req.suiPayment is set when a bot paid successfully
app.get("/articles/:slug", (req, res) => {
  res.json({
    content: "Your article...",
    payment: req.suiPayment ?? null,
  });
});

app.listen(3000);

Revenue Splitting with PublisherVault

Enable a PublisherVault to automatically split payments across publisher, content pool, and protocol in one atomic PTB. Create the vault once via the server API, then configure the vault ID.

bash
# Create a vault (80% publisher / 15% pool / 5% protocol)
curl -X POST http://localhost:3001/sui/v1/vault/create \
  -H "Content-Type: application/json" \
  -d '{
    "publisherBps": 8000,
    "poolAddress": "0xa4f8...",
    "poolBps": 1500,
    "protocolAddress": "0x24ae...",
    "protocolBps": 500
  }'

# Response: { "vaultObjectId": "0x...", "txDigest": "..." }
# Set SUI_VAULT_ID=<vaultObjectId> in your .env and restart.
js
// Pass vaultId to createPaywall to enable split payments.
// Agents will automatically call pay_and_unlock_split instead of pay_and_unlock.
const paywall = createPaywall({
  packageId: process.env.SUI_PACKAGE_ID,
  serverKey: process.env.SUI_SERVER_SECRET_KEY,
  network: "testnet",
  priceMist: 1_000_000,
  vaultId: process.env.SUI_VAULT_ID, // enable split mode
});
The vault stores total_received_mist and payment_count on-chain. Read live stats at GET /sui/v1/vault/:id — no indexer needed.

Configuration

Pass these options to createPaywall({ ... }).

OptionDefaultDescription
packageIdrequiredDeployed Tollgate Move package ID (0x...).
serverKeyrequiredSUI private key: bech32 (suiprivkey1...) or base64 keystore format.
network"testnet""testnet" or "mainnet".
rpcUrlpublic RPCOverride SUI RPC endpoint.
protect["/*"]Path globs to gate, e.g. ["/articles/*"]. Empty array = protect all.
priceMist1000000Price per crawl in MIST. 1 SUI = 1,000,000,000 MIST.
vaultIdPublisherVault object ID. Enables split payments if set.

Path matching

The protect option accepts strings with * wildcards.

js
protect: ["/*"]                  // all routes
protect: ["/articles/*"]        // prefix match
protect: ["/blog/*", "/docs/*"] // multiple prefixes

Bot Detection

Detection runs entirely in-process — no network call, zero overhead for human visitors. Requests are classified as bots by matching the User-Agent header against a curated pattern list.

User-Agent patterns detected
GPTBot, ChatGPT-User, ClaudeBot, anthropic-ai
PerplexityBot, CCBot, Googlebot, bingbot, Applebot
Bytespider, DiffbotCrawler, FacebookBot, LinkedInBot
python-requests, python-httpx, Go-http-client
Scrapy, curl, wget, axios, node-fetch, undici

Payment Object

After a verified payment, req.suiPayment is set on the Express request object.

js
// req.suiPayment shape (simple mode)
{
  verified: true,
  payer:      "0x24ae...",   // agent's SUI address
  amountMist: 1000000,       // MIST received (1 SUI = 1e9 MIST)
  txDigest:   "Fz9k...",    // SUI transaction digest
}

// req.suiPayment shape (vault / split mode)
{
  verified: true,
  payer:       "0x24ae...",
  totalMist:   1000000,
  split: {
    publisherMist: 800000,
    poolMist:      150000,
    protocolMist:   50000,
  },
  txDigest: "Fz9k...",
}

Environment Variables

bash
# Required
SUI_PACKAGE_ID=0xff98a1daa3a52be512b85856a93e749d89bc7d86c36219d53dea54ea9b1d1f9b
SUI_SERVER_SECRET_KEY=suiprivkey1qr9vrgztfcku2a65u9zx09mr02zcd5w8xed7unxhle70hht5wgd92rcl8vk

# Recommended
SUI_NETWORK=testnet
SUI_RPC_URL=https://fullnode.testnet.sui.io:443
SUI_PRICE_MIST=1000000

# Optional — enables split payments
SUI_VAULT_ID=0x...

PORT=3001
Never commit SUI_SERVER_SECRET_KEY to source control. The server keypair signs challenge creation transactions and must stay server-side only.