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.
- Features
- Installation
- Quick start
- Architecture overview
- Module reference
- End-to-end examples
- Key rotation
- Error handling
- TypeScript types reference
- Compatibility
- Release checklist
- Chain reference
- 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
# 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).
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);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.
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.
| Name | Address |
|---|---|
| ValidatorRegistry | 0x0000000000000000000000000000000000000000000000000000000000000001 |
| AccountManager | 0x0000000000000000000000000000000000000000000000000000000000000002 |
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.
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 |
import { … } from "shell-sdk/address"
| Export | Value | Description |
|---|---|---|
SHELL_ADDRESS_LENGTH |
32 |
Address bytes (full BLAKE3 output) |
| 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…"import { … } from "shell-sdk/provider"
import { shellDevnet } from "shell-sdk/provider";
// shellDevnet = { id: 424242, name: "Shell Devnet", nativeCurrency: { symbol: "SHELL", decimals: 18 }, … }| 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 |
| 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" });import { … } from "shell-sdk/signer"
import { … } from "shell-sdk/adapters"
Any object satisfying this interface can be plugged into ShellSigner:
interface SignerAdapter {
sign(message: Uint8Array): Promise<Uint8Array>;
getPublicKey(): Uint8Array;
}| 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);import { generateMlDsa65KeyPair, generateSlhDsaKeyPair } from "shell-sdk/adapters";
const { publicKey, secretKey } = generateMlDsa65KeyPair();
const { publicKey, secretKey } = generateSlhDsaKeyPair(seed /* Uint8Array(96) */);import { generateAdapter, adapterFromKeyPair } from "shell-sdk/adapters";
const adapter = generateAdapter("MlDsa65");
const adapter = adapterFromKeyPair("SphincsSha2256f", pk, sk);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 |
| Function | Description |
|---|---|
signatureTypeFromKeyType(keyType) |
Convert keystore key_type string to SignatureTypeName |
publicKeyFromHex(hex) |
Hex string → Uint8Array |
buildShellSignature(type, bytes) |
Build a ShellSignature object |
import { … } from "shell-sdk/transactions"
| 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) |
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"),
});Low-level builder for any call to the AccountManager system contract:
const tx = buildSystemTransaction({
chainId: 424242,
nonce: 1,
data: "0xdeadbeef…",
});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
});const tx = buildSetValidationCodeTransaction({
chainId: 424242,
nonce: 3,
codeHash: "0xabc123…", // bytes32 hash of custom validation contract
});
const tx = buildClearValidationCodeTransaction({ chainId: 424242, nonce: 4 });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
});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 })import { … } from "shell-sdk/system-contracts"
| Export | Value |
|---|---|
validatorRegistryAddress |
0x0000000000000000000000000000000000000000000000000000000000000001 |
accountManagerAddress |
0x0000000000000000000000000000000000000000000000000000000000000002 |
| Export | Selector for |
|---|---|
rotateKeySelector |
rotateKey(bytes,uint8) |
setValidationCodeSelector |
setValidationCode(bytes32) |
clearValidationCodeSelector |
clearValidationCode() |
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"); // trueimport { … } from "shell-sdk/keystore"
Shell keystore files are JSON objects encrypted with argon2id (KDF) and xchacha20-poly1305 (cipher). The shell CLI generates compatible files.
Plaintext layout after decryption: [secret_key_bytes][public_key_bytes].
| 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…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);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);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);
}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);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 newSignerAll 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");
}
}// 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
}| 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) |
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/.
| 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.
{ "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…" }