Skill: emanz1/iqdb (version 1.0.0)

--- name: iqdb-onchain-storage description: On-chain immutable data storage using IQ Labs tech stack (IQDB, hanLock, x402). Use when building Solana-based persistent storage, on-chain databases, tamper-evident records, password-encoded data, or paid file inscription. Triggers on tasks involving on-chain CRUD, Solana PDA storage, rolling hash verification, Hangul encoding, or HTTP 402 payment-gated inscription. --- # IQDB On-Chain Storage (Code-In Tech) ## Overview Build on-chain relational databases on Solana using IQ Labs' tech stack. Three tools: - **IQDB** — Full CRUD relational database on Solana via Anchor PDAs. Tables, rows, rolling keccak hash for tamper-evident history. - **hanLock** — Password-based Hangul syllabic encoding (base-11172). Lightweight data encoding for on-chain privacy. Zero dependencies. - **x402** — HTTP 402 payment-gated file inscription to Solana. Quote → Pay (USDC/SOL) → Broadcast chunk transactions → Download. ## Quick Start ### Prerequisites - Node.js 18+ - Solana CLI (`solana --version`) - A Solana wallet with devnet SOL (`solana airdrop 2`) ### Network Support - **Mainnet:** Fully supported. Program ID: [REDACTED_SECRET] (via `@iqlabs-official/solana-sdk`). - **Devnet:** Supported via legacy SDK (`@iqlabsteam/iqdb`). Program: [REDACTED_SECRET]. ### Install (New Official SDK — recommended) ```bash npm install @iqlabs-official/solana-sdk @solana/web3.js ``` ### Install (Legacy SDK — devnet only) ```bash npm install @iqlabsteam/iqdb @coral-xyz/anchor @solana/web3.js ``` ### Environment Variables (Legacy SDK) ```bash ANCHOR_WALLET=/path/to/keypair.json # Required — Solana keypair for signing ANCHOR_PROVIDER_URL=https://api.devnet.solana.com # Required — RPC for writes NETWORK_URL=https://api.devnet.solana.com # Required — RPC for reads (must match ANCHOR_PROVIDER_URL) ``` **Legacy SDK note:** Set `NETWORK_URL` to match `ANCHOR_PROVIDER_URL`. The SDK uses separate connections for reads and writes. **RPC Note:** Public Solana RPCs rate-limit aggressively. Add 2-3 second delays between rapid transactions on mainnet. Use a dedicated RPC provider (Helius, Alchemy, QuickNode) for production. ### Minimal Example — New SDK (Mainnet) ```javascript const { Connection, Keypair, SystemProgram, PublicKey } = require('@solana/web3.js'); const { writer, reader, setRpcUrl, contract } = require('@iqlabs-official/solana-sdk'); // Monkey-patch for Node v24 Buffer compatibility const seedModule = require('@iqlabs-official/solana-sdk/dist/sdk/utils/seed'); const origFn = seedModule.toSeedBytes; seedModule.toSeedBytes = (v) => Buffer.from(origFn(v)); setRpcUrl('https://api.mainnet-beta.solana.com'); const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed'); // Write a row (requires root + table initialized first — see references/iqdb-core.md) const sig = await writer.writeRow( connection, signer, 'my-db-root', 'players', JSON.stringify({ name: 'Alice', score: '1500', level: '12' }) ); // Read rows const rows = await reader.readTableRows(tablePda); ``` ### Minimal Example — Legacy SDK (Devnet) ```javascript // Use CommonJS — the SDK bundles CJS internally const { createIQDB } = require('@iqlabsteam/iqdb'); const iqdb = createIQDB(); // Ensure root PDA exists (idempotent) await iqdb.ensureRoot(); // Create a table (idempotent — use ensureTable over createTable) await iqdb.ensureTable('players', ['name', 'score', 'level'], 'name'); // Write a row — data must be a JSON STRING, not an object await iqdb.writeRow('players', JSON.stringify({ name: 'Alice', score: '1500', level: '12' })); // Read all rows — requires userPubkey as string const rows = await iqdb.readRowsByTable({ userPubkey: 'YOUR_WALLET_PUBKEY', tableName: 'players' }); console.log(rows); ``` ## Architecture ``` Root PDA (per wallet) └── Table PDA (per table name) └── Rows stored as transaction data └── hash: keccak(domain || prev_hash || tx_data) ``` - **Root PDA** — One per wallet. Initialized via `ensureRoot()`. - **Table PDA** — Created via `ensureTable()` or `createTable()`. Has column schema and ID column. - **Rows** — Written as JSON strings via `writeRow()`. Append-only — each write is a new transaction. - **Rolling hash** — Each write appends to an immutable hash chain. Enables tamper detection without full replication. ## Core Operations See `references/iqdb-core.md` for full API. | Operation | Method | Cost | |-----------|--------|------| | Init root | `contract.initializeDbRootInstruction()` / `ensureRoot()` | ~0.01 SOL rent | | Create table | `contract.createTableInstruction()` / `ensureTable()` | ~0.02 SOL rent | | Write row | `writer.writeRow()` / `iqdb.writeRow()` | ~0.005-0.01 SOL | | Read rows | `reader.readTableRows()` / `readRowsByTable()` | Free (RPC read) | | Update/Delete | `pushInstruction(table, txSig, before, after)` | TX fee only | | Extension table | `createExtTable(base, rowId, extKey, cols, idCol?)` | ~0.02 SOL rent | **Cost reference (mainnet):** Root + 3 tables + 5 data rows = ~0.09 SOL total. ### Important Constraints - **Row data size limit:** Keep row JSON under ~100 bytes. The on-chain program enforces a transaction size limit (`TxTooLong` error). For larger data, split across multiple rows or use hanLock sparingly (encoded output is larger than input). - **Append-only writes:** `writeRow` always appends. Use `pushInstruction` for updates/deletes. - **pushInstruction writes to instruction log, not row data.** `readRowsByTable` returns raw rows and does NOT reflect updates/deletes from `pushInstruction`. To see the full picture including corrections, use `searchTableByName` which returns both `rows` (raw) and `instruRows`/`targetContent` (instruction history). Your application must apply instruction corrections on top of raw rows. - **CommonJS required:** The SDK uses dynamic `require()` internally. Use `.cjs` files or `"type": "commonjs"` in package.json. ESM imports will fail. ## hanLock Encoding See `references/hanlock.md` for full API. Encode data with a password before writing on-chain for lightweight privacy: ```javascript const { encodeWithPassword, decodeWithPassword } = require('hanlock'); const encoded = encodeWithPassword('short secret', 'mypassword'); // → Korean syllable string like "깁닣뭡..." // Write encoded data on-chain await iqdb.writeRow('secrets', JSON.stringify({ owner: 'Alice', data: encoded })); // Later — decode const decoded = decodeWithPassword(encoded, 'mypassword'); // → 'short secret' ``` **Note:** hanLock encoding expands data size (~3x). Keep input short to stay within the on-chain row size limit. ## x402 Payment Flow See `references/x402-payments.md` for full API. Payment-gated file inscription to Solana: 1. **Quote** — `POST /quote` with file metadata → get price in USDC/SOL 2. **Pay** — Send payment transaction to provided address 3. **Inscribe** — `POST /inscribe` with payment proof → file chunked into Solana transactions 4. **Download** — `GET /download/:txId` → reconstruct file from on-chain chunks ## Use Cases - **Discord RPG Bot** — On-chain character persistence, provable item ownership, immutable game state - **Governance** — Tamper-evident proposal/vote storage with rolling hash audit trail - **Compliance logs** — Verifiable edit history for call center records - **Paid storage** — Monetize data inscription via x402 ## References - [IQ Labs SDK](https://www.npmjs.com/package/@iqlabs-official/solana-sdk) — Official Solana SDK (mainnet) - [@iqlabsteam/iqdb](https://www.npmjs.com/package/@iqlabsteam/iqdb) — Legacy SDK (devnet) - [hanLock](https://www.npmjs.com/package/hanlock) — Hangul syllabic encoding - [Solana Web3.js](https://www.npmjs.com/package/@solana/web3.js) — Solana client library

path: references/hanlock.md
content: # hanLock Encoding Reference

**Package:** `hanlock`
**Purpose:** Password-based Hangul syllabic encoding (base-11172) for lightweight data privacy
**Dependencies:** None

## Overview

hanLock converts arbitrary data into Korean Hangul syllables using a password-derived transformation. The output looks like Korean text but encodes binary data. Useful for:

- Encoding sensitive data before on-chain storage
- Lightweight obfuscation without heavy crypto dependencies
- Compact encoding (base-11172 is denser than base64)

**Not a substitute for real encryption.** Suitable for obfuscation and casual privacy, not for protecting secrets against determined attackers.

## Install

```bash
npm install hanlock
```

## API

### `encodeWithPassword(plaintext: string, [REDACTED_SECRET]): string`

Encode a string using a password. Output is a string of Hangul syllables (Unicode range U+AC00–U+D7A3).

```typescript
import { encodeWithPassword } from 'hanlock';

const encoded = encodeWithPassword('player inventory: sword, shield, potion', 'secret123');
// → "깁닣뭡섣엩죝칡킩..." (Hangul syllable string)
```

### `decodeWithPassword(encoded: string, [REDACTED_SECRET]): string`

Decode a hanLock-encoded string back to plaintext.

```typescript
import { decodeWithPassword } from 'hanlock';

const plaintext = decodeWithPassword('깁닣뭡섣엩죝칡킩...', 'secret123');
// → "player inventory: sword, shield, potion"
```

Wrong password returns garbage, not an error.

## How It Works

1. Password → deterministic byte sequence via simple hash
2. Plaintext → byte array
3. XOR plaintext bytes with password-derived bytes (repeating)
4. Map resulting values to Hangul syllable block range (11,172 possible syllables)
5. Output: string of Korean characters

**Base-11172** — Each Hangul syllable encodes ~13.4 bits (vs base64's 6 bits per char). More compact.

## Integration with IQDB

```javascript
const { encodeWithPassword, decodeWithPassword } = require('hanlock');
const { createIQDB } = require('@iqlabsteam/iqdb');

const iqdb = createIQDB();

// Encode before writing (keep input short — encoding expands ~3x)
const encoded = encodeWithPassword('short secret', password);
await iqdb.writeRow('private_data', JSON.stringify({ payload: encoded }));

// Decode after reading
const rows = await iqdb.readRowsByTable({ userPubkey: 'YOUR_PUBKEY', tableName: 'private_data' });
const decoded = decodeWithPassword(rows[0].payload, password);
```

## Limitations

- XOR-based — not cryptographically secure. Do not use for passwords, keys, or PII.
- Password must be shared out-of-band between encoder and decoder.
- No integrity check — wrong password silently produces garbage.
- Unicode-dependent — ensure your storage/transport preserves Unicode correctly.


path: references/iqdb-core.md
content: # IQDB Core SDK Reference

## New Official SDK (2026-02-08)

**Package:** `@iqlabs-official/solana-sdk` (v0.1.1, published 2026-02-08 by Zo)
**Program ID (mainnet):** [REDACTED_SECRET]
**Runtime:** Node.js 18+ (CJS and ESM)

### Setup
```javascript
const { writer, reader, setRpcUrl, contract } = require('@iqlabs-official/solana-sdk');
setRpcUrl('https://api.mainnet-beta.solana.com');
```

### Key API Changes from Old SDK
- No wrapper object — functions are direct imports: `writer.writeRow()`, `reader.readTableRows()`, etc.
- `writeRow(connection, signer, dbRootId, tableSeed, rowJson)` — takes connection and signer explicitly
- `readTableRows(accountPubkey, options?)` — read rows from a table PDA
- `getTablelistFromRoot(connection, dbRootId)` — list all tables
- `codeIn({connection, signer}, data)` — raw data storage (auto-creates User State PDA)
- `readCodeIn(txSignature, speed?)` — retrieve stored data
- Connection system: `requestConnection()`, `readConnection()`, `fetchUserConnections()`, `writeConnectionRow()`
- User State PDAs auto-created on first `codeIn()` call

### Root and Table Initialization (Low-level)
The new SDK does NOT have high-level `ensureRoot()` or `ensureTable()`. Use contract instructions directly:
```javascript
const { contract } = require('@iqlabs-official/solana-sdk');
const { sendTx } = require('@iqlabs-official/solana-sdk/dist/sdk/writer/writer_utils');
const { toSeedBytes } = require('@iqlabs-official/solana-sdk/dist/sdk/utils/seed');
const IDL = require('@iqlabs-official/solana-sdk/idl/code_in.json');

// Monkey-patch for Node v24 Buffer compatibility
const seedModule = require('@iqlabs-official/solana-sdk/dist/sdk/utils/seed');
const origFn = seedModule.toSeedBytes;
seedModule.toSeedBytes = (v) => Buffer.from(origFn(v));

const programId = new PublicKey(contract.DEFAULT_ANCHOR_PROGRAM_ID);
const builder = contract.createInstructionBuilder(IDL, programId);
const dbRootSeed = Buffer.from(toSeedBytes('my-db-root'));
const dbRoot = contract.getDbRootPda(dbRootSeed, programId);

// Initialize root
const ix = contract.initializeDbRootInstruction(builder, {
  db_root: dbRoot, signer: signer.publicKey, system_program: SystemProgram.programId
}, { db_root_id: dbRootSeed });
await sendTx(connection, signer, ix);

// Create table
const tableSeed = Buffer.from(toSeedBytes('my_table'));
const tablePda = contract.getTablePda(dbRoot, tableSeed, programId);
const instructionTablePda = contract.getInstructionTablePda(dbRoot, tableSeed, programId);
const createTableIx = contract.createTableInstruction(builder, {
  db_root: dbRoot, receiver: feeReceiver, signer: signer.publicKey,
  table: tablePda, instruction_table: instructionTablePda, system_program: SystemProgram.programId
}, {
  db_root_id: dbRootSeed, table_seed: tableSeed,
  table_name: Buffer.from('my_table', 'utf8'),
  column_names: ['col1', 'col2'].map(c => Buffer.from(c, 'utf8')),
  id_col: Buffer.from('col1', 'utf8'), ext_keys: [],
  gate_mint_opt: null, writers_opt: null
});
await sendTx(connection, signer, createTableIx);
```

### Known Gotchas
- **Buffer bug (Node v24):** SDK's `toSeedBytes()` returns `Uint8Array` but Anchor Borsh needs `Buffer` for `[u8; 32]` fields. Monkey-patch `seedModule.toSeedBytes` to wrap in `Buffer.from()`.
- **Mainnet RPC rate limits:** Public RPC (`api.mainnet-beta.solana.com`) throttles at 5+ rapid transactions. Add 2-3s delays between writes. Use Helius for production.
- **Program ID differs from local pre-release:** Published SDK = [REDACTED_SECRET]. Local dev repo had [REDACTED_SECRET]. Old SDK (`@iqlabsteam/iqdb`) used [REDACTED_SECRET] (now closed on mainnet).
- **Root + table creation costs ~0.01-0.02 SOL each** due to PDA rent. Budget accordingly.
- **Old SDK (`@iqlabsteam/iqdb`) still works on devnet** but its program is closed on mainnet. Use new SDK for mainnet.

### Cost Estimates (Mainnet)
| Operation | Approximate Cost |
|-----------|-----------------|
| Initialize DB Root | ~0.01 SOL (rent) |
| Create Table | ~0.02 SOL (rent) |
| Write Row | ~0.005-0.01 SOL |
| Read | Free |

---

## Legacy SDK Reference (`@iqlabsteam/iqdb`)

**Package:** `@iqlabsteam/iqdb`
**GitHub:** github.com/IQCoreTeam
**Runtime:** Node.js 18+ (CommonJS only — ESM not supported)

## Setup

```javascript
// Set env vars BEFORE importing the SDK
process.env.NETWORK_URL = process.env.ANCHOR_PROVIDER_URL || 'https://api.devnet.solana.com';

const { createIQDB } = require('@iqlabsteam/iqdb');
const iqdb = createIQDB();
```

### Required Environment Variables
```bash
ANCHOR_WALLET=/path/to/solana/keypair.json
ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
NETWORK_URL=https://api.devnet.solana.com  # Must match ANCHOR_PROVIDER_URL
```

**RPC Note:** The standard `api.devnet.solana.com` aggressively rate-limits and may return 403 errors. The SDK has a built-in Helius devnet RPC as fallback. For reliable development, use a dedicated RPC or omit `NETWORK_URL` to use the SDK default. If setting a custom `ANCHOR_PROVIDER_URL`, always set `NETWORK_URL` to the same value.

## Writer Methods

### `ensureRoot()`
Initialize the root PDA for the connected wallet. Idempotent — safe to call multiple times.

```javascript
await iqdb.ensureRoot();
```

### `ensureTable(tableName, columns, idCol)`
Create a table if it doesn't exist. **Preferred over `createTable`** — idempotent.

```javascript
await iqdb.ensureTable('users', ['name', 'email', 'role'], 'name');
```

- `tableName` — Table identifier. Must be unique per root.
- `columns` — Array of column names.
- `idCol` — The column that serves as the row identifier.

### `createTable(tableName, columns, idCol?, extKeys?)`
Create a new table. **Fails if table already exists** — use `ensureTable` instead.

```javascript
const result = await iqdb.createTable('users', ['name', 'email'], 'name');
// result: { initialized, initSignature, createSignature, root, table }
```

### `writeRow(tableName, jsonString)`
Write a new row to a table. **Data must be a JSON string, not an object.**

```javascript
await iqdb.writeRow('users', JSON.stringify({
  name: 'Bob', email: '[REDACTED_SECRET]', role: 'admin'
}));
```

- All values stored as strings.
- Append-only — each call creates a new transaction.
- **Row JSON must be under ~100 bytes** or the on-chain program rejects with `TxTooLong`.

### `pushInstruction(tableName, targetTxSig, beforeStr, afterStr)`
Update or delete an existing row by referencing its transaction signature.

```javascript
// Update: provide before and after JSON strings
await iqdb.pushInstruction('users', txSignature,
  JSON.stringify({ name: 'Bob', role: 'admin' }),
  JSON.stringify({ name: 'Bob', role: 'superadmin' })
);

// Delete: provide empty string as afterStr
await iqdb.pushInstruction('users', txSignature,
  JSON.stringify({ name: 'Bob' }),
  ''
);
```

**Important:** `pushInstruction` writes to a separate instruction log — it does NOT modify the original row data. `readRowsByTable` returns raw rows without applying instructions. To get the full picture, use `searchTableByName` which returns both `rows` (raw) and `instruRows`/`targetContent` (instruction corrections). Your application is responsible for applying corrections on top of raw rows.

To get a tx signature for targeting, use `searchTableByName` and inspect the `sigMap` keys.

### `createExtTable(baseTable, rowId, extKey, columns, idCol?, extKeysForExt?)`
Create an extension (child) table linked to a parent table row.

```javascript
await iqdb.createExtTable('users', 'Bob', 'sessions',
  ['session_id', 'timestamp'], 'session_id');
```

### `ensureExtTable(baseTable, rowId, extKey, columns, idCol)`
Idempotent version of `createExtTable`.

```javascript
await iqdb.ensureExtTable('users', 'Bob', 'sessions',
  ['session_id', 'timestamp'], 'session_id');
```

### `updateTableList(tableNames)`
Update the root PDA's table name list.

```javascript
await iqdb.updateTableList(['users', 'sessions', 'logs']);
```

### `updateTable(tableName, columns, idCol?, extKeys?)`
Update an existing table's column schema or extension keys.

```javascript
await iqdb.updateTable('users', ['name', 'email', 'role', 'status'], 'name');
```

## Reader Methods

### `readRowsByTable({ userPubkey, tableName, maxTx?, perTableLimit? })`
Read all rows from a table. Free RPC call.

```javascript
const rows = await iqdb.readRowsByTable({
  userPubkey: [REDACTED_SECRET],
  tableName: 'users'
});
// Returns: [{ name: 'Bob', email: '[REDACTED_SECRET]', role: 'admin' }, ...]
```

### `listRootTables()`
List all tables in the root PDA.

```javascript
const tables = await iqdb.listRootTables();
// Returns: [{ name: 'users', seedHex: '...' }, ...]
```

### `readTableMeta(tableName)`
Read table metadata (columns, ID column, extension keys).

```javascript
const meta = await iqdb.readTableMeta('users');
// Returns: { columns: ['name', 'email', 'role'], idCol: 'name', extKeys: [] }
```

### `searchTableByName({ userPubkey, tableName, maxTx? })`
Full table search with instruction history, signature maps, and extension data.

```javascript
const result = await iqdb.searchTableByName({
  userPubkey: 'YOUR_PUBKEY',
  tableName: 'users'
});
// Returns: { tableName, columns, rows, instruRows, sigContentRows, ... }
```

### `getTableColumns({ userPubkey, tableName })`
Get just the column names for a table.

```javascript
const { columns } = await iqdb.getTableColumns({
  userPubkey: 'YOUR_PUBKEY',
  tableName: 'users'
});
```

## Rolling Hash System

Every write operation extends a keccak hash chain:

```
hash_n = keccak256(domain || hash_{n-1} || tx_data)
```

- **Domain:** Table-specific namespace to prevent cross-table collision
- **Tamper detection:** Any modification to historical data breaks the chain
- **Verification:** Replay all transactions and compare final hash

## Cost Estimates

| Operation | Approximate Cost |
|-----------|-----------------|
| Root PDA init | ~0.002 SOL (rent) |
| Create table | ~0.001 SOL (rent) |
| Write row | ~0.001 SOL (rent) |
| Update/Delete (pushInstruction) | TX fee only (~0.000005 SOL) |
| Read | Free |

100 tables ~ 0.012 SOL total rent.

## Error Handling

Common errors:
- `TxTooLong` — Row JSON exceeds on-chain size limit. Shorten data.
- `Table PDA not found` — Table doesn't exist or `NETWORK_URL` points to wrong RPC.
- `account already in use` — Table already exists. Use `ensureTable` instead of `createTable`.
- `ANCHOR_WALLET is not set` — Set the environment variable before importing the SDK.
- `ColumnMismatch` — Row data keys don't match table schema.


path: references/setup.md
content: # Environment Setup

## Prerequisites

- **Node.js 18+** — `node --version`
- **Solana CLI** — `solana --version`
- **A Solana wallet** — Keypair JSON file

## Install Solana CLI

```bash
# macOS/Linux
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

# Windows — download installer and run with admin privileges
# https://release.anza.xyz/stable/solana-install-init-x86_64-pc-windows-msvc.exe
```

Add to PATH: `export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"`

## Configure for Devnet

```bash
solana config set --url https://api.devnet.solana.com
solana-keygen new --no-bip39-passphrase -o ~/.config/solana/id.json
solana config set --keypair ~/.config/solana/id.json
solana airdrop 2
```

If airdrop fails (rate-limited), use the web faucet at faucet.solana.com.

## Install Dependencies

### New SDK (Mainnet)

```bash
npm install @iqlabs-official/solana-sdk @solana/web3.js
```

The new SDK supports both CJS and ESM. CJS is recommended for compatibility with existing IQDB tooling.

### Legacy SDK (Devnet)

```bash
npm install @iqlabsteam/iqdb @coral-xyz/anchor @solana/web3.js hanlock
```

**Important (legacy only):** Set `"type": "commonjs"` in package.json (or use `.cjs` file extension). The legacy IQDB SDK uses dynamic `require()` internally and does not support ESM.

## New SDK Setup (Mainnet)

```javascript
// setup.cjs
const { Connection } = require('@solana/web3.js');

// Buffer monkey-patch — required on Node v24
// SDK's toSeedBytes() returns Uint8Array, but Anchor Borsh encoder requires Buffer
const seedModule = require('@iqlabs-official/solana-sdk/dist/sdk/utils/seed');
const origFn = seedModule.toSeedBytes;
if (!seedModule._patched) {
  seedModule.toSeedBytes = (v) => Buffer.from(origFn(v));
  seedModule._patched = true;
}

const { IQDBContract, IQDBWriter, IQDBReader } = require('@iqlabs-official/solana-sdk');

const RPC = process.env.ANCHOR_PROVIDER_URL || 'https://solana-rpc.publicnode.com';
const connection = new Connection(RPC, 'confirmed');

// Program ID: 9KLLchQVJpGkw4jPuUmnvqESdR7mtNCYr3qS4iQLabs
const contract = new IQDBContract(connection);
const writer = new IQDBWriter(connection);
const reader = new IQDBReader(connection);

module.exports = { contract, writer, reader, connection };
```

### Required Environment Variables

```bash
ANCHOR_WALLET=/path/to/keypair.json                    # Required — Solana keypair for signing
ANCHOR_PROVIDER_URL=https://solana-rpc.publicnode.com   # Recommended — primary RPC
```

## Legacy SDK Setup (Devnet)

```javascript
// setup.cjs
// Set NETWORK_URL before importing to ensure reads use the same RPC as writes
const RPC = process.env.ANCHOR_PROVIDER_URL || 'https://api.devnet.solana.com';
process.env.NETWORK_URL = RPC;

const { createIQDB } = require('@iqlabsteam/iqdb');

const iqdb = createIQDB();

async function init() {
  await iqdb.ensureRoot();
  console.log('IQDB ready.');
}

module.exports = { iqdb, init };
```

### Required Environment Variables (Legacy)

```bash
ANCHOR_WALLET=/path/to/keypair.json        # Required — Solana keypair for signing
ANCHOR_PROVIDER_URL=https://api.devnet.solana.com  # Required — RPC endpoint
```

**RPC Note:** The standard `api.devnet.solana.com` aggressively rate-limits. The SDK includes a built-in Helius devnet RPC as fallback. For production use, get a dedicated RPC from Helius, Alchemy, or QuickNode.

## Mainnet Checklist

1. Use `https://solana-rpc.publicnode.com` as primary RPC (handles rapid reads without 429s)
2. Use `https://api.mainnet-beta.solana.com` as fallback only
3. Add 2-3 second delays between write transactions to avoid rate limiting
4. Apply Buffer monkey-patch before any SDK calls (Node v24 — `Uint8Array` is not a `Buffer` subclass)
5. Use a funded wallet (real SOL)
6. Test all operations on devnet first
7. Budget for rent costs (~0.012 SOL per 100 tables)
8. Row data must stay under ~120 bytes per write (client-side limit)
9. Omit empty optional fields to save row space (`ci: ""` wastes 7 bytes)


path: references/x402-payments.md
content: # x402 Payment API Reference

**Purpose:** HTTP 402-gated file inscription to Solana
**Payment:** USDC or SOL
**Flow:** Quote → Pay → Inscribe → Download

## Overview

x402 enables paid data inscription to Solana. Files are chunked into transactions and stored permanently on-chain. Payment is required before inscription (HTTP 402 pattern).

## Flow

### 1. Quote

Request a price quote for inscribing data.

```
POST /quote
Content-Type: application/json

{
  "fileSize": 1024,        // bytes
  "fileName": "data.json",
  "paymentToken": "USDC"   // "USDC" or "SOL"
}
```

Response:
```json
{
  "quoteId": "q_abc123",
  "price": "0.05",
  "token": "USDC",
  "paymentAddress": "So1ana...addr",
  "expiresAt": "2026-01-30T12:00:00Z",
  "chunks": 4
}
```

### 2. Pay

Send the quoted amount to the provided payment address. Standard Solana transfer:

```typescript
import { Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js';

// For SOL payment
const tx = new Transaction().add(
  SystemProgram.transfer({
    fromPubkey: wallet.publicKey,
    toPubkey: new PublicKey(quote.paymentAddress),
    lamports: Math.ceil(parseFloat(quote.price) * 1e9)
  })
);

const sig = await connection.sendTransaction(tx, [wallet]);
```

For USDC, use SPL token transfer to the payment address.

### 3. Inscribe

Submit payment proof to trigger inscription.

```
POST /inscribe
Content-Type: application/json

{
  "quoteId": "q_abc123",
  "paymentSignature": "5K7x...txsig"
}
```

Response:
```json
{
  "inscriptionId": "i_def456",
  "status": "processing",
  "txIds": ["tx1...", "tx2...", "tx3...", "tx4..."],
  "totalChunks": 4
}
```

The file is split into chunks that fit within Solana transaction size limits (~1232 bytes per tx). Each chunk is broadcast as a separate transaction.

### 4. Download

Reconstruct the file from on-chain chunks.

```
GET /download/:inscriptionId
```

Response: The original file, reassembled from on-chain transaction data.

### Status Check

```
GET /status/:inscriptionId
```

Response:
```json
{
  "inscriptionId": "i_def456",
  "status": "completed",
  "chunksConfirmed": 4,
  "totalChunks": 4,
  "txIds": ["tx1...", "tx2...", "tx3...", "tx4..."]
}
```

Status values: `processing`, `completed`, `failed`

## Cost Breakdown

- **Inscription fee** — Set by x402 service (covers chunk transaction fees + margin)
- **Solana tx fees** — ~0.000005 SOL per chunk transaction
- **Rent** — Not applicable (data stored in transaction logs, not accounts)

## Integration with IQDB

Use x402 for large data that doesn't fit in IQDB rows (which are PDA-constrained). IQDB stores structured metadata; x402 stores the raw file.

```javascript
// Store metadata in IQDB
const { createIQDB } = require('@iqlabsteam/iqdb');
const iqdb = createIQDB();

await iqdb.writeRow('files', JSON.stringify({
  name: 'report.pdf',
  inscId: 'i_def456',
  size: '102400'
}));

// Actual file data lives on-chain via x402
```

## Limitations

- Max file size depends on x402 service configuration
- Inscription is permanent — no deletion from Solana
- Payment is non-refundable once inscription starts
- Chunk transactions may fail individually — check status for confirmation


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)