You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`whitechain-sdk/crypto` exposes low-level secp256k1 signing, verification, and public-key recovery, backed by a WebAssembly implementation (`tiny-secp256k1`) with an automatic, transparent fallback to a pure-JavaScript implementation (`@noble/curves`) in environments where WASM can't be loaded. The active backend is chosen lazily on first use and cached for the life of the process.
Both backends produce identical, low-S-normalized, RFC6979-deterministic output for the same input — the WASM path is purely a performance optimization, never a behavioral change.
76
+
77
+
## 🔒 Offline Transaction Signing (Cold Storage)
78
+
79
+
`whitechain-sdk/security` exposes `OfflineSigner`, a dedicated signer for **air-gapped** environments — machines with no network connection at all, such as a cold-storage laptop or hardware-adjacent signing device. It constructs and signs legacy and EIP-1559 transactions using only values you supply directly.
80
+
81
+
**Zero network dependencies, by construction:**`OfflineSigner` does not accept a provider, transport, public client, wallet client, RPC URL, chain RPC configuration, or SDK context of any kind — there is no parameter slot for one. It never calls `fetch`, `XMLHttpRequest`, `WebSocket`, an HTTP library, a viem client action, or a network-discovery function, and it never calls `getNetwork`, `getChainId`, `getTransactionCount`, `estimateGas`, `getGasPrice`, `estimateFeesPerGas`, `prepareTransactionRequest`, or any other helper that could silently fill in a missing field via RPC. Every field must come from you; a missing or invalid one fails immediately and locally with a `ValidationError`, never a network error.
- Fetch the account's next nonce, the current chain ID, an appropriate gas limit, and current fee data (`maxFeePerGas`/`maxPriorityFeePerGas`, or `gasPrice` for a legacy transaction).
87
+
- Assemble these into a plain, unsigned transaction object.
88
+
- Transfer **only this unsigned transaction data** to the offline environment (QR code, USB drive, manual entry) — never the private key in the other direction.
89
+
90
+
**2. Offline signing***(air-gapped machine, no network connection)*
91
+
- Import or otherwise securely provide the private key to this machine.
92
+
- Construct an `OfflineSigner` with it.
93
+
- Call `signTransaction(...)`, passing only the manually supplied values from stage 1.
94
+
- Export the resulting raw signed transaction hex.
95
+
- The private key never leaves this machine — it is never transmitted, logged, or included in any error message.
- Transfer only the signed raw transaction hex back — never the private key.
99
+
- Broadcast it via `eth_sendRawTransaction` (or `publicClient.sendRawTransaction({ serializedTransaction })`) on any online node.
100
+
- A successfully *signed* transaction can still fail at this stage: if the nonce, fee levels, account balance, or other chain state changed between stage 1 and stage 3 (for example, another transaction from the same account was mined in between), the node may reject it. Re-run stage 1 with fresh values if that happens.
console.log(signed.raw) // 0x02... (ready to broadcast)
122
+
console.log(signed.hash) // keccak256(signed.raw) — matches eth_sendRawTransaction's return value
123
+
console.log(signed.from) // the address that produced the signature
124
+
```
125
+
126
+
Legacy (pre-EIP-1559) transactions are supported the same way, priced with `gasPrice` instead of `maxFeePerGas`/`maxPriorityFeePerGas`:
127
+
128
+
```ts
129
+
const signed =awaitsigner.signTransaction({
130
+
chainId: 1,
131
+
nonce: 12,
132
+
to: '0xRecipient...',
133
+
value: 0n,
134
+
gas: 21_000n,
135
+
gasPrice: 20_000000000n,
136
+
})
137
+
```
138
+
139
+
Contract creation is supported by omitting `to` (or passing `null`) alongside deployment `data` — this is only ever intentional, since a normal transfer or call always specifies `to`.
140
+
141
+
All fields are validated locally and strictly: private key format/length, `chainId`/`nonce` as safe integers, a positive `gas` limit, non-negative fee/value fields, `maxFeePerGas >= maxPriorityFeePerGas`, address format/checksum, and well-formed hex call data. Every failure throws `ValidationError` synchronously or via a rejected promise — never a network error, since no network call is ever made.
142
+
143
+
### ⚠️ Security warning
144
+
145
+
`OfflineSigner` guarantees that the **signing step itself** performs no network I/O — that guarantee is enforced in code and covered by tests. It **cannot** guarantee the security of anything around that step: the operating system on the air-gapped machine, the removable media used to move data across the gap, how or where the private key is generated and stored, or the physical transfer process. Those remain entirely your responsibility. Treat the offline machine as if it will eventually be compromised, and design your key-management practices accordingly.
146
+
147
+
## 🔌 Plugin System
148
+
149
+
The SDK ships a first-class plugin architecture so community developers can extend the `WhitechainSDK` instance with custom namespaces — NFT marketplace helpers, lending calculators, analytics modules — without forking the core SDK or adding bloat to the core bundle.
150
+
151
+
### Core concepts
152
+
153
+
| Concept | Description |
154
+
|---|---|
155
+
|`WhitechainSDK`| The extensible host class. Accepts plugins at construction time or dynamically via `.use()`. |
156
+
|`ISDKPlugin`| Interface every plugin must implement: `name`, `version`, and `onInitialize(ctx)`. |
157
+
|`SDKContext`| Read-only view of SDK internals (`publicClient`, `walletClient`, `network`, `logger`) passed to `onInitialize`. |
158
+
|`WhitechainSDKPlugins`| Open interface for TypeScript declaration merging — augment it to add IDE autocomplete for your plugin's namespace. |
Plugins may perform async work (fetch on-chain config, resolve ENS, etc.) in `onInitialize`. Use `WhitechainSDK.create()` to ensure all hooks are fully settled before the instance is returned:
1. Export an object (or class instance) that satisfies `ISDKPlugin`.
263
+
2. Choose a unique `name` — it becomes the property key on the SDK instance. Avoid collisions with `publicClient`, `walletClient`, `network`, `logger`, `use`, and `getPlugins`.
264
+
3. Augment `WhitechainSDKPlugins` in your package's `index.d.ts` so consumers get full IDE support.
265
+
4. Keep the plugin self-contained. Do not import SDK internals directly — only use what `SDKContext` exposes.
266
+
5. The core bundle is **not affected** by external plugins; plugins are loaded lazily at runtime and contribute zero bytes to the base bundle.
267
+
63
268
## 🏗️ Design Philosophy
64
269
65
270
**Omitted By Design** to keep the SDK fast and secure:
@@ -81,6 +286,76 @@ Available scripts for local development:
81
286
-`npm run build` – Typecheck and emit ESM to `dist/`
82
287
-`npm run typecheck` – Typecheck only
83
288
-`npm run test` – Run unit tests via Vitest
289
+
-`npm run bench` – Benchmark the WASM vs JS secp256k1 signer (requires `npm run build` first)
290
+
291
+
### 🧪 Foundry Invariant & Stateful Fuzzing Suite
292
+
293
+
We utilize [Foundry](https://book.getfoundry.sh/) to perform deep stateful invariant testing on core AMM and Vault smart contracts. The fuzzing suite bombards contracts with random input sequences to ensure critical economic invariants hold true unconditionally across state space transitions.
294
+
295
+
To run the invariant test suite:
296
+
297
+
```bash
298
+
forge test --match-path "test/invariants/*"
299
+
```
300
+
301
+
#### Key Invariants Tested
302
+
303
+
-**Constant Product Formula (`x * y >= k`)**: Proves that AMM swaps (including 0.3% fee accrual) never decrease total pool constant $k$.
-**User Balance Boundary (`userBalance <= totalSupply`)**: Verifies no single user's LP or Vault share balance exceeds overall contract total supply.
306
+
-**Graceful Revert Handling**: Ensures zero-amount swap attempts and invalid inputs revert gracefully without breaking stateful fuzz runs.
307
+
308
+
#### Configuration (`foundry.toml`)
309
+
310
+
-**Runs**: 10,000 random input sequences per invariant.
311
+
-**Depth**: 500 call transitions per run.
312
+
-**Revert Policy**: `fail_on_revert = false` (handled via stateful `Handler.sol`).
313
+
314
+
### 🧪 Foundry Invariant & Stateful Fuzzing Suite
315
+
316
+
We utilize [Foundry](https://book.getfoundry.sh/) to perform deep stateful invariant testing on core AMM and Vault smart contracts. The fuzzing suite bombards contracts with random input sequences to ensure critical economic invariants hold true unconditionally across state space transitions.
317
+
318
+
To run the invariant test suite:
319
+
320
+
```bash
321
+
forge test --match-path "test/invariants/*"
322
+
```
323
+
324
+
#### Key Invariants Tested
325
+
326
+
-**Constant Product Formula (`x * y >= k`)**: Proves that AMM swaps (including 0.3% fee accrual) never decrease total pool constant $k$.
-**User Balance Boundary (`userBalance <= totalSupply`)**: Verifies no single user's LP or Vault share balance exceeds overall contract total supply.
329
+
-**Graceful Revert Handling**: Ensures zero-amount swap attempts and invalid inputs revert gracefully without breaking stateful fuzz runs.
330
+
331
+
#### Configuration (`foundry.toml`)
332
+
333
+
-**Runs**: 10,000 random input sequences per invariant.
334
+
-**Depth**: 500 call transitions per run.
335
+
-**Revert Policy**: `fail_on_revert = false` (handled via stateful `Handler.sol`).
336
+
337
+
### 🧪 Foundry Invariant & Stateful Fuzzing Suite
338
+
339
+
We utilize [Foundry](https://book.getfoundry.sh/) to perform deep stateful invariant testing on core AMM and Vault smart contracts. The fuzzing suite bombards contracts with random input sequences to ensure critical economic invariants hold true unconditionally across state space transitions.
340
+
341
+
To run the invariant test suite:
342
+
343
+
```bash
344
+
forge test --match-path "test/invariants/*"
345
+
```
346
+
347
+
#### Key Invariants Tested
348
+
349
+
-**Constant Product Formula (`x * y >= k`)**: Proves that AMM swaps (including 0.3% fee accrual) never decrease total pool constant $k$.
0 commit comments