Documentation Index
Fetch the complete documentation index at: https://iqlabs.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
This document is in progress and will be refined.
Installation
npm i @iqlabs-official/solana-sdk
Core Concepts
These are the key concepts to know before using the IQLabs SDK.
Data Storage (Code In)
This is how you store any data (files, text, JSON) on-chain.
How is it stored?
Depending on data size, the SDK picks the optimal method:
- Small data (< 900 bytes): store immediately, fastest
- Medium data (< 8.5 KB): split into multiple transactions
- Large data (>= 8.5 KB): upload in parallel for speed
User State PDA
An on-chain profile account for a user.
What gets stored?
- Profile info (name, profile picture, bio, etc.)
- Number of uploaded files
- Friend request records
Friend requests are not stored as values in the PDA; they are sent as transactions.
When is it created?
It is created automatically the first time you call codeIn(). No extra setup is required, but the first user may need to sign twice.
Connection PDA
An on-chain account that manages relationships between two users (friends, messages, etc.).
What states can it have?
- pending: a friend request was sent but not accepted yet
- approved: the request was accepted and the users are connected
- blocked: one side blocked the other
A blocked connection can only be unblocked by the blocker.
Database Tables
Store JSON data in tables like a database.
How are tables created?
Use createTable() to create a table explicitly. If the DbRoot PDA is running low on space, the SDK automatically expands it in the same transaction — no extra steps needed.
A table is uniquely identified by the combination of dbRootId and tableSeed. The tableSeed is internally hashed (keccak256) to derive the on-chain PDA. A human-readable tableHint (e.g. "users", "chatroom:general") is stored in DbRoot.table_seeds so that anyone reading the DbRoot can discover tables without a hardcoded lookup.
Token & Collection Gating
Tables can be gated so that only users holding a specific token or NFT collection can write data.
Gate Types
| Type | GateType | Description |
|---|
| Token | GateType.Token | User must hold >= amount of the specified SPL token mint |
| Collection | GateType.Collection | User must hold any NFT from the specified Metaplex verified collection |
How it works
- Table creator sets the gate when creating or updating a table
- Writers don’t need to do anything special — the SDK automatically resolves the required token account (and metadata account for collections) when calling
writeRow() or manageRowData()
- If no gate is set, the table is public (default behavior, no change for existing users)
Gate parameter
gate?: {
mint: PublicKey; // token mint address OR collection address
amount?: number; // minimum token amount (default: 1, ignored for collections)
gateType?: GateType; // GateType.Token (default) or GateType.Collection
}
For collection gates, the user can present any NFT from that collection. amount is ignored since NFTs always have amount=1.
Table Creation Permissions
The database owner (DbRoot creator) can control who is allowed to create tables.
Two levels of table creation
| Type | Stored in | Visibility | Permission field |
|---|
| Public table | table_seeds + global_table_seeds | Listed in getTablelistFromRoot() | table_creators |
| Private table | global_table_seeds only | Only accessible if you know the PDA | ext_creators |
table_seeds and global_table_seeds store human-readable hints (e.g. "users"), not hashed seeds. To derive the table PDA from a hint, hash it with toSeedBytes() first.
- If the permission list is empty, anyone can create tables (default, backward-compatible)
- If the permission list has wallets, only those wallets + the DbRoot creator can create tables
Managing permissions
The DbRoot creator can set both lists in a single call:
const ix = iqlabs.contract.manageTableCreatorsInstruction(
builder,
{ signer: wallet.publicKey, db_root: dbRootPda, system_program: SystemProgram.programId },
{
db_root_id: dbRootIdBytes,
table_creators: [adminWallet1, adminWallet2], // who can create public tables
ext_creators: [], // empty = anyone can create private tables
}
);
Pass empty arrays to make creation public again.
Onboarding (private to public)
A private table (exists in global_table_seeds only) can be promoted to public (table_seeds) by anyone in the table_creators list:
const ix = iqlabs.contract.onboardTableInstruction(
builder,
{ signer: wallet.publicKey, db_root: dbRootPda },
{ db_root_id: dbRootIdBytes, table_seed: Buffer.from("my-board") }
// table_seed here matches the hint stored in global_table_seeds
);
This is useful when you want anyone to create content (e.g. threads) but only admins decide which ones are publicly listed (e.g. boards).
Reading table hints
getTablelistFromRoot() returns the hints stored in table_seeds and global_table_seeds as hex strings. Decode them to get the original readable identifiers:
const { tableSeeds } = await iqlabs.reader.getTablelistFromRoot(connection, dbRootId);
for (const seedHex of tableSeeds) {
const hint = Buffer.from(seedHex, "hex").toString("utf8");
console.log(hint); // "users", "po", "chatroom:general", etc.
// To access the table, hash the hint to derive the PDA
const tablePda = iqlabs.contract.getTablePda(
dbRoot, iqlabs.utils.toSeedBytes(hint), programId
);
}
Encryption (Crypto)
The SDK includes a built-in encryption module (iqlabs.crypto) for encrypting data before storing it on-chain.
Three encryption modes
- DH Encryption (single recipient): Ephemeral X25519 ECDH → HKDF-SHA256 → AES-256-GCM. Use when encrypting data for one specific recipient.
- Password Encryption: PBKDF2-SHA256 (250k iterations) → AES-256-GCM. Use for password-protected data that anyone with the password can decrypt.
- Multi-recipient Encryption (PGP-style hybrid): Generates a random content encryption key (CEK), encrypts data once, then wraps the CEK for each recipient via ECDH. Use when encrypting data for multiple recipients.
Key derivation
Users can derive a deterministic X25519 keypair from their wallet signature using deriveX25519Keypair(). This means users don’t need to manage separate encryption keys — their wallet is the key.
Function Details
Data Storage and Retrieval
codeIn()
| Parameters | input: { connection, signer } object
data: data to upload (string or string[])
filename: optional filename (string)
method: upload method (number, default: 0)
filetype: file type hint (string, default: ”)
onProgress: optional progress callback (percent: number) => void |
|---|
| Returns | Transaction signature (string) |
import iqlabs from '@iqlabs-official/solana-sdk';
// Upload data
const signature = await iqlabs.writer.codeIn(
{ connection, signer },
'Hello, blockchain!'
);
// Upload with filename
const sig = await iqlabs.writer.codeIn(
{ connection, signer },
'file contents here',
'hello.txt'
);
readCodeIn()
| Parameters | txSignature: transaction signature (string)
speed: rate limit profile (optional, ‘light’ | ‘medium’ | ‘heavy’ | ‘extreme’)
onProgress: optional progress callback (percent: number) => void |
|---|
| Returns | { metadata: string, data: string | null } |
import iqlabs from '@iqlabs-official/solana-sdk';
const result = await iqlabs.reader.readCodeIn('5Xg7...');
console.log(result.data); // 'Hello, blockchain!'
console.log(result.metadata); // JSON string with file metadata
Connection Management
requestConnection()
| Parameters | connection: Solana RPC Connection
signer: Signer
dbRootId: database ID (Uint8Array or string)
partyA: first user pubkey (string)
partyB: second user pubkey (string)
tableName: connection table name (string or Uint8Array)
columns: column list (Array<string | Uint8Array>)
idCol: ID column (string or Uint8Array)
extKeys: extension keys (Array<string | Uint8Array>) |
|---|
| Returns | Transaction signature (string) |
import iqlabs from '@iqlabs-official/solana-sdk';
await iqlabs.writer.requestConnection(
connection, signer, 'my-db',
myWalletAddress, friendWalletAddress,
'dm_table', ['message', 'timestamp'], 'message_id', []
);
manageConnection()
There is no high-level SDK wrapper for this function. Use the contract-level instruction builder directly.
| Parameters | builder: InstructionBuilder
accounts: { db_root, connection_table, signer } (PublicKey)
args: { db_root_id, connection_seed, new_status } |
|---|
| Returns | TransactionInstruction |
import iqlabs from '@iqlabs-official/solana-sdk';
const builder = iqlabs.contract.createInstructionBuilder();
// Approve a friend request
const approveIx = iqlabs.contract.manageConnectionInstruction(
builder,
{ db_root, connection_table, signer: myPubkey },
{ db_root_id, connection_seed, new_status: iqlabs.contract.CONNECTION_STATUS_APPROVED }
);
// Block a user
const blockIx = iqlabs.contract.manageConnectionInstruction(
builder,
{ db_root, connection_table, signer: myPubkey },
{ db_root_id, connection_seed, new_status: iqlabs.contract.CONNECTION_STATUS_BLOCKED }
);
readConnection()
| Parameters | dbRootId: database ID (Uint8Array or string)
partyA: first wallet (string)
partyB: second wallet (string) |
|---|
| Returns | { status: 'pending' | 'approved' | 'blocked' | 'unknown', requester: 'a' | 'b', blocker: 'a' | 'b' | 'none' } |
import iqlabs from '@iqlabs-official/solana-sdk';
const { status, requester, blocker } = await iqlabs.reader.readConnection('my-db', partyA, partyB);
console.log(status); // 'pending' | 'approved' | 'blocked'
writeConnectionRow()
| Parameters | connection: Solana RPC Connection
signer: Signer
dbRootId: database ID (Uint8Array or string)
connectionSeed: connection seed (Uint8Array or string)
rowJson: JSON data (string) |
|---|
| Returns | Transaction signature (string) |
import iqlabs from '@iqlabs-official/solana-sdk';
await iqlabs.writer.writeConnectionRow(
connection, signer, 'my-db', connectionSeed,
JSON.stringify({ message_id: '123', message: 'Hello friend!', timestamp: Date.now() })
);
fetchUserConnections()
Fetch all connections (friend requests) for a user by analyzing their UserState PDA transaction history. Each connection includes its dbRootId, identifying which app the connection belongs to.
| Parameters | userPubkey: user public key (string or PublicKey)
options: optional settings |
|---|
| Options | limit: max number of transactions to fetch
before: signature to paginate from
speed: rate limit profile (‘light’ | ‘medium’ | ‘heavy’ | ‘extreme’) |
| Returns | Array of { dbRootId, connectionPda, partyA, partyB, status, requester, blocker, timestamp } |
import iqlabs from '@iqlabs-official/solana-sdk';
const connections = await iqlabs.reader.fetchUserConnections(myPubkey, {
speed: 'light',
limit: 100
});
// Filter by status
const pendingRequests = connections.filter(c => c.status === 'pending');
const friends = connections.filter(c => c.status === 'approved');
const blocked = connections.filter(c => c.status === 'blocked');
// Check connection details
connections.forEach(conn => {
console.log(`${conn.partyA} <-> ${conn.partyB}, status: ${conn.status}`);
});
| Parameters | connection: Solana RPC Connection
signer: Signer
dbRootId: database ID (Uint8Array or string)
meta: metadata to store (Uint8Array or string) |
|---|
| Returns | Transaction signature (string) |
import iqlabs from '@iqlabs-official/solana-sdk';
// Store a codeIn tx signature as user metadata
const txSig = await iqlabs.writer.updateUserMetadata(
connection, signer, 'my-db', metaTxSignature
);
Table Management
createTable()
| Parameters | connection: Solana RPC Connection
signer: Signer
dbRootId: database ID (Uint8Array or string)
tableSeed: table identifier — hashed internally via toSeedBytes() for PDA derivation
tableName: display name (Uint8Array or string)
columnNames: column list (Array<Uint8Array | string>)
idCol: ID column (Uint8Array or string)
extKeys: extension keys (Array<Uint8Array | string>)
gate: optional access gate (see Token & Collection Gating)
writers: optional allowed writers (PublicKey[])
tableHint: human-readable identifier stored in DbRoot for discovery (string) |
|---|
| Returns | Transaction signature (string) |
The tableHint is stored in DbRoot.table_seeds so anyone reading the DbRoot can discover tables. The tableSeed is hashed via toSeedBytes() to derive the on-chain PDA — it is not stored. If the DbRoot PDA is running low on space, a realloc instruction is automatically prepended.
import iqlabs from '@iqlabs-official/solana-sdk';
// Basic table — tableHint "users" is stored in DbRoot for discovery
await iqlabs.writer.createTable(
connection, signer, 'my-db', 'users',
'users',
['name', 'email', 'age'],
'name',
[],
undefined, undefined,
'users' // tableHint — readable identifier stored in DbRoot
);
// With token gate
await iqlabs.writer.createTable(
connection, signer, 'my-db', 'vip', 'vip',
['name'], 'user_id', [],
{ mint: tokenMintPubkey, amount: 100, gateType: iqlabs.contract.GateType.Token },
undefined,
'vip'
);
// With NFT collection gate
await iqlabs.writer.createTable(
connection, signer, 'my-db', 'holders', 'holders',
['name'], 'user_id', [],
{ mint: collectionPubkey, gateType: iqlabs.contract.GateType.Collection },
undefined,
'holders'
);
writeRow()
| Parameters | connection: Solana RPC Connection
signer: Signer
dbRootId: database ID (Uint8Array or string)
tableSeed: table name (Uint8Array or string)
rowJson: JSON row data (string)
skipConfirmation: skip tx confirmation (boolean, default: false)
remainingAccounts: optional array of PublicKey to append as read-only reference accounts (e.g. feed PDA for indexing) |
|---|
| Returns | Transaction signature (string) |
import iqlabs from '@iqlabs-official/solana-sdk';
// Write the first row to create the table
await iqlabs.writer.writeRow(connection, signer, 'my-db', 'users', JSON.stringify({
id: 1, name: 'Alice', email: 'alice@example.com'
}));
// Add another row to the same table
await iqlabs.writer.writeRow(connection, signer, 'my-db', 'users', JSON.stringify({
id: 2, name: 'Bob', email: 'bob@example.com'
}));
readTableRows()
| Parameters | account: table PDA (PublicKey or string)
options: optional settings |
|---|
| Options | limit: max number of rows to fetch
before: signature cursor for pagination
signatures: pre-collected signature array (skips RPC fetch if provided)
speed: rate limit profile (‘light’, ‘medium’, ‘heavy’, ‘extreme’) |
| Returns | Array<Record<string, unknown>> |
import iqlabs from '@iqlabs-official/solana-sdk';
// Basic usage
const rows = await iqlabs.reader.readTableRows(tablePda, { limit: 50 });
// Cursor-based pagination
const olderRows = await iqlabs.reader.readTableRows(tablePda, { limit: 50, before: 'sig...' });
// With pre-collected signatures (skips signature fetching, decodes directly)
const sigs = await iqlabs.reader.collectSignatures(tablePda);
const targetIdx = sigs.indexOf('abc123');
const slice = sigs.slice(targetIdx - 25, targetIdx + 25);
const rows = await iqlabs.reader.readTableRows(tablePda, { signatures: slice });
collectSignatures()
Collects all (or up to maxSignatures) transaction signatures for an account. Lightweight — no transaction decoding, only signature strings. Useful for pagination: fetch the full signature list once, then slice and pass to readTableRows().
| Parameters | account: table PDA (PublicKey or string)
maxSignatures: max number of signatures to collect (optional, fetches all if omitted) |
|---|
| Returns | string[] (signature strings) |
import iqlabs from '@iqlabs-official/solana-sdk';
// Collect all signatures
const allSigs = await iqlabs.reader.collectSignatures(tablePda);
// Collect up to 3000 signatures
const sigs = await iqlabs.reader.collectSignatures(tablePda, 3000);
// Use with readTableRows to read from the middle
const targetIdx = sigs.indexOf('abc123');
const chunk = sigs.slice(targetIdx - 25, targetIdx + 25);
const rows = await iqlabs.reader.readTableRows(tablePda, { signatures: chunk });
getTablelistFromRoot()
| Parameters | connection: Solana RPC Connection
dbRootId: database ID (Uint8Array or string) |
|---|
| Returns | { rootPda: PublicKey, creator: string | null, tableSeeds: string[], globalTableSeeds: string[] } |
import iqlabs from '@iqlabs-official/solana-sdk';
const result = await iqlabs.reader.getTablelistFromRoot(connection, 'my-db');
console.log('Creator:', result.creator);
console.log('Table seeds:', result.tableSeeds);
fetchInventoryTransactions()
| Parameters | publicKey: user public key (PublicKey)
limit: max count (number)
before: pagination cursor (optional, string) |
|---|
| Returns | Transaction array |
import iqlabs from '@iqlabs-official/solana-sdk';
const myFiles = await iqlabs.reader.fetchInventoryTransactions(myPubkey, 20);
myFiles.forEach(tx => {
let metadata: { data?: unknown } | null = null;
try {
metadata = JSON.parse(tx.metadata);
} catch {
metadata = null;
}
if (metadata && metadata.data !== undefined) {
const inlineData = typeof metadata.data === 'string'
? metadata.data
: JSON.stringify(metadata.data);
console.log(`Inline data: ${inlineData}`);
} else {
console.log(`Signature: ${tx.signature}`);
}
});
Encryption
deriveX25519Keypair()
Derive a deterministic X25519 keypair from a wallet signature. The same wallet always produces the same keypair.
| Parameters | signMessage: wallet sign function (msg: Uint8Array) => Promise<Uint8Array> |
|---|
| Returns | { privKey: Uint8Array, pubKey: Uint8Array } |
import iqlabs from '@iqlabs-official/solana-sdk';
const { privKey, pubKey } = await iqlabs.crypto.deriveX25519Keypair(
wallet.signMessage
);
dhEncrypt()
| Parameters | recipientPubHex: recipient’s X25519 public key (hex string)
plaintext: data to encrypt (Uint8Array) |
|---|
| Returns | { senderPub: string, iv: string, ciphertext: string } (all hex) |
dhDecrypt()
| Parameters | privKey: recipient’s private key (Uint8Array)
senderPubHex: sender’s ephemeral public key (hex string)
ivHex: IV (hex string)
ciphertextHex: ciphertext (hex string) |
|---|
| Returns | Uint8Array (decrypted plaintext) |
import iqlabs from '@iqlabs-official/solana-sdk';
// Encrypt for a single recipient
const encrypted = await iqlabs.crypto.dhEncrypt(
recipientPubHex,
new TextEncoder().encode('secret message')
);
// Decrypt (recipient side)
const decrypted = await iqlabs.crypto.dhDecrypt(
recipientPrivKey,
encrypted.senderPub,
encrypted.iv,
encrypted.ciphertext
);
console.log(new TextDecoder().decode(decrypted)); // 'secret message'
passwordEncrypt()
| Parameters | password: password (string)
plaintext: data to encrypt (Uint8Array) |
|---|
| Returns | { salt: string, iv: string, ciphertext: string } (all hex) |
passwordDecrypt()
| Parameters | password: password (string)
saltHex: salt (hex string)
ivHex: IV (hex string)
ciphertextHex: ciphertext (hex string) |
|---|
| Returns | Uint8Array (decrypted plaintext) |
import iqlabs from '@iqlabs-official/solana-sdk';
// Encrypt with password
const encrypted = await iqlabs.crypto.passwordEncrypt(
'my-password',
new TextEncoder().encode('secret data')
);
// Decrypt with same password
const decrypted = await iqlabs.crypto.passwordDecrypt(
'my-password',
encrypted.salt,
encrypted.iv,
encrypted.ciphertext
);
multiEncrypt()
| Parameters | recipientPubHexes: recipient public keys (string[])
plaintext: data to encrypt (Uint8Array) |
|---|
| Returns | { recipients: RecipientEntry[], iv: string, ciphertext: string } |
multiDecrypt()
| Parameters | privKey: your private key (Uint8Array)
pubKeyHex: your public key (hex string)
encrypted: the MultiEncryptResult object |
|---|
| Returns | Uint8Array (decrypted plaintext) |
import iqlabs from '@iqlabs-official/solana-sdk';
// Encrypt for multiple recipients
const encrypted = await iqlabs.crypto.multiEncrypt(
[alicePubHex, bobPubHex, carolPubHex],
new TextEncoder().encode('group secret')
);
// Each recipient decrypts with their own key
const plaintext = await iqlabs.crypto.multiDecrypt(
alicePrivKey, alicePubHex, encrypted
);
Environment Settings
setRpcUrl()
| Parameters | url: Solana RPC URL (string) |
|---|
| Returns | void |
import iqlabs from '@iqlabs-official/solana-sdk';
iqlabs.utils.setRpcUrl('https://your-rpc.example.com');
Advanced Functions
These are low-level SDK functions. Not needed for typical usage, but useful when building custom features or debugging.
Writer Functions
manageRowData()
Unified function that handles both table row writes and connection row writes. Auto-detects whether to write to a table or connection based on existing PDAs.
| Module | writer |
|---|
| Parameters | connection: Solana RPC Connection
signer: Signer
dbRootId: database ID (Uint8Array or string)
seed: table or connection seed (Uint8Array or string)
rowJson: JSON row data (string)
tableName: required for table edits (optional, string or Uint8Array)
targetTx: reference tx for table edits (optional, string or Uint8Array) |
| Returns | Transaction signature (string) |
| Use Case | Custom row management, updating existing rows |
import iqlabs from '@iqlabs-official/solana-sdk';
await iqlabs.writer.manageRowData(
connection, signer,
'my-db', // dbRootId
'users', // seed (table seed)
JSON.stringify({ id: 1, name: 'Updated Name' }),
'users', // tableName
originalTxSig // targetTx
);
Reader Functions
readUserState()
Reads the UserState PDA for a given user.
| Module | reader |
|---|
| Parameters | userPubkey: user public key (string) |
| Returns | { owner: string, metadata: string | null, totalSessionFiles: bigint, profileData?: string } |
| Use Case | Fetching user profile data, checking upload counts |
import iqlabs from '@iqlabs-official/solana-sdk';
const userState = await iqlabs.reader.readUserState(userPubkey);
console.log('Owner:', userState.owner);
console.log('Session files:', userState.totalSessionFiles);
console.log('Profile data:', userState.profileData);
Reads metadata associated with a user’s inventory transaction.
| Module | reader |
|---|
| Parameters | txSignature: transaction signature (string) |
| Returns | { onChainPath: string, metadata: string } |
| Use Case | Extracting file metadata from inventory transactions |
import iqlabs from '@iqlabs-official/solana-sdk';
const result = await iqlabs.reader.readInventoryMetadata(txSignature);
console.log('Path:', result.onChainPath);
console.log('Metadata:', result.metadata);
getSessionPdaList()
Retrieves a list of session PDA addresses for a user.
| Module | reader |
|---|
| Parameters | userPubkey: user public key (string) |
| Returns | string[] (session PDA base58 strings) |
| Use Case | Session management, active session tracking |
import iqlabs from '@iqlabs-official/solana-sdk';
const sessions = await iqlabs.reader.getSessionPdaList(userPubkey);
sessions.forEach(pda => {
console.log(`Session PDA: ${pda}`);
});
Utility Functions
deriveDmSeed()
Derives a deterministic seed for direct messaging (DM) between two users. Sorts the two pubkeys alphabetically and hashes with keccak256.
| Module | utils |
|---|
| Parameters | userA: first user pubkey (string)
userB: second user pubkey (string) |
| Returns | Uint8Array (seed bytes) |
| Use Case | Creating consistent connection identifiers, DM channel setup |
import iqlabs from '@iqlabs-official/solana-sdk';
const seed1 = iqlabs.utils.deriveDmSeed(walletA, walletB);
const seed2 = iqlabs.utils.deriveDmSeed(walletB, walletA);
// seed1 and seed2 are identical — order doesn't matter
toSeedBytes()
Converts a seed identifier to bytes. If the input is a 64-character hex string, it returns hex bytes directly. Otherwise, it applies keccak256 hash.
| Module | utils |
|---|
| Parameters | value: seed string or Uint8Array |
| Returns | Uint8Array |
| Use Case | Custom PDA derivation, low-level seed manipulation |
import iqlabs from '@iqlabs-official/solana-sdk';
const seedString = 'my-custom-seed';
const seedBytes = iqlabs.utils.toSeedBytes(seedString);
const [pda, bump] = PublicKey.findProgramAddressSync(
[seedBytes, otherSeed],
programId
);