This document is in progress and will be refined.
Installation
The current release is
0.2.0 (published 2026-09-16). It requires
@solana/web3.js 1.99.0 or newer, the first web3.js release able to parse
Solana version-1 transactions. See Transaction v1 Support.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 (under the inline cap): stored inline in a single transaction, fastest
- Medium data: split into a linked list of chunk transactions
- Large data: uploaded in parallel through a session, for speed
Key related functions
codeIn(): upload data and get a transaction IDreadCodeIn(): read data back from a transaction ID
Transaction v1 Support
Solana transaction format v1 (SIMD-0296) raises the transaction size cap from 1,232 to 4,096 bytes. The v1 feature gate is active on both mainnet and devnet, and SDK0.2.0 uses it to pack roughly 4x more data into each write.
Two transaction profiles
The SDK selects the profile at runtime, per write: it uses the v1 pipeline when
the feature gate is active on the connected cluster and the signer exposes a
raw secret key (a
Keypair). Wallet-adapter signers (Phantom, Mobile Wallet
Adapter) currently always use the legacy pipeline; wallet v1 signing will come
when wallets advertise support. The routing logic (inline vs linked list vs
session) is otherwise identical, and fees are unchanged.
To force a profile, set the environment variable IQ_TX_PROFILE=legacy or
IQ_TX_PROFILE=v1 (v1 still requires a keypair signer).
Account migration
The on-chain program (9KLLchQVJpGkw4jPuUmnvqESdR7mtNCYr3qS4iQLabs, same id on
mainnet and devnet) was upgraded on 2026-09-16 to raise the payload capacity
per account to 4,096 bytes.
- New users initialized after the upgrade get full-size accounts automatically.
- Accounts created before the upgrade are smaller and are grown automatically
by the SDK on the next write, using the permissionless
realloc_accountinstruction. The writer pays the one-time rent difference, about 0.045 SOL across the two accounts. No user action is required.
Reading v1-written data
All SDK readers requestmaxSupportedTransactionVersion: 1, which also returns
legacy and v0 transactions, so previously written data keeps working. If your
app reads transactions with its own web3.js code, upgrade to @solana/web3.js
1.99.0 or newer and request version 1, or the RPC will not return v1-written
data.
Session reads now validate completeness against the on-chain session account’s
total_chunks: an incomplete read throws an error (incomplete session: missing or unexpected chunks) instead of silently returning truncated data.
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 callcodeIn(). 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
Key related functions
requestConnection(): send a friend request (creates pending)manageConnection(): approve/reject/block/unblock a requestreadConnection(): check current relationship statuswriteConnectionRow(): exchange messages/data with a connected friendfetchUserConnections(): fetch all connections (sent & received friend requests)
Database Tables
Store JSON data in tables like a database.How are tables created?
UsecreateTable() 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.Key related functions
createTable(): create a new table (auto-reallocs DbRoot if needed)writeRow(): add a new row to an existing tablereadTableRows(): read rows from a tablegetTablelistFromRoot(): list all tables in a databasefetchInventoryTransactions(): list uploaded files
Token & Collection Gating
Tables can be gated so that only users holding a specific token or NFT collection can write data.Gate Types
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()ormanageRowData() - If no gate is set, the table is public (default behavior, no change for existing users)
Gate parameter
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
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:Onboarding (private to public)
A private table (exists inglobal_table_seeds only) can be promoted to public (table_seeds) by anyone in the table_creators list:
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:
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 usingderiveX25519Keypair(). This means users don’t need to manage separate encryption keys — their wallet is the key.
Key related functions
deriveX25519Keypair(): derive encryption keypair from walletdhEncrypt()/dhDecrypt(): single-recipient encryptionpasswordEncrypt()/passwordDecrypt(): password-based encryptionmultiEncrypt()/multiDecrypt(): multi-recipient encryption
Function Details
Data Storage and Retrieval
codeIn()
codeIn() picks the transaction profile automatically, per write: keypair
signers on a v1-enabled cluster use 3,600-byte chunks, wallet-adapter signers
use legacy 850-byte chunks. See Transaction v1 Support.readCodeIn()
readCodeIn() requests maxSupportedTransactionVersion: 1, so it reads data
written through either transaction profile (legacy, v0, and v1). Session reads
validate completeness against the session account’s total_chunks and throw
incomplete session: missing or unexpected chunks rather than returning
truncated data.Connection Management
requestConnection()
manageConnection()
There is no high-level SDK wrapper for this function. Use the contract-level instruction builder directly.
readConnection()
writeConnectionRow()
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.
updateUserMetadata()
Table Management
createTable()
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.
writeRow()
readTableRows()
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().
getTablelistFromRoot()
fetchInventoryTransactions()
Encryption
deriveX25519Keypair()
Derive a deterministic X25519 keypair from a wallet signature. The same wallet always produces the same keypair.
dhEncrypt()
dhDecrypt()
passwordEncrypt()
passwordDecrypt()
multiEncrypt()
multiDecrypt()
Environment Settings
setRpcUrl()
Advanced Functions
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.
Reader Functions
readUserState()
Reads the UserState PDA for a given user.
readInventoryMetadata()
Reads metadata associated with a user’s inventory transaction.
getSessionPdaList()
Retrieves a list of session PDA addresses for a user.
Utility Functions
Session speed profiles
Many writer/reader functions accept aspeed parameter that controls RPS
and concurrency for the call. The SDK ships four presets and lets you
override raw values directly:
Profiles are mutable —
iqlabs.utils.SESSION_SPEED_PROFILES.heavy.maxRps = 200
will affect every future speed: 'heavy' call.
Exports: SESSION_SPEED_PROFILES, DEFAULT_SESSION_SPEED,
resolveSessionSpeed, resolveSessionConfig, types SessionSpeedKey,
SessionSpeedConfig, SessionSpeedOption.
