shell-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { bytesToHexAddress, PQ_ADDRESS_HRP, PQ_ADDRESS_LENGTH, PQ_ADDRESS_VERSION_V1, bytesToPqAddress, derivePqAddressFromPublicKey, isPqAddress, normalizeHexAddress, normalizePqAddress, pqAddressVersion, pqAddressToBytes, } from "./address.js";
2
+ export { createShellProvider, createShellPublicClient, createShellWsClient, ShellProvider, shellDevnet, } from "./provider.js";
3
+ export { accountManagerAddress, accountManagerHexAddress, clearValidationCodeSelector, encodeClearValidationCodeCalldata, encodeRotateKeyCalldata, encodeSetValidationCodeCalldata, isSystemContractAddress, rotateKeySelector, setValidationCodeSelector, validatorRegistryAddress, validatorRegistryHexAddress, } from "./system-contracts.js";
4
+ export { buildClearValidationCodeTransaction, buildRotateKeyTransaction, buildSetValidationCodeTransaction, buildSignature, buildSignedTransaction, buildSystemTransaction, buildTransaction, buildTransferTransaction, DEFAULT_MAX_FEE_PER_GAS, DEFAULT_MAX_PRIORITY_FEE_PER_GAS, DEFAULT_SYSTEM_GAS_LIMIT, DEFAULT_TRANSFER_GAS_LIMIT, DEFAULT_TX_TYPE, hashTransaction, hexBytes, } from "./transactions.js";
5
+ export { assertSignerMatchesKeystore, decryptKeystore, exportEncryptedKeyJson, parseEncryptedKey, validateEncryptedKeyAddress, } from "./keystore.js";
6
+ export { adapterFromKeyPair, generateAdapter, generateMlDsa65KeyPair, generateSlhDsaKeyPair, MlDsa65Adapter, SlhDsaAdapter, } from "./adapters.js";
7
+ export { buildShellSignature, KEY_TYPE_TO_SIGNATURE_TYPE, publicKeyFromHex, ShellSigner, SIGNATURE_TYPE_IDS, signatureTypeFromKeyType, } from "./signer.js";
@@ -0,0 +1,99 @@
1
+ import { ShellSigner } from "./signer.js";
2
+ import type { ShellEncryptedKey, SignatureTypeName } from "./types.js";
3
+ /**
4
+ * Parsed metadata from an encrypted Shell keystore file.
5
+ *
6
+ * Produced by {@link parseEncryptedKey}; does **not** contain the decrypted private key.
7
+ */
8
+ export interface ParsedShellKeystore {
9
+ /** The raw keystore object as parsed from JSON. */
10
+ raw: ShellEncryptedKey;
11
+ /** Resolved signature algorithm name. */
12
+ signatureType: SignatureTypeName;
13
+ /** Numeric algorithm ID (0/1/2). */
14
+ algorithmId: number;
15
+ /** Raw public key bytes decoded from `raw.public_key`. */
16
+ publicKey: Uint8Array;
17
+ /** Canonical `pq1…` address derived from `publicKey`. */
18
+ canonicalAddress: string;
19
+ /** `0x`-prefixed hex representation of the same address. */
20
+ hexAddress: string;
21
+ }
22
+ /**
23
+ * Parse a Shell keystore file (string or object) and extract public metadata.
24
+ *
25
+ * Does **not** decrypt the private key. Use {@link decryptKeystore} for full
26
+ * decryption.
27
+ *
28
+ * @param input - Keystore JSON string or already-parsed {@link ShellEncryptedKey} object.
29
+ * @returns {@link ParsedShellKeystore} with algorithm info, public key, and derived addresses.
30
+ * @throws {Error} If `key_type` is not a recognised algorithm.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const parsed = parseEncryptedKey(readFileSync("key.json", "utf8"));
35
+ * console.log(parsed.canonicalAddress); // pq1…
36
+ * console.log(parsed.signatureType); // "MlDsa65"
37
+ * ```
38
+ */
39
+ export declare function parseEncryptedKey(input: string | ShellEncryptedKey): ParsedShellKeystore;
40
+ /**
41
+ * Parse a keystore and verify that the declared address matches the public key.
42
+ *
43
+ * @param input - Keystore JSON string or object.
44
+ * @returns {@link ParsedShellKeystore} if validation passes.
45
+ * @throws {Error} If the declared address does not match the address derived from the public key.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const parsed = validateEncryptedKeyAddress(json); // throws if tampered
50
+ * ```
51
+ */
52
+ export declare function validateEncryptedKeyAddress(input: string | ShellEncryptedKey): ParsedShellKeystore;
53
+ /**
54
+ * Serialise a keystore to a pretty-printed JSON string.
55
+ *
56
+ * @param input - Keystore JSON string or object.
57
+ * @returns Indented JSON string (2-space indent).
58
+ */
59
+ export declare function exportEncryptedKeyJson(input: string | ShellEncryptedKey): string;
60
+ /**
61
+ * Assert that a {@link ShellSigner} corresponds to the given keystore.
62
+ *
63
+ * Checks that the signature algorithm and derived address both match.
64
+ *
65
+ * @param signer - The signer to verify.
66
+ * @param keystore - The parsed keystore to compare against.
67
+ * @throws {Error} If the algorithm or address does not match.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * assertSignerMatchesKeystore(signer, parsed); // throws on mismatch
72
+ * ```
73
+ */
74
+ export declare function assertSignerMatchesKeystore(signer: ShellSigner, keystore: ParsedShellKeystore): void;
75
+ /**
76
+ * Decrypt a Shell keystore file and return a ready-to-use {@link ShellSigner}.
77
+ *
78
+ * **KDF**: argon2id (parameters from `kdf_params`)
79
+ * **Cipher**: xchacha20-poly1305 (24-byte nonce from `cipher_params`)
80
+ * **Plaintext layout**: `[secret_key_bytes][public_key_bytes]`
81
+ *
82
+ * The decrypted public key is compared against `raw.public_key` to detect
83
+ * wrong passwords or corrupt files before returning the signer.
84
+ *
85
+ * @param input - Keystore JSON string or object.
86
+ * @param password - The passphrase used to encrypt the key.
87
+ * @returns A fully configured `ShellSigner` ready for signing transactions.
88
+ * @throws {Error} If the KDF or cipher is unsupported.
89
+ * @throws {Error} If decryption fails (wrong password or corrupt ciphertext).
90
+ * @throws {Error} If the decrypted public key does not match the stored one.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const signer = await decryptKeystore(readFileSync("key.json", "utf8"), "my-passphrase");
95
+ * console.log(signer.getAddress()); // pq1…
96
+ * const hash = await provider.sendTransaction(await signer.buildSignedTransaction(…));
97
+ * ```
98
+ */
99
+ export declare function decryptKeystore(input: string | ShellEncryptedKey, password: string): Promise<ShellSigner>;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Encrypted keystore utilities for Shell Chain.
3
+ *
4
+ * Shell keystore files are JSON objects that store a post-quantum private key
5
+ * encrypted with:
6
+ * - **KDF**: argon2id (memory-hard password derivation)
7
+ * - **Cipher**: xchacha20-poly1305 (authenticated encryption)
8
+ *
9
+ * They are generated by the Shell CLI (`shell key generate`) and are
10
+ * compatible with the SDK's `decryptKeystore` function.
11
+ *
12
+ * Plaintext layout after decryption: `[secret_key_bytes][public_key_bytes]`.
13
+ *
14
+ * @module keystore
15
+ */
16
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
17
+ import { argon2id } from "hash-wasm";
18
+ import { derivePqAddressFromPublicKey, normalizeHexAddress, normalizePqAddress } from "./address.js";
19
+ import { adapterFromKeyPair } from "./adapters.js";
20
+ import { ShellSigner, publicKeyFromHex, signatureTypeFromKeyType } from "./signer.js";
21
+ const SIG_IDS = { Dilithium3: 0, MlDsa65: 1, SphincsSha2256f: 2 };
22
+ /**
23
+ * Parse a Shell keystore file (string or object) and extract public metadata.
24
+ *
25
+ * Does **not** decrypt the private key. Use {@link decryptKeystore} for full
26
+ * decryption.
27
+ *
28
+ * @param input - Keystore JSON string or already-parsed {@link ShellEncryptedKey} object.
29
+ * @returns {@link ParsedShellKeystore} with algorithm info, public key, and derived addresses.
30
+ * @throws {Error} If `key_type` is not a recognised algorithm.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const parsed = parseEncryptedKey(readFileSync("key.json", "utf8"));
35
+ * console.log(parsed.canonicalAddress); // pq1…
36
+ * console.log(parsed.signatureType); // "MlDsa65"
37
+ * ```
38
+ */
39
+ export function parseEncryptedKey(input) {
40
+ const raw = typeof input === "string" ? JSON.parse(input) : input;
41
+ const signatureType = signatureTypeFromKeyType(raw.key_type);
42
+ const algorithmId = SIG_IDS[signatureType];
43
+ const publicKey = publicKeyFromHex(raw.public_key);
44
+ const canonicalAddress = derivePqAddressFromPublicKey(publicKey, algorithmId);
45
+ const hexAddress = normalizeHexAddress(canonicalAddress);
46
+ return { raw, signatureType, algorithmId, publicKey, canonicalAddress, hexAddress };
47
+ }
48
+ /**
49
+ * Parse a keystore and verify that the declared address matches the public key.
50
+ *
51
+ * @param input - Keystore JSON string or object.
52
+ * @returns {@link ParsedShellKeystore} if validation passes.
53
+ * @throws {Error} If the declared address does not match the address derived from the public key.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const parsed = validateEncryptedKeyAddress(json); // throws if tampered
58
+ * ```
59
+ */
60
+ export function validateEncryptedKeyAddress(input) {
61
+ const parsed = parseEncryptedKey(input);
62
+ const declared = normalizePqAddress(parsed.raw.address);
63
+ if (declared !== parsed.canonicalAddress) {
64
+ throw new Error("keystore address mismatch: declared=" + declared + " derived=" + parsed.canonicalAddress);
65
+ }
66
+ return parsed;
67
+ }
68
+ /**
69
+ * Serialise a keystore to a pretty-printed JSON string.
70
+ *
71
+ * @param input - Keystore JSON string or object.
72
+ * @returns Indented JSON string (2-space indent).
73
+ */
74
+ export function exportEncryptedKeyJson(input) {
75
+ return JSON.stringify(typeof input === "string" ? JSON.parse(input) : input, null, 2);
76
+ }
77
+ /**
78
+ * Assert that a {@link ShellSigner} corresponds to the given keystore.
79
+ *
80
+ * Checks that the signature algorithm and derived address both match.
81
+ *
82
+ * @param signer - The signer to verify.
83
+ * @param keystore - The parsed keystore to compare against.
84
+ * @throws {Error} If the algorithm or address does not match.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * assertSignerMatchesKeystore(signer, parsed); // throws on mismatch
89
+ * ```
90
+ */
91
+ export function assertSignerMatchesKeystore(signer, keystore) {
92
+ if (signer.signatureType !== keystore.signatureType) {
93
+ throw new Error("algorithm mismatch: signer=" + signer.signatureType + " keystore=" + keystore.signatureType);
94
+ }
95
+ const addr = signer.getAddress();
96
+ if (addr !== keystore.canonicalAddress) {
97
+ throw new Error("address mismatch: signer=" + addr + " keystore=" + keystore.canonicalAddress);
98
+ }
99
+ }
100
+ function hexToBytes(hex) {
101
+ if (hex.length % 2 !== 0)
102
+ throw new Error("invalid hex");
103
+ const buf = new Uint8Array(hex.length / 2);
104
+ for (let i = 0; i < buf.length; i++)
105
+ buf[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
106
+ return buf;
107
+ }
108
+ /**
109
+ * Decrypt a Shell keystore file and return a ready-to-use {@link ShellSigner}.
110
+ *
111
+ * **KDF**: argon2id (parameters from `kdf_params`)
112
+ * **Cipher**: xchacha20-poly1305 (24-byte nonce from `cipher_params`)
113
+ * **Plaintext layout**: `[secret_key_bytes][public_key_bytes]`
114
+ *
115
+ * The decrypted public key is compared against `raw.public_key` to detect
116
+ * wrong passwords or corrupt files before returning the signer.
117
+ *
118
+ * @param input - Keystore JSON string or object.
119
+ * @param password - The passphrase used to encrypt the key.
120
+ * @returns A fully configured `ShellSigner` ready for signing transactions.
121
+ * @throws {Error} If the KDF or cipher is unsupported.
122
+ * @throws {Error} If decryption fails (wrong password or corrupt ciphertext).
123
+ * @throws {Error} If the decrypted public key does not match the stored one.
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const signer = await decryptKeystore(readFileSync("key.json", "utf8"), "my-passphrase");
128
+ * console.log(signer.getAddress()); // pq1…
129
+ * const hash = await provider.sendTransaction(await signer.buildSignedTransaction(…));
130
+ * ```
131
+ */
132
+ export async function decryptKeystore(input, password) {
133
+ const parsed = validateEncryptedKeyAddress(input);
134
+ const ek = parsed.raw;
135
+ if (ek.kdf !== "argon2id")
136
+ throw new Error("unsupported kdf: " + ek.kdf);
137
+ if (ek.cipher !== "xchacha20-poly1305")
138
+ throw new Error("unsupported cipher: " + ek.cipher);
139
+ const salt = hexToBytes(ek.kdf_params.salt);
140
+ const nonce = hexToBytes(ek.cipher_params.nonce);
141
+ const ciphertext = hexToBytes(ek.ciphertext);
142
+ const derivedKeyHex = await argon2id({
143
+ password,
144
+ salt,
145
+ iterations: ek.kdf_params.t_cost,
146
+ memorySize: ek.kdf_params.m_cost,
147
+ parallelism: ek.kdf_params.p_cost,
148
+ hashLength: 32,
149
+ outputType: "hex",
150
+ });
151
+ const derivedKey = hexToBytes(derivedKeyHex);
152
+ const chacha = xchacha20poly1305(derivedKey, nonce);
153
+ const plaintext = chacha.decrypt(ciphertext);
154
+ const pubkeyLen = parsed.publicKey.length;
155
+ const skLen = plaintext.length - pubkeyLen;
156
+ if (skLen <= 0) {
157
+ throw new Error("payload too short: " + plaintext.length + " bytes");
158
+ }
159
+ const secretKey = plaintext.slice(0, skLen);
160
+ const derivedPubkey = plaintext.slice(skLen);
161
+ if (!derivedPubkey.every((b, i) => b === parsed.publicKey[i])) {
162
+ throw new Error("decrypted public key mismatch");
163
+ }
164
+ const adapter = adapterFromKeyPair(parsed.signatureType, parsed.publicKey, secretKey);
165
+ return new ShellSigner(parsed.signatureType, adapter);
166
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Shell Chain RPC provider.
3
+ *
4
+ * Wraps a [viem](https://viem.sh) `PublicClient` for standard `eth_*` methods
5
+ * and adds raw JSON-RPC support for Shell-specific methods (`shell_*`).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createShellProvider } from "shell-sdk/provider";
10
+ *
11
+ * const provider = createShellProvider();
12
+ * const block = await provider.client.getBlockNumber();
13
+ * const hash = await provider.sendTransaction(signedTx);
14
+ * ```
15
+ *
16
+ * @module provider
17
+ */
18
+ import { type Chain, type PublicClient } from "viem";
19
+ import type { SignedShellTransaction } from "./types.js";
20
+ /**
21
+ * Pre-configured viem chain definition for Shell Devnet.
22
+ *
23
+ * - Chain ID: `424242`
24
+ * - HTTP RPC: `http://127.0.0.1:8545`
25
+ * - WebSocket RPC: `ws://127.0.0.1:8546`
26
+ * - Native currency: SHELL (18 decimals)
27
+ */
28
+ export declare const shellDevnet: {
29
+ blockExplorers?: {
30
+ [key: string]: {
31
+ name: string;
32
+ url: string;
33
+ apiUrl?: string | undefined;
34
+ };
35
+ default: {
36
+ name: string;
37
+ url: string;
38
+ apiUrl?: string | undefined;
39
+ };
40
+ } | undefined | undefined;
41
+ blockTime?: number | undefined | undefined;
42
+ contracts?: {
43
+ [x: string]: import("viem").ChainContract | {
44
+ [sourceId: number]: import("viem").ChainContract | undefined;
45
+ } | undefined;
46
+ ensRegistry?: import("viem").ChainContract | undefined;
47
+ ensUniversalResolver?: import("viem").ChainContract | undefined;
48
+ multicall3?: import("viem").ChainContract | undefined;
49
+ erc6492Verifier?: import("viem").ChainContract | undefined;
50
+ } | undefined;
51
+ ensTlds?: readonly string[] | undefined;
52
+ id: 424242;
53
+ name: "Shell Devnet";
54
+ nativeCurrency: {
55
+ readonly decimals: 18;
56
+ readonly name: "SHELL";
57
+ readonly symbol: "SHELL";
58
+ };
59
+ experimental_preconfirmationTime?: number | undefined | undefined;
60
+ rpcUrls: {
61
+ readonly default: {
62
+ readonly http: readonly ["http://127.0.0.1:8545"];
63
+ readonly webSocket: readonly ["ws://127.0.0.1:8546"];
64
+ };
65
+ };
66
+ sourceId?: number | undefined | undefined;
67
+ testnet?: boolean | undefined | undefined;
68
+ custom?: Record<string, unknown> | undefined;
69
+ extendSchema?: Record<string, unknown> | undefined;
70
+ fees?: import("viem").ChainFees<undefined> | undefined;
71
+ formatters?: undefined;
72
+ prepareTransactionRequest?: ((args: import("viem").PrepareTransactionRequestParameters, options: {
73
+ phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
74
+ }) => Promise<import("viem").PrepareTransactionRequestParameters>) | [fn: ((args: import("viem").PrepareTransactionRequestParameters, options: {
75
+ phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
76
+ }) => Promise<import("viem").PrepareTransactionRequestParameters>) | undefined, options: {
77
+ runAt: readonly ("beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters")[];
78
+ }] | undefined;
79
+ serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
80
+ verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
81
+ };
82
+ /** Options accepted by the provider and client factory functions. */
83
+ export interface CreateShellPublicClientOptions {
84
+ /** Override the viem chain config. Defaults to {@link shellDevnet}. */
85
+ chain?: Chain;
86
+ /** Override the HTTP RPC URL. Defaults to the chain's first HTTP URL. */
87
+ rpcHttpUrl?: string;
88
+ /** Override the WebSocket RPC URL. Defaults to the chain's first WS URL. */
89
+ rpcWsUrl?: string;
90
+ }
91
+ /** A typed alias for a viem `PublicClient`. */
92
+ export type ShellPublicClient = PublicClient;
93
+ /**
94
+ * RPC client for Shell Chain.
95
+ *
96
+ * Combines a viem `PublicClient` (accessible via `.client`) for all standard
97
+ * Ethereum JSON-RPC methods with direct `fetch`-based calls for Shell-specific
98
+ * `shell_*` methods.
99
+ *
100
+ * Prefer constructing via {@link createShellProvider} rather than instantiating
101
+ * this class directly.
102
+ */
103
+ export declare class ShellProvider {
104
+ /** Underlying viem `PublicClient` for standard `eth_*` methods. */
105
+ readonly client: ShellPublicClient;
106
+ /** HTTP RPC URL used for Shell-specific JSON-RPC calls. */
107
+ readonly rpcHttpUrl: string;
108
+ constructor(client: ShellPublicClient, rpcHttpUrl: string);
109
+ private request;
110
+ /**
111
+ * Retrieve the on-chain public key for an address.
112
+ *
113
+ * Calls `shell_getPqPubkey`. Returns `null` if the address has not yet
114
+ * submitted a transaction (public key is only recorded on first send).
115
+ *
116
+ * @param address - A `pq1…` or `0x…` address.
117
+ * @returns Hex-encoded public key string, or `null` if unknown.
118
+ */
119
+ getPqPubkey(address: string): Promise<string | null>;
120
+ /**
121
+ * Broadcast a signed Shell transaction.
122
+ *
123
+ * Calls `shell_sendTransaction`.
124
+ *
125
+ * @param signedTransaction - A fully-signed transaction built with {@link ShellSigner.buildSignedTransaction}.
126
+ * @returns The transaction hash as a hex string.
127
+ * @throws {Error} If the node rejects the transaction.
128
+ */
129
+ sendTransaction(signedTransaction: SignedShellTransaction): Promise<string>;
130
+ /**
131
+ * Fetch paginated transaction history for an address.
132
+ *
133
+ * Calls `shell_getTransactionsByAddress`.
134
+ *
135
+ * @param address - The address to query.
136
+ * @param options - Optional pagination and block range filters.
137
+ * @param options.fromBlock - Start of block range (inclusive).
138
+ * @param options.toBlock - End of block range (inclusive).
139
+ * @param options.page - Zero-based page index.
140
+ * @param options.limit - Maximum number of results per page.
141
+ * @returns Raw paginated response from the node.
142
+ */
143
+ getTransactionsByAddress(address: string, options?: {
144
+ fromBlock?: number;
145
+ toBlock?: number;
146
+ page?: number;
147
+ limit?: number;
148
+ }): Promise<unknown>;
149
+ /**
150
+ * Fetch all transaction receipts for a block.
151
+ *
152
+ * Calls `eth_getBlockReceipts`.
153
+ *
154
+ * @param block - Block identifier: `"latest"`, `"earliest"`, or a hex block number.
155
+ * @returns Array of transaction receipt objects.
156
+ */
157
+ getBlockReceipts(block: string): Promise<unknown[]>;
158
+ }
159
+ /**
160
+ * Create a viem `PublicClient` connected to Shell Chain over HTTP.
161
+ *
162
+ * @param options - Optional chain and URL overrides.
163
+ * @returns A configured viem `PublicClient`.
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * const client = createShellPublicClient();
168
+ * const blockNumber = await client.getBlockNumber();
169
+ * ```
170
+ */
171
+ export declare function createShellPublicClient(options?: CreateShellPublicClientOptions): ShellPublicClient;
172
+ /**
173
+ * Create a viem `PublicClient` connected to Shell Chain over WebSocket.
174
+ *
175
+ * Useful for subscribing to `newHeads`, `logs`, and other real-time events.
176
+ *
177
+ * @param options - Optional chain and URL overrides.
178
+ * @returns A configured viem `PublicClient` using a WebSocket transport.
179
+ * @throws {Error} If no WebSocket URL is available for the chain.
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * const wsClient = createShellWsClient();
184
+ * const unwatch = wsClient.watchBlocks({ onBlock: (block) => console.log(block.number) });
185
+ * ```
186
+ */
187
+ export declare function createShellWsClient(options?: CreateShellPublicClientOptions): ShellPublicClient;
188
+ /**
189
+ * Create a {@link ShellProvider} — the recommended entry point for interacting
190
+ * with Shell Chain.
191
+ *
192
+ * Combines a viem HTTP `PublicClient` with Shell-specific RPC helpers.
193
+ *
194
+ * @param options - Optional chain and URL overrides.
195
+ * @returns A fully configured `ShellProvider`.
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const provider = createShellProvider();
200
+ * const balance = await provider.client.getBalance({ address: signer.getHexAddress() });
201
+ * const hash = await provider.sendTransaction(signedTx);
202
+ * ```
203
+ */
204
+ export declare function createShellProvider(options?: CreateShellPublicClientOptions): ShellProvider;
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Shell Chain RPC provider.
3
+ *
4
+ * Wraps a [viem](https://viem.sh) `PublicClient` for standard `eth_*` methods
5
+ * and adds raw JSON-RPC support for Shell-specific methods (`shell_*`).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createShellProvider } from "shell-sdk/provider";
10
+ *
11
+ * const provider = createShellProvider();
12
+ * const block = await provider.client.getBlockNumber();
13
+ * const hash = await provider.sendTransaction(signedTx);
14
+ * ```
15
+ *
16
+ * @module provider
17
+ */
18
+ import { createPublicClient, defineChain, http, webSocket, } from "viem";
19
+ /**
20
+ * Pre-configured viem chain definition for Shell Devnet.
21
+ *
22
+ * - Chain ID: `424242`
23
+ * - HTTP RPC: `http://127.0.0.1:8545`
24
+ * - WebSocket RPC: `ws://127.0.0.1:8546`
25
+ * - Native currency: SHELL (18 decimals)
26
+ */
27
+ export const shellDevnet = defineChain({
28
+ id: 424242,
29
+ name: "Shell Devnet",
30
+ nativeCurrency: {
31
+ decimals: 18,
32
+ name: "SHELL",
33
+ symbol: "SHELL",
34
+ },
35
+ rpcUrls: {
36
+ default: {
37
+ http: ["http://127.0.0.1:8545"],
38
+ webSocket: ["ws://127.0.0.1:8546"],
39
+ },
40
+ },
41
+ });
42
+ /**
43
+ * RPC client for Shell Chain.
44
+ *
45
+ * Combines a viem `PublicClient` (accessible via `.client`) for all standard
46
+ * Ethereum JSON-RPC methods with direct `fetch`-based calls for Shell-specific
47
+ * `shell_*` methods.
48
+ *
49
+ * Prefer constructing via {@link createShellProvider} rather than instantiating
50
+ * this class directly.
51
+ */
52
+ export class ShellProvider {
53
+ /** Underlying viem `PublicClient` for standard `eth_*` methods. */
54
+ client;
55
+ /** HTTP RPC URL used for Shell-specific JSON-RPC calls. */
56
+ rpcHttpUrl;
57
+ constructor(client, rpcHttpUrl) {
58
+ this.client = client;
59
+ this.rpcHttpUrl = rpcHttpUrl;
60
+ }
61
+ async request(method, params) {
62
+ const response = await fetch(this.rpcHttpUrl, {
63
+ method: "POST",
64
+ headers: {
65
+ "content-type": "application/json",
66
+ },
67
+ body: JSON.stringify({
68
+ jsonrpc: "2.0",
69
+ id: 1,
70
+ method,
71
+ params,
72
+ }),
73
+ });
74
+ if (!response.ok) {
75
+ throw new Error(`rpc request failed: ${response.status} ${response.statusText}`);
76
+ }
77
+ const body = (await response.json());
78
+ if ("error" in body && body.error) {
79
+ throw new Error(`[${body.error.code}] ${body.error.message}`);
80
+ }
81
+ return body.result;
82
+ }
83
+ /**
84
+ * Retrieve the on-chain public key for an address.
85
+ *
86
+ * Calls `shell_getPqPubkey`. Returns `null` if the address has not yet
87
+ * submitted a transaction (public key is only recorded on first send).
88
+ *
89
+ * @param address - A `pq1…` or `0x…` address.
90
+ * @returns Hex-encoded public key string, or `null` if unknown.
91
+ */
92
+ async getPqPubkey(address) {
93
+ return this.request("shell_getPqPubkey", [address]);
94
+ }
95
+ /**
96
+ * Broadcast a signed Shell transaction.
97
+ *
98
+ * Calls `shell_sendTransaction`.
99
+ *
100
+ * @param signedTransaction - A fully-signed transaction built with {@link ShellSigner.buildSignedTransaction}.
101
+ * @returns The transaction hash as a hex string.
102
+ * @throws {Error} If the node rejects the transaction.
103
+ */
104
+ async sendTransaction(signedTransaction) {
105
+ return this.request("shell_sendTransaction", [signedTransaction]);
106
+ }
107
+ /**
108
+ * Fetch paginated transaction history for an address.
109
+ *
110
+ * Calls `shell_getTransactionsByAddress`.
111
+ *
112
+ * @param address - The address to query.
113
+ * @param options - Optional pagination and block range filters.
114
+ * @param options.fromBlock - Start of block range (inclusive).
115
+ * @param options.toBlock - End of block range (inclusive).
116
+ * @param options.page - Zero-based page index.
117
+ * @param options.limit - Maximum number of results per page.
118
+ * @returns Raw paginated response from the node.
119
+ */
120
+ async getTransactionsByAddress(address, options = {}) {
121
+ return this.request("shell_getTransactionsByAddress", [
122
+ address,
123
+ options.fromBlock ?? null,
124
+ options.toBlock ?? null,
125
+ options.page ?? null,
126
+ options.limit ?? null,
127
+ ]);
128
+ }
129
+ /**
130
+ * Fetch all transaction receipts for a block.
131
+ *
132
+ * Calls `eth_getBlockReceipts`.
133
+ *
134
+ * @param block - Block identifier: `"latest"`, `"earliest"`, or a hex block number.
135
+ * @returns Array of transaction receipt objects.
136
+ */
137
+ async getBlockReceipts(block) {
138
+ return this.request("eth_getBlockReceipts", [block]);
139
+ }
140
+ }
141
+ /**
142
+ * Create a viem `PublicClient` connected to Shell Chain over HTTP.
143
+ *
144
+ * @param options - Optional chain and URL overrides.
145
+ * @returns A configured viem `PublicClient`.
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const client = createShellPublicClient();
150
+ * const blockNumber = await client.getBlockNumber();
151
+ * ```
152
+ */
153
+ export function createShellPublicClient(options = {}) {
154
+ const chain = options.chain ?? shellDevnet;
155
+ const rpcHttpUrl = options.rpcHttpUrl ?? chain.rpcUrls.default.http[0];
156
+ return createPublicClient({
157
+ chain,
158
+ transport: http(rpcHttpUrl),
159
+ });
160
+ }
161
+ /**
162
+ * Create a viem `PublicClient` connected to Shell Chain over WebSocket.
163
+ *
164
+ * Useful for subscribing to `newHeads`, `logs`, and other real-time events.
165
+ *
166
+ * @param options - Optional chain and URL overrides.
167
+ * @returns A configured viem `PublicClient` using a WebSocket transport.
168
+ * @throws {Error} If no WebSocket URL is available for the chain.
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * const wsClient = createShellWsClient();
173
+ * const unwatch = wsClient.watchBlocks({ onBlock: (block) => console.log(block.number) });
174
+ * ```
175
+ */
176
+ export function createShellWsClient(options = {}) {
177
+ const chain = options.chain ?? shellDevnet;
178
+ const rpcWsUrl = options.rpcWsUrl ?? chain.rpcUrls.default.webSocket?.[0];
179
+ if (!rpcWsUrl) {
180
+ throw new Error("chain does not define a default WebSocket RPC URL");
181
+ }
182
+ return createPublicClient({
183
+ chain,
184
+ transport: webSocket(rpcWsUrl),
185
+ });
186
+ }
187
+ /**
188
+ * Create a {@link ShellProvider} — the recommended entry point for interacting
189
+ * with Shell Chain.
190
+ *
191
+ * Combines a viem HTTP `PublicClient` with Shell-specific RPC helpers.
192
+ *
193
+ * @param options - Optional chain and URL overrides.
194
+ * @returns A fully configured `ShellProvider`.
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * const provider = createShellProvider();
199
+ * const balance = await provider.client.getBalance({ address: signer.getHexAddress() });
200
+ * const hash = await provider.sendTransaction(signedTx);
201
+ * ```
202
+ */
203
+ export function createShellProvider(options = {}) {
204
+ const client = createShellPublicClient(options);
205
+ const chain = options.chain ?? shellDevnet;
206
+ const rpcHttpUrl = options.rpcHttpUrl ?? chain.rpcUrls.default.http[0];
207
+ return new ShellProvider(client, rpcHttpUrl);
208
+ }