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

@px402/client

v0.1.1

Published

px402 client SDK: fetch wrapper, PER deposit/balance/withdraw for agent APIs

Readme

@px402/client

fetch() wrapper that automatically pays on 402, plus PER deposit/withdraw/balance/transfer helpers. The client side of px402.

Install

pnpm add @px402/client @solana/web3.js

Quick start

import { Px402Client } from "@px402/client";
import { Keypair } from "@solana/web3.js";

const wallet = Keypair.fromSecretKey(/* your agent's secret key */);

const client = new Px402Client({
  wallet,
  mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", // Circle devnet USDC (faucet.circle.com)
  cluster: "devnet",
});

// One-line paid fetch. Handles 402 -> pay -> retry transparently.
const res = await client.fetch("https://demo.example.com/api/sentiment?token=SOL");
const data = await res.json();
console.log(res.headers.get("X-Payment-Signature"), data);

Funding the agent

Before the first paid call, fund the agent's PER (private ephemeral rollup) balance:

await client.deposit(1_000_000n); // 1 USDC in micro-USDC

Spent down by 402 calls. Pull funds back to base chain when done:

await client.withdraw(500_000n);

Reading balances

const base = await client.balance();          // base-chain USDC
const per  = await client.privateBalance();   // PER USDC (what gets spent)

Direct transfer

For non-HTTP flows or pre-paying:

await client.transfer({
  destination: "6dRPtBVYiJ6iM7eQqDzCQpBDACBzYoZjGqostfZqrgGU",
  amount: 10_000n,
  clientRefId: "1234567890",
  visibility: "private",
});

Hooks for observability

await client.fetch(url, init, {
  onBeforePay: ({ amount, destination }) => log.info("paying", { amount, destination }),
  onAfterPay:  ({ signature })          => log.info("paid", { signature }),
  onRetry:     ({ attempt, delayMs })   => log.info("retry", { attempt, delayMs }),
});

Errors

| Error | When | |---|---| | PaymentRequiredError | Server returned 402 without payment headers | | InsufficientBalanceError | PER balance < required amount | | MaxRetriesExceededError | Retry budget exhausted before crank verified | | Px402DepositError | deposit() failed; carries phase + partialSignature | | Px402WithdrawError | withdraw() failed; carries phase + partialSignature |

All extend Px402ClientError with a code string.

Safe-retry semantics for deposit / withdraw

deposit() and withdraw() move SPL tokens on the base chain. They are not automatically idempotent — if the call throws mid-flight, you must inspect the failure before retrying or you risk moving funds twice.

On failure, both methods throw a typed error (Px402DepositError / Px402WithdrawError) carrying a phase field:

| phase | What happened | Safe to retry? | |---|---|---| | build | MagicBlock API rejected the request. No tx submitted. | Yes, retry directly. | | submit | RPC rejected sendRawTransaction. Stale blockhash, network, etc. | Yes — rebuild gets a fresh blockhash. | | confirm | Tx was submitted, confirmation failed or timed out. | No — check on-chain first. Tx may still land. |

On confirm failures the error carries partialSignature — the signature the RPC returned before confirmation failed. Look it up on Solscan or via getTransaction before retrying:

import { Px402DepositError } from "@px402/client";

try {
  await client.deposit(1_000_000n);
} catch (err) {
  if (err instanceof Px402DepositError && err.phase === "confirm") {
    const sig = err.partialSignature;
    // Check on-chain: did it land? If yes, no retry needed.
    const status = await connection.getSignatureStatus(sig);
    if (status.value?.confirmationStatus) {
      // Already on-chain — record the sig and move on.
      return sig;
    }
    // Tx never landed (blockhash expired before relay). Safe to rebuild.
  }
  throw err;
}

For build and submit failures a blind retry is fine.

Idempotency for transfer

transfer() uses retries on transient errors (stale blockhash, network blip) inside the 3-attempt loop. Each attempt rebuilds with a fresh blockhash so a race where two transfers land for one call is structurally prevented. Pass an explicit clientRefId when you need server-side reconciliation, since the payment subscriber dedupes by it.

Defaults

| Field | Default | |---|---| | apiUrl | https://payments.magicblock.app | | baseRpcUrl | https://rpc.magicblock.app/devnet | | ephemeralRpcUrl | https://devnet.magicblock.app | | cluster | "devnet" | | visibility | "private" | | fromBalance / toBalance | "base" (only fully-working route on MagicBlock today) |

License

MIT