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

xrpl-gaming-core

v0.2.0

Published

Core of the Kinesis XRPL DynamicNFT Gaming SDK. Provides the XRPLGamingSDK class plus pluggable IDBAdapter and IIPFSAdapter interfaces for minting, updating, transferring, burning and querying mutable game NFTs on the XRP Ledger.

Downloads

251

Readme

xrpl-gaming-core

The core of the XRPL Gaming SDK. Provides the XRPLGamingSDK class and the pluggable adapter interfaces (IDBAdapter, IIPFSAdapter) that power it.

Install

pnpm add xrpl-gaming-core xrpl-gaming-ipfs-pinata xrpl-gaming-db-postgres

Self-hosted usage

You bring your own XRPL wallet seed, XRPL node URL, database, and Pinata account. The SDK glues them together.

import { XRPLGamingSDK } from "xrpl-gaming-core";
import { PinataAdapter } from "xrpl-gaming-ipfs-pinata";
import { PostgresAdapter } from "xrpl-gaming-db-postgres";

const sdk = new XRPLGamingSDK({
  xrpl: {
    nodeUrl: "wss://xrplcluster.com",
    issuerWallet: { seed: process.env.XRPL_ISSUER_SEED! },
  },
  db: new PostgresAdapter({ connectionString: process.env.DATABASE_URL! }),
  ipfs: new PinataAdapter({ jwt: process.env.PINATA_JWT! }),
});

await sdk.init();

// Mint a DynamicNFT for a player
const { record, txHash, offerId } = await sdk.nft.mint({
  metadata: { name: "Wandering Knight", class: "warrior", level: 1, power: 50 },
  playerId: "player-123",
  collection: "characters",
  destination: "rPlayerWalletAddress...", // optional sell offer
});

// Later, level up — updates the on-chain URI via NFTokenModify
await sdk.nft.update(record.tokenId, {
  metadata: { name: "Wandering Knight", class: "warrior", level: 2, power: 95 },
});

// Read current state from your DB
const current = await sdk.nft.get(record.tokenId);

// Transfer to another wallet (creates a sell offer — destination must accept)
const { offerId } = await sdk.nft.transfer(record.tokenId, {
  destination: "rNewOwner...",
});
// `record.ownerAddress` does NOT change yet — XRPL ownership only flips
// when the destination calls NFTokenAcceptOffer. The DB row gets
// `pendingOfferId` and `pendingDestination` annotations so you can show
// "transfer pending" in your UI.
//
// Once you've confirmed the offer was accepted (e.g. by polling
// `account_nfts` or subscribing to ledger events), reconcile the DB:
await sdk.nft.markTransferComplete(record.tokenId, "rNewOwner...");

// Burn (issuer-owned only)
await sdk.nft.burn(record.tokenId);

await sdk.close();

Managed usage (preview)

The managed tier is not yet available. Constructing the SDK with managedApiKey throws a ManagedNotAvailableError with instructions to use the self-hosted path or get in touch.

import { XRPLGamingSDK } from "xrpl-gaming-core";

// Will throw ManagedNotAvailableError until the hosted backend is online.
const sdk = new XRPLGamingSDK({ managedApiKey: "xg_live_xxx" });

DynamicNFT details

Mint flags

mint() exposes the four NFTokenMint flags that matter for game NFTs. All are optional booleans on MintParams:

| MintParams field | XRPL flag | Default | Effect | | ------------------ | ---------------- | ------- | ----------------------------------------------------------------------- | | transferable | tfTransferable | true | NFT can be traded between non-issuer wallets. | | mutable | tfMutable | true | Issuer can later call update() (XLS-46). false freezes the NFT. | | burnable | tfBurnable | false | Issuer can burn the NFT even after a player owns it. | | onlyXRP | tfOnlyXRP | false | Sell/buy offers for the NFT must be denominated in XRP, not IOUs. |

Updating a player-held NFT

update() issues an NFTokenModify transaction that replaces the URI with a new IPFS pointer. XLS-46 requires this transaction to carry an Owner field whenever the issuer is acting on a token they no longer hold (typical once a player has accepted the sell offer). The SDK attaches Owner automatically by resolving the current holder from one of two sources, controlled by UpdateParams.ownerSource:

  • "onchain" (default) — the SDK queries the XRPL via the Clio-only nft_info RPC. Always correct, costs one extra request per update. Requires the SDK's nodeUrl to point at a Clio server. If you self-host a rippled-only node and the call fails, the SDK throws an XrplGamingError that explicitly tells you to either run Clio or switch to "db".
  • "db" — the SDK trusts ownerAddress on its DB record. Skips the network round-trip, but only safe if you reliably call sdk.nft.markTransferComplete(tokenId, newOwner) after every accepted sell offer. Otherwise the modify will be rejected with tecNO_ENTRY / tecNO_PERMISSION.
// Default — works against any Clio-fronted XRPL node:
await sdk.nft.update(tokenId, { metadata: { ...newAttrs } });

// Opt out of the network call — your app guarantees DB freshness:
await sdk.nft.update(tokenId, { metadata: { ...newAttrs }, ownerSource: "db" });

Mint with an inline sell offer (XLS-46)

When you pass destination to mint(), the SDK does not issue a follow-up NFTokenCreateOffer. Instead it packs the XLS-46 inline sell-offer fields (Amount, Destination, Expiration) onto the same NFTokenMint transaction, so mint and offer settle in a single ledger close.

await sdk.nft.mint({
  metadata,
  destination: "rPlayerWalletAddress...",
  amount: "0",                                              // drops; default "0" = free claim
  expiration: new Date(Date.now() + 24 * 60 * 60 * 1000),   // optional Date OR Ripple-time number
});

amount is XRP drops as a string ("0" = free claim, "1000000" = 1 XRP, etc.). expiration accepts either a JS Date or a Ripple-time number (seconds since 2000-01-01 UTC); the SDK calls toRippleTime() for you. Both fields are only meaningful when destination is set — passing them without a destination throws an XrplGamingError, since they describe the inline sell offer rather than the NFT itself. The returned result.offerId is pulled out of the same transaction's metadata, so you get both the new tokenId and the new offer id from a single round-trip — one signed transaction, one fee, and no possibility of an NFT existing on-ledger without its corresponding offer.

Transfer

Transfers use NFTokenCreateOffer (sell offer at 0 drops by default) targeted to the destination wallet, which must accept the offer to complete the transfer. The SDK does not poll the ledger; reconcile via markTransferComplete() once you observe acceptance.

IPFS adapter capabilities

IIPFSAdapter exposes two methods:

  • uploadJson(metadata, opts?) — pin a JSON metadata document. Used by nft.mint / nft.update internally; you rarely call it directly.

  • uploadFile(data, opts?) — pin a binary file (image, audio, video). Use it to pin an NFT's image first, embed the returned ipfs:// URI in the metadata, then mint:

    import { promises as fs } from "node:fs";
    
    const bytes = await fs.readFile("./hero.png");
    const image = await sdk.ipfs.uploadFile(bytes, {
      name: "hero.png",
      contentType: "image/png",
    });
    
    await sdk.nft.mint({
      metadata: { name: "Hero", image: image.uri, attributes: { level: 1 } },
      playerId: "player-123",
    });

    data accepts Uint8Array, ArrayBuffer, Blob, or a Node Buffer. Pass contentType so the asset is served with the right MIME type. Both bundled adapters (xrpl-gaming-ipfs-pinata and any custom one you implement) support both methods.

Operation ordering & consistency model

mint() and update() perform their XRPL transaction first and then write/patch the DB record:

  1. ipfs.uploadJson(metadata) — pin metadata
  2. client.submitAndWait(NFTokenMint | NFTokenModify) — settle on-chain
  3. db.saveNft(...) / db.updateNft(...) — persist the authoritative on-chain result (token id, URI, timestamps)

This guarantees we never advertise a DB row that points at a non-existent or out-of-date ledger object. The trade-off is that if the DB write fails after a successful XRPL settlement, you can rebuild the row by reading account_nfts for the issuer wallet and replaying it through db.saveNft. For mint({ destination }) the mint and the inline sell offer settle in one transaction (XLS-46), so the SDK saves a single DB row with pendingOfferId and pendingDestination already populated — there is no intermediate state in which an NFT can exist on-ledger without its offer.

Transfer is offer-based — you must reconcile

sdk.nft.transfer() does not flip ownership immediately. It issues an NFTokenCreateOffer (sell offer) targeted at the destination wallet and returns the offerId. Until the destination wallet calls NFTokenAcceptOffer on the XRPL, the issuer still owns the token. The DB row reflects this with two columns:

  • pendingOfferId — the open sell offer id
  • pendingDestination — the intended recipient

Your application is responsible for detecting acceptance (poll account_nfts for the destination, or subscribe to ledger events) and then calling sdk.nft.markTransferComplete(tokenId, newOwnerAddress) to clear the pending fields and update ownerAddress. Future managed-tier and server packages will provide a watcher that automates this.

Build your own adapter

Implement IDBAdapter or IIPFSAdapter for any backend you like. See xrpl-gaming-db-mongodb and xrpl-gaming-ipfs-pinata for reference implementations.