{"id":15505313,"url":"https://github.com/hasparus/typechain-usedapp","last_synced_at":"2025-07-31T17:05:25.777Z","repository":{"id":72292007,"uuid":"420087970","full_name":"hasparus/typechain-usedapp","owner":"hasparus","description":null,"archived":false,"fork":false,"pushed_at":"2021-10-25T15:46:57.000Z","size":126,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-19T05:18:29.249Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hasparus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-22T12:20:54.000Z","updated_at":"2023-03-07T05:33:12.000Z","dependencies_parsed_at":"2023-03-08T19:00:29.482Z","dependency_job_id":null,"html_url":"https://github.com/hasparus/typechain-usedapp","commit_stats":{"total_commits":4,"total_committers":1,"mean_commits":4.0,"dds":0.0,"last_synced_commit":"887d604eae6211133c7be6db6e3b3f0d1f3b3b9a"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hasparus/typechain-usedapp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasparus%2Ftypechain-usedapp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasparus%2Ftypechain-usedapp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasparus%2Ftypechain-usedapp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasparus%2Ftypechain-usedapp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hasparus","download_url":"https://codeload.github.com/hasparus/typechain-usedapp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasparus%2Ftypechain-usedapp/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268074227,"owners_count":24191522,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-31T02:00:08.723Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T09:22:34.718Z","updated_at":"2025-07-31T17:05:25.755Z","avatar_url":"https://github.com/hasparus.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1\u003ePossible stronger typing for Ethereum React Hooks\u003c/h1\u003e\n\n- [Problem](#problem)\n  - [`useContractCalls` examples](#usecontractcalls-examples)\n    - [**reading token allowance**](#reading-token-allowance)\n    - [**reading Uniswap pool immutables**](#reading-uniswap-pool-immutables)\n- [Nonsolutions](#nonsolutions)\n- [Possible Solutions](#possible-solutions)\n  - [1. _Zero Runtime_: Stronger types for existing useDApp hooks](#1-zero-runtime-stronger-types-for-existing-usedapp-hooks)\n  - [2. _Zero Codegen_: Ethers.js Contract instance as first argument](#2-zero-codegen-ethersjs-contract-instance-as-first-argument)\n  - [3. Calls as dictionary](#3-calls-as-dictionary)\n  - [4. Lots of codegen](#4-lots-of-codegen)\n    - [**Usage**](#usage)\n    - [**Potential generated code**](#potential-generated-code)\n- [How to run this project?](#how-to-run-this-project)\n\n## Problem\n\n`useDapp` is written in TypeScript, but it doesn't have any information about\ntypes of your contract\n\n`useContractCalls`, useDApps basic function for reading on-chain state, has the\nfollowing signature.\n\n```ts\ninterface ContractCall {\n  abi: Interface;\n  address: string;\n  method: string;\n  args: any[];\n}\n\ndeclare function useContractCalls(\n  calls: Array\u003cContractCall | Falsy\u003e\n): Array\u003cany[] | undefined\u003e;\n```\n\nIt is obviously _powerful_ and allows us to query multiple contracts with one\nhook, batching our calls, but it is not very _convenient_.\n\n\u003cdetails\u003e\n\u003csummary\u003eExpand to see example usage of `useContractCalls`\u003c/summary\u003e\n\n### `useContractCalls` examples\n\n#### **[reading token allowance](https://usedapp.readthedocs.io/en/latest/guide.html#custom-hooks)**\n\n```ts\nfunction useTokenAllowance(\n  tokenAddress: string | Falsy,\n  ownerAddress: string | Falsy,\n  spenderAddress: string | Falsy\n) {\n  const [allowance] =\n    useContractCall(\n      ownerAddress \u0026\u0026\n        spenderAddress \u0026\u0026\n        tokenAddress \u0026\u0026 {\n          abi: ERC20Interface,\n          address: tokenAddress,\n          method: \"allowance\",\n          args: [ownerAddress, spenderAddress],\n        }\n    ) ?? [];\n\n  return allowance;\n}\n```\n\n#### **[reading Uniswap pool immutables](https://docs.uniswap.org/protocol/reference/core/interfaces/pool/IUniswapV3PoolImmutables)**\n\n```ts\nfunction usePoolImmutables(address: string) {\n  const contract = { abi: new Interface(PoolABI), address };\n\n  const [token0, token1, fee, tickSpacing, maxLiquidityPerTick] =\n    useContractCalls(\n      [\"token0\", \"token1\", \"fee\", \"tickSpacing\", \"maxLiquidityPerTick\"].map(\n        (method) =\u003e ({ ...contract, method, args: [] })\n      )\n    ) as any as\n      | [[string], [string], [number], [number], BigNumberish]\n      | undefined[];\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## Nonsolutions\n\nAlternatively to useDApp, we could typed ethers contracts generated by TypeChain\nand with `react-query` or another library used for widely used for data\nfetching.\n\n```ts\nimport { useQuery } from \"react-query\";\nimport { Auction__factory } from \"./typechain-generated\";\n\nconst { data, isLoading, error, status } = useQuery(\n  \"auction-calls\",\n  async () =\u003e {\n    const auction = Auction__factory.connect(address, signer);\n\n    const [reward, token, latestBid] = await Promise.all([\n      auction.reward(),\n      auction.token(),\n      auction.latestBid(),\n    ]);\n\n    return { reward, token, latestBid };\n  }\n);\n```\n\n**[See example on TypeScript Playground](https://www.typescriptlang.org/play?jsx=4\u0026ts=4.5.0-beta#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAORToCGAxjALQCOO+VAsAFC8nIAdk2ARBcAKIAPOuAA26AOrAYACwBK9Jtlx4AFAEpEvAJAB6M3EXo4DCMjkATFBji1GLdrrgB3Farh0NWZURwBrOBgIOC98OAAjOQgGMIZVOmBxTLg6SLwwdFQ6EhsfOjxTCzgAFVUbMHJE9EpgVAAaOHsoWzo5OVRfMSp4eJt4uhg09EcAOkrLTQ84HTjIPoGxUmS0DtRonxtHIfh0gDcbKLg5Vph0cRJoBKSUlDBHCcK5nhM7QVR4JDvGB0DqtAAyEDojkyAHMOrhyFBdsCYGgiHAALwuLAcfQAIjoyBEYmYDF6-TxHToqDwwjghkxAD5jN8fmJ-jkiTBROIsTCggBBLk8wymUxsv7wADatDKUEcHSiYTuHTkH3+ACFgI4ALqYnJlFRwAAK5BArXQM3Jeil4pMhOJghmsro8sMHTtDu5YhmSru7s9wp9atumu1otZOoMvHFtFRUHESBd8sVEGVglV6pgWuchFMhGjfG+ceQCbgQNyAH44AAeBroRkAKQAygB5AByM3+UFhwBI+grMxDhWz2o6ggccg6ACYDDWzPXmQAuOB4iFQ2EzLd43h5nhVZiHo-Hk+ns-nw8xnigSCwPIFFlwOBamHt5AgUZI3hPl9vj+4VpVDab9n2pdAAGExBgKAPGAngn0gwRoI8aoYL+DweTgp9W3OKAe0cQosLgcDyRwhFtUI3dSDNVcgjqKBUDxABuK8qgAcTuXAPmceICGqfIIPSTJeHQKRb3gAiGDVWhbDVVABiFR1AikW5BEcAYNTAxDkKYR9SCER1UBXBAQKfeJw1Mp8Em1AioBXbtYSIqy4FOXocBXX930-QCnKsiBcPwwpKxXMi8IogYADJEGoihgrgBzBBhOAAB8TTNC0awSmFmT3ZyDBXU0KAy7SYKYVC6HQx1GRYotnKkiAMD0Syn388iCNQOLQsCyLorIWL7Og2EUrSoqMEywbEpyyz8pG80xpKlC0KKKqassgjIFQFQ9FasL2riki+i68KZsKub0BrKVPP-KAdWqq9nOHMNHCa+DnM6ALwv20iPva6aCvSsbmrgKUso6K7PzB4BXy83A9SikzXre6zHFsgae0SmqkafVy5Hc58ob-T9Max7kQBHWQwA8gmYagYnnNyqy7tqqzk2enbuq+w6fsKE6AfOy7qeu27VsRyI039dnPpXA65CO9redG-msuFkDCBFsyLNF8yUdwNHHMsnG8fBgDUCAyzJb2kLuZ6pA+pAOKsuG06MqyqbXoVs6awWsqlowsQmZA+rGvN63OutuB4Zi+29cSp2+fG9HsqIECPeKqDSpgcrKu9QQA9e9aGq2i2gul762p5-7FZrY2oDzp9HtHZ6Q-LjrS65luU8rz3LJBia4Xx6HrshwfPzhvTnO11H4r7umrMN9AqZH3BZ6fUnyfARfCeXyyGbrtx0DlNnQ7b2XrdTsaa73v1BG24-iLL3aK9ml2+6ZvdeEk6SbF+Dl+RgRSc4rgZBiZkACeRMSAA)**\n\nBuilt-in polling would satisfy the need for refreshing new data after block\nchanges, but we would lose other features already implemented in useDapp. (e.g.\nWe'd need to reach for [`ethcall`](https://github.com/Destiner/ethcall) for\nmulticalls.)\n\n## Possible Solutions\n\n### 1. _Zero Runtime_: Stronger types for existing useDApp hooks\n\n_See the code in [**./src/zero-runtime.ts**](./src/zero-runtime.ts) or on\n**[TypeScript Playground](https://tsplay.dev/WK80Gw)**_\n\nWide types are definitely the biggest problem with `useContractCalls` for me, so\nlet's try writing new types for it, leveraging type info generated by TypeChain,\nbut without changing the runtime implementation.\n\n```ts\nimport { useContractCalls } from \"@usedapp/core\";\nimport { UseContractCalls } from \"./src/zero-runtime.ts\";\nimport { Dai, Dai__factory } from \"./src/typechain\";\n\nconst useDaiCalls = useContractCalls as UseContractCalls\u003cDai\u003e;\n\nconst daiInterface = Dai__factory.createInterface();\nconst daiAddress = \"0x6B175474E89094C44Da98b954EedeAC495271d0F\";\n\nexport function useDaiBalance() {\n  // const results: [[BigNumber] | undefined, [number] | undefined];\n  const [balance, decimals] = useDaiCalls([\n    {\n      abi: daiInterface,\n      address: daiAddress,\n      method: \"balanceOf\",\n      args: [\"0x2e465ddca6d2c6c81ce6f260ab13148d43e93371\"],\n    },\n    {\n      abi: daiInterface,\n      address: daiAddress,\n      method: \"decimals\",\n      args: [],\n    },\n  ]);\n\n  return (\n    balance?.[0] \u0026\u0026\n    decimals?.[0] \u0026\u0026\n    ethers.utils.formatUnits(balance[0], decimals[0])\n  );\n}\n```\n\nI'll admit this isn't exactly _zero_-runtime, as we need the following assertion\nto narrow `useContractCalls` type.\n\n```ts\nconst useDaiCalls = useContractCalls as UseContractCalls\u003cDai\u003e;\n```\n\nProblem is, we can't easily infer parameter types with existing signature. Even\nif we pass TypeChain's [`DaiInterface`](./src/typechain/Dai.d.ts) as `abi`\nproperty, `Parameters\u003cDaiInterface\u003e[\"encodeFunctionData\"]` returns only the\nparameters of the first overload.\n\n\u003c/details\u003e\n\n### 2. _Zero Codegen_: Ethers.js [Contract](https://docs.ethers.io/v5/api/contract/contract/) instance as first argument\n\nIf we allowed passing Contract instance with types generated by TypeChain as\nfirst argument, we could infer method names and arguments from it in a similar\nway to the above, and we could\n\n```ts\nimport { Dai__factory } from \"./src/typechain\";\n\nconst daiInstance = Dai__factory.connect(address, signer);\n\nuseContractCalls(daiInstance, [\n  {\n    method: \"balanceOf\",\n    args: [\"0x2e465ddca6d2c6c81ce6f260ab13148d43e93371\"],\n  },\n]);\n```\n\nThis solution could be possible implemented inside of useDapp as it doesn't\nrequire knowledge about user's contracts.\n\n### 3. Calls as dictionary\n\n_See the code in [**./src/call-as-dict.ts**](./src/calls-as-dict.ts) or on\n**[TypeScript Playground](https://tsplay.dev/mbdjdw)**_\n\nAssuming we'd never call the same contract method twice in one\n`useContractCalls` invocation, our hook can accept an object of\n`method -\u003e args`.\n\n```ts\nconst { balanceOf, decimals } = useDaiCalls({\n  balanceOf: [\"0x2e465ddca6d2c6c81ce6f260ab13148d43e93371\"],\n  decimals: [],\n});\n```\n\nWe can easily implement this by mapping over entries, passing them to\n`useContractCalls` and collecting received results in a new object.\n\n```ts\nconst useDaiCalls = (calls) =\u003e {\n  const methods = Object.keys(calls);\n\n  const results = useContractCalls(\n    methods.map((method) =\u003e ({\n      abi: daiInterface,\n      address: daiAddress,\n      method,\n      args: calls[method],\n    }))\n  );\n\n  return Object.fromEntries(results.map((result, i) =\u003e [methods[i], result]));\n};\n```\n\n### 4. Lots of codegen\n\nOn TypeChain's side, we could generate a new hook for each possible call.\n\n#### **Usage**\n\n```ts\nimport { useDaiBalanceOf } from './typechain-dist';\n\nconst App() {\n  const balance = formatUnits(useDaiBalanceOf(address), 18);\n}\n```\n\n#### **Potential generated code**\n\n```ts\nfunction useDaiBalanceOf(...args: [string | Falsy]): BigNumber | undefined {\n  return useContractCall({\n    abi: DAI_ABI,\n    addresss: DAI_ADDRESS,\n    method: \"balanceOf\",\n    args: args.some(isFalsy) ? undefined : args,\n  })?.[0];\n}\n```\n\n---\n\n## How to run this project?\n\nReading the types in VSCode should suffice, but to prove that the implementation\nworks and fiddle with it, you can run the development server.\n\n1. Install dependencies\n   ```sh\n   pnpm install\n   ```\n2. Start Hardhat Node\n   ```\n   pnpm hardhat:node\n   ```\n3. Run development server\n   ```\n   pnpm dev\n   ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhasparus%2Ftypechain-usedapp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhasparus%2Ftypechain-usedapp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhasparus%2Ftypechain-usedapp/lists"}