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

shell-sdk

v0.9.3

Published

TypeScript SDK for Shell Chain — build quantum-safe dApps before Q-Day.

Readme

shell-sdk

TypeScript / JavaScript SDK for Shell Chain — build quantum-safe dApps on the PQVM-native post-quantum blockchain secured before Q-Day.

Node ≥ 18 ESM only


v0.23.0 aligned

Addresses, system-contract IDs, and signing hashes now match shell-chain v0.23.0: 32-byte 0x… BLAKE3 addresses, algo_id byte Dilithium3=0, MlDsa65=1, SphincsSha2256f=2, and BLAKE3-based transaction / AA signing hashes.

Table of Contents


Features

  • Post-quantum signing — ML-DSA-65 (FIPS 204) and SLH-DSA-SHA2-256f (FIPS 205)
  • Shell addresses0x-prefixed 64-character lowercase hex (full 32-byte BLAKE3) derived from PQ public keys
  • Native account abstraction — key rotation and custom validation code via system contracts
  • viem integration — standard Ethereum JSON-RPC methods via a typed PublicClient
  • Shell-specific RPCshell_getPqPubkey, shell_sendTransaction, shell_getTransactionsByAddress, shell_getNodeInfo, shell_getWitness
  • Reward-aware history types — block/address transaction summaries expose readable shellType, rewardKind, and STARK reward metadata (rewardLayer, rewardSourceHash, originalSize, compressedSize)
  • Node introspectiongetNodeInfo() returns version, block height, peer count, and storage profile; getWitness() fetches raw PQ signatures for any block
  • Encrypted keystore — argon2id KDF + xchacha20-poly1305 cipher; compatible with the Shell CLI

Installation

# npm
npm install shell-sdk

# yarn
yarn add shell-sdk

# pnpm
pnpm add shell-sdk

Requires Node.js ≥ 18 for the built-in fetch API and WebCrypto (crypto.getRandomValues).


Quick start

Send a SHELL transfer in ~10 lines:

import { MlDsa65Adapter } from "shell-sdk/adapters";
import { createShellProvider } from "shell-sdk/provider";
import { ShellSigner } from "shell-sdk/signer";
import { buildTransferTransaction } from "shell-sdk/transactions";
import { parseEther } from "viem";

const adapter = MlDsa65Adapter.generate();
const signer  = new ShellSigner("MlDsa65", adapter);
const from    = signer.getAddress(); // 0x… (64-char hex)

const provider = createShellProvider();
const nonce    = await provider.client.getTransactionCount({ address: from });

const tx       = buildTransferTransaction({ chainId: 424242, nonce, to: "0x…", value: parseEther("1") });
const signed   = await signer.buildSignedTransaction({ tx });
const hash     = await provider.sendTransaction(signed);
console.log("tx hash:", hash);

Architecture overview

PQ addresses

Shell Chain uses 0x-prefixed 64-character lowercase hex addresses — the full 32-byte BLAKE3 hash of the PQ public key with a one-byte algorithm tag:

address_bytes  = BLAKE3(algo_id || public_key)   // full 32 bytes, no truncation
address_string = "0x" + hex_lower(address_bytes)

Algorithm IDs: Dilithium3=0, MlDsa65=1, SphincsSha2256f=2.

There is no Bech32m/pq1… encoding and no separate version byte: Shell-Chain is a clean-slate chain with no backward bridge to any 20-byte Ethereum address model.

Native account abstraction (AA)

Every account on Shell Chain supports two system-contract operations via the AccountManager (0x…0002):

| Operation | Description | |---|---| | rotateKey | Replace the signing key associated with an address | | setValidationCode | Attach a custom EVM validation contract (smart account) | | clearValidationCode | Revert to the default PQ key validation |

These are sent as ordinary transactions whose to field is the AccountManager address.

System contracts

| Name | Address | |---|---| | ValidatorRegistry | 0x0000000000000000000000000000000000000000000000000000000000000001 | | AccountManager | 0x0000000000000000000000000000000000000000000000000000000000000002 |


Module reference

The package root (shell-sdk) is the stable surface for typical app usage. Lower-level constants and helpers that are more likely to change remain available from subpath imports such as shell-sdk/signer and shell-sdk/transactions.

Types

Defined in src/types.ts. All types are re-exported from the package root.

| Type | Description | |---|---| | HexString | Template-literal type 0x${string} | | AddressLike | Any string accepted as an address | | SignatureTypeName | "ML-DSA-65" \| "Dilithium3" \| "MlDsa65" \| "SphincsSha2256f" | | ShellTransactionRequest | Wire format for a Shell transaction | | ShellSignature | { sig_type, data: number[] } | | SignedShellTransaction | Complete signed transaction ready to broadcast | | ShellAccessListItem | EIP-2930 access list entry | | ShellKnownRpcTxType | Literal union of known Shell RPC transaction kinds | | ShellRpcTransactionSummary | Lightweight transaction summary with Shell reward metadata | | ShellTxByAddressPage | Paginated address history response with effective fromBlock/toBlock range | | ShellKdfParams | argon2id parameters inside a keystore | | ShellCipherParams | xchacha20-poly1305 nonce inside a keystore | | ShellEncryptedKey | Full encrypted keystore file structure |


Addresses

import { … } from "shell-sdk/address"

Constants

| Export | Value | Description | |---|---|---| | SHELL_ADDRESS_LENGTH | 32 | Address bytes (full BLAKE3 output) |

Functions

| Function | Signature | Description | |---|---|---| | bytesToShellAddress | (bytes: Uint8Array) → string | Encode 32 raw bytes as a 0x-prefixed 64-char hex address | | shellAddressToBytes | (address: string) → Uint8Array | Decode a 0x… Shell address to its 32 raw bytes | | normalizeShellAddress | (address: string) → string | Validate and lowercase a 0x… Shell address | | deriveShellAddressFromPublicKey | (pk, algoId) → string | Derive a 32-byte 0x… Shell address from a raw PQ public key | | isShellAddress | (address: string) → boolean | Return true if the string is a valid 32-byte 0x… Shell address |

Legacy aliases (bytesToPqAddress, pqAddressToBytes, normalizePqAddress, derivePqAddressFromPublicKey, isPqAddress) remain exported but are deprecated — they now operate on the same 32-byte 0x… format.

Examples:

import {
  deriveShellAddressFromPublicKey,
  isShellAddress,
  normalizeShellAddress,
} from "shell-sdk/address";

const address = deriveShellAddressFromPublicKey(publicKey, 1 /* MlDsa65 */);
// → "0x9a3f…" (64-char lowercase hex)

console.log(isShellAddress(address)); // true

// Validation / normalisation
normalizeShellAddress("0x9A3F…");  // → "0x9a3f…" (lowercased)
normalizeShellAddress("pq1abc…");  // throws: expected 0x + 64-char hex address, got: "pq1abc…"

Provider / RPC

import { … } from "shell-sdk/provider"

Chain config

import { shellDevnet } from "shell-sdk/provider";
// shellDevnet = { id: 424242, name: "Shell Devnet", nativeCurrency: { symbol: "SHELL", decimals: 18 }, … }

Factory functions

| Function | Description | |---|---| | createShellProvider(options?) | Create a ShellProvider (recommended entry point) | | createShellPublicClient(options?) | Create a viem PublicClient over HTTP | | createShellWsClient(options?) | Create a viem PublicClient over WebSocket |

options: CreateShellPublicClientOptions:

| Field | Type | Default | |---|---|---| | chain | Chain | shellDevnet | | rpcHttpUrl | string | http://127.0.0.1:8545 | | rpcWsUrl | string | ws://127.0.0.1:8546 |

ShellProvider class

| Member | Description | |---|---| | .client | Underlying viem PublicClient for all standard eth_* methods | | .rpcHttpUrl | HTTP RPC URL in use | | getPqPubkey(address) | shell_getPqPubkey → hex public key or null | | sendTransaction(signed) | shell_sendTransaction → tx hash string | | getTransactionsByAddress(address, opts) | shell_getTransactionsByAddress with optional fromBlock/toBlock/page/limit; pin toBlock from page 0 for stable full-history pagination | | getBlockReceipts(block) | eth_getBlockReceiptsShellRpcReceipt[] | | getNodeInfo() | shell_getNodeInfoShellNodeInfo (version, block height, peer count, storage profile) | | getWitness(blockNumberOrHash) | shell_getWitnessShellWitnessBundle or null if pruned | | getStorageProfile() | Convenience wrapper around getNodeInfo()ShellStorageProfile \| undefined |

Examples:

import { createShellProvider } from "shell-sdk/provider";

const provider = createShellProvider();

// Standard eth methods via viem
const block = await provider.client.getBlockNumber();
const balance = await provider.client.getBalance({ address: "0x…" });

// Shell-specific methods
const pubkeyHex = await provider.getPqPubkey("0x…");
const txHash    = await provider.sendTransaction(signedTx);

const history = await provider.getTransactionsByAddress("0x…", { page: 0, limit: 20 });
const older = await provider.getTransactionsByAddress("0x…", {
  page: 1,
  limit: 20,
  toBlock: history.toBlock ?? history.to_block,
});

Custom endpoint:

const provider = createShellProvider({ rpcHttpUrl: "https://rpc.shellchain.example" });

Signer & Adapters

import { … } from "shell-sdk/signer" import { … } from "shell-sdk/adapters"

SignerAdapter interface

Any object satisfying this interface can be plugged into ShellSigner:

interface SignerAdapter {
  sign(message: Uint8Array): Promise<Uint8Array>;
  getPublicKey(): Uint8Array;
}

Concrete adapters

| Class | Algorithm | Key sizes | |---|---|---| | MlDsa65Adapter | ML-DSA-65 (FIPS 204); also used as Dilithium3 stand-in | pk: 1952 B, sk: 4032 B | | SlhDsaAdapter | SLH-DSA-SHA2-256f (FIPS 205) | pk: 64 B, sk: 128 B |

Both adapters expose the same API:

// Generate a fresh key pair (optionally from a deterministic seed)
const adapter = MlDsa65Adapter.generate();
const adapter = MlDsa65Adapter.generate(seed /* Uint8Array(32) */);

// Load from an existing key pair (e.g. from a keystore)
const adapter = MlDsa65Adapter.fromKeyPair(publicKey, secretKey);

Key-pair generators

import { generateMlDsa65KeyPair, generateSlhDsaKeyPair } from "shell-sdk/adapters";

const { publicKey, secretKey } = generateMlDsa65KeyPair();
const { publicKey, secretKey } = generateSlhDsaKeyPair(seed /* Uint8Array(96) */);

generateAdapter / adapterFromKeyPair

import { generateAdapter, adapterFromKeyPair } from "shell-sdk/adapters";

const adapter = generateAdapter("MlDsa65");
const adapter = adapterFromKeyPair("SphincsSha2256f", pk, sk);

ShellSigner class

import { ShellSigner } from "shell-sdk/signer";
import { MlDsa65Adapter } from "shell-sdk/adapters";

const signer = new ShellSigner("MlDsa65", MlDsa65Adapter.generate());

| Member | Description | |---|---| | algorithmId | Numeric algorithm ID (0, 1, or 2) | | getPublicKey() | Raw public key bytes | | getAddress() | 0x… 64-char hex Shell address | | sign(message) | Sign an arbitrary byte message → signature bytes | | buildSignedTransaction(options) | Sign txHash and assemble a SignedShellTransaction |

buildSignedTransaction options:

| Field | Type | Description | |---|---|---| | tx | ShellTransactionRequest | The unsigned transaction | | txHash | Uint8Array | RLP-encoded hash to sign | | includePublicKey? | boolean | Embed sender_pubkey for first-time senders |

Helper functions

| Function | Description | |---|---| | signatureTypeFromKeyType(keyType) | Convert keystore key_type string to SignatureTypeName | | publicKeyFromHex(hex) | Hex string → Uint8Array | | buildShellSignature(type, bytes) | Build a ShellSignature object |


Transaction builders

import { … } from "shell-sdk/transactions"

Constants

| Constant | Value | |---|---| | DEFAULT_TX_TYPE | 2 (EIP-1559) | | DEFAULT_TRANSFER_GAS_LIMIT | 21_000 | | DEFAULT_SYSTEM_GAS_LIMIT | 100_000 | | DEFAULT_MAX_FEE_PER_GAS | 1_000_000_000 (1 Gwei) | | DEFAULT_MAX_PRIORITY_FEE_PER_GAS | 100_000_000 (0.1 Gwei) |

buildTransferTransaction

Build a SHELL token transfer (type-2 EIP-1559 transaction):

import { buildTransferTransaction, hashTransaction } from "shell-sdk/transactions";
import { parseEther } from "viem";

const tx = buildTransferTransaction({
  chainId: 424242,
  nonce: 0,
  to: "0x…",
  value: parseEther("1.5"),
});

buildSystemTransaction

Low-level builder for any call to the AccountManager system contract:

const tx = buildSystemTransaction({
  chainId: 424242,
  nonce: 1,
  data: "0xdeadbeef…",
});

buildRotateKeyTransaction

Rotate the signing key for the sender's account:

import { buildRotateKeyTransaction, hashTransaction } from "shell-sdk/transactions";

const tx = buildRotateKeyTransaction({
  chainId: 424242,
  nonce: 2,
  publicKey: newAdapter.getPublicKey(),
  algorithmId: 1, // MlDsa65
});

buildSetValidationCodeTransaction / buildClearValidationCodeTransaction

const tx = buildSetValidationCodeTransaction({
  chainId: 424242,
  nonce: 3,
  codeHash: "0xabc123…", // bytes32 hash of custom validation contract
});

const tx = buildClearValidationCodeTransaction({ chainId: 424242, nonce: 4 });

buildSignedTransaction

Assemble a SignedShellTransaction directly (use ShellSigner.buildSignedTransaction in practice):

import { buildSignedTransaction } from "shell-sdk/transactions";

const signed = buildSignedTransaction({
  from: "0x…",
  tx,
  signature: sigBytes,
  signatureType: "MlDsa65",
  senderPubkey: publicKey, // optional
});

hashTransaction

Compute the canonical shell-chain v0.23.0 signing hash as BLAKE3 over the structured preimage:

chain_id(8B BE) || nonce(8B BE) || to(32B|zero) || value(32B BE) || data || gas_limit(8B BE) || max_fee_per_gas(8B BE) || max_priority_fee_per_gas(8B BE) || sig_type(1B) || tx_type(1B)

For blob transactions (tx_type === 3), append max_fee_per_blob_gas(8B BE) and each 32-byte blob hash.

import { buildTransferTransaction, hashTransaction } from "shell-sdk/transactions";

const tx     = buildTransferTransaction({ chainId: 424242, nonce: 0, to: "0x…", value: 1n });
const txHash = hashTransaction(tx, signer.signatureType); // Uint8Array (32 bytes)
const signed = await signer.buildSignedTransaction({ tx, txHash });
// Or simply: await signer.buildSignedTransaction({ tx })

System contracts

import { … } from "shell-sdk/system-contracts"

Addresses

| Export | Value | |---|---| | validatorRegistryAddress | 0x0000000000000000000000000000000000000000000000000000000000000001 | | accountManagerAddress | 0x0000000000000000000000000000000000000000000000000000000000000002 |

Function selectors

| Export | Selector for | |---|---| | rotateKeySelector | rotateKey(bytes,uint8) | | setValidationCodeSelector | setValidationCode(bytes32) | | clearValidationCodeSelector | clearValidationCode() |

Calldata encoders

import {
  encodeRotateKeyCalldata,
  encodeSetValidationCodeCalldata,
  encodeClearValidationCodeCalldata,
  isSystemContractAddress,
} from "shell-sdk/system-contracts";

const data = encodeRotateKeyCalldata(newPublicKey, 1 /* MlDsa65 */);
const data = encodeSetValidationCodeCalldata("0xcodehash…");
const data = encodeClearValidationCodeCalldata(); // selector only

isSystemContractAddress("0x0000000000000000000000000000000000000000000000000000000000000002"); // true

Keystore

import { … } from "shell-sdk/keystore"

Shell keystore files are JSON objects encrypted with argon2id (KDF) and xchacha20-poly1305 (cipher). The shell CLI generates compatible files.

Keystore format

{
  "version": 1,
  "address": "0x…",
  "key_type": "mldsa65",
  "kdf": "argon2id",
  "kdf_params": { "m_cost": 65536, "t_cost": 3, "p_cost": 1, "salt": "hex…" },
  "cipher": "xchacha20-poly1305",
  "cipher_params": { "nonce": "hex…" },
  "ciphertext": "hex…",
  "public_key": "hex…"
}

Plaintext layout after decryption: [secret_key_bytes][public_key_bytes].

API

| Function | Description | |---|---| | parseEncryptedKey(input) | Parse keystore JSON → ParsedShellKeystore (no decryption) | | validateEncryptedKeyAddress(input) | Parse + verify address matches derived public-key address | | exportEncryptedKeyJson(input) | Pretty-print keystore JSON string | | assertSignerMatchesKeystore(signer, keystore) | Throw if signer algorithm or address doesn't match | | decryptKeystore(input, password) | Full decryption → Promise<ShellSigner> |

Examples:

import { decryptKeystore, parseEncryptedKey } from "shell-sdk/keystore";
import { readFileSync } from "fs";

const json  = readFileSync("./my-key.json", "utf8");

// Inspect without decrypting
const parsed = parseEncryptedKey(json);
console.log(parsed.canonicalAddress); // 0x…
console.log(parsed.signatureType);    // "MlDsa65"

// Decrypt
const signer = await decryptKeystore(json, "my-passphrase");
console.log(signer.getAddress()); // 0x…

End-to-end examples

Key generation → address → transfer → submit

import { MlDsa65Adapter } from "shell-sdk/adapters";
import { createShellProvider } from "shell-sdk/provider";
import { ShellSigner } from "shell-sdk/signer";
import { buildTransferTransaction, hashTransaction } from "shell-sdk/transactions";


// 1. Generate keys
const adapter = MlDsa65Adapter.generate();
const signer  = new ShellSigner("MlDsa65", adapter);
const from    = signer.getAddress();      // 0x…

console.log("Address:", from);

// 2. Connect to devnet
const provider = createShellProvider(); // defaults to http://127.0.0.1:8545

// 3. Get current nonce
const nonce = await provider.client.getTransactionCount({ address: from });

// 4. Build the transaction
const tx = buildTransferTransaction({
  chainId: 424242,
  nonce,
  to: "0x…",
  value: parseEther("0.5"),
});

// 5. Compute the canonical BLAKE3 signing hash
const txHash = hashTransaction(tx, signer.signatureType);

// 6. Sign and build the complete signed transaction
//    includePublicKey=true is required for accounts that haven't been seen on-chain yet
const signed = await signer.buildSignedTransaction({
  tx,
  txHash: txHash,
  includePublicKey: true,
});

// 7. Broadcast
const hash = await provider.sendTransaction(signed);
console.log("Transaction hash:", hash);

Load from keystore and send

import { decryptKeystore } from "shell-sdk/keystore";
import { createShellProvider } from "shell-sdk/provider";
import { buildTransferTransaction, hashTransaction } from "shell-sdk/transactions";
import { readFileSync } from "fs";
import { parseEther } from "viem";

const signer   = await decryptKeystore(readFileSync("./key.json", "utf8"), process.env.PASSPHRASE!);
const provider = createShellProvider();
const nonce    = await provider.client.getTransactionCount({ address: signer.getAddress() });

const tx = buildTransferTransaction({
  chainId: 424242,
  nonce,
  to: "0x…",
  value: parseEther("10"),
});

const txHash = hashTransaction(tx, signer.signatureType);
const signed = await signer.buildSignedTransaction({ tx, txHash });
const hash   = await provider.sendTransaction(signed);
console.log(hash);

Wallet extension background flow

This is the recommended shape for a Chrome extension background worker: keep the decrypted signer only in memory, fetch the latest nonce from RPC, and use the stable root entrypoint for the common path.

import { createShellProvider, buildTransferTransaction, hashTransaction } from "shell-sdk";

async function submitTransfer({ signer, to, value, rpcHttpUrl }: {
  signer: { getAddress(): string; buildSignedTransaction(args: { tx: unknown; txHash: Uint8Array; includePublicKey?: boolean }): Promise<unknown> };
  to: string;
  value: bigint;
  rpcHttpUrl: string;
}) {
  const provider = createShellProvider({ rpcHttpUrl });
  const nonce = await provider.client.getTransactionCount({ address: signer.getAddress() });

  const tx = buildTransferTransaction({
    chainId: 424242,
    nonce,
    to,
    value,
  });
  const txHash = hashTransaction(tx, signer.signatureType);
  const signed = await signer.buildSignedTransaction({ tx, txHash, includePublicKey: nonce === 0 });

  return provider.sendTransaction(signed);
}

Minimal dApp flow

For a lightweight web app, keep the provider in the page and delegate signing to an injected wallet or background bridge:

import { createShellProvider, normalizePqAddress } from "shell-sdk";

const provider = createShellProvider({
  rpcHttpUrl: "https://rpc.testnet.shell.network",
});

const account = normalizeShellAddress("0x9a3f…");
const history = await provider.getTransactionsByAddress(account, { page: 1, limit: 10 });

console.log("recent txs:", history.transactions);
console.log("total:", history.total);

Key rotation

Shell Chain accounts support key rotation — replacing the signing key without changing the account address. This is a critical security feature for post-quantum safety.

import { MlDsa65Adapter } from "shell-sdk/adapters";
import { ShellSigner } from "shell-sdk/signer";
import { createShellProvider } from "shell-sdk/provider";
import { buildRotateKeyTransaction, hashTransaction } from "shell-sdk/transactions";

const provider = createShellProvider();

// Current signer (must sign the rotation transaction)
const currentSigner = await decryptKeystore(readFileSync("old-key.json", "utf8"), passphrase);

// New key pair to rotate to
const newAdapter = MlDsa65Adapter.generate();
const newSigner  = new ShellSigner("MlDsa65", newAdapter);

const nonce = await provider.client.getTransactionCount({ address: currentSigner.getAddress() });

// Build the rotateKey system transaction
const tx = buildRotateKeyTransaction({
  chainId: 424242,
  nonce,
  publicKey: newAdapter.getPublicKey(),
  algorithmId: newSigner.algorithmId, // 1 for MlDsa65
});

const txHash = hashTransaction(tx, currentSigner.signatureType);

// Sign with the CURRENT key
const signed = await currentSigner.buildSignedTransaction({ tx, txHash });
const hash   = await provider.sendTransaction(signed);
console.log("Key rotated! tx:", hash);
// From the next transaction onwards, use newSigner

Error handling

All SDK functions throw standard Error instances. Common error messages:

| Error message | Cause | |---|---| | expected 32 address bytes, got N | Wrong-length bytes passed to address helpers | | expected 0x prefix, got X | Shell address must start with 0x | | invalid Shell address length | Address must be 32 raw bytes / 64 hex characters | | unsupported key type: X | Keystore key_type not recognised | | unsupported kdf: X | Only argon2id is supported | | unsupported cipher: X | Only xchacha20-poly1305 is supported | | keystore address mismatch | Declared address ≠ derived address in the keystore | | decrypted public key mismatch | Wrong password or corrupt keystore | | rpc request failed: 4XX/5XX | HTTP-level RPC error | | [code] message | JSON-RPC error returned by the node |

try {
  const signer = await decryptKeystore(json, "wrong-password");
} catch (err) {
  if (err instanceof Error && err.message.includes("mismatch")) {
    console.error("Wrong password or corrupt keystore file");
  }
}

TypeScript types reference

// Branded hex string: "0x" + arbitrary hex chars
type HexString = `0x${string}`;

// Any value accepted as a Shell address (0x… 64-char hex)
type AddressLike = string;

// Post-quantum signature algorithm names
type SignatureTypeName = "Dilithium3" | "MlDsa65" | "SphincsSha2256f";

// Wire format sent to shell_sendTransaction
interface SignedShellTransaction {
  from: AddressLike;
  tx: ShellTransactionRequest;
  signature: ShellSignature;
  sender_pubkey?: number[] | null;
}

interface ShellTransactionRequest {
  chain_id: number;
  nonce: number;
  to: AddressLike | null;
  value: string;           // hex-encoded bigint, e.g. "0xde0b6b3a7640000"
  data: HexString;
  gas_limit: number;
  max_fee_per_gas: number;
  max_priority_fee_per_gas: number;
  access_list?: ShellAccessListItem[] | null;
  tx_type?: number;
  max_fee_per_blob_gas?: number | null;
  blob_versioned_hashes?: HexString[] | null;
}

interface ShellSignature {
  sig_type: SignatureTypeName;
  data: number[];          // raw signature bytes as a JS number array
}

Compatibility

| Requirement | Version | |---|---| | Node.js | ≥ 18 (for fetch and crypto.getRandomValues) | | TypeScript | ≥ 5.0 recommended | | Module format | ESM only ("type": "module") | | Browser | Any modern browser with WebCrypto; bundler required (Vite/webpack/esbuild) |

Key dependencies:

| Package | Purpose | |---|---| | viem | Ethereum JSON-RPC client, ABI encoding | | @noble/post-quantum | ML-DSA-65 and SLH-DSA-SHA2-256f | | @noble/hashes | BLAKE3 | | @noble/ciphers | xchacha20-poly1305 | | hash-wasm | argon2id (WASM) |


Release checklist

Before publishing a shell-sdk release candidate:

  1. Run npm test and npm run typecheck.
  2. Confirm the stable root surface still excludes low-level helpers such as hexBytes and internal signer maps.
  3. Verify Browser + Node integration tests both cover signer, keystore, and provider RPC flows.
  4. Review README examples against the current public exports (shell-sdk, shell-sdk/signer, shell-sdk/transactions).
  5. Check package.json exports, files, version, and repository metadata.
  6. Build once from a clean tree and smoke-import the package root plus subpaths from dist/.

Chain reference

| Parameter | Value | |---|---| | Chain ID | 424242 | | Network name | Shell Devnet | | Native currency | SHELL (18 decimals) | | HTTP RPC | http://127.0.0.1:8545 | | WebSocket RPC | ws://127.0.0.1:8546 | | Address format | 0x + 64 lowercase hex chars (32-byte BLAKE3 hash of algo_id ‖ pubkey) | | Default tx type | 2 (EIP-1559) | | Default gas limit (transfer) | 21 000 | | Default gas limit (system) | 100 000 | | Default max fee per gas | 1 Gwei |

For full chain documentation, validator setup, and the Shell CLI reference, see the project wiki or official docs site.