npm package: @omen.foundation/game-sdk
Description: OmenX Game SDK for web applications - OAuth authentication, API integration, and real-time balance/inventory updates
Author: OmenX
Latest version: 1.0.31

# @omen.foundation/game-sdk

OmenX Game SDK for web applications - OAuth authentication and API integration.

## For AI coding assistants (Cursor, Claude, Codex, Copilot, etc.)

After install, read **`AGENTS.md`** in this package, then **`ai/GAME_SDK_FOR_AI.md`** for the full integration spec (client vs server, realtime, errors, every API). Optionally paste **`ai/cursor-rule-snippet.md`** into your project’s `.cursor/rules` so agents know to open those files under `node_modules/@omen.foundation/game-sdk/`.

The SDK supports two modes:
- **Client Mode**: OAuth authentication for frontend applications (browser/React)
- **Server Mode**: API key authentication for Node.js backends

## Installation

```bash
npm install @omen.foundation/game-sdk
```

For **real-time updates** (balance & inventory), also install Ably:

```bash
npm install ably
```

## Quick Start

### Client Mode (OAuth Authentication)

For frontend applications that need user authentication:

```typescript
import { OmenXGameSDK } from '@omen.foundation/game-sdk';

const sdk = new OmenXGameSDK({
  gameId: 'your-game-id',
  onAuth: (authData) => {
    console.log('Authenticated!', authData);
    // User is now authenticated
  },
  onAuthError: (error) => {
    console.error('Auth error:', error);
  },
});

// Initialize SDK
await sdk.init();
```

### Server Mode (API Key Authentication)

For Node.js backends using developer API keys:

```typescript
import { OmenXServerSDK } from '@omen.foundation/game-sdk';

const sdk = new OmenXServerSDK({
  apiKey: 'your-developer-api-key',
  apiBaseUrl: 'https://api.omen.foundation', // Optional
});

// Make API calls directly
const templates = await sdk.getNftTemplates('your-game-id');
const contract = await sdk.getNftContract('your-game-id');
```

**Server timeouts and errors:** Non-2xx `apiCall` responses throw **`OmenXApiError`** (`status`, optional `retryAfterSec`, `bodyText`). Constructor options include `requestTimeoutMs` (default 60s), `createPurchaseRequestTimeoutMs` (default 180s), and `createPurchaseMaxAttempts` (default 4 when `idempotencyKey` is set). **`createPurchase`** retries 429, 408, and transient 5xx with backoff and respects **`Retry-After`** when present; each attempt sends the same **`Idempotency-Key`** header.

### OAuth Authentication

For standalone games (not embedded in iframes):

```typescript
// Authenticate user via OAuth popup
await sdk.authenticate({
  redirectUri: 'https://yourgame.com/auth/callback',
  enablePKCE: true, // Recommended for security
});
```

### Iframe Authentication

For games embedded in the OmenX frontend, authentication is automatic:

```typescript
const sdk = new OmenXGameSDK({
  gameId: 'your-game-id',
  enableIframeAuth: true, // Default: true
  parentOrigin: 'https://omen.foundation', // Optional: for security
  onAuth: (authData) => {
    // Auth data received automatically from parent window
  },
});

await sdk.init();
```

### Making API Calls (Client Mode)

```typescript
// Make authenticated API calls using user's access token
const response = await sdk.apiCall('/v1/nfts/templates?gameId=your-game-id');
const data = await response.json();
```

### Server Mode API Methods

The server SDK provides convenient methods for all API operations:

#### Health & Auth
```typescript
// Health check
const health = await sdk.healthCheck();

// Get API key information
const info = await sdk.getApiKeyInfo();
```

#### Players
```typescript
// Get wallet balances
const balances = await sdk.getPlayerBalances('0x...', '8453');

// Get wallet NFTs
const nfts = await sdk.getPlayerNfts('0x...', '8453', {
  contract: '0x...', // Optional
  cursor: '...',      // Optional
  limit: 20,          // Optional
});

// Conditional fetch (ETag/304) for metadata freshness
const cached = await sdk.getPlayerNftsWithCache('0x...', '56', {
  ifNoneMatch: previousEtag,
});
if (!cached.notModified) {
  previousEtag = cached.etag ?? previousEtag;
  applyInventory(cached.data);
}
```

#### Purchases
```typescript
// Create a purchase
const purchase = await sdk.createPurchase({
  playerWallet: '0x...',
  skuId: 'sku-id',
  quantity: 1,
  idempotencyKey: 'unique-key', // Optional
});
```

#### OAuth (verify user token)
```typescript
// Verify OAuth access token and get user info (for server-side federation)
const result = await sdk.verifyOAuthUser(accessToken);
if (result.success) {
  console.log(result.user.walletAddress, result.user.userId);
} else {
  console.log(result.statusCode, result.error?.code, result.error?.message);
}
```

#### Game Rewards (game_rewards:write scope)
```typescript
// Grant OMENX to a single player (on-chain transfer + PurchaseHistory)
const result = await sdk.grantGameReward({
  walletAddress: '0x...',
  amount: 100,
  gameId: 'my-game',
  gameName: 'My Game',
  chainId: '56',
});

// Batch grant (single on-chain transaction)
const batchResult = await sdk.grantGameRewardBatch({
  payments: [
    { walletAddress: '0x...', amount: 100 },
    { walletAddress: '0x...', amount: 50 },
  ],
  gameId: 'my-game',
  gameName: 'My Game',
  chainId: '56',
});
```

#### Upload Image (images:write scope)
```typescript
// Upload images to IPFS (avatars, clan logos, game media)
const result = await sdk.uploadImage({
  file: base64Data,       // with or without data:image/...;base64, prefix
  fileName: 'avatar.png',
  fileType: 'image/png',  // image/jpeg | image/png | image/webp | image/gif
});
console.log('URL for <img> tag:', result.url);
console.log('IPFS URI:', result.uri);
```

#### NFTs
```typescript
// Get NFT templates
const templates = await sdk.getNftTemplates('your-game-id');

// Get contract address
const contract = await sdk.getNftContract('your-game-id');

// Mint NFTs
const result = await sdk.mintNfts({
  templateId: 'template-id',
  recipientAddress: '0x...',
  quantity: 1,
});

// Batch mint
const batchResult = await sdk.batchMintNfts([
  { templateId: 'template-1', recipientAddress: '0x...', quantity: 1 },
  { templateId: 'template-2', recipientAddress: '0x...', quantity: 2 },
]);

// Update NFT metadata
await sdk.updateNftMetadata('token-id', {
  rarity: 'legendary',
  level: 10,
});

// Burn NFT
await sdk.burnNft('token-id');

// Batch burn
await sdk.batchBurnNfts(['token-1', 'token-2']);

// Pack opener
const packResult = await sdk.packOpener({
  dropTableId: 'drop-table-id',
  recipientAddress: '0x...',
});

// Generic API call
const response = await sdk.apiCall('/v1/custom-endpoint', {
  method: 'POST',
  body: { custom: 'data' },
});
```

### Check Authentication Status

```typescript
if (sdk.isAuthenticated()) {
  const authData = sdk.getAuthData();
  console.log('User:', authData.walletAddress);
}
```

### Logout

```typescript
await sdk.logout();
```

### Real-Time Updates (Balance, Inventory, Metadata)

So your game stays in sync when the user spends or receives OmenX / NFTs on the website or in another client, subscribe to the per-wallet Ably channel. The SDK provides the channel name, event names, and a subscribe helper with idempotency (eventId dedupe).

**Requires:** `ably` installed in your app. Use the same Ably API key as the OmenX frontend (subscribe capability for `wallet:*`).

```typescript
import { subscribeWalletRealtime, getWalletChannelName, REALTIME_EVENTS } from '@omen.foundation/game-sdk';
import * as Ably from 'ably';

// After user logs in and you have their wallet address:
const ably = new Ably.Realtime({ key: 'YOUR_ABLY_API_KEY' });

const unsubscribe = subscribeWalletRealtime(ably, walletAddress, {
  onBalance: (payload) => {
    // Refetch balances (e.g. call your backend or GetPlayerBalances)
    refetchBalances();
  },
  onInventory: (payload) => {
    // Refetch inventory / NFTs (backend cache is source of truth)
    refetchInventory();
  },
  onMetadataUpdate: (payload) => {
    // Metadata update hint: refetch canonical API and apply token-level version guard
    refetchInventory();
  },
});

// When user logs out or wallet changes:
unsubscribe();
```

With React and `ably/react`, pass the client from `useAbly()`:

```typescript
import { useAbly } from 'ably/react';
import { subscribeWalletRealtime } from '@omen.foundation/game-sdk';

// Inside your component, after wallet is available:
const ably = useAbly();
useEffect(() => {
  if (!walletAddress || !ably) return;
  return subscribeWalletRealtime(ably, walletAddress, {
    onBalance: () => refetchBalances(),
    onInventory: () => refetchInventory(),
    onMetadataUpdate: () => refetchInventory(),
  });
}, [ably, walletAddress]);
```

## API Reference

See the [full documentation](./docs/README.md) for detailed API reference.

## License

MIT