shell-sdk
v0.9.3
Published
TypeScript SDK for Shell Chain — build quantum-safe dApps before Q-Day.
Maintainers
Readme
shell-sdk
TypeScript / JavaScript SDK for Shell Chain — build quantum-safe dApps on the PQVM-native post-quantum blockchain secured before Q-Day.
v0.23.0 aligned
Addresses, system-contract IDs, and signing hashes now match shell-chain v0.23.0: 32-byte
0x…BLAKE3 addresses,algo_idbyteDilithium3=0,MlDsa65=1,SphincsSha2256f=2, and BLAKE3-based transaction / AA signing hashes.
Table of Contents
- Features
- Installation
- Quick start
- Architecture overview
- Module reference
- End-to-end examples
- Key rotation
- Error handling
- TypeScript types reference
- Compatibility
- Release checklist
- Chain reference
Features
- Post-quantum signing — ML-DSA-65 (FIPS 204) and SLH-DSA-SHA2-256f (FIPS 205)
- Shell addresses —
0x-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 RPC —
shell_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 introspection —
getNodeInfo()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-sdkRequires Node.js ≥ 18 for the built-in
fetchAPI andWebCrypto(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_getBlockReceipts → ShellRpcReceipt[] |
| getNodeInfo() | shell_getNodeInfo → ShellNodeInfo (version, block height, peer count, storage profile) |
| getWitness(blockNumberOrHash) | shell_getWitness → ShellWitnessBundle 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"); // trueKeystore
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 newSignerError 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:
- Run
npm testandnpm run typecheck. - Confirm the stable root surface still excludes low-level helpers such as
hexBytesand internal signer maps. - Verify Browser + Node integration tests both cover signer, keystore, and provider RPC flows.
- Review README examples against the current public exports (
shell-sdk,shell-sdk/signer,shell-sdk/transactions). - Check
package.jsonexports,files,version, and repository metadata. - 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.
