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

moonshot-blindbox-sdk

v1.1.14

Published

Official SDK for Moonshot Blindbox Smart Contract - A Solana-based blind box platform

Readme

MoonshotBlindbox SDK - How to Use

This guide demonstrates how to use the MoonshotBlindbox SDK to interact with the Moonshot Blindbox Smart Contract on Solana.

Table of Contents

Installation & Setup

npm install moonshot-blindbox-sdk

Backend Scripts

import { MoonshotBlindbox } from "moonshot-blindbox-sdk";
import { generateKeyPairSigner } from "@solana/kit";

// Initialize SDK with your signer and RPC endpoints
const signer = await generateKeyPairSigner();
const moonshot = new MoonshotBlindbox(
  signer,
  "https://api.devnet.solana.com", // RPC URL
  "wss://api.devnet.solana.com", // WebSocket URL
  true // skipSimulation (optional, defaults to false)
);

Frontend Integration

import { MoonshotBlindbox } from "moonshot-blindbox-sdk";
import {
  useWalletAccountTransactionSendingSigner,
  useWallets,
} from "@solana/react";

const wallets = useWallets();

// TODO: Have to implement the logic to handle the wallet selection of users
const transactionSendingSigner = useWalletAccountTransactionSendingSigner(
  wallets[0],
  "solana:devnet" // TODO: Detect the network
);

const moonshot = new MoonshotBlindbox(
  transactionSendingSigner,
  "https://api.devnet.solana.com", // RPC URL
  "wss://api.devnet.solana.com", // WebSocket URL
  true // skipSimulation (optional, defaults to false)
);

Basic Usage

1. Initialize a Config

// Create a new config with ID 1
const configId = 1;
const initializeConfigIx = await moonshot.getInitializeConfigInstruction({
  id: configId,
});

await moonshot.signAndSendTransaction([initializeConfigIx]);
console.log("Config initialized successfully");

2. Grant Roles

import { Role } from "moonshot-blindbox-sdk";

// Grant Admin role to an account
const adminAddress = "YourAdminAddressHere";
const grantAdminIx = await moonshot.getGrantRoleInstruction({
  configId,
  accountToGrant: adminAddress,
  role: Role.Admin,
});

await moonshot.signAndSendTransaction([grantAdminIx]);
console.log("Admin role granted");

3. Create a Campaign

const campaignId = 0;
const now = Math.floor(Date.now() / 1000);

// Define stages with start/end times and purchase limits
const stages = [
  {
    startTime: BigInt(now),
    endTime: BigInt(now + 600),
    maxItemsPerUser: BigInt(2),
    itemsAvailable: BigInt(100),
    itemsPurchased: BigInt(0),
  },
  {
    startTime: BigInt(now + 600),
    endTime: BigInt(now + 1200),
    maxItemsPerUser: BigInt(5),
    itemsAvailable: BigInt(100),
    itemsPurchased: BigInt(0),
  },
];

// Define metadata for the campaign collection
const campaignMetadata = {
  name: "My Campaign Collection",
  symbol: "MCC",
  uri: "https://metadata.example.com/campaign.json",
};

// Define metadata for the individual items/NFTs
const itemMetadata = {
  name: "Campaign Item",
  symbol: "CI",
  uri: "https://metadata.example.com/item.json",
};

// Create campaign returns [instruction, mintAddress]
const [createCampaignIx, mintAddress] =
  await moonshot.getCreateCampaignInstruction({
    configId,
    campaignId,
    stages,
    priceInLamports: BigInt(1000000000), // 1 SOL in lamports
    paymentDestination: adminAddress,
    itemsAvailable: BigInt(200), // Total items available in this campaign
    campaignMetadata,
    itemMetadata,
  });

await moonshot.signAndSendTransaction([createCampaignIx]);
console.log("Campaign created successfully with mint:", mintAddress);

Account Management

Fetch Account Data

import {
  getConfigAddress,
  getCampaignAddress,
  getUserAddress,
} from "moonshot-blindbox-sdk";

// Get config data
const configAddress = await getConfigAddress(configId);
const config = await moonshot.fetchConfig(configAddress);
console.log("Config:", config);

// Get campaign data
const campaignAddress = await getCampaignAddress(campaignId, configAddress);
const campaign = await moonshot.fetchCampaign(campaignAddress);
console.log("Campaign:", campaign);

// Get user data
const userAddress = await getUserAddress(campaignAddress, buyerAddress);
const user = await moonshot.fetchUser(userAddress);
console.log("User:", user);

Check Capabilities

import { Role, getCapabilityAddress } from "moonshot-blindbox-sdk";

// Check if an account has a specific role
const capabilityAddress = await getCapabilityAddress(
  configAddress,
  adminAddress,
  Role.Admin
);
const capability = await moonshot.fetchCapability(capabilityAddress);
if (capability) {
  console.log("Account has Admin role");
} else {
  console.log("Account does not have Admin role");
}

Buy Operations

Single Transaction Pattern (Operator signs everything)

// For scenarios where the operator controls all signatures
const buyerAddress = "BuyerWalletAddressHere";

// getBuyInstruction returns [instruction, mintAddress]
const [buyIx, mintAddress] = await moonshot.getBuyInstruction({
  configId,
  campaignId,
  buyer: buyerAddress,
});

await moonshot.signAndSendTransaction([buyIx]);
console.log("Purchase completed. Mint address:", mintAddress);

Multi-Signature Pattern (Buyer maintains control)

import { generateKeyPairSigner } from "@solana/kit";

// For scenarios where buyer maintains control of their signature
const buyerSigner = await generateKeyPairSigner();
const buyerMoonshot = new MoonshotBlindbox(buyerSigner, RPC_URL, WSS_URL, true);

// Operator prepares the transaction (returns [instruction, mintAddress])
const [buyIx, mintAddress] = await moonshot.getBuyInstruction({
  configId,
  campaignId,
  buyer: buyerSigner.address,
});

// Operator partially signs the transaction
const encodedTransaction = await moonshot.partiallySignTransaction(
  [buyIx],
  buyerSigner.address
);

const encodedTransactionFromAPI = Buffer.from(encodedTransaction).toString("base64");

// Frontend recovers the original encoded transaction
const recoveredEncodedTransaction = Buffer.from(encodedTransactionFromAPI, "base64");

// Buyer completes the signature and sends
await buyerMoonshot.signAndSendEncodedTransaction(encodedTransaction);
console.log("Purchase completed with buyer signature");
console.log("Mint address:", mintAddress);

Transaction Patterns

Batch Operations

import { Role } from "moonshot-blindbox-sdk";

// Perform multiple operations in a single transaction
const instructions = [];

// Example: Grant multiple roles in one transaction
const accounts = ["Address1Here", "Address2Here", "Address3Here"];

for (const account of accounts) {
  const grantRoleIx = await moonshot.getGrantRoleInstruction({
    configId,
    accountToGrant: account,
    role: Role.Operator,
  });
  instructions.push(grantRoleIx);
}

// Send all instructions in one transaction
await moonshot.signAndSendTransaction(instructions);
console.log("Batch operations completed");

Conditional Operations

import { Status, getCampaignAddress } from "moonshot-blindbox-sdk";
import { some, none } from "@solana/kit";

// Check if campaign exists before updating
const campaignAddress = await getCampaignAddress(campaignId, configAddress);
const campaign = await moonshot.fetchCampaign(campaignAddress);

if (campaign && campaign.status === Status.Active) {
  // Update campaign to inactive status
  const updateIx = await moonshot.getUpdateCampaignInstruction({
    configId,
    campaignId,
    status: some(Status.Inactive),
    stages: none(),
    priceInLamports: none(),
    paymentDestination: none(),
    itemsAvailable: none(),
  });

  await moonshot.signAndSendTransaction([updateIx]);
  console.log("Campaign deactivated");
}

Event Parsing

The SDK includes a powerful EventParser for extracting and decoding events emitted by the smart contract. This is essential for monitoring transaction outcomes and building reactive applications.

Available Events

The following events can be parsed from transaction logs:

  • InitializeEvent - Emitted when a new config is initialized
  • UpdateConfigEvent - Emitted when config status is updated
  • GrantRoleEvent - Emitted when a role is granted to an account
  • RevokeRoleEvent - Emitted when a role is revoked from an account
  • CreateCampaignEvent - Emitted when a new campaign is created
  • UpdateCampaignEvent - Emitted when campaign parameters are updated
  • UpdateCampaignMetadataEvent - Emitted when campaign or item metadata is updated
  • BuyEvent - Emitted when a user purchases an item

Basic Event Parsing

import { EventParser, parseTransactionEventCPI } from "moonshot-blindbox-sdk";

// After sending a transaction, parse its events
const signature = await moonshot.signAndSendTransaction([buyIx]);

// Extract event CPI data from the transaction
const eventCPI = await parseTransactionEventCPI(
  "https://api.devnet.solana.com",
  signature
);

// Parse the event data
const parsedEvent = EventParser.parseEventCPI(Buffer.from(eventCPI.data, "base64"));
console.log(`Event type: ${parsedEvent.name}`);
console.log("Event data:", parsedEvent.data);

Parsing Specific Events

// Parse a BuyEvent after purchase
const [buyIx, mintAddress] = await moonshot.getBuyInstruction({
  configId,
  campaignId,
  buyer: buyerAddress,
});

const signature = await moonshot.signAndSendTransaction([buyIx]);
const eventCPI = await parseTransactionEventCPI(RPC_URL, signature);
const event = EventParser.parseEventCPI(Buffer.from(eventCPI.data, "base64"));

if (event.name === "BuyEvent") {
  console.log("Purchase Details:");
  console.log(`Buyer: ${event.data.buyer}`);
  console.log(`Amount: ${event.data.amount}`);
  console.log(`Total Cost: ${event.data.totalLamports} lamports`);
  console.log(`NFT Mint: ${event.data.mint}`);
  console.log(`Token Account: ${event.data.associatedTokenAccount}`);
  console.log(`Metadata: ${event.data.metadata}`);
}

Event Discovery and Validation

import { EventParser } from "moonshot-blindbox-sdk";

// Get all available events
const availableEvents = EventParser.getAvailableEvents();
console.log("Available events:", availableEvents);
// Output: ['InitializeEvent', 'UpdateConfigEvent', 'GrantRoleEvent', ...]

// Check if a specific event is supported
const isBuySupported = EventParser.isEventSupported("BuyEvent");
console.log("BuyEvent supported:", isBuySupported); // true

const isCustomSupported = EventParser.isEventSupported("CustomEvent");
console.log("CustomEvent supported:", isCustomSupported); // false

Complete Example: Transaction Monitoring

import {
  MoonshotBlindbox,
  EventParser,
  parseTransactionEventCPI,
} from "moonshot-blindbox-sdk";

async function monitorPurchase(
  configId: number,
  campaignId: number,
  buyerAddress: string
) {
  try {
    // Create and send purchase transaction
    const [buyIx, expectedMint] = await moonshot.getBuyInstruction({
      configId,
      campaignId,
      buyer: buyerAddress,
    });

    // Operator partially signs
    const encodedTransaction = await operatorMoonshot.partiallySignTransaction(
      [buyIx],
      buyerSigner.address
    );

    // Buyer completes signature and sends
    const signature = await buyerMoonshot.signAndSendEncodedTransaction(
      encodedTransaction
    );
    console.log(`Transaction signature: ${signature}`);

    // Parse the transaction events
    console.log("Parsing transaction events...");
    const eventCPI = await parseTransactionEventCPI(
      "https://api.devnet.solana.com",
      signature
    );

    const parsedEvent = EventParser.parseEventCPI(
      Buffer.from(eventCPI.data, "base64")
    );

    // Handle the event based on type
    switch (parsedEvent.name) {
      case "BuyEvent":
        console.log("✅ Purchase Successful!");
        console.log(`Buyer: ${parsedEvent.data.buyer}`);
        console.log(`Items Purchased: ${parsedEvent.data.amount}`);
        console.log(`Total Paid: ${parsedEvent.data.totalLamports} lamports`);
        console.log(`NFT Mint Address: ${parsedEvent.data.mint}`);
        console.log(`Token Account: ${parsedEvent.data.associatedTokenAccount}`);
        
        // Verify the mint matches what we expected
        if (parsedEvent.data.mint === expectedMint) {
          console.log("✅ Mint address verified!");
        }
        break;

      default:
        console.log(`Unexpected event: ${parsedEvent.name}`);
    }

    return parsedEvent;
  } catch (error) {
    console.error("Transaction or parsing failed:", error);
    throw error;
  }
}

Event Data Structures

Each event has a specific data structure. Here are the key event types:

BuyEvent

{
  buyer: string;              // Buyer's public key
  paymentDestination: string; // Where payment was sent
  campaign: string;           // Campaign public key
  config: string;             // Config public key
  amount: bigint;             // Number of items purchased
  totalLamports: bigint;      // Total amount paid
  mint: string;               // NFT mint address
  associatedTokenAccount: string; // Buyer's token account
  metadata: string;           // Metadata account
  edition: string;            // Master edition account
}

CreateCampaignEvent

{
  campaignId: number;
  campaign: string;
  config: string;
  stages: Array<{
    startTime: bigint;
    endTime: bigint;
    maxItemsPerUser: bigint;
    itemsAvailable: bigint;
    itemsPurchased: bigint;
  }>;
  priceInLamports: bigint;
  paymentDestination: string;
  itemsAvailable: bigint;
  campaignMetadata: {
    name: string;
    symbol: string;
    uri: string;
  };
  itemMetadata: {
    name: string;
    symbol: string;
    uri: string;
  };
}

GrantRoleEvent / RevokeRoleEvent

{
  role: number;    // Role enum value (Admin = 0, Operator = 1)
  account: string; // Account that was granted/revoked the role
}

Error Handling

try {
  const [buyIx, mintAddress] = await moonshot.getBuyInstruction({
    configId,
    campaignId,
    buyer: buyerAddress,
  });

  await moonshot.signAndSendTransaction([buyIx]);
} catch (error: any) {
  if (error.message.includes("InvalidAmount")) {
    console.error("Invalid purchase amount");
  } else if (error.message.includes("PurchaseLimitExceeded")) {
    console.error("Purchase limit exceeded for this stage");
  } else if (error.message.includes("OutOfCampaignDuration")) {
    console.error("Campaign is not currently active");
  } else {
    console.error("Transaction failed:", error.message);
  }
}

Complete Examples

Example 1: Full Campaign Setup

import { MoonshotBlindbox, Status, Role } from "moonshot-blindbox-sdk";
import { generateKeyPairSigner, some, none } from "@solana/kit";

async function setupCompleteCampaign() {
  // Initialize SDK
  const adminSigner = await generateKeyPairSigner();
  const moonshot = new MoonshotBlindbox(
    adminSigner,
    "https://api.devnet.solana.com",
    "wss://api.devnet.solana.com"
  );

  const configId = 1;
  const campaignId = 0;

  try {
    // 1. Initialize config
    console.log("Initializing config...");
    const initConfigIx = await moonshot.getInitializeConfigInstruction({
      id: configId,
    });
    await moonshot.signAndSendTransaction([initConfigIx]);

    // 2. Grant admin role to operator
    console.log("Granting admin role...");
    const operatorAddress = "OperatorWalletAddress";
    const grantAdminIx = await moonshot.getGrantRoleInstruction({
      configId,
      accountToGrant: operatorAddress,
      role: Role.Admin,
    });
    await moonshot.signAndSendTransaction([grantAdminIx]);

    // 3. Create campaign with stages and metadata
    console.log("Creating campaign...");
    const now = Math.floor(Date.now() / 1000);
    const stages = [
      {
        startTime: BigInt(now),
        endTime: BigInt(now + 600),
        maxItemsPerUser: BigInt(2),
        itemsAvailable: BigInt(100),
        itemsPurchased: BigInt(0),
      },
      {
        startTime: BigInt(now + 600),
        endTime: BigInt(now + 1200),
        maxItemsPerUser: BigInt(5),
        itemsAvailable: BigInt(100),
        itemsPurchased: BigInt(0),
      },
    ];

    const campaignMetadata = {
      name: "Pudgy Penguins",
      symbol: "PP",
      uri: "https://metadata.example.com/campaign.json",
    };

    const itemMetadata = {
      name: "Blindbox Item",
      symbol: "BI",
      uri: "https://metadata.example.com/item.json",
    };

    const [createCampaignIx, mintAddress] =
      await moonshot.getCreateCampaignInstruction({
        configId,
        campaignId,
        stages,
        priceInLamports: BigInt(1000000000), // 1 SOL
        paymentDestination: operatorAddress,
        itemsAvailable: BigInt(100), // Total 100 items available
        campaignMetadata,
        itemMetadata,
      });
    await moonshot.signAndSendTransaction([createCampaignIx]);

    console.log("Campaign setup completed successfully!");
    console.log("Mint address:", mintAddress);
    return { configId, campaignId, mintAddress };
  } catch (error) {
    console.error("Campaign setup failed:", error);
    throw error;
  }
}

Example 2: Purchase Flow with Multi-Signature

import {
  MoonshotBlindbox,
  Status,
  getConfigAddress,
  getCampaignAddress,
  getUserAddress,
} from "moonshot-blindbox-sdk";
import { generateKeyPairSigner } from "@solana/kit";

async function purchaseWithBuyerSignature(
  configId: number,
  campaignId: number
) {
  // Setup operator and buyer SDKs
  const operatorSigner = await generateKeyPairSigner();
  const operatorMoonshot = new MoonshotBlindbox(
    operatorSigner,
    "https://api.devnet.solana.com",
    "wss://api.devnet.solana.com"
  );

  const buyerSigner = await generateKeyPairSigner();
  const buyerMoonshot = new MoonshotBlindbox(
    buyerSigner,
    "https://api.devnet.solana.com",
    "wss://api.devnet.solana.com",
    true // Skip simulation for faster transactions
  );

  try {
    // Check campaign status
    const configAddress = await getConfigAddress(configId);
    const campaignAddress = await getCampaignAddress(campaignId, configAddress);
    const campaign = await operatorMoonshot.fetchCampaign(campaignAddress);

    if (!campaign || campaign.status !== Status.Active) {
      throw new Error("Campaign is not active");
    }

    console.log(`Campaign has ${campaign.itemsAvailable} items available`);

    // Check buyer's current purchase status
    const userAddress = await getUserAddress(
      campaignAddress,
      buyerSigner.address
    );
    const user = await operatorMoonshot.fetchUser(userAddress);

    const currentPurchases = user?.itemsPurchased || BigInt(0);
    console.log(`Buyer has already purchased ${currentPurchases} items`);

    // Determine purchase amount (respect stage limits)
    const stageIndex = 0; // Current stage
    const stageLimit = campaign.stages[stageIndex]?.purchaseLimit || BigInt(0);
    const purchasedInStage =
      user?.itemsPurchasedPerStage[stageIndex] || BigInt(0);
    const remainingInStage = stageLimit - purchasedInStage;

    const purchaseAmount = remainingInStage > BigInt(0) ? BigInt(1) : BigInt(0);

    if (purchaseAmount === BigInt(0)) {
      throw new Error("Purchase limit reached for current stage");
    }

    // Create buy instruction (returns [instruction, mintAddress])
    const [buyIx, mintAddress] = await operatorMoonshot.getBuyInstruction({
      configId,
      campaignId,
      buyer: buyerSigner.address,
    });

    // Operator partially signs
    const encodedTransaction = await operatorMoonshot.partiallySignTransaction(
      [buyIx],
      buyerSigner.address
    );

    // Buyer completes signature and sends
    const signature = await buyerMoonshot.signAndSendEncodedTransaction(
      encodedTransaction
    );

    console.log(`Purchase successful! Transaction signature: ${signature}`);
    console.log(`NFT mint address: ${mintAddress}`);

    // Verify final state
    const updatedUser = await operatorMoonshot.fetchUser(userAddress);
    const updatedCampaign = await operatorMoonshot.fetchCampaign(
      campaignAddress
    );

    console.log(`Buyer now has ${updatedUser?.itemsPurchased} total purchases`);
    console.log(
      `Campaign now has ${updatedCampaign?.itemsAvailable} items remaining`
    );

    return signature;
  } catch (error) {
    console.error("Purchase failed:", error);
    throw error;
  }
}

Best Practices

  1. Always check account existence before performing operations
  2. Handle errors gracefully with specific error type checking
  3. Use multi-signature patterns for user-facing applications
  4. Implement retry logic for network-related failures
  5. Validate amounts and limits before creating transactions
  6. Monitor transaction confirmations for critical operations
  7. Use batch operations when possible to reduce transaction fees
  8. Keep private keys secure and never expose them in client-side code
  9. Parse and log events for all critical transactions to ensure proper execution
  10. Implement real-time event monitoring for interactive applications that need to react to blockchain state changes
  11. Cache parsed events to avoid redundant RPC calls when displaying transaction history
  12. Validate event data against expected values (e.g., verify mint addresses match) before considering a transaction successful