# 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](#installation--setup)
- [Basic Usage](#basic-usage)
- [Account Management](#account-management)
- [Buy Operations](#buy-operations)
- [Transaction Patterns](#transaction-patterns)
- [Event Parsing](#event-parsing)
- [Error Handling](#error-handling)
- [Complete Examples](#complete-examples)
- [Best Practices](#best-practices)

## Installation & Setup

```bash
npm install moonshot-blindbox-sdk
```

### Backend Scripts

```typescript
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

```typescript
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

```typescript
// 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

```typescript
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

```typescript
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

```typescript
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

```typescript
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)

```typescript
// 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)

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
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

```typescript
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**
```typescript
{
  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**
```typescript
{
  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**
```typescript
{
  role: number;    // Role enum value (Admin = 0, Operator = 1)
  account: string; // Account that was granted/revoked the role
}
```

## Error Handling

```typescript
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

```typescript
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

```typescript
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
