npm package: @project_zero/prompt-mining-sdk
Description: TypeScript SDK for Prompt Mining contracts
Latest version: 0.0.3-dev

# @project_zero/prompt-mining-sdk

[![npm version](https://img.shields.io/npm/v/@project_zero/prompt-mining-sdk.svg)](https://www.npmjs.com/package/@project_zero/prompt-mining-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

TypeScript SDK for interacting with ERC-7208 prompt mining smart contracts on Nexera Network.

## Overview

The Prompt Mining SDK provides a type-safe, developer-friendly interface for integrating blockchain-based prompt reward systems into LLM applications. This SDK enables:

- **Type-safe contract interactions** via TypeChain-generated types
- **Multiple reward mechanisms** (activity points, ERC-20 transfers, NFT minting)
- **Helper utilities** for signature generation and hashing
- **Meta-transaction support** (ERC-2771) for gasless user experiences
- **Deployment helpers** for testing and custom deployments

**Note:** This SDK is published to npm. Smart contract source code will be open-sourced after security audit completion.

## Architecture

The Prompt Mining SDK serves as the bridge between TypeScript/JavaScript applications and the signature-gated smart contracts deployed on Nexera Network.

```
┌─────────────────────────────────────────────────────────────────┐
│              LLM Service (Customer)                             │
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │        prompt-mining-boilerplate                          │  │
│  │        Uses: @project_zero/prompt-mining-sdk             │  │
│  │                                                           │  │
│  │  1. User submits prompt to LLM service                   │  │
│  │  2. Hash prompt locally: keccak256(prompt)               │  │
│  │  3. Request authorization from PZERO Gateway             │  │
│  └────────────────────┬──────────────────────────────────────┘  │
└─────────────────────────┼────────────────────────────────────────┘
                          │
                          │ HTTP: POST /v1/authorize/mint
                          │ Body: { promptHash, author, activityPoints }
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────┐
│          PZERO API Gateway (Signature Gate)                     │
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  - Validates customer API key                            │  │
│  │  - Enforces tier quotas (Free: 1K, Pro: 100K)            │  │
│  │  - Generates cryptographic signature                     │  │
│  │    signature = sign(hash + author + points + nonce + ...)│  │
│  │  - Returns: { signature, nonce, expiresAt }              │  │
│  └────────────────────┬──────────────────────────────────────┘  │
└─────────────────────────┼────────────────────────────────────────┘
                          │
                          │ Authorization signature
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────┐
│         Blockchain (Nexera - ERC-7208)                          │
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │       Smart Contracts (Signature-Gated)                   │  │
│  │                                                           │  │
│  │  ┌──────────────┐         ┌─────────────────────┐        │  │
│  │  │ PromptMiner  │────────▶│  ActivityPoints     │        │  │
│  │  │              │         │  (ERC-20 Rewards)   │        │  │
│  │  └──────┬───────┘         └─────────────────────┘        │  │
│  │         │                                                 │  │
│  │         │ Stores prompt hashes                           │  │
│  │         ▼                                                 │  │
│  │  ┌──────────────┐         ┌─────────────────────┐        │  │
│  │  │   PromptDO   │◀────────│    DataIndex        │        │  │
│  │  │ (Data Object)│         │    (ERC-7208)       │        │  │
│  │  └──────────────┘         └─────────────────────┘        │  │
│  │                                                           │  │
│  │  THIS SDK: Provides TypeScript interface to interact     │  │
│  │  with all contracts above via ethers.js                  │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                 │
│  4. Contract verifies PZERO signature                           │
│  5. Mints prompt hash to PromptDO (indexed by Data Point)       │
│  6. Rewards user with ActivityPoints                            │
└─────────────────────────────────────────────────────────────────┘
```

### Key Architectural Points

- **Signature-Gated**: All minting operations require authorization signature from PZERO Gateway
- **Privacy-First**: Only prompt hashes are stored on-chain (irreversible)
- **Multi-Tenant**: ERC-7208 Data Points provide isolated storage per customer
- **Three Reward Variants**: Choose activity points, ERC-20 transfers, or NFT minting
- **SDK Role**: Abstracts complex contract interactions into simple TypeScript methods

## Installation

```bash
npm install @project_zero/prompt-mining-sdk ethers@^6.13.5
```

Or with Yarn:

```bash
yarn add @project_zero/prompt-mining-sdk ethers@^6.13.5
```

**Peer Dependencies:**
- `ethers` ^6.13.5 or higher

**Requirements:**
- Node.js >= 18.0.0
- TypeScript >= 4.5.0 (for TypeScript projects)

## Quick Start

```typescript
import { contractFactories } from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

// Setup provider and signer
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

// Connect to deployed PromptMiner contract
const promptMiner = contractFactories.PromptMinerWithActivityPoints.connect(
  process.env.PROMPT_MINER_ADDRESS,
  signer
);

// Hash the prompt
const promptHash = ethers.keccak256(ethers.toUtf8Bytes('What is AI?'));

// Get authorization from PZERO Gateway (not shown - see boilerplate)
const { signature, nonce, expiresAt } = await getPZEROAuthorization(/*...*/);

// Encode action data
const actionData = ethers.AbiCoder.defaultAbiCoder().encode(
  ['uint256', 'uint256', 'uint256'],
  [activityPoints, nonce, expiresAt]
);

// Mint the prompt on-chain
const tx = await promptMiner.mint(promptHash, '', actionData, signature);
await tx.wait();

console.log('Prompt minted! User rewarded with activity points.');
```

## Core Concepts

### Three PromptMiner Variants

The SDK supports three reward mechanisms via different PromptMiner contract variants:

#### 1. PromptMinerWithActivityPoints

Mints activity points (ERC-20 tokens) as rewards.

**Use Cases:**
- Gamification and reputation systems
- Internal point-based economies
- User contribution tracking

**Example:**
```typescript
const promptMiner = contractFactories.PromptMinerWithActivityPoints.connect(
  CONTRACT_ADDRESS,
  signer
);
```

#### 2. PromptMinerWithERC20Transfer

Transfers existing ERC-20 tokens from a treasury as rewards.

**Use Cases:**
- Direct token incentives (USDC, project tokens)
- Payment for quality prompts
- Fiat-backed stablecoin rewards

**Example:**
```typescript
const promptMiner = contractFactories.PromptMinerWithERC20TransferAction.connect(
  CONTRACT_ADDRESS,
  signer
);
```

#### 3. PromptMinerWithMintTo

Mints NFTs or tokens directly to users as rewards.

**Use Cases:**
- NFT collectibles for prompt contributions
- Dynamic token generation
- Exclusive access tokens

**Example:**
```typescript
const promptMiner = contractFactories.PromptMinerWithMintToAction.connect(
  CONTRACT_ADDRESS,
  signer
);
```

### Signature Authorization

All PromptMiner contracts are **signature-gated** via the PZERO API Gateway:

1. Customer requests authorization: `POST /v1/authorize/mint`
2. Gateway validates API key and enforces quota
3. Gateway generates cryptographic signature
4. Customer submits transaction with signature
5. Smart contract verifies signature matches authorized PZERO signer
6. Transaction executes if signature is valid and not expired

**Benefits:**
- Prevents unauthorized minting
- Enables B2B business model with tiered pricing
- Centralized quota management
- Audit trail of all authorization requests

### ERC-7208 Data Points

Prompt hashes are stored in **PromptDO** (Data Object), indexed by **Data Points**:

- Each customer/project receives a unique Data Point ID
- Data Points provide multi-tenant isolation
- PromptMiner contracts must be allowlisted in DataIndex for their Data Point
- Enables multiple customers to use the same contracts with isolated data

## Contract Factories

The SDK exports contract factories for connecting to deployed contracts:

```typescript
import { contractFactories } from '@project_zero/prompt-mining-sdk';

// PromptMiner variants
const promptMinerAP = contractFactories.PromptMinerWithActivityPoints.connect(address, signer);
const promptMinerERC20 = contractFactories.PromptMinerWithERC20TransferAction.connect(address, signer);
const promptMinerMint = contractFactories.PromptMinerWithMintToAction.connect(address, signer);

// Supporting contracts
const activityPoints = contractFactories.ActivityPoints.connect(address, signer);
const promptDO = contractFactories.PromptDO.connect(address, signer);
```

All factories are strongly typed via TypeChain-generated interfaces.

## API Reference

### PromptMiner Methods

All three PromptMiner variants share the same core interface:

#### `mint(promptHash, contentURI, actionData, actionSignature)`

Mints a new prompt and rewards the transaction sender.

**Parameters:**
- `promptHash` (bytes32): keccak256 hash of the prompt
- `contentURI` (string): Optional URI pointing to prompt metadata
- `actionData` (bytes): Encoded action parameters (amount, nonce, expiry)
- `actionSignature` (bytes): Authorization signature from PZERO Gateway

**Returns:** Transaction response

**Example:**
```typescript
const tx = await promptMiner.mint(
  ethers.keccak256(ethers.toUtf8Bytes(prompt)),
  '', // content URI (optional)
  actionData,
  signature
);
await tx.wait();
```

#### `mint(author, promptHash, contentURI, actionData, actionSignature)`

Mints a prompt with explicit author (different from transaction sender).

**Parameters:**
- `author` (address): Ethereum address to receive rewards
- `promptHash` (bytes32): keccak256 hash of the prompt
- `contentURI` (string): Optional URI pointing to prompt metadata
- `actionData` (bytes): Encoded action parameters
- `actionSignature` (bytes): Authorization signature

**Use Case:** Gasless transactions where relayer submits on behalf of user

#### `mintBatch(authors, promptHashes, contentURIs, actionData, actionSignatures)`

Batch mint multiple prompts in one transaction.

**Parameters:**
- `authors` (address[]): Array of author addresses
- `promptHashes` (bytes32[]): Array of prompt hashes
- `contentURIs` (string[]): Array of content URIs
- `actionData` (bytes[]): Array of encoded action data
- `actionSignatures` (bytes[]): Array of authorization signatures

**Returns:** Transaction response

#### `isPromptMinted(promptHash)`

Check if a prompt has already been minted.

**Parameters:**
- `promptHash` (bytes32): keccak256 hash of the prompt

**Returns:** `boolean` - true if minted, false otherwise

**Example:**
```typescript
const promptHash = ethers.keccak256(ethers.toUtf8Bytes(prompt));
const isMinted = await promptMiner.isPromptMinted(promptHash);

if (isMinted) {
  console.log('This prompt already exists on-chain');
}
```

#### `promptAuthor(promptHash)`

Get the author address of a minted prompt.

**Parameters:**
- `promptHash` (bytes32): keccak256 hash of the prompt

**Returns:** `address` - Author's Ethereum address

#### `promptInfo(promptHash)`

Get complete information about a minted prompt.

**Parameters:**
- `promptHash` (bytes32): keccak256 hash of the prompt

**Returns:** Struct with prompt metadata

#### `setActionSigner(newSigner)`

Update the authorized action signer address (PZERO signer).

**Parameters:**
- `newSigner` (address): New authorized signer address

**Access:** Only contract owner

#### `registerAsActiveDataPointChangeSigner(active)`

Register or unregister contract as active data point change signer.

**Parameters:**
- `active` (boolean): true to register, false to unregister

**Access:** Only contract owner

#### `migratePrompt(prompt, newDataPoint, targetContract)`

Migrate a prompt to another PromptMiner contract.

**Parameters:**
- `prompt` (string): Original prompt text
- `newDataPoint` (bytes32): New data point ID
- `targetContract` (address): Target PromptMiner contract address

**Use Case:** Upgrading reward mechanisms or changing data point configuration

### ActivityPoints Methods

Standard ERC-20 interface plus role-based access control.

#### ERC-20 Methods

```typescript
// Query balance
const balance = await activityPoints.balanceOf(userAddress);

// Transfer points
await activityPoints.transfer(recipient, amount);

// Approve spending
await activityPoints.approve(spender, amount);

// Check allowance
const allowance = await activityPoints.allowance(owner, spender);
```

#### Role Management

```typescript
// Grant MINTER_ROLE to PromptMiner
const MINTER_ROLE = await activityPoints.MINTER_ROLE();
await activityPoints.grantRole(MINTER_ROLE, promptMinerAddress);

// Check if address has role
const hasRole = await activityPoints.hasRole(MINTER_ROLE, address);

// Revoke role
await activityPoints.revokeRole(MINTER_ROLE, address);
```

### PromptDO Methods

Data object contract for storing prompt hashes.

```typescript
// Set DataIndex implementation for a data point
await promptDO.setDataIndexImplementation(dataPoint, dataIndexAddress);

// Read data
const value = await promptDO.read(dataPoint, key, defaultValue);

// Write data (requires allowlisted data manager)
await promptDO.write(dataPoint, key, value);
```

### Helper Functions

#### `getPromptHash(chainId, signerAddress, prompt, author, encodedActionData)`

Generate keccak256 hash for signing authorization.

**Parameters:**
- `chainId` (string): Blockchain chain ID
- `signerAddress` (string): PZERO signer address
- `prompt` (string): Full prompt text
- `author` (string): Author Ethereum address
- `encodedActionData` (string): Encoded action parameters

**Returns:** `string` - keccak256 hash

**Example:**
```typescript
import { getPromptHash } from '@project_zero/prompt-mining-sdk';

const hash = getPromptHash(
  '7208',
  '0xPZEROSignerAddress',
  'What is quantum computing?',
  '0xUserAddress',
  encodedActionData
);
```

#### `signMessage(signer, messageHash)`

Sign a message hash with a wallet.

**Parameters:**
- `signer` (Signer): ethers.js Signer instance
- `messageHash` (string): Hash to sign

**Returns:** `Promise<string>` - Signature

**Example:**
```typescript
import { signMessage } from '@project_zero/prompt-mining-sdk';

const signature = await signMessage(wallet, messageHash);
```

## Usage Examples

### Example 1: Basic Prompt Minting (Boilerplate Integration)

Typical usage within the `prompt-mining-boilerplate`:

```typescript
import { contractFactories } from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

async function mintPrompt(prompt: string, author: string, activityPoints: string) {
  // 1. Setup
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
  const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

  // 2. Connect to PromptMiner contract
  const promptMiner = contractFactories.PromptMinerWithActivityPoints.connect(
    process.env.PROMPT_MINER_ADDRESS,
    signer
  );

  // 3. Hash the prompt
  const promptHash = ethers.keccak256(ethers.toUtf8Bytes(prompt));

  // 4. Request authorization from PZERO Gateway
  const response = await fetch(`${process.env.PZERO_API_URL}/authorize/mint`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-pzero-api-key': process.env.PZERO_API_KEY,
    },
    body: JSON.stringify({
      promptHash,
      author,
      activityPoints,
      chainId: process.env.CHAIN_ID,
      timestamp: Date.now(),
    }),
  });

  const { authorization } = await response.json();
  const { signature, nonce, expiresAt } = authorization;

  // 5. Encode action data
  const actionData = ethers.AbiCoder.defaultAbiCoder().encode(
    ['uint256', 'uint256', 'uint256'],
    [activityPoints, nonce, expiresAt]
  );

  // 6. Submit transaction to blockchain
  const tx = await promptMiner.mint(promptHash, '', actionData, signature);
  const receipt = await tx.wait();

  return {
    transactionHash: receipt.hash,
    blockNumber: receipt.blockNumber,
    promptHash,
  };
}
```

### Example 2: Check if Prompt Already Exists

Prevent duplicate minting by checking if prompt is already on-chain:

```typescript
import { contractFactories } from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

async function checkPromptExists(prompt: string) {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);

  const promptMiner = contractFactories.PromptMinerWithActivityPoints.connect(
    process.env.PROMPT_MINER_ADDRESS,
    provider // Read-only, no signer needed
  );

  const promptHash = ethers.keccak256(ethers.toUtf8Bytes(prompt));
  const isMinted = await promptMiner.isPromptMinted(promptHash);

  if (isMinted) {
    const author = await promptMiner.promptAuthor(promptHash);
    console.log('Prompt already minted by:', author);
    return { exists: true, author };
  }

  return { exists: false };
}
```

### Example 3: Query User's Activity Points Balance

```typescript
import { contractFactories } from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

async function getUserActivityPoints(userAddress: string) {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);

  const activityPoints = contractFactories.ActivityPoints.connect(
    process.env.ACTIVITY_POINTS_ADDRESS,
    provider
  );

  const balance = await activityPoints.balanceOf(userAddress);
  const decimals = await activityPoints.decimals();

  // Format balance (activity points typically use 18 decimals)
  const formattedBalance = ethers.formatUnits(balance, decimals);

  console.log(`User ${userAddress} has ${formattedBalance} activity points`);

  return {
    raw: balance.toString(),
    formatted: formattedBalance,
  };
}
```

### Example 4: Batch Minting Multiple Prompts

Mint multiple prompts in a single transaction:

```typescript
import { contractFactories } from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

async function mintMultiplePrompts(
  prompts: string[],
  authors: string[],
  activityPointsArray: string[]
) {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
  const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

  const promptMiner = contractFactories.PromptMinerWithActivityPoints.connect(
    process.env.PROMPT_MINER_ADDRESS,
    signer
  );

  // Hash all prompts
  const promptHashes = prompts.map(p => ethers.keccak256(ethers.toUtf8Bytes(p)));

  // Get authorizations from PZERO Gateway (in parallel)
  const authorizations = await Promise.all(
    prompts.map((_, i) =>
      getPZEROAuthorization(promptHashes[i], authors[i], activityPointsArray[i])
    )
  );

  // Encode action data for each prompt
  const actionDataArray = authorizations.map((auth, i) =>
    ethers.AbiCoder.defaultAbiCoder().encode(
      ['uint256', 'uint256', 'uint256'],
      [activityPointsArray[i], auth.nonce, auth.expiresAt]
    )
  );

  const signatures = authorizations.map(auth => auth.signature);
  const contentURIs = prompts.map(() => ''); // Empty content URIs

  // Batch mint
  const tx = await promptMiner.mintBatch(
    authors,
    promptHashes,
    contentURIs,
    actionDataArray,
    signatures
  );

  const receipt = await tx.wait();
  console.log(`Minted ${prompts.length} prompts in transaction ${receipt.hash}`);

  return receipt;
}
```

### Example 5: Meta-Transactions (Gasless for Users)

Enable users to mint prompts without paying gas fees:

```typescript
import {
  contractFactories,
  getTypedDataForMetaTxMint,
  buildRequest,
} from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

async function mintWithMetaTransaction(
  userSigner: ethers.Signer, // User's wallet (signs message only, no gas)
  relayerSigner: ethers.Signer, // Relayer's wallet (pays gas)
  prompt: string,
  activityPoints: string
) {
  const provider = relayerSigner.provider;

  // Connect contracts
  const promptMiner = contractFactories.PromptMinerWithActivityPoints.connect(
    process.env.PROMPT_MINER_ADDRESS,
    provider
  );

  const erc2771Forwarder = new ethers.Contract(
    process.env.TRUSTED_FORWARDER_ADDRESS,
    ERC2771ForwarderABI,
    relayerSigner
  );

  // Get authorization from PZERO Gateway
  const promptHash = ethers.keccak256(ethers.toUtf8Bytes(prompt));
  const userAddress = await userSigner.getAddress();
  const { signature: actionSignature, nonce, expiresAt } = await getPZEROAuthorization(
    promptHash,
    userAddress,
    activityPoints
  );

  const actionData = ethers.AbiCoder.defaultAbiCoder().encode(
    ['uint256', 'uint256', 'uint256'],
    [activityPoints, nonce, expiresAt]
  );

  // Prepare meta-transaction typed data
  const typedData = await getTypedDataForMetaTxMint(
    promptMiner,
    userSigner,
    userAddress, // from
    500000, // gas limit
    Math.floor(Date.now() / 1000) + 3600, // deadline (1 hour)
    promptHash,
    '', // content URI
    actionData,
    actionSignature
  );

  // User signs typed data (no gas cost)
  const forwardSignature = await userSigner.signTypedData(
    typedData.domain,
    typedData.types,
    typedData.requestForSigning
  );

  // Build forward request
  const forwardRequest = await buildRequest(
    typedData.requestForSigning,
    forwardSignature,
    userSigner
  );

  // Relayer executes transaction (pays gas)
  const tx = await erc2771Forwarder.execute(forwardRequest);
  const receipt = await tx.wait();

  console.log('Meta-transaction executed! User paid no gas.');
  return receipt;
}
```

## Deployment Helpers

The SDK includes helpers for deploying contracts. These are primarily for testing and custom deployments.

**Note:** Production contracts are deployed and managed by the PZERO team.

### Deploy Complete System

```typescript
import {
  deployPromptDO,
  deployActivityPoints,
  deployPromptMinerWithActivityPoints,
} from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

async function deployCompleteSystem() {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
  const deployer = new ethers.Wallet(process.env.DEPLOYER_PRIVATE_KEY, provider);

  console.log('Deploying contracts...');

  // 1. Deploy PromptDO (data object storage)
  const promptDOResult = await deployPromptDO(deployer);
  console.log('PromptDO deployed at:', promptDOResult.address);

  // 2. Deploy ActivityPoints (ERC-20 rewards token)
  const activityPointsResult = await deployActivityPoints(deployer);
  console.log('ActivityPoints deployed at:', activityPointsResult.address);

  // 3. Deploy PromptMiner
  const config = {
    dataIndexAddress: process.env.DATA_INDEX_ADDRESS,
    dataPoint: process.env.DATA_POINT_ID,
    promptDOAddress: promptDOResult.address,
    actionSigner: process.env.PZERO_SIGNER_ADDRESS,
    activityPointsAddress: activityPointsResult.address,
  };

  const promptMinerResult = await deployPromptMinerWithActivityPoints(
    deployer,
    process.env.TRUSTED_FORWARDER_ADDRESS,
    config
  );
  console.log('PromptMiner deployed at:', promptMinerResult.address);

  // 4. Grant MINTER_ROLE to PromptMiner
  const MINTER_ROLE = await activityPointsResult.contract.MINTER_ROLE();
  await activityPointsResult.contract.grantRole(MINTER_ROLE, promptMinerResult.address);
  console.log('Granted MINTER_ROLE to PromptMiner');

  return {
    promptDO: promptDOResult,
    activityPoints: activityPointsResult,
    promptMiner: promptMinerResult,
  };
}
```

### Deploy Other Variants

```typescript
import {
  deployPromptMinerWithERC20Transfer,
  deployPromptMinerWithMintTo,
} from '@project_zero/prompt-mining-sdk';

// ERC20 Transfer variant
const config1 = {
  dataIndexAddress: DATA_INDEX_ADDRESS,
  dataPoint: DATA_POINT_ID,
  promptDOAddress: PROMPT_DO_ADDRESS,
  actionSigner: PZERO_SIGNER_ADDRESS,
  actionToken: USDC_TOKEN_ADDRESS, // Token to transfer as rewards
};

const erc20TransferResult = await deployPromptMinerWithERC20Transfer(
  deployer,
  config1,
  TRUSTED_FORWARDER_ADDRESS
);

// MintTo variant
const config2 = {
  dataIndexAddress: DATA_INDEX_ADDRESS,
  dataPoint: DATA_POINT_ID,
  promptDOAddress: PROMPT_DO_ADDRESS,
  actionSigner: PZERO_SIGNER_ADDRESS,
  tokenAddress: NFT_TOKEN_ADDRESS, // Token to mint
};

const mintToResult = await deployPromptMinerWithMintTo(
  deployer,
  config2,
  TRUSTED_FORWARDER_ADDRESS
);
```

## Integration with Boilerplate

The `prompt-mining-boilerplate` repository is a template that customers clone to integrate prompt mining into their LLM services. It uses this SDK for all blockchain interactions.

### Typical Boilerplate Integration Pattern

```typescript
// File: prompt-mining-boilerplate/src/services/blockchain.service.ts

import { contractFactories } from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

class BlockchainService {
  private promptMiner: any;
  private activityPoints: any;
  private provider: ethers.Provider;
  private signer: ethers.Signer;

  constructor() {
    this.provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
    this.signer = new ethers.Wallet(process.env.PRIVATE_KEY, this.provider);

    this.promptMiner = contractFactories.PromptMinerWithActivityPoints.connect(
      process.env.PROMPT_MINER_ADDRESS,
      this.signer
    );

    this.activityPoints = contractFactories.ActivityPoints.connect(
      process.env.ACTIVITY_POINTS_ADDRESS,
      this.provider
    );
  }

  async mintPrompt(
    promptHash: string,
    actionData: string,
    actionSignature: string
  ) {
    const tx = await this.promptMiner.mint(
      promptHash,
      '', // content URI
      actionData,
      actionSignature
    );
    return await tx.wait();
  }

  async isPromptMinted(promptHash: string): Promise<boolean> {
    return await this.promptMiner.isPromptMinted(promptHash);
  }

  async getUserBalance(userAddress: string): Promise<string> {
    const balance = await this.activityPoints.balanceOf(userAddress);
    return ethers.formatEther(balance);
  }
}

export default new BlockchainService();
```

### Boilerplate Environment Configuration

The boilerplate requires these environment variables:

```env
# Blockchain Configuration
RPC_URL=https://rpc.nexera.network
CHAIN_ID=7208
PRIVATE_KEY=0x...

# Contract Addresses
PROMPT_MINER_ADDRESS=0x...
ACTIVITY_POINTS_ADDRESS=0x...
PROMPT_DO_ADDRESS=0x...
DATA_INDEX_ADDRESS=0x...

# PZERO API Integration
PZERO_API_URL=https://api.projectzero.io/v1
PZERO_API_KEY=pzero_live_...
PZERO_CLIENT_ID=your-company-id
```

## Manual Testing & Development

### Local Hardhat Network Testing

```typescript
import { ethers } from 'hardhat';
import {
  contractFactories,
  deployPromptDO,
  deployActivityPoints,
  deployPromptMinerWithActivityPoints,
} from '@project_zero/prompt-mining-sdk';

describe('PromptMiner Local Testing', () => {
  let promptMiner: any;
  let activityPoints: any;
  let deployer: any;
  let user: any;

  before(async () => {
    [deployer, user] = await ethers.getSigners();

    // Deploy contracts
    const promptDOResult = await deployPromptDO(deployer);
    const activityPointsResult = await deployActivityPoints(deployer);

    const config = {
      dataIndexAddress: DATA_INDEX_ADDRESS,
      dataPoint: DATA_POINT_ID,
      promptDOAddress: promptDOResult.address,
      actionSigner: deployer.address, // Use deployer as signer for testing
      activityPointsAddress: activityPointsResult.address,
    };

    const promptMinerResult = await deployPromptMinerWithActivityPoints(
      deployer,
      FORWARDER_ADDRESS,
      config
    );

    // Grant MINTER_ROLE
    const MINTER_ROLE = await activityPointsResult.contract.MINTER_ROLE();
    await activityPointsResult.contract.grantRole(
      MINTER_ROLE,
      promptMinerResult.address
    );

    promptMiner = promptMinerResult.contract;
    activityPoints = activityPointsResult.contract;
  });

  it('should mint a prompt and reward user', async () => {
    const prompt = 'What is blockchain?';
    const promptHash = ethers.keccak256(ethers.toUtf8Bytes(prompt));
    const activityPointsAmount = ethers.parseEther('10');

    // Generate signature (in testing, use deployer as signer)
    const actionData = ethers.AbiCoder.defaultAbiCoder().encode(
      ['uint256', 'uint256', 'uint256'],
      [activityPointsAmount, 1, Math.floor(Date.now() / 1000) + 3600]
    );

    const messageHash = ethers.solidityPackedKeccak256(
      ['bytes32', 'address', 'uint256', 'uint256', 'uint256', 'uint256'],
      [promptHash, user.address, activityPointsAmount, 1, Math.floor(Date.now() / 1000) + 3600, 31337]
    );

    const signature = await deployer.signMessage(ethers.getBytes(messageHash));

    // Mint
    const tx = await promptMiner.connect(user).mint(
      promptHash,
      '',
      actionData,
      signature
    );
    await tx.wait();

    // Verify
    const isMinted = await promptMiner.isPromptMinted(promptHash);
    expect(isMinted).to.be.true;

    const balance = await activityPoints.balanceOf(user.address);
    expect(balance).to.equal(activityPointsAmount);
  });
});
```

### Testing with Nexera Testnet

```typescript
import { contractFactories } from '@project_zero/prompt-mining-sdk';
import { ethers } from 'ethers';

// Connect to Nexera testnet
const provider = new ethers.JsonRpcProvider('https://rpc.testnet.nexera.network');
const signer = new ethers.Wallet(process.env.TESTNET_PRIVATE_KEY, provider);

// Use deployed testnet contracts
const promptMiner = contractFactories.PromptMinerWithActivityPoints.connect(
  '0xTestnetPromptMinerAddress',
  signer
);

// Test minting
const promptHash = ethers.keccak256(ethers.toUtf8Bytes('Test prompt'));

// Get authorization from PZERO Gateway (testnet endpoint)
// ... obtain signature ...

const tx = await promptMiner.mint(promptHash, '', actionData, signature);
console.log('Transaction hash:', tx.hash);

const receipt = await tx.wait();
console.log('Minted at block:', receipt.blockNumber);
```

## Advanced Topics

### Role-Based Access Control

All contracts use OpenZeppelin's AccessControl for role management.

**Common Roles:**
- `DEFAULT_ADMIN_ROLE`: Contract owner, can grant/revoke all roles
- `MINTER_ROLE` (ActivityPoints): Can mint activity points to users
- Custom roles per contract variant

**Managing Roles:**
```typescript
// Get role identifier
const MINTER_ROLE = await activityPoints.MINTER_ROLE();

// Grant role
await activityPoints.grantRole(MINTER_ROLE, promptMinerAddress);

// Check role
const hasRole = await activityPoints.hasRole(MINTER_ROLE, address);

// Revoke role
await activityPoints.revokeRole(MINTER_ROLE, address);

// Renounce role (self-remove)
await activityPoints.renounceRole(MINTER_ROLE, await signer.getAddress());
```

### Upgradeable Proxy Pattern

All contracts are deployed using OpenZeppelin's TransparentUpgradeableProxy pattern:

- **Proxy Contract**: Holds state and delegates calls to implementation
- **Implementation Contract**: Contains logic, can be upgraded
- **ProxyAdmin**: Manages upgrades

**Benefits:**
- Upgrade contract logic without losing state
- Fix bugs or add features post-deployment
- Maintain same contract address

**Upgrade Process** (handled by PZERO team):
1. Deploy new implementation contract
2. Call `ProxyAdmin.upgrade(proxy, newImplementation)`
3. State is preserved, logic is updated

### ERC-2771 Meta-Transactions

Meta-transactions allow users to interact without paying gas fees.

**How it works:**
1. User signs EIP-712 typed data (free)
2. Relayer submits transaction to `ERC2771Forwarder` (pays gas)
3. Forwarder verifies signature and forwards call to target contract
4. Target contract extracts original sender from calldata

**SDK Support:**
```typescript
import { getTypedDataForMetaTxMint, buildRequest } from '@project_zero/prompt-mining-sdk';

// Prepare typed data
const typedData = await getTypedDataForMetaTxMint(
  promptMiner,
  userSigner,
  from,
  gas,
  deadline,
  promptHash,
  contentURI,
  actionData,
  actionSignature
);

// User signs (no gas)
const forwardSig = await userSigner.signTypedData(
  typedData.domain,
  typedData.types,
  typedData.requestForSigning
);

// Build and execute via relayer
const request = await buildRequest(typedData.requestForSigning, forwardSig, userSigner);
await forwarder.connect(relayer).execute(request);
```

### ERC-7208 Data Points

Data Points provide multi-tenant data isolation:

**Architecture:**
- `DataPointRegistry`: Allocates data point IDs
- `DataIndex`: Manages allowlisted data managers per data point
- `PromptDO`: Stores data indexed by data point

**Setup:**
```typescript
// 1. Allocate data point (done by PZERO)
await dataPointRegistry.allocate(customerAddress);

// 2. Allowlist PromptMiner for the data point
await dataIndex.allowDataManager(dataPointId, promptMinerAddress);

// 3. Set DataIndex implementation in PromptDO
await promptDO.setDataIndexImplementation(dataPointId, dataIndexAddress);
```

**Benefits:**
- Multiple customers share same contracts
- Data is isolated by data point
- Fine-grained access control

### Signature Verification Logic

Smart contracts verify signatures using the following format:

```solidity
bytes32 message = keccak256(abi.encodePacked(
  promptHash,
  author,
  activityPoints,
  nonce,
  expiresAt,
  chainId
));

bytes32 ethSignedMessage = keccak256(abi.encodePacked(
  "\x19Ethereum Signed Message:\n32",
  message
));

address recoveredSigner = ecrecover(ethSignedMessage, v, r, s);
require(recoveredSigner == actionSigner, "Invalid signature");
require(block.timestamp <= expiresAt, "Signature expired");
```

**SDK Helper:**
```typescript
import { getPromptHash, signMessage } from '@project_zero/prompt-mining-sdk';

const messageHash = getPromptHash(
  chainId,
  signerAddress,
  prompt,
  author,
  encodedActionData
);

const signature = await signMessage(wallet, messageHash);
```

## Troubleshooting

### "Signature verification failed"

**Causes:**
- Signature not from authorized PZERO signer
- Signature expired (check `expiresAt` timestamp)
- Incorrect `actionData` encoding
- Wrong `chainId` parameter

**Solution:**
- Verify PZERO signer address matches contract's `actionSigner`
- Check signature hasn't expired (default: 15 minutes)
- Ensure `actionData` encoding matches signature parameters exactly
- Use correct chain ID (7208 for mainnet, 72080 for testnet)

### "PromptMiner not allowlisted"

**Cause:** PromptMiner contract not allowlisted in DataIndex for the data point.

**Solution:**
- Contact PZERO team to allowlist PromptMiner
- Verify with: `await dataIndex.isAllowedDataManager(dataPointId, promptMinerAddress)`

### "Insufficient gas" or "Transaction reverted"

**Causes:**
- Insufficient gas limit
- Insufficient funds in wallet
- Reverted due to require statement

**Solutions:**
- Estimate gas first: `await promptMiner.mint.estimateGas(...)`
- Add 20% buffer to estimated gas
- Check wallet balance: `await provider.getBalance(address)`
- Use try-catch to get revert reason:

```typescript
try {
  const tx = await promptMiner.mint(...);
  await tx.wait();
} catch (error: any) {
  console.error('Transaction failed:', error.reason || error.message);
}
```

### "Nonce already used" or "Prompt already minted"

**Causes:**
- Prompt hash already exists on-chain
- Duplicate transaction submission

**Solution:**
- Check if minted first: `await promptMiner.isPromptMinted(promptHash)`
- Use idempotent API calls (PZERO Gateway handles this)

### TypeScript Type Errors

**Cause:** Missing or incorrect TypeScript types.

**Solution:**
- Ensure TypeScript >= 4.5.0
- Install `@types/node` if missing
- Check tsconfig.json includes proper compiler options
- Regenerate types if contracts changed

## TypeScript Type Definitions

The SDK includes full TypeScript type definitions via TypeChain.

```typescript
import type {
  PromptMinerWithActivityPointsActionUpgradeable,
  ActivityPoints,
  PromptDO,
} from '@project_zero/prompt-mining-sdk';

// Strongly typed contract instances
const promptMiner: PromptMinerWithActivityPointsActionUpgradeable =
  contractFactories.PromptMinerWithActivityPoints.connect(address, signer);

const activityPoints: ActivityPoints =
  contractFactories.ActivityPoints.connect(address, signer);

const promptDO: PromptDO =
  contractFactories.PromptDO.connect(address, signer);
```

**Type-Safe Method Calls:**
```typescript
// TypeScript knows exact parameter types and return types
const balance: bigint = await activityPoints.balanceOf(userAddress);
const isMinted: boolean = await promptMiner.isPromptMinted(promptHash);
```

## Contract Addresses

Smart contracts are deployed on **Nexera Network**.

### Mainnet (Chain ID: 7208)

Production contract addresses will be provided by PZERO team upon deployment.

**Typical Configuration:**
```env
CHAIN_ID=7208
RPC_URL=https://rpc.nexera.network
PROMPT_MINER_ADDRESS=0x... # Provided by PZERO
ACTIVITY_POINTS_ADDRESS=0x... # Provided by PZERO
PROMPT_DO_ADDRESS=0x... # Provided by PZERO
DATA_INDEX_ADDRESS=0x... # Provided by PZERO
```

### Testnet (Chain ID: 72080)

Test contracts available for integration testing.

**Typical Configuration:**
```env
CHAIN_ID=72080
RPC_URL=https://rpc.testnet.nexera.network
# Contact PZERO for testnet contract addresses
```

**Note:** Smart contract source code will be open-sourced after security audit is complete.

## Support & Resources

### Getting Started

1. **Register for PZERO API Access**: [gateway.projectzero.io/register](https://gateway.projectzero.io/register)
   - Receive `PZERO_API_KEY` and `PZERO_CLIENT_ID`
   - Choose tier (Free: 1K/month, Pro: 100K/month)

2. **Clone Boilerplate Template**: `prompt-mining-boilerplate` repository (public)
   - Ready-to-use integration template
   - Frontend examples included
   - Complete documentation

3. **Install SDK**: `npm install @project_zero/prompt-mining-sdk ethers@^6.13.5`

### Documentation

- **Boilerplate Documentation**: See `prompt-mining-boilerplate` README
- **API Gateway Documentation**: See `prompt-mining-api-gateway` README
- **PZERO Docs**: [docs.projectzero.io](https://docs.projectzero.io) (if available)

### Support Channels

- **Email**: support@projectzero.io
- **Issues**: For SDK bugs, contact PZERO support
- **Integration Help**: Consult boilerplate documentation and examples

## Contributing

This SDK is maintained by the PZERO team. For bug reports or feature requests, please contact support@projectzero.io.

## License

MIT License

Copyright (c) 2025 Project Zero

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

---

**Built with TypeChain for type-safe smart contract interactions.**