shell-sdk 0.6.0 → 0.7.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 CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0] — 2026-04-30 ⚠️ BREAKING
4
+
5
+ ### Breaking Changes — F-PQ1-ONLY (pq1 bech32m addresses everywhere)
6
+
7
+ This is a **breaking release**. All `0x` hex address support has been removed.
8
+
9
+ #### Removed
10
+ - `hexAddress()` / `hexAddressFromPubkey()` exports deleted — use `pq1Address()` instead.
11
+ - `normalizePqAddress()` no longer accepts `0x...` hex strings — only `pq1...` bech32m.
12
+ - Keystore `address` field: `encryptKeystore()` now always emits `pq1...` bech32m (was `0x...` hex).
13
+ - `decryptKeystore()` rejects keystores with `0x` address fields (use `shell-node key migrate` first).
14
+
15
+ #### Added
16
+ - `pq1Address(pubkey, sigType)` — canonical address derivation returning `pq1...` bech32m string.
17
+ - SK-only keystore format: `encryptKeystore` stores only the secret key in ciphertext (no pk concatenation), matching `shell-node key generate` output for full cross-language compatibility.
18
+ - Full cross-format compatibility: SDK keystores are now byte-for-byte compatible with Rust CLI keystores.
19
+
20
+ #### Updated
21
+ - All SDK test fixtures regenerated with `pq1` addresses.
22
+ - `SIGNATURE_TYPE_IDS.MlDsa65 = 1` (was incorrectly aliased to `0` in v0.6.x).
23
+ - ML-DSA-65 (FIPS 204) via `@noble/post-quantum` — real implementation, not a Dilithium3 alias.
24
+
25
+ ### Migration Guide
26
+ ```js
27
+ // Before (0.6.x)
28
+ import { hexAddress } from 'shell-sdk';
29
+ const addr = hexAddress(pubkey, sigType); // "0x..."
30
+
31
+ // After (0.7.0)
32
+ import { pq1Address } from 'shell-sdk';
33
+ const addr = pq1Address(pubkey, sigType); // "pq1..."
34
+ ```
35
+
36
+ For existing keystores with `0x` addresses: run `shell-node key migrate <keystore.json>` to upgrade to pq1 format.
37
+
38
+ ### Compatibility
39
+ - Requires `shell-chain v0.21.0+` (pq1-only RPC).
40
+ - Not backwards-compatible with SDK `0.6.x` address handling.
41
+
42
+ ## [0.6.0] — 2026-04-27
43
+
44
+ ### Added — ML-DSA-65 (FIPS 204) + Keystore SK-only format
45
+
46
+ #### Crypto (`keystore.ts`, `address.ts`)
47
+ - **`MlDsa65Adapter`**: real FIPS 204 ML-DSA-65 implementation via `@noble/post-quantum`.
48
+ - **`SIGNATURE_TYPE_IDS.MlDsa65 = 1`**: ML-DSA-65 is now a distinct algorithm (was aliased to Dilithium3=0).
49
+ - **SK-only keystore**: `encryptKeystore` stores only secret key bytes in ciphertext, matching `shell-node` CLI format. Cross-language compatible.
50
+ - **`decryptCliKeystore`** helper removed — standard `decryptKeystore` now handles CLI keystores.
51
+
52
+ #### Compatibility
53
+ - Fully cross-language compatible: Rust CLI keystores decryptable by SDK and vice versa.
54
+ - `shell-chain v0.20.0+` required for ML-DSA-65 transaction signing.
55
+
3
56
  ## [0.5.0] — 2026-04-26
4
57
 
5
58
  ### Added — AA Phase 2 (matches `shell-chain v0.19.0`)
@@ -4,12 +4,10 @@
4
4
  * `@noble/post-quantum` provides ML-DSA-65 and SLH-DSA-SHA2-256f via
5
5
  * WebAssembly-accelerated pure-JS implementations.
6
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.
7
+ * **Algorithm distinction**: `"Dilithium3"` (type_id=0) and `"ML-DSA-65"` /
8
+ * `"MlDsa65"` (type_id=1) are now separate algorithms on chain with distinct
9
+ * address derivation. `MlDsa65Adapter` and `DilithiumAdapter` both use the
10
+ * same underlying `ml_dsa65` crypto (FIPS 204), but produce different addresses.
13
11
  *
14
12
  * @module adapters
15
13
  */
package/dist/adapters.js CHANGED
@@ -4,12 +4,10 @@
4
4
  * `@noble/post-quantum` provides ML-DSA-65 and SLH-DSA-SHA2-256f via
5
5
  * WebAssembly-accelerated pure-JS implementations.
6
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.
7
+ * **Algorithm distinction**: `"Dilithium3"` (type_id=0) and `"ML-DSA-65"` /
8
+ * `"MlDsa65"` (type_id=1) are now separate algorithms on chain with distinct
9
+ * address derivation. `MlDsa65Adapter` and `DilithiumAdapter` both use the
10
+ * same underlying `ml_dsa65` crypto (FIPS 204), but produce different addresses.
13
11
  *
14
12
  * @module adapters
15
13
  */
package/dist/address.d.ts CHANGED
@@ -4,7 +4,6 @@ export declare const PQ_ADDRESS_HRP = "pq";
4
4
  export declare const PQ_ADDRESS_LENGTH = 20;
5
5
  /** Version byte for V1 Shell addresses (`0x01`). */
6
6
  export declare const PQ_ADDRESS_VERSION_V1 = 1;
7
- type HexAddress = `0x${string}`;
8
7
  /**
9
8
  * Encode 20 raw address bytes as a `pq1…` bech32m address.
10
9
  *
@@ -45,40 +44,15 @@ export declare function pqAddressToBytes(address: string): Uint8Array;
45
44
  */
46
45
  export declare function pqAddressVersion(address: string): number;
47
46
  /**
48
- * Parse a `0x…` hex address string into its raw 20 bytes.
47
+ * Normalise an address: validates it is a `pq1…` bech32m address and returns it.
49
48
  *
50
- * @param address - A `0x`-prefixed 40-character hex address.
51
- * @returns The 20-byte address payload.
52
- * @throws {Error} If the string does not start with `"0x"` or is not exactly 20 bytes.
53
- */
54
- export declare function hexAddressToBytes(address: string): Uint8Array;
55
- /**
56
- * Encode 20 raw address bytes as a `0x…` hex address.
57
- *
58
- * @param bytes - Exactly 20 address bytes.
59
- * @returns A `0x`-prefixed 40-character hex string.
60
- * @throws {Error} If `bytes.length !== 20`.
61
- */
62
- export declare function bytesToHexAddress(bytes: Uint8Array): HexAddress;
63
- /**
64
- * Normalise an address to `pq1…` bech32m form.
49
+ * Hex (`0x…`) addresses are no longer accepted. Use `pq1…` format everywhere.
65
50
  *
66
- * Accepts either a `pq1…` or `0x…` address and always returns the canonical
67
- * bech32m form.
68
- *
69
- * @param address - A `pq1…` or `0x…` address.
70
- * @returns The `pq1…` bech32m address.
51
+ * @param address - A `pq1…` bech32m address.
52
+ * @returns The same `pq1…` address (validated).
53
+ * @throws {Error} If the address is not a valid `pq1…` bech32m address.
71
54
  */
72
55
  export declare function normalizePqAddress(address: string): string;
73
- /**
74
- * Normalise an address to `0x…` hex form.
75
- *
76
- * Accepts either a `pq1…` or `0x…` address and always returns the hex form.
77
- *
78
- * @param address - A `pq1…` or `0x…` address.
79
- * @returns The `0x`-prefixed hex address.
80
- */
81
- export declare function normalizeHexAddress(address: string): HexAddress;
82
56
  /**
83
57
  * Derive a `pq1…` address from a raw post-quantum public key.
84
58
  *
@@ -116,4 +90,3 @@ export declare function derivePqAddressFromPublicKey(publicKey: Uint8Array, algo
116
90
  * ```
117
91
  */
118
92
  export declare function isPqAddress(address: string): boolean;
119
- export {};
package/dist/address.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * PQ address utilities for Shell Chain.
3
3
  *
4
- * Shell Chain uses **bech32m**-encoded addresses (prefix `"pq"`) instead of
5
- * Ethereum's checksummed hex format. Each address encodes a version byte and
6
- * 20 address bytes derived from the account's post-quantum public key:
4
+ * Shell Chain uses **bech32m**-encoded addresses (prefix `"pq"`) exclusively.
5
+ * Each address encodes a version byte and 20 address bytes derived from the
6
+ * account's post-quantum public key:
7
7
  *
8
8
  * ```
9
9
  * address_bytes = blake3(version || algo_id || public_key)[0..20]
@@ -12,14 +12,10 @@
12
12
  *
13
13
  * Algorithm IDs: Dilithium3=0, MlDsa65=1, SphincsSha2256f=2.
14
14
  *
15
- * Both pq1… and 0x… representations refer to the same underlying 20 bytes;
16
- * the SDK accepts either form in most places via the `AddressLike` type.
17
- *
18
15
  * @module address
19
16
  */
20
17
  import { blake3 } from "@noble/hashes/blake3";
21
18
  import { bech32m } from "@scure/base";
22
- import { bytesToHex, hexToBytes } from "viem";
23
19
  /** Human-readable part (HRP) used in Shell bech32m addresses. */
24
20
  export const PQ_ADDRESS_HRP = "pq";
25
21
  /** Number of raw address bytes (excluding the version byte). */
@@ -31,11 +27,6 @@ function assertBech32Address(value) {
31
27
  throw new Error("invalid bech32m address");
32
28
  }
33
29
  }
34
- function assertHexAddress(value) {
35
- if (!value.startsWith("0x")) {
36
- throw new Error("invalid hex address");
37
- }
38
- }
39
30
  /**
40
31
  * Encode 20 raw address bytes as a `pq1…` bech32m address.
41
32
  *
@@ -106,61 +97,18 @@ export function pqAddressVersion(address) {
106
97
  return bytes[0];
107
98
  }
108
99
  /**
109
- * Parse a `0x…` hex address string into its raw 20 bytes.
100
+ * Normalise an address: validates it is a `pq1…` bech32m address and returns it.
110
101
  *
111
- * @param address - A `0x`-prefixed 40-character hex address.
112
- * @returns The 20-byte address payload.
113
- * @throws {Error} If the string does not start with `"0x"` or is not exactly 20 bytes.
114
- */
115
- export function hexAddressToBytes(address) {
116
- assertHexAddress(address);
117
- const bytes = hexToBytes(address);
118
- if (bytes.length !== PQ_ADDRESS_LENGTH) {
119
- throw new Error(`expected ${PQ_ADDRESS_LENGTH} address bytes, got ${bytes.length}`);
120
- }
121
- return bytes;
122
- }
123
- /**
124
- * Encode 20 raw address bytes as a `0x…` hex address.
102
+ * Hex (`0x…`) addresses are no longer accepted. Use `pq1…` format everywhere.
125
103
  *
126
- * @param bytes - Exactly 20 address bytes.
127
- * @returns A `0x`-prefixed 40-character hex string.
128
- * @throws {Error} If `bytes.length !== 20`.
129
- */
130
- export function bytesToHexAddress(bytes) {
131
- if (bytes.length !== PQ_ADDRESS_LENGTH) {
132
- throw new Error(`expected ${PQ_ADDRESS_LENGTH} address bytes, got ${bytes.length}`);
133
- }
134
- return bytesToHex(bytes);
135
- }
136
- /**
137
- * Normalise an address to `pq1…` bech32m form.
138
- *
139
- * Accepts either a `pq1…` or `0x…` address and always returns the canonical
140
- * bech32m form.
141
- *
142
- * @param address - A `pq1…` or `0x…` address.
143
- * @returns The `pq1…` bech32m address.
104
+ * @param address - A `pq1…` bech32m address.
105
+ * @returns The same `pq1…` address (validated).
106
+ * @throws {Error} If the address is not a valid `pq1…` bech32m address.
144
107
  */
145
108
  export function normalizePqAddress(address) {
146
- if (isPqAddress(address)) {
147
- return address;
148
- }
149
- return bytesToPqAddress(hexAddressToBytes(address));
150
- }
151
- /**
152
- * Normalise an address to `0x…` hex form.
153
- *
154
- * Accepts either a `pq1…` or `0x…` address and always returns the hex form.
155
- *
156
- * @param address - A `pq1…` or `0x…` address.
157
- * @returns The `0x`-prefixed hex address.
158
- */
159
- export function normalizeHexAddress(address) {
160
- if (isPqAddress(address)) {
161
- return bytesToHexAddress(pqAddressToBytes(address));
109
+ if (!isPqAddress(address)) {
110
+ throw new Error(`expected a pq1… bech32m address, got: "${address.slice(0, 10)}…"`);
162
111
  }
163
- assertHexAddress(address);
164
112
  return address;
165
113
  }
166
114
  /**
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { bytesToHexAddress, PQ_ADDRESS_HRP, PQ_ADDRESS_LENGTH, PQ_ADDRESS_VERSION_V1, bytesToPqAddress, derivePqAddressFromPublicKey, isPqAddress, normalizeHexAddress, normalizePqAddress, pqAddressVersion, pqAddressToBytes, } from "./address.js";
1
+ export { PQ_ADDRESS_HRP, PQ_ADDRESS_LENGTH, PQ_ADDRESS_VERSION_V1, bytesToPqAddress, derivePqAddressFromPublicKey, isPqAddress, normalizePqAddress, pqAddressVersion, pqAddressToBytes, } from "./address.js";
2
2
  export { createShellProvider, createShellPublicClient, createShellWsClient, ShellProvider, shellDevnet, type CreateShellPublicClientOptions, } from "./provider.js";
3
- export { accountManagerAddress, accountManagerHexAddress, cancelRecoverySelector, clearValidationCodeSelector, encodeCancelRecoveryCalldata, encodeClearValidationCodeCalldata, encodeExecuteRecoveryCalldata, encodeRotateKeyCalldata, encodeSetGuardiansCalldata, encodeSetValidationCodeCalldata, encodeSubmitRecoveryCalldata, executeRecoverySelector, isSystemContractAddress, rotateKeySelector, setGuardiansSelector, setValidationCodeSelector, submitRecoverySelector, validatorRegistryAddress, validatorRegistryHexAddress, } from "./system-contracts.js";
3
+ export { accountManagerAddress, cancelRecoverySelector, clearValidationCodeSelector, encodeCancelRecoveryCalldata, encodeClearValidationCodeCalldata, encodeExecuteRecoveryCalldata, encodeRotateKeyCalldata, encodeSetGuardiansCalldata, encodeSetValidationCodeCalldata, encodeSubmitRecoveryCalldata, executeRecoverySelector, isSystemContractAddress, rotateKeySelector, setGuardiansSelector, setValidationCodeSelector, submitRecoverySelector, validatorRegistryAddress, } from "./system-contracts.js";
4
4
  export { AA_BUNDLE_TX_TYPE, AA_MAX_INNER_CALLS, buildBatchTransaction, buildClearValidationCodeTransaction, buildContractPaymasterTransaction, buildInnerCall, buildInnerTransfer, buildRotateKeyTransaction, buildSessionKeyTransaction, buildSetValidationCodeTransaction, buildSignature, buildSignedTransaction, buildSponsoredTransaction, buildSystemTransaction, buildTransaction, buildTransferTransaction, DEFAULT_AA_GAS_LIMIT, DEFAULT_MAX_FEE_PER_GAS, DEFAULT_MAX_PRIORITY_FEE_PER_GAS, DEFAULT_SYSTEM_GAS_LIMIT, DEFAULT_TRANSFER_GAS_LIMIT, DEFAULT_TX_TYPE, hashBatchTransaction, hashTransaction, type BuildBatchTransactionOptions, type BuildContractPaymasterTransactionOptions, type BuildSessionKeyTransactionOptions, type BuildSponsoredTransactionOptions, } 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";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- export { bytesToHexAddress, PQ_ADDRESS_HRP, PQ_ADDRESS_LENGTH, PQ_ADDRESS_VERSION_V1, bytesToPqAddress, derivePqAddressFromPublicKey, isPqAddress, normalizeHexAddress, normalizePqAddress, pqAddressVersion, pqAddressToBytes, } from "./address.js";
1
+ export { PQ_ADDRESS_HRP, PQ_ADDRESS_LENGTH, PQ_ADDRESS_VERSION_V1, bytesToPqAddress, derivePqAddressFromPublicKey, isPqAddress, normalizePqAddress, pqAddressVersion, pqAddressToBytes, } from "./address.js";
2
2
  export { createShellProvider, createShellPublicClient, createShellWsClient, ShellProvider, shellDevnet, } from "./provider.js";
3
- export { accountManagerAddress, accountManagerHexAddress, cancelRecoverySelector, clearValidationCodeSelector, encodeCancelRecoveryCalldata, encodeClearValidationCodeCalldata, encodeExecuteRecoveryCalldata, encodeRotateKeyCalldata, encodeSetGuardiansCalldata, encodeSetValidationCodeCalldata, encodeSubmitRecoveryCalldata, executeRecoverySelector, isSystemContractAddress, rotateKeySelector, setGuardiansSelector, setValidationCodeSelector, submitRecoverySelector, validatorRegistryAddress, validatorRegistryHexAddress, } from "./system-contracts.js";
3
+ export { accountManagerAddress, cancelRecoverySelector, clearValidationCodeSelector, encodeCancelRecoveryCalldata, encodeClearValidationCodeCalldata, encodeExecuteRecoveryCalldata, encodeRotateKeyCalldata, encodeSetGuardiansCalldata, encodeSetValidationCodeCalldata, encodeSubmitRecoveryCalldata, executeRecoverySelector, isSystemContractAddress, rotateKeySelector, setGuardiansSelector, setValidationCodeSelector, submitRecoverySelector, validatorRegistryAddress, } from "./system-contracts.js";
4
4
  export { AA_BUNDLE_TX_TYPE, AA_MAX_INNER_CALLS, buildBatchTransaction, buildClearValidationCodeTransaction, buildContractPaymasterTransaction, buildInnerCall, buildInnerTransfer, buildRotateKeyTransaction, buildSessionKeyTransaction, buildSetValidationCodeTransaction, buildSignature, buildSignedTransaction, buildSponsoredTransaction, buildSystemTransaction, buildTransaction, buildTransferTransaction, DEFAULT_AA_GAS_LIMIT, DEFAULT_MAX_FEE_PER_GAS, DEFAULT_MAX_PRIORITY_FEE_PER_GAS, DEFAULT_SYSTEM_GAS_LIMIT, DEFAULT_TRANSFER_GAS_LIMIT, DEFAULT_TX_TYPE, hashBatchTransaction, 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";
@@ -16,8 +16,6 @@ export interface ParsedShellKeystore {
16
16
  publicKey: Uint8Array;
17
17
  /** Canonical `pq1…` address derived from `publicKey`. */
18
18
  canonicalAddress: string;
19
- /** `0x`-prefixed hex representation of the same address. */
20
- hexAddress: string;
21
19
  }
22
20
  /**
23
21
  * Parse a Shell keystore file (string or object) and extract public metadata.
@@ -33,7 +31,7 @@ export interface ParsedShellKeystore {
33
31
  * ```typescript
34
32
  * const parsed = parseEncryptedKey(readFileSync("key.json", "utf8"));
35
33
  * console.log(parsed.canonicalAddress); // pq1…
36
- * console.log(parsed.signatureType); // "MlDsa65"
34
+ * console.log(parsed.signatureType); // "ML-DSA-65"
37
35
  * ```
38
36
  */
39
37
  export declare function parseEncryptedKey(input: string | ShellEncryptedKey): ParsedShellKeystore;
@@ -77,17 +75,14 @@ export declare function assertSignerMatchesKeystore(signer: ShellSigner, keystor
77
75
  *
78
76
  * **KDF**: argon2id (parameters from `kdf_params`)
79
77
  * **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.
78
+ * **Plaintext layout (v1)**: secret key bytes only (sk-only format).
79
+ * The public key is read from the `public_key` JSON field directly.
84
80
  *
85
81
  * @param input - Keystore JSON string or object.
86
82
  * @param password - The passphrase used to encrypt the key.
87
83
  * @returns A fully configured `ShellSigner` ready for signing transactions.
88
84
  * @throws {Error} If the KDF or cipher is unsupported.
89
85
  * @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
86
  *
92
87
  * @example
93
88
  * ```typescript
package/dist/keystore.js CHANGED
@@ -6,19 +6,23 @@
6
6
  * - **KDF**: argon2id (memory-hard password derivation)
7
7
  * - **Cipher**: xchacha20-poly1305 (authenticated encryption)
8
8
  *
9
- * They are generated by the Shell CLI (`shell key generate`) and are
10
- * compatible with the SDK's `decryptKeystore` function.
9
+ * ## Keystore format (v1 canonical)
11
10
  *
12
- * Plaintext layout after decryption: `[secret_key_bytes][public_key_bytes]`.
11
+ * The **ciphertext** contains **only the secret key bytes** (sk-only format).
12
+ * The public key is stored separately in the `public_key` field as plain hex.
13
+ * This matches the format produced by `shell-node key generate`.
14
+ *
15
+ * Previous SDK versions expected `sk || pk` in the ciphertext. That format
16
+ * is no longer produced or accepted; all new keystores use sk-only.
13
17
  *
14
18
  * @module keystore
15
19
  */
16
20
  import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
17
21
  import { argon2id } from "hash-wasm";
18
- import { derivePqAddressFromPublicKey, normalizeHexAddress, normalizePqAddress } from "./address.js";
22
+ import { derivePqAddressFromPublicKey, normalizePqAddress } from "./address.js";
19
23
  import { adapterFromKeyPair } from "./adapters.js";
20
- import { ShellSigner, publicKeyFromHex, signatureTypeFromKeyType } from "./signer.js";
21
- const SIG_IDS = { "ML-DSA-65": 0, Dilithium3: 0, MlDsa65: 0, SphincsSha2256f: 2 };
24
+ import { ShellSigner, canonicalSignatureType, publicKeyFromHex, signatureTypeFromKeyType, } from "./signer.js";
25
+ const SIG_IDS = { "ML-DSA-65": 1, Dilithium3: 0, MlDsa65: 1, SphincsSha2256f: 2 };
22
26
  /**
23
27
  * Parse a Shell keystore file (string or object) and extract public metadata.
24
28
  *
@@ -33,7 +37,7 @@ const SIG_IDS = { "ML-DSA-65": 0, Dilithium3: 0, MlDsa65: 0, SphincsSha2256f: 2
33
37
  * ```typescript
34
38
  * const parsed = parseEncryptedKey(readFileSync("key.json", "utf8"));
35
39
  * console.log(parsed.canonicalAddress); // pq1…
36
- * console.log(parsed.signatureType); // "MlDsa65"
40
+ * console.log(parsed.signatureType); // "ML-DSA-65"
37
41
  * ```
38
42
  */
39
43
  export function parseEncryptedKey(input) {
@@ -42,8 +46,7 @@ export function parseEncryptedKey(input) {
42
46
  const algorithmId = SIG_IDS[signatureType];
43
47
  const publicKey = publicKeyFromHex(raw.public_key);
44
48
  const canonicalAddress = derivePqAddressFromPublicKey(publicKey, algorithmId);
45
- const hexAddress = normalizeHexAddress(canonicalAddress);
46
- return { raw, signatureType, algorithmId, publicKey, canonicalAddress, hexAddress };
49
+ return { raw, signatureType, algorithmId, publicKey, canonicalAddress };
47
50
  }
48
51
  /**
49
52
  * Parse a keystore and verify that the declared address matches the public key.
@@ -89,7 +92,7 @@ export function exportEncryptedKeyJson(input) {
89
92
  * ```
90
93
  */
91
94
  export function assertSignerMatchesKeystore(signer, keystore) {
92
- if (signer.signatureType !== keystore.signatureType) {
95
+ if (canonicalSignatureType(signer.signatureType) !== keystore.signatureType) {
93
96
  throw new Error("algorithm mismatch: signer=" + signer.signatureType + " keystore=" + keystore.signatureType);
94
97
  }
95
98
  const addr = signer.getAddress();
@@ -110,17 +113,14 @@ function hexToBytes(hex) {
110
113
  *
111
114
  * **KDF**: argon2id (parameters from `kdf_params`)
112
115
  * **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.
116
+ * **Plaintext layout (v1)**: secret key bytes only (sk-only format).
117
+ * The public key is read from the `public_key` JSON field directly.
117
118
  *
118
119
  * @param input - Keystore JSON string or object.
119
120
  * @param password - The passphrase used to encrypt the key.
120
121
  * @returns A fully configured `ShellSigner` ready for signing transactions.
121
122
  * @throws {Error} If the KDF or cipher is unsupported.
122
123
  * @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
124
  *
125
125
  * @example
126
126
  * ```typescript
@@ -150,17 +150,8 @@ export async function decryptKeystore(input, password) {
150
150
  });
151
151
  const derivedKey = hexToBytes(derivedKeyHex);
152
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
- }
153
+ // Plaintext is sk-only; public key comes from the JSON `public_key` field.
154
+ const secretKey = chacha.decrypt(ciphertext);
164
155
  const adapter = adapterFromKeyPair(parsed.signatureType, parsed.publicKey, secretKey);
165
156
  return new ShellSigner(parsed.signatureType, adapter);
166
157
  }
@@ -113,7 +113,7 @@ export declare class ShellProvider {
113
113
  * Calls `shell_getPqPubkey`. Returns `null` if the address has not yet
114
114
  * submitted a transaction (public key is only recorded on first send).
115
115
  *
116
- * @param address - A `pq1…` or `0x…` address.
116
+ * @param address - A `pq1…` bech32m address.
117
117
  * @returns Hex-encoded public key string, or `null` if unknown.
118
118
  */
119
119
  getPqPubkey(address: string): Promise<string | null>;
@@ -279,7 +279,7 @@ export declare function createShellWsClient(options?: CreateShellPublicClientOpt
279
279
  * @example
280
280
  * ```typescript
281
281
  * const provider = createShellProvider();
282
- * const balance = await provider.client.getBalance({ address: signer.getHexAddress() });
282
+ * const balance = await provider.getBalance(signer.getAddress());
283
283
  * const hash = await provider.sendTransaction(signedTx);
284
284
  * ```
285
285
  */
package/dist/provider.js CHANGED
@@ -86,7 +86,7 @@ export class ShellProvider {
86
86
  * Calls `shell_getPqPubkey`. Returns `null` if the address has not yet
87
87
  * submitted a transaction (public key is only recorded on first send).
88
88
  *
89
- * @param address - A `pq1…` or `0x…` address.
89
+ * @param address - A `pq1…` bech32m address.
90
90
  * @returns Hex-encoded public key string, or `null` if unknown.
91
91
  */
92
92
  async getPqPubkey(address) {
@@ -294,7 +294,7 @@ export function createShellWsClient(options = {}) {
294
294
  * @example
295
295
  * ```typescript
296
296
  * const provider = createShellProvider();
297
- * const balance = await provider.client.getBalance({ address: signer.getHexAddress() });
297
+ * const balance = await provider.getBalance(signer.getAddress());
298
298
  * const hash = await provider.sendTransaction(signedTx);
299
299
  * ```
300
300
  */
package/dist/signer.d.ts CHANGED
@@ -4,9 +4,8 @@ import type { SignedShellTransaction, SignatureTypeName } from "./types.js";
4
4
  * Maps each {@link SignatureTypeName} to its numeric algorithm ID used in
5
5
  * address derivation and on-chain records.
6
6
  *
7
- * - `"ML-DSA-65"` → `0` (canonical FIPS 204 name)
8
- * - `"Dilithium3"` → `0` (legacy alias, same algorithm)
9
- * - `"MlDsa65"` → `0` (camelCase alias, same algorithm)
7
+ * - `"Dilithium3"` → `0` (Round 3 Dilithium, legacy compat)
8
+ * - `"ML-DSA-65"` / `"MlDsa65"` → `1` (FIPS 204 ML-DSA-65, canonical)
10
9
  * - `"SphincsSha2256f"` → `2`
11
10
  */
12
11
  export declare const SIGNATURE_TYPE_IDS: Record<SignatureTypeName, number>;
@@ -15,9 +14,9 @@ export declare const SIGNATURE_TYPE_IDS: Record<SignatureTypeName, number>;
15
14
  * corresponding {@link SignatureTypeName}.
16
15
  *
17
16
  * Keys are lowercase; matching is done after calling `.toLowerCase()`.
18
- * Always returns the FIPS 204 canonical name `"ML-DSA-65"` for ML-DSA-65 variants.
19
17
  */
20
18
  export declare const KEY_TYPE_TO_SIGNATURE_TYPE: Record<string, SignatureTypeName>;
19
+ export declare function canonicalSignatureType(signatureType: SignatureTypeName): SignatureTypeName;
21
20
  /**
22
21
  * Minimal interface that any post-quantum signing implementation must satisfy
23
22
  * to be used with {@link ShellSigner}.
@@ -55,8 +54,7 @@ export interface SignerAdapter {
55
54
  * const adapter = MlDsa65Adapter.generate();
56
55
  * const signer = new ShellSigner("MlDsa65", adapter);
57
56
  *
58
- * console.log(signer.getAddress()); // pq1…
59
- * console.log(signer.getHexAddress()); // 0x…
57
+ * console.log(signer.getAddress()); // pq1…
60
58
  * ```
61
59
  */
62
60
  export declare class ShellSigner {
@@ -83,12 +81,6 @@ export declare class ShellSigner {
83
81
  * The address is computed deterministically from the public key and algorithm ID.
84
82
  */
85
83
  getAddress(): string;
86
- /**
87
- * Return the `0x…` hex representation of this signer's address.
88
- *
89
- * Equivalent to `normalizeHexAddress(signer.getAddress())`.
90
- */
91
- getHexAddress(): `0x${string}`;
92
84
  /**
93
85
  * Sign a raw byte message with the underlying adapter.
94
86
  *
@@ -132,7 +124,7 @@ export declare class ShellSigner {
132
124
  *
133
125
  * @example
134
126
  * ```typescript
135
- * signatureTypeFromKeyType("mldsa65"); // "MlDsa65"
127
+ * signatureTypeFromKeyType("mldsa65"); // "ML-DSA-65"
136
128
  * signatureTypeFromKeyType("sphincs-sha2-256f"); // "SphincsSha2256f"
137
129
  * ```
138
130
  */
package/dist/signer.js CHANGED
@@ -10,21 +10,20 @@
10
10
  * @module signer
11
11
  */
12
12
  import { hexToBytes } from "viem";
13
- import { derivePqAddressFromPublicKey, normalizeHexAddress, normalizePqAddress } from "./address.js";
13
+ import { derivePqAddressFromPublicKey, normalizePqAddress } from "./address.js";
14
14
  import { buildSignature, buildSignedTransaction, } from "./transactions.js";
15
15
  /**
16
16
  * Maps each {@link SignatureTypeName} to its numeric algorithm ID used in
17
17
  * address derivation and on-chain records.
18
18
  *
19
- * - `"ML-DSA-65"` → `0` (canonical FIPS 204 name)
20
- * - `"Dilithium3"` → `0` (legacy alias, same algorithm)
21
- * - `"MlDsa65"` → `0` (camelCase alias, same algorithm)
19
+ * - `"Dilithium3"` → `0` (Round 3 Dilithium, legacy compat)
20
+ * - `"ML-DSA-65"` / `"MlDsa65"` → `1` (FIPS 204 ML-DSA-65, canonical)
22
21
  * - `"SphincsSha2256f"` → `2`
23
22
  */
24
23
  export const SIGNATURE_TYPE_IDS = {
25
- "ML-DSA-65": 0,
24
+ "ML-DSA-65": 1,
26
25
  Dilithium3: 0,
27
- MlDsa65: 0,
26
+ MlDsa65: 1,
28
27
  SphincsSha2256f: 2,
29
28
  };
30
29
  /**
@@ -32,14 +31,16 @@ export const SIGNATURE_TYPE_IDS = {
32
31
  * corresponding {@link SignatureTypeName}.
33
32
  *
34
33
  * Keys are lowercase; matching is done after calling `.toLowerCase()`.
35
- * Always returns the FIPS 204 canonical name `"ML-DSA-65"` for ML-DSA-65 variants.
36
34
  */
37
35
  export const KEY_TYPE_TO_SIGNATURE_TYPE = {
38
36
  "ml-dsa-65": "ML-DSA-65",
39
37
  mldsa65: "ML-DSA-65",
40
- dilithium3: "ML-DSA-65",
38
+ dilithium3: "Dilithium3",
41
39
  "sphincs-sha2-256f": "SphincsSha2256f",
42
40
  };
41
+ export function canonicalSignatureType(signatureType) {
42
+ return signatureType === "MlDsa65" ? "ML-DSA-65" : signatureType;
43
+ }
43
44
  /**
44
45
  * High-level Shell Chain signer.
45
46
  *
@@ -54,8 +55,7 @@ export const KEY_TYPE_TO_SIGNATURE_TYPE = {
54
55
  * const adapter = MlDsa65Adapter.generate();
55
56
  * const signer = new ShellSigner("MlDsa65", adapter);
56
57
  *
57
- * console.log(signer.getAddress()); // pq1…
58
- * console.log(signer.getHexAddress()); // 0x…
58
+ * console.log(signer.getAddress()); // pq1…
59
59
  * ```
60
60
  */
61
61
  export class ShellSigner {
@@ -68,7 +68,7 @@ export class ShellSigner {
68
68
  * @param adapter - An adapter providing `sign` and `getPublicKey`.
69
69
  */
70
70
  constructor(signatureType, adapter) {
71
- this.signatureType = signatureType;
71
+ this.signatureType = canonicalSignatureType(signatureType);
72
72
  this.adapter = adapter;
73
73
  }
74
74
  /**
@@ -91,14 +91,6 @@ export class ShellSigner {
91
91
  getAddress() {
92
92
  return derivePqAddressFromPublicKey(this.getPublicKey(), this.algorithmId);
93
93
  }
94
- /**
95
- * Return the `0x…` hex representation of this signer's address.
96
- *
97
- * Equivalent to `normalizeHexAddress(signer.getAddress())`.
98
- */
99
- getHexAddress() {
100
- return normalizeHexAddress(this.getAddress());
101
- }
102
94
  /**
103
95
  * Sign a raw byte message with the underlying adapter.
104
96
  *
@@ -150,7 +142,7 @@ export class ShellSigner {
150
142
  *
151
143
  * @example
152
144
  * ```typescript
153
- * signatureTypeFromKeyType("mldsa65"); // "MlDsa65"
145
+ * signatureTypeFromKeyType("mldsa65"); // "ML-DSA-65"
154
146
  * signatureTypeFromKeyType("sphincs-sha2-256f"); // "SphincsSha2256f"
155
147
  * ```
156
148
  */
@@ -1,8 +1,4 @@
1
1
  import type { AddressLike, HexString } from "./types.js";
2
- /** Hex address of the ValidatorRegistry system contract (`0x…0001`). */
3
- export declare const validatorRegistryHexAddress = "0x0000000000000000000000000000000000000001";
4
- /** Hex address of the AccountManager system contract (`0x…0002`). */
5
- export declare const accountManagerHexAddress = "0x0000000000000000000000000000000000000002";
6
2
  /** `pq1…` bech32m address of the ValidatorRegistry system contract. */
7
3
  export declare const validatorRegistryAddress: string;
8
4
  /** `pq1…` bech32m address of the AccountManager system contract. */
@@ -57,17 +53,13 @@ export declare function encodeClearValidationCodeCalldata(): HexString;
57
53
  /**
58
54
  * Return `true` if `address` refers to one of the Shell system contracts.
59
55
  *
60
- * Accepts both `pq1…` and `0x…` forms for the AccountManager and
61
- * ValidatorRegistry addresses.
62
- *
63
- * @param address - Address to test (any format accepted by `AddressLike`).
56
+ * @param address - `pq1…` address to test.
64
57
  * @returns `true` if the address matches AccountManager or ValidatorRegistry.
65
58
  *
66
59
  * @example
67
60
  * ```typescript
68
- * isSystemContractAddress("0x0000000000000000000000000000000000000002"); // true
69
- * isSystemContractAddress(accountManagerAddress); // true
70
- * isSystemContractAddress("pq1someuser…"); // false
61
+ * isSystemContractAddress(accountManagerAddress); // true
62
+ * isSystemContractAddress("pq1someuser…"); // false
71
63
  * ```
72
64
  */
73
65
  export declare function isSystemContractAddress(address: AddressLike): boolean;
@@ -78,7 +70,7 @@ export declare function isSystemContractAddress(address: AddressLike): boolean;
78
70
  * account owner can call this (the call must be made as an inner call from
79
71
  * the owner's AA bundle, or as a direct transaction).
80
72
  *
81
- * @param guardians - Array of guardian addresses (1..5). May be hex or `pq1…` form.
73
+ * @param guardians - Array of guardian addresses (1..5). Must be `pq1…` bech32m form.
82
74
  * @param threshold - k-of-n required votes (1 ≤ threshold ≤ guardians.length).
83
75
  * @param timelock - Minimum blocks between threshold-reach and execution (≥ 100).
84
76
  * @returns `HexString` with the 4-byte selector prepended.
@@ -86,7 +78,7 @@ export declare function isSystemContractAddress(address: AddressLike): boolean;
86
78
  * @example
87
79
  * ```typescript
88
80
  * const data = encodeSetGuardiansCalldata(
89
- * ["0xGuardian1…", "0xGuardian2…", "0xGuardian3…"],
81
+ * ["pq1guardian1…", "pq1guardian2…", "pq1guardian3…"],
90
82
  * 2, // 2-of-3 threshold
91
83
  * 100, // 100-block timelock
92
84
  * );
@@ -100,7 +92,7 @@ export declare function encodeSetGuardiansCalldata(guardians: AddressLike[], thr
100
92
  * When k-of-n threshold is reached, the proposal becomes executable after
101
93
  * `timelock` blocks.
102
94
  *
103
- * @param account - Account being recovered (0x hex or `pq1…` form).
95
+ * @param account - Account being recovered (`pq1…` bech32m form).
104
96
  * @param newPubkey - Raw bytes of the new PQ public key.
105
97
  * @param newAlgo - Algorithm ID for the new key (ML-DSA-65 = 0, etc.).
106
98
  * @returns `HexString` with the 4-byte selector prepended.
@@ -108,7 +100,7 @@ export declare function encodeSetGuardiansCalldata(guardians: AddressLike[], thr
108
100
  * @example
109
101
  * ```typescript
110
102
  * const data = encodeSubmitRecoveryCalldata(
111
- * "0xAccountAddress…",
103
+ * "pq1accountaddress…",
112
104
  * newPubkeyBytes,
113
105
  * 0, // ML-DSA-65
114
106
  * );
@@ -18,7 +18,7 @@
18
18
  * @module system-contracts
19
19
  */
20
20
  import { bytesToHex, encodeAbiParameters, keccak256, toBytes } from "viem";
21
- import { bytesToPqAddress } from "./address.js";
21
+ import { bytesToPqAddress, pqAddressToBytes } from "./address.js";
22
22
  const SYSTEM_ADDRESS_LENGTH = 20;
23
23
  function systemAddress(lastByte) {
24
24
  const bytes = new Uint8Array(SYSTEM_ADDRESS_LENGTH);
@@ -28,10 +28,6 @@ function systemAddress(lastByte) {
28
28
  function selector(signature) {
29
29
  return keccak256(toBytes(signature)).slice(0, 10);
30
30
  }
31
- /** Hex address of the ValidatorRegistry system contract (`0x…0001`). */
32
- export const validatorRegistryHexAddress = "0x0000000000000000000000000000000000000001";
33
- /** Hex address of the AccountManager system contract (`0x…0002`). */
34
- export const accountManagerHexAddress = "0x0000000000000000000000000000000000000002";
35
31
  /** `pq1…` bech32m address of the ValidatorRegistry system contract. */
36
32
  export const validatorRegistryAddress = bytesToPqAddress(systemAddress(1));
37
33
  /** `pq1…` bech32m address of the AccountManager system contract. */
@@ -100,24 +96,17 @@ export function encodeClearValidationCodeCalldata() {
100
96
  /**
101
97
  * Return `true` if `address` refers to one of the Shell system contracts.
102
98
  *
103
- * Accepts both `pq1…` and `0x…` forms for the AccountManager and
104
- * ValidatorRegistry addresses.
105
- *
106
- * @param address - Address to test (any format accepted by `AddressLike`).
99
+ * @param address - `pq1…` address to test.
107
100
  * @returns `true` if the address matches AccountManager or ValidatorRegistry.
108
101
  *
109
102
  * @example
110
103
  * ```typescript
111
- * isSystemContractAddress("0x0000000000000000000000000000000000000002"); // true
112
- * isSystemContractAddress(accountManagerAddress); // true
113
- * isSystemContractAddress("pq1someuser…"); // false
104
+ * isSystemContractAddress(accountManagerAddress); // true
105
+ * isSystemContractAddress("pq1someuser…"); // false
114
106
  * ```
115
107
  */
116
108
  export function isSystemContractAddress(address) {
117
- return (address === accountManagerAddress ||
118
- address === validatorRegistryAddress ||
119
- address === accountManagerHexAddress ||
120
- address === validatorRegistryHexAddress);
109
+ return address === accountManagerAddress || address === validatorRegistryAddress;
121
110
  }
122
111
  // ---------------------------------------------------------------------------
123
112
  // AA Phase 2 — Guardian Recovery calldata encoders (v0.19.0-dev)
@@ -129,7 +118,7 @@ export function isSystemContractAddress(address) {
129
118
  * account owner can call this (the call must be made as an inner call from
130
119
  * the owner's AA bundle, or as a direct transaction).
131
120
  *
132
- * @param guardians - Array of guardian addresses (1..5). May be hex or `pq1…` form.
121
+ * @param guardians - Array of guardian addresses (1..5). Must be `pq1…` bech32m form.
133
122
  * @param threshold - k-of-n required votes (1 ≤ threshold ≤ guardians.length).
134
123
  * @param timelock - Minimum blocks between threshold-reach and execution (≥ 100).
135
124
  * @returns `HexString` with the 4-byte selector prepended.
@@ -137,18 +126,15 @@ export function isSystemContractAddress(address) {
137
126
  * @example
138
127
  * ```typescript
139
128
  * const data = encodeSetGuardiansCalldata(
140
- * ["0xGuardian1…", "0xGuardian2…", "0xGuardian3…"],
129
+ * ["pq1guardian1…", "pq1guardian2…", "pq1guardian3…"],
141
130
  * 2, // 2-of-3 threshold
142
131
  * 100, // 100-block timelock
143
132
  * );
144
133
  * ```
145
134
  */
146
135
  export function encodeSetGuardiansCalldata(guardians, threshold, timelock) {
147
- // Normalise to 0x… form; encodeAbiParameters expects `0x${string}` addresses.
148
- const hexGuardians = guardians.map((g) => {
149
- const s = typeof g === "string" ? g : bytesToHex(g);
150
- return (s.startsWith("0x") ? s : `0x${s}`);
151
- });
136
+ // Decode pq1… addresses to raw 20 bytes, then to 0x-hex for ABI encoding.
137
+ const hexGuardians = guardians.map((g) => bytesToHex(pqAddressToBytes(g)));
152
138
  const encoded = encodeAbiParameters([{ type: "address[]" }, { type: "uint8" }, { type: "uint64" }], [hexGuardians, threshold, BigInt(timelock)]);
153
139
  return `${setGuardiansSelector}${encoded.slice(2)}`;
154
140
  }
@@ -159,7 +145,7 @@ export function encodeSetGuardiansCalldata(guardians, threshold, timelock) {
159
145
  * When k-of-n threshold is reached, the proposal becomes executable after
160
146
  * `timelock` blocks.
161
147
  *
162
- * @param account - Account being recovered (0x hex or `pq1…` form).
148
+ * @param account - Account being recovered (`pq1…` bech32m form).
163
149
  * @param newPubkey - Raw bytes of the new PQ public key.
164
150
  * @param newAlgo - Algorithm ID for the new key (ML-DSA-65 = 0, etc.).
165
151
  * @returns `HexString` with the 4-byte selector prepended.
@@ -167,7 +153,7 @@ export function encodeSetGuardiansCalldata(guardians, threshold, timelock) {
167
153
  * @example
168
154
  * ```typescript
169
155
  * const data = encodeSubmitRecoveryCalldata(
170
- * "0xAccountAddress…",
156
+ * "pq1accountaddress…",
171
157
  * newPubkeyBytes,
172
158
  * 0, // ML-DSA-65
173
159
  * );
@@ -209,9 +195,7 @@ export function encodeCancelRecoveryCalldata(account) {
209
195
  // ---------------------------------------------------------------------------
210
196
  // Internal helpers
211
197
  // ---------------------------------------------------------------------------
198
+ /** Convert a pq1… address to a 0x-hex form for EVM ABI encoding. */
212
199
  function normaliseToHex(address) {
213
- if (typeof address !== "string") {
214
- return bytesToHex(address);
215
- }
216
- return (address.startsWith("0x") ? address : `0x${address}`);
200
+ return bytesToHex(pqAddressToBytes(address));
217
201
  }
@@ -11,7 +11,7 @@ import { bytesToHex, keccak256, toRlp, hexToBytes } from "viem";
11
11
  import { AA_BUNDLE_TX_TYPE, AA_MAX_INNER_CALLS } from "./types.js";
12
12
  export { AA_BUNDLE_TX_TYPE, AA_MAX_INNER_CALLS };
13
13
  import { accountManagerAddress, encodeClearValidationCodeCalldata, encodeRotateKeyCalldata, encodeSetValidationCodeCalldata, } from "./system-contracts.js";
14
- import { normalizeHexAddress } from "./address.js";
14
+ import { pqAddressToBytes } from "./address.js";
15
15
  /** Default transaction type: `2` (EIP-1559). */
16
16
  export const DEFAULT_TX_TYPE = 2;
17
17
  /** Default gas limit for simple SHELL token transfers (`21_000`). */
@@ -40,7 +40,7 @@ function toRlpAccessList(accessList) {
40
40
  return [];
41
41
  }
42
42
  return accessList.map((item) => [
43
- normalizeHexAddress(item.address),
43
+ bytesToHex(pqAddressToBytes(item.address)),
44
44
  item.storage_keys.map((key) => key),
45
45
  ]);
46
46
  }
@@ -249,7 +249,7 @@ export function hashTransaction(tx) {
249
249
  const fields = [
250
250
  toRlpUint(tx.chain_id),
251
251
  toRlpUint(tx.nonce),
252
- tx.to ? normalizeHexAddress(tx.to) : "0x",
252
+ tx.to ? bytesToHex(pqAddressToBytes(tx.to)) : "0x",
253
253
  toRlpUint(tx.value),
254
254
  tx.data,
255
255
  toRlpUint(tx.gas_limit),
@@ -368,14 +368,14 @@ export function hashBatchTransaction(tx, bundle) {
368
368
  }
369
369
  // Encode inner calls for signing (matches chain's encode_for_signing).
370
370
  const innerCallsRlp = bundle.inner_calls.map((call) => [
371
- call.to ? normalizeHexAddress(call.to) : "0x",
371
+ call.to ? bytesToHex(pqAddressToBytes(call.to)) : "0x",
372
372
  toRlpUint(call.value),
373
373
  call.data,
374
374
  toRlpUint(call.gas_limit),
375
375
  ]);
376
376
  // Paymaster: 20-byte address or empty bytes.
377
377
  const paymasterField = bundle.paymaster
378
- ? normalizeHexAddress(bundle.paymaster)
378
+ ? bytesToHex(pqAddressToBytes(bundle.paymaster))
379
379
  : "0x";
380
380
  // paymaster_context: raw bytes or empty.
381
381
  const paymasterContextField = bundle.paymaster_context && bundle.paymaster_context.length > 0
@@ -384,7 +384,7 @@ export function hashBatchTransaction(tx, bundle) {
384
384
  const txFields = [
385
385
  toRlpUint(tx.chain_id),
386
386
  toRlpUint(tx.nonce),
387
- tx.to ? normalizeHexAddress(tx.to) : "0x",
387
+ tx.to ? bytesToHex(pqAddressToBytes(tx.to)) : "0x",
388
388
  toRlpUint(tx.value),
389
389
  tx.data,
390
390
  toRlpUint(tx.gas_limit),
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** A `0x`-prefixed hex string, e.g. `"0xdeadbeef"`. */
2
2
  export type HexString = `0x${string}`;
3
- /** Any string accepted as an address — either a `pq1…` bech32m address or a `0x…` hex address. */
3
+ /** A `pq1…` bech32m address string (Shell Chain canonical address format). */
4
4
  export type AddressLike = string;
5
5
  /** A single entry in an EIP-2930 access list. */
6
6
  export interface ShellAccessListItem {
@@ -18,7 +18,7 @@ export interface ShellTransactionRequest {
18
18
  chain_id: number;
19
19
  /** Sender account nonce. */
20
20
  nonce: number;
21
- /** Recipient address (pq1 or 0x…), or `null` for contract deployment. */
21
+ /** Recipient address (`pq1…` bech32m format), or `null` for contract deployment. */
22
22
  to: AddressLike | null;
23
23
  /** Transfer value as a hex-encoded bigint string, e.g. `"0xde0b6b3a7640000"`. */
24
24
  value: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "TypeScript SDK for Shell Chain — build quantum-safe dApps before Q-Day.",
5
5
  "license": "MIT",
6
6
  "type": "module",