npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@iqlabs-official/ethereum-sdk

v0.1.3

Published

IQLabs Ethereum SDK

Readme

IQLabs Ethereum SDK

Draft: This document is in progress and will be refined.

The Ethereum port of the IQLabs SDK. Same primitives — on-chain data storage, IQDB tables, friend connections, and end-to-end encryption — built on ethers v6 and a single deployed contract.

npm install @iqlabs-official/ethereum-sdk

Table of Contents

  1. Core Concepts

  2. Function Details

2.1. Advanced Functions (list only)


Core Concepts

These are the key concepts to know before using the IQLabs Ethereum SDK.


Data Storage (Code In)

This is how you store any data (files, text, JSON) on-chain. Data is inscribed into transaction calldata; nothing is written to contract storage. Reads reconstruct data by walking a linked list of transactions.

How is it stored?

Depending on data size, the SDK picks the optimal method:

  • Inline (small): data fits in a single transaction's metadata field — no chunking
  • Linked list (large): data is split into chunks (CHUNK_SIZE), uploaded via sendCode() calls in batches up to ~96 KB each, and the tail tx hash is recorded

Key related functions

  • codeIn(): upload data and get a transaction hash
  • readCodeIn(): read data back from a transaction hash

User State

An on-chain record per user address — managed by the contract, not a separate account/PDA.

What gets stored?

  • User-set metadata (name, profile, bio — anything you serialize and pass to updateUserMetadata)
  • userTxChainTail: the most recent inventory write, used as the head of the user's tx-chain

When is it created?

There is no explicit "create user" step. The first codeIn() call writes both the inventory entry and advances the chain tail. Each tx-chain write costs a small BASIC_FEE for the pointer-update step (the second of the two transactions).


Connection State

An on-chain relationship between two addresses (friends, DM channels, etc.).

What states can it have?

  • pending: a request was sent but not accepted yet
  • approved: the request was accepted and the users are connected
  • blocked: one side blocked the other

Important: A blocked connection can only be unblocked by the blocker.

Key related functions


Database Tables

Store JSON data in tables like a database.

How are tables created?

Use createTable() before writing rows. (Unlike the Solana SDK, tables must exist before writeRow() is called — the row write reads the table's txChainTail for staleness check.)

Note: A table is uniquely identified by the combination of dbRootId and tableName. Both are hashed with keccak256 internally to form mapping keys.

Key related functions


Token & Collection Gating

Tables can be gated so that only users holding a specific ERC-20 token or ERC-721 collection can write data.

Gate Types

| Type | gateType | Description | |------|-----------|-------------| | Token (ERC-20) | 0 | User must hold >= amount of the specified token contract | | Collection (ERC-721) | 1 | User must hold any NFT from the specified collection contract |

How it works

  • Table creator sets the gate when creating or updating a table
  • Writers only need to hold the required asset — the contract checks balance on-chain when writeRow() is called
  • If tokenAddress is ZeroAddress, the table is public (default behavior)

Gate parameter

gate?: {
  tokenAddress: string;  // ERC-20 or ERC-721 contract address (ZeroAddress for public)
  amount: number;        // minimum balance (ignored for collection gate)
  gateType: 0 | 1;       // 0 = token, 1 = collection
}

Notes

  • For token gates, amount is the minimum balance required (e.g., 100 means "must hold >= 100 tokens", in raw units — apply your own decimal scaling)
  • For collection gates, the user can present any NFT from that collection. amount is ignored
  • Omitting gate (or passing { tokenAddress: ZeroAddress, amount: 0, gateType: 0 }) creates a public table

Encryption (Crypto)

The SDK includes a built-in encryption module (iqlabs.crypto) for encrypting data before storing it on-chain. Identical primitives to the Solana SDK so the same plaintext can flow across chains.

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(). Their wallet is the key — no separate keystore.

Key related functions


Function Details

Data Storage and Retrieval

codeIn()

| Parameters | signer: ethers.Signerdata: data to upload (string or string[])filename: optional filename (string, default: "")filetype: file type hint (string, default: """text/plain")onProgress: optional progress callback (percent: number) => void | |----------|--------------------------| | Returns | Transaction hash (string) |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';
import { Wallet, JsonRpcProvider } from 'ethers';

const signer = new Wallet(privateKey, new JsonRpcProvider(rpcUrl));

// Upload inline data
const txHash = await iqlabs.writer.codeIn(signer, 'Hello, blockchain!');

// Upload with filename + type (large data is chunked automatically)
const txHash2 = await iqlabs.writer.codeIn(
  signer,
  longString,
  'hello.txt',
  'text/plain',
  (pct) => console.log(`upload: ${pct.toFixed(1)}%`),
);

readCodeIn()

| Parameters | txHash: transaction hash (string)onProgress: optional progress callback (percent: number) => void | |----------|--------------------------| | Returns | { metadata: { handle, typeField, offset, beforeUserTx }, data: string } |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';

const result = await iqlabs.reader.readCodeIn('0x5Xg7...');
console.log(result.data);              // 'Hello, blockchain!'
console.log(result.metadata.typeField); // 'text/plain'

Connection Management

requestConnection()

| Parameters | signer: ethers.SignerdbRootId: database ID (string)receiver: counterparty address (string)tableName: connection table name (string)columns: column list (string[])idCol: ID column (string)extKeys: extension keys (string[], default: []) | |----------|--------------------------| | Returns | Transaction hash (string) |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';

await iqlabs.writer.requestConnection(
  signer, 'my-db', friendAddress,
  'dm_table', ['message', 'timestamp'], 'message_id',
);

The connection seed is derived deterministically from the two addresses via deriveDmSeed(sender, receiver), so either party can recompute it.


manageConnection()

| Parameters | signer: ethers.SignerotherParty: counterparty address (string)dbRootId: database ID (string)newStatus: new status (number — 0 pending, 1 approved, 2 blocked) | |----------|--------------------------| | Returns | Transaction hash (string) |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';

// Approve a friend request
await iqlabs.writer.manageConnection(signer, friendAddress, 'my-db', 1);

// Block a user
await iqlabs.writer.manageConnection(signer, friendAddress, 'my-db', 2);

readConnection()

| Parameters | dbRootId: database ID (string)partyA: first wallet (string)partyB: second wallet (string) | |----------|--------------------------| | Returns | { status: 'pending' \| 'approved' \| 'blocked' \| 'unknown', requester: 'a' \| 'b', blocker: 'a' \| 'b' \| 'none' } |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';

const { status, requester, blocker } = await iqlabs.reader.readConnection(
  'my-db', addressA, addressB,
);
console.log(status); // 'pending' | 'approved' | 'blocked'

writeConnectionRow()

| Parameters | signer: ethers.SignerotherParty: counterparty address (string)dbRootId: database ID (string)rowJson: JSON data (string)onProgress: optional progress callback (percent: number) => void | |----------|--------------------------| | Returns | Transaction hash (string) |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';

await iqlabs.writer.writeConnectionRow(
  signer, friendAddress, 'my-db',
  JSON.stringify({ message_id: '123', message: 'Hello friend!', timestamp: Date.now() }),
);

readConnectionRows()

Read all rows written between two parties on the connection's tx-chain.

| Parameters | dbRootId: database ID (string)partyA: first wallet (string)partyB: second wallet (string)options: { limit?: number } (optional) | |----------|--------------------------| | Returns | Array<{ txHash: string, data: any }> (most recent first) |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';

const messages = await iqlabs.reader.readConnectionRows('my-db', myAddr, friendAddr, { limit: 50 });
messages.forEach(m => console.log(m.data));

fetchUserConnections()

Fetch all connection records for a user. Unlike the Solana SDK (which scans tx history), the Ethereum contract maintains an indexed list of connection keys per address, so this is a direct on-chain read.

| Parameters | userAddress: user address (string) | |----------|--------------------------| | Returns | Array<{ connectionKey, partyA, partyB, status }> |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';

const connections = await iqlabs.reader.fetchUserConnections(myAddress);

const pending  = connections.filter(c => c.status === 'pending');
const friends  = connections.filter(c => c.status === 'approved');
const blocked  = connections.filter(c => c.status === 'blocked');

Table Management

initializeDbRoot()

Initialize a database root before any tables are created under it. Required once per dbRootId.

| Parameters | signer: ethers.SignerdbRootId: database ID (string) | |----------|--------------------------| | Returns | Transaction hash (string) |

Example:

await iqlabs.writer.initializeDbRoot(signer, 'my-db');

createTable()

| Parameters | signer: ethers.SignerdbRootId: database ID (string)tableName: table name (string)columns: column names (string[])idCol: ID column (string)extKeys: extension keys (string[], default: [])gate: optional access gate, see Token & Collection Gatingwriters: optional writer whitelist (string[], default: [])isPrivate: create private table (boolean, default: false) | |----------|--------------------------| | Returns | Transaction hash (string) |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';
import { ZeroAddress } from 'ethers';

// Public table
await iqlabs.writer.createTable(
  signer, 'my-db', 'users', ['name', 'email'], 'user_id',
);

// Token-gated table (ERC-20, must hold >= 100)
await iqlabs.writer.createTable(
  signer, 'my-db', 'vip', ['name'], 'user_id', [],
  { tokenAddress: erc20Address, amount: 100, gateType: 0 },
);

// NFT-collection-gated table (ERC-721)
await iqlabs.writer.createTable(
  signer, 'my-db', 'holders', ['name'], 'user_id', [],
  { tokenAddress: nftAddress, amount: 0, gateType: 1 },
);

updateTable()

Modify an existing table's schema/gate/writers. Same parameters as createTable() minus isPrivate.

| Parameters | signer, dbRootId, tableName, columns, idCol, extKeys, gate, writers | |----------|--------------------------| | Returns | Transaction hash (string) |


writeRow()

| Parameters | signer: ethers.SignerdbRootId: database ID (string)tableName: table name (string)rowJson: JSON row data (string)onProgress: optional progress callback (percent: number) => void | |----------|--------------------------| | Returns | Transaction hash (string) |

Example:

await iqlabs.writer.writeRow(signer, 'my-db', 'users', JSON.stringify({
  id: 1, name: 'Alice', email: '[email protected]',
}));

Each writeRow call is a 2-tx flow: the data write (dbCodeIn) and a pointer-update (updateTableTxChainTail) which charges LINKED_LIST_FEE. The SDK awaits both before returning.


readTableRows()

Walk the table's tx-chain backwards from the tail and reconstruct each row.

| Parameters | dbRootId: database ID (string)tableName: table name (string)options: { limit?: number } (optional) | |----------|--------------------------| | Returns | Array<{ txHash: string, data: any }> (most recent first; data is parsed JSON when possible) |

Example:

const rows = await iqlabs.reader.readTableRows('my-db', 'users', { limit: 50 });
rows.forEach(r => console.log(r.data));

getTablelistFromRoot()

| Parameters | dbRootId: database ID (string) | |----------|--------------------------| | Returns | { creator: string, tables: TableEntry[], globalTables: TableEntry[] } where TableEntry = { name, seedHex } |

Example:

const result = await iqlabs.reader.getTablelistFromRoot('my-db');
console.log('Creator:', result.creator);
result.tables.forEach(t => console.log(t.name, t.seedHex));

fetchInventoryTransactions()

Walk a user's inventory tx-chain (everything they've uploaded via codeIn).

| Parameters | userAddress: user address (string)options: { limit?: number } (optional) | |----------|--------------------------| | Returns | Array<{ txHash, handle, tailTx, typeField, offset }> |

Example:

const myFiles = await iqlabs.reader.fetchInventoryTransactions(myAddress, { limit: 20 });
myFiles.forEach(tx => {
  console.log(`${tx.txHash}: ${tx.handle} (${tx.typeField})`);
});

Encryption

deriveX25519Keypair()

Derive a deterministic X25519 keypair from a wallet signature. The same wallet always produces the same keypair.

| Parameters | signMessage: function (msg: Uint8Array) => Promise<Uint8Array> | |----------|--------------------------| | Returns | { privKey: Uint8Array, pubKey: Uint8Array } |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';
import { getBytes } from 'ethers';

// Adapter: ethers signMessage returns a hex string; convert to bytes.
const sign = async (msg: Uint8Array) => getBytes(await signer.signMessage(msg));

const { privKey, pubKey } = await iqlabs.crypto.deriveX25519Keypair(sign);

dhEncrypt()

| Parameters | recipientPubHex: recipient X25519 pubkey (hex)plaintext: data to encrypt (Uint8Array) | |----------|--------------------------| | Returns | { senderPub, iv, ciphertext } (all hex) |

dhDecrypt()

| Parameters | privKey: recipient private key (Uint8Array)senderPubHex: sender pubkey (hex)ivHex, ciphertextHex (hex) | |----------|--------------------------| | Returns | Uint8Array (decrypted plaintext) |

Example:

const enc = await iqlabs.crypto.dhEncrypt(recipientPubHex, new TextEncoder().encode('secret'));
const dec = await iqlabs.crypto.dhDecrypt(myPrivKey, enc.senderPub, enc.iv, enc.ciphertext);

passwordEncrypt() / passwordDecrypt()

| Parameters (encrypt) | password: stringplaintext: Uint8Array | |----------|--------------------------| | Returns (encrypt) | { salt, iv, ciphertext } (all hex) | | Parameters (decrypt) | password, saltHex, ivHex, ciphertextHex | | Returns (decrypt) | Uint8Array |

Example:

const enc = await iqlabs.crypto.passwordEncrypt('my-password', new TextEncoder().encode('secret'));
const dec = await iqlabs.crypto.passwordDecrypt('my-password', enc.salt, enc.iv, enc.ciphertext);

multiEncrypt() / multiDecrypt()

| Parameters (encrypt) | recipientPubHexes: string[]plaintext: Uint8Array | |----------|--------------------------| | Returns (encrypt) | { recipients: RecipientEntry[], iv, ciphertext } | | Parameters (decrypt) | privKey: Uint8ArraypubKeyHex: your pubkey (hex)encrypted: the MultiEncryptResult | | Returns (decrypt) | Uint8Array |

Example:

const enc = 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, enc);

Environment Settings

setRpcUrl()

Override the RPC URL used by all reader functions. Default: https://rpc.sepolia.org. Falls back to env vars IQLABS_RPC_ENDPOINT, ETHEREUM_RPC_URL, or RPC_URL if unset.

| Parameters | url: Ethereum RPC URL (string) | |----------|--------------------------| | Returns | void |

Example:

iqlabs.setRpcUrl('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY');

Note: Writers (signer-based functions) use the provider attached to the Signer, not this URL. setRpcUrl only affects readers.

getRpcUrl()

| Returns | string — the currently configured RPC URL |

console.log(iqlabs.getRpcUrl());

User Metadata

updateUserMetadata()

| Parameters | signer: ethers.Signermetadata: string \| Uint8Array | |----------|--------------------------| | Returns | Transaction hash (string) |

Example:

await iqlabs.writer.updateUserMetadata(signer, JSON.stringify({ name: 'Alice', bio: 'gm' }));

Advanced Functions

These are advanced/internal helpers; this doc lists them only. See API docs (in progress) for details.

  • prepareUpload() / uploadLinkedList() / toChunks() (writer)
  • manageRowData() (writer) — overwrite a specific row by targetTx
  • manageTableCreators() (writer)
  • readUserState() (reader)
  • fetchTableMeta() (reader)
  • readSendCodeChain() / walkCalldataChain() / isEnd() (reader)
  • deriveDmSeed() (utils)
  • hexToBytes() / bytesToHex() / validatePubKey() (crypto)

Network Selection

The SDK supports multiple EVM networks. The active network determines both the RPC endpoint and the deployed contract address that readers/writers talk to. Default is sepolia.

setNetwork()

Switch the active network mode. Call this once at app startup (e.g. on page load) before any reader/writer call.

| Parameters | mode: "sepolia" \| "monad"rpcUrl: optional override (string) — defaults to the chain's public RPC | |----------|--------------------------| | Returns | void |

Example:

import iqlabs from '@iqlabs-official/ethereum-sdk';

// Sepolia (default — usually no call needed)
iqlabs.setNetwork('sepolia');

// Monad mainnet
iqlabs.setNetwork('monad');

// Or with a custom RPC (e.g. Alchemy)
iqlabs.setNetwork('monad', 'https://your-alchemy-monad-url');

getNetwork()

| Returns | "sepolia" \| "monad" — the currently active network |

assertChainMatches()

Throws if the configured RPC's chainId doesn't match the active network mode. Use defensively before sending a transaction if you don't fully control the user's RPC.

await iqlabs.assertChainMatches(signer);

Supported Networks

| Mode | Chain ID | Currency | Default RPC | |------|---------:|----------|-------------| | sepolia | 11155111 | ETH | https://rpc.sepolia.org | | monad | 143 | MON | https://rpc.monad.xyz |


Contract

Deployed addresses (resolved automatically from setNetwork()):

| Network | Address | |---------|---------| | Sepolia | 0xB1C16271954c7238672c3666FD22Ee14C6d065Db | | Monad | 0xeFd9376835076Bf8d83826F6A2277BB5362Cd893 |

To pin a specific deployment manually, pass the address directly:

import { getContract } from '@iqlabs-official/ethereum-sdk/dist/contract';
const c = getContract(signer, '0xYourDeployment');

Migrating from v0.1.0: the default Sepolia address changed in v0.1.1 (new deployment with IQ-discount support and per-network fee setters). If you need to read data from the v0.1.0 contract (0xa580a977a3103565993531d19536220A54783397), pass it explicitly to getContract().


Additional Resources