shell-sdk 0.1.0 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## [0.2.0] — 2026-04-14
4
+
5
+ ### Added
6
+ - **API freeze**: `ShellSigner`, `ShellProvider`, and `ShellWallet` are now stable public APIs.
7
+ - ML-DSA-65 cross-validation tests confirming wire compatibility with `pqcrypto-dilithium` v0.5 (pk=1952 B, sk=4032 B, sig=3309 B).
8
+ - `examples/minimal-dapp`: Node.js (`node-demo.mjs`) and browser (`browser-demo.html`) integration examples.
9
+ - JSDoc complete for all public-facing exports.
10
+ - `MlDsa65Adapter` round-trip sign+verify test.
11
+
12
+ ### Changed
13
+ - `adapters.ts`: both `"Dilithium3"` and `"MlDsa65"` aliases now explicitly document ML-DSA-65 (FIPS 204) wire compatibility with the chain's Dilithium3 verifier.
14
+ - `hashTransaction()` canonical RLP field ordering aligned with `shell-chain` deserialiser.
15
+ - Package root export surface narrowed to stable application-facing APIs.
16
+
17
+
18
+
19
+ - **Signing compatibility confirmed**: `pqcrypto-dilithium` v0.5 (shell-chain) implements FIPS 204 ML-DSA-65, byte-identical with `@noble/post-quantum` `ml_dsa65`. The `Dilithium3` alias in the SDK now documents this equivalence explicitly (pk=1952, sk=4032, sig=3309 bytes).
20
+ - add cross-validation tests verifying ML-DSA-65 key/signature sizes against chain expectations
21
+ - add `MlDsa65Adapter` round-trip sign+verify test
22
+ - clarify `adapters.ts` JSDoc: both `"Dilithium3"` and `"MlDsa65"` route to ML-DSA-65 (FIPS 204) which is wire-compatible with the chain's Dilithium3 verifier
23
+
24
+ ## 0.2.0-rc.1
25
+
26
+ - add Browser and Node integration tests for signer, keystore, and provider flows
27
+ - add Rust compatibility vectors for address derivation and transaction hashing
28
+ - fix `hashTransaction()` to match the Rust node's canonical RLP field ordering
29
+ - fix `hashTransaction()` to accept canonical `pq1...` recipient addresses
30
+ - narrow the package root export surface to stable application-facing APIs
31
+ - document extension background flow, minimal dApp usage, and release checklist
package/README.md CHANGED
@@ -22,10 +22,13 @@
22
22
  - [System contracts](#system-contracts)
23
23
  - [Keystore](#keystore)
24
24
  - [End-to-end examples](#end-to-end-examples)
25
+ - [Wallet extension background flow](#wallet-extension-background-flow)
26
+ - [Minimal dApp flow](#minimal-dapp-flow)
25
27
  - [Key rotation](#key-rotation)
26
28
  - [Error handling](#error-handling)
27
29
  - [TypeScript types reference](#typescript-types-reference)
28
30
  - [Compatibility](#compatibility)
31
+ - [Release checklist](#release-checklist)
29
32
  - [Chain reference](#chain-reference)
30
33
 
31
34
  ---
@@ -128,6 +131,8 @@ These are sent as ordinary transactions whose `to` field is the AccountManager a
128
131
 
129
132
  ## Module reference
130
133
 
134
+ 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`.
135
+
131
136
  ### Types
132
137
 
133
138
  Defined in `src/types.ts`. All types are re-exported from the package root.
@@ -434,9 +439,11 @@ const signed = buildSignedTransaction({
434
439
 
435
440
  #### `hashTransaction`
436
441
 
437
- RLP-encode a `ShellTransactionRequest` and return its **keccak256** hash as a `Uint8Array`. This is the value you must pass as `txHash` to `signer.buildSignedTransaction`.
442
+ RLP-encode a `ShellTransactionRequest` using the Rust node's canonical field order and return its **keccak256** hash as a `Uint8Array`. This is the value you must pass as `txHash` to `signer.buildSignedTransaction`.
443
+
444
+ Shell Chain signs the full unsigned transaction payload in this order:
438
445
 
439
- Shell Chain computes the signing hash identically to Ethereum EIP-1559: `keccak256(RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]))`.
446
+ `[chainId, nonce, to, value, data, gasLimit, maxFeePerGas, maxPriorityFeePerGas, accessList, txType, blobFeeFlag, maxFeePerBlobGas, blobVersionedHashes]`
440
447
 
441
448
  ```typescript
442
449
  import { buildTransferTransaction, hashTransaction } from "shell-sdk/transactions";
@@ -620,6 +627,55 @@ console.log(hash);
620
627
 
621
628
  ---
622
629
 
630
+ ### Wallet extension background flow
631
+
632
+ 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.
633
+
634
+ ```typescript
635
+ import { createShellProvider, buildTransferTransaction, hashTransaction } from "shell-sdk";
636
+
637
+ async function submitTransfer({ signer, to, value, rpcHttpUrl }: {
638
+ signer: { getHexAddress(): `0x${string}`; buildSignedTransaction(args: { tx: unknown; txHash: Uint8Array; includePublicKey?: boolean }): Promise<unknown> };
639
+ to: string;
640
+ value: bigint;
641
+ rpcHttpUrl: string;
642
+ }) {
643
+ const provider = createShellProvider({ rpcHttpUrl });
644
+ const nonce = await provider.client.getTransactionCount({ address: signer.getHexAddress() });
645
+
646
+ const tx = buildTransferTransaction({
647
+ chainId: 424242,
648
+ nonce,
649
+ to,
650
+ value,
651
+ });
652
+ const txHash = hashTransaction(tx);
653
+ const signed = await signer.buildSignedTransaction({ tx, txHash, includePublicKey: nonce === 0 });
654
+
655
+ return provider.sendTransaction(signed);
656
+ }
657
+ ```
658
+
659
+ ### Minimal dApp flow
660
+
661
+ For a lightweight web app, keep the provider in the page and delegate signing to an injected wallet or background bridge:
662
+
663
+ ```typescript
664
+ import { createShellProvider, normalizePqAddress } from "shell-sdk";
665
+
666
+ const provider = createShellProvider({
667
+ rpcHttpUrl: "https://rpc.testnet.shell.network",
668
+ });
669
+
670
+ const account = normalizePqAddress("0x1234...abcd");
671
+ const history = await provider.getTransactionsByAddress(account, { page: 1, limit: 10 });
672
+
673
+ console.log("recent txs:", history.transactions);
674
+ console.log("total:", history.total);
675
+ ```
676
+
677
+ ---
678
+
623
679
  ## Key rotation
624
680
 
625
681
  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.
@@ -755,6 +811,19 @@ interface ShellSignature {
755
811
 
756
812
  ---
757
813
 
814
+ ## Release checklist
815
+
816
+ Before publishing a `shell-sdk` release candidate:
817
+
818
+ 1. Run `npm test` and `npm run typecheck`.
819
+ 2. Confirm the stable root surface still excludes low-level helpers such as `hexBytes` and internal signer maps.
820
+ 3. Verify Browser + Node integration tests both cover signer, keystore, and provider RPC flows.
821
+ 4. Review README examples against the current public exports (`shell-sdk`, `shell-sdk/signer`, `shell-sdk/transactions`).
822
+ 5. Check `package.json` `exports`, `files`, `version`, and repository metadata.
823
+ 6. Build once from a clean tree and smoke-import the package root plus subpaths from `dist/`.
824
+
825
+ ---
826
+
758
827
  ## Chain reference
759
828
 
760
829
  | Parameter | Value |
@@ -1,8 +1,15 @@
1
1
  /**
2
2
  * Concrete SignerAdapter implementations for each PQ algorithm.
3
3
  *
4
- * @noble/post-quantum provides ML-DSA-65 and SLH-DSA-SHA2-256f.
5
- * Dilithium3 (pre-FIPS Round-3) uses {@link MlDsa65Adapter} as a stand-in.
4
+ * `@noble/post-quantum` provides ML-DSA-65 and SLH-DSA-SHA2-256f via
5
+ * WebAssembly-accelerated pure-JS implementations.
6
+ *
7
+ * **Dilithium3 compatibility note**: `pqcrypto-dilithium` v0.5 (used by
8
+ * shell-chain) implements ML-DSA-65 (FIPS 204) under the `dilithium3` name.
9
+ * `@noble/post-quantum` `ml_dsa65` produces byte-identical keys and
10
+ * signatures (pk=1952, sk=4032, sig=3309), so `MlDsa65Adapter` is fully
11
+ * wire-compatible with the chain's Dilithium3 verifier. Both `"Dilithium3"`
12
+ * and `"MlDsa65"` algorithm names route to the same adapter.
6
13
  *
7
14
  * @module adapters
8
15
  */
@@ -45,8 +52,10 @@ export declare function generateSlhDsaKeyPair(seed?: Uint8Array): SlhDsaKeyPair;
45
52
  /**
46
53
  * {@link SignerAdapter} for ML-DSA-65 (NIST FIPS 204).
47
54
  *
48
- * Also used as the implementation for `"Dilithium3"` (the pre-standardisation
49
- * Round-3 variant) since the wire format is compatible.
55
+ * This is the primary signing adapter for Shell Chain. It is also used for
56
+ * `"Dilithium3"` keys since `pqcrypto-dilithium` v0.5 (the Rust crate used
57
+ * by shell-chain) implements FIPS 204 ML-DSA-65 — producing byte-identical
58
+ * keys and signatures (pk=1952 bytes, sk=4032 bytes, sig=3309 bytes).
50
59
  *
51
60
  * @example
52
61
  * ```typescript
package/dist/adapters.js CHANGED
@@ -1,8 +1,15 @@
1
1
  /**
2
2
  * Concrete SignerAdapter implementations for each PQ algorithm.
3
3
  *
4
- * @noble/post-quantum provides ML-DSA-65 and SLH-DSA-SHA2-256f.
5
- * Dilithium3 (pre-FIPS Round-3) uses {@link MlDsa65Adapter} as a stand-in.
4
+ * `@noble/post-quantum` provides ML-DSA-65 and SLH-DSA-SHA2-256f via
5
+ * WebAssembly-accelerated pure-JS implementations.
6
+ *
7
+ * **Dilithium3 compatibility note**: `pqcrypto-dilithium` v0.5 (used by
8
+ * shell-chain) implements ML-DSA-65 (FIPS 204) under the `dilithium3` name.
9
+ * `@noble/post-quantum` `ml_dsa65` produces byte-identical keys and
10
+ * signatures (pk=1952, sk=4032, sig=3309), so `MlDsa65Adapter` is fully
11
+ * wire-compatible with the chain's Dilithium3 verifier. Both `"Dilithium3"`
12
+ * and `"MlDsa65"` algorithm names route to the same adapter.
6
13
  *
7
14
  * @module adapters
8
15
  */
@@ -41,8 +48,10 @@ export function generateSlhDsaKeyPair(seed) {
41
48
  /**
42
49
  * {@link SignerAdapter} for ML-DSA-65 (NIST FIPS 204).
43
50
  *
44
- * Also used as the implementation for `"Dilithium3"` (the pre-standardisation
45
- * Round-3 variant) since the wire format is compatible.
51
+ * This is the primary signing adapter for Shell Chain. It is also used for
52
+ * `"Dilithium3"` keys since `pqcrypto-dilithium` v0.5 (the Rust crate used
53
+ * by shell-chain) implements FIPS 204 ML-DSA-65 — producing byte-identical
54
+ * keys and signatures (pk=1952 bytes, sk=4032 bytes, sig=3309 bytes).
46
55
  *
47
56
  * @example
48
57
  * ```typescript
@@ -86,7 +95,7 @@ export class MlDsa65Adapter {
86
95
  * @param message - The bytes to sign (typically an RLP-encoded tx hash).
87
96
  */
88
97
  async sign(message) {
89
- return ml_dsa65.sign(this._secretKey, message);
98
+ return ml_dsa65.sign(message, this._secretKey);
90
99
  }
91
100
  }
92
101
  /**
@@ -134,7 +143,7 @@ export class SlhDsaAdapter {
134
143
  * @param message - The bytes to sign (typically an RLP-encoded tx hash).
135
144
  */
136
145
  async sign(message) {
137
- return slh_dsa_sha2_256f.sign(this._secretKey, message);
146
+ return slh_dsa_sha2_256f.sign(message, this._secretKey);
138
147
  }
139
148
  }
140
149
  /**
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export { bytesToHexAddress, PQ_ADDRESS_HRP, PQ_ADDRESS_LENGTH, PQ_ADDRESS_VERSION_V1, bytesToPqAddress, derivePqAddressFromPublicKey, isPqAddress, normalizeHexAddress, normalizePqAddress, pqAddressVersion, pqAddressToBytes, } from "./address.js";
2
2
  export { createShellProvider, createShellPublicClient, createShellWsClient, ShellProvider, shellDevnet, type CreateShellPublicClientOptions, } from "./provider.js";
3
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";
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, } from "./transactions.js";
5
5
  export { assertSignerMatchesKeystore, decryptKeystore, exportEncryptedKeyJson, parseEncryptedKey, validateEncryptedKeyAddress, type ParsedShellKeystore, } from "./keystore.js";
6
6
  export { adapterFromKeyPair, generateAdapter, generateMlDsa65KeyPair, generateSlhDsaKeyPair, MlDsa65Adapter, SlhDsaAdapter, type MlDsa65KeyPair, type SlhDsaKeyPair, } from "./adapters.js";
7
- export { buildShellSignature, KEY_TYPE_TO_SIGNATURE_TYPE, publicKeyFromHex, ShellSigner, SIGNATURE_TYPE_IDS, signatureTypeFromKeyType, type SignerAdapter, } from "./signer.js";
7
+ export { buildShellSignature, publicKeyFromHex, ShellSigner, signatureTypeFromKeyType, type SignerAdapter, } from "./signer.js";
8
8
  export type { AddressLike, HexString, ShellAccessListItem, ShellCipherParams, ShellEncryptedKey, ShellSendTransactionParams, ShellKdfParams, ShellSignature, ShellTransactionRequest, ShellTxByAddressPage, SignedShellTransaction, SignatureTypeName, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { bytesToHexAddress, PQ_ADDRESS_HRP, PQ_ADDRESS_LENGTH, PQ_ADDRESS_VERSION_V1, bytesToPqAddress, derivePqAddressFromPublicKey, isPqAddress, normalizeHexAddress, normalizePqAddress, pqAddressVersion, pqAddressToBytes, } from "./address.js";
2
2
  export { createShellProvider, createShellPublicClient, createShellWsClient, ShellProvider, shellDevnet, } from "./provider.js";
3
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";
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, } from "./transactions.js";
5
5
  export { assertSignerMatchesKeystore, decryptKeystore, exportEncryptedKeyJson, parseEncryptedKey, validateEncryptedKeyAddress, } from "./keystore.js";
6
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";
7
+ export { buildShellSignature, publicKeyFromHex, ShellSigner, signatureTypeFromKeyType, } from "./signer.js";
@@ -190,8 +190,8 @@ export declare function hexBytes(bytes: Uint8Array): HexString;
190
190
  * `keccak256(RLP(tx))` — the same scheme as Ethereum EIP-1559 signing.
191
191
  *
192
192
  * **Encoding order** (EIP-2718 type-2 fields):
193
- * chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit,
194
- * to, value, data, accessList, maxFeePerBlobGas (if present), blobVersionedHashes (if present)
193
+ * chainId, nonce, to, value, data, gasLimit, maxFeePerGas, maxPriorityFeePerGas,
194
+ * accessList, txType, blobFeeFlag, maxFeePerBlobGas, blobVersionedHashes
195
195
  *
196
196
  * @example
197
197
  * ```typescript
@@ -7,8 +7,9 @@
7
7
  *
8
8
  * @module transactions
9
9
  */
10
- import { bytesToHex, keccak256, toRlp, numberToHex, hexToBytes } from "viem";
10
+ import { bytesToHex, keccak256, toRlp, hexToBytes } from "viem";
11
11
  import { accountManagerAddress, encodeClearValidationCodeCalldata, encodeRotateKeyCalldata, encodeSetValidationCodeCalldata, } from "./system-contracts.js";
12
+ import { normalizeHexAddress } from "./address.js";
12
13
  /** Default transaction type: `2` (EIP-1559). */
13
14
  export const DEFAULT_TX_TYPE = 2;
14
15
  /** Default gas limit for simple SHELL token transfers (`21_000`). */
@@ -25,6 +26,22 @@ function toByteArray(bytes) {
25
26
  function toHexData(data) {
26
27
  return data ?? "0x";
27
28
  }
29
+ function toRlpUint(value) {
30
+ const numeric = typeof value === "string" ? BigInt(value) : BigInt(value);
31
+ if (numeric === 0n) {
32
+ return "0x";
33
+ }
34
+ return `0x${numeric.toString(16)}`;
35
+ }
36
+ function toRlpAccessList(accessList) {
37
+ if (!accessList || accessList.length === 0) {
38
+ return [];
39
+ }
40
+ return accessList.map((item) => [
41
+ normalizeHexAddress(item.address),
42
+ item.storage_keys.map((key) => key),
43
+ ]);
44
+ }
28
45
  /**
29
46
  * Low-level transaction builder that maps camelCase options to the
30
47
  * snake_case wire format expected by the Shell node.
@@ -210,8 +227,8 @@ export function hexBytes(bytes) {
210
227
  * `keccak256(RLP(tx))` — the same scheme as Ethereum EIP-1559 signing.
211
228
  *
212
229
  * **Encoding order** (EIP-2718 type-2 fields):
213
- * chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit,
214
- * to, value, data, accessList, maxFeePerBlobGas (if present), blobVersionedHashes (if present)
230
+ * chainId, nonce, to, value, data, gasLimit, maxFeePerGas, maxPriorityFeePerGas,
231
+ * accessList, txType, blobFeeFlag, maxFeePerBlobGas, blobVersionedHashes
215
232
  *
216
233
  * @example
217
234
  * ```typescript
@@ -226,24 +243,21 @@ export function hexBytes(bytes) {
226
243
  * @returns 32-byte keccak256 hash as a `Uint8Array`.
227
244
  */
228
245
  export function hashTransaction(tx) {
229
- const to = tx.to ? hexToBytes(tx.to.startsWith("0x") ? tx.to : `0x${tx.to}`) : new Uint8Array(0);
230
- const data = hexToBytes(tx.data);
231
- const value = hexToBytes(tx.value);
232
246
  const fields = [
233
- numberToHex(tx.chain_id),
234
- numberToHex(tx.nonce),
235
- numberToHex(tx.max_priority_fee_per_gas),
236
- numberToHex(tx.max_fee_per_gas),
237
- numberToHex(tx.gas_limit),
238
- bytesToHex(to),
239
- bytesToHex(value),
240
- bytesToHex(data),
241
- "0x", // empty access list
247
+ toRlpUint(tx.chain_id),
248
+ toRlpUint(tx.nonce),
249
+ tx.to ? normalizeHexAddress(tx.to) : "0x",
250
+ toRlpUint(tx.value),
251
+ tx.data,
252
+ toRlpUint(tx.gas_limit),
253
+ toRlpUint(tx.max_fee_per_gas),
254
+ toRlpUint(tx.max_priority_fee_per_gas),
255
+ toRlpAccessList(tx.access_list),
256
+ toRlpUint(tx.tx_type ?? DEFAULT_TX_TYPE),
257
+ toRlpUint(tx.max_fee_per_blob_gas != null ? 1 : 0),
258
+ toRlpUint(tx.max_fee_per_blob_gas ?? 0),
259
+ (tx.blob_versioned_hashes ?? []).map((hash) => hash),
242
260
  ];
243
- if (tx.max_fee_per_blob_gas != null) {
244
- fields.push(numberToHex(tx.max_fee_per_blob_gas));
245
- fields.push("0x"); // empty blob versioned hashes
246
- }
247
261
  const rlpEncoded = toRlp(fields);
248
262
  const hash = hexToBytes(keccak256(rlpEncoded));
249
263
  return hash;
@@ -0,0 +1,59 @@
1
+ # Shell SDK — Minimal dApp Example
2
+
3
+ Demonstrates the core Shell SDK flow:
4
+ 1. Generate a post-quantum key pair (ML-DSA-65)
5
+ 2. Create a signer and derive address
6
+ 3. Query balance from a Shell node
7
+ 4. Build + sign a transaction (without broadcasting)
8
+
9
+ ## Node.js (server / CLI)
10
+
11
+ ```bash
12
+ # From the shell-sdk root:
13
+ npm install
14
+ npm run build
15
+
16
+ # Run the demo (points at localhost:8545 by default)
17
+ node examples/minimal-dapp/node-demo.mjs
18
+
19
+ # Point at a different RPC endpoint:
20
+ SHELL_RPC_URL=http://my-node:8545 node examples/minimal-dapp/node-demo.mjs
21
+ ```
22
+
23
+ Expected output:
24
+ ```
25
+ Address (pq1…): pq1…
26
+ Address (0x…): 0x…
27
+ Balance (wei): 0 ← 0 for a fresh account; fund it via the faucet
28
+ Signed tx type: 2
29
+ Signature length (bytes): 3309
30
+ Sender pubkey set: true
31
+
32
+ All done! Connect to a live node and call provider.sendTransaction(signed) to broadcast.
33
+ ```
34
+
35
+ ## Browser
36
+
37
+ ```bash
38
+ # Build the SDK first
39
+ cd ../../ # shell-sdk root
40
+ npm run build
41
+
42
+ # Serve this directory with any HTTP server
43
+ npx serve examples/minimal-dapp/
44
+ # or
45
+ python3 -m http.server 8080
46
+ ```
47
+
48
+ Then open `http://localhost:8080/browser-demo.html` in your browser.
49
+
50
+ ## Key sizes (ML-DSA-65 / FIPS 204)
51
+
52
+ | Field | Bytes |
53
+ |-------------|-------|
54
+ | Public key | 1952 |
55
+ | Secret key | 4032 |
56
+ | Signature | 3309 |
57
+
58
+ These match `pqcrypto-dilithium` v0.5 (used by shell-chain), confirming wire
59
+ compatibility. Both `"Dilithium3"` and `"MlDsa65"` keys use this algorithm.
@@ -0,0 +1,98 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Shell SDK — Minimal Browser dApp</title>
6
+ <style>
7
+ body { font-family: monospace; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
8
+ pre { background: #1a1a1a; color: #d4d4d4; padding: 1rem; border-radius: 4px; overflow-x: auto; }
9
+ button { padding: .5rem 1.2rem; cursor: pointer; margin: .25rem; }
10
+ #log { white-space: pre-wrap; }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <h1>Shell SDK — Minimal Browser dApp</h1>
15
+ <p>This page runs entirely in the browser using ES modules from a CDN (or local build).</p>
16
+
17
+ <button onclick="generateKey()">1. Generate Key</button>
18
+ <button onclick="queryBalance()">2. Query Balance</button>
19
+ <button onclick="buildTx()">3. Build &amp; Sign Tx</button>
20
+
21
+ <pre id="log">Click a button to start…</pre>
22
+
23
+ <script type="module">
24
+ // Adjust the import path to your local build or CDN URL.
25
+ // e.g. import { MlDsa65Adapter, ... } from "./shell-sdk.esm.js";
26
+ // For local dev: `cd shell-sdk && npm run build` then serve this folder.
27
+ const SDK_URL = "./../../dist/index.js";
28
+
29
+ let signer = null;
30
+
31
+ function log(...args) {
32
+ const el = document.getElementById("log");
33
+ el.textContent += args.map(String).join(" ") + "\n";
34
+ }
35
+
36
+ async function loadSdk() {
37
+ try {
38
+ return await import(SDK_URL);
39
+ } catch (e) {
40
+ log("Failed to load SDK:", e.message);
41
+ log("Make sure you have run `npm run build` in the shell-sdk directory");
42
+ log("and are serving this file from an HTTP server (not file://).");
43
+ throw e;
44
+ }
45
+ }
46
+
47
+ window.generateKey = async function () {
48
+ const { MlDsa65Adapter, ShellSigner } = await loadSdk();
49
+ const adapter = MlDsa65Adapter.generate();
50
+ signer = new ShellSigner("MlDsa65", adapter);
51
+ log("=== Key Generated ===");
52
+ log("Address (pq1…):", signer.getAddress());
53
+ log("Address (0x…):", signer.getHexAddress());
54
+ log("Public key size:", adapter.getPublicKey().length, "bytes");
55
+ };
56
+
57
+ window.queryBalance = async function () {
58
+ if (!signer) { log("Generate a key first!"); return; }
59
+ const { createShellProvider } = await loadSdk();
60
+ const rpc = prompt("RPC URL?", "http://127.0.0.1:8545");
61
+ if (!rpc) return;
62
+ const provider = createShellProvider({ url: rpc });
63
+ try {
64
+ const bal = await provider.getBalance({ address: signer.getHexAddress() });
65
+ log("Balance:", bal.toString(), "wei");
66
+ } catch (e) {
67
+ log("getBalance error:", e.message);
68
+ }
69
+ };
70
+
71
+ window.buildTx = async function () {
72
+ if (!signer) { log("Generate a key first!"); return; }
73
+ const { hashTransaction } = await loadSdk();
74
+ const tx = {
75
+ from: signer.getHexAddress(),
76
+ chain_id: 1337,
77
+ nonce: 0,
78
+ to: "0x0000000000000000000000000000000000000001",
79
+ value: "0x1",
80
+ data: "0x",
81
+ gas_limit: 21000,
82
+ max_fee_per_gas: 1_000_000_000,
83
+ max_priority_fee_per_gas: 1_000_000_000,
84
+ };
85
+ const txHash = hashTransaction(tx);
86
+ const signed = await signer.buildSignedTransaction({ tx, txHash, includePublicKey: true });
87
+ log("=== Signed Transaction ===");
88
+ log("Type:", signed.type);
89
+ log("From:", signed.from);
90
+ log("To:", signed.to);
91
+ log("Value:", signed.value.toString());
92
+ log("Sig length:", signed.signature.data.length, "bytes");
93
+ log("Has pubkey:", signed.sender_pubkey != null);
94
+ log("\nCall provider.sendTransaction(signed) to broadcast to a live node.");
95
+ };
96
+ </script>
97
+ </body>
98
+ </html>
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shell SDK minimal dApp — Node.js script
3
+ *
4
+ * Demonstrates: connect provider → query balance → sign + build transaction
5
+ *
6
+ * Usage:
7
+ * npm install shell-sdk
8
+ * node node-demo.mjs
9
+ *
10
+ * By default this points at the Shell devnet RPC. Set SHELL_RPC_URL to
11
+ * override.
12
+ */
13
+
14
+ import { MlDsa65Adapter, ShellSigner, createShellProvider } from "shell-sdk";
15
+
16
+ const RPC_URL = process.env.SHELL_RPC_URL ?? "http://127.0.0.1:8545";
17
+
18
+ async function main() {
19
+ // ── 1. Create a provider ──────────────────────────────────────────────
20
+ const provider = createShellProvider({ url: RPC_URL });
21
+
22
+ // ── 2. Generate a key pair and build a signer ─────────────────────────
23
+ const adapter = MlDsa65Adapter.generate();
24
+ const signer = new ShellSigner("MlDsa65", adapter);
25
+
26
+ console.log("Address (pq1…):", signer.getAddress());
27
+ console.log("Address (0x…):", signer.getHexAddress());
28
+
29
+ // ── 3. Query balance ───────────────────────────────────────────────────
30
+ try {
31
+ const balance = await provider.client.getBalance({ address: signer.getHexAddress() });
32
+ console.log("Balance (wei):", balance.toString());
33
+ } catch (err) {
34
+ console.warn("getBalance failed (node may not be running):", err.message);
35
+ }
36
+
37
+ // ── 4. Build and sign a transaction ───────────────────────────────────
38
+ // NOTE: sending requires a funded account + live node.
39
+ // This section shows the sign-only flow without broadcasting.
40
+ const TO = "0x0000000000000000000000000000000000000001";
41
+
42
+ /** @type {import("shell-sdk").ShellTransactionRequest} */
43
+ const tx = {
44
+ from: signer.getHexAddress(),
45
+ chain_id: 1337,
46
+ nonce: 0,
47
+ to: TO,
48
+ value: "0x1",
49
+ data: "0x",
50
+ gas_limit: 21000,
51
+ max_fee_per_gas: 1_000_000_000,
52
+ max_priority_fee_per_gas: 1_000_000_000,
53
+ };
54
+
55
+ const { hashTransaction } = await import("shell-sdk");
56
+ const txHash = hashTransaction(tx);
57
+ const signed = await signer.buildSignedTransaction({ tx, txHash, includePublicKey: true });
58
+ console.log("Signed tx type:", signed.tx.tx_type ?? 2, "(EIP-1559)");
59
+ console.log("Signature length (bytes):", signed.signature.data.length);
60
+ console.log("Sender pubkey set:", signed.sender_pubkey != null);
61
+
62
+ console.log("\nAll done! Connect to a live node and call provider.sendTransaction(signed) to broadcast.");
63
+ }
64
+
65
+ main().catch((err) => {
66
+ console.error(err);
67
+ process.exit(1);
68
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "TypeScript SDK for Shell Chain",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,12 +45,15 @@
45
45
  }
46
46
  },
47
47
  "files": [
48
- "dist"
48
+ "dist",
49
+ "examples",
50
+ "CHANGELOG.md"
49
51
  ],
50
52
  "scripts": {
51
53
  "build": "tsc -p tsconfig.json",
52
54
  "clean": "rm -rf dist",
53
- "typecheck": "tsc -p tsconfig.json --noEmit"
55
+ "typecheck": "tsc -p tsconfig.json --noEmit",
56
+ "test": "npm run build && node --test tests/**/*.test.mjs"
54
57
  },
55
58
  "keywords": [
56
59
  "shell-chain",