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

tollgate-pay-per-piece

v0.1.3

Published

Tollgate SDK for routing per-piece creator payments through FeeRouter on Arc testnet.

Readme

tollgate-pay-per-piece

TypeScript SDK for routing per-piece creator payments through the Tollgate FeeRouter on Arc testnet.

The default import contains the chain constants, FeeRouter reads and writes, nonce reservation, and a storage-independent split registry interface. Node file storage and x402 support are separate entry points.

Before you start

  • This targets Arc testnet only. Chain ID, RPC default, and the USDC token address are all Arc-testnet constants (src/chain.ts). There is no mainnet path today.
  • FEE_ROUTER_ADDRESS (src/fee-router-contract.ts) is Tollgate's own deployed FeeRouter contract on Arc testnet — you don't deploy your own. You're routing payments through shared, Tollgate-operated infrastructure, not standing up independent settlement infra. Splits are namespaced by creator address, so this is safe to share across integrators, but it does mean your payouts depend on that contract staying live and unmodified.
  • You need a funded payer wallet before any of this works: a private key holding some Arc testnet USDC (covers both the payment amount and gas — Arc gas is paid in USDC). Get testnet USDC from https://faucet.circle.com. Nothing in the code below will succeed against an empty wallet.
  • The @x402/evm/@x402/fetch peer dependencies are pinned to exact versions (2.17.0) in package.json. If your app already depends on different versions of these for its own x402 handling, you'll get a peer conflict — check before installing.

Install

npm install tollgate-pay-per-piece

Or use it from this repo without publishing a new version:

cd pay-per-piece
npm install
npm run build
cd ../your-app
npm install --install-links ../pay-per-piece

Route a per-piece payment

import path from "node:path";
import {
  createFeeRouterPublicClient,
  createFeeRouterSigner,
  ensureCreatorSplit,
  payViaSplit,
} from "tollgate-pay-per-piece";
import { createFileSplitRegistryStore } from "tollgate-pay-per-piece/stores/file";

const privateKey = process.env.PAYER_PRIVATE_KEY;
const recipient = process.env.CREATOR_ADDRESS;
if (!privateKey || !recipient) throw new Error("Payer and creator are required.");

const publicClient = createFeeRouterPublicClient();
const signer = createFeeRouterSigner(privateKey);
const store = createFileSplitRegistryStore(
  path.join(process.cwd(), "data", "fee-router-splits.json"),
);
const split = await ensureCreatorSplit(
  store,
  "article-store",
  recipient,
  [recipient],
  [10_000],
  signer,
  publicClient,
);
const payment = await payViaSplit(
  signer,
  BigInt(split.splitId),
  1_000n,
  publicClient,
);

console.log(payment.txHash);

payViaSplit checks the payer's ERC-20 USDC balance and allowance, submits an approval when needed, waits for successful receipts, and validates the mined Routed event before returning. The current approval policy grants the larger of the payment amount or a 10,000-USDC standing allowance, matching the proven settlement path; use a dedicated capped testnet payer and treat that allowance as an explicit security boundary.

Split registry storage

The core package accepts any store with this interface:

type SplitRegistryStore = {
  read(): Promise<FeeRouterSplitRegistry>;
  write(registry: FeeRouterSplitRegistry): Promise<void>;
  getOrInsert?(
    key: FeeRouterSplitKey,
    insert: () => Promise<FeeRouterSplitRecord>,
  ): Promise<{ record: FeeRouterSplitRecord; inserted: boolean }>;
};

The optional operation claims a split identity before it invokes the on-chain insert callback. Existing two-method stores keep working through the process-local fallback; stores that coordinate multiple writers should implement getOrInsert and leave ambiguous failed claims pending rather than retrying an on-chain transaction.

The Node-only createFileSplitRegistryStore(path) implementation lives at tollgate-pay-per-piece/stores/file. It keeps the registry JSON format unchanged, writes a temporary sibling and renames it over the target, and uses sibling pending claims plus a short write lock to coordinate cooperating processes on one host. A crashed or ambiguous creation stays pending for manual chain reconciliation. Independent hosts and network filesystems need a database-backed implementation with a unique split-identity constraint. Nonce and same-payer payment locks remain process-local; run one process per payer account or supply external coordination when multiple workers share a payer.

Optional x402 paid fetch

Install the optional peers only when an x402 client is needed:

npm install @x402/[email protected] @x402/[email protected]
import { createX402PaidFetch } from "tollgate-pay-per-piece/adapters/x402";

async function fetchPaidResource(x402Signer) {
  const paidFetch = createX402PaidFetch({ signer: x402Signer });
  return paidFetch("https://example.com/paid-resource");
}

The x402Signer argument must use the upstream ClientEvmSigner shape: an address plus signTypedData. It is not the SDK's FeeRouterSigner. Browser wallets, KMS-backed signers, and Circle W3S signers can supply that shape without adding provider credentials to the core package.

Toy paywall

The worked example is a local-only Node HTTP app. It creates or reuses one creator split, routes one 1,000-atomic-USDC testnet payment, and reveals the static article only after the payment receipt and Routed event validate.

cd examples/toy-paywall
npm install
npm test
npm start

Set TOY_PAYWALL_PRIVATE_KEY to a dedicated throwaway Arc testnet payer and TOY_PAYWALL_RECIPIENT_ADDRESS to the test recipient before posting to /unlock. Fund the payer with enough Arc testnet USDC for gas and the article price; Arc exposes that balance through the native 18-decimal view and the ARC_USDC 6-decimal contract view. Do not use a production key or expose this toy server publicly.

The server fails closed after any ambiguous settlement error. Check the chain state and restart it before retrying so a mined payment is never submitted twice. See the recorded Arc testnet proof for the fresh-key acceptance run.

Verification

npm test
npm run typecheck
npm run build