Skip to content
eucalyptus-viminalisPublic

About

Agent-native content marketplace

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

84 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

nustuf

nustuf

Agent-native content marketplace. Publish a file behind a USDC paywall with one command, and let AI agents discover it on-chain and buy it on their own.

3rd place, The Synthesis 2026 hackathon (Best Use of Locus / Agent Services on Base).

test

Why

AI agents are getting good at finding information, but they still can't pay for it. Most paid content sits behind accounts, checkouts and card forms built for humans, so an agent either stops or needs a person to step in.

nustuf removes the human from both sides of the sale. A creator turns any file into a paid link, and an agent with a wallet can find that link through an on-chain registry, receive an HTTP 402 Payment Required, pay in USDC and download the file, all without anyone setting up a store or an account. It is built on the x402 payment protocol, Locus agent wallets and Base.

Demo

Promo page a buyer lands on Live feed of on-chain drops
Promo page Live feed

A full publish, discover and buy run, with terminal output, is in demo/terminal-output.md.

Quick start

Prerequisites

Need For Notes
Node.js 18+ and pnpm everything nustuf is not on the npm registry yet, so install from source (below). Running the tests needs Node 21+.
Tailscale publish --public (selling) Free Personal plan is enough. Install it, run tailscale up, then in the admin console enable MagicDNS and HTTPS certificates, and allow Funnel (the first publish --public run prints an enable link if it is still off). This gives your drop a stable https://<machine>.<tailnet>.ts.net URL. No Tailscale? Use --tunnel cloudflared (install cloudflared) for a temporary URL that changes on restart.
A wallet with USDC (nustuf-generated, or Locus) buy Run nustuf wallet create --role buyer and fund it (see Wallets), or use a Locus wallet via skills/nustuf-buy/SKILL.md. Buying and discovering need no Tailscale.
CDP API keys mainnet payments See Configuration. Skip by selling on Base Sepolia.

Install

nustuf is not on the npm registry yet, so install it from source:

git clone https://github.com/eucalyptus-viminalis/nustuf.git
cd nustuf
pnpm install
pnpm link --global  # makes `nustuf` available globally

πŸ€– Agents: Check the skills/ directory for step-by-step guides:

Tailscale setup (one time, for selling)

publish --public uses Tailscale Funnel to give your drop a stable https://<machine>.<tailnet>.ts.net URL. Setting it up takes a few minutes:

  1. Install Tailscale and run tailscale up to log in. The free Personal plan is enough.
  2. In the admin console, open DNS and enable MagicDNS, then enable HTTPS Certificates.
  3. Allow Funnel for your device. If it is still off, the first nustuf publish --public run prints an enable link; open it and approve.
  4. Pick a machine name you are happy to publish. It becomes part of your public URL and appears in certificate transparency logs (see docs/privacy.md).

Buying and discovering do not need Tailscale. If you would rather skip it, add --tunnel cloudflared to publish for a temporary URL that changes on every restart.

Sell something

nustuf publish --file ./track.mp3 --price 0.50 --pay-to 0xYOUR_ADDRESS --public

This starts a server with x402 payment gating, exposes it at a stable public URL through Tailscale Funnel (https://<machine>.<tailnet>.ts.net), and prints a shareable promo link. The URL survives restarts, so on-chain listings stay valid. It needs Tailscale installed and logged in (free Personal plan is enough, with MagicDNS and HTTPS certificates enabled). No Tailscale account? Add --tunnel cloudflared for a temporary Cloudflare quick tunnel; its URL changes on every restart. Payments default to Base mainnet, which needs CDP keys (see Configuration). To try it without them, sell on Base Sepolia with --network eip155:84532.

Add --announce to list the drop in the on-chain registry as soon as its public URL is live, so agents can find it:

nustuf publish --file ./track.mp3 --price 0.50 --pay-to 0xYOUR_ADDRESS --public --announce --title "My Track"

A full testnet run, with payments and the listing both on Base Sepolia:

nustuf publish --file ./track.mp3 --price 0.50 --pay-to 0xYOUR_ADDRESS --public --network eip155:84532 --announce --title "My Track"

The listing uses the sale price, expires when the sale window ends, and includes a SHA-256 hash of the file. It is signed by the first key found among --private-key-file, DEPLOYER_PRIVATE_KEY (shell or .env), the seller wallet and .locus.json; the publish summary prints the signer address and its balance. It is written to the same chain as the payments (--network), so a Base Sepolia drop is listed on Base Sepolia; a --testnet or NUSTUF_REGISTRY_CHAIN that disagrees with --network is an error. Listing on mainnet costs a little real gas. If the signer has no ETH on that chain, publish exits before going live. The announce result (tx hash or error) shows up in nustuf status --json. If the announce fails, the drop stays live and nustuf prints the nustuf announce command to retry by hand.

Note: in a terminal, nustuf publish opens an interactive wizard prefilled from your flags and saved profile (nustuf config). Without a terminal (agents, CI, scripts) it runs straight from the flags, and --public also needs --public-confirm I_UNDERSTAND_PUBLIC_EXPOSURE.

Discover drops

nustuf discover --active

Queries the registry for live releases. It reads Base mainnet by default, so testnet drops only show up with --testnet (Base Sepolia). Filter with --creator, --max-price and --limit, and add --json for agent-friendly output. In --json, price is rounded to 2 decimals; use priceRaw (USDC base units) for sub-cent prices.

Buy something

nustuf buy https://some-nustuf-url.com/            # pays from your nustuf buyer wallet
nustuf buy https://some-nustuf-url.com/ --locus    # or pay with a Locus wallet

Pays and downloads the file. With no flags it uses the wallet from nustuf wallet create --role buyer.

Files are saved to ~/Downloads/nustuf/ by default (set NUSTUF_DOWNLOAD_DIR to change it, or pass --out <file>). nustuf never overwrites an existing file; the new one is saved as name (1).ext, and the full path is printed.

Browse the feed

nustuf feed-ui

Opens a local web UI showing live announcements from the on-chain registry. No wallet or config needed.

  • --testnet reads Base Sepolia instead of Base mainnet.
  • --port <port> changes the local port (default 3000, or the PORT env var).
  • --public also exposes the feed at a public URL, using Tailscale Funnel by default (stable URL) or --tunnel cloudflared (temporary URL). Without it, the feed stays on localhost.

See what you're serving

nustuf status          # what this machine is serving right now
nustuf status --json   # same, machine-readable
nustuf stop <id>       # stop one item (id or unique prefix), or: nustuf stop all

Every nustuf publish records itself under ~/.nustuf/runs/, so status works from any terminal. Runs started before this was added show as unknown in status --all and are not listed as serving.

For a menu bar indicator, install SwiftBar (brew install --cask swiftbar) and copy or symlink integrations/swiftbar/nustuf.10s.sh into its plugin folder. SwiftBar runs plugins with a minimal PATH, so the plugin adds the usual Node locations (Homebrew, nvm, Volta, fnm); if it still can't find nustuf, set NUSTUF_CLI to its full path in the plugin's environment settings. It shows a green dot and count while anything is live, with a per-item menu to open, copy the link, or stop.

Wallets

nustuf wallet create --role buyer --testnet   # prints an address; send USDC there
nustuf wallet create --role seller --testnet  # prints an address; send a little ETH there
nustuf wallet show                            # addresses and USDC/ETH balances

nustuf generates the wallet, so you only send funds to its public address from a wallet you already use. Keys are stored in ~/.nustuf/wallets/<role>.key (mode 600). These are hot wallets: keep balances small. create never overwrites an existing wallet, and export prints the key only in an interactive terminal.

  • buyer: nustuf buy pays from it when you pass no --locus or --buyer-private-key-file. It needs USDC on the seller's network and no ETH.
  • seller: --announce signs with it when there is no --private-key-file or DEPLOYER_PRIVATE_KEY. It needs a little ETH for gas, and its address works as --pay-to.
  • Network: --network <caip2> or --testnet (default: your configured chain, else Base mainnet). It only affects the funding hint and the balances shown.

Configuration

Set these as environment variables or in a .env file.

Variable Needed for Notes
LOCUS_API_KEY buy --locus Locus agent wallet. A .locus.json file with { "apiKey": "..." } also works. No wallet yet? See skills/nustuf-buy/SKILL.md.
NUSTUF_REGISTRY_ADDRESS publish --announce, announce, discover Optional. Defaults to the deployed registry, 0x134597d9Cc6270571C2b8245c4235f7838C0d65D.
NUSTUF_REGISTRY_CHAIN publish --announce, announce, discover Optional. base (default) or base-sepolia for announce and discover. publish --announce lists on the --network chain, and exits with an error if this disagrees.
DEPLOYER_PRIVATE_KEY publish --announce, announce, identity Optional. Key that signs on-chain writes; without it nustuf uses the seller wallet. Use a dedicated low-value key.
SELLER_PAY_TO publish Optional. Payout address when --pay-to is not given (after that, the saved profile).
NUSTUF_DOWNLOAD_DIR buy Optional. Download folder (default ~/Downloads/nustuf/).
CDP_API_KEY_ID, CDP_API_KEY_SECRET, FACILITATOR_MODE=cdp_mainnet mainnet payments Coinbase Developer Platform keys for settling USDC on Base mainnet.

How it works

Creator                          Blockchain                        Buyer Agent
  β”‚                                  β”‚                                  β”‚
  β”œβ”€ nustuf publish ────────────────►│ announce (on-chain registry)     β”‚
  β”‚  (server + Tailscale Funnel)     β”‚                                  β”‚
  β”‚                                  │◄──────────── nustuf discover ─────
  β”‚                                  β”‚  (query active releases)         β”‚
  │◄─────────────────────────────────┼──────────── nustuf buy ───────────
  β”‚  402 Payment Required            β”‚                                  β”‚
  │◄─────────────────────────────────┼──────── USDC payment ─────────────
  β”‚  βœ… Content delivered            β”‚                                  β”‚
  1. Publish: the creator runs nustuf publish. A local Express server gates the file with x402, Tailscale Funnel gives it a stable public URL (a Cloudflare quick tunnel with --tunnel cloudflared), and --announce writes the drop to the registry.
  2. Discover: a buyer agent reads active releases from the registry with viem.
  3. Buy: the agent requests the URL, receives a 402 Payment Required with the price and payee, pays in USDC from its wallet (a nustuf buyer wallet or Locus), and gets the file.

The buyer never needs to know the file format or create an account; the HTTP response tells it everything it needs to pay.

Agent skills

nustuf ships 3 agent skills in the skills/ directory. Each has a SKILL.md with full instructions an agent can follow autonomously:

Skill Description
nustuf-publish Publish content behind a USDC paywall
nustuf-buy Purchase content with a nustuf buyer wallet or Locus
nustuf-discover Discover live drops from the on-chain registry

Running nustuf servers also expose skill metadata at /.well-known/skills/index.json for automated agent discovery.

CLI reference

nustuf publish    Publish content behind payment gate
nustuf buy        Purchase content
nustuf discover   Find live releases
nustuf feed-ui    Browse live releases in the browser
nustuf announce   Register a drop on-chain
nustuf status     Show what this machine is serving
nustuf stop       Stop a published item (or all)
nustuf host       Multi-host server
nustuf config     Manage configuration
nustuf wallet     Generate and check a buyer or seller wallet
nustuf identity   ERC-8004 agent identity (testnet, draft)
nustuf version    Show the version

Run nustuf --help or nustuf <command> --help for details.

Stack

  • Node.js and Express: CLI and payment-gated content server
  • x402: HTTP 402 payment protocol
  • Locus: agent wallet for autonomous USDC payments
  • Base: L2 for USDC payments and the on-chain registry
  • Solidity and Foundry: the NustufRegistry contract (contracts/)
  • viem: chain reads and writes
  • Tailscale Funnel: stable public URLs for a machine you own (Cloudflare quick tunnels remain as --tunnel cloudflared)

Privacy

What Tailscale's relays can and cannot see when you sell with --public, and what ends up public: see docs/privacy.md.

Smart contract

NustufRegistry is deployed at the same address on both networks:

Tests

pnpm test             # unit tests, plus integration tests that start the real server
pnpm test:contracts   # Foundry tests for NustufRegistry
pnpm check            # version sync, no machine-specific paths, skill command scope, terminology, no comments

The integration tests run the server against a fake x402 facilitator on localhost, so they need no wallet, network access or credentials. They check that an unpaid download gets 402 Payment Required with the right payment requirements, and that download codes are enforced. pnpm test needs Node 21 or later.

The commands that spend money or write to the registry have their own tests. test/cli_inputs.test.js runs nustuf buy and checks that bad URLs, --out paths, download-code and key inputs fail before any request or payment. test/announce.test.js checks the nustuf announce plan (price to USDC base units, expiry, URL and content-hash validation), the signer-key order and the gas check. test/help.test.js checks that every --help exits cleanly and only suggests commands that exist. CI runs all of it, plus pnpm check, on every push and pull request.

Status

nustuf was built during a hackathon and works end to end, but it is not production software:

  • The package is not published to npm yet; install from source as above.
  • Funnel traffic goes through Tailscale's relays and is subject to an unpublished, non-configurable bandwidth cap, so it suits small files better than large ones. With --tunnel cloudflared, the URL lasts only as long as the publishing process runs.
  • nustuf host still uses Cloudflare quick tunnels.
  • A drop is announced once, on its first public URL. If the tunnel URL changes (quick tunnels, or a renamed machine or tailnet), nustuf warns and prints a nustuf announce command rather than writing a second listing.

License

MIT

About

Agent-native content marketplace

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages