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.
Prerequisites
Before using the publisher SDK, you need:
- SUI CLI installed and a funded testnet address
- The Tollgate Move package deployed (or use the shared testnet deployment)
- The server keypair private key (the address that creates challenges)
Deploy the Move contract
# 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
# 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 .envFund the server address
# Testnet faucet (or visit https://faucet.sui.io)
sui client faucet
# Check balance
sui client balanceInstallation
npm install ai-paywall-sdk-sui @mysten/sui@mysten/sui is a peer dependency — your project controls the version.
Quick Start
Express
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.
# 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.// 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
});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({ ... }).
| Option | Default | Description |
|---|---|---|
packageId | required | Deployed Tollgate Move package ID (0x...). |
serverKey | required | SUI private key: bech32 (suiprivkey1...) or base64 keystore format. |
network | "testnet" | "testnet" or "mainnet". |
rpcUrl | public RPC | Override SUI RPC endpoint. |
protect | ["/*"] | Path globs to gate, e.g. ["/articles/*"]. Empty array = protect all. |
priceMist | 1000000 | Price per crawl in MIST. 1 SUI = 1,000,000,000 MIST. |
vaultId | — | PublisherVault object ID. Enables split payments if set. |
Path matching
The protect option accepts strings with * wildcards.
protect: ["/*"] // all routes
protect: ["/articles/*"] // prefix match
protect: ["/blog/*", "/docs/*"] // multiple prefixesBot 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.
// 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
# 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=3001SUI_SERVER_SECRET_KEY to source control. The server keypair signs challenge creation transactions and must stay server-side only.