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

@pixels-online/pixels-client-js-sdk

v3.21.0

Published

Pixels Client JS SDK

Readme

Pixels BuildOn Client JS SDK

A powerful TypeScript SDK for integrating Pixels BuildOn offerwall functionality into web applications and games. This SDK provides real-time offer management, player progression tracking, and reward handling capabilities.

🚀 Features

  • Real-time Offer Management - Live updates via Server-Sent Events (SSE)
  • Player Progression Tracking - Monitor player stats, achievements, and conditions
  • Reward System Integration - Handle various reward types (coins, items, exp, etc.)
  • Event-Driven Architecture - React to offer events and player actions
  • TypeScript Support - Full type safety and IntelliSense support
  • Flexible Configuration - Customizable asset resolution and hooks
  • Multi-Environment Support - Local, Test, staging, and production environments
  • Auto-Reconnection - Robust connection management with retry logic

📦 Installation

npm install @pixels-online/pixels-client-js-sdk

🔧 Quick Start

1. Basic Setup

import { OfferwallClient } from '@pixels-online/pixels-client-js-sdk';

const client = new OfferwallClient({
  env: 'test', // or 'live' for production
  tokenProvider: async () => {
    // Fetch JWT token from your server. We recommend using our server-side SDK on your server for JWT creation
    const response = await fetch('/api/auth/pixels-token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
    });
    const data = await response.json();
    return data.token;
  },
  assetResolver: (_, id: string) => {
    return {
      name: exampleGameLib[id]?.name || 'Unknown2',
      image: exampleGameLib[id]?.image,
    };
  },
  fallbackRewardImage: 'https://example.com/default-reward.png',
  hooks: {
    onOfferSurfaced: (offer) => {
      var random = Math.random() < 0.5;
      return random; // 50% chance to show the offer
    },
  },
  autoConnect: true,
});

2. Initialize and Connect

// Initialize the client
await client.initialize();

// Listen for offers
client.on('offersUpdated', (offers) => {
  console.log('Available offers:', offers);
  displayOffersInUI(offers);
});

// Listen for player updates
client.on('playerUpdated', (player) => {
  console.log('Player snapshot updated:', player.snapshot);
  console.log('Player data updated:', player.data);
  updatePlayerStatsUI(player);
});

3. Render Offer UI/UX

Each offer object returned by the SDK includes helper methods for rendering offer state in your UI.

Checking if an Offer is Claimable

Use offer.canClaim() to determine whether the player has met all requirements and can claim the offer:

const claimable = offer.canClaim?.();
renderClaimButton(offer, { disabled: !claimable });

Displaying Offer Progress

Use offer.getProgressPercent() to get the player's overall completion percentage (0–100):

const progress = offer.getProgressPercent?.() ?? 0;

// Example: render a progress bar
renderProgressBar(offer, { percent: progress });

Rendering Completion Conditions

Use offer.getCompletionConditions() to get a list of individual conditions, each with isMet (boolean) and text (human-readable description):

const conditions = offer.getCompletionConditions?.() ?? [];

for (const condition of conditions) {
  renderCondition({
    text: condition.text,
    completed: condition.isMet,
  });
}

// Example React rendering
function OfferConditions({ offer }) {
  const conditions = offer.getCompletionConditions?.() ?? [];
  return (
    <ul>
      {conditions.map((c, i) => (
        <li key={i} style={{ color: c.isMet ? 'green' : 'gray' }}>
          {c.isMet ? '✓' : '○'} {c.text}
        </li>
      ))}
    </ul>
  );
}

Rendering Team Offer Sibling Progress

For team offers (offers where all team members must complete for anyone to claim), use offer.getSiblingProgress() to display each sibling's progress:

const isTeamOffer = offer.labels?.includes('team_offer');

if (isTeamOffer) {
  const siblingProgress = offer.getSiblingProgress?.() ?? [];

  for (const sibling of siblingProgress) {
    renderSiblingRow({
      playerId: sibling.playerId,
      percentCompleted: sibling.percentCompleted,
      isComplete: sibling.isComplete,
    });
  }
}

// Example React rendering
function TeamProgress({ offer }) {
  const siblings = offer.getSiblingProgress?.() ?? [];
  if (!siblings.length) return null;

  return (
    <div>
      <h4>Team Progress</h4>
      {siblings.map((s) => (
        <div key={s.playerId}>
          <span>{s.playerId}</span>
          <span>{s.percentCompleted}%</span>
          <span>{s.isComplete ? '✓ Complete' : 'In Progress'}</span>
        </div>
      ))}
    </div>
  );
}

3. Claim Offers

// Claim an offer
try {
  const result = await client.claimOffer(offerId);
  console.log('Offer claimed successfully:', result);
} catch (error) {
  console.error('Failed to claim offer:', error);
}

🛒 Shop Purchases

Let players buy your game's shop items without leaving your app. Requires in-app shop purchases to be enabled for your org by Stacked (allowInAppShopPurchases) — contact Stacked to get it turned on.

Two things to know up front:

  • Game-scoped balance. Purchases spend the player's Stacked balance earned in your game only — balance from other games can never be spent through the client SDK. The player's in-game balance is already available via getStacked() (stacked.currencies).
  • Linked account required. The player must have linked their Stacked account (no-stacked-account otherwise — send them through the stacked-link flow you already use).

Rendering the shop

getShopItems() returns everything a shop screen needs — only live, in-window items are returned, and every price/limit shown here is re-checked server-side at purchase time:

const items = await client.getShopItems();
const balance = client.getStacked()?.currencies?.['cur_your_currency']?.balance ?? 0;

for (const item of items) {
  const price = item.prices?.['cur_your_currency']; // may be USD-converted server-side
  renderShopEntry({
    name: item.name,           // + item.description, item.image, item.category
    price,
    soldOut: item.supply != null && (item.sold ?? 0) >= item.supply,
    limitReached: item.maxPerPlayer != null && item.purchaseCount >= item.maxPerPlayer,
    canAfford: price != null && balance >= price,
    endsAt: item.availableUntil, // show a countdown if you like
  });
}

Buying with balance

// The price is resolved server-side from your catalog — the client never
// sends an amount.
import { OfferwallApiError } from '@pixels-online/pixels-client-js-sdk';

try {
  const { purchaseId, rewards, spent } = await client.purchaseShopItem(
    item._id,
    'cur_your_currency',
  );
  // Show pending/success UI. The item itself is delivered to YOUR BACKEND
  // via the `user.gain_rewards` webhook (source: 'shop') — grant it there,
  // deduped on the webhook's `instanceId` (=== purchaseId).
} catch (error) {
  if (error instanceof OfferwallApiError) {
    switch (error.code) {
      case 'insufficient-balance':      // balance > 0 but below the price
      case 'no-coin-balance':           // zero/no balance in the spend currency
      case 'no-player-data':            // player has never earned in this game
        // all three mean "not enough in-game balance" — send them to earn more
        break;
      case 'sold-out':                  // supply exhausted
      case 'max-per-player-reached':    // per-player cap hit
      case 'no-stacked-account':        // player hasn't linked Stacked
      case 'purchase-in-progress':      // a purchase is already in flight
      case 'currency-not-priced':       // item has no price for that currencyId
      case 'in-app-shop-purchases-not-enabled': // org flag not granted
        // handle each case in your UI
        break;
    }
  }
}

⚠️ Never blind-retry purchaseShopItem after a network failure. A timed-out request may still have succeeded server-side. Reconcile via your webhook (instanceId) or getShopItems() purchaseCount instead.

Delivery is the same webhook channel used for offer claims: your backend receives user.gain_rewards with source: 'shop', campaignId (the shop item id), instanceId (the purchase id — your idempotency key), and the item's rewards.

The same catalog items can also be paid for with crypto — see the next section. A common shop UI offers both: "Buy with balance" when canAfford, and "Pay with crypto" as the alternative.

💳 Crypto Checkout (pay for shop items with crypto)

Players can also pay for a shop item on-the-fly with crypto — an ERC20 transfer from any wallet, no Stacked balance needed. The flow is: the server quotes the catalog price and a deposit wallet → the player's wallet signs and broadcasts transfer(operator, amount) → the SDK has the server verify the mined transaction and execute delivery. Delivery lands on the same user.gain_rewards webhook as every other shop purchase (dedupe on instanceId) — your backend does not change at all.

Requirements: the same allowInAppShopPurchases org flag, a linked Stacked account, and crypto exchange enabled by Stacked (deposit-disabled otherwise). Any wallet may pay — nothing is credited to the payer's balance; the funds buy the item for the signed-in player.

The SDK bundles no wallet libraries. Anything that satisfies the minimal EIP-1193 interface (request({ method, params })) plugs in, and there are three ways to integrate, from zero-effort to full control:

A. No wallet stack — let the SDK find a wallet

Discovery uses EIP-6963 announcements (Ronin, MetaMask, Coinbase, OKX…) plus legacy injection points like window.ronin.provider.

// Optional: render your own wallet picker (EIP-6963 gives name + icon)
const wallets = await client.getAvailableWallets();

try {
  const result = await client.purchaseShopItemWithCrypto(item._id, {
    currencyId: 'cur_pixel',
    network: 'ronin',
    provider: wallets[0]?.provider, // omit to auto-pick the first found
    onStatus: (status) => {
      // 'connecting' | 'approving' | 'switching-chain' | 'awaiting-signature'
      // | 'broadcast' | 'verifying' | 'executing' | 'delivered' | 'failed'
      updateCheckoutUi(status);
    },
  });
  // result: { success: true, status: 'approved', txHash, shopItemId }
} catch (error) {
  if (error instanceof CryptoPurchaseError) {
    // wallet-side, wallet-independent codes:
    // 'no-wallet-available' | 'user-rejected' | 'wrong-chain' | 'wallet-error'
  } else if (error instanceof OfferwallApiError) {
    // server-side codes: 'sold-out', 'insufficient-balance' (wallet balance
    // too low), 'shop-item-not-purchasable-with-crypto', 'deposit-disabled', …
  }
}

B. You already have a wallet stack (reown/wagmi/Tanto/Ronin SDK)

Give the SDK a lazy provider getter — resolved at purchase time, so it survives reconnects. Every major stack exposes an EIP-1193 provider:

const client = new OfferwallClient({
  // ...
  // reown AppKit:      () => modal.getWalletProvider()
  // wagmi connector:   () => connector.getProvider()
  // Ronin wallet SDK:  () => walletSdk.getProvider()
  // Tanto Connect:     () => connector.getProvider()
  getWalletProvider: () => modal.getWalletProvider(),
});

await client.purchaseShopItemWithCrypto(item._id, {
  currencyId: 'cur_pixel',
  network: 'ronin',
});

C. Full control — you broadcast, the SDK does the rest

For AA/smart wallets, native bridges, or custom signing UX. The SDK's only wallet-facing need is a transaction hash:

// 1. Server-priced quote + prepared ERC20 transfer (no wallet involved)
const quote = await client.prepareCryptoPurchase(item._id, {
  currencyId: 'cur_pixel',
  network: 'ronin',
  walletAddress: myAddress,
});
// quote.tx = { to: <token contract>, data: <transfer calldata>, value: '0x0' }
// quote.chainId = the chain the transfer MUST be broadcast on

// 2. Broadcast however you like — on quote.chainId!
const txHash = await mySmartWallet.sendTransaction({ ...quote.tx, chainId: quote.chainId });

// 3. Hand back the hash — the SDK verifies + executes delivery
const result = await client.submitCryptoPurchase(quote, txHash);

Crash recovery — call this on startup

Funds leave the wallet at broadcast, but the server only learns about the transaction at verify. The SDK persists every broadcast hash to localStorage and can finish an interrupted purchase later:

// after auth, once per app start
client.resumePendingCryptoPurchases().catch(console.error);

⚠️ Never re-broadcast a payment after a failure. A broadcast-or-later failure means the money is on-chain; recovery is always by transaction hash (submitCryptoPurchase / resumePendingCryptoPurchases), which is replay-safe — delivery fires exactly once server-side.

Result semantics: success: true means the server accepted the funds and dispatched delivery — treat your user.gain_rewards webhook (deduped on instanceId) as the source of truth for the item being granted. A result of { success: false, status: 'delivery-failed' } means the payment was accepted but the item could not be delivered (e.g. it sold out between quote and finalize) — surface a "contact support" state with the txHash. status: 'approving' is non-terminal: the purchase is still in flight and a later resumePendingCryptoPurchases() will finish it. Pending records are strictly scoped to the player who made them — another account on the same browser can never resume (or receive) someone else's purchase.

Progress is also emitted as an event, mirroring onStatus:

client.events.on(OfferEvent.CRYPTO_PURCHASE_STATUS, ({ status, shopItemId, txHash }) => {
  // drive a global checkout indicator
});

🎁 Gift Cards (Bitrefill)

Let a player cash out the balance they earned in your game as a real gift card, emailed to them by Bitrefill.

Funding is scoped to your game. The card is paid for with the player's balance in this game only — never the cross-game aggregate on their Stacked account, which stays behind the Stacked app's own consent. Likewise, getGiftCardHistory() returns only the redemptions your game funded.

Availability is double-gated: your org needs allowInAppShopPurchases (the same grant the shop surface uses) and the platform-wide bitrefill feature flag must be on. getGiftCardConfig() collapses both into one answer.

1. Decide whether to show the section

const config = await client.getGiftCardConfig();
if (!config) return; // gift cards are off platform-wide — render nothing

// config.currencies[].balance is the player's balance IN THIS GAME
const pixel = config.currencies.find((c) => c.id === 'cur_pixel');
if (!pixel || pixel.balance <= 0) return;

getGiftCardConfig() rejects (rather than returning null) when the player specifically can't redeem — in-app-shop-purchases-not-enabled, no-stacked-account, account-restricted — so you find out before showing a CTA that would fail.

2. Validate the amount before quoting

min / max / step constrain the net card value — what the player receives after the surcharge — not the gross their balance converts to. This is the single easiest thing to get wrong, so the SDK ships the same arithmetic the server runs:

import {
  giftCardValueIssue,
  giftCardGrossForNet,
  snapGiftCardValue,
} from '@pixels-online/pixels-client-js-sdk';

// "I want a $25 card" → is that purchasable?
const issue = giftCardValueIssue(config, 25);
// null | 'usd-amount-too-small' | 'usd-amount-too-large' | 'usd-amount-not-on-step'

// Snap a slider to the nearest valid amount (always rounds DOWN)
const valid = snapGiftCardValue(config, 27); // e.g. 20 on a $10-step product

// USD the player must spend to land on a $25 card
const grossUsd = giftCardGrossForNet(config, 25);

3. Quote, then purchase

const quote = await client.quoteGiftCard({
  sourceCurrencyId: 'cur_pixel',
  amount: 5000, // in-game currency to spend
});

// quote.targetAmountAfterFee is the card value the player receives.
// quote.priceUsd is the gross; quote.totalFee is the surcharge between them.
if (!quote.canWithdraw) return; // withdrawal limits currently block this

const result = await client.purchaseGiftCard({
  sourceCurrencyId: 'cur_pixel',
  amount: 5000,
  recipientEmail: '[email protected]', // REQUIRED — Bitrefill mails the code here
  recipientName: 'Player One', // optional
});
// { pendingWithdrawId, status: 'waiting', targetAmountAfterFee, ... }

quoteGiftCard() persists nothing and moves no balance, so it is safe to call on every keystroke (debounced). Re-quote before purchasing if the player idles — the FX rate behind exchangeRate moves and only the purchase is authoritative.

purchaseGiftCard() is not idempotent — each call is a separate purchase. Disable the CTA while one is in flight. A rejected redemption refunds the balance automatically; a failure to create one never debits.

4. Show redemption status

The purchase returns immediately with status: 'waiting'; Bitrefill is charged asynchronously. Poll history for the outcome:

const { payouts } = await client.getGiftCardHistory({ page: 1, limit: 25 });

for (const p of payouts) {
  if (p.status === 'complete') {
    // code/link are only present once complete
    console.log(p.bitrefill?.code, p.bitrefill?.link);
  } else if (p.status === 'rejected') {
    console.log('refunded:', p.error);
  } else {
    // 'waiting' / 'approved' / 'processing' — p.autoApprovalEta is a hint
    console.log('pending', p.bitrefill?.hasCode);
  }
}

The server withholds code and link until the payout is complete; bitrefill.hasCode tells you a code exists without exposing it.

📊 Player Data Structure

The SDK provides player information through the IClientPlayer interface:

interface IClientPlayer {
  snapshot: IPlayerSnapshot; // Core player data (levels, currencies, achievements, etc.)
  data?: IPlayerData | null; // Additional game-specific data
}

Accessing Player Information

// Get the current player
const player = client.getPlayer();

if (player) {
  // Access core player data
  console.log('Player ID:', player.snapshot.playerId);
  console.log('Player level:', player.snapshot.levels?.combat?.level);
  console.log('Currency balance:', player.snapshot.currencies?.gold?.balance);

  // Access additional player data (if available)
  if (player.data) {
    console.log('Additional currencies:', player.data.currencies);
  }
}

🎯 Configuration Options

OfferwallConfig

interface OfferwallConfig {
  /** Environment: 'test' | 'live' | custom endpoint */
  env: 'test' | 'live' | (string & {});

  /** Auto-connect on initialization (default: false) */
  autoConnect?: boolean;

  /** Enable auto-reconnection (default: true) */
  reconnect?: boolean;

  /** Reconnection delay in ms (default: 1000) */
  reconnectDelay?: number;

  /** Max reconnection attempts (default: 5) */
  maxReconnectAttempts?: number;

  /** Enable debug logging (default: false) */
  debug?: boolean;

  /** Event hooks for custom logic */
  hooks?: Partial<OfferwallHooks>;

  /** Custom asset resolver for rewards */
  assetResolver?: AssetResolver;

  /** Fallback image for unknown rewards */
  fallbackRewardImage: string;

  /** JWT token provider function */
  tokenProvider: TokenProvider;
}

🎨 Asset Resolution

Customize how rewards are displayed in your game:

const gameAssets = {
  gems: { name: 'Gems', image: '/assets/gems.png' },
  gold: { name: 'Gold Coins', image: '/assets/gold.png' },
  sword_1: { name: 'Iron Sword', image: '/assets/sword_iron.png' },
};

const client = new OfferwallClient({
  // ... other config
  assetResolver: (reward, assetId) => {
    const asset = gameAssets[assetId];
    if (asset) {
      return { name: asset.name, image: asset.image };
    }
    return { name: `Unknown Item (${assetId})`, image: null };
  },
});

🎣 Event Hooks

Implement custom logic with event hooks:

const client = new OfferwallClient({
  // ... other config
  hooks: {
    // Control which offers to show
    onOfferSurfaced: (offer) => {
      // Custom logic to determine if offer should be shown
      const currentPlayer = client.getPlayer();
      return currentPlayer?.snapshot.levels?.overall?.level >= offer.minLevel;
    },

    // Handle successful offer claims
    onOfferClaimed: async (offer, rewards) => {
      console.log('Offer completed!', { offer, rewards });
      // Award rewards in your game
      await showConfetti(rewards);
    },

    // Handle connection events
    onConnect: () => {
      console.log('Connected to Pixels BuildOn');
      showConnectionStatus('connected');
    },

    onDisconnect: () => {
      console.log('Disconnected from Pixels BuildOn');
      showConnectionStatus('disconnected');
    },
  },
});

📡 Events

Listen to various events emitted by the client:

// Offer-related events
client.on('offersUpdated', (offers) => {
  /* Handle offers update */
});
client.on('offerAdded', (offer) => {
  /* Handle new offer */
});
client.on('offerRemoved', (offerId) => {
  /* Handle offer removal */
});
client.on('offerUpdated', (offer) => {
  /* Handle offer changes */
});

// Player-related events
client.on('playerUpdated', (player) => {
  /* Handle player data changes */
});

// Connection events
client.on('connected', () => {
  /* Handle connection */
});
client.on('disconnected', () => {
  /* Handle disconnection */
});
client.on('reconnecting', (attempt) => {
  /* Handle reconnection attempts */
});

// Error events
client.on('error', (error) => {
  /* Handle errors */
});

🏗️ API Reference

OfferwallClient Methods

initialize(): Promise<void>

Initialize the client and establish connection.

disconnect(): Promise<void>

Disconnect from the service.

refreshOffersAndPlayer(): { offers: IClientOffer[], player: IClientPlayer }

Refresh and get all current offers.

claimOffer(offerId: string): Promise<ClaimResult>

Claim an offer and receive rewards.

getConnectionState(): ConnectionState

Get current connection status.

Shop Methods

getShopItems(): Promise<ClientShopItem[]>

Live shop items for this game, including this player's purchaseCount per item. Requires the org's allowInAppShopPurchases flag.

purchaseShopItem(shopItemId: string, currencyId: string): Promise<ShopPurchaseResult>

Buy an item with the player's Stacked balance earned in this game. Price is catalog-resolved server-side; delivery via the user.gain_rewards webhook (dedupe on instanceId === purchaseId). Never blind-retry on network failure.

Crypto Checkout Methods

getAvailableWallets(): Promise<DiscoveredWallet[]>

Wallets installed on the page (EIP-6963 metadata + legacy injection points like window.ronin.provider) — render a picker and pass a result's provider to purchaseShopItemWithCrypto.

purchaseShopItemWithCrypto(shopItemId: string, opts: CryptoPurchaseOptions): Promise<CryptoPurchaseResult>

One-shot crypto checkout: connect → quote → chain-switch → sign → verify → execute. Provider resolution: opts.provider → config.getWalletProvider() → discovery → rejects CryptoPurchaseError('no-wallet-available'). Progress via opts.onStatus and the CRYPTO_PURCHASE_STATUS event.

prepareCryptoPurchase(shopItemId, { currencyId, network, walletAddress }): Promise<CryptoPurchaseQuote>

Wallet-free quote: server-resolved price + prepared ERC20 transfer request (quote.tx, broadcast on quote.chainId). For integrators who broadcast themselves (AA wallets, custom UX).

submitCryptoPurchase(purchase, txHash, opts?): Promise<CryptoPurchaseResult>

Finish a purchase from its broadcast tx hash (verify → execute, replay-safe, persists the hash for crash recovery). Never re-broadcast — always resume by hash.

resumePendingCryptoPurchases(opts?): Promise<Array<CryptoPurchaseResult | { txHash, error }>>

Finish purchases interrupted mid-flow. Call once on startup after auth. Strictly scoped to the current player's own records.

listPendingCryptoPurchases(): PendingCryptoPurchase[]

Read-only view of every stored pending record for this env — including ones resumePendingCryptoPurchases skips (identity mismatch, or saved before a JWT could be fetched). Each record points at funds that already left a wallet; surface these in support tooling instead of letting the 7-day TTL discard them invisibly.

Gift Card Methods

getGiftCardConfig(): Promise<GiftCardConfig | null>

Availability, Bitrefill's purchasable bounds, and the player's spendable balances in this game, in one call. null = gift cards are off platform-wide. Rejects with in-app-shop-purchases-not-enabled / no-stacked-account / account-restricted when this specific player can't redeem.

quoteGiftCard({ sourceCurrencyId, amount, recipientEmail? }): Promise<GiftCardQuote>

Server-priced, non-committal quote — persists nothing, moves no balance. targetAmountAfterFee is the card value the player receives; check canWithdraw before enabling the CTA.

purchaseGiftCard({ sourceCurrencyId, amount, recipientEmail, recipientName? }): Promise<GiftCardPurchaseResult>

Buy a card with this game's balance. recipientEmail is required — Bitrefill mails the code there. Returns status: 'waiting'; not idempotent.

getGiftCardHistory(params?): Promise<PaginatedGiftCardPayouts>

Redemption status for payouts this game funded, newest first. bitrefill.code/bitrefill.link appear only once status === 'complete'.

Utility Functions

import { meetsConditions, AssetHelper } from '@pixels-online/pixels-client-js-sdk';

// Gift card bounds math — mirrors the server's own arithmetic
import {
  giftCardValueIssue, // null | 'usd-amount-too-small' | ... for a net card value
  giftCardNetValue, // gross USD -> net card value after the surcharge
  giftCardGrossForNet, // net card value -> gross USD needed (rounds up)
  snapGiftCardValue, // snap DOWN to the nearest purchasable step
} from '@pixels-online/pixels-client-js-sdk';

// Check if player meets offer conditions
const canClaim = meetsConditions(player.snapshot, offer.surfacingConditions);

// Asset helper utilities
const assetHelper = new AssetHelper(assetResolver, fallbackImage);
const rewardAsset = assetHelper.resolveRewardAsset(reward, assetId);

🌍 Environments

The SDK supports multiple environments:

  • test - Sandbox environment for development
  • live - Production environment

� Migration Guide

v1.0.0+ Breaking Changes

Player Data Structure Update

The player data structure has been updated to provide better separation between core player data and additional game-specific data:

  • Old: IClientPlayerSnapshot (flat structure)
  • New: IClientPlayer (structured with snapshot and data properties)

Migration Steps:

// Before (v0.x)
const player = client.getPlayer(); // IClientPlayerSnapshot
const level = player.levels?.combat?.level;
const gold = player.currencies?.gold?.balance;

// After (v1.0+)
const player = client.getPlayer(); // IClientPlayer
const level = player.snapshot.levels?.combat?.level;
const gold = player.snapshot.currencies?.gold?.balance;

// Access additional data (new feature)
const additionalCurrencies = player.data?.currencies;

Event Handler Updates:

// Before
client.on('playerUpdated', (playerSnapshot) => {
  updateUI(playerSnapshot.currencies);
});

// After
client.on('playerUpdated', (player) => {
  updateUI(player.snapshot.currencies);
  // Also handle additional data if needed
  if (player.data) {
    handleAdditionalData(player.data);
  }
});

�📋 Requirements

  • Node.js 16+
  • TypeScript 4.5+ (if using TypeScript)
  • Modern browser with EventSource support

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the AGPLv3 License - see the LICENSE.md file for details.

🔗 Links

📞 Support

For support and questions:

  • Create an issue on GitLab
  • Contact the Pixels team

Made with ❤️ by the Pixels team