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

@playmos/sdk

v0.3.3

Published

Playmos SDK — stablecoin payments for games on Base. One SDK for IAP (1%), skill-game prize-pool entries (10%, 60/30/10), and agent economies. USD in, USDC on-chain, no crypto UX for players.

Readme

@playmos/sdk

🧪 Beta — Playmos SDK. Runs on Base Sepolia testnet. The value-movement primitives (escrow / marketplace / transfer) are proven on-chain but not yet production-hardened, and nothing is on mainnet. Build and integrate freely against the sandbox; don't route real user funds yet.

Stablecoin payments for games on Base. One SDK for in-app purchases (1%), skill-game prize-pool entries (10%, 60/30/10), and in-game economies (player · NPC · agent commerce via transfer()). USD in, USDC on-chain — no crypto UX for your players.

Install

npm i @playmos/sdk

Requires Node 18 / 20 / 22 LTS for the monorepo toolchains (Hardhat/service). See repo docs/TOOLCHAIN.md (issue #5).

Browser vs server entry

  • Games (browser / Vite / Phaser): import { Playmos } from "@playmos/sdk" — this entry is browser-safe and does not import Node crypto (issue #9).
  • Your backend (webhook receivers): import { verifyWebhook } from "@playmos/sdk/server" — HMAC verification uses Node crypto and must stay off the client bundle.

The sandbox API also sends CORS headers so browser pay() / enterRound() work without a same-origin proxy (issue #10).

Quickstart — the no-wallet sandbox

The public sandbox key pk_test_playmos_sandbox runs against a real Playmos test service on Base Sepolia. You don't need a wallet: on a pk_test key with no wallet configured, the SDK routes to a server-settle path where Playmos signs and submits the on-chain transaction for you. You still get a real, confirmed txHash — just signed by the service. No wallet, no gas, no crypto.

The key comes wired to two demo games: game_sandbox_iap (IAP) and game_sandbox_skill (prize pool).

In-app purchase — pay()

import { Playmos } from "@playmos/sdk";

// Public sandbox key — client-safe, like Stripe's pk_test_.
// No wallet needed: Playmos server-settles the test payment for you.
const playmos = new Playmos({ apiKey: "pk_test_playmos_sandbox" });

const payment = await playmos.pay({
  gameId: "game_sandbox_iap", // public sandbox IAP game — include it (this key spans IAP + skill)
  amount: "0.99",             // USD string — sandbox server-settle cap $1.00/request
  sku: "gems_500",            // your product id
  playerId: "player_abc",     // your opaque user id
});

payment.id;     // "pay_…"     — real, server-issued (ULID)
payment.status; // "confirmed" — real, on Base Sepolia
payment.txHash; // 0x…         — open on sepolia.basescan.org/tx/{txHash}

Verify before you grant

Grant items on a verified confirmation — never on the client pay() return alone.

import { Playmos } from "@playmos/sdk";

// Your server — the secret key lives here, never in a client bundle.
const server = new Playmos({ apiKey: process.env.PLAYMOS_SECRET! }); // sk_test_…

const result = await server.verify(payment.id); // on-chain read — idempotent, safe to retry

// Terminal success is exactly "confirmed" (IAP + entries) — not "settled" / "succeeded".
if (result.status === "confirmed") {
  grantItem(result.playerId, result.sku);
}

Skill-game entry — enterRound()

const entry = await playmos.enterRound({
  gameId: "game_sandbox_skill",
  roundId: "5m-5944009",
  amount: "1.00",         // grows this round's pool
  playerId: "player_abc",
});
// entry.status === "confirmed"; entry.split is the on-chain 60/30/10 breakdown

Move USDC between wallets — transfer()

The base value-movement primitive: move USDC from one wallet to another with a configurable per-call fee. The game logic is the authority — you already decided the move is valid — so this is a direct, unconditional push (reach for escrow/marketplace when a trust boundary needs fair exchange). Settled through the on-chain PlaymosTransfer contract; the split is verifiable on BaseScan.

// NPC→NPC with a 5% fee (Playmos Town P2P). Requires sk_test_ + agents.createWallet first.
// Full walkthrough: see "In-game economies — the Playmos Town golden path" below.
// Confirm: status === "settled" && txHash, or await playmos.transfers.wait(id) if settling.
const t = await playmos.transfer({
  // `from` is OPTIONAL in the sandbox — omit it and the service uses its server-held signer wallet
  // (Phase 1a's only payer), so you don't need to know that address to run this example.
  to:     "0x…blacksmith",
  amount: "0.80",         // USD; USDC settles on-chain. Stay UNDER the $1.00 sandbox cap (see below).
  feeBps: 500,            // 5% — 0–10000
  feeSink: "0x…treasury", // required when feeBps > 0
  memo:   "ore",          // optional, echoed on the receipt
});
t.status;          // "settled" — real, on Base Sepolia
t.txHash;          // 0x… open on sepolia.basescan.org
t.net;             // "0.76"  (payee)
t.fee;             // "0.04"  (feeSink)
t.idempotentReplay; // false on the first settle

feeBps: 0 is an untaxed reward/faucet transfer (no fee taken; feeSink not needed) — e.g. a monster-bounty payout from a world wallet to a fighter. Set feeBps: 10000 for a 100% first-party sale (all of it to feeSink).

| Field | Required | Notes | |---|---|---| | from | no | Payer wallet — a 0x address. Omit in the sandbox and the service defaults it to its server-held signer wallet (Phase 1a's only payer). If supplied, it must be that wallet. | | to | yes | Payee wallet — a 0x address. | | amount | yes | USD decimal string ("0.80"); > 0, ≤ 2 dp. Sandbox cap: ≤ $1.00 per transfer (see limits below). | | feeBps | no (default 0) | Per-call fee in bps, 0–10000. 0 = untaxed reward/faucet. | | feeSink | when feeBps > 0 | Fee destination 0x address. | | memo | no | Optional note, echoed on the receipt. | | idempotencyKey | no | Pass the same key on a retry to guarantee at most one on-chain transfer (see below). |

Sandbox limits (enforced before any signing). The public sandbox signs from a shared funded testnet wallet, so the transfer path is guarded: $1.00 max per transfer (a larger amount is a 400), 5 requests/minute per IP and per key (429), and a $20/day budget across all sandbox transfers (429). These are testnet abuse controls — not product limits.

Retries are safe (idempotent, no double-spend). Every transfer is anchored on a requirement id derived from idempotencyKey. The service durably reserves the transfer before broadcasting, so a retry with the same key — or two concurrent calls with the same key — broadcast at most one transaction; the loser returns the original result with idempotentReplay: true. Preview the split with no network via previewTransferSplit(amount, feeBps). Reusing an id with different terms (payTo/amount/fee/sink/from) is rejected, not silently replayed.

REST (no SDK). The SDK's transfer() is a thin wrapper over POST /v1/transfers:

curl -X POST https://<sandbox-host>/v1/transfers \
  -H "Authorization: Bearer pk_test_playmos_sandbox" \
  -H "Content-Type: application/json" \
  -d '{ "to": "0x…blacksmith", "amount": "0.80", "feeBps": 500, "feeSink": "0x…treasury", "memo": "ore", "idempotencyKey": "ore-trade-1" }'

Fields match the table above (from optional → defaults to the signer; feeBps/feeSink as documented). Poll GET /v1/transfers/:id to reconcile a transfer's status against the chain (it recovers a transfer left mid-flight by a crash).

Sandbox only (Phase 1a): transfer settles server-held NPC/agent wallets on Base Sepolia. The player-signed (EIP-3009) non-custodial path is Phase 1b.

In-game economies — the Playmos Town golden path

In-game economies = commerce between players, NPCs, and agents (AI or scripted). The money primitive is transfer() (entity-agnostic); NPC/agent wallets use agents.* (API names unchanged). This is the loop the Playmos Town example runs end to end: give each NPC a wallet, fund it, then move value with a per-call fee — P2P (5%), a monster bounty (0%), a shop sale (100%). Proven on Base Sepolia (transfer layer) — not a claim that a full MMO product is shipped.

Agents need a SECRET test key — sk_test_, not pk_test_. Assigning NPC wallets and transferring from an NPC are privileged, server-side actions, so they require an sk_test_ key kept on your backend — not the public pk_test_playmos_sandbox. pk_test_ runs the no-wallet pay() / enterRound() demos above; sk_test_ unlocks agents.* and NPC-funded transfer.

import { Playmos } from "@playmos/sdk";

// Server-side only — an sk_test_ secret key, never shipped to the browser.
const playmos = new Playmos({ apiKey: process.env.PLAYMOS_SK_TEST });
const feeSink = process.env.PLAYMOS_FEE_SINK; // 0x treasury / shop wallet

// 1) Assign a wallet to an NPC your game already owns (idempotent — safe to re-call).
const miner = await playmos.agents.createWallet({ agentId: "npc_pico_miner" });
miner.address; // 0x… — the NPC's USDC wallet on Base Sepolia

// 2) Fund it from your treasury (sandbox faucet, per-NPC lifetime cap).
const funded = await playmos.agents.fund({ agentId: "npc_pico_miner", amount: "0.20" });
funded.funding.status; // "settled" | "settling"

// 3a) P2P trade — 5% fee to the treasury (feeBps: 500). NPC `from` ⇒ gasless (NPC signs, Playmos relays + pays gas).
const p2p = await playmos.transfer({
  from:    "npc_pico_miner",
  to:      "npc_bruna_blacksmith",
  amount:  "0.10",
  feeBps:  500,       // 5% P2P
  feeSink,            // required when feeBps > 0
});

// Confirm with the new ergonomics (#54): wait for a terminal state — no hand-rolled poll loop.
const settled = await playmos.transfers.wait(p2p.id);
settled.status; // "settled" — txHash on sepolia.basescan.org

// 3b) Monster-bounty payout — 0% untaxed reward from the treasury signer (omit `from`, no feeSink).
await playmos.transfer({ to: "npc_wynn_ranger", amount: "0.05", feeBps: 0 });

// 3c) First-party shop sale — 100% to the shop (feeBps: 10000).
await playmos.transfer({ from: "npc_kane_knight", to: feeSink, amount: "0.05", feeBps: 10_000, feeSink });

Prefer one call? Confirm inline. Pass { confirm: true } and transfer blocks until the transfer is terminal before it resolves — identical to calling transfers.wait(id) yourself (#54):

const t = await playmos.transfer(
  { from: "npc_pico_miner", to: "npc_bruna_blacksmith", amount: "0.10", feeBps: 500, feeSink },
  { confirm: true },
);
t.status; // "settled" (or "failed") — already terminal, no follow-up call

The sandbox throttles (5 req/min per key), so back-to-back legs can 429. Retries are automatic — the SDK backs off and retries a 429 up to twice by default, and every write carries an idempotency key so a retry never double-broadcasts (tune with new Playmos({ apiKey, retry: { maxRetries } }), or retry: false to opt out).

The full 5-NPC walkthrough — create → fund → P2P / shop / bounty, each confirmed on-chain — is docs/examples/playmos-town-sdk.mjs:

PLAYMOS_SK_TEST=sk_test_… PLAYMOS_FEE_SINK=0x… node docs/examples/playmos-town-sdk.mjs

Mock mode — offline, deterministic

For CI and wiring checks, mock: true returns instant, deterministic results with no network and no chain. Results carry mock: true and use the real status union, so your handling code sees the exact production shape.

const playmos = new Playmos({ apiKey: "pk_test_playmos_sandbox", mock: true });
const payment = await playmos.pay({
  gameId: "game_sandbox_iap", sku: "gems_100", amount: "0.99", playerId: "player_abc",
});
// instant — payment.status === "confirmed", payment.mock === true

Mock mode is not test mode: mock is fabricated and offline; the sandbox is real and settles on Base Sepolia.

Production — real player wallets

Outside the sandbox (a pk_live key), or whenever you want the player to sign their own on-chain transaction, configure a wallet connector. With a wallet present the SDK uses the client-signed path instead of server-settle.

const playmos = new Playmos({
  apiKey: "pk_live_…",
  wallet: { connector: "base-account" }, // or "injected" in a wallet browser
});

Promote by swapping the key — the same code that passed in the sandbox works live.

Value movement — escrow, marketplace, transfer (0.2.0, testnet)

0.2.0 adds non-custodial USDC value-movement primitives on top of the settlement core:

  • playmos.transfer(...) — a direct USDC transfer.
  • playmos.escrow.hold / release / refund / get — hold funds against a deal, then pay the payee (fee skimmed at release) or refund the payer (100%). Every term is frozen on-chain at hold.
  • playmos.marketplace.list / buy / confirm / refund / cancel / get — a listing packaged as an escrow deal (buy = hold, confirm = release, timeout/dispute = refund), including buy({ deliver: true }) to hold+release in one call.

Status quo (2026-07): testnet-sandbox only. These paths are proven on Base Sepolia (independently reconciled on-chain) but are not marketed as production-hardened, and are not on mainnet. For money preflights, point the service at a dedicated RPC (Alchemy/QuickNode) — do not rely on a load-balanced public endpoint for read-your-write consistency.

Resolve status-code contract

The escrow/marketplace resolve endpoints (release/refund/confirm) return a deterministic contract — handle it explicitly:

| Code | Meaning | Client action | |------|---------|---------------| | 404 | Unknown escrow / listing | Terminal — the id does not exist | | 409 | Not resolvable (already released/refunded, or genuinely never held) | Terminal — do not retry | | 503 | Transient (RPC read-lag, or a crash-orphaned hold being reconciled) | Retry with backoff | | 400 | Malformed input | Terminal — fix the request |

On a 503, retry the release/confirm — it means the on-chain state just hasn't propagated yet, not that anything failed.

Docs

Full documentation — skill games, webhooks, gas, payouts, in-game economies, and the REST API — at playmos.io.