{"_id":"@a16k/hana","_rev":"5-f61aeda1cb96650402b5510340a05c13","name":"@a16k/hana","dist-tags":{"latest":"1.1.1"},"versions":{"1.0.0":{"name":"@a16k/hana","version":"1.0.0","keywords":["ethereum","web3","evm","blockchain","smart-contracts","viem","ethers","adapter","abi","typescript","multicall","caching","testing","mocking"],"author":{"name":"a16k"},"license":"Apache-2.0","_id":"@a16k/hana@1.0.0","maintainers":[{"name":"pouriya-rtx","email":"pouriya.chibaie.dev@gmail.com"},{"name":"farajioranj","email":"farajioranj@gmail.com"},{"name":"saeedabdi","email":"saeedabdi.job@gmail.com"}],"homepage":"https://github.com/a16k/hana#readme","bugs":{"url":"https://github.com/a16k/hana/issues"},"dist":{"shasum":"daebf652a669c5d968f33bb7fca8113943dac112","tarball":"https://registry.npmjs.org/@a16k/hana/-/hana-1.0.0.tgz","fileCount":39,"integrity":"sha512-HDNs85yqNlkLKaOpVqrVfed/cb2XyqkbKH2VVhWoJUIeYQjboY7oaFAx8akmYUf0C0OdCNLbwFg3sK9kolb7mw==","signatures":[{"sig":"MEYCIQD7MyRlAlIg1aMc/olT9tnK09BpfQ/vuNMPWd/LK6iaWgIhALySRlO8hOfW8WIrGrqsYUdbpIDLf05TxqiQv6EJgm+O","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":887028},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":"20 || >=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./viem":{"import":{"types":"./dist/viem.d.ts","default":"./dist/viem.js"},"require":{"types":"./dist/viem.d.cts","default":"./dist/viem.cjs"}},"./web3":{"import":{"types":"./dist/web3.d.ts","default":"./dist/web3.js"},"require":{"types":"./dist/web3.d.cts","default":"./dist/web3.cjs"}},"./ethers":{"import":{"types":"./dist/ethers.d.ts","default":"./dist/ethers.js"},"require":{"types":"./dist/ethers.d.cts","default":"./dist/ethers.cjs"}},"./ethers5":{"import":{"types":"./dist/ethers5.d.ts","default":"./dist/ethers5.js"},"require":{"types":"./dist/ethers5.d.cts","default":"./dist/ethers5.cjs"}},"./testing":{"import":{"types":"./dist/testing.d.ts","default":"./dist/testing.js"},"require":{"types":"./dist/testing.d.cts","default":"./dist/testing.cjs"}},"./package.json":"./package.json"},"gitHead":"c72d0961a77995db284a50c6d3da5a2b9ef2ccbe","scripts":{"lint":"biome check .","test":"vitest run","build":"rm -rf dist && tsup","watch":"tsup --watch","format":"biome check --write .","coverage":"vitest run --coverage","test:dist":"sh scripts/smoke-dist.sh","typecheck":"tsc --noEmit --p tsconfig.typecheck.json && tsc --noEmit --p tsconfig.scripts.json","test:watch":"vitest --reporter=verbose","test:mutation":"tsx scripts/mutation-test.ts","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build","typecheck:watch":"tsc --noEmit --p tsconfig.typecheck.json --watch","generate:ethereum":"tsx scripts/generateEthereum.ts","generate:artifacts":"sh scripts/generateArtifacts.sh","generate:multicall-addresses":"tsx scripts/generateMulticallAddresses.ts"},"_npmUser":{"name":"farajioranj","email":"farajioranj@gmail.com"},"repository":{"url":"git+https://github.com/a16k/hana.git","type":"git"},"_npmVersion":"10.8.2","description":"HANA: a Hardened, Adapter-agnostic Network Abstraction for effortless Ethereum development across Web3 libraries","directories":{},"sideEffects":false,"_nodeVersion":"20.17.0","dependencies":{"ox":"^0.9.6","abitype":"^1.1.0","lru-cache":"^11.2.1","lodash.ismatch":"^4.4.0","safe-stable-stringify":"^2.5.0"},"publishConfig":{"access":"public"},"typesVersions":{"*":{".":["./dist/index.d.ts"],"viem":["./dist/viem.d.ts"],"web3":["./dist/web3.d.ts"],"ethers":["./dist/ethers.d.ts"],"ethers5":["./dist/ethers5.d.ts"],"testing":["./dist/testing.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.5","tsup":"^8.5.0","viem":"^2.55.2","web3":"^4.16.0","sinon":"^21.0.0","ethers":"^5.8.0 || ^6.17.0","vitest":"^3.2.4","ethers-v5":"npm:ethers@^5.8.0","typescript":"^5.9.2","@types/node":"^24.5.1","@types/sinon":"^17.0.4","@biomejs/biome":"^2.5.3","tsconfig-paths":"^4.2.0","@open-rpc/typings":"^1.12.4","@vitest/coverage-v8":"3.2.4","vite-tsconfig-paths":"^5.1.4","@types/lodash.ismatch":"^4.4.9"},"peerDependencies":{"viem":"^2.55.2","web3":"^4.16.0","sinon":"^21.0.0","ethers":"^5.8.0 || ^6.17.0","ethers-v5":"npm:ethers@^5.8.0","@types/sinon":"^17.0.4"},"peerDependenciesMeta":{"viem":{"optional":true},"web3":{"optional":true},"sinon":{"optional":true},"ethers":{"optional":true},"ethers-v5":{"optional":true},"@types/sinon":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/hana_1.0.0_1784397518544_0.005217682478295682","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@a16k/hana","version":"1.1.1","keywords":["ethereum","web3","evm","blockchain","smart-contracts","viem","ethers","adapter","abi","typescript","multicall","caching","testing","mocking"],"author":{"name":"a16k"},"license":"Apache-2.0","_id":"@a16k/hana@1.1.1","maintainers":[{"name":"pouriya-rtx","email":"pouriya.chibaie.dev@gmail.com"},{"name":"farajioranj","email":"farajioranj@gmail.com"},{"name":"realdev78","email":"saeedabdi.1402@gmail.com"}],"homepage":"https://github.com/a16k-lab/hana#readme","bugs":{"url":"https://github.com/a16k-lab/hana/issues"},"dist":{"shasum":"eee9d20d89493daa84a1a394992573418081db9d","tarball":"https://registry.npmjs.org/@a16k/hana/-/hana-1.1.1.tgz","fileCount":51,"integrity":"sha512-8rFGwlZuMC5Y7NfG3kxc7OAImbgtijY2Pr+iaahsLNXo4xMcXonXJ7u8TYIubltPgalNSDaCORTXf4kE/Z/bUw==","signatures":[{"sig":"MEUCIQDU9S5jRLEfjMQjWQLTvpiNIJULMBZuU5zBjyM3TnOi3wIgX1XLB/V3TRiAEzncR9OFr+UwukUpcnubFU+XmGuVa5g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":915944},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":"20 || >=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./viem":{"import":{"types":"./dist/viem.d.ts","default":"./dist/viem.js"},"require":{"types":"./dist/viem.d.cts","default":"./dist/viem.cjs"}},"./web3":{"import":{"types":"./dist/web3.d.ts","default":"./dist/web3.js"},"require":{"types":"./dist/web3.d.cts","default":"./dist/web3.cjs"}},"./react":{"import":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"require":{"types":"./dist/react.d.cts","default":"./dist/react.cjs"}},"./ethers":{"import":{"types":"./dist/ethers.d.ts","default":"./dist/ethers.js"},"require":{"types":"./dist/ethers.d.cts","default":"./dist/ethers.cjs"}},"./ethers5":{"import":{"types":"./dist/ethers5.d.ts","default":"./dist/ethers5.js"},"require":{"types":"./dist/ethers5.d.cts","default":"./dist/ethers5.cjs"}},"./testing":{"import":{"types":"./dist/testing.d.ts","default":"./dist/testing.js"},"require":{"types":"./dist/testing.d.cts","default":"./dist/testing.cjs"}},"./package.json":"./package.json"},"gitHead":"40f949a2dbdbd45c2d2db48b9572da8a081ac48c","scripts":{"lint":"biome check .","test":"vitest run","build":"rm -rf dist && tsup && node scripts/add-use-client.mjs","watch":"tsup --watch","format":"biome check --write .","coverage":"vitest run --coverage","test:dist":"sh scripts/smoke-dist.sh","typecheck":"tsc --noEmit --p tsconfig.typecheck.json && tsc --noEmit --p tsconfig.scripts.json","test:watch":"vitest --reporter=verbose","test:mutation":"tsx scripts/mutation-test.ts","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build","typecheck:watch":"tsc --noEmit --p tsconfig.typecheck.json --watch","generate:ethereum":"tsx scripts/generateEthereum.ts","generate:artifacts":"sh scripts/generateArtifacts.sh","generate:multicall-addresses":"tsx scripts/generateMulticallAddresses.ts"},"_npmUser":{"name":"farajioranj","email":"farajioranj@gmail.com"},"repository":{"url":"git+https://github.com/a16k-lab/hana.git","type":"git"},"_npmVersion":"10.8.2","description":"HANA: a Hardened, Adapter-agnostic Network Abstraction for effortless Ethereum development across Web3 libraries","directories":{},"sideEffects":false,"_nodeVersion":"20.17.0","dependencies":{"ox":"^0.9.6","abitype":"^1.1.0","lru-cache":"^11.2.1","lodash.ismatch":"^4.4.0","safe-stable-stringify":"^2.5.0"},"publishConfig":{"access":"public"},"typesVersions":{"*":{".":["./dist/index.d.ts"],"viem":["./dist/viem.d.ts"],"web3":["./dist/web3.d.ts"],"react":["./dist/react.d.ts"],"ethers":["./dist/ethers.d.ts"],"ethers5":["./dist/ethers5.d.ts"],"testing":["./dist/testing.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.5","tsup":"^8.5.0","viem":"^2.55.2","web3":"^4.16.0","react":"^19.2.7","sinon":"^21.0.0","ethers":"^5.8.0 || ^6.17.0","vitest":"^3.2.4","ethers-v5":"npm:ethers@^5.8.0","happy-dom":"^20.11.0","react-dom":"^19.2.7","typescript":"^5.9.2","@types/node":"^24.5.1","@types/react":"^19.2.17","@types/sinon":"^17.0.4","@biomejs/biome":"^2.5.3","tsconfig-paths":"^4.2.0","@open-rpc/typings":"^1.12.4","@vitest/coverage-v8":"3.2.4","vite-tsconfig-paths":"^5.1.4","@types/lodash.ismatch":"^4.4.9","@testing-library/react":"^16.3.2"},"peerDependencies":{"viem":"^2.55.2","web3":"^4.16.0","react":">=16.8.0","sinon":"^21.0.0","ethers":"^5.8.0 || ^6.17.0","ethers-v5":"npm:ethers@^5.8.0","@types/sinon":"^17.0.4"},"peerDependenciesMeta":{"viem":{"optional":true},"web3":{"optional":true},"react":{"optional":true},"sinon":{"optional":true},"ethers":{"optional":true},"ethers-v5":{"optional":true},"@types/sinon":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/hana_1.1.1_1784400553068_0.7161649560221186","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-07-18T17:58:38.408Z","modified":"2026-07-21T11:27:54.989Z","1.0.0":"2026-07-18T17:58:38.732Z","1.1.1":"2026-07-18T18:49:13.206Z"},"bugs":{"url":"https://github.com/a16k-lab/hana/issues"},"author":{"name":"a16k"},"license":"Apache-2.0","homepage":"https://github.com/a16k-lab/hana#readme","keywords":["ethereum","web3","evm","blockchain","smart-contracts","viem","ethers","adapter","abi","typescript","multicall","caching","testing","mocking"],"repository":{"url":"git+https://github.com/a16k-lab/hana.git","type":"git"},"description":"HANA: a Hardened, Adapter-agnostic Network Abstraction for effortless Ethereum development across Web3 libraries","maintainers":[{"email":"pouriya.chibaie.dev@gmail.com","name":"pouriya-rtx"},{"email":"farajioranj@gmail.com","name":"farajioranj"},{"email":"saeedabdi.1402@gmail.com","name":"realdev78"},{"email":"eferbarndefi@gmail.com","name":"eferbarn"}],"readme":"# HANA\n\n**H**ardened · **A**dapter-agnostic · **N**etwork · **A**bstraction\n\nHANA is a hardened, adapter-agnostic network abstraction for effortless Ethereum\ndevelopment. Write your contract logic once against a single, fully type-safe API\nand run it on top of whichever Web3 library you prefer, with built-in caching,\ntype-safe contract APIs, and easy-to-use testing mocks.\n\n- **Hardened** - extra-audited and optimized.\n- **Adapter-agnostic** - one API across `viem`, `ethers` (v6 and v5), and\n  `web3.js` via swappable adapters (an EIP-1193-compatible default adapter is\n  built in).\n- **Network** - reads, writes, events, and batching for any EVM chain.\n- **Abstraction** - a single, unified, type-safe layer instead of raw provider\n  calls.\n\n## Installation\n\n```sh\nnpm install @a16k/hana\n```\n\nThe default adapter talks to any EIP-1193 provider (or an RPC URL) with no extra\ndependencies. To back HANA with a specific library, install that library too and\nimport the matching adapter (see [Adapters](#adapters)).\n\n## Quick start\n\n```ts\nimport { createHana, erc20 } from \"@a16k/hana\";\n\nconst hana = createHana({ rpcUrl: process.env.RPC_URL });\n\n// Read, with built-in caching.\nconst balance = await hana.read({\n  abi: erc20.abi,\n  address: \"0x...\",\n  fn: \"balanceOf\",\n  args: { account: \"0x...\" },\n});\n\n// Or use a contract instance.\nconst token = hana.contract({ abi: erc20.abi, address: \"0x...\" });\nconst symbol = await token.read(\"symbol\");\n```\n\n## Adapters\n\nThe same HANA API runs on top of whichever Web3 library you already use. Pass an\nadapter to `createHana` and every read, write, contract, event, and batch call\nworks identically:\n\n```ts\nconst hana = createHana({ adapter: new SomeAdapter({ /* your client */ }) });\n```\n\n| Library    | Subpath              | Adapter           |\n| ---------- | -------------------- | ----------------- |\n| viem       | `@a16k/hana/viem`    | `ViemAdapter`     |\n| ethers v6  | `@a16k/hana/ethers`  | `EthersV6Adapter` |\n| ethers v5  | `@a16k/hana/ethers5` | `EthersV5Adapter` |\n| web3.js v4 | `@a16k/hana/web3`    | `Web3Adapter`     |\n\nEach adapter wraps an existing client or provider (or builds one from an\n`rpcUrl`, or falls back to `globalThis.ethereum`), so HANA reuses your one\nconnection. Values follow a single convention regardless of the backing library:\namounts are `bigint` and hashes, addresses, and bytes are `0x${string}`. The\nshape-heavy methods (block, transaction, receipt, logs, and EIP-5792 wallet\ncalls) are inherited from the default adapter and routed through your client as\nan EIP-1193 transport, so they behave the same everywhere. Reads work with any\nprovider or RPC URL; writes need a signer or wallet-capable client.\n\nThe ethers adapters are the one place that transport is conditional. Only a\nJSON-RPC provider (`JsonRpcProvider`, `BrowserProvider`, or another\n`JsonRpcApiProvider`; in v5, a `JsonRpcProvider` subclass such as\n`Web3Provider`) implements the raw `send` those shape-heavy methods route\nthrough. A non-JSON-RPC provider (`FallbackProvider`, `getDefaultProvider()`)\nstill serves the typed reads (`call`, `estimateGas`, `getBalance`,\n`getBytecode`, `getChainId`, `getBlockNumber`) and, with a signer, writes; the\nshape-heavy methods throw a `HanaError` naming the provider class instead.\n\n### viem\n\n```sh\nnpm install viem\n```\n\n```ts\nimport { createHana } from \"@a16k/hana\";\nimport { ViemAdapter } from \"@a16k/hana/viem\";\nimport { createPublicClient, http } from \"viem\";\nimport { mainnet } from \"viem/chains\";\n\nconst client = createPublicClient({ chain: mainnet, transport: http() });\nconst hana = createHana({ adapter: new ViemAdapter({ client }) });\n```\n\n`ViemAdapter` also accepts `{ provider }` (any EIP-1193 provider) or\n`{ rpcUrl }`. For writes, pass a wallet client.\n\n### ethers v6\n\n```sh\nnpm install ethers\n```\n\n```ts\nimport { createHana } from \"@a16k/hana\";\nimport { EthersV6Adapter } from \"@a16k/hana/ethers\";\nimport { JsonRpcProvider } from \"ethers\";\n\nconst provider = new JsonRpcProvider(process.env.RPC_URL);\nconst hana = createHana({ adapter: new EthersV6Adapter({ provider }) });\n```\n\n`EthersV6Adapter` also accepts `{ rpcUrl }`. For writes, pass a `{ signer }`; its\nprovider is used for reads when no `provider` is given.\n\n### ethers v5\n\nethers v5 and v6 both publish as `ethers`, so install v5 under an alias and\nimport the adapter from `@a16k/hana/ethers5`:\n\n```sh\nnpm install ethers-v5@npm:ethers@^5.8.0\n```\n\n```ts\nimport { createHana } from \"@a16k/hana\";\nimport { EthersV5Adapter } from \"@a16k/hana/ethers5\";\nimport { providers } from \"ethers-v5\";\n\nconst provider = new providers.JsonRpcProvider(process.env.RPC_URL);\nconst hana = createHana({ adapter: new EthersV5Adapter({ provider }) });\n```\n\n`EthersV5Adapter` also accepts `{ signer }` or `{ rpcUrl }`. v5's `BigNumber`\nvalues are converted to `bigint` at the boundary, so the HANA API is unchanged.\n\n### web3.js v4\n\n```sh\nnpm install web3\n```\n\n```ts\nimport { createHana } from \"@a16k/hana\";\nimport { Web3Adapter } from \"@a16k/hana/web3\";\nimport { Web3 } from \"web3\";\n\nconst web3 = new Web3(process.env.RPC_URL);\nconst hana = createHana({ adapter: new Web3Adapter({ web3 }) });\n```\n\n`Web3Adapter` also accepts `{ provider }` or `{ rpcUrl }`. Note that web3.js is\nin maintenance (sunset by ChainSafe); this adapter targets legacy and migration\ninterop.\n\n### Disposing clients\n\nA client registers a `chainChanged` listener only when its adapter's `provider`\nexposes an EIP-1193 `on`, which in practice means the default adapter over an\nevent-emitting source such as `window.ethereum`. When that provider is\nlong-lived and shared, a dApp that recreates its client on wallet or route\nchanges accumulates listeners, and the provider retains every old client and its\ncache. Call `dispose()` on a client you are discarding:\n\n```ts\nconst hana = createHana();\n\n// ...later, when the client is no longer needed:\nhana.dispose();\n```\n\n`dispose()` removes only that client's own `chainChanged` listener, and it is a\nno-op when no listener was registered, so it is always safe to call.\n\nThe library adapters (`ViemAdapter`, `EthersV6Adapter`, `EthersV5Adapter`,\n`Web3Adapter`) hand the client their library's own client or a `request`-only\nshim, none of which expose `on`. Two consequences: those clients never need\n`dispose()`, and they do not observe a network switch, so a client kept across\none keeps serving the old chain id and the cache it namespaced under it, even if\nthe underlying `window.ethereum` emits `chainChanged`. Until this is addressed,\nrecreate the client when you switch chains behind one of those adapters:\n`hana.cache.clear()` is not enough, because the memoized chain id survives it.\n\n### Cache lifetime and reorgs\n\nThe cache has no reorg awareness and no time-based expiry. A read, call, balance,\ntransaction, block, or event query is cached under a key derived from its\narguments and served from then on; the default `LruStore` evicts only by capacity\n(least-recently-used), never by age. A client also never invalidates its own\nwrites. So a value read at the chain tip keeps being served indefinitely even if\nthat block is later orphaned by a reorg.\n\nOn reorg-prone chains, invalidate the affected entries yourself once you know a\nblock was reorged. Use `hana.cache.clear()` for a full reset, or the targeted\nmethods for a single kind of entry: `invalidateRead` / `invalidateReadsMatching`\n/ `clearReads`, `invalidateCall` / `invalidateCallsMatching` / `clearCalls`,\n`invalidateBalance` / `clearBalances`, `invalidateTransaction` /\n`clearTransactions`, and `invalidateBlock` / `clearBlocks`. Cached event queries\nhave no dedicated invalidator, so drop them with `hana.cache.clear()`.\n\n## Hooks\n\nHooks let you intercept and modify any adapter method call. They live on\n`hana.hooks` (and `client.hooks`), keyed by a `before:<method>` /\n`after:<method>` naming convention, one pair per method (`read`, `write`,\n`getBlock`, `getChainId`, `multicall`, `getEvents`, and so on). For a typed\nadapter, `hana.hooks.on` autocompletes the valid names.\n\n```ts\nimport { createHana } from \"@a16k/hana\";\n\nconst hana = createHana({ rpcUrl: process.env.RPC_URL });\n\n// `before:` hooks can inspect or replace the arguments.\nhana.hooks.on(\"before:read\", ({ args }) => {\n  console.log(\"reading\", args[0].fn);\n});\n\n// A `before:` hook can also short-circuit the call: `resolve` sets the return\n// value and the underlying method is not called.\nhana.hooks.on(\"before:read\", ({ resolve }) => {\n  resolve(0n);\n});\n\n// `after:` hooks see the resolved result and can override it.\nhana.hooks.on(\"after:getChainId\", ({ result, setResult }) => {\n  console.log(\"chain id\", result);\n  setResult(result);\n});\n```\n\nContracts do not have their own hook registry: use `contract.client.hooks`.\nClient-level method hooks (such as `before:read`) still fire for contract reads,\nbecause the interceptor wraps the whole client.\n\n### Semantics\n\n- **Order.** Multiple handlers per hook run sequentially in registration order.\n  Registering the same function twice runs it twice.\n- **Modifying args.** In a `before:` hook, `setArgs(...args)` replaces the entire\n  argument tuple (it is not a merge); omitted arguments become `undefined`.\n- **Short-circuiting.** `resolve(value)` in a `before:` hook skips the method and\n  returns `value`. First call wins: later `resolve` calls are ignored.\n- **Modifying results.** In an `after:` hook, `result` is the already-resolved\n  value (async methods are awaited before `after:` hooks run), and\n  `setResult(value)` overrides what the caller receives.\n- **Async.** Handlers may be async. Once any handler returns a promise, the rest\n  are chained after it, so async handlers run in sequence, never in parallel. A\n  single async hook promotes an otherwise synchronous call to a `Promise`, so\n  `await` the call to observe completion.\n- **Errors.** If the method throws or rejects, the error propagates to the caller\n  and `after:` hooks are skipped (there is no error channel). A handler that\n  throws aborts the remaining handlers for that hook.\n\n### Removing handlers\n\n```ts\nimport type { BeforeMethodHook } from \"@a16k/hana\";\n\n// A standalone handler is not contextually typed, so annotate it.\nconst onRead: BeforeMethodHook<typeof hana.read> = ({ args }) =>\n  console.log(args[0].fn);\nhana.hooks.on(\"before:read\", onRead);\nhana.hooks.off(\"before:read\", onRead); // returns true if a handler was removed\n\n// One-shot handlers remove themselves after firing once.\nhana.hooks.once(\"after:getChainId\", ({ result }) => console.log(\"chain\", result));\n```\n\n`off` matches by function identity, so keep a reference (an inline handler cannot\nbe removed). A `once` handler cannot be cancelled with `off(hook, originalFn)`\nbecause `once` registers an internal wrapper. Passing a non-function handler to\n`on` or `once` throws an `InvalidHookHandlerError`. Note that an unknown or\nmisspelled hook name (e.g. `before:reed`) is a silent no-op: it registers under a\ndead key and never fires. To type your own handlers, use the exported\n`BeforeMethodHook`, `AfterMethodHook`, and `MethodHooks` helpers.\n\n## React & Next.js\n\n`@a16k/hana/react` ships first-class hooks. They are a thin state layer: the\nclient's own cache, request deduplication, and multicall batching do the heavy\nlifting, so there is no second query-cache to configure. React (>= 16.8) is an\noptional peer dependency, needed only for this entry.\n\n```tsx\nimport { createHana } from \"@a16k/hana\";\nimport { HanaProvider, useRead, useWrite } from \"@a16k/hana/react\";\n\n// Create the client ONCE (module scope), never per render.\nconst hana = createHana({ rpcUrl: \"https://...\" });\n// In the browser with a wallet: createHana({ provider: window.ethereum })\n\nexport function App() {\n  return (\n    <HanaProvider hana={hana}>\n      <Balance owner=\"0x...\" />\n    </HanaProvider>\n  );\n}\n\nfunction Balance({ owner }: { owner: `0x${string}` }) {\n  const { data, isLoading, error, refetch } = useRead({\n    abi: erc20Abi,\n    address: \"0xA0b8...\",\n    fn: \"balanceOf\",          // fully typed from the abi\n    args: { owner },          // typed args; bigint-safe\n    refetchInterval: 15_000,  // optional polling\n  });\n\n  const { write, status, receipt } = useWrite();\n  const send = () =>\n    write({ abi: erc20Abi, address: \"0xA0b8...\", fn: \"transfer\",\n            args: { to: owner, amount: 1_000_000n } });\n\n  if (isLoading) return <span>Loading...</span>;\n  if (error) return <button onClick={refetch}>Retry</button>;\n  return <span>{data?.toString()} {status === \"mined\" && \"sent!\"}</span>;\n}\n```\n\nThe hooks are `useHana()` (the client from context), `useContract({ abi,\naddress })` (a memoized contract instance), `useRead(params)` (`data` /\n`isLoading` / `isFetching` / `error` / `refetch`, with `enabled` and\n`refetchInterval` options), and `useWrite()` (`write` / `status` / `hash` /\n`receipt` / `reset`, tracking `idle -> pending -> submitted -> mined`).\n\nNotes for Next.js: the shipped entry carries the `\"use client\"` directive, so\nimporting the hooks from a client component Just Works with the App Router. On\nthe server (route handlers, server components), use the plain client from\n`@a16k/hana` as a module singleton; its cache is shared across requests.\nReads mounted in the same render tick are batched into one multicall, and a\nMetaMask network switch (`chainChanged`) clears the cache automatically.\n\n## Errors\n\nEverything HANA throws is a `HanaError` (or a subclass, like\n`BlockNotFoundError`). Detect them with the exported `isHanaError` guard rather\nthan `instanceof`:\n\n```ts\nimport { isHanaError } from \"@a16k/hana\";\n\ntry {\n  await hana.getBlock();\n} catch (error) {\n  if (isHanaError(error)) console.error(error.kind, error.message);\n}\n```\n\nPrefer `isHanaError` over `instanceof HanaError` for cross-module checks. In\nthe shipped package both builds share one `HanaError` class across the subpath\nentries (`.`, `./testing`, `./viem`, ...), so `instanceof` works there today,\nbut that is an artifact of the current build layout, not a guarantee: a\nconsumer that mixes `require` and `import` of the package (the dual-package\nhazard), or a bundler configuration that duplicates the chunk, ends up with two\ndistinct classes, and an error thrown by one is not an `instanceof` the other.\n\n```ts\nconst { HanaError, isHanaError } = require(\"@a16k/hana\");\nconst { MissingStubError } = require(\"@a16k/hana/testing\");\n\n// Reliable everywhere: matches a `Symbol.for` brand shared by every copy of\n// the class, across entries, formats, and duplicated bundles.\nisHanaError(new MissingStubError({ method: \"read\" })); // true\n```\n\nTo branch on a specific error, switch on the `kind` discriminant: unlike `name`,\nit is a fixed string literal that callers cannot override and minifiers cannot\nmangle. Keep a `default` branch, since subclasses can add their own `kind`.\n\n## Testing\n\nThe `@a16k/hana/testing` entry provides mock clients and adapters for unit tests\nwithout a network:\n\n```ts\nimport { erc20 } from \"@a16k/hana\";\nimport { createMockHana } from \"@a16k/hana/testing\";\n\nconst hana = createMockHana();\nhana.onRead({ abi: erc20.abi, fn: \"symbol\" }).resolves(\"TEST\");\n\nconst symbol = await hana.read({ abi: erc20.abi, address: \"0x...\", fn: \"symbol\" });\n// \"TEST\"\n```\n\nThe mocks are built on [Sinon](https://sinonjs.org), which is a peer dependency\nof the `/testing` entry only (the main entry needs no test tooling). Install it\nalongside HANA in projects that use the mocks:\n\n```sh\nnpm install --save-dev sinon @types/sinon\n```\n\n## License\n\nHANA is licensed under the [Apache License 2.0](./LICENSE).\n\nHANA is a fork of [Drift](https://github.com/ryangoree/drift) by Ryan Goree,\nalso licensed under Apache 2.0. See [`NOTICE`](./NOTICE) for attribution.\n","readmeFilename":"README.md"}